Why is the diffuse map not applied to my MeshView?
Asked Answered
M

1

2

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

enter image description here

Thank you very much for the help!

Muse answered 29/6, 2015 at 7:42 Comment(0)
Z
5

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.

topview

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);
    }
}

enter image description here

Zulazulch answered 29/6, 2015 at 14:13 Comment(3)
Wow, and there I thought it would be easy. Thanks a lot for the help!Muse
Your welcome! By the way, there is a good book to get more info of JavaFX 3D: Pro JavaFX 8 from Vos et. al. apress.com/pro-javafx-8Zulazulch
Thank you for the book-hint, I'll check it out. One more question: What's with the clockwise and counter clockwise directions? I guess for the points it's irrelevant, but for the texture and faces it seems to be important. At least that's what I gathered from when I created a chart sample. Haven't found any documentation on it.Muse

© 2022 - 2024 — McMap. All rights reserved.