All Downloads are FREE. Search and download functionalities are using the official Maven repository.

us.ihmc.scs2.sessionVisualizer.jfx.controllers.camera.CameraFocalPointHandler Maven / Gradle / Ivy

package us.ihmc.scs2.sessionVisualizer.jfx.controllers.camera;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.transform.Transform;
import us.ihmc.euclid.matrix.RotationMatrix;
import us.ihmc.euclid.tools.EuclidCoreFactories;
import us.ihmc.euclid.tuple3D.Vector3D;
import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly;
import us.ihmc.euclid.tuple3D.interfaces.Vector3DBasics;
import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly;
import us.ihmc.scs2.sessionVisualizer.jfx.tools.JavaFXMissingTools;
import us.ihmc.scs2.sessionVisualizer.jfx.tools.TranslateSCS2;
import us.ihmc.scs2.sessionVisualizer.jfx.yoComposite.Tuple3DProperty;

import java.util.ArrayList;
import java.util.List;
import java.util.function.DoubleUnaryOperator;
import java.util.function.Predicate;

/**
 * This class handles the control in position of the camera's focal point.
 * 

* The focal point can be controlled: *

    *
  • Pressing keyboards keys (ASDW) to translate in the world's horizontal plane (independent of * the camera's pitch angle). *
  • Setting directly the location of the focal point. *
  • Tracking a node or coordinates. *
*

* * @author Sylvain Bertrand */ public class CameraFocalPointHandler { /** The current translation of the focal point. */ private final TranslateSCS2 focalPointTranslation = new TranslateSCS2(); /** The translation used to map keyboard to translation. It is expressed in world frame */ private final TranslateSCS2 offsetTranslation = new TranslateSCS2(); /** Current orientation of the camera necessary when translating the camera in its local frame. */ private final ObjectProperty cameraOrientation = new SimpleObjectProperty<>(this, "cameraOrientation", null); /** * When set to true, the translations forward/backward and left/right will be performed on a * horizontal plane, i.e. perpendicular the given up axis. */ private final BooleanProperty keepTranslationLeveled = new SimpleBooleanProperty(this, "keepTranslationLeveled", true); /** * Condition to trigger the use of the fast modifier to make the camera translate faster when using * the keyboard. */ private final ObjectProperty> fastModifierPredicate = new SimpleObjectProperty<>(this, "fastModifierPredicate", null); /** Slow camera translation modifier when using the keyboard. */ private final DoubleProperty slowModifier = new SimpleDoubleProperty(this, "slowModifier", 0.0075); /** * Fast camera translation modifier when using the keyboard. It is triggered when the condition held * in {@link #fastModifierPredicate} is fulfilled. */ private final DoubleProperty fastModifier = new SimpleDoubleProperty(this, "fastModifier", 0.0125); private DoubleUnaryOperator translationRateModifier; /** Key binding for moving the camera forward. Its default value is: {@code KeyCode.W}. */ private final ObjectProperty forwardKey = new SimpleObjectProperty<>(this, "forwardKey", KeyCode.W); /** Key binding for moving the camera backward. Its default value is: {@code KeyCode.S}. */ private final ObjectProperty backwardKey = new SimpleObjectProperty<>(this, "backwardKey", KeyCode.S); /** Key binding for moving the camera to the left. Its default value is: {@code KeyCode.A}. */ private final ObjectProperty leftKey = new SimpleObjectProperty<>(this, "leftKey", KeyCode.A); /** Key binding for moving the camera to the right. Its default value is: {@code KeyCode.D}. */ private final ObjectProperty rightKey = new SimpleObjectProperty<>(this, "rightKey", KeyCode.D); /** Key binding for moving the camera upward. Its default value is: {@code KeyCode.Q}. */ private final ObjectProperty upKey = new SimpleObjectProperty<>(this, "upKey", KeyCode.Q); /** Key binding for moving the camera downward. Its default value is: {@code KeyCode.Z}. */ private final ObjectProperty downKey = new SimpleObjectProperty<>(this, "downKey", KeyCode.Z); // Following is for following a target public enum TrackingTargetType { Disabled, Node, YoCoordinates } ; private final ObjectProperty targetType = new SimpleObjectProperty<>(this, "", TrackingTargetType.Disabled); private final ObjectProperty coordinatesTracked = new SimpleObjectProperty<>(this, "coordinatesTracked", null); private final ObjectProperty nodeTracked = new SimpleObjectProperty<>(this, "nodeTracked", null); private final TranslateSCS2 trackingTranslate = new TranslateSCS2(); private final ChangeListener nodeTrackingListener = (o, oldTransform, newTransform) -> { if (targetType.get() == TrackingTargetType.Node) trackingTranslate.setFrom(newTransform); }; private final Vector3DReadOnly down; private final List updateTasks = new ArrayList<>(); /** * Creates a calculator for the camera translation. * * @param up indicates which way is up. */ public CameraFocalPointHandler(Vector3DReadOnly up) { down = EuclidCoreFactories.newNegativeLinkedVector3D(up); targetType.addListener((o, oldValue, newValue) -> { if (newValue == TrackingTargetType.Disabled) { offsetTranslation.set(trackingTranslate); trackingTranslate.setToZero(); } }); nodeTracked.addListener((o, oldValue, newValue) -> { if (oldValue != null) oldValue.localToSceneTransformProperty().removeListener(nodeTrackingListener); if (newValue != null) { targetType.set(TrackingTargetType.Node); // Reset the focus translation whenever the target gets updated offsetTranslation.setToZero(); newValue.localToSceneTransformProperty().addListener(nodeTrackingListener); nodeTrackingListener.changed(null, null, newValue.getLocalToSceneTransform()); } else if (targetType.get() == TrackingTargetType.Node) { disableTracking(); } }); coordinatesTracked.addListener(new ChangeListener<>() { private final ChangeListener trackingTranslateUpdater = (o, oldValue, newValue) -> trackingTranslate.set(coordinatesTracked.get()); @Override public void changed(ObservableValue o, Tuple3DProperty oldValue, Tuple3DProperty newValue) { if (newValue != null) { targetType.set(TrackingTargetType.YoCoordinates); // Reset the focus translation whenever the target gets updated offsetTranslation.setToZero(); newValue.xProperty().addListener(trackingTranslateUpdater); newValue.yProperty().addListener(trackingTranslateUpdater); newValue.zProperty().addListener(trackingTranslateUpdater); } else if (targetType.get() == TrackingTargetType.YoCoordinates) { CameraFocalPointHandler.this.disableTracking(); } if (oldValue != null) { oldValue.xProperty().removeListener(trackingTranslateUpdater); oldValue.yProperty().removeListener(trackingTranslateUpdater); oldValue.zProperty().removeListener(trackingTranslateUpdater); } } }); focalPointTranslation.xProperty().bind(trackingTranslate.xProperty().add(offsetTranslation.xProperty())); focalPointTranslation.yProperty().bind(trackingTranslate.yProperty().add(offsetTranslation.yProperty())); focalPointTranslation.zProperty().bind(trackingTranslate.zProperty().add(offsetTranslation.zProperty())); } public void disableTracking() { targetType.set(TrackingTargetType.Disabled); } public boolean isTrackingDisabled() { return targetType.get() == TrackingTargetType.Disabled; } public void update() { for (int i = 0; i < updateTasks.size(); i++) { updateTasks.get(i).run(); } } /** * Creates an {@link EventHandler} to translate the camera using keyboard bindings. * * @return an {@link EventHandler} to translate the camera with the keyboard. */ public FocalPointKeyEventHandler createKeyEventHandler() { FocalPointKeyEventHandler keyEventHandler = new FocalPointKeyEventHandler(); updateTasks.add(() -> keyEventHandler.update()); return keyEventHandler; } public class FocalPointKeyEventHandler implements EventHandler { private boolean isTranslating = false; private final Vector3D activeTranslationCameraFrame = new Vector3D(); private final Vector3D activeTranslationWorldFrame = new Vector3D(); private void update() { translateWorldFrame(activeTranslationWorldFrame); } @Override public void handle(KeyEvent event) { double modifier; if (fastModifierPredicate.get() == null || !fastModifierPredicate.get().test(event)) modifier = slowModifier.get(); else modifier = fastModifier.get(); if (translationRateModifier != null) modifier = translationRateModifier.applyAsDouble(modifier); KeyCode keyDown = event.getCode(); boolean isKeyReleased = event.getEventType() == KeyEvent.KEY_RELEASED; if (keyDown == forwardKey.get()) activeTranslationCameraFrame.setZ(isKeyReleased ? 0.0 : modifier); if (keyDown == backwardKey.get()) activeTranslationCameraFrame.setZ(isKeyReleased ? 0.0 : -modifier); if (keyDown == rightKey.get()) activeTranslationCameraFrame.setX(isKeyReleased ? 0.0 : modifier); if (keyDown == leftKey.get()) activeTranslationCameraFrame.setX(isKeyReleased ? 0.0 : -modifier); if (keyDown == downKey.get()) activeTranslationCameraFrame.setY(isKeyReleased ? 0.0 : modifier); if (keyDown == upKey.get()) activeTranslationCameraFrame.setY(isKeyReleased ? 0.0 : -modifier); isTranslating = activeTranslationCameraFrame.getX() != 0.0 || activeTranslationCameraFrame.getY() != 0.0 || activeTranslationCameraFrame.getZ() != 0.0; toWorldFrame(activeTranslationCameraFrame, activeTranslationWorldFrame); } public boolean isTranslating() { return isTranslating; } public Vector3DReadOnly getActiveTranslationCameraFrame() { return activeTranslationCameraFrame; } public Vector3DReadOnly getActiveTranslationWorldFrame() { return activeTranslationWorldFrame; } } public void toWorldFrame(Vector3DReadOnly inCameraFrame, Vector3DBasics outWorldFrame) { outWorldFrame.set(inCameraFrame); if (outWorldFrame.getX() == 0.0 && outWorldFrame.getY() == 0.0 && outWorldFrame.getZ() == 0.0) return; if (cameraOrientation.get() == null) return; if (keepTranslationLeveled.get()) { double mxz = cameraOrientation.get().getMxz(); double myz = cameraOrientation.get().getMyz(); double mzz = cameraOrientation.get().getMzz(); Vector3D cameraZAxis = new Vector3D(mxz, myz, mzz); Vector3D xAxisLeveled = new Vector3D(); xAxisLeveled.cross(down, cameraZAxis); xAxisLeveled.normalize(); Vector3D yAxisLeveled = new Vector3D(down); Vector3D zAxisLeveled = new Vector3D(); zAxisLeveled.cross(xAxisLeveled, yAxisLeveled); RotationMatrix rotation = new RotationMatrix(); rotation.setColumns(xAxisLeveled, yAxisLeveled, zAxisLeveled); rotation.transform(outWorldFrame); } else { JavaFXMissingTools.applyTranform(cameraOrientation.get(), outWorldFrame); } } /** * Update the camera translation after applying a translation offset in the camera local frame. * * @param translationOffset the translation offset in local frame to apply. Not modified. */ public void translateCameraFrame(Vector3DReadOnly translationOffset) { translateCameraFrame(translationOffset.getX(), translationOffset.getY(), translationOffset.getZ()); } /** * Update the camera translation after applying a translation offset in the camera local frame. * * @param dx the forward/backward translation offset in the camera local frame. * @param dy the left/right translation offset in the camera local frame. * @param dz the up/down translation offset in the camera local frame. */ public void translateCameraFrame(double dx, double dy, double dz) { if (cameraOrientation.get() == null) { translateWorldFrame(dx, dy, dz); return; } Vector3D shift = new Vector3D(dx, dy, dz); if (keepTranslationLeveled.get()) { double mxz = cameraOrientation.get().getMxz(); double myz = cameraOrientation.get().getMyz(); double mzz = cameraOrientation.get().getMzz(); Vector3D cameraZAxis = new Vector3D(mxz, myz, mzz); Vector3D xAxisLeveled = new Vector3D(); xAxisLeveled.cross(down, cameraZAxis); xAxisLeveled.normalize(); Vector3D yAxisLeveled = new Vector3D(down); Vector3D zAxisLeveled = new Vector3D(); zAxisLeveled.cross(xAxisLeveled, yAxisLeveled); RotationMatrix rotation = new RotationMatrix(); rotation.setColumns(xAxisLeveled, yAxisLeveled, zAxisLeveled); rotation.transform(shift); } else { JavaFXMissingTools.applyTranform(cameraOrientation.get(), shift); } offsetTranslation.add(shift); } public void translateWorldFrame(Tuple3DReadOnly translation) { translateWorldFrame(translation.getX(), translation.getY(), translation.getZ()); } /** * Update the camera translation after applying a translation offset in the world frame. * * @param dx the translation offset along the world x-axis. * @param dy the translation offset along the world y-axis. * @param dz the translation offset along the world z-axis. */ public void translateWorldFrame(double dx, double dy, double dz) { if (Double.isFinite(dx)) offsetTranslation.addX(dx); if (Double.isFinite(dy)) offsetTranslation.addY(dy); if (Double.isFinite(dz)) offsetTranslation.addZ(dz); } public void setPositionWorldFrame(Tuple3DReadOnly position) { setPositionWorldFrame(position.getX(), position.getY(), position.getZ()); } public void setPositionWorldFrame(double x, double y, double z) { if (Double.isFinite(x)) { offsetTranslation.setX(x); if (targetType.get() != TrackingTargetType.Disabled) offsetTranslation.subX(trackingTranslate.getX()); } if (Double.isFinite(y)) { offsetTranslation.setY(y); if (targetType.get() != TrackingTargetType.Disabled) offsetTranslation.subY(trackingTranslate.getY()); } if (Double.isFinite(z)) { offsetTranslation.setZ(z); if (targetType.get() != TrackingTargetType.Disabled) offsetTranslation.subZ(trackingTranslate.getZ()); } } /** * Sets the reference to the current camera orientation to enable translation in the camera frame, * i.e. first person. * * @param cameraOrientation the reference to the current camera orientation. Not modified. */ public void setCameraOrientation(Transform cameraOrientation) { this.cameraOrientation.set(cameraOrientation); } public void setTranslationRateModifier(DoubleUnaryOperator translationRateModifier) { this.translationRateModifier = translationRateModifier; } /** * Get the reference to the translation of the focal point expressed in world frame. * * @return the focal point position. */ public TranslateSCS2 getTranslation() { return focalPointTranslation; } /** * Gets the translation that can be set to change the focal point position. * * @return the translation that can be set to change the focal point position. */ public TranslateSCS2 getOffsetTranslation() { return offsetTranslation; } public final BooleanProperty keepTranslationLeveledProperty() { return keepTranslationLeveled; } public final ObjectProperty> fastModifierPredicateProperty() { return fastModifierPredicate; } public final DoubleProperty slowModifierProperty() { return slowModifier; } public final DoubleProperty fastModifierProperty() { return fastModifier; } public final ObjectProperty forwardKeyProperty() { return forwardKey; } public final ObjectProperty backwardKeyProperty() { return backwardKey; } public final ObjectProperty leftKeyProperty() { return leftKey; } public final ObjectProperty rightKeyProperty() { return rightKey; } public final ObjectProperty upKeyProperty() { return upKey; } public final ObjectProperty downKeyProperty() { return downKey; } public TranslateSCS2 getTrackingTranslate() { return trackingTranslate; } public ObjectProperty targetTypeProperty() { return targetType; } public ObjectProperty coordinatesToTrackProperty() { return coordinatesTracked; } public ObjectProperty nodeToTrackProperty() { return nodeTracked; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy