WorldWind球面线相交Bug?

6
我看到WorldWind的球体-直线相交逻辑似乎有矛盾的行为。我创建了一个球体和一条直线,它们相交,但是相交返回null(扫描代码查找注释:// *** This is where it gets whacky)。
这是视觉上发生的事情(直线是灰色的,很难看到):Sphere-Line Intersecting
public class WWTest extends ApplicationTemplate {

    public static class VisualizationFrame extends ApplicationTemplate.AppFrame {

        public VisualizationFrame() {
            super(new Dimension(1200, 1024));
            final Globe globe = getWwd().getModel().getGlobe();

            //Create a sphere at 0,0 on the surface of the Earth wtih a 60 NMi radius
            final Vec4 sphereCenter = globe.computePointFromLocation(LatLon.ZERO);
            final Sphere sphere = new Sphere(sphereCenter, 111120);
            // Draw the sphere
            final RenderableLayer sphereLayer = new RenderableLayer();
            sphereLayer.addRenderable(sphere);

            final RenderableLayer pathLayer = new RenderableLayer();
            // Create a line at 10k feet (3048 meters) that starts outside the sphere at (2,-2) and proceeds into the sphere at (0.5, 0.5)
            final Position lineStart = Position.fromDegrees(2, -2, 3048);
            final Position lineEnds = Position.fromDegrees(0.5, 0.5, 3048);
            final Path asPath = new Path(lineStart, lineEnds);
            pathLayer.addRenderable(asPath);

            // Now that we've visualized the line, let's do some intersection math
            final Vec4 lineStartsAsVec = globe.computePointFromPosition(lineStart);
            final Vec4 lineEndsAsVec = globe.computePointFromPosition(lineEnds);
            final Line asLine = Line.fromSegment(lineStartsAsVec, lineEndsAsVec);

            // *** This is where it gets whacky - true, but no intersection?
            final boolean doesIntersect = sphere.intersects(asLine);
            final Intersection[] intersection = sphere.intersect(asLine);
            //outputs: Intersection found: null
            System.out.println(doesIntersect ? "Intersection found: " + Arrays.toString(intersection) : "No intersection, Why Not!?!?"); 

            insertBeforeCompass(getWwd(), sphereLayer);
            insertBeforeCompass(getWwd(), pathLayer);
            getWwd().getView().setEyePosition(Position.fromDegrees(0, 0, 500_000));
            getLayerPanel().update(getWwd());
        }
    }

    public static void main(String[] args) {
        ApplicationTemplate.start("World Wind Sphere-Line Intersection", VisualizationFrame.class);

    }
}

以下是我在Maven项目中声明的依赖项,以获取WorldWind(我也尝试过版本“2.0.0-986”,但似乎没有帮助):

<dependency>
    <groupId>gov.nasa</groupId>
    <artifactId>worldwind</artifactId>
    <version>2.0.0</version>
</dependency>
<dependency>
    <groupId>gov.nasa</groupId>
    <artifactId>worldwindx</artifactId>
    <version>2.0.0</version>
</dependency>
<dependency>
    <groupId>org.jogamp.gluegen</groupId>
    <artifactId>gluegen-rt-main</artifactId>
    <version>2.2.4</version>
</dependency>
<dependency>
    <groupId>org.jogamp.jogl</groupId>
    <artifactId>jogl-all-main</artifactId>
    <version>2.2.4</version>
</dependency>

为了彻底,这里是代码导入:

import gov.nasa.worldwind.geom.Intersection;
import gov.nasa.worldwind.geom.LatLon;
import gov.nasa.worldwind.geom.Line;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.geom.Sphere;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.layers.RenderableLayer;
import gov.nasa.worldwind.render.Path;
import gov.nasa.worldwindx.examples.ApplicationTemplate;
import static gov.nasa.worldwindx.examples.ApplicationTemplate.insertBeforeCompass;
import java.awt.Dimension;
import java.util.Arrays;

已联系WorldWind的主要技术POC,并表示将把此错误放入他们的队列中进行修复。感谢Chris和Kyle的帮助 :) - Jason
2个回答

2

如果你查看Sphere#intersect()的实现,它期望的是以球体原点为中心的坐标系中的线(而不是地球的坐标系),这几乎肯定是一个错误。你应该能够执行以下操作:

final Vec4 pa = lineStartsAsVec.subtract3(sphereCenter);
final Vec4 pb = lineEndsAsVec.subtract3(sphereCenter);
final Line asLine2 = Line.fromSegment(pa, pb);
final Intersection[] intersection = sphere.intersect(asLine2);

请记住,返回的交点仍然是以球体原点为中心的笛卡尔坐标系,因此要将它们转换回World Wind笛卡尔坐标系,您需要执行以下操作:

final Vec4 intersectionPos = intersection[0].getIntersectionPoint().add3(sphereCenter);

实现还考虑了线是无限长的,因此它将返回两个点而不是一个点。
你可以很容易地实现自己的intersect()版本,它适用于普通坐标并考虑线的长度,请参见这里

@Chris_K 如果你有兴趣并且愿意帮忙编写一些具有挑战性的高效WGS-84计算几何例程,请给我发消息,你可以在FB、Twitter、LinkedIn等社交媒体上找到我。 - Jason

2
如果你查看Worldwind源代码,特别是Sphere类的intersects()和intersect()方法,并将它们作为输入逐步执行,你会发现以下内容:
该方法:
public boolean intersects(Line line)

这里的返回值为true,因为球心到直线的距离小于球的半径,符合预期。

对于该方法:

public final Intersection[] intersect(Line line)

原来,二次方程的判别式小于零(即二次方程没有实根,而是两个不同的复数根)。

WorldWind API参考文档在这里

涉及的具体方法包括:

    /**
 * Tests for intersection with a <code>Line</code>.
 *
 * @param line the <code>Line</code> with which to test for intersection
 *
 * @return true if <code>line</code> intersects or makes a tangent with the surface of this <code>Sphere</code>
 *
 * @throws IllegalArgumentException if <code>line</code> is null
 */
public boolean intersects(Line line)
{
    if (line == null)
    {
        String msg = Logging.getMessage("nullValue.LineIsNull");
        Logging.logger().severe(msg);
        throw new IllegalArgumentException(msg);
    }
    return line.distanceTo(this.center) <= this.radius;
}

并且:

    /**
 * Obtains the intersections of this sphere with a line. The returned array may be either null or of zero length if
 * no intersections are discovered. It does not contain null elements and will have a size of 2 at most. Tangential
 * intersections are marked as such. <code>line</code> is considered to have infinite length in both directions.
 *
 * @param line the <code>Line</code> with which to intersect this <code>Sphere</code>
 *
 * @return an array containing all the intersections of this <code>Sphere</code> and <code>line</code>
 *
 * @throws IllegalArgumentException if <code>line</code> is null
 */
public final Intersection[] intersect(Line line)
{
    if (line == null)
    {
        String message = Logging.getMessage("nullValue.LineIsNull");
        Logging.logger().severe(message);
        throw new IllegalArgumentException(message);
    }

    double a = line.getDirection().getLengthSquared3();
    double b = 2 * line.selfDot();
    double c = line.getOrigin().getLengthSquared3() - this.radius * this.radius;

    double discriminant = Sphere.discriminant(a, b, c);
    if (discriminant < 0)
        return null;

    double discriminantRoot = Math.sqrt(discriminant);
    if (discriminant == 0)
    {
        Vec4 p = line.getPointAt((-b - discriminantRoot) / (2 * a));
        return new Intersection[] {new Intersection(p, true)};
    }
    else // (discriminant > 0)
    {
        Vec4 near = line.getPointAt((-b - discriminantRoot) / (2 * a));
        Vec4 far = line.getPointAt((-b + discriminantRoot) / (2 * a));
        return new Intersection[] {new Intersection(near, false), new Intersection(far, false)};
    }
}

使用以下内容:

    /**
 * Calculates a discriminant. A discriminant is useful to determine the number of roots to a quadratic equation. If
 * the discriminant is less than zero, there are no roots. If it equals zero, there is one root. If it is greater
 * than zero, there are two roots.
 *
 * @param a the coefficient of the second order pronumeral
 * @param b the coefficient of the first order pronumeral
 * @param c the constant parameter in the quadratic equation
 *
 * @return the discriminant "b squared minus 4ac"
 */
private static double discriminant(double a, double b, double c)
{
    return b * b - 4 * a * c;
}

在这种情况下,你的代码无法通过以下测试:
if (discriminant < 0)

测试。

看起来我回答这个问题有点慢,与此同时Chris K指出这是由于intersect()方法期望线坐标以球的原点而不是地球的原点为中心。

正如Chris K所说,这似乎是一个错误,应该向这个源代码的维护者记录。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接