Problem
I'd like to apply a diffuse map to a MeshView. When I apply a Material with the diffuse map to the MeshView, it's not visible. The same material applied to a Box however is visible.
Question
What do I have to do to apply a diffuse map to a MeshView?
Code
The code generates an Image with random noise. The image is used as diffuse map in a PhongMaterial. The image is displayed, above it the box with the material applied, and above the box the MeshView (a pyramid) with the material applied. The material isn't visible on the pyramid. You can use mouse dragging for rotation.
import java.util.Random;
import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class Test extends Application {
private double mousePosX, mousePosY;
private double mouseOldX, mouseOldY;
private final Rotate rotateX = new Rotate(20, Rotate.X_AXIS);
private final Rotate rotateY = new Rotate(-45, Rotate.Y_AXIS);
@Override
public void start(Stage primaryStage) {
// cube
Group group = new Group();
// size of the cube
double size = 400;
group.getTransforms().addAll(rotateX, rotateY);
Image diffuseMap = createImage( size);
// show noise image
ImageView iv = new ImageView( diffuseMap);
iv.setTranslateX(-0.5*size);
iv.setTranslateY(-0.20*size);
iv.setRotate(90);
iv.setRotationAxis(new Point3D(1,0,0));
group.getChildren().add( iv);
// create material out of the noise image
PhongMaterial material = new PhongMaterial();
material.setDiffuseMap(diffuseMap);
// create box with noise diffuse map
Box box = new Box( 100,100,100);
box.setMaterial(material);
group.getChildren().add( box);
// create pyramid with diffuse map
float h = 150; // Height
float s = 150; // Side
TriangleMesh pyramidMesh = new TriangleMesh();
pyramidMesh.getTexCoords().addAll(1,1,1,0,0,1,0,0);
pyramidMesh.getPoints().addAll(
0, 0, 0, // Point 0 - Top
0, h, -s/2, // Point 1 - Front
-s/2, h, 0, // Point 2 - Left
s/2, h, 0, // Point 3 - Back
0, h, s/2 // Point 4 - Right
);
pyramidMesh.getFaces().addAll(
0,0, 2,0, 1,0, // Front left face
0,0, 1,0, 3,0, // Front right face
0,0, 3,0, 4,0, // Back right face
0,0, 4,0, 2,0, // Back left face
4,0, 1,0, 2,0, // Bottom rear face
4,0, 3,0, 1,0 // Bottom front face
);
MeshView pyramid = new MeshView(pyramidMesh);
pyramid.setDrawMode(DrawMode.FILL);
pyramid.setTranslateY(-250);
// apply material
// TODO: why is the diffuse map not displayed?
pyramid.setMaterial(material);
group.getChildren().add(pyramid);
// scene
StackPane root = new StackPane();
root.getChildren().add(group);
Scene scene = new Scene(root, 1600, 900, true, SceneAntialiasing.BALANCED);
scene.setCamera(new PerspectiveCamera());
// interaction listeners
scene.setOnMousePressed(me -> {
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
});
scene.setOnMouseDragged(me -> {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
rotateX.setAngle(rotateX.getAngle()-(mousePosY - mouseOldY));
rotateY.setAngle(rotateY.getAngle()+(mousePosX - mouseOldX));
mouseOldX = mousePosX;
mouseOldY = mousePosY;
});
primaryStage.setResizable(false);
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* Create image with random noise
*/
public Image createImage( double size) {
Random rnd = new Random();
int width = (int) size;
int height = (int) size;
WritableImage wr = new WritableImage(width, height);
PixelWriter pw = wr.getPixelWriter();
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
Color color = Color.rgb(rnd.nextInt( 256), rnd.nextInt( 256), rnd.nextInt( 256));
pw.setColor(x, y, color);
}
}
return wr;
}
public static void main(String[] args) {
launch(args);
}
}
Screenshot
Thank you very much for the help!
Problem
This shape consists of 6 single shapes. The four triangles all meet in point A.
And a rectangle at the bottom, because they are triangles you need two triangles for creating a rectangle. The following picture shows the topview of that shape.
As you can see, we have 5 points. So they have to be added to the Points of the TriangleMesh. One point consists of a tupel of three float values (x,y,z). So this Array always consists of a 3, 6, 9,...,15 and so on sizes.
Textures
If you would have an image as material of your mesh (like you do), you have to get the Texture Coordinates and add the image coordinates to them. But what do you add here? These are the 2D coordinates of your image, that you want to set as material. It begins by (0.0, 0.0) (u0, v0) and goes up to (1.0, 1.0) (u1, v1). It's the 2D coordiante of the image piece you want to show on your 3D mesh. As you only have a noisy image, you can do 0,0 to 1,1 but you need 3 points for the triangle.
Facing
Now you have to do the facing. This is like you want to place some plates in the space between the lines of you shape. As you already have seen, you have to add four pieces for the triangles and two pieces for the bottom.
The facing is a tuple of six values. Because we are painting triangles it is always something like: From point, to point, to point. So the tuple consists of 6 values. p0, t0, p1, t1, p2, t2. These values are indices to the points and texture arrays. p0 points to the first tree tuple of the point array, t0 points the first two tuple of texture coordinates array.
(counter) clockwise
Maybe my explanation is not fully right, but this is how I understood it:
The default camera of JavaFX is counter clockwise, so if you put the faces in counter clockwise your faces front will be visible to the camera. The faces backs not, this is done internally in JavaFX for performance issues. The back face will not be rendered with any material, as long as you do not set the culling to back.
To view the bottom of this triangle the camera have to change it's perspective, so it's clockwise. And again the same thing with face front and back.
In your example, I've named the points, because now you can see which faces I've rendered first and which one at last. For example:
ABC is the first face for me, it's the triangle between points A, B and C. These point's get their texture from the material image coordinates.
For more information on these things, visit the Blog from José Pereda: http://jperedadnr.blogspot.de/2015/01/creating-and-texturing-javafx-3d-shapes.html
Hopefully you have understand everything, then down below you'll find my solution.
Solution
import java.util.Random;
import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class Test extends Application {
private double mousePosX, mousePosY;
private double mouseOldX, mouseOldY;
private final Rotate rotateX = new Rotate(20, Rotate.X_AXIS);
private final Rotate rotateY = new Rotate(-45, Rotate.Y_AXIS);
@Override
public void start(Stage primaryStage) {
// cube
Group group = new Group();
// size of the cube
double size = 400;
group.getTransforms().addAll(rotateX, rotateY);
Image diffuseMap = createImage(size);
// show noise image
ImageView iv = new ImageView(diffuseMap);
iv.setTranslateX(-0.5 * size);
iv.setTranslateY(-0.20 * size);
iv.setRotate(90);
iv.setRotationAxis(new Point3D(1, 0, 0));
group.getChildren().add(iv);
// create material out of the noise image
PhongMaterial material = new PhongMaterial();
material.setDiffuseMap(diffuseMap);
// create box with noise diffuse map
Box box = new Box(100, 100, 100);
box.setMaterial(material);
group.getChildren().add(box);
// create pyramid with diffuse map
float h = 150; // Height
float s = 150; // Side
float hs = s / 2;
// coordinates of the mapped image
float x0 = 0.0f;
float y0 = 0.0f;
float x1 = 1.0f;
float y1 = 1.0f;
TriangleMesh pyramidMesh = new TriangleMesh();
pyramidMesh.getPoints().addAll( //
0.0f, 0.0f, 0.0f, // A 0 Top of Pyramid
hs, h, -hs, // B 1
hs, h, hs, // C 2
-hs, h, hs, // D 3
-hs, h, -hs // E 4
);
pyramidMesh.getTexCoords().addAll( //
x0, y0, // 0
x0, y1, // 1
x1, y0, // 2
x1, y1 // 3
);
pyramidMesh.getFaces().addAll(// index of point, index of texture, index of point, index of texture, index of point, index of texture
0, 0, 1, 1, 2, 3, // ABC (counter clockwise)
0, 0, 2, 1, 3, 3, // ACD (counter clockwise)
0, 0, 3, 1, 4, 3, // ADE (counter clockwise)
0, 0, 4, 1, 1, 3, // AEB (counter clockwise)
4, 0, 3, 1, 2, 3, // EDC (Bottom first triangle clock wise)
2, 0, 1, 1, 4, 3 // CBE (Bottom second triangle clock wise)
);
MeshView pyramid = new MeshView();
pyramid.setMesh(pyramidMesh);
pyramid.setDrawMode(DrawMode.FILL);
pyramid.setTranslateY(-250);
// apply material
// TODO: why is the diffuse map not displayed?
pyramid.setMaterial(material);
group.getChildren().add(pyramid);
// scene
StackPane root = new StackPane();
root.getChildren().add(group);
Scene scene = new Scene(root, 1600, 900, true, SceneAntialiasing.BALANCED);
scene.setCamera(new PerspectiveCamera());
// interaction listeners
scene.setOnMousePressed(me -> {
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
});
scene.setOnMouseDragged(me -> {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
rotateX.setAngle(rotateX.getAngle() - (mousePosY - mouseOldY));
rotateY.setAngle(rotateY.getAngle() + (mousePosX - mouseOldX));
mouseOldX = mousePosX;
mouseOldY = mousePosY;
});
primaryStage.setResizable(false);
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* Create image with random noise
*/
public Image createImage(double size) {
Random rnd = new Random();
int width = (int) size;
int height = (int) size;
WritableImage wr = new WritableImage(width, height);
PixelWriter pw = wr.getPixelWriter();
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
Color color = Color.rgb(rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
pw.setColor(x, y, color);
}
}
return wr;
}
public static void main(String[] args) {
launch(args);
}
}