2

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
    There is a really great [blog post on a rubik's cube in JavaFX 3D](http://jperedadnr.blogspot.com/2014/04/rubikfx-solving-rubiks-cube-with-javafx.html) by Jose Pereda. – jewelsea Nov 30 '15 at 18:33
  • Yeah I went through that and then tried to build one without importing a 3D model and creating one such in javafx only. – Debarun Mukherjee Nov 30 '15 at 18:57
  • This is related to your current issue, but note that the [Builder classes are deprecated](https://community.oracle.com/thread/2544323) and will probably be removed from a future version of JavaFX. – jewelsea Nov 30 '15 at 19:05
  • From now on I wont be using them, since I learned the new way to handle such problems from the answer and will probably be shifting to FXML soon. – Debarun Mukherjee Nov 30 '15 at 19:15

2 Answers2

5

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:

Cube Net

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());

Cuboid texture

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:

Palette

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:

Rubik's cube

The code required to create it can be found here.

Community
  • 1
  • 1
José Pereda
  • 44,311
  • 7
  • 104
  • 132
  • if I create rectangles using `TriangleMesh`, map the texture coordinates of images of particular colors (say red for one rectangle, green for another, and so on..) with the rectangles and then transform them to form a cube, I get the same problem. Am I making the same mistake as I had done with the `RectangleBuilder` class, or this is due to some other reason? – Debarun Mukherjee Dec 01 '15 at 15:51
  • Using textures was an option that I considered as well (especially since the mesh always has to contain texture coordinate - at least, it seems for me like this, according to the exising `VertexFormat`s). However, there are some subtle difficulties. First of all, specifying the texture coordinates correctly, and second (related to that) : The cube-elements for the rubik cube *all* have differently colored sides - so regardless of *how* exactly they are represented, there are some manual, hand-written definitions involved... – Marco13 Dec 01 '15 at 15:52
  • I've edited my answer, using a different approach: instead of generating an image for each cubie, I use one single image with the 7 required colors, and modify the texture indices accordingly – José Pereda Dec 02 '15 at 03:03
  • Wow this makes the cube much more accurate ! I will follow this approach. Thanks sir ! – Debarun Mukherjee Dec 02 '15 at 06:54
4

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.

Community
  • 1
  • 1
Marco13
  • 53,703
  • 9
  • 80
  • 159
  • Can you please explain how did you generate the points in the Point3D points[] array ? – Debarun Mukherjee Dec 01 '15 at 12:54
  • if I create rectangles using `TriangleMesh`, map the texture coordinates of images of particular colors (say red for one rectangle, green for another, and so on..) with the rectangles and then transform them to form a cube, I get the same problem. Am I making the same mistake as I had done with the `RectangleBuilder` class, or this is due to some other reason? – Debarun Mukherjee Dec 01 '15 at 15:37
  • I am not using the Rectangle class. I am using `TriangleMesh` to draw a rectangle `TriangleMesh re1 = new TriangleMesh(); re1.getPoints().addAll(x1,y1,z1,.. ); re1.getTexCoords().addAll(u1,v1,..); re1.getFaces().addAll( //corresponding vertices with the texture coordinates ); MeshView rectangle1 = new MeshView(re1);` In this manner I am creating 6 rectangles, add images to them and then transforming them to form the cube, which lands me to the same problem. – Debarun Mukherjee Dec 01 '15 at 16:28
  • It's hard to discuss/debug this in the comments here. How exactly are you transforming the rectangle-Meshes to form a Cube? (Or more general: In how far differs your code from something like the `buildSceneGaph` that I posted above?) – Marco13 Dec 01 '15 at 16:33
  • @DebarunMukherjee I edited the post. The reason that I originally gave was wrong. Sorry for that. – Marco13 Dec 01 '15 at 17:23