How to render 3D graphics properly


I was trying to make a rubiks cube in javafx an ended up with a very bad model as given in this <a href="http://i.stack.imgur.com/8RCEV.png" rel="nofollow">Image</a>. 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 }


<blockquote> <h2>EDIT:</h2>

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 <a href="https://stackoverflow.com/revisions/34005357/2" rel="nofollow">Revision History</a>. 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.

<hr />

<sup>*</sup> 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 <a href="https://stackoverflow.com/revisions/34005357/2" rel="nofollow">Revision History</a> for the "real 3D" solution.


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 <em>single</em> mesh for each cube and color the faces as required for the Rubik's Cube.

This can be achieved by using a <em><a href="http://en.wikipedia.org/wiki/Net_%28polyhedron%29" rel="nofollow">net</a></em> image to color the faces of the mesh:

<a href="https://i.stack.imgur.com/PCmIW.png" rel="nofollow"><img alt="Cube Net" class="b-lazy" data-src="https://i.stack.imgur.com/PCmIW.png" data-original="https://i.stack.imgur.com/PCmIW.png" src="https://etrip.eimg.top/images/2019/05/07/timg.gif" /></a>

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 <a href="https://github.com/Birdasaur/FXyz" rel="nofollow">FXyz</a> library.

CuboidMesh cube = new CuboidMesh(); cube.setTextureModeImage(getClass().getResource("cubeNet.png").toExternalForm());

<a href="https://i.stack.imgur.com/I1XNv.png" rel="nofollow"><img alt="Cuboid texture" class="b-lazy" data-src="https://i.stack.imgur.com/I1XNv.png" data-original="https://i.stack.imgur.com/I1XNv.png" src="https://etrip.eimg.top/images/2019/05/07/timg.gif" /></a>

This <a href="http://jperedadnr.blogspot.com.es/2015/01/creating-and-texturing-javafx-3d-shapes.html" rel="nofollow">post</a> shows you how different faces of a single mesh can be colored.


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:

<a href="https://i.stack.imgur.com/uN4dv.png" rel="nofollow"><img alt="Palette" class="b-lazy" data-src="https://i.stack.imgur.com/uN4dv.png" data-original="https://i.stack.imgur.com/uN4dv.png" src="https://etrip.eimg.top/images/2019/05/07/timg.gif" /></a>

and then modify the texture indices accordingly, as explained in this <a href="https://stackoverflow.com/a/26834319/3956070" rel="nofollow">answer</a>.

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:

<a href="https://i.stack.imgur.com/txESG.png" rel="nofollow"><img alt="Rubik's cube" class="b-lazy" data-src="https://i.stack.imgur.com/txESG.png" data-original="https://i.stack.imgur.com/txESG.png" src="https://etrip.eimg.top/images/2019/05/07/timg.gif" /></a>

The code required to create it can be found <a href="https://gist.github.com/jperedadnr/28534fcdce605b75382b" rel="nofollow">here</a>.


  • JavaFX 3D - How to set different cameras for Group with 3D object and SubScene with UI Controls?
  • A code for finding one root of fifth degree polynomial
  • Import functions in Matlab for every local function
  • Using scilab to solve and plot differential equations
  • Adding Column SQL Access [closed]
  • Java Import Package (to package above present work directory)
  • Using initialize method in a controller in FXML?
  • How to get mouse position in chart-space
  • ScrollPane jumps to top when deleting nodes
  • Enumerating Controls on a Form
  • Xmonad multiple submap key combos
  • Passing variable arguments using PowerShell's Start-Process cmdlet
  • Use tryCatch within R loop
  • Is there some graphical way to create my own configuration file on SonarLint?
  • Javascript, Regex - I need to grab each section of a string contained in brackets
  • URLConnection doesn't work since API 10 and higher?
  • ASP.NET MVC 2 Preview 2 - display directory list rather than home/index
  • Is it possible to open regedit and navigate to straight to a specific key using process.start?
  • JBoss External Properties Files in Classpath
  • Creating Java object from class name with constructor, which contains parameters [duplicate]
  • htaccess add www if not subdomain, if subdomain remove www
  • How to use RequestBodyAdvice
  • Jetty Server not starting: Unable to establish loopback connection
  • Debugging ASP.NET on a built-in web server suddenly stops
  • Update CALayer sublayers immediately
  • Jenkins: How To Build multiple projects from a TFS repository?
  • JFileChooser in front of fullscreen Swing application
  • Array.prototype.includes - not transformed with babel
  • How to convert from System.Drawing.Color to Excel.ColorFormat in C#? Change comment color
  • javascript inside java/jsp code
  • using conditional logic : check if record exists; if it does, update it, if not, create it
  • Android Studio and gradle
  • python regex in pyparsing
  • Android Google Maps API OnLocationChanged only called once
  • IndexOutOfRangeException on multidimensional array despite using GetLength check
  • unknown Exception android
  • Checking variable from a different class in C#
  • How can i traverse a binary tree from right to left in java?
  • failed to connect to specific WiFi in android programmatically
  • How can I use threading to 'tick' a timer to be accessed by other threads?