【问题标题】:Javafx continuously rotate object using mouse drag to accelerateJavafx 使用鼠标拖动持续旋转对象以加速
【发布时间】:2018-12-07 22:40:08
【问题描述】:

我有一个项目正在尝试为其开发控件。我需要能够通过单击鼠标并拖动和释放来连续旋转对象。释放后,我希望对象继续从其初始点旋转到我释放鼠标的位置。(前提是对象尚未到达此位置。)我以前见过以这种方式工作的应用程序,但我似乎不能在线查找任何代码以显示它是如何实现的。在将鼠标控件添加到我的项目之前,我正在使用下面的代码来测试它们。它已经包含简单的拖放机制。但是,对于如何添加连续旋转,我将不胜感激。我希望鼠标能够控制对象沿任意轴的旋转速度。正如我设置的那样,它沿任何轴旋转。但是我想要拖动而不是旋转对象,使对象以与拖动鼠标的距离成比例的速度旋转(递归旋转)。这将随着距离的增加而增加速度。如果我将相同的方向拖动两次相同的距离,速度应该会加倍,然后物体会以两倍的速度旋转。

package javafxapplication3;



import javafx.application.Application;
import static javafx.application.Application.launch;
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.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;

public class trafo extends Application {
    final Group root = new Group();
    final XformWorld world = new XformWorld();
    final PerspectiveCamera camera = new PerspectiveCamera(true);
    final XformCamera cameraXform = 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;
    double mousePosX, mousePosY, mouseOldX, mouseOldY, mouseDeltaX, mouseDeltaY;
    double mouseFactorX, mouseFactorY;

    @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);
        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(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);
        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);
        world.getChildren().addAll(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());
            } else if (me.isSecondaryButtonDown()) {
                camera.setTranslateZ(camera.getTranslateZ() + mouseDeltaY);
            }
        });
    }

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

}

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 t = new Rotate();

    public XformCamera() {
        super();
    }

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

}

【问题讨论】:

  • "释放后我希望对象继续从其初始点旋转到我释放鼠标的位置。" 这是什么意思?举个例子吧。
  • 澄清一下,我想要类似于 google earth 的旋转:google.com/earth 只是我希望旋转以与松开鼠标时相同的速度继续。我想我的意思是我想要一个旋转速度。也许这就像启动对 my rotate_x 和 rotate_y 函数的递归调用以响应鼠标释放事件一样简单?我需要计算一个旋转速度。这应该基于我拖动的速度,但也是我放开鼠标的即时性。这样图像就没有赶上它@user1803551
  • "我需要计算一个旋转速度。这应该基于我拖动的速度,以及我放开鼠标的即时性。"那是什么计算?我们不能告诉你你想要什么。您必须具体说明您希望它如何工作。 Edit您的问题及相关细节。
  • @user1803551 编辑更有帮助吗?我相信它的速度可以像计算 mouseDelta_X 和 mouseDelta_Y 一样计算。
  • 所以当你拖动鼠标时,在释放之前,什么都没有发生?只有在释放时对象才会开始旋转?

标签: animation javafx 3d rotation javafx-8


【解决方案1】:

所以按照我的看法,您想计算从要旋转的对象中间产生的阻力的弧度,并在该对象未正确旋转时旋转,它将以恒定速度旋转直到达到所需的旋转。我从来没有做过这样的事情,所以我不知道我的答案是否真的有效,但我至少可以尝试,因为我认为我知道解决方案。现在下面的代码可能会或可能不会工作,如果有什么不正确的地方请告诉我,我会尝试修复它。

public class RotationNode extends Pane {

    double startMouseAngle, goalAngle = 0.0, angleBeforeRotation;
    boolean currentlyRotating = false;

    public RotationNode(double angle) {
        //Parameter angle is to give this Pane a start angle if you want to
        super();

        //This can all change to whatever you want it to be
        setTranslateX(400.0);
        setTranslateY(200.0);
        setPrefSize(100.0, 100.0);
        setRotationAxis(new Point3D(0.0, 0.0, 360.0));

        //This is just to show something is actually rotating
        Rectangle r = new Rectangle(0, 0, 100, 100);
        r.setFill(Color.BLUE);
        getChildren().add(r);

        setRotate(angle);
        angleBeforeRotation = 0.0;

        addEventHandler(MouseEvent.MOUSE_PRESSED, ov -> {
            if(ov.getButton() == MouseButton.PRIMARY) {
                double x = ov.getX();
                double y = ov.getY();
                startMouseAngle = Math.toDegrees(Math.atan2(x, y));
                angleBeforeRotation = currentlyRotating ? goalAngle : getRotate();
                ov.consume();
            }
        });

        addEventHandler(MouseEvent.MOUSE_DRAGGED, ov -> {
            if(ov.getButton() == MouseButton.PRIMARY) {
                double x = ov.getX();
                double y = ov.getY();
                double a = Math.toDegrees(Math.atan2(x, y));
                double deltaAngle = startMouseAngle - a;
                goalAngle = angleBeforeRotation + deltaAngle;
                if(!currentlyRotating) {
                    new changeRotation();
                }
                ov.consume();
            }
        });
    }

    private class changeRotation {

        private final long TIME_STEP = 25;

        private final double ANGLE_PER_SECOND = 90;
        public changeRotation() {
            final ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();

            Runnable changeWidthCmd = () -> {
                double rotationDelta = ANGLE_PER_SECOND / (1000.0 / TIME_STEP);
                rotationDelta *= getRotate() > goalAngle ? -1 : 1;
                setRotate(getRotate() + rotationDelta);
                if(getRotate() - goalAngle < ANGLE_PER_SECOND / (1000.0 / TIME_STEP) && getRotate() - goalAngle > -ANGLE_PER_SECOND / (1000.0 / TIME_STEP)) {
                    setRotate(goalAngle);
                    currentlyRotating = false;
                    service.shutdown();
                }
            };

            currentlyRotating = true;
            service.scheduleAtFixedRate(changeWidthCmd, 0, TIME_STEP, TimeUnit.MILLISECONDS);
        }
    }
}

希望对你有帮助

莱纳吉

编辑:
更新了代码,它现在可以工作了。基于鼠标拖动的旋转有效,但中心点很奇怪,效果不佳。我会尝试改进这个并更新这个答案

重要编辑:
我忘了提到这个答案包含使用外部库的代码。要添加它,请将其添加到项目 pom 文件中:

<dependencies>
    <dependency>
        <groupId>io.reactivex.rxjava2</groupId>
        <artifactId>rxjava</artifactId>
        <version>2.1.14</version>
    </dependency>
</dependencies>

或从
https://jar-download.com/?detail_search=g%3A%22io.reactivex.rxjava2%22&g=io.reactivex.rxjava2&p=1
下载库并将该 jar 添加到项目依赖项中。

【讨论】:

  • 澄清一下,我想要类似于谷歌地球旋转的东西:google.com/earth 只是我希望旋转以与松开鼠标时相同的速度继续。我想我的意思是我想要一个旋转速度。也许这就像响应鼠标释放事件启动对 my rotate_x 和 rotate_y 函数的递归调用一样简单?我需要计算一个旋转速度。这应该基于我拖动的速度,但也是我放开鼠标的即时性。这样图像就没有赶上它。
  • 所以你想让物体通过移动鼠标来获得旋转速度,并且只要鼠标在拖动,速度就会立即适应鼠标的移动速度,但是一旦鼠标松开,物体的旋转速度将减小直到为零。我想我可以为你做,但这需要一些时间。顺便问一下,它是二维对象还是三维对象?
  • 是的,感谢您的回复。你几乎拥有它,我希望速度继续而不是降低,除非拖动相反的方向。当鼠标被释放时,它应该以相同的速度继续沿着它的路径,直到再次被鼠标操作。它是一个 3D 对象。不过我想我可以找到解决办法。我只希望有一种更简单的方法来做到这一点。也许使用 javafx 动画和持续时间。但是我可以创建速度变量,然后使用鼠标加速/减速速度。
  • 我想这意味着我需要一个“旋转模式”,除了已经创建的控件之外,鼠标拖动可以改变速度。我可以为旋转模式制作一个按钮。那么在自旋模式下创建一个递归函数可能是最简单的,该函数具有用于进一步改变速度或方向的参数。 @伦纳德
  • 如果我在 OpenGL 中这样做,我会这样做
猜你喜欢
  • 2021-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-11
相关资源
最近更新 更多