javajavafx3drotationrubiks-cube

Rotate an object about a pivot in javaFX


I'm trying to create a Rubik's cube in javaFX and I am struggling with the rotating of the faces. Up until now I have just been adding the 9 cubies in a face into a group and rotating that 90 degrees to get the full face to rotate in the correct way however this method does not seem to work for multiple rotations.

Instead I want to rotate each cubie about a pivot point at the centre of each face. To do this I tried to use the Rotate classes built in pivot parameter but it seems to have no affect on the rotation. Here is a simplified version of the function I'm using to rotate the front Face where the 2D array frontFace contains all the cubies in the front face from top left to bottom right:

public void rotate() {
    Rotate r = new Rotate(45, 0, 0, -100, Rotate.Z_AXIS);
    for (int x = 0; x < 3; x++) {
        for (int y = 0; y < 3; y++) {
            frontFace[x][y].getCube().getTransforms().add(r);
        }
    }
}   

This produces the following result (I have rotated it 45 degrees so it is easier to see):

Current Rotation

Whereas I would like it to rotate like this:

Ideal rotation

This is my first project using javaFX and any help would be greatly appreciated! The full project so far can be found here:

https://gofile.io/d/KidwKh

EDIT: I have been asked to include a Minimal, Reproducible Example so I have tried to condense the issue down into one class that hopefully isn't to confusing, here is the code:

import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.AmbientLight;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;

public class Example extends Application {


    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;


    public static int red = 0;
    public static int green = 1;
    public static int blue = 2;
    public static int yellow = 3;
    public static int orange = 4;
    public static int white = 5;
    public static int grey = 6;



    public static int WIDTH = 1400;
    public static int HEIGHT = 800;



    private static final SmartGroup MainCubeGroup = new SmartGroup();
    private static PhongMaterial mat = new PhongMaterial();


    MeshView[][] frontFace = new MeshView[3][3];

    @Override
    public void start(Stage primaryStage) {
        ViewCamera camera = new ViewCamera(MainCubeGroup);
        mat.setDiffuseMap(new Image(getClass().getResourceAsStream("pallete.png")));
        MainCubeGroup.getChildren().addAll(new AmbientLight(Color.WHITE));
        Scene scene = new Scene(MainCubeGroup, WIDTH, HEIGHT, true);

        //creates just frontFace of cube
        frontFace[0][0] = createCube(new Point3D(-100, -100, -100), new int[]{blue, grey, white, grey, red, grey});
        frontFace[0][1] = createCube(new Point3D(0, -100, -100),  new int[]{blue, grey, white, grey, grey, grey});
        frontFace[0][2] = createCube(new Point3D(100, -100, -100), new int[] {blue, green, white, grey, grey, grey});
        frontFace[1][0] = createCube(new Point3D(-100, 0, -100), new int[]{blue, grey, grey, grey, red, grey});
        frontFace[1][1] = createCube(new Point3D(0, 0, -100), new int[]{blue, grey, grey, grey, grey, grey});
        frontFace[1][2] = createCube(new Point3D(100, 0, -100),  new int[]{blue, green, grey, grey, grey, grey});
        frontFace[2][0] = createCube(new Point3D(-100, 100, -100), new int[]{blue, grey, grey, grey, red, yellow});
        frontFace[2][1] = createCube(new Point3D(0, 100, -100), new int[]{blue, grey, grey, grey, grey, yellow});
        frontFace[2][2] = createCube(new Point3D(100, 100, -100),  new int[]{blue, green, grey, grey, grey, yellow});


        rotate(45);


        scene.setFill(Color.BLANCHEDALMOND);
        scene.setCamera(camera);
        primaryStage.setTitle("DDD");
        primaryStage.setScene(scene);
        MainCubeGroup.translateXProperty().set(WIDTH / 2);
        MainCubeGroup.translateYProperty().set(HEIGHT / 2);
        primaryStage.show();
        camera.initMouseControl(scene, primaryStage);
    }


    public static void main(String[] args) {launch(args);}


    public void rotate(int angle) {
        Rotate r = new Rotate(angle, 0, 0, -100, Rotate.Z_AXIS);
        for (int x = 0; x < 3; x++) {
            for (int y = 0; y < 3; y++) {
                frontFace[x][y].getTransforms().add(r);
            }
        }
    }


    public static MeshView createCube(Point3D position, int[] face){
        int sideLength = 90;
        TriangleMesh meshCube = new TriangleMesh();
        meshCube.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
        );

        meshCube.getPoints().addAll(
                sideLength/2,  sideLength/2,  sideLength/2,  //point0
                sideLength/2, -sideLength/2,  sideLength/2,  //point1
                sideLength/2,  sideLength/2, -sideLength/2,  //point2
                sideLength/2, -sideLength/2, -sideLength/2,  //point3
                -sideLength/2,  sideLength/2,  sideLength/2, //point4
                -sideLength/2, -sideLength/2,  sideLength/2, //point5
                -sideLength/2,  sideLength/2, -sideLength/2, //point6
                -sideLength/2, -sideLength/2, -sideLength/2  //point7
        );
        //textures
        meshCube.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]

        );
        MeshView cube = new MeshView(meshCube);
        cube.setDrawMode(DrawMode.FILL);
        cube.setMaterial(mat);
        cube.setTranslateX(position.getX());
        cube.setTranslateY(position.getY());
        cube.setTranslateZ(position.getZ());
        MainCubeGroup.getChildren().add(cube);
        return cube;
    }
}

Here is the image, pallete.png which is used in the code if you would like the colours although I don't think it's relevant to the issue: pallete.png

Thanks.


Solution

  • When you append the Rotate object r to a frontFace Node, each such Node interprets the coordinates of the Rotate object in that Node’s own coordinate system. Each cube has defined its center as (0, 0, -100) in its own coordinate system, so rotating around that pivot point rotates the cube around its own center.

    What you want is for all of the frontFace Nodes to be rotated about a single pivot point in the same coordinate system—their parent Group’s coordinate system.

    You can then repeatedly convert that pivot point to the coordinate system of each frontFace using the parentToLocal method, and apply a new Rotate instance based on that “local” pivot point.

    public void rotate(int angle) {
        // These coordinates are in the parent Group's coordinate system.
        Point3D pivot = new Point3D(0, 0, -100);
    
        for (int x = 0; x < 3; x++) {
            for (int y = 0; y < 3; y++) {
                Node cube = frontFace[x][y];
    
                Point3D localPivot = cube.parentToLocal(pivot);
    
                Rotate r = new Rotate(angle,
                    localPivot.getX(), localPivot.getY(), localPivot.getZ(),
                    Rotate.Z_AXIS);
                cube.getTransforms().add(r);
            }
        }
    }