Search code examples
javajavafx3djavafx-3d

How to set LinearGradient for 3D Box in JavaFX


I've found some similar answer to my question but the solution was dedicated to the sphere. Anyway I tried it and of course it's not what I expected.

the code I used is:

Scene aux2 = new Scene(new StackPane(), 100, 100,
                new LinearGradient(0, 0, 1, 0, true, CycleMethod.REFLECT,
                        new Stop(0, Color.GREEN), new Stop(0.3, Color.YELLOW),
                        new Stop(0.6, Color.BLUE), new Stop(0.9, Color.RED)));


        WritableImage snapshot = aux2.snapshot(null);

        PhongMaterial material = new PhongMaterial();
        material.setDiffuseMap(snapshot);

        box.setMaterial(material);

the effect is here: https://imagizer.imageshack.com/img922/589/VEL43d.png

How to get the same filling (which is in ellipse)around the box and set the top surface with the top color of the gradient ?


Solution

  • The sphere solution that you have mentioned doesn't work for a 3D box, because the built-in box will apply the exact same diffuse image to each and every flat 6 faces of the box.

    This other solution uses a custom mesh for the 3D box, so you can play in a different way with the diffuse image.

    We can adapt that solution to your use case, as, if I get it right, you want to apply the same image with a linear gradient to the 4 vertical faces, while keeping the border color on the 2 horizontal faces.

    So we need an image like this:

    Scene aux2 = new Scene(new StackPane(), 10, 100,
                new LinearGradient(0, 1, 0, 0, true, CycleMethod.REFLECT,
                        new Stop(0, Color.GREEN), new Stop(0.3, Color.YELLOW),
                        new Stop(0.6, Color.BLUE), new Stop(0.9, Color.RED)));
    
    WritableImage snapshot = aux2.snapshot(null);
    PhongMaterial material = new PhongMaterial(Color.WHITE);
    material.setDiffuseMap(snapshot);
    

    Note that I've set a vertical linear gradient. If you render that image you will get something like:

    Now you need to generate a cuboid mesh:

    public MeshView createCuboid(float w, float h, float d) {
        float hw = w / 2f;
        float hh = h / 2f;
        float hd = d / 2f;
    
        float points[] = {
                hw,  hh,  hd,
                hw,  hh, -hd,
                hw, -hh,  hd,
                hw, -hh, -hd,
                -hw,  hh,  hd,
                -hw,  hh, -hd,
                -hw, -hh,  hd,
                -hw, -hh, -hd};
    
        float tex[] = {
                0.01f, 0.01f,
                0.01f, 0.99f};
    
        float normals[] = {
                1f,  0f,  0f,
                -1f,  0f,  0f,
                0f,  1f,  0f,
                0f, -1f,  0f,
                0f,  0f,  1f,
                0f,  0f, -1f,
        };
    
        int faces[] = {
                0, 0, 0, 2, 0, 1, 1, 0, 0,
                2, 0, 1, 3, 0, 1, 1, 0, 0,
                4, 1, 0, 5, 1, 0, 6, 1, 1,
                6, 1, 1, 5, 1, 0, 7, 1, 1,
                0, 2, 0, 1, 2, 0, 4, 2, 0,
                4, 2, 0, 1, 2, 0, 5, 2, 0,
                2, 3, 1, 6, 3, 1, 3, 3, 1,
                3, 3, 1, 6, 3, 1, 7, 3, 1,
                0, 4, 0, 4, 4, 0, 2, 4, 1,
                2, 4, 1, 4, 4, 0, 6, 4, 1,
                1, 5, 0, 3, 5, 1, 5, 5, 0,
                5, 5, 0, 3, 5, 1, 7, 5, 1};
    
        TriangleMesh mesh = new TriangleMesh();
        mesh.setVertexFormat(VertexFormat.POINT_NORMAL_TEXCOORD);
        mesh.getPoints().addAll(points);
        mesh.getTexCoords().addAll(tex);
        mesh.getNormals().addAll(normals);
        mesh.getFaces().addAll(faces);
    
        return new MeshView(mesh);
    }
    

    If you check the texture coordinates, I've just selected two pair of coordinates, one almost at the bottom of the diffuse image, and one almost at the top. JavaFX will do the rest interpolating every pixel from the given image. I didn't pick the border to prevent any possible issue with different colors at the border of the image.

    The faces array lists for every triangle (up to 12) the indices of the three vertices, three normals, and a three texture coordinates.

    For instance, the first triangle has vertices with indices 0, 2, 1, the normal for each vertex is 0, and the texture indices are 0, 1, 0 (bottom - top - bottom). The next triangle has indices 2, 3, 1, same normal 0, and texture indices 1, 1, 0 (top - top - bottom). Note that bottom is red, top is green (Y coordinate goes down from top-left corner of the image).

    The faces with indices 4, 5 have texture indices 0, 0, 0, so these will be at the bottom, and the faces 6, 7, have indices 1, 1, 1, so these will be at the top.

    So now all we need is:

    MeshView mv = createCuboid(10, 100, 10);
    mv.setMaterial(material);
    Group cuboidGroup = new Group(mv);
    Scene scene = new Scene(cuboidGroup, 400, 600, true, SceneAntialiasing.BALANCED);
    

    Resulting in something like:

    Hopefully, this is the result you were looking for. Otherwise, you can play with the gradient and the mesh to achieve it.