JavaFX Déplacer des objets 3D avec la souris sur un plan virtuel


Comme je créais mon premier jeu 3D dans JavaFX - où vous seriez en mesure d'assembler des navires à partir de pièces en utilisant la souris. Cela pose un problème car JAVAFX ne semble pas avoir de métods natifs qui fonctionnent pour convertir les coordonnées 2D de l'écran PerspectiveCamera en espace 3D de la scène.

Voici une représentation de ce que j'essaie de réaliser. Un bloc déplacé par la souris doit se déplacer sur un plan imaginaire qui est toujours tourné de 90 par rapport à la caméra: Représentation J'ai essayé de résoudre le problème de trigonométrie sans grand succès. Je n'ai pas joint d'extrait de code car je cherche une solution mathématique plus générique, mais je la fournirai si demandé.

Toute l'aide serait appréciée!

Résultat souhaité: Avant

Après

Author: José Pereda, 2015-02-26

1 answers

Comme le souligne @jdub1581, le Camera est la clé pour lier le mouvement de la souris avec vos objets 3D sur la scène.

Pour starterts, nous connaissons l'API publique PickResult, qui nous permet de sélectionner un objet 3D avec la souris, en fonction de certaines techniques de ray tracing effectuées à partir de la position de la caméra.

Mais une fois que nous avons un objet, le déplacer est un problème différent.

À la recherche d'une solution à ce problème (déplacement d'objets 3D avec une souris 2D dans l'espace 3D) il y a quelque temps, j'ai trouvé le Camera3D classe au projet Toys sur le référentiel OpenJFX.

Il a une méthode de promesse appelée unProjectDirection:

/*
 * returns 3D direction from the Camera position to the mouse
 * in the Scene space 
 */

public Vec3d unProjectDirection(double sceneX, double sceneY, 
                                double sWidth, double sHeight) {
}

Puisque vous avez demandé une explication mathématique, cette méthode utilise la trigonométrie que vous recherchiez. Cela vous donnera un vecteur 3D basé sur les coordonnées de la souris (x,y), en utilisant une classe private Vec3d (que nous pouvons remplacer par public Point3D):

double tanOfHalfFOV = Math.tan(Math.toRadians(camera.getFieldOfView()) * 0.5f);
Vec3d vMouse = new Vec3d(tanOfHalfFOV*(2*sceneX/sWidth-1), tanOfHalfFOV*(2*sceneY/sWidth-sHeight/sWidth), 1);

D'autres transformations sont appliquées pour obtenir un vecteur normalisé dans la scène coordonner.

L'étape suivante transformera ce vecteur normalisé en coordonnées réelles, en utilisant simplement la distance entre la caméra et l'objet, donnée sur le résultat du choix, et en transformant la position de l'objet.

Fondamentalement, cet extrait de code décrit l'ensemble du processus de glisser un objet:

    scene.setOnMousePressed((MouseEvent me) -> {
        vecIni = unProjectDirection(me.getSceneX(), me.getSceneY(), 
                     scene.getWidth(),scene.getHeight()); 
        distance=me.getPickResult().getIntersectedDistance();           
    });

    scene.setOnMouseDragged((MouseEvent me) -> {
        vecPos = unProjectDirection(mousePosX, mousePosY,
                 scene.getWidth(),scene.getHeight());
        Point3D p=vecPos.subtract(vecIni).multiply(distance);
        node.getTransforms().add(new Translate(p.getX(),p.getY(),p.getZ()));
        vecIni=vecPos;
        distance=me.getPickResult().getIntersectedDistance();
    });

Et ceci est un exemple de base de travail complet:

    public class Drag3DObject extends Application {

    private final Group root = new Group();
    private PerspectiveCamera camera;
    private final double sceneWidth = 800;
    private final double sceneHeight = 600;

    private double mousePosX;
    private double mousePosY;
    private double mouseOldX;
    private double mouseOldY;
    private final Rotate rotateX = new Rotate(-20, Rotate.X_AXIS);
    private final Rotate rotateY = new Rotate(-20, Rotate.Y_AXIS);

    private volatile boolean isPicking=false;
    private Point3D vecIni, vecPos;
    private double distance;
    private Sphere s;

    @Override
    public void start(Stage stage) {
        Box floor = new Box(1500, 10, 1500);
        floor.setMaterial(new PhongMaterial(Color.GRAY));
        floor.setTranslateY(150);
        root.getChildren().add(floor);

        Sphere sphere = new Sphere(150);
        sphere.setMaterial(new PhongMaterial(Color.RED));
        sphere.setTranslateY(-5);
        root.getChildren().add(sphere);

        Scene scene = new Scene(root, sceneWidth, sceneHeight, true, SceneAntialiasing.BALANCED);
        scene.setFill(Color.web("3d3d3d"));

        camera = new PerspectiveCamera(true);
        camera.setVerticalFieldOfView(false);

        camera.setNearClip(0.1);
        camera.setFarClip(100000.0);
        camera.getTransforms().addAll (rotateX, rotateY, new Translate(0, 0, -3000));

        PointLight light = new PointLight(Color.GAINSBORO);
        root.getChildren().add(light);
        root.getChildren().add(new AmbientLight(Color.WHITE));
        scene.setCamera(camera);

        scene.setOnMousePressed((MouseEvent me) -> {
            mousePosX = me.getSceneX();
            mousePosY = me.getSceneY();
            PickResult pr = me.getPickResult();
            if(pr!=null && pr.getIntersectedNode() != null && pr.getIntersectedNode() instanceof Sphere){
                distance=pr.getIntersectedDistance();
                s = (Sphere) pr.getIntersectedNode();
                isPicking=true;
                vecIni = unProjectDirection(mousePosX, mousePosY, scene.getWidth(),scene.getHeight());
            }
        });
        scene.setOnMouseDragged((MouseEvent me) -> {
            mousePosX = me.getSceneX();
            mousePosY = me.getSceneY();
            if(isPicking){
                vecPos = unProjectDirection(mousePosX, mousePosY, scene.getWidth(),scene.getHeight());
                Point3D p=vecPos.subtract(vecIni).multiply(distance);
                s.getTransforms().add(new Translate(p.getX(),p.getY(),p.getZ()));
                vecIni=vecPos;
                PickResult pr = me.getPickResult();
                if(pr!=null && pr.getIntersectedNode() != null && pr.getIntersectedNode()==s){
                    distance=pr.getIntersectedDistance();
                } else {
                    isPicking=false;
                }
            } else {
                rotateX.setAngle(rotateX.getAngle()-(mousePosY - mouseOldY));
                rotateY.setAngle(rotateY.getAngle()+(mousePosX - mouseOldX));
                mouseOldX = mousePosX;
                mouseOldY = mousePosY;
            }
        });
        scene.setOnMouseReleased((MouseEvent me)->{
            if(isPicking){
                isPicking=false;
            }
        });

        stage.setTitle("3D Dragging");
        stage.setScene(scene);
        stage.show();
    }

    /*
     From fx83dfeatures.Camera3D
     http://hg.openjdk.java.net/openjfx/8u-dev/rt/file/5d371a34ddf1/apps/toys/FX8-3DFeatures/src/fx83dfeatures/Camera3D.java
    */
    public Point3D unProjectDirection(double sceneX, double sceneY, double sWidth, double sHeight) {
        double tanHFov = Math.tan(Math.toRadians(camera.getFieldOfView()) * 0.5f);
        Point3D vMouse = new Point3D(tanHFov*(2*sceneX/sWidth-1), tanHFov*(2*sceneY/sWidth-sHeight/sWidth), 1);

        Point3D result = localToSceneDirection(vMouse);
        return result.normalize();
    }

    public Point3D localToScene(Point3D pt) {
        Point3D res = camera.localToParentTransformProperty().get().transform(pt);
        if (camera.getParent() != null) {
            res = camera.getParent().localToSceneTransformProperty().get().transform(res);
        }
        return res;
    }

    public Point3D localToSceneDirection(Point3D dir) {
        Point3D res = localToScene(dir);
        return res.subtract(localToScene(new Point3D(0, 0, 0)));
    }

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

Qui vous permettra de choisir et de faire glisser la sphère sur la scène:

glisser 3d

 7
Author: José Pereda, 2015-02-26 21:09:37