I want to color some triangles of a TriangleMesh in different colors.
What would be the easiest way to do this, which might even be possible in the fxml file?
The java code:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.*;
import javafx.scene.layout.Pane;
import javafx.scene.shape.MeshView;
import javafx.stage.Stage;
import java.io.IOException;
public class ColoredMesh extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(
ColoredMesh.class.getResource(
"mesh.fxml"
)
);
MeshView meshView = fxmlLoader.load();
// mesh.setDrawMode(DrawMode.LINE);
meshView.setTranslateX(-200);
meshView.setTranslateY(400);
meshView.setRotate(90);
Camera camera = new PerspectiveCamera();
camera.setRotate(90);
Scene scene = new Scene(new Pane(meshView), 800, 400);
scene.setCamera(camera);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The Polygon / Pyramid / TriangleMesh file:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.MeshView?>
<?import javafx.scene.shape.TriangleMesh?>
<MeshView>
<mesh>
<TriangleMesh>
<points>
0 100 100
100 100 0
0 100 -100
-100 100 0
0 0 0
</points>
<texCoords>
0 0
</texCoords>
<faces>
0 0 4 0 1 0
1 0 4 0 2 0
2 0 4 0 3 0
3 0 4 0 0 0
0 0 1 0 2 0
0 0 2 0 3 0
</faces>
</TriangleMesh>
</mesh>
</MeshView>
I guess this could have been closed as a duplicate (see the resources section for references to potential duplicates).
However, I think it was interesting and pretty unique how the question was framed such that it defined most of the model using FXML, so I thought I'd adapt that to an answer.
Full explanation is outside of what I am prepared to write up here at this time, but I refer you to other resources where you can find some more information if you need it.
This is rendered to a gif, so fidelity isn't great, actual output on PC looks better, and the gif output is weirdly massively sped up, but it does give an indication of what the solution does.
Loads a textured model and animates it around the X and Y axes.
import javafx.animation.*;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Point3D;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.paint.*;
import javafx.scene.shape.MeshView;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.io.IOException;
public class ColoredMesh extends Application {
private static final Color INDIA_INK = Color.rgb(60,61,76);
private static final Color AMBIENT_GRAY = Color.rgb(100, 100, 100);
private static final Duration ROTATION_STEP_TIME = Duration.seconds(5);
@Override
public void start(Stage stage) throws IOException {
MeshView meshView = loadModel();
Scene scene = createScene(meshView);
stage.setScene(scene);
stage.show();
animateNode(meshView);
}
private Scene createScene(MeshView meshView) {
PerspectiveCamera camera = new PerspectiveCamera();
AmbientLight ambientLight = new AmbientLight(AMBIENT_GRAY);
Scene scene = new Scene(
new Group(
ambientLight,
meshView
),
200, 200,
INDIA_INK
);
scene.setCamera(camera);
return scene;
}
private MeshView loadModel() throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(
ColoredMesh.class.getResource(
"pyramid-mesh.fxml"
)
);
MeshView meshView = fxmlLoader.load();
meshView.setTranslateX(100);
meshView.setTranslateY(40);
meshView.setTranslateZ(100);
// We have defined the material in the fxml which creates the MeshView.
// However, I leave this commented code here to show how the material
// can be defined in Java rather than FXML, if that were preferable.
// texture(meshView);
return meshView;
}
private void texture(MeshView meshView) {
PhongMaterial texturedMaterial = new PhongMaterial();
texturedMaterial.setDiffuseMap(
new Image(
ColoredMesh.class.getResource(
"texture.png"
).toExternalForm()
)
);
meshView.setMaterial(texturedMaterial);
}
private void animateNode(MeshView meshView) {
Animation rotateY = createRotationAnimation(Rotate.Y_AXIS, meshView);
Animation rotateX = createRotationAnimation(Rotate.X_AXIS, meshView);
rotateY.setOnFinished(e -> rotateX.play());
rotateX.setOnFinished(e -> rotateY.play());
rotateY.play();
}
private Animation createRotationAnimation(Point3D axis, Node node) {
RotateTransition animation = new RotateTransition(
ROTATION_STEP_TIME,
node
);
animation.setAxis(axis);
animation.setFromAngle(0);
animation.setToAngle(360);
return animation;
}
public static void main(String[] args) {
launch(args);
}
}
The texture is an image that is defined as a diffuse map for a PhongMaterial that is specified for the MeshView that is the node for the mesh.
In this example, the material is defined in FXML, but you could do it in code if you wish (the example Java code contains a commented-out section that does this).
The texture coordinates in the mesh are defining the midpoints of each of the color swatches within the texture image.
For the pyramid overall based on a triangle model, there are six triangular polygons used. For the base, there are two triangles, but they are both colored the same, so only five texture coordinates are needed to achieve a solid color for each face.
When the faces are defined, each reference to a vertex point is followed by a reference to a texture coordinate. Each triangular face is defined to use the same texture coordinate for all vertex points, which results in a uniform solid coloring for the face which matches the color located at that texture coordinate in the texture image.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.MeshView?>
<?import javafx.scene.shape.TriangleMesh?>
<?import javafx.scene.paint.PhongMaterial?>
<?import javafx.scene.image.Image?>
<MeshView>
<material>
<PhongMaterial>
<diffuseMap>
<Image url="@texture.png"/>
</diffuseMap>
</PhongMaterial>
</material>
<mesh>
<TriangleMesh>
<points>
0 100 100
100 100 0
0 100 -100
-100 100 0
0 0 0
</points>
<texCoords>
0.1 0.5
0.3 0.5
0.5 0.5
0.7 0.5
0.9 0.5
</texCoords>
<faces>
0 0 4 0 1 0
1 1 4 1 2 1
2 2 4 2 3 2
3 3 4 3 0 3
0 4 1 4 2 4
0 4 2 4 3 4
</faces>
</TriangleMesh>
</mesh>
</MeshView>
You can generate the texture image any way you want.
For this demo, I wrote a small program that created the image from a snapshot created in JavaFX.
This is the code for the texture creation if interested, but it is not necessary to use it to run the demonstration.
The concept is known as a TextureAtlas.
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.layout.FlowPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
public class TextureMaker extends Application {
private static final int SWATCH_SIZE = 10;
private static final Color colors[] = {
Color.RED,
Color.GREEN,
Color.BLUE,
Color.MAGENTA,
Color.CYAN
};
@Override
public void start(Stage stage) throws IOException {
Rectangle[] colorSwatches = Arrays.stream(colors)
.map(this::createColoredRect)
.toArray(Rectangle[]::new);
FlowPane flowPane = new FlowPane(colorSwatches);
flowPane.setPrefSize(colors.length * SWATCH_SIZE, SWATCH_SIZE);
Scene scene = new Scene(
flowPane,
Color.rgb(60,61,76)
);
Image textureImage = scene.snapshot(null);
File textureFile = new File("texture.png");
ImageIO.write(
SwingFXUtils.fromFXImage(textureImage, null),
"png",
textureFile
);
System.out.println("Wrote: " + textureFile.getAbsolutePath());
stage.setScene(scene);
stage.show();
}
public Rectangle createColoredRect(Color color) {
return new Rectangle(SWATCH_SIZE,SWATCH_SIZE, color);
}
public static void main(String[] args) {
launch(args);
}
}
This implementation creates an image and saves it to disk, but you could create the image on the fly in your main application, without saving it disk if you wanted. There are demonstrations of such an approach in the linked similar questions.
The above solution is not a general-purpose answer on methods to arbitrarily color any given mesh.
Instead, it is more focused on answering a particular question:
For complex models, instead of defining the mesh data (face/vertex/texture coordinates) in FXML, I think I'd advise using a standard 3d format (eg .obj or .stl) that is supported by other pieces of software.
There are pre-created models available in common formats on the web (but not fxml). Such models can come with the necessary image maps and texture co-ord definitions to color them.
You can import those models into JavaFX using third-party importer libraries for the various 3D model formats. These JavaFX importer libraries can be found on the web if you search, e.g. the InteractiveMesh and f(x)yz libraries.
A very good tutorial (better than anything I could come up with) for this is Create 3D Shapes using MeshView.
The tutorial maps an image to the faces of a pyramid, like the mesh in your question. It uses a photo image, but in your case, you would use different regions of solid colors. Look particularly at the section on the texture coordinates.
Click on the image in the tutorial and it will show a neat overlay mapping the texture coordinates onto the image. This really helps visualize what is going on.