Search code examples
javaopenglworldwind

Java NASA Worldwind Polygon with Internal Border


So im aiming to make a system where you draw a surface polygon and it essentially makes a dashed border around the polygon. The issue is I want this border to be a set size, meaning each dash should be a set size around the polygon. Also Im working on trying to figure out how to make it so that if you increase the border size of a surface polygon that it will not expand outwards, only inwards, this way dimensions are skewed.

Long story short I started essentially calculating my own dashed border and drawing each dash by hand. The problem with this comes in at the angles where they meet and chopping off extra length when a dash would extend outside of the polygon. The picture attached shows what I have so far and what its essentially supposed to look like. I also sort of want to keep the normal border on the polygon the way it is in the picture, but that is not mandatory.

I guess i'm just surprised this is so hard to do in NASA Worldwind where in directX is was about 20 lines. This makes me think I'm going about it the wrong way.

Just as a note. I looked into using stencil buffers, but this proved to be almost a waste of time to get working and to run correctly through world wind. I dont think I have enough of a grasp to do this correctly.

P.S. None of the things I have started using are set in stone. I just need the final outcome to be correct. Thanks for the help.

What I have so far with drawing each polygon of the border.

TLDR: Need a to draw Polygons with an internal border of set width.


Solution

  • I suggest first to draw the (green) area, and second to draw a dashed (white) line.
    When drawing the line, a Geometry Shader can be used to convert the line segments to quads (triangle stripes with 4 points), which enclose the thin region, which would be filled by a solid line, in the given line thickness. The fragment shader finnaly has to dash the line (cut parts of the line). Finally the Fragment Shader has to generate the dashed line (cut parts out of the line).

    To draw the line, the Primitive type GL_LINE_STRIP_ADJACENCY has to be used (see GLSL Tutorial - Primitive Assembly), because each line segment has to know its predecessor and successor, to calculate the angle bisectors in the corners. If a polygon is defined by a list of lines, that can be done easily. The last point of the list must be added at the beginning of the list of points and the first point of the list must be added at the end of the list of points.

    e.g. If a polygon consist of the points A, B, C and D, then the list of points for the GL_LINE_STRIP_ADJACENCY would be D, A, B, C, D and A. This would give the following 4 line segments:

    • A to B, with predecessor D and successor C
    • B to C, with predecessor A and successor D
    • C to D, with predecessor B and successor A
    • D to A, with predecessor C and successor B

    The Vertex Shader just has to pass through the corner points of the polygon:

    #version 400
    
    layout (location = 0) in vec3 inPos;
    
    out TVertexData
    {
        out vec3 pos;
    } outData;
    
    void main()
    {
        outData.pos = inPos;
        gl_Position = vec4( inPos, 1.0 );
    }
    

    The Geometry Shader calculate the angle bisectors at the start end at the end of each line segment and generates a quad according to the thickness of the line.
    Since the fragment shader still needs to generate the dashed line, the length of the line and the center of the line are calculated and passed to the fragment shader.

    #version 400
    
    layout( lines_adjacency ) in;
    layout( triangle_strip, max_vertices = 4 ) out;
    
    in TVertexData
    {
        vec3 pos;
    } inData[];
    
    out TGeometryData
    {
        vec3  linePos;
        vec3  lineMidPoint;
        float lineLen;
    } outData;
    
    uniform float u_thickness;
    
    void main()
    {
        if ( gl_InvocationID != 0 )
            return;
    
        vec3 pos0    = inData[1].pos;
        vec3 pos1    = inData[2].pos;
        vec3 dirPred = normalize( inData[0].pos - pos0 );
        vec3 dirSucc = normalize( inData[3].pos - pos1 );
        vec3 dirLine = normalize( pos1 - pos0 ); 
        vec3 dirNorm = normalize( dirPred - dirLine * dot(dirLine, dirPred) );
        dirSucc = faceforward( dirSucc, -dirNorm, dirSucc );
    
        vec3 dir0 = abs( dot(dirPred, dirLine) ) > 0.99 ? dirNorm : normalize( dirPred + dirLine );
        vec3 dir1 = abs( dot(dirSucc, dirLine) ) > 0.99 ? dirNorm : normalize( dirSucc - dirLine );
    
        vec3 pos01 = pos0 + dir0 * u_thickness / dot(dir0, dirNorm);
        vec3 pos11 = pos1 + dir1 * u_thickness / dot(dir1, dirNorm);
    
        vec3 lineMidPoint0 = (pos0 + pos1) * 0.5;
        vec3 lineMidPoint1 = lineMidPoint0 + dirNorm * u_thickness;
    
        outData.lineLen = length( pos1 - pos0 );
    
        outData.lineMidPoint = lineMidPoint0; 
        outData.linePos = pos0;
        gl_Position = vec4(pos0, 1.0); EmitVertex();
        outData.linePos = pos1;
        gl_Position = vec4(pos1, 1.0); EmitVertex();
    
        outData. lineMidPoint = lineMidPoint1; 
        outData.linePos = pos01;
        gl_Position = vec4(pos01, 1.0); EmitVertex();
        outData.linePos = pos11;
        gl_Position = vec4(pos11, 1.0); EmitVertex();
    
        EndPrimitive();
    }
    

    Finally the Fragment Shader generates the dashed line by discarding fragments in the gaps.

    #version 400
    
    in TGeometryData
    {
        vec3  linePos;
        vec3  lineMidPoint;
        float lineLen;
    } inData;
    
    out vec4 fragColor;
    
    uniform float u_dashLen;
    uniform float u_gapLen;
    uniform vec4  u_color;
    
    void main()
    {
        float midDist  = length( inData.linePos - inData.lineMidPoint ); 
        float modDist  = mod( midDist + u_dashLen * 0.5, u_dashLen + u_gapLen );
        float onDash   = max( step( modDist, u_dashLen ), 
                              step( inData.lineLen * 0.5 - u_dashLen, midDist ) );
        if ( onDash < 0.5 )
            discard;
        fragColor      = u_color * onDash;
    }
    

    Note, this Fragment Shader uses the discard keyword, to discard fragment which are in the gaps of the dashed line. If Blending would be used, then (if ( onDash < 0.5 ) discard;) could be omitted, because the fragments in the gaps would be drawn with an alpha channel of 0.

    If the filled (green) polygon is drawn first, and the dashed (white) line is drawn second, then the result may look like this:

    enter image description here