/******************************************************************************* * Copyright (c) 2012, 2013 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.g3d.vtk.swt; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import javax.vecmath.AxisAngle4d; import javax.vecmath.Point3d; import javax.vecmath.Quat4d; import javax.vecmath.Vector3d; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Cursor; import org.eclipse.swt.widgets.Display; import org.simantics.g3d.math.EulerTools; import org.simantics.g3d.math.EulerTools.Order; import org.simantics.g3d.math.MathTools; import org.simantics.g3d.math.Ray; import org.simantics.g3d.preferences.PreferenceConstants; import org.simantics.g3d.scenegraph.IG3DNode; import org.simantics.g3d.scenegraph.base.INode; import org.simantics.g3d.scenegraph.structural.IStructuralNode; import org.simantics.g3d.vtk.Activator; import org.simantics.g3d.vtk.common.VTKNodeMap; import org.simantics.g3d.vtk.gizmo.RotateAxisGizmo; import org.simantics.g3d.vtk.utils.vtkUtil; import org.simantics.utils.threads.ThreadUtils; import vtk.vtkProp; /** * FIXME: complete rewrite. * * @author Marko Luukkainen * */ public class RotateAction extends vtkSwtAction{ public static final int X = 0; public static final int Y = 1; public static final int Z = 2; public static final int P = 3; private VTKNodeMap nodeMap; //private TranslateGizmo gizmo = new TranslateGizmo(); private RotateAxisGizmo gizmo = new RotateAxisGizmo(); private IG3DNode node; private Cursor activeCursor;// = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); private Cursor dragCursor;// = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR); int stepMethod = 1; Order order = Order.YXZ; private int steps; private double angles[]; int index = P; boolean valid = false; private boolean worldCoord = true; //private AxisAngle4d aa = null; private Quat4d parentWorldOrientation = null; //AxisAngle4d rotation = new AxisAngle4d(); Quat4d worldOrientation = new Quat4d(); public void setNode(IG3DNode node) { this.node = node; if ((node instanceof IStructuralNode) && ((IStructuralNode)node).isPartOfInstantiatedModel() && !((IStructuralNode)node).isInstantiatedModelRoot()) { setEnabled(false); } else { setEnabled(true); } String set = org.simantics.g3d.Activator.getDefault().getPreferenceStore().getString(PreferenceConstants.ORIENTATION_PRESENTATION); if (set.equals("aa")) { stepMethod = 0; } else if (set.equals("euler")){ stepMethod = 1; String eulerOrder = org.simantics.g3d.Activator.getDefault().getPreferenceStore().getString(PreferenceConstants.EULER_ANGLE_ORDER); try { order = Order.valueOf(eulerOrder); } catch (Exception e) { order = Order.YXZ; } } else { stepMethod = 2; } } public IG3DNode getNode() { return node; } public RotateAction(InteractiveVtkComposite panel, VTKNodeMap nodeMap) { super(panel); setImageDescriptor(Activator.imageDescriptorFromPlugin("com.famfamfam.silk", "icons/arrow_rotate_clockwise.png")); setText("Rotate"); this.nodeMap = nodeMap; steps = 36; angles = new double[steps+1]; for (int i = 0; i < angles.length; i++) { angles[i] = - Math.PI + (Math.PI * i * 2.0 / steps); } activeCursor = Display.getCurrent().getSystemCursor(SWT.CURSOR_HAND); dragCursor = Display.getCurrent().getSystemCursor(SWT.CURSOR_CROSS); } public void attach() { if (node == null) return; super.attach(); ThreadUtils.asyncExec(panel.getThreadQueue(), new Runnable() { public void run() { attachUI(); update(); } }); } public void deattach() { node = null; nodeMap.commit("Rotate"); deattachUI(); super.deattach(); panel.refresh(); } private void attachUI() { panel.getComponent().setCursor(activeCursor); gizmo.attach(panel); } private void deattachUI() { panel.getComponent().setCursor(Display.getCurrent().getSystemCursor(SWT.CURSOR_ARROW)); gizmo.deattach(); } @Override public boolean keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ESCAPE) panel.useDefaultAction(); if (valid) return true; if (e.getKeyCode() == KeyEvent.VK_X) { if (index != X) index = X; else index = P; } if (e.getKeyCode() == KeyEvent.VK_Y) { if (index != Y) index = Y; else index = P; } if (e.getKeyCode() == KeyEvent.VK_Z) { if (index != Z) index = Z; else index = P; } if (e.getKeyCode() == KeyEvent.VK_G) { worldCoord = !worldCoord; } gizmo.setType(index); panel.refresh(); return true; } @Override public boolean keyReleased(KeyEvent e) { return false; } @Override public boolean mouseClicked(MouseEvent e) { if (e.getClickCount() > 1) { if (isOverNode(e)) { return true; } panel.useDefaultAction(); //if(!gizmo.isPartOf(actor)) // panel.useDefaultAction(); return true; } return false; } public void setWorldCoord(boolean b) { if (worldCoord == b) return; worldCoord = b; update(); } private void update() { Vector3d nodePos = node.getWorldPosition(); System.out.println(nodePos); gizmo.setPosition(nodePos); if (worldCoord) { gizmo.setRotation(new AxisAngle4d()); parentWorldOrientation = null; } else { AxisAngle4d aa = new AxisAngle4d(); parentWorldOrientation = ((IG3DNode)node.getParent()).getWorldOrientation(); aa.set(parentWorldOrientation); gizmo.setRotation(aa); } Point3d camPos = new Point3d(panel.getRenderer().GetActiveCamera().GetPosition()); Vector3d p = new Vector3d(nodePos); p.sub(camPos); if (parentWorldOrientation != null) { Quat4d qi = new Quat4d(parentWorldOrientation); qi.inverse(); MathTools.rotate(parentWorldOrientation, p, p); } if (panel.getRenderer().GetActiveCamera().GetParallelProjection() == 0) { double distance = p.length(); p.negate(); double fov = panel.getRenderer().GetActiveCamera().GetViewAngle(); float s = (float) (Math.sin(fov) * distance * 0.1); Vector3d scale = new Vector3d(1., 1., 1.); // if (p.x > 0.f) // scale.x = -1.; // if (p.y > 0.f) // scale.y = -1.; // if (p.z > 0.f) // scale.z = -1.; scale.scale(s); gizmo.setScale(scale); } else { Vector3d scale = new Vector3d(1.f, 1.f, 1.f); double s = panel.getRenderer().GetActiveCamera().GetParallelScale() / 5.; // if (p.x > 0.f) // scale.x = -1.; // if (p.y > 0.f) // scale.y = -1.; // if (p.z > 0.f) // scale.z = -1.; scale.scale(s); gizmo.setScale(scale); } panel.refresh(); } private boolean isOverNode(MouseEvent e) { vtkProp picked[] = panel.pick(e.getX(), e.getY()); if (picked !=null) { for (int i = 0; i < picked.length; i++) { if (node.equals(nodeMap.getNode(picked[i]))) return true; } } return false; } @Override public boolean mousePressed(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { if (isOverNode(e)) { valid = true; if ((e.getModifiers() & MouseEvent.CTRL_MASK) > 0) { useStep = true; } else { useStep = false; } worldOrientation = node.getWorldOrientation(); doChanges(true, e.getX(), e.getY()); panel.getComponent().setCursor(dragCursor); } else { valid = false; getDefaultAction().mousePressed(e); panel.getComponent().setCursor(activeCursor); } } else { getDefaultAction().mousePressed(e); } return true; } @Override public boolean mouseReleased(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { valid = false; worldOrientation = null; panel.getComponent().setCursor(activeCursor); } else { getDefaultAction().mouseReleased(e); } return true; } @Override public boolean mouseDragged(MouseEvent e) { if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) > 0 && valid) { if ((e.getModifiers() & MouseEvent.CTRL_MASK) > 0) { useStep = true; } else { useStep = false; } doChanges(false, e.getX(), e.getY()); //nodeMap.modified(node); update(); } else { getDefaultAction().mouseDragged(e); update(); } return true; } Vector3d axis = null; @Override public boolean keyTyped(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_LEFT) { inputType = InputType.KEY; axis = new Vector3d(0.0,1.0,0.0); } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) { inputType = InputType.KEY; axis = new Vector3d(0.0,-1.0,0.0); } else if (e.getKeyCode() ==KeyEvent.VK_UP) { inputType = InputType.KEY; axis = new Vector3d(1.0,0.0,0.0); } else if (e.getKeyCode() == KeyEvent.VK_DOWN) { inputType = InputType.KEY; axis = new Vector3d(-1.0,0.0,0.0); } return true; } public void doChanges(boolean pressed, int x, int y) { Ray ray = vtkUtil.createMouseRay(panel.getRenderer(),x, y); Vector3d p = node.getWorldPosition(); if (pressed) { Vector3d axis = getRotationAxis(); if (axis != null) { if (!worldCoord) { MathTools.rotate(parentWorldOrientation, axis, axis); } double s[] = new double[2]; Vector3d i2 = new Vector3d(); boolean intersect = MathTools.intersectStraightPlane(ray.pos, ray.dir, p, axis, i2, s); double dot = Math.abs(ray.dir.dot(axis)); if (intersect && dot > 0.4) inputType = InputType.INTERSECT; else inputType = InputType.NONINTERSECT; if (inputType == InputType.INTERSECT) { // picking ray and plane defined by gizmo's center point and // rotation axis can intersect // vector from center point to intersection point i2.sub(p); // creating vectors i and j that are lying on the plane and // are perpendicular // vectors are used to calculate polar coordinate for // intersection point j.set(i2); i.cross(j, axis); System.out.println("I,J " + i + " " + j); double angleI = i2.angle(i); double angleJ = i2.angle(j); prevAngle = Math.atan2(Math.cos(angleJ), Math.cos(angleI)); } else { // picking ray and plane defined by gizmo's center point and // rotation axis are parallel, // so we'll use cross product of rotation axis and picking // ray to detect amount of rotation i.cross(ray.dir, axis); MathTools.intersectStraightStraight(ray.pos, ray.dir, p, i, new Vector3d(), new Vector3d(), s); prevS = s[1]; } } } if (inputType != InputType.KEY) axis = getRotationAxis(); if (axis == null) { return; } Vector3d taxis = null; if (!worldCoord) { taxis = new Vector3d(axis); MathTools.rotate(parentWorldOrientation, axis, axis); } System.out.println(inputType); if (inputType == InputType.INTERSECT) { double s[] = new double[2]; Vector3d i2 = new Vector3d(); MathTools.intersectStraightPlane(ray.pos, ray.dir, p, axis, i2, s); i2.sub(p); double angleI = i2.angle(i); double angleJ = i2.angle(j); double angle = Math.atan2(Math.cos(angleJ), Math.cos(angleI)); System.out.println("Angle " + angle + " i " + angleI + " j " + angleJ + " prev " + prevAngle); if(!worldCoord) axis = taxis; if (useStep) { //setOrientation(MathTools.getQuat(rotation)); AxisAngle4d rot = new AxisAngle4d(axis,angle-prevAngle); Quat4d qrot = new Quat4d(); MathTools.getQuat(rot, qrot); //prevAngle = angle; qrot.mulInverse(worldOrientation); if (stepMethod == 0) { rot.set(qrot); rot.angle = roundAngle(rot.angle); //qrot.set(rot); MathTools.getQuat(rot,qrot); setOrientation(qrot); } else if (stepMethod == 1){ //Vector3d euler = MathTools.getEuler(qrot); Vector3d euler = EulerTools.getEulerFromQuat(order, qrot); euler.x = roundAngle(euler.x); euler.y = roundAngle(euler.y); euler.z = roundAngle(euler.z); //Quat4d q = MathTools.getQuat(euler); Quat4d q = EulerTools.getQuatFromEuler(order, euler); setOrientation(q); System.out.println(" (" + MathTools.radToDeg(euler.x) + " " + MathTools.radToDeg(euler.y) + " " + MathTools.radToDeg(euler.z) + ") " + qrot + " "+ q); } else { setOrientation(qrot); } } else { if (worldCoord) { //G3DTools.multiplyOrientation(mo.getG3DNode(graph).getWorldOrientation(), new AxisAngle4d(axis,angle-prevAngle)); AxisAngle4d aa = MathTools.getAxisAngle(node.getWorldOrientation()); AxisAngle4d rot = new AxisAngle4d(axis,angle-prevAngle); MathTools.multiplyOrientation(aa, rot); setWorldOrientation(MathTools.getQuat(rot)); } else { AxisAngle4d aa = MathTools.getAxisAngle(node.getOrientation()); AxisAngle4d rot = new AxisAngle4d(axis,angle-prevAngle); MathTools.multiplyOrientation(aa, rot); setOrientation(MathTools.getQuat(rot)); //G3DTools.multiplyOrientation(mo.getG3DNode(graph).getLocalOrientation(), new AxisAngle4d(axis,angle-prevAngle)); } prevAngle = angle; } } else if (inputType == InputType.NONINTERSECT){ double s[] = new double[2]; MathTools.intersectStraightStraight(ray.pos, ray.dir, p, i, new Vector3d(), new Vector3d(), s); if(!worldCoord) axis = taxis; if (useStep) { //setOrientation(MathTools.getQuat(rotation)); AxisAngle4d rot = new AxisAngle4d(axis,s[1] - prevS); Quat4d qrot = new Quat4d(); //qrot.set(rot); MathTools.getQuat(rot, qrot); //prevAngle = angle; qrot.mulInverse(worldOrientation); if (stepMethod == 0) { rot.set(qrot); rot.angle = roundAngle(rot.angle); //qrot.set(rot); MathTools.getQuat(rot,qrot); setOrientation(qrot); } else if (stepMethod == 1){ //Vector3d euler = MathTools.getEuler(qrot); Vector3d euler = EulerTools.getEulerFromQuat(order, qrot); euler.x = roundAngle(euler.x); euler.y = roundAngle(euler.y); euler.z = roundAngle(euler.z); //Quat4d q = MathTools.getQuat(euler); Quat4d q = EulerTools.getQuatFromEuler(order, euler); setOrientation(q); System.out.println(" (" + MathTools.radToDeg(euler.x) + " " + MathTools.radToDeg(euler.y) + " " + MathTools.radToDeg(euler.z) + ") " + qrot + " "+ q); } else { setOrientation(qrot); } prevS = s[1]; // G3DTools.setOrientation(mo.getG3DNode(graph).getLocalOrientation(), rotations.get(mo)); // G3DTools.multiplyOrientation(mo.getG3DNode(graph).getWorldOrientation(), new AxisAngle4d(axis,s[1] - prevS)); // AxisAngle4d aa = G3DTools.getOrientation(mo.getG3DNode(graph).getLocalOrientation()); // rotations.put(mo, aa); // Vector3d euler = MathTools.getEuler(aa); // euler.x = roundAngle(euler.x); // euler.y = roundAngle(euler.y); // euler.z = roundAngle(euler.z); // aa = MathTools.getFromEuler2(euler); // prevS = s[1]; // G3DTools.setOrientation(mo.getG3DNode(graph).getLocalOrientation(), aa); // Vector3d e = MathTools.getEuler(G3DTools.getOrientation(mo.getG3DNode(graph).getLocalOrientation())); // e.scale(180.0/Math.PI); // text += G3DTools.getOrientation(mo.getG3DNode(graph).getLocalOrientation()) + " " + e + " "; } else { if (worldCoord) { AxisAngle4d aa = MathTools.getAxisAngle(node.getWorldOrientation()); AxisAngle4d rot = new AxisAngle4d(axis,s[1] - prevS); MathTools.multiplyOrientation(aa, rot); setWorldOrientation(MathTools.getQuat(rot)); //G3DTools.multiplyOrientation(mo.getG3DNode(graph).getWorldOrientation(), new AxisAngle4d(axis,s[1] - prevS)); } else { AxisAngle4d aa = MathTools.getAxisAngle(node.getOrientation()); AxisAngle4d rot = new AxisAngle4d(axis,s[1] - prevS); MathTools.multiplyOrientation(aa, rot); setOrientation(MathTools.getQuat(rot)); //G3DTools.multiplyOrientation(mo.getG3DNode(graph).getLocalOrientation(), new AxisAngle4d(axis,s[1] - prevS)); } //text += G3DTools.getOrientation(mo.getG3DNode(graph).getLocalOrientation()) + " " + MathTools.getEuler(G3DTools.getOrientation(mo.getG3DNode(graph).getLocalOrientation())) + " "; prevS = s[1]; } } else { if (worldCoord) { AxisAngle4d aa = MathTools.getAxisAngle(node.getWorldOrientation()); AxisAngle4d rot = new AxisAngle4d(axis,Math.PI * 0.5); MathTools.multiplyOrientation(aa, rot); setWorldOrientation(MathTools.getQuat(rot)); //G3DTools.multiplyOrientation(mo.getG3DNode(graph).getWorldOrientation(), new AxisAngle4d(axis,Math.PI * 0.5)); } else { AxisAngle4d aa = MathTools.getAxisAngle(node.getOrientation()); AxisAngle4d rot = new AxisAngle4d(axis,Math.PI * 0.5); MathTools.multiplyOrientation(aa, rot); setOrientation(MathTools.getQuat(rot)); //G3DTools.multiplyOrientation(mo.getG3DNode(graph).getLocalOrientation(), new AxisAngle4d(axis,Math.PI * 0.5)); } // text += G3DTools.getOrientation(mo.getG3DNode(graph).getLocalOrientation()) + " " + MathTools.getEuler(G3DTools.getOrientation(mo.getG3DNode(graph).getLocalOrientation())) + " "; } //setInfoText(text); } protected void setOrientation(Quat4d q) { node.setOrientation(q); } protected void setWorldOrientation(Quat4d q) { node.setWorldOrientation(q); } @Override public boolean mouseMoved(MouseEvent e) { return getDefaultAction().mouseMoved(e); } private Vector3d getRotationAxis() { switch (index) { case X: return new Vector3d(1.0, 0.0, 0.0); case Y: return new Vector3d(0.0, 1.0, 0.0); case Z: return new Vector3d(0.0, 0.0, 1.0); case P: Vector3d axis = new Vector3d(panel.getRenderer().GetActiveCamera() .GetDirectionOfProjection()); axis.normalize(); return axis; default: return null; } } private double prevS = 0.0; private Vector3d i = new Vector3d(); private Vector3d j = new Vector3d(); private double prevAngle = 0; enum InputType{INTERSECT,NONINTERSECT,KEY,NONE}; InputType inputType; private boolean useStep = false; private double roundAngle(double angle) { while (angle < - Math.PI) angle += Math.PI*2.0; while (angle > Math.PI) angle -= Math.PI*2.0; int index = 0; while (angle > angles[index]) index++; if (index == 0) { angle = angles[0]; } else { double d = angle - angles[index - 1]; double d2 = angles[index] - angle; if (d < d2) angle = angles[index - 1]; else angle = angles[index]; } return angle; } }