从符号到地形的世界风线

Worldwind Line from Symbol to Terrain

Worldwind 的 Point PlaceMark 渲染器具有通过调用 setLineEnabled 从 Placemark 向下放置一条线到地形的功能,如以下屏幕截图所示:

我想要做的是添加这样一行,它也适用于可渲染的战术符号。我的第一个想法是从 PointPlacemark renderable and add it to the AbstractTacticalSymbol 可渲染中借用逻辑来做到这一点。我已经尝试过了,但到目前为止我一直没有成功。

这是我到目前为止所做的:

  1. 将其添加到 OrderedSymbol class:

    public Vec4 terrainPoint;
    
  2. 已更新 computeSymbolPoints 以计算 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. 添加了此逻辑(取自 PointPlacemark.java 并更新为符合 AbstractTacticalSymbol.java)。请注意,我已将 lineEnabled 设置为 true,因此它应该默认绘制线条。

    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. 将此调用添加到 drawOrderedRenderable 方法的开头:

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

我相信这与 PointPlacemark 为使地形线出现所做的工作非常相似,但这是我在 运行 TacticalSymbols 示例中进行更改时得到的结果:

这是包含我(尝试)更改的整个 AbsractTacticalSymbol 文件:http://pastebin.com/aAC7zn0p(对于 SO 来说太大了)

好的,所以这里的问题是框架内正射投影和透视投影的混合。至关重要的是,如果我们查看 PointPlaceMark 的 beginDrawing,我们会看到:

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);
}

就是这样。但是如果我们查看 AbstractTacticalSymbol 的 beginDrawing,我们会看到更多的代码,尤其是这两行:

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

将 OpenGL 投影从透视模式切换到正交模式,两种截然不同的投影技术不能很好地混合,除了一些值得注意的情况:其中之一 UI 在 3D 场景上渲染,例如:渲染图标! video showing the difference between orthographic and perspective rendering

我觉得用文字解释很尴尬,但是透视渲染给你透视,而正交渲染没有,所以你得到的东西很像 2D 游戏,这对 UI 很有效,但不是用于逼真的 3D 图像。

但是 PointPlaceMark 也渲染了一个图标,那么那个文件在哪里在两种投影模式之间切换呢?结果他们在 doDrawOrderedRenderable after 调用 drawLine(第 976 行)时这样做了。

那么,为什么会出错呢?现在框架内部发生了很多魔法,所以我不能完全确定会发生什么,但我已经大致了解出了什么问题。它出错是因为透视投影允许您以与正交投影(在框架中)截然不同的方式提供坐标,在这种情况下,可能向投影渲染提供 (x,y,z) 会在 (X,Y,Z) 处呈现一个点) 世界 space,而正交渲染在 (x,y,z) 屏幕 space(或剪辑 space 处呈现,我不是这方面的专家)。因此,当您现在在坐标 (300000,300000,z) 处从图标到地面画一条线时,它们当然会从您的屏幕上掉下来,并且不可见,因为您没有 300000x3000000 像素的屏幕.也可能是这两种方法都允许在世界 space 中提供坐标(尽管这似乎不太可能),在这种情况下,下图说明了问题。两个摄像头都指向下面方框的相同方向,但看到的东西不同。

请特别注意透视渲染如何让您看到更多框。

因此,因为渲染代码在 render() 方法中以透视投影开始,解决这个问题就像延迟正交投影在 之后开始一样简单已经画好了线,就像在 PointPlaceMark 的代码中一样。 这正是我所做的 here(第 1962 至 1968 行),它只是将几行代码移到正交投影之外,所以在 beginDrawing 之前,您几乎完成了。

现在这个解决方案不是很优雅,因为代码的功能现在在很大程度上取决于它的执行顺序,这通常很容易出错。这部分是因为我做了简单的修复,但主要是因为该框架遵守已弃用的 OpenGL 切换视角标准(除其他外),所以我无法产生真正完美的解决方案,无论这样的解决方案是否在我的范围内能力。

根据您的喜好,您可能希望使用继承来创建 SymbolWithLine 超类或接口,或者使用组合来添加功能。或者你可以这样保留它,如果你不需要这个功能和许多其他 类。不管怎样,我希望这些信息足以解决这个问题。

根据您的要求,以下几行演示了线宽和线颜色的变化(第 1965 行):

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

Updated code for AbstractTacticalSymbol

我不确定这是否符合 "canonical answer",但我很乐意根据任何建议更新答案,或者进一步澄清我的解释。我认为答案的关键在于对正交投影与透视投影的理解,但我觉得这并不是对该问题进行规范回答的地方。