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

drawGraphicsData() works incorrectly in some cases #81

Open IngweLand opened 10 years ago

IngweLand commented 10 years ago
//Create some shape
var ss:flash.display.Shape = new flash.display.Shape();
            var com:Vector.<int> = new <int>[1,2,2,2];
            var points:Vector.<Number> = new <Number>[53.1,14.5,90,70.5,25,70.5,53.1,14.5];
            ss.graphics.beginFill(0xffffff);
            ss.graphics.drawPath(com, points);
            ss.graphics.endFill();
            com = new <int>[1,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2];
            points = new <Number>[90.4,70.2,53.5,14.2,53.05,13.5,52.65,14.3,24.55,70.3,24.2,71,25,71,90,71,90.95,71,90.4,70.2,52.7,14.8,89.6,70.8,90,70,25,70,25.45,70.7,53.55,14.7,52.7,14.8];
            ss.graphics.beginFill(0);
            ss.graphics.drawPath(com, points);
            ss.graphics.endFill();

//read data
            var v:Vector.<IGraphicsData> = ss.graphics.readGraphicsData();

//draw with Native shape
            var sss:flash.display.Shape = new flash.display.Shape();
            sss.graphics.drawGraphicsData(v);
            Starling.current.nativeStage.addChild(sss);

            //draw with Starling shape
            var s:ShapeEx = new ShapeEx();
            s.x = 200;
            s.graphics.drawGraphicsData(v);
            addChild(s);
IonSwitz commented 10 years ago

Thanks for trying out this brand new API. There are a few bugs, and some scenarios that doesn't work correctly.

I have noticed, for example, that a circle generated by flash.display.graphics.drawCircle comes back as a bunch of curves, which isn't drawn correctly in the Starling Extension,

So, yes, I will look into your example (thank you, @IngweLand , for providing a good reproduction case, those are invaluable), and I will also try to continue to work on supporting a larger range of functionality.

EDIT: I notice that if I comment out the second part of the shape, ie stop after the first "endFill", the triangle gets drawn correctly in the Starling shape.

EDIT2: If I boost all values with a factor of 10, it becomes easier to see what is going on:

var ss:flash.display.Shape = new flash.display.Shape();
        var com:Vector.<int> = new <int>[1,2,2,2];
        var points:Vector.<Number> = new <Number>[531,145,900,705,250,705,531,145];
        ss.graphics.beginFill(0xffffff);
        ss.graphics.drawPath(com, points);
        ss.graphics.endFill();
        com = new <int>[1,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2];
        points = new <Number>[904,702,535,142,530.5,135,526.5,143,245.5,703,242,710,250,710,900,710,909.5,710,904,702,527,148,896,708,900,700,250,700,254.5,707,535.5,147,527,148];
        ss.graphics.beginFill(0xFF00FF);
        ss.graphics.drawPath(com, points);
        ss.graphics.endFill();

From what it seems like, the fill command struggles in this scenario. Interesting...

EDIT3: Also, yes, the winding of drawPath isn't supported either, so even if the drawPath was changed to add GraphicsPathWinding.NON_ZERO at the end (to get rid of the "inner" triangles in each "vertex" of the larger triangle ) the Starling shape is still bogus.

EDIT4: (Doing a bit of running commentary here)

It seems that there is a problem with the fill algorithm, maybe? I have added a "lineStyle" to the code here, to verify the lines that are drawn, and maybe the fill algorithm runs into some issues here. (the triangle to the left is the correct flash version, the one on the right is the Starling Graphics version)

image

I will convert the "linePath" to regular moveTo/lineTo tomorrow and see what we can find...

IonSwitz commented 10 years ago

Yes, it is a problem with the Fill algorithm, not the new drawGraphicsData() API in itself.

The same problem can be observed by using moveTo/lineTo:

        var s2:starling.display.Shape = new starling.display.Shape();
        s2.graphics.lineStyle(1, 0x00FFFF, 1.0);
        s2.graphics.beginFill(0xFF00FF, 0.5);
        s2.graphics.moveTo(904, 702.0); 
        s2.graphics.lineTo(535, 142.0); 
        s2.graphics.lineTo(530.5, 135.0); 
        s2.graphics.lineTo(526.5, 143.0);
        s2.graphics.lineTo( 245.5, 703.0);
        s2.graphics.lineTo(242.0, 710.0);
        s2.graphics.lineTo(250.0, 710.0);
        s2.graphics.lineTo(900.0, 710.0);
        s2.graphics.lineTo(909.5, 710.0);
        s2.graphics.lineTo(904.0, 702.0);
        s2.graphics.moveTo(527.0, 148.0);
        s2.graphics.lineTo(896.0, 708.0);
        s2.graphics.lineTo(900.0, 700.0);
        s2.graphics.lineTo(250.0, 700.0);
        s2.graphics.lineTo(254.5, 707.0);
        s2.graphics.lineTo(535.5, 147.0);
        s2.graphics.lineTo(527.0, 148.0);
        s2.graphics.endFill();
        addChild(s2);

The first point, the "moveTo"-point is the lower right corner of the large triangle, and the second point, the "lineTo", is the one that is almost to the top of the triangle, where the fill starts on top. The initial vertex point of the fill, the one that is added as "moveTo", doesn't get added to "_currentFill" in the Graphics class.

Maybe @jonathanrpace could shed more light on this, if he has any time to spare. :)

In the meantime, I will look at it.

EDIT:

There are two problems, as far as I can see: 1 - get the first vertex in a shape that starts with a moveTo to be included in the fill. 2 - make the fill aware of "inside" the shape and "outside" the shape. The correct fill in the image above should be a hollow triangle, as the one to the left is, but instead the inner part of the triangle gets filled twice. There's some sort of winding issue going on here as well, I would guess.

For 1, I have a solution that seems to work, in Graphics moveTo, adding: if ( _fillStyleSet ) { if ( _currentFill == null ) { // Added to make sure that the first vertex in a shape gets added to the fill as well. createFill(); _currentFill.addVertex(x, y); } else _currentFill.addDegenerates( x, y ); }

seems to solve problem 1.

After this fix, filling a triangle by a moveTo(vert1), lineTo(vert2), lineTo(vert3) works fine, while before it didn't give a fill.

However, problem 2 is harder for me, I haven't worked much with triangulation of fills before.

@jonathanrpace , I believe this is your area of expertise. :)

IonSwitz commented 10 years ago

I have added the fix for problem 1 above here to the master

IngweLand commented 10 years ago

Any progress on fixing this issue? A real world example of when this is important is complex shapes like text, or similar complex figures. This was the only reason why I did not start using this extension in my app (I have a complex svg image, which has to be drawn in Starling. And it perfectly worked almost in all cases, excluding text and some shapes. Eventually, I have to create bitmaps from this svg. But your extension will definitely rock).

IonSwitz commented 10 years ago

Unfortunately not. I have been unable to figure out a way to solve this Fill issue. I will make a new attempt soon, but no clever insights have emerged yet, unfortunately. I will definitely make this a priority now, though.

The SVG drawing code sounds absolutely amazing, by the way. Is that something you would be able to share, or is it a closed source implementation?

IngweLand commented 10 years ago

I can not share the code, as it is a closed project. However, actually, everything is pretty easy. SVGWeb lib is used to convert svg xml to flash display list sprite. Then you are open to what you want to do. In case of your extension - I just read graphics data from that created sprite and draw it with ShapeEx, which sounds like really powerful thing. There is another svg to flash lib (https://github.com/LucasLorentz/AS3SVGRenderer), easier to work with - but slower.

IonSwitz commented 10 years ago

Ok, thanks for that.

I will investigate this issue again, but I worry it might be tricky.

IngweLand commented 10 years ago

Do you know the exact reason (but do not know how to fix it), or finding this reason is the tricky part?

jonathanrpace commented 10 years ago

The problem lies squarely within the Fill class's triangulator.

The algorithm at the heart of it is a fairly simple ear-clipping one. I imagine there are all sorts of corner cases the algorithm fails on, but the big omission is lack of support for 'holes' - ie you can't draw a shape within a shape and expect it to 'cut out' a hole. In its current state, this pretty much rules out any hopes of being able to throw any old SVG data at it.

Adding this sort of behavior isn't trivial, and may need to completely dispense with the simple ear-clipping algorithm. To be honest I'm holding out for someone with a knack for this to come along and give it a go. The last time I tried re-writing it I ran out of tears.

IonSwitz commented 10 years ago

What Jonathan said ;)

I have been searching for ways to make this work as it does in Flash, but..No such luck. I'm sure it is doable, I just haven't had the army of epiphanies needed to solve this.

makc commented 10 years ago

This thing can do holes, but it has it's own problems (such as collinear edges or vertices with same y-s).

IonSwitz commented 10 years ago

And: "Interior holes must not touch other holes, nor touch the polyline boundary", so it would fail on the triangle case above.