从符号到地形的世界风线
Worldwind Line from Symbol to Terrain
Worldwind 的 Point PlaceMark 渲染器具有通过调用 setLineEnabled 从 Placemark 向下放置一条线到地形的功能,如以下屏幕截图所示:
我想要做的是添加这样一行,它也适用于可渲染的战术符号。我的第一个想法是从 PointPlacemark renderable and add it to the AbstractTacticalSymbol 可渲染中借用逻辑来做到这一点。我已经尝试过了,但到目前为止我一直没有成功。
这是我到目前为止所做的:
将其添加到 OrderedSymbol class:
public Vec4 terrainPoint;
已更新 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);
}
添加了此逻辑(取自 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());
}
}
将此调用添加到 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",但我很乐意根据任何建议更新答案,或者进一步澄清我的解释。我认为答案的关键在于对正交投影与透视投影的理解,但我觉得这并不是对该问题进行规范回答的地方。
Worldwind 的 Point PlaceMark 渲染器具有通过调用 setLineEnabled 从 Placemark 向下放置一条线到地形的功能,如以下屏幕截图所示:
我想要做的是添加这样一行,它也适用于可渲染的战术符号。我的第一个想法是从 PointPlacemark renderable and add it to the AbstractTacticalSymbol 可渲染中借用逻辑来做到这一点。我已经尝试过了,但到目前为止我一直没有成功。
这是我到目前为止所做的:
将其添加到 OrderedSymbol class:
public Vec4 terrainPoint;
已更新 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); }
添加了此逻辑(取自 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()); } }
将此调用添加到 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",但我很乐意根据任何建议更新答案,或者进一步澄清我的解释。我认为答案的关键在于对正交投影与透视投影的理解,但我觉得这并不是对该问题进行规范回答的地方。