问题
I was trying to make a rubiks cube in javafx
an ended up with a very bad model as given in this Image. I am giving my source for code for this, where I have used RectangleBuilder
class to create rectangles and transformed in 3d. To fix the graphics i had also tried to build the rectangles used TriangleMesh
class and after adding materials to them, transformed them in 3d to end up again in the same bad graphics. Why does this occur and how to get rid of it ?
import javafx.scene.transform.Rotate;
import javafx.scene.PerspectiveCamera;
import javafx.scene.transform.Translate;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.SceneAntialiasing;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.RectangleBuilder;
import javafx.scene.transform.Rotate;
import javafx.util.Duration;
public class NewFXMain1 extends Application {
public class Cube extends Group {
final Rotate rx = new Rotate(0,Rotate.X_AXIS);
final Rotate ry = new Rotate(0,Rotate.Y_AXIS);
final Rotate rz = new Rotate(0,Rotate.Z_AXIS);
public Cube(double size, Color back,Color bottom,Color right,Color left,Color top,Color front, double shade) {
getTransforms().addAll(rz, ry, rx);
getChildren().addAll(
RectangleBuilder.create() // back face
.width(size).height(size)
.fill(back.deriveColor(0.0, 1.0, (1 - 0.5*shade), 1.0))
.translateX(-0.5*size)
.translateY(-0.5*size)
.translateZ(0.5*size)
.smooth(true)
.stroke(Color.BLACK)
.build(),
RectangleBuilder.create() // bottom face
.width(size).height(size)
.fill(bottom.deriveColor(0.0, 1.0, (1 - 0.4*shade), 1.0))
.translateX(-0.5*size)
.translateY(0)
.rotationAxis(Rotate.X_AXIS)
.rotate(90)
.smooth(true)
.stroke(Color.BLACK)
.build(),
RectangleBuilder.create() // right face
.width(size).height(size)
.fill(right.deriveColor(0.0, 1.0, (1 - 0.3*shade), 1.0))
.translateX(-1*size)
.translateY(-0.5*size)
.rotationAxis(Rotate.Y_AXIS)
.rotate(90)
.smooth(true)
.stroke(Color.BLACK)
.build(),
RectangleBuilder.create() // left face
.width(size).height(size)
.fill(left.deriveColor(0.0, 1.0, (1 - 0.2*shade), 1.0))
.translateX(0)
.translateY(-0.5*size)
.rotationAxis(Rotate.Y_AXIS)
.rotate(90)
.smooth(true)
.stroke(Color.BLACK)
.build(),
RectangleBuilder.create() // top face
.width(size).height(size)
.fill(top.deriveColor(0.0, 1.0, (1 - 0.1*shade), 1.0))
.translateX(-0.5*size)
.translateY(-1*size)
.rotationAxis(Rotate.X_AXIS)
.rotate(90)
.smooth(true)
.stroke(Color.BLACK)
.build(),
RectangleBuilder.create() // front face
.width(size).height(size)
.fill(front)
.translateX(-0.5*size)
.translateY(-0.5*size)
.translateZ(-0.5*size)
.smooth(true)
.stroke(Color.BLACK)
.build()
);
}
}
PerspectiveCamera camera = new PerspectiveCamera(true);
@Override public void start(Stage primaryStage) throws Exception {
Group root = new Group();
Scene scene=new Scene(root,600,600,true);
camera.setNearClip(0.00001);
camera.setFarClip(10000000.0);
camera.getTransforms().addAll (
new Rotate(0, Rotate.Y_AXIS),
new Rotate(0, Rotate.X_AXIS),
new Translate(0, 0, -1000));
scene.setCamera(camera);
Cube c1 = new Cube(50,Color.BLUE.darker(),Color.BLUE.darker(),Color.ORANGE.darker(),Color.BLUE.darker(),Color.BLUE.darker(),Color.RED.darker(),1);
c1.setTranslateX(100);
Cube c2 = new Cube(50,Color.GREEN.darker(),Color.GREEN.darker(),Color.GREEN.darker(),Color.YELLOW.darker(),Color.BLUE.darker(),Color.RED.darker(),1);
c2.setTranslateX(50);
Cube c3 = new Cube(50,Color.CYAN.brighter(),Color.GREEN.darker(),Color.GREEN.darker(),Color.YELLOW.darker(),Color.BLUE.darker(),Color.RED.darker(),1);
c3.setTranslateX(50);
c3.setTranslateZ(50);
Cube c4 = new Cube(50,Color.CYAN.brighter(),Color.GREEN.darker(),Color.ORANGE.darker(),Color.YELLOW.darker(),Color.BLUE.darker(),Color.RED.darker(),1);
c4.setTranslateX(100);
c4.setTranslateZ(50);
Cube c5 = new Cube(50,Color.BLUE.darker(),Color.GREEN.darker(),Color.ORANGE.darker(),Color.BLUE.darker(),Color.BLUE.darker(),Color.RED.darker(),1);
c5.setTranslateX(100);
c5.setTranslateY(50);
Cube c6 = new Cube(50,Color.GREEN.darker(),Color.GREEN.darker(),Color.GREEN.darker(),Color.YELLOW.darker(),Color.BLUE.darker(),Color.RED.darker(),1);
c6.setTranslateX(50);
c6.setTranslateY(50);
Cube c7 = new Cube(50,Color.CYAN.brighter(),Color.GREEN.darker(),Color.GREEN.darker(),Color.YELLOW.darker(),Color.BLUE.darker(),Color.RED.darker(),1);
c7.setTranslateX(50);
c7.setTranslateZ(50);
c7.setTranslateY(50);
Cube c8 = new Cube(50,Color.CYAN.brighter(),Color.GREEN.darker(),Color.ORANGE.darker(),Color.YELLOW.darker(),Color.BLUE.darker(),Color.RED.darker(),1);
c8.setTranslateX(100);
c8.setTranslateZ(50);
c8.setTranslateY(50);
handleMouse(scene,root);
Group k=new Group(c1,c2,c3,c4,c5,c6,c7,c8);
k.setTranslateZ(70);
root.getChildren().addAll(k);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
private static final double CONTROL_MULTIPLIER = 0.1;
private static final double SHIFT_MULTIPLIER = 10.0;
private static final double MOUSE_SPEED = 0.1;
private static final double ROTATION_SPEED = 2.0;
double mousePosX,mousePosY,mouseOldX,mouseOldY,mouseDeltaX,mouseDeltaY;
private void handleMouse(Scene scene, final Node root) {
scene.setOnMousePressed(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent me) {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
}
});
scene.setOnMouseDragged(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent me) {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
double modifier = 1.0;
if (me.isControlDown()) {
modifier = CONTROL_MULTIPLIER;
}
if (me.isShiftDown()) {
modifier = SHIFT_MULTIPLIER;
}
if (me.isPrimaryButtonDown()) {
camera.setRotationAxis(Rotate.Y_AXIS);camera.setRotate(camera.getRotate() -
mouseDeltaX*modifier*ROTATION_SPEED); //
camera.setRotationAxis(Rotate.X_AXIS);camera.setRotate(camera.getRotate() +
mouseDeltaY*modifier*ROTATION_SPEED); // -
}
else if (me.isSecondaryButtonDown()) {
double z = camera.getTranslateZ();
double newZ = z + mouseDeltaX*MOUSE_SPEED*modifier;
camera.setTranslateZ(newZ);
}
}
}); // setOnMouseDragged
} //handleMouse
}
回答1:
EDIT:
The reason for the rendering artifacts that was originally given here was wrong, and the proposed solution may not be appropriate*. Details can be found in the Revision History. The actual solution is far simpler. Apologies for any inconveniences.
The reason for the rendering artifacts is that your camera clip planes are too far apart. You are setting
camera.setNearClip(0.00001);
camera.setFarClip(10000000.0);
which is far beyond what can sensibly be represented in a normal Z-buffer. Changing these lines to
camera.setNearClip(0.1);
camera.setFarClip(10000.0);
will fix the rendering errors.
* The original solution suggested to model the boxes from several Mesh
instances. This has the advantage that it allows defining normals and thus to achieve a "realistic" looking 3D effect, but involves a bit more effort. See the Revision History for the "real 3D" solution.
回答2:
While @Marco13 is a great and valid answer, if you don't want to import a model, as @jewelsea mentions, there is also a way to create one single mesh for each cube and color the faces as required for the Rubik's Cube.
This can be achieved by using a net image to color the faces of the mesh:
If you apply this image to a Box
:
Box cube = new Box();
PhongMaterial material = new PhongMaterial();
material.setDiffuseMap(new Image(getClass().getResource("cubeNet.png").toExternalForm()));
cube.setMaterial(material);
you will get the same image repeated for the six faces.
So you can create your own box to map properly the texture coordinates, or use CuboidMesh
from FXyz library.
CuboidMesh cube = new CuboidMesh();
cube.setTextureModeImage(getClass().getResource("cubeNet.png").toExternalForm());
This post shows you how different faces of a single mesh can be colored.
EDIT
Given that for any of the 27 cubies, a different image should be provided, a better approach is just using an image like this one:
and then modify the texture indices accordingly, as explained in this answer.
Basically, for each color:
public static final int RED = 0;
public static final int GREEN = 1;
public static final int BLUE = 2;
public static final int YELLOW = 3;
public static final int ORANGE = 4;
public static final int WHITE = 5;
public static final int GRAY = 6;
its normalized x texture coordinate will be:
public static final float X_RED = 0.5f / 7f;
public static final float X_GREEN = 1.5f / 7f;
public static final float X_BLUE = 2.5f / 7f;
public static final float X_YELLOW = 3.5f / 7f;
public static final float X_ORANGE = 4.5f / 7f;
public static final float X_WHITE = 5.5f / 7f;
public static final float X_GRAY = 6.5f / 7f;
So using a TriangleMesh
to create a box:
private TriangleMesh createCube(int[] face) {
TriangleMesh m = new TriangleMesh();
m.getPoints().addAll(
0.5f, 0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f
);
m.getTexCoords().addAll(
X_RED, 0.5f,
X_GREEN, 0.5f,
X_BLUE, 0.5f,
X_YELLOW, 0.5f,
X_ORANGE, 0.5f,
X_WHITE, 0.5f,
X_GRAY, 0.5f
);
Finally, we just need to add the faces: a list of vertices and texture indices. Let's create an array of indices ordered based on the common notation of faces on the Rubik's cube: F - R - U - B - L - D:
m.getFaces().addAll(
2, face[0], 3, face[0], 6, face[0], // F
3, face[0], 7, face[0], 6, face[0],
0, face[1], 1, face[1], 2, face[1], // R
2, face[1], 1, face[1], 3, face[1],
1, face[2], 5, face[2], 3, face[2], // U
5, face[2], 7, face[2], 3, face[2],
0, face[3], 4, face[3], 1, face[3], // B
4, face[3], 5, face[3], 1, face[3],
4, face[4], 6, face[4], 5, face[4], // L
6, face[4], 7, face[4], 5, face[4],
0, face[5], 2, face[5], 4, face[5], // D
2, face[5], 6, face[5], 4, face[5]
);
return m;
}
Now it's very simple to create the cube and its 27 cubies, based on a single cubie and a pattern of colors.
This code
int[] p = new int[]{BLUE, GRAY, GRAY, GRAY, ORANGE, WHITE};
MeshView meshP = new MeshView();
meshP.setMesh(createCube(p));
PhongMaterial mat = new PhongMaterial();
mat.setDiffuseMap(new Image(getClass().getResourceAsStream("palette.png")));
meshP.setMaterial(mat);
will create the cubie for the front-right-up position.
Defining the 27 positions, this will be the Rubik's cube:
The code required to create it can be found here.
来源:https://stackoverflow.com/questions/34001900/how-to-render-3d-graphics-properly