|
|
|
@ -6,77 +6,794 @@ package com.jme3.gde.scenecomposer.tools; |
|
|
|
|
|
|
|
|
|
import com.jme3.gde.core.sceneexplorer.SceneExplorerTopComponent; |
|
|
|
|
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.undoredo.AbstractUndoableSceneEdit; |
|
|
|
|
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.Vector3f; |
|
|
|
|
import com.jme3.scene.Node; |
|
|
|
|
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; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* |
|
|
|
|
* 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 |
|
|
|
|
*/ |
|
|
|
|
public class SelectTool extends SceneEditTool { |
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
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) { |
|
|
|
|
// 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 |
|
|
|
|
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 |
|
|
|
|
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 |
|
|
|
|
public void draggedPrimary(Vector2f screenCoord, boolean pressed, JmeNode rootNode, DataObject currentDataObject) { |
|
|
|
|
wasDragging = pressed; |
|
|
|
|
wasDraggingL = pressed; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|