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
282 stars 89 forks source link

Stroke has incorrect geometry when the thickness is very large #132

Open alexeevilya opened 9 years ago

alexeevilya commented 9 years ago

Hi, guys! I've got a problem when trying to scale the stroke, keeping the thickness. When I change the stroke thickness to some large value, it draws incorrectly.

Please, look at the picture: img

Here is project src: https://www.dropbox.com/s/om7v84i2iibpp53/pathTest.zip?dl=0

Thanks!

alexeevilya commented 9 years ago

Another artifact: img I think it has the same cause.

IonSwitz commented 9 years ago

Yeah, setting the thickness to a large value ( you dont have to scale the stroke to make it appear) can make the joints degenerate. It is an unfortunate side effect of the math code. The rectangles that are created to draw the lines become overlapped and it looks very bad.

You can try to solve the same problem by drawing 2 quadrilaterals on a piece of paper to fit every type of joint in every angle. It rapidly becomes very hard to make the joint look good without overdraw or disfigured shapes as a result :)

alexeevilya commented 9 years ago

Ion, thanx, but are you sure that nothing can be done? (until a certain scale, everything looks great). For my app it is a catastrophic problem (

IonSwitz commented 9 years ago

What is the actual problem you are trying to solve?

Is it the scaling? Or is it when you need to draw lines with many control points?

If it is the scaling, you can do the scaling math on the vertices of the Stroke yourself, before you add them as vertices to the Stroke, and keep the thickness constant (and small) so the artifacts aren't as visible.

In the second example, what is the goal there? If you wish to draw smooth curves, check out the ShapeEx.graphics.NaturalCubicSplineTo method

IonSwitz commented 9 years ago

Well, I have to agree that I do find something odd that goes beyond what should be expected when the line turns on itself with large thickness... There is definitely something wrong. I will try to look into it, even though I doubt I will find the bug tonight.

IonSwitz commented 9 years ago

Could you please test with the following additional code:

                if ( elbowThickness > d0 )
                    elbowThickness = d0;
                if ( elbowThickness > d1 )
                    elbowThickness = d1;

Add that to the Stroke class, around line 353, as the last four lines in the "if ( treatAsRegular )" statement.

I know this is probably cumbersome for you to test, but I would like to see the effect of this on the problems you are seeing. I don't want to submit it as a fix, but it seems to make some of my test cases look better (if not perfect)

Just let me know what happens, ok?

alexeevilya commented 9 years ago

Ion, Thank you very much for your care and help to solve this problem! Unfortunately, that kills one problem, but born another. There's a new artifact: img1 In the real application, it looks like this, before: img3 after: img2

IonSwitz commented 9 years ago

Ah, ok. Can I ask you how many points you have per stroke in your "real" application? It looks like a program to map sailing boats, perhaps?

The original problem occurs when the thickness of a joint gets thicker than the distance of the line segments, and the fix tried to correct that. It seems to me that you have a very large amount of data points in your "real" application, making each line segment between two vertices pretty small compared to the thickness?

IF that is the case, the thickness will rapidly get thicker than each line segment, and what I would do in that case would be to "clean" the input data, removing points that are (more or less) on the same line, or points that create "dents" in the line.

IonSwitz commented 9 years ago

If you try this instead: if ( elbowThickness > d0 ) elbowThickness = vThickness; if ( elbowThickness > d1 ) elbowThickness = vThickness;

could you let me know the difference?

alexeevilya commented 9 years ago

Ion, yes, it is a sailing boats map application. As for the number of vertices, then I use simplify algorithm, and there is a one vertex for the one really map point after simplifying. Sorry for my English, one moment ... ;)

IonSwitz commented 9 years ago

Don't worry about your English, I understand you fine. I am sorry I can't help you very well.

alexeevilya commented 9 years ago

Thank you, now I'm testing the new changes by you.

alexeevilya commented 9 years ago

img4 Oh my God! ;)

alexeevilya commented 9 years ago

By the way, my other sandbox to test an application is project, that reference is attached above. https://www.dropbox.com/s/om7v84i2iibpp53/pathTest.zip?dl=0

IonSwitz commented 9 years ago

The lines get very thin in some places, but the bad joints look better.. Hmmm..

alexeevilya commented 9 years ago

Please, look at the dropbox example... img5

IonSwitz commented 9 years ago

I am away from my Flash dev environment today, so I cant test any code. Sorry.

alexeevilya commented 9 years ago

Oh, no problem. But as you can see, the line in dropbox example is very thin, although there is only four vertexes in that.

alexeevilya commented 9 years ago

Please, look at this example - https://www.dropbox.com/s/wr1cu8ynz2axioa/PathTest.swf?dl=0 You need to scroll by the wheel.

alexeevilya commented 9 years ago

In short, the problem occurs when the thickness of a joint gets thicker than the distance of the line segments. Unfortunately, the fix does not correct that problem.

IonSwitz commented 9 years ago

Yeah, I figured it doesnt solve it. And I wrote that earlier: "The original problem occurs when the thickness of a joint gets thicker than the distance of the line segments" :) Unfortunately, I can't do any work on this for a few days (Going to Berlin for the Quo Vadis game conference)

alexeevilya commented 9 years ago

All clear. However, I will be very grateful to you, Ion, if you can find time to fix the problem after coming back.

IonSwitz commented 9 years ago

I will try. In the mean while, if you wish to try something different, you can check out the FastStroke class. It doesnt make such nice joints between line segments, as it just creates rectangles for the lines, but it might help you if the joints look too bad. It should work very similar to the Stroke class.

However, it is much less used and might not work very well. It was one of my experiments at one point

Also, of course, that will not support the "postProcess" method

alexeevilya commented 9 years ago

Thank you, Ion! That looks nice, but:

  1. It has same trouble with premultiplied alpha, as Stroke (issue #131);
  2. NOTE! This reduces the FPS to 45-50 frames!!! It looks, like SlowStroke, unfortunatelly ;((

..and what capacity is?Is it the number of lineTo()/moveTo() calls? Or lineTo() calls * 2?

alexeevilya commented 9 years ago

Number one is fixed by change line 307 at FastStroke class:

RenderSupport.setBlendFactors(_material.premultipliedAlpha, this.blendMode == BlendMode.AUTO ? renderSupport.blendMode : this.blendMode);

but fps is bad (the fan on my laptop is working like crazy now)

IonSwitz commented 9 years ago

Ok, thank you for trying that out at least.

alexeevilya commented 9 years ago

Hmmm.. Maybe I'm doing something wrong again? If you call it fast, then is it probably really fast? ;)

IonSwitz commented 9 years ago

I was hoping it would be faster in certain scenarios but it turned out it wasn't a whole lot better than the regular Stroke, except for some very specific cases. As I said, it was an experiment. Thanks for trying it out.

alexeevilya commented 9 years ago

Don"t mention it! Thank YOU! By the way, I found comparison of FastStroke, Stroke and 'StrokeGraphics' in #92 thread. What 'StrokeGraphics' is? Should I try it?

IonSwitz commented 9 years ago

The "StrokeGraphics" mentioned is the Stroke you get when using the Shape.graphics API

So, no, it's not a separate class.

alexeevilya commented 9 years ago

Hi Ion! Have you any news about our problem? Do you try to fix that?

IonSwitz commented 9 years ago

No, sorry, I have no new ideas for a solution to this problem, except making a dynamic cleaning of the in-data points depending on the zoom value. When the view covers a large area of the map, fewer points should be needed in order to present a good line, and it should be possible to clean them up a bit in those scenarios

alexeevilya commented 9 years ago

Thank you, IonSwitz! Yea, dynamic cleaning of the in-data points is a way to fix the problem and many cartografical tools uses simplification algorithms for that, but its reduce the performance itself (especially if uses in every frame actions such as follow-zoom animation) ;(

IonSwitz commented 9 years ago

I have submitted some new code that will help you when scaling a Stroke.

when you scale the stroke, do this to maintain screen-relative stroke thickness:

this.scale = scaleValue; stroke.scaleGeometry( scaleValue); // new method on Stroke class

Note: Combining this with adding new points to the Stroke during the same frame is a bit nasty, and should be avoided. If you generate your full Stroke once, and then just use this for scaling, it should work fine, and be relatively fast. Currently, it's probably a good idea to set the "scaleGeometry" to 1.0 before calling "lineTo" on the Stroke.

So, instead of having to re-add all stroke points ( "lineTo" ) to the Stroke when scaling, you can just do this extra call. It should be a lot faster than re-adding all stroke points, but it is still some math that has to be done. Please try it, if you are still using this functionality.

Also, I have added some fixes that improves the quality of drawing thick lines, so your sharp turns should look a lot better.

Also, as an experiment, I have added a method on Stroke allowing a "cullDistance". Basically, when a new point is added to the Stroke, it can be ignored if the cullDistance is higher than the distance from the last point. This is situationally good, I believe, but might not help you much, since you already do some "cleaning" of the point data sets

alexeevilya commented 9 years ago

Thank you, IonSwitz!

Now I use the FastStroke in my app that gives me a satisfactory quality of visualization and performance. But it very good news! I'll try it and post here my experience soon. Thank you very much! ;)

alexeevilya commented 9 years ago

IonSwitz, sorry that took so long to reply! I tried your new solution and want to tell you that it's very clean and good idea to transform existing geometry instead of create new. However, the problem with the artifacts in the Stroke class still remained in some cases, so I use the FastStroke class. I've added to it your adjustThicknessOfGeometry function and now it works faster in zoom animations. As for cullDistance functionality it would be nice if it were somehow integrated into the adjustThicknessOfGeometry func. Otherwise, I need to recreate all geometry again to use the 'cullDistance' (in zoom animations) instead of use adjustThicknessOfGeometry transformation. But I understand that this is not possible.

ps. Sorry for my eng, I hope you understand what I keep in mind ;)

IonSwitz commented 9 years ago

Hello again. Yes, I understand how you mean, and don't worry about your English :)

I will look into such an integration, but I am not sure if it will be fully possible. Can I ask how many data points you have in your plots?`It sounds like there are quite many :)

alexeevilya commented 9 years ago

Hello, IonSwitz! Thanx ;) "How many data points...?" - Oh, this is a hard question. I have not so many data points. It sounds like one or two thousands. But the difficulty lies in the fact that I need to draw smooth curves (bezier). I tried to use the Loop-Blinn method (something like http://blog.bwhiting.co.uk/?p=423) and draw all in the fragment shader, but it has so many troubles... Now I use your curveTo() realization, adapted to FastStroke class - and this means that I need to have many straight segments to make a curve really smooths. So there are very many "data points" ;)