Worldwind Line from Symbol to Terrain
Asked Answered
T

1

7

Worldwind's Point PlaceMark renderable has the feature to drop a line from the Placemark down to the terrain by calling setLineEnabled as in this screenshot:

enter image description here

What I'm trying to do is add a line like this that also works with the Tactical Symbol renderable. My first thought was to just borrow the logic to do this from the PointPlacemark renderable and add it to the AbstractTacticalSymbol renderable. I've tried that and I have been unsuccessful so far.

Here is what I've done so far:

  1. Added this to OrderedSymbol class:

    public Vec4 terrainPoint;
    
  2. Updated computeSymbolPoints to calculate terrainPoint

    protected void computeSymbolPoints(DrawContext dc, OrderedSymbol osym)
    {
        osym.placePoint = null;
        osym.screenPoint = null;
        osym.terrainPoint = null;
        osym.eyeDistance = 0;
    
        Position pos = this.getPosition();
        if (pos == null)
            return;
    
        if (this.altitudeMode == WorldWind.CLAMP_TO_GROUND || dc.is2DGlobe())
        {
            osym.placePoint = dc.computeTerrainPoint(pos.getLatitude(), pos.getLongitude(), 0);
        }
        else if (this.altitudeMode == WorldWind.RELATIVE_TO_GROUND)
        {
            osym.placePoint = dc.computeTerrainPoint(pos.getLatitude(), pos.getLongitude(), pos.getAltitude());
        }
        else // Default to ABSOLUTE
        {
            double height = pos.getElevation() * dc.getVerticalExaggeration();
            osym.placePoint = dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(), height);
        }
    
        if (osym.placePoint == null)
            return;
    
        // Compute the symbol's screen location the distance between the eye point and the place point.
        osym.screenPoint = dc.getView().project(osym.placePoint);
        osym.eyeDistance = osym.placePoint.distanceTo3(dc.getView().getEyePoint());
    
        // Compute a terrain point if needed.
        if (this.isLineEnabled() && this.altitudeMode != WorldWind.CLAMP_TO_GROUND && !dc.is2DGlobe())
            osym.terrainPoint = dc.computeTerrainPoint(pos.getLatitude(), pos.getLongitude(), 0);
    
    }
    
  3. Added this logic (taken from PointPlacemark.java and updated for compliance to AbstractTacticalSymbol.java). Note that I have lineEnabled set to true, so it should draw the line by default.

    boolean lineEnabled = true;
    
    
    double lineWidth = 1;
    protected int linePickWidth = 10;
    Color lineColor = Color.white;
    
    /**
     * Indicates whether a line from the placemark point to the corresponding position on the terrain is drawn.
     *
     * @return true if the line is drawn, otherwise false.
     */
    public boolean isLineEnabled()
    {
        return lineEnabled;
    }
    
    /**
     * Specifies whether a line from the placemark point to the corresponding position on the terrain is drawn.
     *
     * @param lineEnabled true if the line is drawn, otherwise false.
     */
    public void setLineEnabled(boolean lineEnabled)
    {
        this.lineEnabled = lineEnabled;
    }
    
    /**
     * Determines whether the placemark's optional line should be drawn and whether it intersects the view frustum.
     *
     * @param dc the current draw context.
     *
     * @return true if the line should be drawn and it intersects the view frustum, otherwise false.
     */
    protected boolean isDrawLine(DrawContext dc, OrderedSymbol opm)
    {
        if (!this.isLineEnabled() || dc.is2DGlobe() || this.getAltitudeMode() == WorldWind.CLAMP_TO_GROUND
            || opm.terrainPoint == null)
            return false;
    
        if (dc.isPickingMode())
            return dc.getPickFrustums().intersectsAny(opm.placePoint, opm.terrainPoint);
        else
            return dc.getView().getFrustumInModelCoordinates().intersectsSegment(opm.placePoint, opm.terrainPoint);
    }
    
    
    
    
    /**
     * Draws the placemark's line.
     *
     * @param dc             the current draw context.
     * @param pickCandidates the pick support object to use when adding this as a pick candidate.
     */
    protected void drawLine(DrawContext dc, PickSupport pickCandidates, OrderedSymbol opm)
    {
        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
    
        if ((!dc.isDeepPickingEnabled()))
            gl.glEnable(GL.GL_DEPTH_TEST);
        gl.glDepthFunc(GL.GL_LEQUAL);
        gl.glDepthMask(true);
    
        try
        {
            dc.getView().pushReferenceCenter(dc, opm.placePoint); // draw relative to the place point
    
            this.setLineWidth(dc);
            this.setLineColor(dc, pickCandidates);
    
            gl.glBegin(GL2.GL_LINE_STRIP);
            gl.glVertex3d(Vec4.ZERO.x, Vec4.ZERO.y, Vec4.ZERO.z);
            gl.glVertex3d(opm.terrainPoint.x - opm.placePoint.x, opm.terrainPoint.y - opm.placePoint.y,
                opm.terrainPoint.z - opm.placePoint.z);
            gl.glEnd();
        }
        finally
        {
            dc.getView().popReferenceCenter(dc);
        }
    }
    
    
    /**
     * Sets the width of the placemark's line during rendering.
     *
     * @param dc the current draw context.
     */
    protected void setLineWidth(DrawContext dc)
    {
        Double lineWidth = this.lineWidth;
        if (lineWidth != null)
        {
            GL gl = dc.getGL();
    
            if (dc.isPickingMode())
            {
                gl.glLineWidth(lineWidth.floatValue() + linePickWidth);
            }
            else
                gl.glLineWidth(lineWidth.floatValue());
    
            if (!dc.isPickingMode())
            {
                gl.glHint(GL.GL_LINE_SMOOTH_HINT, GL.GL_FASTEST);
                gl.glEnable(GL.GL_LINE_SMOOTH);
            }
        }
    }
    
    
    /**
     * Sets the color of the placemark's line during rendering.
     *
     * @param dc             the current draw context.
     * @param pickCandidates the pick support object to use when adding this as a pick candidate.
     */
    protected void setLineColor(DrawContext dc, PickSupport pickCandidates)
    {
        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
    
        if (!dc.isPickingMode())
        {
            Color color = this.lineColor;
            gl.glColor4ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue(),
                (byte) color.getAlpha());
        }
        else
        {
            Color pickColor = dc.getUniquePickColor();
            Object delegateOwner = this.getDelegateOwner();
            pickCandidates.addPickableObject(pickColor.getRGB(), delegateOwner != null ? delegateOwner : this,
                this.getPosition());
            gl.glColor3ub((byte) pickColor.getRed(), (byte) pickColor.getGreen(), (byte) pickColor.getBlue());
        }
    }
    
  4. Added this call to the beginning of the drawOrderedRenderable method:

    boolean drawLine = this.isDrawLine(dc, osym);
    if (drawLine)
        this.drawLine(dc, pickCandidates, osym);
    

I believe this closely mirrors what PointPlacemark is doing to get the line to terrain appear, but this is what I get when I run the TacticalSymbols example with my changes:

enter image description here

Here is the whole AbsractTacticalSymbol file with my (attempted) changes: http://pastebin.com/aAC7zn0p (its too large for SO)

Transvestite answered 18/6, 2016 at 18:55 Comment(0)
F
5

Ok, so the problem here is a mixing between orthographic and perspective projection within the framework. Crucially, if we look at PointPlaceMark's beginDrawing we see:

GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.

int attrMask =
        GL2.GL_DEPTH_BUFFER_BIT // for depth test, depth mask and depth func
            ... bunch more bits being set ...

gl.glPushAttrib(attrMask);

if (!dc.isPickingMode())
{
    gl.glEnable(GL.GL_BLEND);
    OGLUtil.applyBlending(gl, false);
}

That's it. But if we look at AbstractTacticalSymbol's beginDrawing we see a great deal more code, in particular these two lines:

this.BEogsh.pushProjectionIdentity(gl);
gl.glOrtho(0d, viewport.getWidth(), 0d, viewport.getHeight(), 0d, -1d);

Which switch the OpenGL projection from Perspective to Orthographic mode, two wildly different projection techniques that don't mix very well, except for a few notable cases: one of them UI rendering over a 3D scene, for instance: rendering icons! video showing the difference between orthographic and perspective rendering

I find it awkward to explain in words, but perspective rendering gives you perspective, and orthographic rendering doesn't, so you get something much like a 2D game, which works well for UI, but not for realistic 3D images.

But PointPlaceMark also renders an icon, so where does that file switch between the two projection modes? Turns out they do that in doDrawOrderedRenderable after calling drawLine (line 976).

So then, why does it go wrong? Now there's a lot of magic going on inside the framework, so I can't be exactly sure what happens, but I've got a broad idea what goes wrong. It goes wrong because perspective projection allows you to supply coordinates in a wildly different fashion to orthographic projection (in the framework), in this case perhaps supplying (x,y,z) to projection rendering renders a point at (X,Y,Z) world space, while orthographic rendering renders at (x,y,z) screen space (or clip space, I'm not a pro at this). So when you now draw a line from the icon to the ground, at coordinates (300000,300000,z), they will of course fall out of your screen, and not be visible, because you don't have a screen with 300000x3000000 pixels. It could also be that both approaches allow coordinates to be supplied in world space (though this seems unlikely), in this case the picture below illustrates the problem. Both cameras are pointed in the same direction at the boxes below, but see different things.

Perspective vs Orthographic Notice in particular how the perspective rendering allows you to see much more boxes.

So, because the rendering code starts out in perspective projection in the render() method, fixing this is as simple as delaying the orthographic projection to start after we've drawn the line, like in PointPlaceMark's code. That's exactly what I do here (lines 1962 to 1968), it's just moving a few lines of your code outside of the orthographic projection, so before beginDrawing, you were almost done.

Now this solution is not very elegant, because the functionality of the code now heavily depends on the order in which it is executed, which is generally very error prone. This is in part because of the simple fix I made, but mostly because the framework adheres to deprecated OpenGL standards for switching perspective (among other things), so I cannot produce a truly perfect solution, regardless of whether such a solution would lie within my abilities.

Depending on your preference, you may want to work with inheritance to create a SymbolWithLine superclass or interface, or work with composition to add the functionality. Or you can leave it like this, if you won't need this functionality with many other classes. Anyway, I hope this is enough information to get this problem resolved.

As per your request, the following lines demonstrate the line width and line color changes (line 1965):

this.lineColor = Color.CYAN;
this.lineWidth = 3;
this.drawLine(dc, this.pickSupport, osym);

Line color and width

Updated code for AbstractTacticalSymbol

I'm not sure if this qualifies as a "canonical answer", but I'd be happy to update the answer with any suggestions, or clarify my explanations a bit more. I think the crux of the answer lies in the understanding of orthographic versus perspective projection, but this is not really the place for a canonical answer to that question, I feel.

Fortepiano answered 25/6, 2016 at 21:27 Comment(2)
Thank you for taking a look. This is exactly what I was looking for (the screenshot) -- along with the ability to control the line width and color. If you don't mind, you explain the changes you needed to make?Transvestite
That explanation helps immensely. Thank you again.Transvestite

© 2022 - 2024 — McMap. All rights reserved.