How to set axis (triad) at fixed position on scree

2020-02-02 00:20发布

问题:

How to set axis (triad) at fixed position on screen in JavaFX? I am currently developing one application in which I want to show axis (triad) at fixed position on my screen (i.e. bottom-left corner). I want rotation of axis should be in sync with the main object. Zoom and Translate operation should not be applied to axis.

But I am facing some difficulties to show axis at specific position on screen.

I have used screenToLocal method to get fixed position in scene but it only returns Point2D object which is not helpful to set 3D translate values.

Can you please give me solution for this problem?

Source code based on this example is as below:

import javafx.application.Application;
    import javafx.event.EventHandler;
    import javafx.geometry.BoundingBox;
    import javafx.geometry.Bounds;
    import javafx.geometry.Point2D;
    import javafx.geometry.Point3D;
    import javafx.scene.DepthTest;
    import javafx.scene.Group;
    import javafx.scene.PerspectiveCamera;
    import javafx.scene.Scene;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.input.ScrollEvent;
    import javafx.scene.paint.Color;
    import javafx.scene.paint.Material;
    import javafx.scene.paint.PhongMaterial;
    import javafx.scene.shape.Box;
    import javafx.scene.shape.Cylinder;
    import javafx.scene.shape.DrawMode;
    import javafx.scene.shape.MeshView;
    import javafx.scene.shape.Sphere;
    import javafx.scene.shape.TriangleMesh;
    import javafx.scene.transform.Rotate;
    import javafx.scene.transform.Transform;
    import javafx.scene.transform.Translate;
    import javafx.stage.Stage;


    public class TrafoTest extends Application {
        final Group root = new Group();
        Group axis = new Group();
        final XformWorld world = new XformWorld();
        final PerspectiveCamera camera = new PerspectiveCamera(true);
        final XformCamera cameraXform = new XformCamera();
        final XformCamera cameraXform2 = new XformCamera();
        final XformCamera cameraXform3 = new XformCamera();
        private static final double CAMERA_INITIAL_DISTANCE = -1000;
        private static final double CAMERA_NEAR_CLIP = 0.1;
        private static final double CAMERA_FAR_CLIP = 10000.0;
        private static final double MOUSE_SPEED = 1;
        private static final double ROTATION_SPEED = 4.0;
        private static final double TRACK_SPEED = 0.02;
        double mousePosX, mousePosY, mouseOldX, mouseOldY, mouseDeltaX, mouseDeltaY;
        double mouseFactorX, mouseFactorY;
        Stage stage;


        @Override
        public void start(Stage primaryStage) {
            root.getChildren().add(world);
            root.setDepthTest(DepthTest.ENABLE);
            buildCamera();
            buildBodySystem();
            Scene scene = new Scene(root, 800, 600, true);
            scene.setFill(Color.GREY);
            handleMouse(scene);
            this.stage = primaryStage;
            primaryStage.setTitle("TrafoTest");
            primaryStage.setScene(scene);
            primaryStage.show();
            scene.setCamera(camera);

            mouseFactorX = 180.0 / scene.getWidth();
            mouseFactorY = 180.0 / scene.getHeight();
        }

        private void buildCamera() {
            root.getChildren().add(cameraXform);
            cameraXform.getChildren().add(cameraXform2);
            cameraXform2.getChildren().add(cameraXform3);
            cameraXform3.getChildren().add(camera);
            camera.setNearClip(CAMERA_NEAR_CLIP);
            camera.setFarClip(CAMERA_FAR_CLIP);
            camera.setTranslateZ(CAMERA_INITIAL_DISTANCE);
        }

        private void buildBodySystem() {
            PhongMaterial whiteMaterial = new PhongMaterial();
            whiteMaterial.setDiffuseColor(Color.WHITE);
            whiteMaterial.setSpecularColor(Color.LIGHTBLUE);
            Box box = new Box(400, 200, 100);
            box.setMaterial(whiteMaterial);
            box.setDrawMode(DrawMode.LINE);
            PhongMaterial redMaterial = new PhongMaterial();
            redMaterial.setDiffuseColor(Color.DARKRED);
            redMaterial.setSpecularColor(Color.RED);
            Sphere sphere = new Sphere(5);
            sphere.setMaterial(redMaterial);
            sphere.setTranslateX(200.0);
            sphere.setTranslateY(-100.0);
            sphere.setTranslateZ(-50.0);
            axis = drawReferenceFrame();
            world.getChildren().addAll(axis);
            world.getChildren().add(box);
            world.getChildren().addAll(sphere);
        }

        private void handleMouse(Scene scene) {
            scene.setOnMousePressed((MouseEvent me) -> {
                mousePosX = me.getSceneX();
                mousePosY = me.getSceneY();
                mouseOldX = me.getSceneX();
                mouseOldY = me.getSceneY();
            });
            scene.setOnMouseDragged((MouseEvent me) -> {
                mouseOldX = mousePosX;
                mouseOldY = mousePosY;
                mousePosX = me.getSceneX();
                mousePosY = me.getSceneY();
                mouseDeltaX = (mousePosX - mouseOldX);
                mouseDeltaY = (mousePosY - mouseOldY);
                if (me.isPrimaryButtonDown()) {
                    cameraXform.ry(mouseDeltaX * 180.0 / scene.getWidth());
                    cameraXform.rx(-mouseDeltaY * 180.0 / scene.getHeight());

                    BoundingBox point = (BoundingBox) root.screenToLocal(new BoundingBox(root.getLayoutX()+350, root.getLayoutY()+650, 0, 0,0, 20));
                    System.out.println(point);

                    axis.setTranslateX(point.getMinX());
                    axis.setTranslateY(point.getMinY());
                    axis.setTranslateZ(point.getMinZ());
                } else if (me.isSecondaryButtonDown()) {
                    cameraXform2.setTx((cameraXform2.t.getX() + (-mouseDeltaX)*MOUSE_SPEED*TRACK_SPEED));  
                    cameraXform2.setTy((cameraXform2.t.getY() + (-mouseDeltaY)*MOUSE_SPEED*TRACK_SPEED));

                    camera.setTranslateZ(camera.getTranslateZ() + mouseDeltaY);
                }
            });

            scene.setOnScroll(new EventHandler<ScrollEvent>() {

                @Override
                public void handle(ScrollEvent event) {         
                    double z = cameraXform3.getTranslateZ();
                    double newZ = z - event.getDeltaY() * MOUSE_SPEED * 0.05;
                    cameraXform3.setTranslateZ(newZ);                       
                }

            });
        }

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


        private  Group drawReferenceFrame(){
            Group G1= new Group();

            Cylinder CX = new  Cylinder(2,25);
            Cylinder CY = new  Cylinder(2,25);
            Cylinder CZ = new  Cylinder(2,25);
            Sphere S = new Sphere(4);



            Material mat =new PhongMaterial(Color.WHITE);
            PhongMaterial Xmat = new PhongMaterial();
            Xmat.setDiffuseColor(Color.GREEN);
            PhongMaterial Ymat = new PhongMaterial();
            Ymat.setDiffuseColor(Color.BLUE);
            PhongMaterial Zmat = new PhongMaterial();
            Zmat.setDiffuseColor(Color.RED);

            S.setMaterial(Zmat);
            CY.setMaterial(mat);
    //      CY.setRotationAxis(Rotate.X_AXIS);

    //      CY.setRotate(90);
            CY.setTranslateY(-12.5);

            CX.setMaterial(mat);
            CX.setTranslateX(15);
            CX.setRotationAxis(Rotate.Z_AXIS);

            CX.setRotate(90);

            CZ.setMaterial(mat);
            CZ.setRotationAxis(Rotate.X_AXIS);
            CZ.setRotate(90);
            CZ.setTranslateZ(-12.5);
            G1.getChildren().addAll(CX,CY,CZ,S);

            TriangleMesh coneMeshY = createCone(3.5f, 7.5f);
            TriangleMesh coneMeshX = createCone(3.5f, 7.5f);
            TriangleMesh coneMeshZ = createCone(3.5f, 7.5f);
            MeshView yCone = new MeshView(coneMeshY);
            MeshView xCone = new MeshView(coneMeshX);
            MeshView zCone = new MeshView(coneMeshZ);
            yCone.setMaterial(Ymat);
            yCone.setTranslateY(-32.5);
            yCone.setDrawMode(DrawMode.FILL);

            xCone.setMaterial(Xmat);
            xCone.setTranslateY(-3.75);
            xCone.setRotationAxis(Rotate.Z_AXIS);
            xCone.setRotate(90);
            xCone.setTranslateX(28.5);
            xCone.setDrawMode(DrawMode.FILL);

            zCone.setRotationAxis(Rotate.X_AXIS);
            zCone.setTranslateY(-3.75);
            zCone.setRotate(90);
            zCone.setTranslateZ(-28.5);
            zCone.setDrawMode(DrawMode.FILL);
            zCone.setMaterial(Zmat);

            G1.getChildren().addAll(xCone,yCone,zCone);
    //      G1.setScale(0.45);
            return G1;
        }

        private TriangleMesh createCone( float radius, float height) {
            int divisions=500;
            TriangleMesh mesh = new TriangleMesh();
            mesh.getPoints().addAll(0,0,0);        
            double segment_angle = 2.0 * Math.PI / divisions;
            float x, z;
            double angle;
            double halfCount = (Math.PI / 2 - Math.PI / (divisions / 2)); 
            for(int i=divisions+1;--i >= 0; ) {
                angle = segment_angle * i;
                x = (float)(radius * Math.cos(angle - halfCount));
                z = (float)(radius * Math.sin(angle - halfCount));
                mesh.getPoints().addAll(x,height,z); 
            }   
            mesh.getPoints().addAll(0,height,0); 


            mesh.getTexCoords().addAll(0,0); 

            for(int i=1;i<=divisions;i++) {
                mesh.getFaces().addAll(
                    0,0,i+1,0,i,0,           //COunter clock wise
                    divisions+2,0,i,0,i+1,0   // Clock wise
                ); 
            }
            return mesh;
        }
    }

    class XformWorld extends Group {
        final Translate t = new Translate(0.0, 0.0, 0.0);
        final Rotate rx = new Rotate(0, 0, 0, 0, Rotate.X_AXIS);
        final Rotate ry = new Rotate(0, 0, 0, 0, Rotate.Y_AXIS);
        final Rotate rz = new Rotate(0, 0, 0, 0, Rotate.Z_AXIS);

        public XformWorld() {
            super();
            this.getTransforms().addAll(t, rx, ry, rz);
        }
    }

    class XformCamera extends Group {
        Point3D px = new Point3D(1.0, 0.0, 0.0);
        Point3D py = new Point3D(0.0, 1.0, 0.0);
        Rotate r;
        Transform tx = new Rotate();
        Translate t = new Translate();
        public XformCamera() {
            super();
        }

        public void rx(double angle) {
            r = new Rotate(angle, px);
            this.tx = tx.createConcatenation(r);
            this.getTransforms().clear();
            this.getTransforms().addAll(tx);
        }

        public void ry(double angle) {
            r = new Rotate(angle, py);
            this.tx = tx.createConcatenation(r);
            this.getTransforms().clear();
            this.getTransforms().addAll(tx);
        }    

        public void setTx(double x) { 
            t.setX(x); 
            this.getTransforms().clear();
            this.getTransforms().addAll(t);
        }

        public void setTy(double y) {
            t.setY(y);
            this.getTransforms().clear();
            this.getTransforms().addAll(t);
        }
    }

In above code, first I have translated the axis at left-bottom corner of my screen, but after rotating the main object (ie. BOX and SPHERE), axis is also translated.I wanted to rotate main object and axis about their own origins.

If I tried to zoom in (changed camera Z position), then also I axis location changed with respect to screen.

In above cases, I wanted to display my axis at left-bottom corner of the screen and Rotation of the axis should be in sync with the main object.

回答1:

I wanted the axis to be fixed at left, bottom corner of the screen and axis should rotate about it's own origin.

This variation leaves the axes at the origin, moves the box to

P = (size / 2, -size / 2, -size / 2)

relative to the axes, and pans the camera toward the bottom, center of the screen. Uncomment the call to camera.setTranslateX() to pan left. Moving the mouse causes the group to rotate about the axes' origin. Press shift to rotate about z, and use the mouse wheel to dolly the camera.

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;

/**
 * @see http://stackoverflow.com/a/37734966/230513
 * @see http://stackoverflow.com/a/37714700/230513
 * @see http://stackoverflow.com/a/37685167/230513
 * @see http://stackoverflow.com/a/37370840/230513
 */
public class TriadBox extends Application {

    private static final double SIZE = 300;
    private final Content content = Content.create(SIZE);

    private static final class Content {

        private static final double WIDTH = 3;
        private final Group group = new Group();
        private final Rotate rx = new Rotate(0, Rotate.X_AXIS);
        private final Rotate ry = new Rotate(0, Rotate.Y_AXIS);
        private final Rotate rz = new Rotate(0, Rotate.Z_AXIS);
        private final Box xAxis;
        private final Box yAxis;
        private final Box zAxis;
        private final Box box;

        private static Content create(double size) {
            Content c = new Content(size);
            c.group.getChildren().addAll(c.box, c.xAxis, c.yAxis, c.zAxis);
            c.group.getTransforms().addAll(c.rz, c.ry, c.rx);
            return c;
        }

        private Content(double size) {
            xAxis = createBox(size * 2, WIDTH, WIDTH);
            yAxis = createBox(WIDTH, size * 2, WIDTH);
            zAxis = createBox(WIDTH, WIDTH, size * 2);
            double edge = 3 * size / 4;
            box = new Box(edge, edge, edge);
            box.setMaterial(new PhongMaterial(Color.CORAL));
            box.setTranslateX(size / 2);
            box.setTranslateY(-size / 2);
            box.setTranslateZ(-size / 2);
        }

        private Box createBox(double w, double h, double d) {
            Box b = new Box(w, h, d);
            b.setMaterial(new PhongMaterial(Color.AQUA));
            return b;
        }
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setTitle("JavaFX 3D");
        Scene scene = new Scene(content.group, SIZE * 2, SIZE * 2, true);
        primaryStage.setScene(scene);
        scene.setFill(Color.BLACK);
        scene.setOnMouseMoved((final MouseEvent e) -> {
            if (e.isShiftDown()) {
                content.rz.setAngle(e.getSceneX() * 360 / scene.getWidth());
            } else {
                content.rx.setAngle(e.getSceneY() * 360 / scene.getHeight());
                content.ry.setAngle(e.getSceneX() * 360 / scene.getWidth());
            }
        });
        PerspectiveCamera camera = new PerspectiveCamera(true);
        camera.setFarClip(SIZE * 6);
        //camera.setTranslateX(SIZE / 2);
        camera.setTranslateY(-SIZE / 2);
        camera.setTranslateZ(-4.5 * SIZE);
        scene.setCamera(camera);
        scene.setOnScroll((final ScrollEvent e) -> {
            camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY());
        });
        primaryStage.show();
    }

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


回答2:

It may be easier to have separate groups for the rotatable content versus the fixed triad. In the example below, cube contains a single child, box, and three transforms, rx, ry and rz. In contrast, axes contains three axes with no transforms. The axes are translated to the lower left corner as a group, leaving the mouse handler free to manipulate the Rotate instances belonging to cube.

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;

/**
 * @see http://stackoverflow.com/a/37685167/230513
 * @see http://stackoverflow.com/a/37370840/230513
 */
public class TriadBox extends Application {

    private static final double SIZE = 300;
    private final Content content = Content.create(SIZE);

    private static final class Content {

        private static final double WIDTH = 3;
        private final Group group = new Group();
        private final Group cube = new Group();
        private final Group axes = new Group();
        private final Rotate rx = new Rotate(0, Rotate.X_AXIS);
        private final Rotate ry = new Rotate(0, Rotate.Y_AXIS);
        private final Rotate rz = new Rotate(0, Rotate.Z_AXIS);
        private final Box xAxis;
        private final Box yAxis;
        private final Box zAxis;
        private final Box box;

        private static Content create(double size) {
            Content c = new Content(size);
            c.cube.getChildren().add(c.box);
            c.cube.getTransforms().addAll(c.rz, c.ry, c.rx);
            c.axes.getChildren().addAll(c.xAxis, c.yAxis, c.zAxis);
            c.axes.setTranslateX(-size / 2);
            c.axes.setTranslateY(size / 2);
            c.group.getChildren().addAll(c.cube, c.axes);
            return c;
        }

        private Content(double size) {
            xAxis = createBox(size*2, WIDTH, WIDTH);
            yAxis = createBox(WIDTH, size*2, WIDTH);
            zAxis = createBox(WIDTH, WIDTH, size*2);
            double edge = 3 * SIZE / 4;
            box = new Box(edge, edge, edge);
            box.setMaterial(new PhongMaterial(Color.CORAL));
        }

        private Box createBox(double w, double h, double d) {
            Box b = new Box(w, h, d);
            b.setMaterial(new PhongMaterial(Color.AQUA));
            return b;
        }
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setTitle("JavaFX 3D");
        Scene scene = new Scene(content.group, SIZE * 2, SIZE * 2, true);
        primaryStage.setScene(scene);
        scene.setFill(Color.BLACK);
        scene.setOnMouseMoved((final MouseEvent e) -> {
            content.rx.setAngle(e.getSceneY() * 360 / scene.getHeight());
            content.ry.setAngle(e.getSceneX() * 360 / scene.getWidth());
        });
        PerspectiveCamera camera = new PerspectiveCamera(true);
        camera.setFarClip(SIZE * 6);
        camera.setTranslateZ(-3 * SIZE);
        scene.setCamera(camera);
        scene.setOnScroll((final ScrollEvent e) -> {
            camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY());
        });
        primaryStage.show();
    }

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


回答3:

How to set axis (triad) at fixed position…I also wanted axis rotation in sync with box.

In this example, the Content.create() factory method translates the triad of axes is to the point

P = (-size / 2, size / 2, 0)

Because its separate group has no transforms, the triad's origin "sticks" to that point in the space viewed by the camera.

In contrast, the example below translates the triad of axes is to the point

P = (-size / 2, size / 2, size / 2)

Because the axes are in the same group as box, they undergo the same rotations.

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;

/**
 * @see https://stackoverflow.com/a/37714700/230513
 * @see https://stackoverflow.com/a/37685167/230513
 * @see https://stackoverflow.com/a/37370840/230513
 */
public class TriadBox extends Application {

    private static final double SIZE = 300;
    private final Content content = Content.create(SIZE);

    private static final class Content {

        private static final double WIDTH = 3;
        private final Group group = new Group();
        private final Rotate rx = new Rotate(0, Rotate.X_AXIS);
        private final Rotate ry = new Rotate(0, Rotate.Y_AXIS);
        private final Rotate rz = new Rotate(0, Rotate.Z_AXIS);
        private final Box xAxis;
        private final Box yAxis;
        private final Box zAxis;
        private final Box box;

        private static Content create(double size) {
            Content c = new Content(size);
            c.group.getChildren().addAll(c.box, c.xAxis, c.yAxis, c.zAxis);
            c.group.getTransforms().addAll(c.rz, c.ry, c.rx);
            return c;
        }

        private Content(double size) {
            xAxis = createBox(size * 2, WIDTH, WIDTH);
            xAxis.setTranslateY(size / 2);
            xAxis.setTranslateZ(size / 2);
            yAxis = createBox(WIDTH, size * 2, WIDTH);
            yAxis.setTranslateX(-size / 2);
            yAxis.setTranslateZ(size / 2);
            zAxis = createBox(WIDTH, WIDTH, size * 2);
            zAxis.setTranslateX(-size / 2);
            zAxis.setTranslateY(size / 2);
            double edge = 3 * size / 4;
            box = new Box(edge, edge, edge);
            box.setMaterial(new PhongMaterial(Color.CORAL));
        }

        private Box createBox(double w, double h, double d) {
            Box b = new Box(w, h, d);
            b.setMaterial(new PhongMaterial(Color.AQUA));
            return b;
        }
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setTitle("JavaFX 3D");
        Scene scene = new Scene(content.group, SIZE * 2, SIZE * 2, true);
        primaryStage.setScene(scene);
        scene.setFill(Color.BLACK);
        scene.setOnMouseMoved((final MouseEvent e) -> {
            if (e.isShiftDown()) {
                content.rz.setAngle(e.getSceneX() * 360 / scene.getWidth());
            } else {
                content.rx.setAngle(e.getSceneY() * 360 / scene.getHeight());
                content.ry.setAngle(e.getSceneX() * 360 / scene.getWidth());
            }
        });
        PerspectiveCamera camera = new PerspectiveCamera(true);
        camera.setFarClip(SIZE * 6);
        camera.setTranslateZ(-3.5 * SIZE);
        scene.setCamera(camera);
        scene.setOnScroll((final ScrollEvent e) -> {
            camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY());
        });
        primaryStage.show();
    }

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


回答4:

I wanted to rotate both main object and axis about their own center.

For that, I would return to the original example that has separate groups for the content and triad. The variation below adds the same threes Rotate transforms to both cube and axes. As a result, the setOnMouseMoved() implementation causes the the two to rotate in synchrony. Mouse over the stage to see the effect.

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;

/**
 * @see https://stackoverflow.com/a/37743539/230513
 * @see https://stackoverflow.com/a/37370840/230513
 */
public class TriadBox extends Application {

    private static final double SIZE = 300;
    private final Content content = Content.create(SIZE);

    private static final class Content {

        private static final double WIDTH = 3;
        private final Group group = new Group();
        private final Group cube = new Group();
        private final Group axes = new Group();
        private final Rotate rx = new Rotate(0, Rotate.X_AXIS);
        private final Rotate ry = new Rotate(0, Rotate.Y_AXIS);
        private final Rotate rz = new Rotate(0, Rotate.Z_AXIS);
        private final Box xAxis;
        private final Box yAxis;
        private final Box zAxis;
        private final Box box;

        private static Content create(double size) {
            Content c = new Content(size);
            c.cube.getChildren().add(c.box);
            c.cube.getTransforms().addAll(c.rz, c.ry, c.rx);
            c.cube.setTranslateX(size / 2);
            c.axes.getChildren().addAll(c.xAxis, c.yAxis, c.zAxis);
            c.axes.getTransforms().addAll(c.rz, c.ry, c.rx);
            c.axes.setTranslateX(-size / 2);
            c.group.getChildren().addAll(c.cube, c.axes);
            return c;
        }

        private Content(double size) {
            xAxis = createBox(size, WIDTH, WIDTH);
            yAxis = createBox(WIDTH, size, WIDTH);
            zAxis = createBox(WIDTH, WIDTH, size);
            double edge = 3 * size / 5;
            box = new Box(edge, edge, edge);
            box.setMaterial(new PhongMaterial(Color.CORAL));
        }

        private Box createBox(double w, double h, double d) {
            Box b = new Box(w, h, d);
            b.setMaterial(new PhongMaterial(Color.AQUA));
            return b;
        }
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setTitle("JavaFX 3D");
        Scene scene = new Scene(content.group, SIZE * 2, SIZE * 2, true);
        primaryStage.setScene(scene);
        scene.setFill(Color.BLACK);
        scene.setOnMouseMoved((final MouseEvent e) -> {
            if (e.isShiftDown()) {
                content.rz.setAngle(e.getSceneX() * 360 / scene.getWidth());
            } else {
                content.rx.setAngle(e.getSceneY() * 360 / scene.getHeight());
                content.ry.setAngle(e.getSceneX() * 360 / scene.getWidth());
            }
        });
        PerspectiveCamera camera = new PerspectiveCamera(true);
        camera.setFarClip(SIZE * 6);
        camera.setTranslateZ(-4 * SIZE);
        scene.setCamera(camera);
        scene.setOnScroll((final ScrollEvent e) -> {
            camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY());
        });
        primaryStage.show();
    }

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


回答5:

In order to see what you were seeing, I made these changes to TrafoTest and set the scene to black.

I then modified this example to make the scene look more like yours, adding a sphere on one corner and shifting the axes to the corner opposite. I substituted a variation of your custom rotation Group and your mouse handler from here. The new group also rotates around z when isShiftDown(). Also note the slightly simpler camera dolly in the setOnScroll() handler. In particular, note that the axes can be translated independently as a group. Try this variation in Content.create().

c.axes.setTranslateX(c.axes.getTranslateX() - 20);
c.axes.setTranslateY(c.axes.getTranslateY() + 20);
c.axes.setTranslateZ(c.axes.getTranslateZ() + 20);

import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Transform;
import javafx.stage.Stage;

/**
 * @see https://stackoverflow.com/a/37755149/230513
 * @see https://stackoverflow.com/a/37743539/230513
 * @see https://stackoverflow.com/a/37370840/230513
 */
public class TriadBox extends Application {

    private static final double SIZE = 300;
    private final Content content = Content.create(SIZE);
    private double mousePosX, mousePosY, mouseOldX, mouseOldY, mouseDeltaX, mouseDeltaY;

    private static final class Content {

        private static final double WIDTH = 3;
        private final Xform group = new Xform();
        private final Group cube = new Group();
        private final Group axes = new Group();
        private final Box xAxis;
        private final Box yAxis;
        private final Box zAxis;
        private final Box box;
        private final Sphere sphere;

        private static Content create(double size) {
            Content c = new Content(size);
            c.cube.getChildren().addAll(c.box, c.sphere);
            c.axes.getChildren().addAll(c.xAxis, c.yAxis, c.zAxis);
            c.group.getChildren().addAll(c.cube, c.axes);
            return c;
        }

        private Content(double size) {
            double edge = 3 * size / 4;
            xAxis = createBox(edge, WIDTH, WIDTH, edge);
            yAxis = createBox(WIDTH, edge / 2, WIDTH, edge);
            zAxis = createBox(WIDTH, WIDTH, edge / 4, edge);
            box = new Box(edge, edge / 2, edge / 4);
            box.setDrawMode(DrawMode.LINE);
            sphere = new Sphere(8);
            PhongMaterial redMaterial = new PhongMaterial();
            redMaterial.setDiffuseColor(Color.CORAL.darker());
            redMaterial.setSpecularColor(Color.CORAL);
            sphere.setMaterial(redMaterial);
            sphere.setTranslateX(edge / 2);
            sphere.setTranslateY(-edge / 4);
            sphere.setTranslateZ(-edge / 8);
        }

        private Box createBox(double w, double h, double d, double edge) {
            Box b = new Box(w, h, d);
            b.setMaterial(new PhongMaterial(Color.AQUA));
            b.setTranslateX(-edge / 2 + w / 2);
            b.setTranslateY(edge / 4 - h / 2);
            b.setTranslateZ(edge / 8 - d / 2);
            return b;
        }
    }

    private static class Xform extends Group {

        private final Point3D px = new Point3D(1.0, 0.0, 0.0);
        private final Point3D py = new Point3D(0.0, 1.0, 0.0);
        private Rotate r;
        private Transform t = new Rotate();

        public void rx(double angle) {
            r = new Rotate(angle, px);
            this.t = t.createConcatenation(r);
            this.getTransforms().clear();
            this.getTransforms().addAll(t);
        }

        public void ry(double angle) {
            r = new Rotate(angle, py);
            this.t = t.createConcatenation(r);
            this.getTransforms().clear();
            this.getTransforms().addAll(t);
        }

        public void rz(double angle) {
            r = new Rotate(angle);
            this.t = t.createConcatenation(r);
            this.getTransforms().clear();
            this.getTransforms().addAll(t);
        }
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setTitle("JavaFX 3D");
        Scene scene = new Scene(content.group, SIZE * 2, SIZE * 2, true);
        primaryStage.setScene(scene);
        scene.setFill(Color.BLACK);
        PerspectiveCamera camera = new PerspectiveCamera(true);
        camera.setFarClip(SIZE * 6);
        camera.setTranslateZ(-2 * SIZE);
        scene.setCamera(camera);
        scene.setOnMousePressed((MouseEvent e) -> {
            mousePosX = e.getSceneX();
            mousePosY = e.getSceneY();
            mouseOldX = e.getSceneX();
            mouseOldY = e.getSceneY();
        });
        scene.setOnMouseDragged((MouseEvent e) -> {
            mouseOldX = mousePosX;
            mouseOldY = mousePosY;
            mousePosX = e.getSceneX();
            mousePosY = e.getSceneY();
            mouseDeltaX = (mousePosX - mouseOldX);
            mouseDeltaY = (mousePosY - mouseOldY);
            if (e.isShiftDown()) {
                content.group.rz(-mouseDeltaX * 180.0 / scene.getWidth());
            } else if (e.isPrimaryButtonDown()) {
                content.group.rx(+mouseDeltaY * 180.0 / scene.getHeight());
                content.group.ry(-mouseDeltaX * 180.0 / scene.getWidth());
            } else if (e.isSecondaryButtonDown()) {
                camera.setTranslateX(camera.getTranslateX() - mouseDeltaX * 0.1);
                camera.setTranslateY(camera.getTranslateY() - mouseDeltaY * 0.1);
                camera.setTranslateZ(camera.getTranslateZ() + mouseDeltaY);
            }
        });
        scene.setOnScroll((final ScrollEvent e) -> {
            camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY());
        });
        primaryStage.show();
    }

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