Coloring individual triangles in a triangle mesh on javafx
Asked Answered
P

2

7

I have a triangle mesh object in JAVAFX and would like to either

  1. color individual triangles of the triangle mesh

or

  1. color individual vertices of each triangle and have the triangle be colored according to an interpolation of the colors of each vertex (say for instance with a Gouraud shading).

The particular triangle mesh object is an icosphere with millions of faces (that is why I am using a triangle mesh: I need speed).

I have NOT used texture coordinates since I have been unable to find a clear explanation to it using JAVAFX, plus I am hoping that there is a easier way.

Papaverine answered 9/11, 2014 at 18:43 Comment(0)
F
13

The way coloring works in JavaFX 3D meshes is by the material you assign them. For one mesh there's one material, and it's not possible to assing different materials to different triangles of the same mesh.

So if you want to avoid textures, I'm afraid the only way is grouping triangles with the same color in the same mesh, and creating so many meshes as colors.

On the contrary, with textures is relatively easy..., since you have just one mesh, one material and one image with all the coloring.

I've made an example of an icosahedron, built a triangle mesh for it and added a single texture to color all the faces.

For that, we need:

  • the 3D coordinates of the 12 vertex,
  • the 2D normalized coordinates of the uv mapping for the textures.
  • and the 20 faces. Each face is defined by 6 indices p0, t0, p1, t1, p3, t3, where p0, p1, p2 and p3 are indices into the points array, and t0, t1, t2 and t3 are indices into the texCoords array.

    public class IcosahedronMesh extends MeshView {

    public IcosahedronMesh(){
        setMesh(createCube());
    }
    private TriangleMesh createCube() {
        TriangleMesh m = new TriangleMesh();
    
        // POINTS
        m.getPoints().addAll(
            0f, 0f, -0.951057f, 
            0f, 0f, 0.951057f, 
            -0.850651f, 0f, -0.425325f, 
            0.850651f, 0f, 0.425325f, 
            0.688191f, -0.5f, -0.425325f, 
            0.688191f, 0.5f, -0.425325f, 
            -0.688191f, -0.5f, 0.425325f, 
            -0.688191f, 0.5f, 0.425325f, 
            -0.262866f, -0.809017f, -0.425325f, 
            -0.262866f, 0.809017f, -0.425325f, 
            0.262866f, -0.809017f, 0.425325f, 
            0.262866f, 0.809017f, 0.425325f
        );
    
        // TEXTURES
        m.getTexCoords().addAll(
                0.181818f, 0f, 
                0.363636f, 0f, 
                0.545455f, 0f, 
                0.727273f, 0f, 
                0.909091f, 0f,
                0.0909091f, 0.333333f,
                0.272727f, 0.333333f, 
                0.454545f, 0.333333f, 
                0.636364f, 0.333333f, 
                0.818182f, 0.333333f, 
                1f, 0.333333f, 
                0f, 0.666667f, 
                0.181818f, 0.666667f, 
                0.363636f, 0.666667f, 
                0.545455f, 0.666667f, 
                0.727273f, 0.666667f, 
                0.909091f, 0.666667f, 
                0.0909091f, 1f, 
                0.272727f, 1f, 
                0.454545f, 1f, 
                0.636364f, 1f, 
                0.818182f, 1f
        );
    
        // FACES
        m.getFaces().addAll(
                1, 6, 11, 5, 7, 0, 
                1, 12, 7, 11, 6, 5, 
                1, 7, 6, 6, 10, 1, 
                1, 13, 10, 12, 3, 6, 
                1, 8, 3, 7, 11, 2,
                4, 14, 8, 13, 0, 7, 
                5, 9, 4, 8, 0, 3, 
                9, 15, 5, 14, 0, 8, 
                2, 10, 9, 9, 0, 4, 
                8, 16, 2, 15, 0, 9,
                11, 5, 9, 6, 7, 12,
                7, 11, 2, 12, 6, 17, 
                6, 6, 8, 7, 10, 13, 
                10, 12, 4, 13, 3, 18, 
                3, 7, 5, 8, 11, 14,
                4, 13, 10, 14, 8, 19, 
                5, 8, 3, 9, 4, 15, 
                9, 14, 11, 15, 5, 20, 
                2, 9, 7, 10, 9, 16, 
                8, 15, 6, 16, 2, 21
        );
        return m;
    }
    

    }

Now we need an image with the coloring for each face, based on the net of an icosahedron, like this:

Net of an icosahedron

(Image found here)

Note that the mapping is done from the (0,0) to (1,1) normalized coordinates to the image (left,top) to (right, bottom) pixels.

Let's finally create the scene, load the mesh and add the texture to its material:

@Override
public void start(Stage primaryStage) throws Exception {
    Group sceneRoot = new Group();
    Scene scene = new Scene(sceneRoot, 600, 600, true, SceneAntialiasing.BALANCED);
    scene.setFill(Color.BLACK);
    PerspectiveCamera camera = new PerspectiveCamera(true);
    camera.setNearClip(0.1);
    camera.setFarClip(10000.0);
    camera.setTranslateZ(-4);
    scene.setCamera(camera);

    IcosahedronMesh mesh = new IcosahedronMesh();
    mesh.setCullFace(CullFace.FRONT);
    PhongMaterial mat = new PhongMaterial();
    mat.setDiffuseMap(new Image(getClass().getResourceAsStream("icosah_net.png")));
    mesh.setMaterial(mat);
    Rotate rotateY = new Rotate(0, 0, 0, 0, Rotate.Y_AXIS);
    mesh.getTransforms().addAll(new Rotate(30,Rotate.X_AXIS),rotateY);

    sceneRoot.getChildren().addAll(mesh, new AmbientLight(Color.WHITE));

    primaryStage.setTitle("JavaFX 3D - Icosahedron");
    primaryStage.setScene(scene);
    primaryStage.show();        
}

This is how it looks like:

Icosahedron

EDIT

Now, if you think about how the texture is applied, you could simplify the image up to several squares with the palette of colors you need:

Palette of colors

And the texture coordinates can be really simplified:

m.getTexCoords().addAll(
        0.1f, 0.5f, // 0 red
        0.3f, 0.5f, // 1 green
        0.5f, 0.5f, // 2 blue
        0.7f, 0.5f, // 3 yellow
        0.9f, 0.5f  // 4 orange
);

Finally, we have to map those points in our faces. Following the same pattern as the net image:

m.getFaces().addAll(
        1, 0, 11, 0, 7, 0, 
        1, 4, 7, 4, 6, 4, 
        1, 4, 6, 4, 10, 4, 
        1, 2, 10, 2, 3, 2, 
        1, 2, 3, 2, 11, 2,                
        4, 3, 8, 3, 0, 3, 
        5, 3, 4, 3, 0, 3, 
        9, 1, 5, 1, 0, 1, 
        2, 1, 9, 1, 0, 1, 
        8, 0, 2, 0, 0, 0, 

        11, 3, 9, 3, 7, 3,
        7, 1, 2, 1, 6, 1, 
        6, 1, 8, 1, 10, 1, 
        10, 0, 4, 0, 3, 0, 
        3, 0, 5, 0, 11, 0,

        4, 4, 10, 4, 8, 4, 
        5, 4, 3, 4, 4, 4, 
        9, 2, 11, 2, 5, 2, 
        2, 2, 7, 2, 9, 2, 
        8, 3, 6, 3, 2, 3
);

Now we'll have a very neat icosahedron, since we get rid of the borders and bad resolution of the image:

Improved icosahedron

This can be extended to any triangle mesh, or use any algorithm to refine the triangles.

Frisket answered 9/11, 2014 at 22:58 Comment(6)
This last edit (using the palette of colored squares) really helps since I already have a color map function assigned to my icosphere. I will post my finished code as soon as I make the changes. Thanks!Papaverine
Yes, for sure, since providing a full image could be really difficult. With a color mapping function you will need to create a small image anyway, but you could use Snapshot on runtime!. By the way, I've done also the algorithm for the icosphere ...Lauter
Is there a way that I can get away from having to actually write to disk (or external device) an image file to use a texture? In other words: can I use a specific texture without having to use Image? Since my color map will change on runtime I don't want to have to write to disk every time I run it. Also, this might be a security issue (writing to disk) for someone using my app. @JosePapaverine
I've posted all the code to create an icosphere and a palette of colors here, within the F(X)yz library.Lauter
@AlvaroAlvarezParrilla In the meantime, have you found a solution for not writing to disk?Brewton
@SimonMarynissen Check the FXyz3D implementation for Palette, that generates an image for the texture based on a number of colors. As you can see here, you can select not to save the image to a file.Lauter
S
0

Thanks to José for this really helpful introduction and the hint to his implementation based on a number of colors.

In order to understand better what must be done to create the colored material without any image from disk I modified José example given here.

The application main class:

public class IcosahedronApp extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        Group sceneRoot = new Group();
        Scene scene = new Scene(sceneRoot, 600, 600, true, SceneAntialiasing.BALANCED);
        scene.setFill(Color.BLACK);
        PerspectiveCamera camera = new PerspectiveCamera(true);
        camera.setNearClip(0.1);
        camera.setFarClip(10000.0);
        camera.setTranslateZ(-4);
        scene.setCamera(camera);
        IcosahedronMesh mesh = new IcosahedronMesh();
        mesh.setCullFace(CullFace.FRONT);
        Rotate rotateY = new Rotate(0, 0, 0, 0, Rotate.Y_AXIS);
        mesh.getTransforms().addAll(new Rotate(30,Rotate.X_AXIS),rotateY);
        sceneRoot.getChildren().addAll(mesh, new AmbientLight(Color.WHITE));
        primaryStage.setTitle("JavaFX 3D - Icosahedron");
        primaryStage.setScene(scene);
        primaryStage.show();        
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

and the IcosahedronMesh

public class IcosahedronMesh extends MeshView {
    private final static Color[] COLORS = {
        Color.RED,
        Color.GREEN,
        Color.BLUE,
        Color.YELLOW,
        Color.ORANGE,
    };

    public IcosahedronMesh() {
        setMesh(createCube());
    }

    private TriangleMesh createCube() {
        TriangleMesh m = new TriangleMesh();
        // coloring
        PhongMaterial mat = new PhongMaterial();
        WritableImage image = new WritableImage(COLORS.length, 1);
        PixelWriter writer = image.getPixelWriter();
        for (int i = 0; i < COLORS.length; i++)
            writer.setColor(i, 0, COLORS[i]);
        mat.setDiffuseMap(image);
        setMaterial(mat);
        // POINTS
        m.getPoints().addAll(
                // Point 1
                0f, 0f, -0.951057f,
                // Point 2
                0f, 0f, 0.951057f,
                // Point 3
                -0.850651f, 0f, -0.425325f,
                // Point 4
                0.850651f, 0f, 0.425325f,
                // Point 5
                0.688191f, -0.5f, -0.425325f,
                // Point 6
                0.688191f, 0.5f, -0.425325f,
                // Point 7
                -0.688191f, -0.5f, 0.425325f,
                // Point 8
                -0.688191f, 0.5f, 0.425325f,
                // Point 9
                -0.262866f, -0.809017f, -0.425325f,
                // Point 10
                -0.262866f, 0.809017f, -0.425325f,
                // Point 11
                0.262866f, -0.809017f, 0.425325f,
                // Point 12
                0.262866f, 0.809017f, 0.425325f);
        // TEXTURES
        m.getTexCoords().addAll(
                // 0 red
                0.1f, 0.5f,
                // 1 green
                0.3f, 0.5f,
                // 2 blue
                0.5f, 0.5f,
                // 3 yellow
                0.7f, 0.5f,
                // 4 orange
                0.9f, 0.5f);
        // FACES
        m.getFaces().addAll(
                // Face 1
                1, 0, 11, 0, 7, 0,
                // Face 2
                1, 4, 7, 4, 6, 4,
                // Face 3
                1, 4, 6, 4, 10, 4,
                // Face 4
                1, 2, 10, 2, 3, 2,
                // Face 5
                1, 2, 3, 2, 11, 2,
                // Face 6
                4, 3, 8, 3, 0, 3,
                // Face 7
                5, 3, 4, 3, 0, 3,
                // Face 8
                9, 1, 5, 1, 0, 1,
                // Face 9
                2, 1, 9, 1, 0, 1,
                // Face 10
                8, 0, 2, 0, 0, 0,
                // Face 11
                11, 3, 9, 3, 7, 3,
                // Face 12
                7, 1, 2, 1, 6, 1,
                // Face 13
                6, 1, 8, 1, 10, 1,
                // Face 14
                10, 0, 4, 0, 3, 0,
                // Face 15
                3, 0, 5, 0, 11, 0,
                // Face 16
                4, 4, 10, 4, 8, 4,
                // Face 17
                5, 4, 3, 4, 4, 4,
                // Face 18
                9, 2, 11, 2, 5, 2,
                // Face 19
                2, 2, 7, 2, 9, 2,
                // Face 20
                8, 3, 6, 3, 2, 3);
        return m;
    }
}

More or less this is the last example from José. I did only replace the diffuse map by a WritableImage.

Seems to work well ...

Shotton answered 28/12, 2021 at 11:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.