是否有可能在 JavaFX 8 3D 场景中找到沿射线(例如 PickRay)的点,从 3D 空间中的任意点开始,具有一些 3D 方向矢量,其中射线与网格中的三角形相交(MeshView 中的三角形网格)?
我知道这是在 Camera/MouseHandler 中完成的,用于鼠标拾取,但我看不到任何方法来处理任意光线。
是否有可能在 JavaFX 8 3D 场景中找到沿射线(例如 PickRay)的点,从 3D 空间中的任意点开始,具有一些 3D 方向矢量,其中射线与网格中的三角形相交(MeshView 中的三角形网格)?
我知道这是在 Camera/MouseHandler 中完成的,用于鼠标拾取,但我看不到任何方法来处理任意光线。
正如@jdub1581 所建议的那样,射线只是一个几何向量,因此为了找到与该向量相交的三角形列表,我们需要解决“线与平面相交”和“线与三角形边界内的平面相交”的问题。
假设我们有一个TriangleMesh
,我们有一个顶点列表和一个面列表。每个顶点有 3 个坐标,每个面有 3 个顶点(不考虑纹理、法线、...)。为了简单起见,让我们使用两个列表Point3D
来存储它们。
在此链接中,有几个 3D 形状可供使用。让我们抓住一个CuboidMesh
。
CuboidMesh cuboid = new CuboidMesh(10f,12f,4f,4);
这将给我们这个 3D 形状:
现在,如果我们看一下网格,我们可以创建两个包含顶点和面的列表:
List<Point3D> vertices=Arrays.asList(new Point3D(5.0, 6.0, 2.0),
new Point3D(5.0, 6.0, 2.0), new Point3D(5.0, -6.0, 2.0), ...,
new Point3D(-1.875, -2.25, -2.0), new Point3D(-1.875, -1.5, -2.0));
List<Point3D> faces=Arrays.asList(new Point3D(0, 386, 388),
new Point3D(98, 387, 386.0), new Point3D(100, 388, 387), ...,
new Point3D(383, 1535, 1537), new Point3D(1536, 1537, 1535));
让我们在我们的场景中添加一些 3D 点,一个原点和一个目标,都在全局坐标中,并定义向量的方向,归一化:
Point3D gloOrigin=new Point3D(4,-7,-4);
Point3D gloTarget=new Point3D(2,3,2);
Point3D direction=gloTarget.subtract(gloOrigin).normalize(); // -0.154,0.771,0.617
光线方程将是这样的:
r(t) = (4,-7,-4)+t*(-0.154,0.771,0.617)
如果我们在这两点之间添加一个细长的圆柱体,我们将得到我们的射线的视觉表示:
边界框相交
第一步将检查光线是否与我们形状的边界框相交。在形状的局部坐标中,我们有 6 个面,由它们的法线给出,它们有 6 个中心:
Bounds locBounds = cuboid.getBoundsInLocal();
List<Point3D> normals=Arrays.asList(new Point3D(-1,0,0),new Point3D(1,0,0),
new Point3D(0,-1,0), new Point3D(0,1,0), new Point3D(0,0,-1), new Point3D(0,0,1));
List<Point3D> positions=Arrays.asList(new Point3D(locBounds.getMinX(),0,0),
new Point3D(locBounds.getMaxX(),0,0), new Point3D(0,locBounds.getMinY(),0),
new Point3D(0,locBounds.getMaxY(),0), new Point3D(0,0,locBounds.getMinZ()),
new Point3D(0,0,locBounds.getMaxZ()));
由于我们将在本地系统上工作,因此我们需要在此坐标中的原点:
Point3D gloOriginInLoc = cuboid.sceneToLocal(gloOrigin); // 4,-7,-4 since the box is centered in 0,0,0
现在,对于六个面中的任何一个,我们通过这个链接t
得到到平面的距离。然后我们可以检查该点是否属于该框。
AtomicInteger counter = new AtomicInteger();
IntStream.range(0, 6).forEach(i->{
double d=-normals.get(i).dotProduct(positions.get(i));
double t=-(gloOriginInLoc.dotProduct(normals.get(i))+d)/
(gloDirection.dotProduct(normals.get(i)));
Point3D locInter=gloOriginInLoc.add(gloDirection.multiply(t));
if(locBounds.contains(locInter)){
counter.getAndIncrement();
}
});
如果counter.get()>0
那时我们在射线和形状之间有一些交点,我们可以继续处理三角形。在本例中,这些将是交点:(3.5,-4.5,-2) 和 (2.5,0.5,2)。
三角形相交
有几种算法可用于查找光线是否与网格的任何三角形相交,因此我们不需要重新发明轮子。
我用过的是Tomas Möller & Ben Trumbore 的。它将提供t
从原点到平面的距离,以及u,v
给定交点的三角形内的坐标。
一旦我们有了形状的局部坐标原点,并且我们知道了射线的方向,这个算法的实现是这样的:
private final float EPS = 0.000001f;
public List<Point3D> getIntersections(Point3D origin, Point3D direction,
List<Point3D> points, List<Point3D> faces){
return faces.parallelStream().filter(f->{
// vertices indices
int p0=(int)f.getX();
int p1=(int)f.getY();
int p2=(int)f.getZ();
// vertices 3D coordinates
Point3D a = points.get(p0);
Point3D b = points.get(p1);
Point3D c = points.get(p2);
Point3D edge1 = b.substract(a);
Point3D edge2 = c.substract(a);
Point3D pvec=direction.crossProduct(edge2);
float det=edge1.dotProduct(pvec);
if(det<=-EPS || det>=EPS){
float inv_det=1f/det;
Point3D tvec=origin.substract(a);
float u = tvec.dotProduct(pvec)*inv_det;
if(u>=0f && u<=1f){
Point3D qvec=tvec.crossProduct(edge1);
float v = direction.dotProduct(qvec)*inv_det;
if(v>=0 && u+v<=1f){
float t = c.dotProduct(qvec)*inv_det;
System.out.println("t: "+t+", u: "+u+", v: "+v);
return true;
}
}
}
return false;
}).collect(Collectors.toList());
}
在这个样本中,我们找到三个面,由这些顶点给出:(85, 1245, 1274), (85, 1274, 1266) 和 (351, 1476, 1479)。
如果我们绘制这些面将看到交叉点:
请注意,通过在形状的局部坐标系中执行所有操作,我们保存了将每个三角形转换到全局系统的操作。
这个算法真的很快。我在不到 40 毫秒的时间内测试了多达 3M 个三角形。
此测试的所有代码都可以在此处获得。
好吧,我差点把这个给宰了,所以我将提供一个非常容易理解的教程。它写得很好,必须承认我也学到了很多东西!
我将把数学留在文章中,因为它涉及到很多内容(转换点和使用矩阵)
总结:
射线上的任何点都是与原点的距离的函数
Ray(t) = Origin + Direction(t)
希望这可以帮助!
编辑:
在 Jose 的出色示例之后,我冒昧地创建了一个 Ray 类和一个 SimpleRayTest 示例来显示光线在距离上的路径(将光线想象为抛射物)。虽然它不包括三角形交点,但它应该有助于可视化光线的工作原理。
Jose 提供的图书馆链接中也提供了资源。