使用 JavaFX 3D 创建坐标网格最实用的方法是什么?

What is the most practical way to create coordinate grid with JavaFX 3D?

我想用 JavaFX 创建一个 3D 演示应用程序来可视化 3D 中点的移动 space,首先我需要设置一个坐标网格以供视觉参考。不幸的是,我找不到像这张图片中这样的网格的示例代码:

有谁知道创建类似的东西最实用的方法是什么?

已经有一些解决方案了。

FXyz3D library has a CubeWorld class,正好给你一个参考网格。

使用起来相当简单。只需从 JCenter 导入 'org.fxyz3d:fxyz3d:0.3.0' 依赖项并使用它:

CubeWorld cubeWorld = new CubeWorld(5000, 500, true);
Sphere sphere = new Sphere(100);
sphere.setMaterial(new PhongMaterial(Color.FIREBRICK));
sphere.getTransforms().add(new Translate(100, 200, 300));

Scene scene = new Scene(new Group(cubeWorld, sphere), 800, 800, true, SceneAntialiasing.BALANCED);

如您所见,解决方案是基于对每个面使用 2D 矩形,并使用 3D 圆柱体创建网格线。它有很好的功能(比如自闪电或根据相机的正面不显示网格),但它的节点非常密集(上面的示例有 168 个节点)。

还有其他使用较少节点的解决方案。比如这个sample,正好也和Leap Motion有关,我就用了一个TriangleMesh.

这是一个简单的解决方案,而且只有两个网格。但是,您看到的是三角形,而不是正方形。

所以让我们尝试摆脱三角形。为此,我将使用 PolygonMesh,就像在另一个 , based on the 3DViewer project that is available at the OpenJFX repository, contains already a PolygonalMesh 实现中一样,它允许每个面有任意数量的点,因此任何多边形都可以是面。

这将为您提供基于方形面的平面网格:

private PolygonMesh createQuadrilateralMesh(float width, float height, int subDivX, int subDivY) {
    final float minX = - width / 2f;
    final float minY = - height / 2f;
    final float maxX = width / 2f;
    final float maxY = height / 2f;

    final int pointSize = 3;
    final int texCoordSize = 2;
    // 4 point indices and 4 texCoord indices per face
    final int faceSize = 8;
    int numDivX = subDivX + 1;
    int numVerts = (subDivY + 1) * numDivX;
    float points[] = new float[numVerts * pointSize];
    float texCoords[] = new float[numVerts * texCoordSize];
    int faceCount = subDivX * subDivY;
    int faces[][] = new int[faceCount][faceSize];

    // Create points and texCoords
    for (int y = 0; y <= subDivY; y++) {
        float dy = (float) y / subDivY;
        double fy = (1 - dy) * minY + dy * maxY;

        for (int x = 0; x <= subDivX; x++) {
            float dx = (float) x / subDivX;
            double fx = (1 - dx) * minX + dx * maxX;

            int index = y * numDivX * pointSize + (x * pointSize);
            points[index] = (float) fx;
            points[index + 1] = (float) fy;
            points[index + 2] = 0.0f;

            index = y * numDivX * texCoordSize + (x * texCoordSize);
            texCoords[index] = dx;
            texCoords[index + 1] = dy;
        }
    }

    // Create faces
    int index = 0;
    for (int y = 0; y < subDivY; y++) {
        for (int x = 0; x < subDivX; x++) {
            int p00 = y * numDivX + x;
            int p01 = p00 + 1;
            int p10 = p00 + numDivX;
            int p11 = p10 + 1;
            int tc00 = y * numDivX + x;
            int tc01 = tc00 + 1;
            int tc10 = tc00 + numDivX;
            int tc11 = tc10 + 1;

            faces[index][0] = p00;
            faces[index][1] = tc00;
            faces[index][2] = p10;
            faces[index][3] = tc10;
            faces[index][4] = p11;
            faces[index][5] = tc11;
            faces[index][6] = p01;
            faces[index++][7] = tc01;
        }
    }

    int[] smooth = new int[faceCount];

    PolygonMesh mesh = new PolygonMesh(points, texCoords, faces);
    mesh.getFaceSmoothingGroups().addAll(smooth);
    return mesh;
}

因此您可以使用其中的 2 个或 3 个来创建这样的坐标系:

public Group createGrid(float size, float delta) {
    if (delta < 1) {
        delta = 1;
    }
    final PolygonMesh plane = createQuadrilateralMesh(size, size, (int) (size / delta), (int) (size / delta));

    final PolygonMesh plane2 = createQuadrilateralMesh(size, size, (int) (size / delta / 5), (int) (size / delta / 5));

    PolygonMeshView meshViewXY = new PolygonMeshView(plane);
    meshViewXY.setDrawMode(DrawMode.LINE);
    meshViewXY.setCullFace(CullFace.NONE);

    PolygonMeshView meshViewXZ = new PolygonMeshView(plane);
    meshViewXZ.setDrawMode(DrawMode.LINE);
    meshViewXZ.setCullFace(CullFace.NONE);
    meshViewXZ.getTransforms().add(new Rotate(90, Rotate.X_AXIS));

    PolygonMeshView meshViewYZ = new PolygonMeshView(plane);
    meshViewYZ.setDrawMode(DrawMode.LINE);
    meshViewYZ.setCullFace(CullFace.NONE);
    meshViewYZ.getTransforms().add(new Rotate(90, Rotate.Y_AXIS));

    PolygonMeshView meshViewXY2 = new PolygonMeshView(plane2);
    meshViewXY2.setDrawMode(DrawMode.LINE);
    meshViewXY2.setCullFace(CullFace.NONE);
    meshViewXY2.getTransforms().add(new Translate(size / 1000f, size / 1000f, 0));

    PolygonMeshView meshViewXZ2 = new PolygonMeshView(plane2);
    meshViewXZ2.setDrawMode(DrawMode.LINE);
    meshViewXZ2.setCullFace(CullFace.NONE);
    meshViewXZ2.getTransforms().add(new Translate(size / 1000f, size / 1000f, 0));
    meshViewXZ2.getTransforms().add(new Rotate(90, Rotate.X_AXIS));

    PolygonMeshView meshViewYZ2 = new PolygonMeshView(plane2);
    meshViewYZ2.setDrawMode(DrawMode.LINE);
    meshViewYZ2.setCullFace(CullFace.NONE);
    meshViewYZ2.getTransforms().add(new Translate(size / 1000f, size / 1000f, 0));
    meshViewYZ2.getTransforms().add(new Rotate(90, Rotate.Y_AXIS));

    return new Group(meshViewXY, meshViewXY2, meshViewXZ, meshViewXZ2 /*, meshViewYZ, meshViewYZ2 */);
}

请注意,我已经复制了平面以每 5 行模拟一个更宽的笔划。

最后添加坐标轴:

public Group getAxes(double scale) {
    Cylinder axisX = new Cylinder(1, 200);
    axisX.getTransforms().addAll(new Rotate(90, Rotate.Z_AXIS), new Translate(0, -100, 0));
    axisX.setMaterial(new PhongMaterial(Color.RED));

    Cylinder axisY = new Cylinder(1, 200);
    axisY.getTransforms().add(new Translate(0, 100, 0));
    axisY.setMaterial(new PhongMaterial(Color.GREEN));

    Cylinder axisZ = new Cylinder(1, 200);
    axisZ.setMaterial(new PhongMaterial(Color.BLUE));
    axisZ.getTransforms().addAll(new Rotate(90, Rotate.X_AXIS), new Translate(0, 100, 0));

    Group group = new Group(axisX, axisY, axisZ);
    group.getTransforms().add(new Scale(scale, scale, scale));
    return group;
}

现在你有:

final Group axes = getAxes(0.5);
final Group grid = createGrid(200, 10);

final Sphere sphere = new Sphere(5);
sphere.getTransforms().add(new Translate(20, 15, 40));

Scene scene = new Scene(new Group(axes, grid, sphere), 800, 800, true, SceneAntialiasing.BALANCED);

本次样本的节点总数为14。

当然,还可以添加标签等诸多功能进行改进。