StarlingGraphics / Starling-Extension-Graphics

flash.display.Graphics style extension for the Starling Flash GPU rendering framework
https://github.com/StarlingGraphics/Starling-Extension-Graphics/wiki
MIT License
285 stars 88 forks source link

Stroke hitTest issue #67

Closed AlBirdie closed 10 years ago

AlBirdie commented 10 years ago

As described in the forum post; http://forum.starling-framework.org/topic/hittest-starlingdisplayshape

I've made a little test case to illustrate the issue. Hopefully it helps to analyse the issue.

import flash.geom.Point;
import flash.geom.Rectangle;

import starling.display.Sprite;
import starling.display.graphics.Stroke;
import starling.events.Touch;
import starling.events.TouchEvent;

public class StrokeHitTest extends Sprite
{
    private var _strokeContainer:Sprite;
    private var _stroke:Stroke;

    public function StrokeHitTest()
    {
        super();

        _strokeContainer = new Sprite();
        addChild(_strokeContainer);
        _strokeContainer.clipRect = new Rectangle(0, 0, 500, 500);
        _strokeContainer.addEventListener(TouchEvent.TOUCH, strokeContainer_touchHandler);
    }

    public function drawStroke():void {
        _stroke = new Stroke();
        _stroke.material.color = 0xff0000;
        _stroke.name = "red";
        _stroke.addVertex(50,50, 1);
        _stroke.addVertex(350, 350, 1);
        _strokeContainer.addChild(_stroke);

        _stroke = new Stroke();
        _stroke.material.color = 0xffffff;
        _stroke.name = "white";
        _stroke.addVertex(20,50, 1);
        _stroke.addVertex(350, 200, 1);
        _strokeContainer.addChild(_stroke);

        _stroke = new Stroke();
        _stroke.material.color = 0x00ff00;
        _stroke.name = "green";
        _stroke.addVertex(350,50, 1);
        _stroke.addVertex(0, 350, 1);
        _strokeContainer.addChild(_stroke);

        _stroke = new Stroke();
        _stroke.material.color = 0x0000ff;
        _stroke.name = "blue";
        _stroke.addVertex(400,400, 1);
        _stroke.addVertex(250, 10, 1);
        _strokeContainer.addChild(_stroke);

        // add other lines here
    }

    private function strokeContainer_touchHandler(event:TouchEvent):void {
        var _touch:Touch = event.getTouch(this);

        if(!_touch)
            return;

        var _globalPoint:Point = new Point(_touch.globalX, _touch.globalY);
        var _localPoint:Point = this.globalToLocal(_globalPoint, _localPoint);

        trace(_strokeContainer.hitTest(_localPoint, true).name);

        // this actually doesn't work properly for all strokes
        // I haven't yet figured out a pattern here.
        // Besides, the hitTest is largely off for non vertical/horizontal strokes 
    }
}
IonSwitz commented 10 years ago

Thank you, I will check on this ASAP.

AlBirdie commented 10 years ago

Thanks mate. Looking forward to that!

IonSwitz commented 10 years ago

Ok, now I am at home and have had some time to look into this.

The problem we are facing is that the getBounds of the Graphic class returns the Axis Aligned Bounding Box (AABB) for the object, ( ie the Rectangle created by the startpoint x and y, to the endpoint x and y) for all the classes, regardless of what is in there, even if it is just one diagonal line.

So, the functionality works if you make boxes. ;)

There is an embryo of a structure for proper hit tests, in the method shapeHitTest , but currently it is only the Fill class that implements this and looks at actual geometry data for the hit test.

Adding proper hit tests would take some time. Sorry for not being able to deliver a proper solution off the bat.

IonSwitz commented 10 years ago

Then again, I can never resist a challenge. So, yes, I have added this functionality to the Stroke and Fill (others classes inheriting from Graphic still do bounding box hit tests)

if you set the parameter precisionHitTest = true in a Graphic object (it will do hit calculations based on the path of the stroke.

So far, only checked in the code to my IonSwitz master branch here. If you are savvy enough, you can try it there, otherwise you will have to wait a bit more :)

IonSwitz commented 10 years ago

I've now also added precisionHitTest to the Graphics class, so that geometry created by this code (Strokes and Fills incidentally) work with Geometry level precision.

Still only available in the IonSwitz master fork

@AlBirdie If you use my fork, you can also play with the new GraphicsPath and GraphicsData code.

Generating two stars like this ( change the texture to something you have locally)

var v:Vector. = new Vector.(); v.push(new GraphicsLine(1, 0xFFFFFF, 0.4)); v.push(new GraphicsTextureFill(Texture.fromBitmap( new GlowTiledBMP(), false ))); v.push(new GraphicsPath(Vector.([1, 2, 2, 2, 2, 2]), Vector.([66, 10, 23, 127, 122, 50, 10, 50, 109, 127, 66, 10]))); v.push(new GraphicsEndFill()); v.push(new GraphicsTextureFill(Texture.fromBitmap( new GlowTiledBMP(), false ))); v.push(new GraphicsPath(Vector.([1, 2, 2, 2, 2, 2]), Vector.([366, 10, 323, 127, 422, 50, 310, 50, 409, 127, 366, 10]))); v.push(new GraphicsEndFill());

        var shape:Shape = new Shape();
        shape.graphics.precisionHitTest = true;
        shape.graphics.drawGraphicsData(v);
        _strokeContainer.addChild(shape);

and you can see the precisionHitTest in action :)

AlBirdie commented 10 years ago

Wow, I never expected this to be implemented that quick! Can't thank you enough IonSwitz! I'll fork your branch first thing tomorrow morning to check it out. I'll be back. ;)

IonSwitz commented 10 years ago

Very cool, looking forward to your input.

One word of warning, though: If you scale your shapeContainer, you're on your own. ;)

I just tested to set scaleX = 2.0 , and everything just broke to all hell. So .. Don't. ;)

AlBirdie commented 10 years ago

No worries, I'll just be using this for lines and arches, no scaling involved.

It is really nice to see that this great project re-gained some momentum. It it wasn't for this, our app would have never been ported to Starling. We're using it to draw fully interactive charts and it has been nothing but a pleasure thus far.

AlBirdie commented 10 years ago

Awesome work IonSwitz! That works like a charm. Thanks again for your effort. I'll leave it up to you to close the issue once you've merged this into the Master branch if that's ok.

AlBirdie commented 10 years ago

Again, this is so cool. Just implemented it into our chart. However, I was wondering if it would be possible to introduce a hitArea to the Graphics object. Though not necessary for larger objects, if you want to hit a 1 or 2px line on a mobile, you better be good at pointing precisely. ;) But that would just be the icing on the cake.

IonSwitz commented 10 years ago

Well, first off, thank you for the praise. This kind of stuff is fun ;)

Could you describe a use case for the hitArea wish? How would you want to use it? I could probably create some kind of presicionHitDistance:Number setting, that would increase the distance from the line that would count as a hit. That would probably be simple to add and it would allow for increased ability to touch 1px lines on your mobile :)

How would you want the hitArea to work?

AlBirdie commented 10 years ago

I reckon precisionHitDistance the way you've described it is already a pretty sound solution to the issue. Streamlined and no further objects involved to measure the hitArea (thinking of Rectangles like it is done in Feathers (works only for rectangular objects, so not an option for this anyway)). Like it!

IonSwitz commented 10 years ago

Ok, the precisionHitDistance code has been added, and the IonSwitz fork has been merged to the master properly.

The distance works as follows:The value of the precisionHitDistance (default 0 ) is added to the distance test where I check if the line's thickness is enough to reach to touched point. So the value acts as a padding to the line thickness, if that makes sense?

If you have a 1 px thick line and you set a precisionHitDistance of 8, you need to pick within 9 pixels of the line to hit it. At least, that's the intention. Ofcourse, there might be bugs :)

So, all the new code is now available on the StarlingGraphics/master fork.

However, since I am a complete noob when it comes to generating SWC files, the SWC has not been built. This is beginning to feel like a severe problem, and I really don't know what to do about it. I simply don't have the skills and the confidence to build a SWC ( I get nowhere when I try to use Flash Builder to do this. I simply don't know what I am doing)

In exchange for this hitTest code, do you know how to build the SWCs, AlBirdie? Could you either talk me through it, or just do it, and Email me the generated SWC or something?

There is some kind of Flash Builder library project included as part of this extension, but I can't make heads or tails of it.

AlBirdie commented 10 years ago

Makes perfect sense! Thank you very much. I'll test that right away and will pass the whole precisionHitTest to our QA in the next weeks once I've finished implementing the new feature we needed this for.

Exchange conditions accepted. ;) Happy to help you out. Since creating the SWC is pretty straightforward in Flash Builder I can talk you through it (shouldn't take to long), or just send you the SWC. Mail would be fine, not sure if a pull request would work (I guess not), haven't found out how to directly upload a file to a repo yet. Your choice, I'm fine with any of those ways.

AlBirdie commented 10 years ago

Oh and FYI, that precisionHitDistance works brilliantly! At least my mediocre testing skills confirm that it works very well. Love the new features!

IonSwitz commented 10 years ago

Very cool! Glad you like it!

The best way would - ofcourse - be for me to learn the damn SWC process, but I have very limited time. If you wanna do the build, and make sure to use the latest Starling (1.4.1) and AIR 3.9 ( I guess? ) , build with Flash Builder 4.7 (which I think is required) and Email me the SWC at %%deleted%% , that would be fantastic.

If you could do one "debug" and one "Release" build, that would be fantastic too. (Not sure how SWCs work, but I assume one can do separate compiles?)

Let me know when you have the Email address, I will remove it from this post once you verify that you have it :)

AlBirdie commented 10 years ago

Got the address, thanks. Actually, I haven't build a SWC yet either. But I've had a quick read through the web and it seems as if Flash Builder pretty much builds it automatically (if you enabled the option, otherwise project -> build will do) and puts it in the project's bin directory. Since it is only a library project, there is no release or debug option, that is done in the main project that includes the executable(s).

IonSwitz commented 10 years ago

Ok, cool. Thank you so much.

IonSwitz commented 10 years ago

Thanks to the awesome AlBirdie, the new SWC is now in place! :)

AlBirdie commented 10 years ago

Thanks again for the good work. Solved -> closing.