JavaFX 3D Graphics has 3 pre-defined shapes (cylinder, box, sphere); other shapes require the use of MeshView which the following code utilizes to create a multi-colored 3D cube (not obtainable with ‘box’). For background reading, the reference by Peter Lager is recommended.
The cube is drawn into the default Processing window by taking advantage of the StackPane that the FX2D renderer was designed with. A TriangleMesh is intially used to create the vertices and textures and then passed to the MeshView. A unique solid color taken from the .png image below is used for each triangle; there are two triangles per face. Numbering of the cube vertices required trial and error and studying openGL numbering systems used by others. A helpful template used to construct the cube is shown below; otherwise it is difficult to visualize all of the coordinates. Base indices are marked with circles. Counter clockwise winding was used for all triangles; ie, whatever index was used as the starting point, subsequent indices were coded in a counter clockwise direction. It takes 6 integers to set the color for each triangle: pt1, tex1, pt2, tex2, pt3, tex3. In our example tex1…3 are all the same number since we are using a single color per triangle.
Animation in the three axes (x,y,z) was connected to three JavaFX buttons taking advantage of RotateTransition and does not utilize a camera.
Source code:
// Reference: http://lagers.org.uk/javafx/usemeshview.html
import javafx.scene.canvas.Canvas;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.Pane;
import javafx.scene.AmbientLight;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.shape.Line;
import javafx.scene.shape.DrawMode;
import javafx.scene.image.Image;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import javafx.scene.transform.Rotate;
import javafx.animation.RotateTransition;
import javafx.util.Duration;
import javafx.scene.control.Button;
Canvas canvas;
StackPane root;
Pane pane;
MeshView meshView;
Group group;
int _wndW = 600;
int _wndH = 500;
MeshView cube(float s) {
TriangleMesh mesh = new TriangleMesh();
mesh.getPoints().addAll(
-s, -s, s,
s, -s, s,
-s, s, s,
s, s, s,
-s, -s, -s,
s, -s, -s,
-s, s, -s,
s, s, -s
);
// Taken from .png image mapped to a 1.0 x 1.0 coordinate system
float[] texCoords = {
0.042, 0.5, // 0 Front triangle1
0.125, 0.5, // 1 Front triangle2
0.208, 0.5, // 2 Back triangle1
0.291, 0.5, // 3 Back triangle2
0.375, 0.5, // 4 Top triangle1
0.458, 0.5, // 5 Top triangle2
0.542, 0.5, // 6 Bottom triangle1
0.625, 0.5, // 7 Bottom triangle2
0.708, 0.5, // 8 Right triangle1
0.792, 0.5, // 9 Right triangle2
0.875, 0.5, // 10 Left triangle1
0.958, 0.5 // 11 Left triangle2
};
// Each triangle requires 6 integers: pt1, tex1, pt2, tex2, pt3, tex3 (point index, texture index)
// Each face has two triangles
// Use counterclockwise winding for each triangle
int[] faces = {
0, 0, 2, 0, 3, 0, // Front triangle1
0, 1, 3, 1, 1, 1, // Front triangle2
4, 2, 7, 2, 6, 2, // Back triangle1
4, 3, 5, 3, 7, 3, // Back triangle2
2, 4, 6, 4, 7, 4, // Top triangle1
2, 5, 7, 5, 3, 5, // Top triangle2
0, 6, 5, 6, 4, 6, // Bottom triangle1
0, 7, 1, 7, 5, 7, // Bottom triangle2
1, 8, 3, 8, 7, 8, // Right triangle1
1, 9, 7, 9, 5, 9, // Right triangle2
0, 10, 6, 10, 2, 10, // Left triangle1
0, 11, 4, 11, 6, 11 // Left triangle2
};
mesh.getFaces().addAll(faces);
MeshView meshView = new MeshView(mesh);
meshView.setLayoutX(250);
meshView.setLayoutY(200);
PhongMaterial material = new PhongMaterial();
try {
Image image = new Image(new FileInputStream(sketchPath() + "/triangleColors.png"));
material.setDiffuseMap(image);
}
catch ( FileNotFoundException e) {
println("File not found: ", e);
}
meshView.setMaterial(material);
meshView.setCullFace(CullFace.BACK);
mesh.getTexCoords().addAll(texCoords);
group = new Group(meshView, new AmbientLight(Color.WHITE));
return meshView;
}
void doRotation(int axis) {
RotateTransition rt = new RotateTransition(Duration.millis(3000), meshView);
rt.setByAngle(360); // Rotate 360 degrees
if (axis == 0) {
rt.setAxis(Rotate.X_AXIS);
} else if (axis == 1) {
rt.setAxis(Rotate.Y_AXIS);
} else {
rt.setAxis(Rotate.Z_AXIS);
}
rt.setCycleCount(RotateTransition.INDEFINITE); // Repeat indefinitely
rt.setAutoReverse(true); // Rotate back and forth
rt.play(); // Start animation
}
void setup() {
size(_wndW, _wndH, FX2D);
surface.setTitle("JavaFX MeshView 3D Cube Default Wnd");
canvas = (Canvas)surface.getNative();
root = (StackPane)canvas.getParent();
pane = new Pane();
// Axes
Line xAxis = new Line(400, 50, 540, 50);
xAxis.setStroke(Color.color(1.0, 0.0, 0.0, 0.95));
xAxis.setStrokeWidth(5);
Line yAxis = new Line(400, 50, 400, 190);
yAxis.setStroke(Color.color(0.0, 0.0, 1.0, 0.95));
yAxis.setStrokeWidth(5);
Line zAxis = new Line(400, 45, 430, 15);
zAxis.setStroke(Color.color(0.0, 1.0, 0.0, 0.95));
zAxis.setStrokeWidth(5);
// Axes Labels
textSize(16);
fill(0);
text("X", 550, 55);
text("Y", 395, 210);
text("Z", 435, 20);
text("Axes", 450, 150);
// Controls
Button btnX = new Button("X");
btnX.setLayoutX(30);
btnX.setLayoutY(30);
btnX.setOnAction(e -> {
doRotation(0);
}
);
Button btnY = new Button("Y");
btnY.setLayoutX(60);
btnY.setLayoutY(30);
btnY.setOnAction(e -> {
doRotation(1);
}
);
Button btnZ = new Button("Z");
btnZ.setLayoutX(90);
btnZ.setLayoutY(30);
btnZ.setOnAction(e -> {
doRotation(2);
}
);
meshView = cube(100);
meshView.setLayoutX(200);
meshView.setLayoutY(250);
pane.getChildren().addAll(meshView, group, btnX, btnY, btnZ, xAxis, yAxis, zAxis);
root.getChildren().add(pane);
}
Cube Template:
triangleColors.png (copy/paste to sketch folder):

