|
@@ -6,77 +6,794 @@ package com.jme3.gde.scenecomposer.tools;
|
|
|
|
|
|
import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent;
|
|
import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent;
|
|
import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
|
|
import com.jme3.gde.core.sceneexplorer.nodes.JmeNode;
|
|
|
|
+import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
|
|
import com.jme3.gde.core.sceneviewer.SceneViewerTopComponent;
|
|
import com.jme3.gde.core.sceneviewer.SceneViewerTopComponent;
|
|
|
|
+import com.jme3.gde.core.undoredo.AbstractUndoableSceneEdit;
|
|
import com.jme3.gde.scenecomposer.SceneEditTool;
|
|
import com.jme3.gde.scenecomposer.SceneEditTool;
|
|
|
|
+import com.jme3.input.KeyInput;
|
|
|
|
+import com.jme3.input.event.KeyInputEvent;
|
|
|
|
+import com.jme3.math.FastMath;
|
|
|
|
+import com.jme3.math.Matrix3f;
|
|
|
|
+import com.jme3.math.Quaternion;
|
|
import com.jme3.math.Vector2f;
|
|
import com.jme3.math.Vector2f;
|
|
import com.jme3.math.Vector3f;
|
|
import com.jme3.math.Vector3f;
|
|
|
|
+import com.jme3.scene.Node;
|
|
import com.jme3.scene.Spatial;
|
|
import com.jme3.scene.Spatial;
|
|
|
|
+import com.jme3.terrain.Terrain;
|
|
|
|
+import java.util.logging.Level;
|
|
|
|
+import java.util.logging.Logger;
|
|
import org.openide.loaders.DataObject;
|
|
import org.openide.loaders.DataObject;
|
|
|
|
|
|
/**
|
|
/**
|
|
- *
|
|
|
|
|
|
+ * This duplicates the Blender manipulate tool.
|
|
|
|
+ * It supports quick access to Grab, Rotate, and Scale operations
|
|
|
|
+ * by typing one of the following keys: 'g', 'r', or 's'
|
|
|
|
+ * Those keys can be followed by an axis key to specify what axis
|
|
|
|
+ * to perform the transformation: x, y, z
|
|
|
|
+ * Then, after the operation and axis are selected, you can type in a
|
|
|
|
+ * number and then hit 'enter' to complete the transformation.
|
|
|
|
+ *
|
|
|
|
+ * Ctrl+Shift+D will duplicate an object
|
|
|
|
+ * X will delete an object
|
|
|
|
+ *
|
|
|
|
+ * ITEMS TO FINISH:
|
|
|
|
+ * 1) fixed scale and rotation values by holding Ctrl and dragging mouse
|
|
|
|
+ * BUGS:
|
|
|
|
+ * 1) window always needs focus from primary click when it should focus from secondary and middle mouse
|
|
|
|
+ * 2) ESC will not reset currentState (ESC get hijacked)
|
|
|
|
+ * 3) rotation towards screen is busted (wrong axis)
|
|
|
|
+ *
|
|
* @author Brent Owens
|
|
* @author Brent Owens
|
|
*/
|
|
*/
|
|
public class SelectTool extends SceneEditTool {
|
|
public class SelectTool extends SceneEditTool {
|
|
|
|
|
|
protected Spatial selected;
|
|
protected Spatial selected;
|
|
- private boolean wasDragging = false;
|
|
|
|
|
|
+
|
|
|
|
+ private enum State {translate, rotate, scale};
|
|
|
|
+ private State currentState = null;
|
|
|
|
+
|
|
|
|
+ private enum Axis {x, y, z};
|
|
|
|
+ private Axis currentAxis = null;
|
|
|
|
+
|
|
|
|
+ private StringBuilder numberBuilder = new StringBuilder(); // gets appended with numbers
|
|
|
|
+
|
|
|
|
+ private Quaternion startRot;
|
|
|
|
+ private Vector3f startTrans;
|
|
|
|
+ private Vector3f startScale;
|
|
|
|
+
|
|
|
|
+ private boolean wasDraggingL = false;
|
|
|
|
+ private boolean wasDraggingR = false;
|
|
|
|
+ private boolean wasDownR = false;
|
|
|
|
+ private boolean ctrlDown = false;
|
|
|
|
+ private boolean shiftDown = false;
|
|
|
|
+ private boolean altDown = false;
|
|
|
|
+
|
|
|
|
+ private MoveUndo moving;
|
|
|
|
+ private ScaleUndo scaling;
|
|
|
|
+ private RotateUndo rotating;
|
|
|
|
+ private Vector2f startMouseCoord; // for scaling and rotation
|
|
|
|
+ private Vector2f startSelectedCoord; // for scaling and rotation
|
|
|
|
+ private float lastRotAngle; // used for rotation
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * This is stateful:
|
|
|
|
+ * First it checks for a command (rotate, translate, delete, etc..)
|
|
|
|
+ * Then it checks for an axis (x,y,z)
|
|
|
|
+ * Then it checks for a number (user typed a number
|
|
|
|
+ * Then, finally, it checks if Enter was hit.
|
|
|
|
+ *
|
|
|
|
+ * If either of the commands was actioned, the preceeding states/axis/amount
|
|
|
|
+ * will be reset. For example if the user types: G Y 2 R
|
|
|
|
+ * Then it will:
|
|
|
|
+ * 1) Set state as 'Translate' for the G (grab)
|
|
|
|
+ * 2) Set the axis as 'Y'; it will translate along the Y axis
|
|
|
|
+ * 3) Distance will be 2, when the 2 key is hit
|
|
|
|
+ * 4) Distance, Axis, and state are then reset because a new state was set: Rotate
|
|
|
|
+ * it won't actually translate because 'Enter' was not hit and 'R' reset the state.
|
|
|
|
+ *
|
|
|
|
+ */
|
|
|
|
+ @Override
|
|
|
|
+ public void keyPressed(KeyInputEvent kie) {
|
|
|
|
+
|
|
|
|
+ checkModificatorKeys(kie); // alt,shift,ctrl
|
|
|
|
+
|
|
|
|
+ if (selected == null)
|
|
|
|
+ return; // only do anything if a spatial is selected
|
|
|
|
+
|
|
|
|
+ // key released
|
|
|
|
+ if (kie.isReleased()) {
|
|
|
|
+ boolean commandUsed = checkCommandKey(kie);
|
|
|
|
+ boolean stateChange = checkStateKey(kie);
|
|
|
|
+ boolean axisChange = checkAxisKey(kie);
|
|
|
|
+ boolean numberChange = checkNumberKey(kie);
|
|
|
|
+ boolean enterHit = checkEnterHit(kie);
|
|
|
|
+ boolean escHit = checkEscHit(kie);
|
|
|
|
+
|
|
|
|
+ if (commandUsed)
|
|
|
|
+ return; // commands take priority
|
|
|
|
+
|
|
|
|
+ if (stateChange) {
|
|
|
|
+ currentAxis = null;
|
|
|
|
+ numberBuilder = new StringBuilder();
|
|
|
|
+ recordInitialState(selected);
|
|
|
|
+ }
|
|
|
|
+ else if (axisChange) {}
|
|
|
|
+ else if (numberChange) {}
|
|
|
|
+ else if (enterHit) {
|
|
|
|
+ if (currentState != null && numberBuilder.length() > 0) {
|
|
|
|
+ applyKeyedChangeState(selected);
|
|
|
|
+ clearState(false);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // -----------------------
|
|
|
|
+ // reset conditions below:
|
|
|
|
+
|
|
|
|
+ if (escHit) {
|
|
|
|
+ if (moving != null)
|
|
|
|
+ moving.sceneUndo();
|
|
|
|
+
|
|
|
|
+ moving = null;
|
|
|
|
+ clearState();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!stateChange && !axisChange && !numberChange && !enterHit && !escHit) {
|
|
|
|
+ // nothing valid was hit, reset the state
|
|
|
|
+ //clearState(); // this will be
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Abort any manipulations
|
|
|
|
+ */
|
|
|
|
+ private void clearState() {
|
|
|
|
+ clearState(true);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void clearState(boolean resetSelected) {
|
|
|
|
+ if (resetSelected && selected != null) {
|
|
|
|
+ // reset the transforms
|
|
|
|
+ if (startRot != null)
|
|
|
|
+ selected.setLocalRotation(startRot);
|
|
|
|
+ if (startTrans != null)
|
|
|
|
+ selected.setLocalTranslation(startTrans);
|
|
|
|
+ if (startScale != null)
|
|
|
|
+ selected.setLocalScale(startScale);
|
|
|
|
+ }
|
|
|
|
+ currentState = null;
|
|
|
|
+ currentAxis = null;
|
|
|
|
+ numberBuilder = new StringBuilder();
|
|
|
|
+ startRot = null;
|
|
|
|
+ startTrans = null;
|
|
|
|
+ startScale = null;
|
|
|
|
+ startMouseCoord = null;
|
|
|
|
+ startSelectedCoord = null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ private void recordInitialState(Spatial selected) {
|
|
|
|
+ startRot = selected.getLocalRotation().clone();
|
|
|
|
+ startTrans = selected.getLocalTranslation().clone();
|
|
|
|
+ startScale = selected.getLocalScale().clone();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Applies the changes entered by a number, not by mouse.
|
|
|
|
+ * Translate: adds the value to the current local translation
|
|
|
|
+ * Rotate: rotates by X degrees
|
|
|
|
+ * Scale: scale the current scale by X amount
|
|
|
|
+ */
|
|
|
|
+ private void applyKeyedChangeState(Spatial selected) {
|
|
|
|
+ Float value = null;
|
|
|
|
+ try {
|
|
|
|
+ value = new Float(numberBuilder.toString());
|
|
|
|
+ } catch (NumberFormatException e){
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (currentState == State.translate) {
|
|
|
|
+ float x = 0, y= 0, z = 0;
|
|
|
|
+ if (currentAxis == Axis.x)
|
|
|
|
+ x = value;
|
|
|
|
+ else if (currentAxis == Axis.y)
|
|
|
|
+ y = value;
|
|
|
|
+ else if (currentAxis == Axis.z)
|
|
|
|
+ z = value;
|
|
|
|
+ Vector3f before = selected.getLocalTranslation().clone();
|
|
|
|
+ Vector3f after = selected.getLocalTranslation().addLocal(x, y, z);
|
|
|
|
+ selected.setLocalTranslation(after);
|
|
|
|
+ actionPerformed(new MoveUndo(selected, before, after));
|
|
|
|
+ } else if (currentState == State.scale) {
|
|
|
|
+ float x = 1, y= 1, z = 1;
|
|
|
|
+ if (currentAxis == Axis.x)
|
|
|
|
+ x = value;
|
|
|
|
+ else if (currentAxis == Axis.y)
|
|
|
|
+ y = value;
|
|
|
|
+ else if (currentAxis == Axis.z)
|
|
|
|
+ z = value;
|
|
|
|
+ else if (currentAxis == null) {
|
|
|
|
+ x = value;
|
|
|
|
+ y = value;
|
|
|
|
+ z = value;
|
|
|
|
+ }
|
|
|
|
+ Vector3f before = selected.getLocalScale().clone();
|
|
|
|
+ Vector3f after = selected.getLocalScale().multLocal(x, y, z);
|
|
|
|
+ selected.setLocalScale(after);
|
|
|
|
+ actionPerformed(new ScaleUndo(selected, before, after));
|
|
|
|
+ } else if (currentState == State.rotate) {
|
|
|
|
+ float x = 0, y= 0, z = 0;
|
|
|
|
+ if (currentAxis == Axis.x)
|
|
|
|
+ x = 1;
|
|
|
|
+ else if (currentAxis == Axis.y)
|
|
|
|
+ y = 1;
|
|
|
|
+ else if (currentAxis == Axis.z)
|
|
|
|
+ z = 1;
|
|
|
|
+ Vector3f axis = new Vector3f(x,y,z);
|
|
|
|
+ Quaternion initialRot = selected.getLocalRotation().clone();
|
|
|
|
+ Quaternion rot = new Quaternion();
|
|
|
|
+ rot = rot.fromAngleAxis(value*FastMath.DEG_TO_RAD, axis);
|
|
|
|
+ selected.setLocalRotation(selected.getLocalRotation().mult(rot));
|
|
|
|
+ RotateUndo undo = new RotateUndo(selected, initialRot, rot);
|
|
|
|
+ actionPerformed(undo);
|
|
|
|
+ toolController.updateSelection(null);// force a re-draw of the bbox shape
|
|
|
|
+ toolController.updateSelection(selected);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void checkModificatorKeys(KeyInputEvent kie) {
|
|
|
|
+ if (kie.getKeyCode() == KeyInput.KEY_LCONTROL || kie.getKeyCode() == KeyInput.KEY_RCONTROL)
|
|
|
|
+ ctrlDown = kie.isPressed();
|
|
|
|
+
|
|
|
|
+ if (kie.getKeyCode() == KeyInput.KEY_LSHIFT || kie.getKeyCode() == KeyInput.KEY_RSHIFT)
|
|
|
|
+ shiftDown = kie.isPressed();
|
|
|
|
+
|
|
|
|
+ if (kie.getKeyCode() == KeyInput.KEY_LMENU || kie.getKeyCode() == KeyInput.KEY_RMENU)
|
|
|
|
+ altDown = kie.isPressed();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private boolean checkCommandKey(KeyInputEvent kie) {
|
|
|
|
+ if (kie.getKeyCode() == KeyInput.KEY_D) {
|
|
|
|
+ if (ctrlDown && shiftDown) {
|
|
|
|
+ duplicateSelected();
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // X will only delete if the user isn't already transforming
|
|
|
|
+ if (currentState == null && kie.getKeyCode() == KeyInput.KEY_X) {
|
|
|
|
+ if (!ctrlDown && !shiftDown) {
|
|
|
|
+ deleteSelected();
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private boolean checkStateKey(KeyInputEvent kie) {
|
|
|
|
+ if (kie.getKeyCode() == KeyInput.KEY_G) {
|
|
|
|
+ currentState = State.translate;
|
|
|
|
+ return true;
|
|
|
|
+ } else if (kie.getKeyCode() == KeyInput.KEY_R) {
|
|
|
|
+ currentState = State.rotate;
|
|
|
|
+ return true;
|
|
|
|
+ } else if (kie.getKeyCode() == KeyInput.KEY_S) {
|
|
|
|
+ currentState = State.scale;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private boolean checkAxisKey(KeyInputEvent kie) {
|
|
|
|
+ if (kie.getKeyCode() == KeyInput.KEY_X) {
|
|
|
|
+ currentAxis = Axis.x;
|
|
|
|
+ return true;
|
|
|
|
+ } else if (kie.getKeyCode() == KeyInput.KEY_Y) {
|
|
|
|
+ currentAxis = Axis.y;
|
|
|
|
+ return true;
|
|
|
|
+ } else if (kie.getKeyCode() == KeyInput.KEY_Z) {
|
|
|
|
+ currentAxis = Axis.z;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private boolean checkNumberKey(KeyInputEvent kie) {
|
|
|
|
+ if (kie.getKeyCode() == KeyInput.KEY_MINUS) {
|
|
|
|
+ if (numberBuilder.length() > 0) {
|
|
|
|
+ if (numberBuilder.charAt(0) == '-') {
|
|
|
|
+ numberBuilder.replace(0, 1, "");
|
|
|
|
+ } else {
|
|
|
|
+ numberBuilder.insert(0, '-');
|
|
|
|
+ }
|
|
|
|
+ } else
|
|
|
|
+ numberBuilder.append('-');
|
|
|
|
+ return true;
|
|
|
|
+ } else if (kie.getKeyCode() == KeyInput.KEY_0) {
|
|
|
|
+ numberBuilder.append('0');
|
|
|
|
+ return true;
|
|
|
|
+ } else if (kie.getKeyCode() == KeyInput.KEY_1) {
|
|
|
|
+ numberBuilder.append('1');
|
|
|
|
+ return true;
|
|
|
|
+ } else if (kie.getKeyCode() == KeyInput.KEY_2) {
|
|
|
|
+ numberBuilder.append('2');
|
|
|
|
+ return true;
|
|
|
|
+ } else if (kie.getKeyCode() == KeyInput.KEY_3) {
|
|
|
|
+ numberBuilder.append('3');
|
|
|
|
+ return true;
|
|
|
|
+ } else if (kie.getKeyCode() == KeyInput.KEY_4) {
|
|
|
|
+ numberBuilder.append('4');
|
|
|
|
+ return true;
|
|
|
|
+ } else if (kie.getKeyCode() == KeyInput.KEY_5) {
|
|
|
|
+ numberBuilder.append('5');
|
|
|
|
+ return true;
|
|
|
|
+ } else if (kie.getKeyCode() == KeyInput.KEY_6) {
|
|
|
|
+ numberBuilder.append('6');
|
|
|
|
+ return true;
|
|
|
|
+ } else if (kie.getKeyCode() == KeyInput.KEY_7) {
|
|
|
|
+ numberBuilder.append('7');
|
|
|
|
+ return true;
|
|
|
|
+ } else if (kie.getKeyCode() == KeyInput.KEY_8) {
|
|
|
|
+ numberBuilder.append('8');
|
|
|
|
+ return true;
|
|
|
|
+ } else if (kie.getKeyCode() == KeyInput.KEY_9) {
|
|
|
|
+ numberBuilder.append('9');
|
|
|
|
+ return true;
|
|
|
|
+ } else if (kie.getKeyCode() == KeyInput.KEY_PERIOD) {
|
|
|
|
+ if (numberBuilder.indexOf(".") == -1){ // if it doesn't exist yet
|
|
|
|
+ if (numberBuilder.length() == 0 ||
|
|
|
|
+ (numberBuilder.length() == 1 && numberBuilder.charAt(0) == '-'))
|
|
|
|
+ numberBuilder.append("0.");
|
|
|
|
+ else
|
|
|
|
+ numberBuilder.append(".");
|
|
|
|
+ }
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private boolean checkEnterHit(KeyInputEvent kie) {
|
|
|
|
+ if (kie.getKeyCode() == KeyInput.KEY_RETURN) {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private boolean checkEscHit(KeyInputEvent kie) {
|
|
|
|
+ if (kie.getKeyCode() == KeyInput.KEY_ESCAPE) {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
public void actionPrimary(Vector2f screenCoord, boolean pressed, final JmeNode rootNode, DataObject dataObject) {
|
|
public void actionPrimary(Vector2f screenCoord, boolean pressed, final JmeNode rootNode, DataObject dataObject) {
|
|
- if (!pressed && !wasDragging) {
|
|
|
|
- // mouse released and wasn't dragging, select a new spatial
|
|
|
|
- final Spatial result = pickWorldSpatial(getCamera(), screenCoord, rootNode);
|
|
|
|
-
|
|
|
|
- java.awt.EventQueue.invokeLater(new Runnable() {
|
|
|
|
-
|
|
|
|
- public void run() {
|
|
|
|
|
|
+ if (!pressed) {
|
|
|
|
+ // left mouse released
|
|
|
|
+ if (!wasDraggingL) {
|
|
|
|
+ // left mouse pressed
|
|
|
|
+ if (currentState != null) {
|
|
|
|
+ // finish manipulating the spatial
|
|
|
|
+ if (moving != null) {
|
|
|
|
+ moving.after = selected.getLocalTranslation().clone();
|
|
|
|
+ actionPerformed(moving);
|
|
|
|
+ moving = null;
|
|
|
|
+ clearState(false);
|
|
|
|
+ } else if (scaling != null) {
|
|
|
|
+ scaling.after = selected.getLocalScale().clone();
|
|
|
|
+ actionPerformed(scaling);
|
|
|
|
+ scaling = null;
|
|
|
|
+ clearState(false);
|
|
|
|
+ } else if (rotating != null) {
|
|
|
|
+ rotating.after = selected.getLocalRotation().clone();
|
|
|
|
+ actionPerformed(rotating);
|
|
|
|
+ rotating = null;
|
|
|
|
+ clearState(false);
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ // mouse released and wasn't dragging, place cursor
|
|
|
|
+ final Vector3f result = pickWorldLocation(getCamera(), screenCoord, rootNode);
|
|
if (result != null) {
|
|
if (result != null) {
|
|
-// System.out.println(rootNode.getChild(result).getName());
|
|
|
|
- SceneViewerTopComponent.findInstance().setActivatedNodes(new org.openide.nodes.Node[]{rootNode.getChild(result)});
|
|
|
|
- SceneExplorerTopComponent.findInstance().setSelectedNode(rootNode.getChild(result));
|
|
|
|
-
|
|
|
|
- } else {
|
|
|
|
- SceneViewerTopComponent.findInstance().setActivatedNodes(new org.openide.nodes.Node[]{rootNode});
|
|
|
|
- SceneExplorerTopComponent.findInstance().setSelectedNode(rootNode);
|
|
|
|
|
|
+ if (toolController.isSnapToGrid())
|
|
|
|
+ result.set(Math.round(result.x), result.y, Math.round(result.z));
|
|
|
|
+ toolController.doSetCursorLocation(result);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- });
|
|
|
|
-
|
|
|
|
- if (result != null) {
|
|
|
|
- updateToolsTransformation();
|
|
|
|
}
|
|
}
|
|
- }
|
|
|
|
-
|
|
|
|
- if (!pressed) {
|
|
|
|
- wasDragging = false;
|
|
|
|
|
|
+ wasDraggingL = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
public void actionSecondary(final Vector2f screenCoord, boolean pressed, final JmeNode rootNode, DataObject dataObject) {
|
|
public void actionSecondary(final Vector2f screenCoord, boolean pressed, final JmeNode rootNode, DataObject dataObject) {
|
|
- if (!pressed && !wasDragging) {
|
|
|
|
- final Vector3f result = pickWorldLocation(getCamera(), screenCoord, rootNode);
|
|
|
|
- if (result != null) {
|
|
|
|
- toolController.doSetCursorLocation(result);
|
|
|
|
|
|
+ if (pressed) {
|
|
|
|
+ // mouse down
|
|
|
|
+
|
|
|
|
+ if (!wasDraggingR && !wasDownR) { // wasn't dragging and was not down already
|
|
|
|
+ // pick on the spot
|
|
|
|
+ Spatial s = pickWorldSpatial(camera, screenCoord, rootNode);
|
|
|
|
+ if (!toolController.selectTerrain() && isTerrain(s) ) {
|
|
|
|
+ // only select non-terrain
|
|
|
|
+ selected = null;
|
|
|
|
+ return;
|
|
|
|
+ } else {
|
|
|
|
+
|
|
|
|
+ // climb up and find the Model Node (parent) and select that, don't select the geom
|
|
|
|
+ Spatial linkNodeParent = findModelNodeParent(s);
|
|
|
|
+ if (linkNodeParent != null)
|
|
|
|
+ s = linkNodeParent;
|
|
|
|
+ else
|
|
|
|
+ return;
|
|
|
|
+ final Spatial selec = s;
|
|
|
|
+ selected = selec;
|
|
|
|
+ java.awt.EventQueue.invokeLater(new Runnable() {
|
|
|
|
+ @Override
|
|
|
|
+ public void run() {
|
|
|
|
+ if (selec != null) {
|
|
|
|
+ SceneViewerTopComponent.findInstance().setActivatedNodes(new org.openide.nodes.Node[]{rootNode.getChild(selec)});
|
|
|
|
+ SceneExplorerTopComponent.findInstance().setSelectedNode(rootNode.getChild(selec));
|
|
|
|
+ } else {
|
|
|
|
+ SceneViewerTopComponent.findInstance().setActivatedNodes(new org.openide.nodes.Node[]{rootNode});
|
|
|
|
+ SceneExplorerTopComponent.findInstance().setSelectedNode(rootNode);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ toolController.updateSelection(selected);
|
|
}
|
|
}
|
|
- }
|
|
|
|
- if (!pressed) {
|
|
|
|
- wasDragging = false;
|
|
|
|
|
|
+ wasDownR = true;
|
|
|
|
+ } else {
|
|
|
|
+ // mouse up, stop everything
|
|
|
|
+ wasDownR = false;
|
|
|
|
+ wasDraggingR = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Climb up the spatial until we find the first node parent.
|
|
|
|
+ * TODO: use userData to determine the actual model's parent.
|
|
|
|
+ */
|
|
|
|
+ private Spatial findModelNodeParent(Spatial child) {
|
|
|
|
+ if (child instanceof Node)
|
|
|
|
+ return child;
|
|
|
|
+
|
|
|
|
+ if (child.getParent() != null)
|
|
|
|
+ return findModelNodeParent(child.getParent());
|
|
|
|
+
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
|
|
@Override
|
|
@Override
|
|
- public void mouseMoved(Vector2f screenCoord) {
|
|
|
|
|
|
+ public void mouseMoved(Vector2f screenCoord, JmeNode rootNode, DataObject currentDataObject, JmeSpatial selectedSpatial) {
|
|
|
|
+ if (currentState != null) {
|
|
|
|
+ //if (currentAxis != null) {
|
|
|
|
+ // manipulate from specific axis
|
|
|
|
+ handleMouseManipulate(screenCoord, currentState, currentAxis, rootNode, currentDataObject, selectedSpatial);
|
|
|
|
+ //} else {
|
|
|
|
+ // manipulate from screen coordinates
|
|
|
|
+ //}
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) {
|
|
public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) {
|
|
- wasDragging = pressed;
|
|
|
|
|
|
+ wasDraggingL = pressed;
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) {
|
|
public void draggedSecondary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) {
|
|
- wasDragging = pressed;
|
|
|
|
|
|
+ wasDraggingR = pressed;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Manipulate the spatial
|
|
|
|
+ */
|
|
|
|
+ private void handleMouseManipulate( Vector2f screenCoord,
|
|
|
|
+ State state,
|
|
|
|
+ Axis axis,
|
|
|
|
+ JmeNode rootNode,
|
|
|
|
+ DataObject currentDataObject,
|
|
|
|
+ JmeSpatial selectedSpatial)
|
|
|
|
+ {
|
|
|
|
+ if (state == State.translate) {
|
|
|
|
+ doMouseTranslate(axis, screenCoord, rootNode, selectedSpatial);
|
|
|
|
+ }
|
|
|
|
+ else if (state == State.scale) {
|
|
|
|
+ doMouseScale(axis, screenCoord, rootNode, selectedSpatial);
|
|
|
|
+ }
|
|
|
|
+ else if (state == State.rotate) {
|
|
|
|
+ doMouseRotate(axis, screenCoord, rootNode, selectedSpatial);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void doMouseTranslate(Axis axis, Vector2f screenCoord, JmeNode rootNode, JmeSpatial selectedSpatial) {
|
|
|
|
+ // free form translation
|
|
|
|
+ if (axis == null) {
|
|
|
|
+ if (toolController.isSnapToScene()) {
|
|
|
|
+ if (moving == null)
|
|
|
|
+ moving = new MoveUndo(selected, selected.getLocalTranslation().clone(), null);
|
|
|
|
+ Vector3f loc = pickWorldLocation(camera, screenCoord, rootNode, selectedSpatial);
|
|
|
|
+ if (loc != null) {
|
|
|
|
+ //Node sel = selectedSpatial.getLookup().lookup(Node.class);
|
|
|
|
+ if (toolController.isSnapToGrid()) {
|
|
|
|
+ selected.setLocalTranslation(loc.set(Math.round(loc.x), loc.y, Math.round(loc.z)));
|
|
|
|
+ } else {
|
|
|
|
+ selected.setLocalTranslation(loc);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ //TODO drag alone a camera-oriented axis
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ //snap to specific axis, ignoring snapToScene
|
|
|
|
+ if (moving == null)
|
|
|
|
+ moving = new MoveUndo(selected, selected.getLocalTranslation().clone(), null);
|
|
|
|
+ Vector3f loc = pickWorldLocation(camera, screenCoord, rootNode, selectedSpatial);
|
|
|
|
+ if (loc != null) {
|
|
|
|
+ if (toolController.isSnapToGrid())
|
|
|
|
+ loc.set(Math.round(loc.x), Math.round(loc.y), Math.round(loc.z)); // round the values
|
|
|
|
+
|
|
|
|
+ //Node sel = selectedSpatial.getLookup().lookup(Node.class);
|
|
|
|
+ Vector3f prev = selected.getLocalTranslation().clone();
|
|
|
|
+ Vector3f newLoc = new Vector3f(prev);
|
|
|
|
+ if (axis == Axis.x)
|
|
|
|
+ newLoc.x = loc.x;
|
|
|
|
+ else if (axis == Axis.y)
|
|
|
|
+ newLoc.y = loc.y;
|
|
|
|
+ if (axis == Axis.z)
|
|
|
|
+ newLoc.z = loc.z;
|
|
|
|
+
|
|
|
|
+ selected.setLocalTranslation(newLoc);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void doMouseScale(Axis axis, Vector2f screenCoord, JmeNode rootNode, JmeSpatial selectedSpatial) {
|
|
|
|
+ // scale based on the original mouse position and original model-to-screen position
|
|
|
|
+ // and compare that to the distance from the new mouse position and the original distance
|
|
|
|
+ if (startMouseCoord == null)
|
|
|
|
+ startMouseCoord = screenCoord.clone();
|
|
|
|
+ if (startSelectedCoord == null) {
|
|
|
|
+ Vector3f screen = getCamera().getScreenCoordinates(selected.getWorldTranslation());
|
|
|
|
+ startSelectedCoord = new Vector2f(screen.x, screen.y);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (scaling == null)
|
|
|
|
+ scaling = new ScaleUndo(selected, selected.getLocalScale().clone(), null);
|
|
|
|
+
|
|
|
|
+ float origDist = startMouseCoord.distanceSquared(startSelectedCoord);
|
|
|
|
+ float newDist = screenCoord.distanceSquared(startSelectedCoord);
|
|
|
|
+ if (origDist == 0)
|
|
|
|
+ origDist = 1;
|
|
|
|
+ float ratio = newDist/origDist;
|
|
|
|
+ Vector3f prev = selected.getLocalScale();
|
|
|
|
+ if (axis == Axis.x)
|
|
|
|
+ selected.setLocalScale(ratio, prev.y, prev.z);
|
|
|
|
+ else if (axis == Axis.y)
|
|
|
|
+ selected.setLocalScale(prev.x, ratio, prev.z);
|
|
|
|
+ else if (axis == Axis.z)
|
|
|
|
+ selected.setLocalScale(prev.x, prev.y, ratio);
|
|
|
|
+ else
|
|
|
|
+ selected.setLocalScale(ratio, ratio, ratio);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void doMouseRotate(Axis axis, Vector2f screenCoord, JmeNode rootNode, JmeSpatial selectedSpatial) {
|
|
|
|
+ if (startMouseCoord == null)
|
|
|
|
+ startMouseCoord = screenCoord.clone();
|
|
|
|
+ if (startSelectedCoord == null) {
|
|
|
|
+ Vector3f screen = getCamera().getScreenCoordinates(selected.getWorldTranslation());
|
|
|
|
+ startSelectedCoord = new Vector2f(screen.x, screen.y);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (rotating == null)
|
|
|
|
+ rotating = new RotateUndo(selected, selected.getLocalRotation().clone(), null);
|
|
|
|
+
|
|
|
|
+ Vector2f origRot = startMouseCoord.subtract(startSelectedCoord);
|
|
|
|
+ Vector2f newRot = screenCoord.subtract(startSelectedCoord);
|
|
|
|
+ float newRotAngle = origRot.angleBetween(newRot);
|
|
|
|
+ float temp = newRotAngle;
|
|
|
|
+
|
|
|
|
+ if (lastRotAngle != 0)
|
|
|
|
+ newRotAngle -= lastRotAngle;
|
|
|
|
+
|
|
|
|
+ lastRotAngle = temp;
|
|
|
|
+
|
|
|
|
+ Quaternion rotate = new Quaternion();
|
|
|
|
+ if (axis == Axis.x) {
|
|
|
|
+ rotate = rotate.fromAngleAxis(newRotAngle, Vector3f.UNIT_X);
|
|
|
|
+ } else if (axis == Axis.y) {
|
|
|
|
+ rotate = rotate.fromAngleAxis(newRotAngle, Vector3f.UNIT_Y);
|
|
|
|
+ } else if (axis == Axis.z) {
|
|
|
|
+ rotate = rotate.fromAngleAxis(newRotAngle, Vector3f.UNIT_Z);
|
|
|
|
+ } else {
|
|
|
|
+ Vector3f screen = getCamera().getScreenCoordinates(selected.getWorldTranslation());
|
|
|
|
+ rotate = rotate.fromAngleAxis(newRotAngle, screen.normalize());
|
|
|
|
+ }
|
|
|
|
+ selected.setLocalRotation(selected.getLocalRotation().mult(rotate));
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void duplicateSelected() {
|
|
|
|
+ if (selected == null)
|
|
|
|
+ return;
|
|
|
|
+ Spatial clone = selected.clone();
|
|
|
|
+ clone.move(1, 0, 1);
|
|
|
|
+
|
|
|
|
+ selected.getParent().attachChild(clone);
|
|
|
|
+ actionPerformed(new DuplicateUndo(clone, selected.getParent()));
|
|
|
|
+ selected = clone;
|
|
|
|
+ final Spatial cloned = clone;
|
|
|
|
+ final JmeNode rootNode = toolController.getRootNode();
|
|
|
|
+ refreshSelected(rootNode, selected.getParent());
|
|
|
|
+
|
|
|
|
+ java.awt.EventQueue.invokeLater(new Runnable() {
|
|
|
|
+ @Override
|
|
|
|
+ public void run() {
|
|
|
|
+ if (cloned != null) {
|
|
|
|
+ SceneViewerTopComponent.findInstance().setActivatedNodes(new org.openide.nodes.Node[]{rootNode.getChild(cloned)});
|
|
|
|
+ SceneExplorerTopComponent.findInstance().setSelectedNode(rootNode.getChild(cloned));
|
|
|
|
+ } else {
|
|
|
|
+ SceneViewerTopComponent.findInstance().setActivatedNodes(new org.openide.nodes.Node[]{rootNode});
|
|
|
|
+ SceneExplorerTopComponent.findInstance().setSelectedNode(rootNode);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // set to automatically 'grab'/'translate' the new cloned model
|
|
|
|
+ toolController.updateSelection(selected);
|
|
|
|
+ currentState = State.translate;
|
|
|
|
+ currentAxis = null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void deleteSelected() {
|
|
|
|
+ if (selected == null)
|
|
|
|
+ return;
|
|
|
|
+ Node parent = selected.getParent();
|
|
|
|
+ selected.removeFromParent();
|
|
|
|
+ actionPerformed(new DeleteUndo(selected, parent));
|
|
|
|
+
|
|
|
|
+ selected = null;
|
|
|
|
+ toolController.updateSelection(selected);
|
|
|
|
+
|
|
|
|
+ final JmeNode rootNode = toolController.getRootNode();
|
|
|
|
+ refreshSelected(rootNode, parent);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void refreshSelected(final JmeNode jmeRootNode, final Node parent) {
|
|
|
|
+ java.awt.EventQueue.invokeLater(new Runnable() {
|
|
|
|
+ @Override
|
|
|
|
+ public void run() {
|
|
|
|
+ jmeRootNode.getChild(parent).refresh(false);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private class MoveUndo extends AbstractUndoableSceneEdit {
|
|
|
|
+
|
|
|
|
+ private Spatial spatial;
|
|
|
|
+ private Vector3f before,after;
|
|
|
|
+
|
|
|
|
+ MoveUndo(Spatial spatial, Vector3f before, Vector3f after) {
|
|
|
|
+ this.spatial = spatial;
|
|
|
|
+ this.before = before;
|
|
|
|
+ this.after = after;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void sceneUndo() {
|
|
|
|
+ spatial.setLocalTranslation(before);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void sceneRedo() {
|
|
|
|
+ spatial.setLocalTranslation(after);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private class ScaleUndo extends AbstractUndoableSceneEdit {
|
|
|
|
+
|
|
|
|
+ private Spatial spatial;
|
|
|
|
+ private Vector3f before,after;
|
|
|
|
+
|
|
|
|
+ ScaleUndo(Spatial spatial, Vector3f before, Vector3f after) {
|
|
|
|
+ this.spatial = spatial;
|
|
|
|
+ this.before = before;
|
|
|
|
+ this.after = after;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void sceneUndo() {
|
|
|
|
+ spatial.setLocalScale(before);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void sceneRedo() {
|
|
|
|
+ spatial.setLocalScale(after);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private class RotateUndo extends AbstractUndoableSceneEdit {
|
|
|
|
+
|
|
|
|
+ private Spatial spatial;
|
|
|
|
+ private Quaternion before,after;
|
|
|
|
+
|
|
|
|
+ RotateUndo(Spatial spatial, Quaternion before, Quaternion after) {
|
|
|
|
+ this.spatial = spatial;
|
|
|
|
+ this.before = before;
|
|
|
|
+ this.after = after;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void sceneUndo() {
|
|
|
|
+ spatial.setLocalRotation(before);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void sceneRedo() {
|
|
|
|
+ spatial.setLocalRotation(after);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private class DeleteUndo extends AbstractUndoableSceneEdit {
|
|
|
|
+
|
|
|
|
+ private Spatial spatial;
|
|
|
|
+ private Node parent;
|
|
|
|
+
|
|
|
|
+ DeleteUndo(Spatial spatial, Node parent) {
|
|
|
|
+ this.spatial = spatial;
|
|
|
|
+ this.parent = parent;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void sceneUndo() {
|
|
|
|
+ parent.attachChild(spatial);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void sceneRedo() {
|
|
|
|
+ spatial.removeFromParent();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private class DuplicateUndo extends AbstractUndoableSceneEdit {
|
|
|
|
+
|
|
|
|
+ private Spatial spatial;
|
|
|
|
+ private Node parent;
|
|
|
|
+
|
|
|
|
+ DuplicateUndo(Spatial spatial, Node parent) {
|
|
|
|
+ this.spatial = spatial;
|
|
|
|
+ this.parent = parent;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void sceneUndo() {
|
|
|
|
+ spatial.removeFromParent();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void sceneRedo() {
|
|
|
|
+ parent.attachChild(spatial);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Check if the selected item is a Terrain
|
|
|
|
+ * It will climb up the parent tree to see if
|
|
|
|
+ * a parent is terrain too.
|
|
|
|
+ * Recursive call.
|
|
|
|
+ */
|
|
|
|
+ protected boolean isTerrain(Spatial s) {
|
|
|
|
+ if (s instanceof Terrain)
|
|
|
|
+ return true;
|
|
|
|
+
|
|
|
|
+ if (s.getParent() != null) {
|
|
|
|
+ return isTerrain(s.getParent());
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
}
|
|
}
|
|
|
|
+
|
|
}
|
|
}
|