Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for "dashed" polylines #5159

Merged
merged 20 commits into from
Apr 20, 2017
Merged

Conversation

jasonbeverage
Copy link
Contributor

This PR adds support for screen space "dashed" polylines. This attempts to get something similar to OpenGL line stippling to work with Cesium's neat wide polyline algorithm. I tried various techniques using the texture coordinates of the line but none ended up looking as nice as this one.

There is a new PolylineDashMaterialProperty that controls the PolylineDash material.

I've modified the PolylineMaterialAppearanceVS.glsl shader to compute a varying, v_angle, that is fed to the fragment shader. This angle is the screen space angle between the line and the x axis quantized to 45 degree increments. I've quantized rather than using the actual value to try to avoid artifacts as the polyline expands.

The fragment shader takes the incoming pixel coordinate, rotates it by the incoming angle and then does a simple stippling along the x-axis on the rotated pixel coordinate. The fragment location is rotated to create a striped pattern in the direction of the line so it actually appears like it's being stippled along the line.

I've updated the Polyline and Polyline CZML sandcastle examples to have a dashed line so you can see it in action.

dash

@hpinkos
Copy link
Contributor

hpinkos commented Mar 27, 2017

This is awesome @jasonbeverage, thanks! This has been a frequently requested feature and will make the Cesium community very happy 😄

@pjcozzi pjcozzi mentioned this pull request Mar 28, 2017
@pjcozzi
Copy link
Contributor

pjcozzi commented Mar 28, 2017

Fixes #2584

@pjcozzi
Copy link
Contributor

pjcozzi commented Mar 28, 2017

Thanks @jasonbeverage. This is a pleasant surprise and will be a very popular feature!

I would like to review this before the final merge. @bagnell should also take a look at this first since he wrote the original polyline shader, but he's busy for probably all of this week. In the meantime, if anyone has cycles to review or even just test this, please jump in!

@jasonbeverage I'm not sure if you've already read it, but you might like this paper: Shader-Based Antialiased, Dashed, Stroked Polylines

@mramato
Copy link
Contributor

mramato commented Mar 28, 2017

Awesome work @jasonbeverage. You keep cranking out some great stuff. I'm not qualified to review the shaders here, but the Entity and CZML pieces look spot on (will look closer as soon as I have time if no one else beats me to it).

One of the things me and @bagnell have talked about for stippled lines is the ability to specify a custom pattern (perhaps through a string like -- - ---). Would that be an easy extension to this implementation? (I'm just asking out of curiosity). I know @bagnell also had some ideas (such as using images as part of line styles but that's also outside the scope of this PR).

@emackey
Copy link
Contributor

emackey commented Mar 28, 2017

This is crazy awesome.

@mramato You probably know shaders can't directly take a string, but we could pass a float parameter to indicate a percentage of dash vs. gap, or I could even imagine passing a one-dimensional alpha map that supplies a dashing pattern to use. That would be for a future version of course, this seems cool enough as-is that it should probably go in without a lot of feature creep. Also I was thinking it could be useful to supply a second color for use in the gaps, instead of just discarding them (for example, use the same color with less alpha, or alternate the brightness, etc). But again, that's feature creep, and this is plenty good enough already.

Dashed lines (as they are now) will be very useful in combination with the depthFail feature going on in #5160. Underground lines are crying out for dashes.

@jasonbeverage
Copy link
Contributor Author

Don't merge this just yet, I'm going to try to push a couple more things and see what you think.

@emackey
Copy link
Contributor

emackey commented Mar 28, 2017

@jasonbeverage great. If you're making changes can you switch Color.BLUE to Color.CYAN to make the line more visible? Likewise in the CZML crank the green channel to 255. Thanks!

@jasonbeverage
Copy link
Contributor Author

Sure thing, just pushed that change.

@emackey
Copy link
Contributor

emackey commented Mar 28, 2017

I got asked by @GatorScott to test with a more curved path, and I had an inclined GEO handy. Here's a gist. It works pretty well, even for this tough case.

@jasonbeverage
Copy link
Contributor Author

I just pushed a few changes.

There is a new dashPattern property for the PolylineDashMaterial that defines a 16 bit stipple pattern.

I changed some of the defaults so that dashLength is 16 pixels so it lines up per pixel (approximately) with the dash pattern. The default dash pattern is 255 (0000 0000 1111 1111).

I've also added a new Polyline Dash sandcastle example.

Also, just wanted to say thanks to @timoore for doing the initial work on this effort, including the dashPattern logic. Our original approach just used the distance along the line to drop pixels, but that caused too many artifacts with your wide polyine algorithm (similar to what you'd see with the StipeMaterial with the direction set to vertical). I came up with the screen space fragment shader based stippling just recently and the artifacts aren't nearly as bad.

polyline dash

}
result.color = Property.getValueOrClonedDefault(this._color, time, defaultColor, result.color);
result.dashLength = Property.getValueOrDefault(this._dashLength, time, defaultDashLength, result.dashLength);
result.dashPattern = Property.getValueOrDefault(this._dashPattern, time, defaultDashPattern, result._dashPattern);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think result._dashPattern gets an underscore here.

* @param {Object} [options] Object with the following properties:
* @param {Property} [options.color=Color.WHITE] A Property specifying the {@link Color} of the line.
* @param {Property} [options.dashLength=16.0] A numeric Property specifying the length of the dash pattern in pixel.s
* @param {Property} [options.dashPattern=255.0.0] A numeric Property specifying a 16 bit pattern for the dash
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo

@emackey
Copy link
Contributor

emackey commented Mar 28, 2017

I wonder if CZML (and maybe Entity) can take a 16-character string instead of numeric dashPattern. For example:

var dashPattern = '        --------';

Could this be converted to 255 by testing each char position and setting a bit? This would make the pattern more user-friendly.

@emackey
Copy link
Contributor

emackey commented Mar 28, 2017

Here's some code to turn an arbitrary-length string of spaces and dashes into a closest-approximation 16-bit field. This just iterates the 16 bits, picks the closest char based on length of input string, and figures out if it will set that bit or not.

    function dashStringToBits(dashPatternString) {
        var dashPatternLength = dashPatternString.length;
        var dashPatternNumber = 0;
        var currentBitValue = 65536;
        for (var i = 0; i < 16; ++i) {
            currentBitValue = currentBitValue >> 1;
            var charPos = Math.floor((i / 16) * dashPatternLength);
            var char = dashPatternString.charAt(charPos);
            if (char !== ' ') {
                dashPatternNumber |= currentBitValue;
            }
        }
        return dashPatternNumber;
    }
dashStringToBits(' -')
255
dashStringToBits('- ')
65280
dashStringToBits('    ----')
255
dashStringToBits('        --------')
255
dashStringToBits('  -  ---')
3135
dashStringToBits('               -')
1
dashStringToBits('              --')
3
dashStringToBits('             ---')
7

@jasonbeverage
Copy link
Contributor Author

Just pushed a fix for my fat finger typos :)

@jasonbeverage
Copy link
Contributor Author

I like the dash string idea. Would PolylineDashMaterialProperty.getValue just need to call that to get the appropriate float value to feed the uniform?

@emackey
Copy link
Contributor

emackey commented Mar 28, 2017

Would PolylineDashMaterialProperty.getValue just need to call that

Off the top of my head I'm not sure where it goes. I'd have to research a bit. Curious to hear @mramato's thoughts before we go too far on it.

@jasonbeverage
Copy link
Contributor Author

I've just been mashing 0 and 1 into the Windows programmer calculator and copying the resulting decimal value :)

I also just tried your gist and it looks awesome!

@pjcozzi
Copy link
Contributor

pjcozzi commented Mar 28, 2017

Also, just wanted to say thanks to @timoore for doing the initial work on this effort, including the dashPattern logic...

Thanks again to both of you!

@bagnell
Copy link
Contributor

bagnell commented Apr 7, 2017

I only have that one comment. Looks good! Thanks @jasonbeverage!

I believe @lilleyse also wants to review this.

@bagnell
Copy link
Contributor

bagnell commented Apr 7, 2017

@jasonbeverage I spent some time playing with your branch and the PolylineDashMaterial shader today. I created a branch (dashedlines-2) that has your changes plus some of my own. It adds a length in meters from the first point on the line as a new vertex attribute and I tried to compute dashPosition from that. This way you won't see the abrupt changes from v_angle or much of a change in the dash pattern when panning from using gl_FragCoord. It looks pretty good when panning or rotating as long as the camera is mostly looking top-down, but as soon as you start rotating, certain camera views will cause the dashes to shrink or stretch. Any ideas you have about this would be greatly appreciated!

I still want to see this PR merged into master. Computing the dashes based on line length can sit in a branch until someone has time to work on it.

* @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
*/
PolylineDashMaterialProperty.prototype.equals = function(other) {
return this === other || //
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the trailing // here and below required?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @pjcozzi it's not necessary and I've just pushed a change removing it.

else {
lineDir = normalize(nextWindow.xy - positionWindow.xy);
}
angle = atan(lineDir.x, lineDir.y) - atan(1.0, 0.0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

atan can be a performance killer.

  • Can you precompute atan(1.0, 0.0)? I would not trust all GLSL compilers to optimize that.
  • What level of precision is required for this? Would it be possible to replace the other atan call with a taylor series expansion of a few terms? Seems like yes since it is being quantized.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed a change precomputing the atan(1.0,0.0). We could use a taylor series or a lookup table for the other atan, but I'd save that for later unless performance is a killer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last time I tested - which was awhile ago - atan is pretty much the worse thing we can do in a shader. @bagnell said there was already inverse trig in the polyline shader, but I don't see it at quick glance.

If you're not up for it now, we'll try to make this change soon.

@jasonbeverage
Copy link
Contributor Author

Hey @bagnell , we tried almost the exact same thing initially, and I think it would work fine if you were just drawing regular old lines. But I couldn't figure out a way to get it to play nice with the wide polyline aglo. I think the problem is that the length in meters actually changes along the line when you move the verts around, so you end up with funky artifacts depending on how the line rotates. Your algo also does something along the near plane where it looks like vert is pushed really far towards the near plane so you end up stretching the distance in meters artificially resulting in really wide dashes.

I tried and tried but couldn't come up with something that worked so I ended up scrapping it in favor of this purely fragment based stippling.

@jasonbeverage
Copy link
Contributor Author

@bagnell I tried wrapping the block in polylinecommon with the ifdef, but it looks like just adding #define POLYLINE_DASH to the material didn't work. It behaves like it's not actually set. Is there somewhere else the define would need to go?

@denverpierce
Copy link
Contributor

@mramato for CZML, I'd think it'd be easier and more intuitive to write a bit mask instead of a 16bit number, especially for users more familiar with the data side of things.

IE:

{
dashLength:16,
dashPatthern: '0000000011111111'
}

is more human read/writeable than:

dashPattern: 255

@mramato
Copy link
Contributor

mramato commented Apr 17, 2017

@bagnell and @pjcozzi do you have any additional comments here? I want to take another pass on the Entity updates (just because I haven't looked at this in a few weeks) but I don't expect any surprises.

Barring any issues, I would like to get this merged ASAP so that it has time to stew before 1.32 on May 1st.

Thanks.

@pjcozzi
Copy link
Contributor

pjcozzi commented Apr 17, 2017

@mramato review and merge when ready.

@bagnell
Copy link
Contributor

bagnell commented Apr 17, 2017

@bagnell I tried wrapping the block in polylinecommon with the ifdef, but it looks like just adding #define POLYLINE_DASH to the material didn't work. It behaves like it's not actually set. Is there somewhere else the define would need to go?

Sorry, it needs to be in the vertex shader. Can you modify the PolylineMaterialAppearance.vertexShaderSource getter to search the material shaderSource for varying float v_angle and then append the #define?

@mramato
Copy link
Contributor

mramato commented Apr 18, 2017

@mramato for CZML, I'd think it'd be easier and more intuitive to write a bit mask instead of a 16bit number, especially for users more familiar with the data side of things.

We can probably add this in as a CZML-only feature in a small PR after this gets merged. We'll also need to update the spec and writer too. (Of course we need that for the new dash stuff as well). CC @shunter

@pjcozzi
Copy link
Contributor

pjcozzi commented Apr 20, 2017

@jasonbeverage we'd like to merge this in the next few days so it has some time in master before it ships in Cesium 1.33 on May 1. Can you make the change @bagnell suggested, #5159 (comment), or do you want us to?

Does anyone else want to test or review this?

@jasonbeverage
Copy link
Contributor Author

jasonbeverage commented Apr 20, 2017 via email

@bagnell
Copy link
Contributor

bagnell commented Apr 20, 2017

@mramato Can you merge when ready?

@denverpierce
Copy link
Contributor

I threw some fairly complex linework at it and it performed admirably.

@mramato
Copy link
Contributor

mramato commented Apr 20, 2017

Thanks again @jasonbeverage!

@mramato mramato merged commit 380e1bf into CesiumGS:master Apr 20, 2017
@jasonbeverage
Copy link
Contributor Author

Thanks for merging this! Glad to be able to contribute.

@pjcozzi
Copy link
Contributor

pjcozzi commented Apr 20, 2017

Fantastic! This will quickly become a popular feature. Thanks again @jasonbeverage!

@initialed85
Copy link

@jasonbeverage I had a thirst for dashed lines that you have now quenched, thankyou.

@Tezraine
Copy link

I know I'm a bit late to the party, but I just wanted to mention that with ECMA6, the 0b format for numbers is supported. Also 0b1111111100000000 (255) if reversed and overlapped is effectively a bitwise OR that leaves you with all 1s. 0b1111000000001111 (61455) or 0b0000111111110000 (4080) is the same pattern, but is the same forwards and backwards.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants