/* * 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 2005, by EADS CRC * (C) Copyright 2007, by EADS France */ package org.jcae.viewer3d; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.media.j3d.*; import javax.swing.JDialog; import javax.swing.JTextPane; import javax.vecmath.*; import org.jcae.viewer3d.cad.ViewableCAD; import org.jdesktop.j3d.utils.behaviors.vp.AxisBehavior; import com.sun.j3d.utils.picking.PickResult; import com.sun.j3d.utils.universe.PlatformGeometry; import com.sun.j3d.utils.universe.SimpleUniverse; import com.sun.j3d.utils.universe.Viewer; import com.sun.j3d.utils.universe.ViewingPlatform; import java.io.File; import javax.imageio.ImageIO; import javax.swing.SwingUtilities; /** * An AWT component wich display Viewable in a Java3D canvas. * This class is responsible for handling picking and refresh events. * The expected navigation behavior is the one of the OrbitBehavior of Java3D. * Multiple selection is available using ctrl+Left click. * @author Jerome Robert * @todo all methods must be implemented. public methods may be added. */ public class View extends Canvas3D implements PositionListener { private static final float zFactorAbs=Float.parseFloat(System.getProperty("javax.media.j3d.zFactorAbs", "1.0f")); private static final float zFactorRel=Float.parseFloat(System.getProperty("javax.media.j3d.zFactorRel", "1.0f")); // when using high scaling value it would be better to set // -Dj3d.useBoxForGroupBounds=true private static final double X_SCALE = Double.parseDouble( System.getProperty("org.jcae.viewer3d.scale.x", "1.0")); private static final double Y_SCALE = Double.parseDouble( System.getProperty("org.jcae.viewer3d.scale.y", "1.0")); private static final double Z_SCALE = Double.parseDouble( System.getProperty("org.jcae.viewer3d.scale.z", "1.0")); private static final boolean DO_SCALE = X_SCALE != 1.0 || Y_SCALE != 1.0 || Z_SCALE != 1.0; /** Cheat codes to change polygon offset on CAD */ private class PAKeyListener extends KeyAdapter { float offset=zFactorAbs; float offsetRel=zFactorRel; @Override public void keyPressed(KeyEvent e) { boolean found=true; switch(e.getKeyChar()) { case ']': offset = checkBounds(2.0f * offset, offset); break; case '[': offset = checkBounds(0.5f * offset, offset); break; case '}': offsetRel = checkBounds(2.0f * offsetRel, offsetRel); break; case '{': offsetRel = checkBounds(0.5f * offsetRel, offsetRel); break; case '@': offset=zFactorAbs; offsetRel=zFactorRel; break; default: found=false; } if(found) { ViewableCAD.polygonAttrFront.setPolygonOffset(offset); ViewableCAD.polygonAttrBack.setPolygonOffset(offset); ViewableCAD.polygonAttrNone.setPolygonOffset(offset); ViewableCAD.polygonAttrFront.setPolygonOffsetFactor(offsetRel); ViewableCAD.polygonAttrBack.setPolygonOffsetFactor(offsetRel); ViewableCAD.polygonAttrNone.setPolygonOffsetFactor(offsetRel); System.out.println("zFactorAbs: "+offset+" zFactorRel: "+offsetRel); } } public final float checkBounds(float x, float old) { if (x == 0.0f) { if (old > 0.0f) return Float.MIN_VALUE; else return - Float.MIN_VALUE; } else if (x == Float.POSITIVE_INFINITY) return Float.MAX_VALUE; else if (x == Float.NEGATIVE_INFINITY) return - Float.MAX_VALUE; return x; } } final public static float FrontClipDistanceFactor=0.005f; final public static float BackClipDistanceFactor=5f; static JTextPane textPane; private Switch originAxisSwitch=new Switch(Switch.CHILD_NONE); private Switch fixedAxisSwitch=new Switch(Switch.CHILD_NONE); private TransformGroup fixedAxisTransformGroup=new TransformGroup(); private TransformGroup originAxisTransformGroup=new TransformGroup(); private PickResult lastPickResult; private View navigationMaster; private List<PositionListener> positionListeners=Collections.synchronizedList(new ArrayList<PositionListener>()); private transient BufferedImage snapshot; private transient Object snapshotLock=new Object(); private transient boolean takeSnapshot; private transient ScreenshotListener screenshotListener; static private SimpleUniverse sharedUniverse; private SimpleUniverse universe; static private Map<Viewable, ViewSpecificGroup> viewableToViewSpecificGroup=Collections.synchronizedMap(new HashMap<Viewable, ViewSpecificGroup>()); private ViewingPlatform viewingPlatform; private BranchGroup axisBranchGroup=new BranchGroup(); private Viewable currentViewable; protected ViewBehavior orbit; private AxisBehavior axisBehavior; ModelClip modelClip; private boolean isModelClip=false; private BranchGroup widgetsBranchGroup; private BranchGroup unClipWidgetsBranchGroup; private ClipBox clipBox=null; private PrintWriter writer=null; private List<Runnable> postRenderers=new ArrayList<Runnable>(); private boolean locked; private Cursor unlockedCursor; // This is to manage screen buffer capture private volatile Thread contextThread = null; // Color buffer private volatile ImageComponent2D imageComponent = null; private volatile Object waitScreenshot = new Object(); private volatile boolean takeScreenshot = false; private volatile Point screenshotPosition = new Point(); // Depth Buffer private volatile DepthComponentFloat depthComponent = null; private volatile Object waitDepthCapture = new Object(); private volatile boolean takeDepthBuffer = false; private volatile Point depthCapturePosition = new Point(); /** * From https://java3d.dev.java.net/issues/show_bug.cgi?id=89 * Finds the preferred <code>GraphicsConfiguration</code> object * for the system. This object can then be used to create the * Canvas3D objet for this system. * @param window the window in which the Canvas3D will reside * * @return The best <code>GraphicsConfiguration</code> object for * the system. */ private static GraphicsConfiguration getPreferredConfiguration(Window window) { if(window==null) return SimpleUniverse.getPreferredConfiguration(); GraphicsDevice device = window.getGraphicsConfiguration().getDevice(); GraphicsConfigTemplate3D template = new GraphicsConfigTemplate3D(); String stereo; // Check if the user has set the Java 3D stereo option. // Getting the system properties causes appletviewer to fail with a // security exception without a try/catch. stereo = (String) java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Object run() { return System.getProperty("j3d.stereo"); } }); // update template based on properties. if (stereo != null) { if (stereo.equals("REQUIRED")) template.setStereo(GraphicsConfigTemplate.REQUIRED); else if (stereo.equals("PREFERRED")) template.setStereo(GraphicsConfigTemplate.PREFERRED); } // Return the GraphicsConfiguration that best fits our needs. return device.getBestConfiguration(template); } /** * See https://java3d.dev.java.net/issues/show_bug.cgi?id=89 * @deprecated Will cause a "java.lang.IllegalArgumentException: adding a * container to a container on a different GraphicsDevice" in dual screen * mode. */ @Deprecated public View() { this(null, false, true); } /** * See https://java3d.dev.java.net/issues/show_bug.cgi?id=89 * @deprecated Will cause a "java.lang.IllegalArgumentException: adding a * container to a container on a different GraphicsDevice" in dual screen * mode. */ @Deprecated public View(boolean offscreen) { this(null, offscreen, true); } /** * See https://java3d.dev.java.net/issues/show_bug.cgi?id=89 * @deprecated Will cause a "java.lang.IllegalArgumentException: adding a * container to a container on a different GraphicsDevice" in dual screen * mode. */ @Deprecated public View(boolean offscreen, boolean isSharedUniverse) { this(null, offscreen, isSharedUniverse); } public View(Window window) { this(window, false, true); } public View(Window window, boolean offscreen) { this(window, offscreen, true); } public View(Window window, boolean offscreen, boolean isSharedUniverse) { super(getPreferredConfiguration(window), offscreen); if(offscreen) { getScreen3D().setPhysicalScreenWidth(0.0254/90.0 * 1600); getScreen3D().setPhysicalScreenHeight(0.0254/90.0 * 1200); } if(isSharedUniverse) { if(sharedUniverse==null) { sharedUniverse=new SimpleUniverse(this); universe=View.sharedUniverse; viewingPlatform=universe.getViewingPlatform(); } else { universe=View.sharedUniverse; viewingPlatform=createViewingPlatform(); sharedUniverse.getLocale().addBranchGraph(viewingPlatform); } } else { universe=new SimpleUniverse(this); viewingPlatform=universe.getViewingPlatform(); } PlatformGeometry platformGeometry = new PlatformGeometry(); platformGeometry.addChild(createLights()); platformGeometry.setCapability(Group.ALLOW_CHILDREN_WRITE); platformGeometry.setCapability(Group.ALLOW_CHILDREN_EXTEND); viewingPlatform.setPlatformGeometry(platformGeometry); orbit = new ViewBehavior(this); orbit.setCapability(Node.ALLOW_BOUNDS_WRITE); viewingPlatform.setViewPlatformBehavior(orbit); originAxisSwitch.setCapability(Switch.ALLOW_SWITCH_READ); originAxisSwitch.setCapability(Switch.ALLOW_SWITCH_WRITE); fixedAxisSwitch.setCapability(Switch.ALLOW_SWITCH_READ); fixedAxisSwitch.setCapability(Switch.ALLOW_SWITCH_WRITE); fixedAxisTransformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); // Create the origin axis createAxis(originAxisTransformGroup); originAxisSwitch.addChild(originAxisTransformGroup); ViewSpecificGroup vsp=new ViewSpecificGroup(); vsp.addView(getView()); vsp.addChild(originAxisSwitch); axisBranchGroup.addChild(vsp); universe.addBranchGraph(axisBranchGroup); createClipBranchGroup(); createWidgetsBranchGroup(); createUnClipWidgetsBranchGroup(); createFixedAxis(); getView().setFieldOfView(Math.PI/12); getView().setFrontClipPolicy(javax.media.j3d.View.PHYSICAL_EYE); getView().setBackClipPolicy(javax.media.j3d.View.PHYSICAL_EYE); zoomTo(0,0,0,1.0f); addKeyListener(new PAKeyListener()); setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); } private void createFixedAxis() { final TransformGroup tg=new TransformGroup(); tg.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); tg.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); Transform3D t3d=new Transform3D(); t3d.set(new Vector3d(-4, -4, -40)); createAxis(fixedAxisTransformGroup); tg.setTransform(t3d); tg.addChild(fixedAxisTransformGroup); fixedAxisSwitch.addChild(tg); ViewSpecificGroup vsp=new ViewSpecificGroup(); vsp.addView(getView()); BranchGroup bg = new BranchGroup(); vsp.addChild(fixedAxisSwitch); bg.addChild(vsp); axisBehavior = new AxisBehavior( fixedAxisTransformGroup, viewingPlatform.getViewPlatformTransform()); bg.addChild(axisBehavior); addComponentListener(new ComponentAdapter() { private Transform3D myT3d=new Transform3D(); @Override public void componentResized(ComponentEvent e) { if(getWidth()!=0) { tg.getTransform(myT3d); myT3d.set(new Vector3d(-4, -4*((float)getHeight())/getWidth(), -40)); tg.setTransform(myT3d); } } }); viewingPlatform.getPlatformGeometry().addChild(bg); } private void createWidgetsBranchGroup(){ widgetsBranchGroup=new BranchGroup(); widgetsBranchGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND); widgetsBranchGroup.setCapability(Node.ALLOW_BOUNDS_READ); widgetsBranchGroup.setCapability(Group.ALLOW_CHILDREN_WRITE); widgetsBranchGroup.setCapability(Group.ALLOW_CHILDREN_READ); BranchGroup parent=new BranchGroup(); parent.setCapability(Group.ALLOW_CHILDREN_EXTEND); parent.setCapability(BranchGroup.ALLOW_DETACH); parent.setCapability(Node.ALLOW_BOUNDS_READ); parent.setCapability(Group.ALLOW_CHILDREN_WRITE); ViewSpecificGroup vsg=new ViewSpecificGroup(); vsg.setCapability(ViewSpecificGroup.ALLOW_VIEW_WRITE); vsg.setCapability(ViewSpecificGroup.ALLOW_VIEW_READ); vsg.setCapability(Node.ALLOW_BOUNDS_READ); vsg.setUserData(parent); vsg.addChild(widgetsBranchGroup); parent.addChild(vsg); universe.getLocale().addBranchGraph(parent); vsg.addView(getView()); } private void createUnClipWidgetsBranchGroup(){ unClipWidgetsBranchGroup=new BranchGroup(); unClipWidgetsBranchGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND); unClipWidgetsBranchGroup.setCapability(Node.ALLOW_BOUNDS_READ); unClipWidgetsBranchGroup.setCapability(Group.ALLOW_CHILDREN_WRITE); unClipWidgetsBranchGroup.setCapability(Group.ALLOW_CHILDREN_READ); BranchGroup parent=new BranchGroup(); parent.setCapability(Group.ALLOW_CHILDREN_EXTEND); parent.setCapability(BranchGroup.ALLOW_DETACH); parent.setCapability(Node.ALLOW_BOUNDS_READ); parent.setCapability(Group.ALLOW_CHILDREN_WRITE); ViewSpecificGroup vsg=new ViewSpecificGroup(); vsg.setCapability(ViewSpecificGroup.ALLOW_VIEW_WRITE); vsg.setCapability(ViewSpecificGroup.ALLOW_VIEW_READ); vsg.setCapability(Node.ALLOW_BOUNDS_READ); vsg.setUserData(parent); vsg.addChild(unClipWidgetsBranchGroup); parent.addChild(vsg); universe.getLocale().addBranchGraph(parent); vsg.addView(getView()); } private void createClipBranchGroup(){ BranchGroup clipBranchGroup=new BranchGroup(); clipBranchGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND); clipBranchGroup.setCapability(Node.ALLOW_BOUNDS_READ); clipBranchGroup.setCapability(Group.ALLOW_CHILDREN_WRITE); clipBranchGroup.setCapability(Group.ALLOW_CHILDREN_READ); modelClip=new ModelClip(); modelClip.setEnables(new boolean[]{false, false, false, false, false, false}); modelClip.setCapability(ModelClip.ALLOW_ENABLE_READ); modelClip.setCapability(ModelClip.ALLOW_ENABLE_WRITE); modelClip.setCapability(ModelClip.ALLOW_PLANE_READ); modelClip.setCapability(ModelClip.ALLOW_PLANE_WRITE); modelClip.setCapability(ModelClip.ALLOW_SCOPE_READ); modelClip.setCapability(ModelClip.ALLOW_SCOPE_WRITE); modelClip.setCapability(ModelClip.ALLOW_INFLUENCING_BOUNDS_WRITE); clipBranchGroup.addChild(modelClip); universe.getLocale().addBranchGraph(clipBranchGroup); } /* (non-Javadoc) * @see java.lang.Object#finalize() */ @Override protected void finalize() throws Throwable { Viewable[] vs=getViewables(); for(int i=0; i<vs.length; i++) { remove(vs[i]); } universe.getLocale().removeBranchGraph(axisBranchGroup); modelClip.removeAllScopes(); universe.getLocale().removeBranchGraph((BranchGroup)modelClip.getParent()); BranchGroup parent=(BranchGroup)widgetsBranchGroup.getParent().getParent(); universe.getLocale().removeBranchGraph(parent); parent=(BranchGroup)unClipWidgetsBranchGroup.getParent().getParent(); universe.getLocale().removeBranchGraph(parent); } /** set a PrintWriter for the viewer messages*/ public void setPrintWriter(PrintWriter writer){ this.writer=writer; } /** print a line in the PrintWriter*/ public void println(String line){ if(writer!=null) { writer.println(line); writer.flush(); } } public TransformGroup getOriginAxisTransformGroup() { return originAxisTransformGroup; } /** * @return */ private ViewingPlatform createViewingPlatform() { ViewingPlatform vp=new ViewingPlatform(); vp.setUniverse(universe); Viewer viewer=new Viewer(this); viewer.setViewingPlatform(vp); return vp; } /** return all the clipped groups in the view*/ private Group[] getAllClipGroup(){ int numGroup=0; Viewable[] viewable=getViewables(); numGroup+=widgetsBranchGroup.numChildren(); numGroup+=viewable.length; Group[] toReturn=new Group[numGroup]; int ii=0; for(int i=0;i<widgetsBranchGroup.numChildren();i++){ toReturn[ii++]=(Group) widgetsBranchGroup.getChild(i); } for(int i=0;i<viewable.length;i++){ toReturn[ii++]=getBranchGroup(viewable[i]); } return toReturn; } /** Add a Viewable to the current view */ public void add(Viewable viewable) { ViewSpecificGroup vsg=viewableToViewSpecificGroup.get(viewable); if(vsg==null) { Node node=viewable.getJ3DNode(); if(DO_SCALE) { final Node fNode = node; final Transform3D t3d = new Transform3D(); t3d.setScale(new Vector3d(X_SCALE, Y_SCALE, Z_SCALE)); TransformGroup tg = new TransformGroup(t3d); tg.addChild(node); node = tg; } BranchGroup parent=new BranchGroup(); parent.setCapability(Group.ALLOW_CHILDREN_EXTEND); parent.setCapability(BranchGroup.ALLOW_DETACH); parent.setCapability(Node.ALLOW_BOUNDS_READ); parent.setCapability(Group.ALLOW_CHILDREN_WRITE); vsg=new ViewSpecificGroup(); vsg.setCapability(ViewSpecificGroup.ALLOW_VIEW_WRITE); vsg.setCapability(ViewSpecificGroup.ALLOW_VIEW_READ); vsg.setCapability(Node.ALLOW_BOUNDS_READ); vsg.setUserData(parent); viewableToViewSpecificGroup.put(viewable, vsg); vsg.addChild(node); parent.addChild(vsg); universe.getLocale().addBranchGraph(parent); } vsg.addView(getView()); if(currentViewable==null) currentViewable=viewable; updateModelClipGroup(); } /** * Allow to had a custom branchgroup to the view * @param branchGroup */ public void addBranchGroup(BranchGroup branchGroup) { branchGroup.setCapability(BranchGroup.ALLOW_DETACH); universe.getLocale().addBranchGraph(branchGroup); } /** * add a widget BranchGroup * @param branchGroup */ public void addWidgetBranchGroup(BranchGroup branchGroup) { branchGroup.setCapability(BranchGroup.ALLOW_DETACH); widgetsBranchGroup.addChild(branchGroup); updateModelClipGroup(); } /** * remove the specified widget BranchGroup * @param branchGroup */ public void removeWidgetBranchGroup(BranchGroup branchGroup){ widgetsBranchGroup.removeChild(branchGroup); updateModelClipGroup(); } /** * add a widget BranchGroup not clip by the clipModel * @param branchGroup */ public void addUnClipWidgetBranchGroup(BranchGroup branchGroup) { branchGroup.setCapability(BranchGroup.ALLOW_DETACH); unClipWidgetsBranchGroup.addChild(branchGroup); } /** * remove the specified widget BranchGroup not clip by the clipModel * @param branchGroup */ public void removeUnClipWidgetBranchGroup(BranchGroup branchGroup){ unClipWidgetsBranchGroup.removeChild(branchGroup); } private void setModelClip(ModelClip newModelClip){ removeModelClip(); updateModelClipGroup(); for(int i=0;i<6;i++){ if(newModelClip.getEnable(i)){ Vector4d plane=new Vector4d(); newModelClip.getPlane(i,plane); modelClip.setPlane(i,plane); modelClip.setEnable(i,true); } } modelClip.setInfluencingBounds(new BoundingSphere(new Point3d(),Double.MAX_VALUE)); isModelClip=true; } /** update the group list of the modelclip*/ private void updateModelClipGroup(){ if(!isModelClip) return; modelClip.removeAllScopes(); Group[] all=getAllClipGroup(); for(int i=0;i<all.length;i++){ modelClip.addScope(all[i]); } } private void initModelClipScope(ModelClip modelClip) { Viewable[] viewables=getViewables(); for(int i=0;i<viewables.length;i++) modelClip.addScope(getBranchGroup(viewables[i])); modelClip.addScope(widgetsBranchGroup); } /** * remove the ModelClip of the view */ public void removeModelClip(){ modelClip.setEnables(new boolean[]{false, false, false, false, false, false}); modelClip.removeAllScopes(); isModelClip=false; if(clipBox!=null) removeUnClipWidgetBranchGroup(clipBox.getShape()); clipBox=null; } /** create the modelclip with the specified planes and remove the previous modelclip * Warning : all shared viewables will be clipped in other views !! * */ public void setClipPlanes(Vector4d[] planes){ ModelClip mc=new ModelClip(); mc.setEnables(new boolean[]{false, false, false, false, false, false}); for(int ii=0;ii<planes.length;ii++){ mc.setPlane(ii,planes[ii]); mc.setEnable(ii,true); } mc.setInfluencingBounds(new BoundingSphere(new Point3d(),Double.MAX_VALUE)); setModelClip(mc); } /** create a clip Box and remove the previous modelclip * Warning : all shared viewables will be clipped in other views !! * */ public void setClipBox(ClipBox box) { setClipPlanes(box.getClipBoxPlanes()); addUnClipWidgetBranchGroup(box.getShape()); clipBox=box; } /**returns true if the point is in the modelclip*/ public boolean isInModelClip(Point3d pt){ if(!isModelClip) return true; Vector4d plane=new Vector4d(); double[] ptValues=new double[4]; pt.get(ptValues); ptValues[3]=1; Vector4d p=new Vector4d(ptValues); for(int i=0;i<6;i++){ if(!modelClip.getEnable(i)) continue; modelClip.getPlane(i,plane); if(plane.dot(p)>0) return false; } return true; } /** * @param view */ public void addPositionListener(PositionListener listener) { positionListeners.add(listener); } private Node createAxis(TransformGroup transformGroup) { float[] f=new float[] { 0, 0, 0, 1, 0, 0, // x line 0.9f, 0.1f, 0, 1, 0, 0, // x arrow 1 0.9f, -0.1f, 0, 1, 0, 0, // x arrow 2 0.9f, 0, 0.1f, 1, 0, 0, // x arrow 3 0.9f, 0, -0.1f, 1, 0, 0, // x arrow 1 0, 0, 0, 0, 1, 0, // y line 0.1f, 0.9f, 0, 0, 1, 0, // y arrow 1 -0.1f, 0.9f, 0, 0, 1, 0, // y arrow 2 0, 0.9f, 0.1f, 0, 1, 0, // y arrow 3 0, 0.9f, -0.1f, 0, 1, 0, // y arrow 4 0, 0, 0, 0, 0, 1, // z line 0.1f, 0, 0.9f, 0, 0, 1, // z arrow 1 -0.1f, 0, 0.9f, 0, 0, 1, // z arrow 2 0, 0.1f, 0.9f, 0, 0, 1, // z arrow 3 0, -0.1f, 0.9f, 0, 0, 1 // z arrow 4 }; LineArray la = new LineArray(f.length/3, GeometryArray.COORDINATES); la.setCoordinates(0, f); Appearance a = new Appearance(); Color3f color=new Color3f(0.5f, 0.5f, 0.7f); ColoringAttributes ca = new ColoringAttributes(color, ColoringAttributes.FASTEST); a.setColoringAttributes(ca); Shape3D s3d = new Shape3D(la); s3d.setAppearance(a); transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); transformGroup.addChild(s3d); transformGroup.addChild(new RasterTextLabel("x", Color.WHITE, 1.1f, 0, 0)); transformGroup.addChild(new RasterTextLabel("y", Color.WHITE, 0, 1.1f, 0)); transformGroup.addChild(new RasterTextLabel("z", Color.WHITE, 0, 0, 1.1f)); transformGroup.setCapability(Node.ALLOW_BOUNDS_READ); return transformGroup; } private Node createLights() { Group toReturn = new Group(); Color3f cl = new Color3f(1f, 1f, 1f); //Color3f cd = new Color3f(0.4f, 0.4f, 0.4f); BoundingSphere bs = new BoundingSphere(new Point3d(0,0,-10), Double.MAX_VALUE/2); DirectionalLight l1 = new DirectionalLight(cl, new Vector3f(0,0,1f)); l1.setInfluencingBounds(bs); DirectionalLight l2 = new DirectionalLight(cl, new Vector3f(0,0,-1f)); l2.setInfluencingBounds(bs); //Light l3 = new DirectionalLight(cd, new Vector3f(0,1f,0)); l2.setInfluencingBounds(bs); toReturn.addChild(l1); toReturn.addChild(l2); //toReturn.addChild(l3); toReturn.addChild(new AmbientLight(new Color3f(0.7f, 0.7f, 0.7f))); return toReturn; } private void displayTransform3d(Transform3D transform) { if(textPane==null) { JDialog d=new JDialog(); textPane=new JTextPane(); d.setContentPane(textPane); d.setVisible(true); } String cr=System.getProperty("line.separator"); String s="scale="+transform.getScale()+cr; Matrix3f m=new Matrix3f(); Vector3f v=new Vector3f(); transform.get(m); transform.get(v); s+="rotation="+m+cr; s+="translation="+v+cr; textPane.setText(s); } protected void firePositionChanged() { Iterator<PositionListener> it=positionListeners.iterator(); while(it.hasNext()) { PositionListener p=it.next(); p.positionChanged(); } } /** Fit the view to show the specified viewable */ public void fit(Viewable viewable) { BoundingSphere b= (BoundingSphere)getBranchGroup(viewable).getBounds(); Point3d c=new Point3d(); b.getCenter(c); zoomTo((float)c.x,(float)c.y,(float)c.z,(float)b.getRadius()); } /** Fit the view to show all the Viewable */ public void fitAll() { BoundingSphere bs=getBound(); if(bs.getRadius()<=0) bs=new BoundingSphere(); Point3d c=new Point3d(); bs.getCenter(c); zoomTo((float)c.x,(float)c.y,(float)c.z,(float)bs.getRadius()); } /** * This transforms from Normalized Eyes Coordinates (NEC) to Display Coordinates in AWT meanings (the z value is the same value of the zbuffer). * You can go from virtual world coordinates to NEC with getVWorldProjection method and using left projection. * @param point * @param pointTransformed */ public void normalizedEyeCoordinateToViewportCoordinate(Tuple4d point, Tuple3d pointTransformed) { // Transform from [-1,1] to [0,1] if(point.y < -1. || point.y > 1.) throw new RuntimeException("PWET : " + point); pointTransformed.x = (point.x + 1.) * .5; pointTransformed.y = (point.y + 1.) * .5; pointTransformed.z = (-point.z + 1.) * .5; pointTransformed.x *= getWidth(); pointTransformed.y *= getHeight(); pointTransformed.y = getHeight() - pointTransformed.y; } /** restore the Front clip distance to see all the Viewable */ public void restoreFrontClipDistance(){ BoundingSphere bs=getBound(); if(bs.getRadius()<=0) bs=new BoundingSphere(); getView().setFrontClipDistance(FrontClipDistanceFactor*(float)bs.getRadius()); } public void setFrontClipDistance(double d){ getView().setFrontClipDistance(d); } public double getFrontClipDistance(){ return getView().getFrontClipDistance(); } public double getBackClipDistance(){ return getView().getBackClipDistance(); } public void setBackClipDistance(double d){ getView().setBackClipDistance(d); } public BoundingSphere getBound() { Iterator<ViewSpecificGroup> it=viewableToViewSpecificGroup.values().iterator(); ArrayList<Bounds> bounds=new ArrayList<Bounds>(); while(it.hasNext()) { ViewSpecificGroup bg=it.next(); if(bg.indexOfView(getView())!=-1) { Bounds b=bg.getBounds(); bounds.add(b); } } if(isOriginAxisVisible()) bounds.add(originAxisTransformGroup.getBounds()); BoundingSphere bs; if(!bounds.isEmpty()) { Bounds bbs= bounds.get(0); if(bbs instanceof BoundingSphere) bs=(BoundingSphere) bbs; else bs = new BoundingSphere(bbs); bs.combine(bounds.toArray(new Bounds[bounds.size()])); } else bs=new BoundingSphere(); return bs; } /** * Get the cloned branchgroup of a viewable for this view. * The viewables may used it to modify a branchgroup whithout rebuilding * it entirely. * @param view * @return * */ protected BranchGroup getBranchGroup(Viewable viewable) { ViewSpecificGroup vsp=viewableToViewSpecificGroup.get(viewable); if(vsp==null) return null; return (BranchGroup)vsp.getUserData(); } /** Return viewables shown in this view */ public Viewable[] getViewables() { ArrayList<Viewable> toReturn=new ArrayList<Viewable>(); Iterator<Map.Entry<Viewable, ViewSpecificGroup>> it=viewableToViewSpecificGroup.entrySet().iterator(); while(it.hasNext()) { Map.Entry<Viewable, ViewSpecificGroup> e=it.next(); Viewable v=e.getKey(); ViewSpecificGroup vsg=e.getValue(); if(vsg.indexOfView(getView())!=-1) { toReturn.add(v); } } return toReturn.toArray(new Viewable[toReturn.size()]); } public Viewable getCurrentViewable() { return currentViewable; } public ViewingPlatform getViewingPlatform() { return viewingPlatform; } public void setCurrentViewable(Viewable v) { currentViewable=v; } /** Move the view to the specified position */ public void move(Transform3D position) { viewingPlatform.getViewPlatformTransform().setTransform(position); } /** * Implement PositionListener. * This listener is fired when the navigation master move. */ public void positionChanged() { move(navigationMaster.where()); } /** * Overloaded for to be able to take snapshots and draw overlays * (selection rectangle) * @see takeSnapshot */ @Override public void postSwap() { super.postSwap(); contextThread = Thread.currentThread(); try { if (takeSnapshot) { snapshot = getImage(); takeSnapshot = false; synchronized(snapshotLock) { snapshotLock.notifyAll(); } if(screenshotListener!=null) screenshotListener.shot(snapshot); } if(takeScreenshot) { captureColorBuffer(); takeScreenshot = false; synchronized(waitScreenshot) { waitScreenshot.notifyAll(); } } if(takeDepthBuffer) { captureDepth(); takeDepthBuffer = false; synchronized (waitDepthCapture) { waitDepthCapture.notifyAll(); } //System.out.println("ZBUFFER : " + Arrays.toString(depth)); } } catch(Exception ex) { ex.printStackTrace(); } } /** * Return an Image representing the current Canvas3D * This method is not synchronized with the Java3D rendering. * It used by takeSnapShopt method (which add synchronization), * and for the rendering of the rectangle selection. */ protected BufferedImage getImage() { Dimension dim=getSize(); GraphicsContext3D ctx = getGraphicsContext3D(); Raster ras=new Raster(); ras.setSize(dim); ras.setImage(new ImageComponent2D(ImageComponent.FORMAT_RGB,dim.width, dim.height)); ctx.readRaster(ras); // Now strip out the image info return ras.getImage().getImage(); } /** * This is the new and simpliest method to take screenshot. * Be careful : this method was not already tested. * Warning : This methods works in all thread except in J3D behaviour threads. * I don't know if it works in other J3D threads. I don't investigate much. Certainly * a mutex that lock all the threads... * @param x * @param y * @param width * @param height * @return */ public synchronized BufferedImage getScreenshot(int x, int y, int width, int height) { imageComponent = new ImageComponent2D(ImageComponent.FORMAT_RGB, width, height); screenshotPosition.x = x; screenshotPosition.y = y; if(Thread.currentThread().equals(contextThread)) { captureColorBuffer(); } else { takeScreenshot = true; // If we aren't in the swing thread go to it to send refresh event if(!SwingUtilities.isEventDispatchThread()) { try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { getView().repaint(); } }); } catch (InterruptedException ex) { Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex); } catch (InvocationTargetException ex) { Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex); } } else getView().repaint(); try { // If the screenshot is not already done then wait if(takeScreenshot) { synchronized (waitScreenshot) { waitScreenshot.wait(); } } } catch (InterruptedException ex) { Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex); } } // Now strip out the image info return imageComponent.getImage(); } private void captureColorBuffer() { GraphicsContext3D ctx = getGraphicsContext3D(); Raster ras=new Raster(); ras.setSrcOffset(screenshotPosition); ras.setSize(new Dimension(imageComponent.getWidth(), imageComponent.getHeight())); ras.setImage(imageComponent); ctx.readRaster(ras); imageComponent = ras.getImage(); } /** * This method create a png file in the tempory directory containing the depth buffer. * To see correctly the depth buffer a normalisation and scale color is maded. * @param buffer * @param width * @param height */ private void debugDepthBuffer(float[] buffer, int width, int height) { System.out.println("width : " + width); System.out.println("height : " + height); System.out.println("buffer length : " + buffer.length); assert buffer.length == width * height : "buffer with not good length"; //assert false; float zmin =Float.MAX_VALUE, zmax = Float.MIN_VALUE; for(float z : buffer) { zmin = Math.min(z, zmin); zmax = Math.max(z, zmax); } BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); for(int i = 0 ; i < depthComponent.getWidth() ; ++i) { for(int j = 0 ; j < depthComponent.getHeight() ; ++j) {//aZbuffer[(x - xMin) + (xMax - xMin + 1) * (y - yMin)]; float z = buffer[i + j * depthComponent.getWidth()]; float zNormalized = (z-zmin) / (zmax - zmin); int c = (int)( zNormalized * 255.); assert c >=0; assert c < 256; int rgb = c << 8 | c << 16 | c; //System.out.println("C : " + c); //int rgb = c; image.setRGB(i, j, rgb); } } try { ImageIO.write(image, "PNG", File.createTempFile("pwet", "png")); } catch (IOException ex) { Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex); } } /** * This function works only with a patched java3d version. * Contact a jCAE developper on the forum for more information. * Warning : see warning of getScreenshot * See RectangleSelection program for example of use. * @param x * @param y * @param width * @param height * @return */ public synchronized float[] getDepthBuffer(int x, int y, int width, int height) { depthComponent = new DepthComponentFloat(width, height); depthCapturePosition.x = x; depthCapturePosition.y = y; if(Thread.currentThread().equals(contextThread)) { captureDepth(); } else { takeDepthBuffer = true; // If we aren't in the swing thread go to it to send refresh event if(!SwingUtilities.isEventDispatchThread()) { try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { getView().repaint(); } }); } catch (InterruptedException ex) { Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex); } catch (InvocationTargetException ex) { Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex); } } else getView().repaint(); try { // If the screenshot is not already done then wait if(takeDepthBuffer) { synchronized (waitDepthCapture) { waitDepthCapture.wait(); } } } catch (InterruptedException ex) { Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex); } } float[] zbuffer = new float[depthComponent.getWidth() * depthComponent.getHeight()]; depthComponent.getDepthData(zbuffer); // UNCOMMENT FOR DEBUGGING //debugDepthBuffer(zbuffer, depthComponent.getWidth() , depthComponent.getHeight()); return zbuffer; } private void captureDepth() { GraphicsContext3D ctx = getGraphicsContext3D(); depthComponent.setCapability(DepthComponent.ALLOW_DATA_READ); Raster ras=new Raster(new Point3f(0.0f, 0.0f, 0.0f), Raster.RASTER_DEPTH, depthCapturePosition.x, depthCapturePosition.y, depthComponent.getWidth(), depthComponent.getHeight(), null, depthComponent); ras.setCapability(Raster.ALLOW_DEPTH_COMPONENT_READ); ras.setCapability(Raster.ALLOW_IMAGE_READ); ras.setCapability(Raster.ALLOW_TYPE_READ); //ras.setImage(new ImageComponent2D(ImageComponent.FORMAT_RGB,dim.width, dim.height)); ctx.readRaster(ras); } /** Remove a viewable from this view */ public void remove(Viewable viewable) { ViewSpecificGroup vsg=viewableToViewSpecificGroup.get(viewable); if(vsg!=null) { vsg.removeView(getView()); if(vsg.numViews()==0) { universe.getLocale().removeBranchGraph(getBranchGroup(viewable)); viewableToViewSpecificGroup.remove(viewable); vsg.removeAllChildren(); } } if(currentViewable==viewable) { Viewable[] vs=getViewables(); if(vs.length>0) currentViewable=vs[0]; else currentViewable=null; } updateModelClipGroup(); } /** inform if the view contains the viewable*/ public boolean contains(Viewable viewable){ ViewSpecificGroup vsg=viewableToViewSpecificGroup.get(viewable); if(vsg==null) return false; Enumeration e=vsg.getAllViews(); while(e.hasMoreElements()) if(e.nextElement()==this.getView()) return true; return false; } /** Create a navigation link with the specified view. * This will ensure that the current view show the same Position as the * specified view. * @param view the master view, null mean no master. */ public void setNavigationMaster(View view) { navigationMaster=view; view.addPositionListener(this); } /** * Set the list of Viewables to which picking events will be dispatched. * The Viewable objects not in this list will not be pickable. * @param viewables */ public void setPickableViewables(Collection viewables) { //TODO } /** * Set the picking mode: * <ul> * <li>0, for single click picking</li> * <li>1, for rectangular selection picking</li> * <li>2, for polygonal selection picking</li> * </ul> * @param mode */ public void setPickingMode(short mode) { //TODO } /** * Display 3D x-y-z cartesian, at the center of the view. * These axis are visible whenever the 3D origin cannot be seen. * Axis are identified with strings "x","y" and "z". * The size of the axis do not depends on the zoom level. * @param show */ public void setFixedAxisVisible(boolean show) { if(show) fixedAxisSwitch.setWhichChild(0); else fixedAxisSwitch.setWhichChild(Switch.CHILD_NONE); } /** * Display 3D x-y-z cartesian at the 3D origin of the model. * Axis are identified with strings "x","y" and "z". * The size of the axis do not depends on the zoom level. * @param show */ public void setOriginAxisVisible(boolean show) { if(show) originAxisSwitch.setWhichChild(0); else originAxisSwitch.setWhichChild(Switch.CHILD_NONE); } public boolean isOriginAxisVisible() { return originAxisSwitch.getWhichChild()==0; } /** * Take a snapshot of the current view * Do not use this for offscreen rendering. See "On-screen Rendering vs. Off-screen Rendering" in * Canvas3D javadoc. This method should be wrapped in a SwingUtilities.invokeXXXX statements. * @deprecated Use takeScreenshot Not thread safe. In some configuration a deadlock could * occure. */ // snapshotLock.wait(); must not be run in an AWT thread because // it would block the AWT event thread. Then AWT would never notify the // J3D rendering thread and snapshotLock.wait() would never return @Deprecated public BufferedImage takeSnapshot() { takeSnapshot=true; synchronized(snapshotLock) { try { snapshotLock.wait(); } catch(InterruptedException ex) { ex.printStackTrace(); return new BufferedImage(0, 0, BufferedImage.TYPE_BYTE_INDEXED); } } return snapshot; } /** * Take a snapshot of the current view * Do not use this for offscreen rendering. See "On-screen Rendering vs. Off-screen Rendering" in * Canvas3D javadoc. */ public void takeScreenshot(ScreenshotListener listener) { screenshotListener=listener; takeSnapshot=true; getView().repaint(); } /** * Take a snapshot of the current view in Off-screen mode * Do not use this for on-screen rendering. See "On-screen Rendering vs. Off-screen Rendering" in * Canvas3D javadoc. */ public BufferedImage takeSnapshot(int w, int h) { getScreen3D().setSize(w,h); BufferedImage image=new BufferedImage(w,h, ImageComponent.FORMAT_RGB); setOffScreenBuffer(new ImageComponent2D(ImageComponent.FORMAT_RGB, image)); renderOffScreenBuffer(); waitForOffScreenRendering(); return getOffScreenBuffer().getImage(); } /** Return the current position of the view */ public Transform3D where() { Transform3D t3d=new Transform3D(); viewingPlatform.getViewPlatformTransform().getTransform(t3d); return t3d; } /** Modify the view to best see what is include a given sphere * @param x x coordinate of the center of the sphere * @param y y coordinate of the center of the sphere * @param z z coordinate of the center of the sphere * @param radius radius of the sphere */ public void zoomTo(float x, float y, float z, float radius) { Point3d c=new Point3d(x,y,z); BoundingSphere b=new BoundingSphere(c,radius); orbit.setBounds(b); orbit.setRotationCenter(c); orbit.setZoomFactor(b.getRadius()); orbit.setTransFactors(b.getRadius()/10,b.getRadius()/10); orbit.setRotFactors(0.5, 0.5); orbit.setSchedulingBounds(new BoundingSphere(c,b.getRadius()*100)); axisBehavior.setSchedulingBounds(orbit.getSchedulingBounds()); //getView().setWindowResizePolicy(javax.media.j3d.View.VIRTUAL_WORLD); Transform3D t3d = new Transform3D(); viewingPlatform.getViewPlatformTransform().getTransform(t3d); //calculate the translation vector for a identity rotation matrix float focal=(float) Math.tan(getView().getFieldOfView()/2); Vector3f correction=new Vector3f(0f, 0f, radius/focal); t3d.setTranslation(new Vector3f()); //rotate the translation vector t3d.transform(correction); correction.add(new Vector3f(x,y,z)); t3d.setTranslation(correction); viewingPlatform.getViewPlatformTransform().setTransform(t3d); double backClipDistance = radius*(1/focal+1); getView().setFrontClipDistance(backClipDistance/2000); getView().setBackClipDistance(backClipDistance); } public double[] getRotationCenter() { Point3d p3d=new Point3d(); orbit.getRotationCenter(p3d); double[] toReturn=new double[3]; p3d.get(toReturn); return toReturn; } public void setRotationCenter(double x, double y, double z) { orbit.setRotationCenter(new Point3d(x, y, z)); } /** * Allow the user to specify the rotation center. * The next click will be concidered as a rotation center * redefinition. */ public void setChangeRotationCenter(boolean status) { this.requestFocus(); ((ViewBehavior)orbit).setChangeRotationCenter(true); } /** set the current mouse mode : see ViewBehavior*/ public void setMouseMode(int mode){ this.requestFocus(); ((ViewBehavior)orbit).setMouseMode(mode); } public int getMouseMode(){ return ((ViewBehavior)orbit).getMouseMode(); } public final static byte TOP =0; public final static byte BOTTOM =1; public final static byte LEFT =2; public final static byte RIGHT =3; public final static byte FRONT =4; public final static byte BACK =5; /** * Change the view angle to a predefined one. * It could be TOP, BOTTOM, LEFT, RIGHT, FRONT, BACK. */ public void setOrientation(byte orientation) { Point3d eye=null; Vector3d up=new Vector3d(0,1,0); switch(orientation) { case TOP: eye=new Point3d(0, 1, 0); up=new Vector3d(0,0,-1); break; case BOTTOM: eye=new Point3d(0, -1, 0); up=new Vector3d(0,0,1); break; case LEFT: eye=new Point3d(-1, 0, 0); break; case RIGHT: eye=new Point3d(1, 0, 0); break; case FRONT: eye=new Point3d(0, 0, 1); break; case BACK: eye=new Point3d(0, 0, -1); break; default: throw new IllegalArgumentException(); } BoundingSphere bs=getBound(); Transform3D t3d=new Transform3D(); Point3d center=new Point3d(); bs.getCenter(center); float focal=(float) Math.tan(getView().getFieldOfView()/2); eye.scale(bs.getRadius()/focal); t3d.lookAt(eye, center, up); t3d.invert(); move(t3d); } /** refresh the clip planes value*/ protected void fireViewableChanged(Viewable viewable){ if(!isModelClip) return; int planeCount=0; for(int i=0;i<6;i++){ if(modelClip.getEnable(i)) planeCount++; } Vector4d[] planes=new Vector4d[planeCount]; int ii=0; for(int i=0;i<6;i++){ if(modelClip.getEnable(i)) { planes[ii]=new Vector4d(); modelClip.getPlane(i,planes[ii]); ii++; } } setClipPlanes(planes); } public static void viewableChanged(Viewable viewable){ ViewSpecificGroup vsg=viewableToViewSpecificGroup.get(viewable); if(vsg!=null){ Enumeration e=vsg.getAllViews(); while(e.hasMoreElements()){ Object o=e.nextElement(); if( o instanceof javax.media.j3d.View){ Enumeration ee=((javax.media.j3d.View)o).getAllCanvas3Ds(); while(ee.hasMoreElements()){ Object oo=ee.nextElement(); if( oo instanceof View){ ((View)oo).fireViewableChanged(viewable); } } } } } } public static void stopRenderer(Viewable viewable){ ViewSpecificGroup vsg=viewableToViewSpecificGroup.get(viewable); if(vsg!=null){ Enumeration e=vsg.getAllViews(); while(e.hasMoreElements()){ Object o=e.nextElement(); if( o instanceof javax.media.j3d.View){ Enumeration ee=((javax.media.j3d.View)o).getAllCanvas3Ds(); while(ee.hasMoreElements()){ Object oo=ee.nextElement(); if( oo instanceof View){ ((View)oo).stopRenderer(); } } } } } } public static void startRenderer(Viewable viewable){ ViewSpecificGroup vsg=viewableToViewSpecificGroup.get(viewable); if(vsg!=null){ Enumeration e=vsg.getAllViews(); while(e.hasMoreElements()){ Object o=e.nextElement(); if( o instanceof javax.media.j3d.View){ Enumeration ee=((javax.media.j3d.View)o).getAllCanvas3Ds(); while(ee.hasMoreElements()){ Object oo=ee.nextElement(); if( oo instanceof View){ ((View)oo).startRenderer(); } } } } } } /** * Runnable to be call in the postRender method * @param runnable * @todo think if it's good to set this "protected * @todo think about synchronization (set this method synchronized * or not as postRender is called by Java3D thread) */ protected void addPostRenderer(Runnable runnable) { postRenderers.add(runnable); } protected void removePostRenderer(Runnable runnable) { postRenderers.remove(runnable); } @Override final public void postRender() { for(int i=0; i<postRenderers.size(); i++) { Runnable r = postRenderers.get(i); r.run(); } } /** Let this view ignore all input (mouse and keyboard) events */ public void lock() { if(!locked) { locked = true; orbit.lock(); unlockedCursor = getCursor(); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); } } /** @see lock */ public void unlock() { if(locked) { locked = false; orbit.unlock(); setCursor(unlockedCursor); } } @Override protected void processKeyEvent(KeyEvent e) { if(!locked) super.processKeyEvent(e); } @Override protected void processMouseEvent(MouseEvent e) { if(!locked) super.processMouseEvent(e); } }