Solved
The solution related to smoothing groups.
Within the loadFixedSphereMeshView() method, I added:
... after points array
int faceSmoothingGroups[] = {
0, 0, 0, 0, 0, 0, 0, 0
};
... before faces array
and then added:
... after setting the faces
mesh.getFaceSmoothingGroups().setAll(faceSmoothingGroups);
Note: The number of items in the faceSmootingGroups array must equal the number of faces in the mesh.
My two TriangleMesh based Octahedrons are rendering with strange dark 'shadows' whereas the standard Box is rendering perfectly. I am trying to understand what I am doing wrong when I construct the Octahedrons.
Please take a quick look at the image and short video I have included as these illustrate the visual artifact I am trying to understand.
Short Video Illustrating Rendering
The Coordinate System I am referencing
I am working on a small application that will display a 3D spherical mesh generated by MeshBuilder - a class in another project. The starting point for that spherical mesh is a simple Octahedron. The included code below is a small application intended for me to understand how JavaFX 3D works as I am new to it.
The program displays a standard JavaFX green Box in the center, a red Octahedron to the right and a second blue Octahedron to the left. There are check boxes that change the rendering of the Octahedrons while the program is running.
The red Octahedron is constructed 'manually' in the method loadFixedSphereMeshView() and the blue Octahedron is constructed by using the MeshBuilder in loadProceeduralSphereMeshView(). The code in MeshBuilder is unit tested against the hard coded Octahedron to ensure it is building the same Octahedron.
I've set things up this way so that I can:
- See how meshes should be rendered by referencing the Box
- Change the code for the red Octahedron to get it to render correctly
- Unit test the MeshBuilder against the red Octahedron data once that renders correctly
I'm focussed on trying to understand what I am doing wrong when I construct the red Octahedron manually that results in it rendering with those 'shadow' artifacts.
The jerkiness of the video is the capture software not the program.
Thanks for reading.
public class Main extends Application {
private static final int VIEWPORT_SIZE = 800;
private static final String textureLoc = "https://www.sketchuptextureclub.com/public/texture_f/slab-marble-emperador-cream-light-preview.jpg";
private Image texture;
private final PhongMaterial texturedMaterial = new PhongMaterial();
private final PhongMaterial redMaterial = new PhongMaterial();
private final PhongMaterial blueMaterial = new PhongMaterial();
private final PhongMaterial greenMaterial = new PhongMaterial();
private final MeshView fixedSphereMeshView = loadFixedSphereMeshView();
private final MeshView proceeduralSphereMeshView = loadProceeduralSphereMeshView();
@Override
public void start(Stage stage) throws Exception {
texture = new Image(textureLoc);
texturedMaterial.setDiffuseMap(texture);
redMaterial.setDiffuseColor(Color.RED);
blueMaterial.setDiffuseColor(Color.BLUE);
greenMaterial.setDiffuseColor(Color.GREEN);
Group group = buildScene();
RotateTransition rotate = rotate3dGroup(group);
VBox layout = new VBox(
createControls(rotate),
createScene3D(group)
);
// Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(layout);
stage.setTitle("Icosphere Viewer");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
System.setProperty("prism.dirtyopts", "false");
launch(args);
}
private MeshView loadFixedSphereMeshView() {
float[] points = {
0, -1, 0, // p0
0, 0, -1, // p1
1, 0, 0, // p2
0, 0, 1, // p3
-1, 0, 0, // p4
0, 1, 0 // p5
};
float[] texCoords = {
1, 1, // index t0
1, 0, // index t1
0, 1, // index t2
0, 0 // index t3
};
int[] faces = {
0, 0, 1, 1, 2, 2, // p0 t0 p1 t1 p2 t2
0, 0, 2, 2, 3, 3, // p0 t0 p2 t2 p3 t3
0, 0, 3, 1, 4, 2, // p0 t0 p3 t1 p4 t2
0, 0, 4, 2, 1, 3, // p0 t0 p4 t2 p1 t3
5, 0, 2, 0, 1, 0, // p5 t0 p2 t0 p1 t0
5, 0, 3, 0, 2, 0, // p5 t0 p3 t0 p2 t0
5, 0, 4, 0, 3, 0, // p5 t0 p4 t0 p3 t0
5, 0, 1, 0, 4, 0 // p5 t0 p1 t0 p4 t0
};
TriangleMesh mesh = new TriangleMesh();
mesh.getPoints().setAll(points);
mesh.getTexCoords().setAll(texCoords);
mesh.getFaces().setAll(faces);
return new MeshView(mesh);
}
private MeshView loadProceeduralSphereMeshView() {
SphereMesh sphereMesh = MeshBuilder.buildOctosphereMesh(0);
float[] points = sphereMesh.getPoints();
float[] texCoords = {
1, 1, // index t0
1, 0, // index t1
0, 1, // index t2
0, 0 // index t3
};
int[] faces = sphereMesh.getFaces();
TriangleMesh mesh = new TriangleMesh();
mesh.getPoints().setAll(points);
mesh.getTexCoords().setAll(texCoords);
mesh.getFaces().setAll(faces);
return new MeshView(mesh);
}
private Group buildScene() {
Box box = new Box(1, 1, 1);
box.setTranslateX(0);
box.setTranslateY(0);
box.setTranslateZ(0);
box.setMaterial(greenMaterial);
proceeduralSphereMeshView.setTranslateX(-2);
proceeduralSphereMeshView.setTranslateY(0);
proceeduralSphereMeshView.setTranslateZ(0);
proceeduralSphereMeshView.setMaterial(blueMaterial);
fixedSphereMeshView.setTranslateX(2);
fixedSphereMeshView.setTranslateY(0);
fixedSphereMeshView.setTranslateZ(0);
fixedSphereMeshView.setMaterial(redMaterial);
Group group = new Group(proceeduralSphereMeshView, fixedSphereMeshView, box);
group.setTranslateZ(10);
return group;
}
private SubScene createScene3D(Group group) {
SubScene scene3d = new SubScene(group, VIEWPORT_SIZE, VIEWPORT_SIZE * 9.0 / 16, true, SceneAntialiasing.BALANCED);
scene3d.setFill(Color.LIGHTGRAY);
PerspectiveCamera camera = new PerspectiveCamera(true);
scene3d.setCamera(camera);
return scene3d;
}
private VBox createControls(RotateTransition rotateTransition) {
CheckBox cull = new CheckBox("Cull Back");
proceeduralSphereMeshView.cullFaceProperty().bind(
Bindings.when(
cull.selectedProperty())
.then(CullFace.BACK)
.otherwise(CullFace.NONE)
);
fixedSphereMeshView.cullFaceProperty().bind(
Bindings.when(
cull.selectedProperty())
.then(CullFace.BACK)
.otherwise(CullFace.NONE)
);
CheckBox wireframe = new CheckBox("Wireframe");
proceeduralSphereMeshView.drawModeProperty().bind(
Bindings.when(
wireframe.selectedProperty())
.then(DrawMode.LINE)
.otherwise(DrawMode.FILL)
);
fixedSphereMeshView.drawModeProperty().bind(
Bindings.when(
wireframe.selectedProperty())
.then(DrawMode.LINE)
.otherwise(DrawMode.FILL)
);
CheckBox texture = new CheckBox("Texture");
proceeduralSphereMeshView.materialProperty().bind(
Bindings.when(
texture.selectedProperty())
.then(texturedMaterial)
.otherwise((PhongMaterial) blueMaterial)
);
fixedSphereMeshView.materialProperty().bind(
Bindings.when(
texture.selectedProperty())
.then(texturedMaterial)
.otherwise((PhongMaterial) redMaterial)
);
CheckBox rotate = new CheckBox("Rotate");
rotate.selectedProperty().addListener(observable -> {
if (rotate.isSelected()) {
rotateTransition.play();
} else {
rotateTransition.pause();
}
});
VBox controls = new VBox(10, rotate, texture, cull, wireframe);
controls.setPadding(new Insets(10));
return controls;
}
private RotateTransition rotate3dGroup(Group group) {
RotateTransition rotate = new RotateTransition(Duration.seconds(10), group);
rotate.setAxis(Rotate.Y_AXIS);
rotate.setFromAngle(0);
rotate.setToAngle(360);
rotate.setInterpolator(Interpolator.LINEAR);
rotate.setCycleCount(RotateTransition.INDEFINITE);
return rotate;
}
}