/* * Project Info: http://jcae.sourceforge.net * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * (C) Copyright 2007, by EADS France */ package org.jcae.viewer3d; import java.awt.*; import java.awt.event.MouseEvent; import java.lang.reflect.Field; import java.util.logging.Logger; import javax.media.j3d.*; import javax.vecmath.*; import com.sun.j3d.utils.behaviors.vp.OrbitBehavior; import com.sun.j3d.utils.picking.PickCanvas; import com.sun.j3d.utils.picking.PickResult; import com.sun.j3d.utils.picking.PickTool; import org.jcae.viewer3d.cad.ViewableCAD; public class ViewBehavior extends OrbitBehavior { private final static Logger LOGGER= Logger.getLogger(ViewBehavior.class.getName()); /** For debugging (display the selection polytope) */ private final static boolean DEBUG_SEL_POLYTOPE = Boolean.getBoolean("org.jcae.viewer3d.showPolytope"); // dirty workaround for bug // https://java3d.dev.java.net/issues/show_bug.cgi?id=179 // because xtrans, ytrans and ztrans should be protected not private private final static Field FIELD_XTRANS; private final static Field FIELD_YTRANS; private final static Field FIELD_ZTRANS; private final static Field FIELD_ROTATETRANSFORM; static { Field xtrans=null, ytrans=null, ztrans=null, rotateTransform=null; try { xtrans = OrbitBehavior.class.getDeclaredField("xtrans"); ytrans = OrbitBehavior.class.getDeclaredField("ytrans"); ztrans = OrbitBehavior.class.getDeclaredField("ztrans"); rotateTransform = OrbitBehavior.class.getDeclaredField("rotateTransform"); xtrans.setAccessible(true); ytrans.setAccessible(true); ztrans.setAccessible(true); rotateTransform.setAccessible(true); } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } FIELD_XTRANS=xtrans; FIELD_YTRANS=ytrans; FIELD_ZTRANS=ztrans; FIELD_ROTATETRANSFORM=rotateTransform; } /** For debugging (display the selection polytope) */ private BranchGroup selectionPolytope; private Point anchor; private final View view; private final SelectionRectangle selectionRectangle3D; private boolean changeRotationCenter; //Viewer mouse modes public final static int DEFAULT_MODE=0; public final static int CLIP_RECTANGLE_MODE=1; public final static int CLIP_BOX_MODE=2; public final static int RECTANGLE_MODE=3; private int mouseMode=DEFAULT_MODE; private BranchGroup firstClipBoxPointGroup=null; private Point3d firstClipBoxPoint3d=null; private boolean locked; public ViewBehavior(View view) { super(view, OrbitBehavior.REVERSE_ALL); this.view = view; selectionRectangle3D = new SelectionRectangle.SelectionRectangle2D(view); } public void setChangeRotationCenter(boolean status) { view.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); changeRotationCenter=status; } /** Let this ignore mouse events */ public void lock() { this.locked = true; } /** @see unlock */ public void unlock() { this.locked = false; } @Override protected void processMouseEvent(MouseEvent evt) { if(!locked) switch(mouseMode){ case CLIP_RECTANGLE_MODE : rectangleClipMode(evt); break; case CLIP_BOX_MODE : clipBoxMode(evt); break; case RECTANGLE_MODE : rectangleMode(evt); break; case DEFAULT_MODE: default : defaultMode(evt); } } private void clipBoxMode(MouseEvent evt) { if (evt.getID() == MouseEvent.MOUSE_CLICKED && evt.getButton() == MouseEvent.BUTTON1) { if(firstClipBoxPointGroup==null){ firstClipBoxPoint3d=getPickPoint3d(evt); if(firstClipBoxPoint3d==null) return; PointAttributes pa=new PointAttributes(); pa.setPointSize(4); Appearance app=new Appearance(); app.setPointAttributes(pa); PointArray pt=new PointArray(1,GeometryArray.COORDINATES | GeometryArray.COLOR_3); pt.setCoordinate(0,firstClipBoxPoint3d); pt.setColor(0,new Color3f(1,0,0)); Shape3D s=new Shape3D(); s.setAppearance(app); s.setGeometry(pt); firstClipBoxPointGroup=new BranchGroup(); firstClipBoxPointGroup.addChild(s); view.addUnClipWidgetBranchGroup(firstClipBoxPointGroup); view.println("Select the second box point"); } else { Point3d secondClipBoxPoint3d=getPickPoint3d(evt); view.setClipBox(new ClipBox(firstClipBoxPoint3d,secondClipBoxPoint3d)); restoreDefaultMode(); } } else defaultMode(evt); } private Point3d getPickPoint3d(MouseEvent evt){ PickViewable result=basicPickPoint(evt); Point3d toReturn=null; if(result!=null) toReturn=result.getIntersection().getPointCoordinates(); return toReturn; } /** Defines what to do for the DEFAULT_MODE*/ private void defaultMode(MouseEvent evt){ if (evt.getID() == MouseEvent.MOUSE_CLICKED && evt.getButton() == MouseEvent.BUTTON1) { if(changeRotationCenter) changeRotationCenter(evt); else pickPoint(evt); } else { super.processMouseEvent(evt); if(motion) { Transform3D t3d = new Transform3D(); getViewingPlatform().getViewPlatformTransform().getTransform(t3d); fixOriginAxis(t3d); //notify slave view to change their position view.firePositionChanged(); } } } /** Defines what to do for the BOX_MODE*/ private void rectangleClipMode(MouseEvent evt){ if (anchor == null && evt.getButton() == MouseEvent.BUTTON1 && evt.getID() == MouseEvent.MOUSE_PRESSED) { startRectangleDrawing(evt, Color.MAGENTA); } else if (anchor != null) { if (evt.getButton() == MouseEvent.BUTTON1 && evt.getID() == MouseEvent.MOUSE_RELEASED) { endRectangleDrawing(evt); createClipRectanglePlanes(evt); } else { selectionRectangle3D.setGeometry(anchor, evt.getPoint()); } } } /** Defines what to do for the RECTANGLE_MODE*/ private void rectangleMode(MouseEvent evt){ if (anchor == null && evt.getButton() == MouseEvent.BUTTON1 && evt.getID() == MouseEvent.MOUSE_PRESSED) { startRectangleDrawing(evt, Color.WHITE); } else if (anchor != null) { if (evt.getButton() == MouseEvent.BUTTON1 && evt.getID() == MouseEvent.MOUSE_RELEASED) { pickRectangle(evt); endRectangleDrawing(evt); } else { selectionRectangle3D.setGeometry(anchor, evt.getPoint()); } } } /** clear all variables needed for other modes*/ private void restoreDefaultMode(){ //for box and rectangle mode anchor=null; //for changeRotationCenter changeRotationCenter=false; view.setCursor(Cursor.getDefaultCursor()); // for clipBox cancelClipBox(); mouseMode=DEFAULT_MODE; endRectangleDrawing(null); view.println("> Default mode"); } private void cancelClipBox() { if(firstClipBoxPointGroup==null) return; view.removeUnClipWidgetBranchGroup(firstClipBoxPointGroup); firstClipBoxPointGroup=null; } public void setMouseMode(int mode){ restoreDefaultMode(); switch(mode){ case CLIP_RECTANGLE_MODE : mouseMode=CLIP_RECTANGLE_MODE; view.println("> Clip rectangle mode (press space key to escape)"); break; case CLIP_BOX_MODE : view.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); mouseMode=CLIP_BOX_MODE; view.println("> Clip box mode (press space key to escape)"); view.println("Select the first box point"); break; case RECTANGLE_MODE : mouseMode=RECTANGLE_MODE; view.println("> Rectangle Selection mode (press space key to escape)"); break; } } public int getMouseMode(){ return mouseMode; } private Tuple3d getTranslation() { Vector3d toReturn=null; try { toReturn = new Vector3d( FIELD_XTRANS.getDouble(this), FIELD_YTRANS.getDouble(this), FIELD_ZTRANS.getDouble(this) ); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return toReturn; } private void setTranslation(Tuple3d vector) { try { FIELD_XTRANS.setDouble(this, vector.x); FIELD_YTRANS.setDouble(this, vector.y); FIELD_ZTRANS.setDouble(this, vector.z); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } private Transform3D getRotateTransform() { try { return (Transform3D) FIELD_ROTATETRANSFORM.get(this); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return null; } private void changeRotationCenter(MouseEvent evt) { PickViewable result=basicPickPoint(evt); if(result!=null) { Point3d newCenter=result.getIntersection().getPointCoordinates(); Point3d oldCenter=new Point3d(); getRotationCenter(oldCenter); Tuple3d trans = getTranslation(); Point3d centerDelta=new Point3d(); centerDelta.sub(oldCenter, newCenter); Transform3D invRot=new Transform3D(getRotateTransform()); invRot.invert(); invRot.transform(centerDelta); trans.add(centerDelta); //https://java3d.dev.java.net/issues/show_bug.cgi?id=179 //setTranslation(trans); setRotationCenter(newCenter); } changeRotationCenter=false; view.setCursor(Cursor.getDefaultCursor()); } protected PickViewable basicPickPoint(MouseEvent evt) { Viewable cv = view.getCurrentViewable(); if (cv == null) return null; if (!evt.isControlDown()) { LOGGER.finest("Ctrl is up so everything is unselected"); cv.unselectAll(); } PickCanvas pickCanvas = new PickCanvas(view, view.getBranchGroup(cv)); pickCanvas.setMode(PickTool.GEOMETRY_INTERSECT_INFO); pickCanvas.setTolerance(5.0f); pickCanvas.setShapeLocation(evt.getX(), evt.getY()); long time = System.currentTimeMillis(); PickViewable result = pickPoint(pickCanvas.pickAllSorted()); //PickViewable result=new PickViewable(pickCanvas.pickClosest(), 0); long time2 = System.currentTimeMillis(); LOGGER.finest("picked viewable is " + cv + " in " + (time2 - time) + " ms"); return result; } /** returns the first PickViewable in the modelClip and * between the front and back clip planes*/ protected PickViewable pickPoint(PickResult[] result){ if(result==null) return null; PickViewable toReturn=null; //Front and back clip distance are not related to picking distance, //neither in PHYSICAL_EYE neither in VIRTUAL_EYE modes, so they //cannot be used to filter picking. We keep this code as a remind. //double minDistance=view.getFrontClipDistance(); //double maxDistance=view.getBackClipDistance(); double minDistance=-Double.MAX_VALUE; double maxDistance=Double.MAX_VALUE; double d; for(int i=0;i<result.length;i++){ PickResult pr=result[i]; for(int j=0;j<pr.numIntersections();j++){ Point3d pt=pr.getIntersection(j).getPointCoordinatesVW(); if(view.isInModelClip(pt)){ d=pr.getIntersection(j).getDistance(); if( (d>minDistance)&(d<maxDistance)){ toReturn=new PickViewable(pr,j); maxDistance=d; } } } } return toReturn; } protected void pickPoint(MouseEvent evt) { PickViewable result = basicPickPoint(evt); if (result != null) { view.getCurrentViewable().pick(result); view.getCurrentViewable().pick(result, view); } } protected void startRectangleDrawing(MouseEvent evt, Color color) { anchor = evt.getPoint(); selectionRectangle3D.setGeometry(anchor, anchor); selectionRectangle3D.setColor(color); selectionRectangle3D.setVisible(true); } protected void endRectangleDrawing(MouseEvent evt) { anchor = null; selectionRectangle3D.setVisible(false); } protected void createClipRectanglePlanes(MouseEvent evt) { Viewable cv = view.getCurrentViewable(); if (cv == null) return; // if (!evt.isControlDown()) // { // Logger.global.finest("Ctrl is up so everything is unselected"); // cv.unselectAll(); // } Vector4d[] planes=new Vector4d[4]; new ViewPyramid(view, selectionRectangle3D.getGeometry2D()).getSidePlanes(planes); view.setClipPlanes(planes); } /** For debugging */ private void showSelectionPolytope(ViewPyramid shape) { if(DEBUG_SEL_POLYTOPE) { if(selectionPolytope==null) { selectionPolytope = new BranchGroup(); selectionPolytope.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND); selectionPolytope.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE); selectionPolytope.setCapability(BranchGroup.ALLOW_CHILDREN_READ); view.addBranchGroup(selectionPolytope); } selectionPolytope.removeAllChildren(); BranchGroup bg = new BranchGroup(); bg.setCapability(BranchGroup.ALLOW_DETACH); bg.addChild(shape.getShape3D()); selectionPolytope.addChild(bg); } } protected void pickRectangle(MouseEvent evt) { Viewable cv = view.getCurrentViewable(); if (cv == null) return; com.sun.j3d.utils.pickfast.PickCanvas pickCanvas = new com.sun.j3d.utils.pickfast.PickCanvas( view, view.getBranchGroup(cv)); ViewPyramid shape = new ViewPyramid(view, selectionRectangle3D.getGeometry2D(), view.getBound()); showSelectionPolytope(shape); Vector4d[] v = new Vector4d[4]; shape.getPlanes(v); pickCanvas.setMode(PickInfo.PICK_GEOMETRY); pickCanvas.setShapeBounds(shape, shape.getStartPoint()); PickInfo[] result = pickCanvas.pickAllSorted(); if (result != null && result.length > 0) { cv.pickArea(result, shape); cv.pickArea(result, shape, view); //Workaround for a bug in java3d. It does not fire all material //attribute changes so we force it in the next frame view.addPostRenderer(new Runnable() { public void run() { ViewableCAD.polygonAttrFront.setPolygonOffset( ViewableCAD.polygonAttrFront.getPolygonOffset()); ViewableCAD.polygonAttrBack.setPolygonOffset( ViewableCAD.polygonAttrBack.getPolygonOffset()); view.removePostRenderer(this); } }); } } private void fixOriginAxis(Transform3D t3d) { Vector3f translation = new Vector3f(); t3d.get(translation); Transform3D t3d2 = new Transform3D(); double scale = translation.length() / 10 * Math.tan(view.getView().getFieldOfView()); t3d2.setScale(scale); view.getOriginAxisTransformGroup().setTransform(t3d2); } }