如何在Java/JavaFX中基于2D点或路径创建自定义3D模型
在JavaFX中实现自定义带面的3D模型解决方案
核心思路
要实现类似Photoshop的带上下底面及侧面的3D模型,无需依赖第三方库,直接用JavaFX原生的TriangleMesh即可完成。核心是将用户选取的2D多边形(已转为3D点),拆解为上底面、下底面、侧面的三角面,并正确配置顶点、纹理坐标与三角面索引。
步骤分解
1. 生成上下底面的3D顶点
基于用户已有的上底面3D点,生成下底面顶点(Z值取上底面Z减去模型厚度,厚度可由用户指定):
// 用户已生成的上底面3D点 List<Point3D> topPoints = points3D; // 生成下底面顶点,厚度设为24(可替换为用户输入值) List<Point3D> bottomPoints = new ArrayList<>(); double thickness = 24; for (Point3D p : topPoints) { bottomPoints.add(new Point3D(p.getX(), p.getY(), p.getZ() - thickness)); }
2. 构建TriangleMesh的顶点数组
将上下底面的顶点按X、Y、Z顺序存入float数组,作为网格的顶点数据:
int totalVertex = topPoints.size() + bottomPoints.size(); float[] meshPoints = new float[totalVertex * 3]; int idx = 0; // 写入上底面顶点 for (Point3D p : topPoints) { meshPoints[idx++] = (float)p.getX(); meshPoints[idx++] = (float)p.getY(); meshPoints[idx++] = (float)p.getZ(); } // 写入下底面顶点 for (Point3D p : bottomPoints) { meshPoints[idx++] = (float)p.getX(); meshPoints[idx++] = (float)p.getY(); meshPoints[idx++] = (float)p.getZ(); }
3. 配置纹理坐标(可选,用于贴石材原图)
如果需要给模型贴石材纹理,将Canvas上的2D点转换为0-1范围的纹理坐标(UV):
int texCount = topPoints.size(); float[] texCoords = new float[texCount * 2]; idx = 0; double canvasWidth = canvas.getWidth(); double canvasHeight = canvas.getHeight(); // 转换原始2D点为纹理坐标(JavaFX纹理Y轴反向) for (Point2D p2d : points) { texCoords[idx++] = (float)(p2d.getX() / canvasWidth); texCoords[idx++] = (float)(1 - p2d.getY() / canvasHeight); } // 下底面复用上底面的纹理坐标 float[] fullTexCoords = new float[texCoords.length * 2]; System.arraycopy(texCoords, 0, fullTexCoords, 0, texCoords.length); System.arraycopy(texCoords, 0, fullTexCoords, texCoords.length, texCoords.length);
4. 生成三角面索引
这是保证模型正确显示的关键,分别生成上底面、下底面、侧面的三角面:
List<Integer> faceIndices = new ArrayList<>(); int topVertexCount = topPoints.size(); // 上底面三角面(假设顶点按顺时针/逆时针有序排列) for (int i = 1; i < topVertexCount - 1; i++) { // 格式:顶点索引,纹理索引, 顶点索引,纹理索引, 顶点索引,纹理索引 faceIndices.add(0); faceIndices.add(0); faceIndices.add(i); faceIndices.add(i); faceIndices.add(i+1); faceIndices.add(i+1); } // 下底面三角面(反转顶点顺序,保证法线方向正确) for (int i = 1; i < topVertexCount - 1; i++) { faceIndices.add(topVertexCount); faceIndices.add(0); faceIndices.add(topVertexCount + i + 1); faceIndices.add(i+1); faceIndices.add(topVertexCount + i); faceIndices.add(i); } // 侧面三角面(连接上下底面相邻顶点) for (int i = 0; i < topVertexCount; i++) { int nextIdx = (i + 1) % topVertexCount; // 第一个三角形 faceIndices.add(i); faceIndices.add(i); faceIndices.add(nextIdx); faceIndices.add(nextIdx); faceIndices.add(topVertexCount + i); faceIndices.add(i); // 第二个三角形 faceIndices.add(nextIdx); faceIndices.add(nextIdx); faceIndices.add(topVertexCount + nextIdx); faceIndices.add(nextIdx); faceIndices.add(topVertexCount + i); faceIndices.add(i); } // 转换为int数组 int[] faceArray = new int[faceIndices.size()]; for (int i = 0; i < faceIndices.size(); i++) { faceArray[i] = faceIndices.get(i); }
5. 创建MeshView并展示
将构建好的网格添加到3D场景,配置相机与光照:
TriangleMesh mesh = new TriangleMesh(); mesh.getPoints().setAll(meshPoints); mesh.getTexCoords().setAll(fullTexCoords); mesh.getFaces().setAll(faceArray); MeshView meshView = new MeshView(mesh); // 绑定石材纹理(可选) Image stoneTexture = imageView.getImage(); meshView.setMaterial(new PhongMaterial(Color.WHITE, stoneTexture, null, null, null)); // 构建3D场景 Group root3d = new Group(meshView); Scene scene = new Scene(root3d, 800, 600, true, SceneAntialiasing.BALANCED); scene.setFill(Color.GRAY); // 设置透视相机 PerspectiveCamera camera = new PerspectiveCamera(true); camera.setTranslateZ(-1000); // 调整距离让模型完整显示 scene.setCamera(camera); // 添加光照 AmbientLight ambientLight = new AmbientLight(Color.LIGHTGRAY); PointLight pointLight = new PointLight(Color.WHITE); pointLight.setTranslateX(500); pointLight.setTranslateY(-300); pointLight.setTranslateZ(-1000); root3d.getChildren().addAll(ambientLight, pointLight); // 显示窗口 Stage stage = new Stage(); stage.setScene(scene); stage.show();
关键注意事项
- 顶点顺序:用户选取的2D点必须按顺时针或逆时针有序排列,否则三角面会出现背面消隐问题。若点顺序混乱,需先按多边形中心点的极角排序。
- 坐标偏移:Canvas原点在左上角,3D场景原点默认在中心,可通过平移顶点(如X减去Canvas宽度一半、Y减去高度一半)让模型居中。
- 背面消隐调试:若模型显示不全,可临时设置
meshView.setCullFace(CullFace.NONE)关闭背面消隐,最终需保证三角面法线方向正确。
内容的提问来源于stack exchange,提问作者FarHooD
相关产品推荐
相关产品推荐

