/* JThreeD.java A set of classes to parse, represent and display tensegrity models using Java3D -- see comments at Model3D.load(InputStream) for data file format description History: 12 Jun 2008 Do not handle exceptions in load(), pass them on. Remove ThreeDClient: messages probably are not useful anymore and should be done with subscribe pattern anyway. Do not close the InputStream in load(). Merge all reset functions into resetTransform(). Add reset() which puts things back in initial state and redraws. 10 Jun 2008 Use new Java3DModel class instead of identically behaved JModel3D class which is no longer with us. 09 Aug 2006 Take advantage of new member functions LineSegment.p1() and p2(). 14 Jun 2006 Move scene graph to JThreeD and extract JModel3D from this file to a separate file. Fix comment for m_magX. 12 Jun 2006 In To do, indicate reason screen resizing needs fixing. Remove needless java.net.* and java.awt.event.* imports. 01 Jun 2006 Add resetMagnification(). Don't clip magnification values. Change addPoint3d() to addPoint() etc. Adjust rotation magnification by the model magnification (slow it down as model gets bigger). 08 Dec 2005 Allow struts to have three different colors. Hard code equality of m_colors.length and m_appearances.length. 14 Oct 2005 Simplify addMember() and addPoint3f() and make their code symmetric. 04 Oct 2005 Remove m_bounds. 30 Sep 2005 Displays models from files. 21 Sep 2005 Magnification working. 20 Sep 2005 Created from ThreeD.java. To do: Fix screen resizing -- crashes JVM with EXCEPTION_ACCESS_VIOLATION when app Frame is resized. Only happens in Windows -- Linux works OK. */ import java.awt.*; import java.io.*; import javax.media.j3d.*; import javax.vecmath.*; import com.sun.j3d.utils.universe.SimpleUniverse; /** Manage the way an instance of Java3DModel is drawn on Canvas. Much of the rendering technique and geometry management is adapted from the Java3D 1.3.1 FourByFour example. @author Bob Burkhardt @see Java3DModel */ public class JThreeD extends Canvas3D { /** The model geometry data. */ Java3DModel m_md; /** Java3D Universe for the model. */ SimpleUniverse m_universe; /** Current Java3D scene graph. */ BranchGroup m_root; /** AWT Colors for scene graph. */ private final static Color3f m_colors[] = new Color3f[10]; /** Java3D Appearances for scene graph. */ private static Appearance m_appearances[]; /** AWT color for background of Java3D scene graph. */ private final static Color3f BACKGROUND_COLOR = new Color3f(0.6f, 0.6f, 0.6f); /** AWT color for Java3D ambient light for scene graph. */ private final static Color3f AMBIENT_LIGHT_COLOR = new Color3f(0.6f, 0.6f, 0.6f); /** AWT color for Java3D directional light for scene graph. */ private final static Color3f DIRECTIONAL_LIGHT_COLOR = new Color3f(0.4f, 0.4f, 0.4f); /** Java3D directional light direction for scene graph. */ private final static Vector3f DIRECTIONAL_LIGHT_DIRECTION = new Vector3f(0.3f, 0.2f, -1.0f); /** AWT color for Java3D emmissive color for scene graph. */ private final static Color3f EMISSIVE_COLOR = new Color3f(Color.black); /** AWT color for Java3D specular color for scene graph. */ private final static Color3f SPECULAR_COLOR = new Color3f(Color.white); /** Margin adjustment for the base scale value so there's a little space around the model at magnification 1.0. */ final static double MARGIN_SCALE_ADJUST = 0.85; /** Maximum magnification value (specified by client). */ final double m_maxMagnification; /** Adjustment so dragging moves the front of the figure by more than the length of the drag by the given adjustment factor. */ final static double ROTATION_MAGNIFICATION_FACTOR = 3.0; /** The current magnification. */ double m_mag = 1.0; /** One of the three dynamic transformations. The magnification transformation. Applied after rotation and shift. */ TransformGroup m_magX; /** One of the three dynamic transformations. Rotation matrix which determines the orientation of the model display. Applied before shift and magnification. */ TransformGroup m_rot; /** One of the three dynamic transformations. An offset applied to the location of the model after any rotation and before magnification. */ TransformGroup m_shift; /** X coordinate of current shift vector. Ranges from -m_md.m_radius to m_md.m_radius. Applied after rotation and before magnification. */ double m_shiftX; /** Y coordinate of current shift vector. Ranges from -m_md.m_radius to m_md.m_radius. Applied after rotation and before magnification. */ double m_shiftY; /** Coordinates of last observed mouse position (only updated during drags). */ int m_x = 0, m_y = 0; /** Dimension of largest square fitting on the Canvas. */ int m_dim; /** Helpers for setting transformation values. Used by JThreeD and/or Java3DModel. Take a load off the garbage collector. */ Transform3D m_transform = new Transform3D(); Transform3D m_transform2 = new Transform3D(); Vector3d m_vec = new Vector3d(); Matrix3d m_mat = new Matrix3d(); /** Create an intance of this GUI component. @param maxMagnification How much magnification to allow (>= 1.0) @param width width of component @param height height of component */ public JThreeD(double maxMagnification, int width, int height) { super(SimpleUniverse.getPreferredConfiguration()); m_md = new Java3DModel(); m_dim = width < height ? width : height; m_maxMagnification = maxMagnification; m_universe = new SimpleUniverse(this); m_universe.getViewer().getView().setProjectionPolicy (View.PARALLEL_PROJECTION); m_universe.getViewer().getView().setFrontClipDistance(-10.0); m_universe.getViewingPlatform().setNominalViewingTransform(); if (m_appearances == null) { m_appearances = new Appearance[m_colors.length]; Material material; for (int i = 0; i < m_colors.length; i++) { material = new Material(m_colors[i], EMISSIVE_COLOR, m_colors[i], SPECULAR_COLOR, 100.f); material.setLightingEnable(true); m_appearances[i] = new Appearance(); m_appearances[i].setMaterial(material); } } } static { // member colors (ambient and diffuse) m_colors[0] = new Color3f(Color.black); m_colors[1] = new Color3f(Color.green); // strut m_colors[2] = new Color3f(Color.red); m_colors[3] = new Color3f(Color.blue); m_colors[4] = new Color3f(Color.red); m_colors[5] = new Color3f(Color.blue); m_colors[6] = new Color3f(Color.black); m_colors[7] = new Color3f(Color.white); m_colors[8] = new Color3f(Color.orange); // strut m_colors[9] = new Color3f(Color.yellow); // strut } /** Initialize the model geometry from data contained in a stream. Set scaling. @exception IOException Input problem with the stream of model data. @exception Model3D.FileFormatException Format problem with the stream. @exception Model3D.ModelException Problem storing model in Model3D. */ public void load(InputStream is) throws IOException, Model3D.FileFormatException, Model3D.ModelException { m_md.load(is); createSceneGraph(); resetTransform(); } /** Set initial mouse coordinates for start of mouse-drag rotation or shift. @param x x coordinate of mouse @param y y coordinate of mouse */ public void startDrag(int x, int y) { m_x = x; m_y = y; } /** Reset transform to initial state. */ private void resetTransform() { // magnification setMagnification(1.0); // rotation m_transform.setIdentity(); m_rot.setTransform(m_transform); // shift setShift(0.0f, 0.0f); } /** Reset transform and model to initial state. */ public void reset() { resetTransform(); m_md.reset(); createSceneGraph(); } /** Increment the rotation depending on new mouse coordinates. @param x x coordinate of mouse @param y y coordinate of mouse */ public void updateRotation(int x, int y) { if (x == m_x && y == m_y) return; double deltax = 2.0*(x - m_x)/m_dim; double deltay = -2.0*(y - m_y)/m_dim; m_x = x; m_y = y; // angle of rotation (radians) double angle = Math.sqrt(deltax*deltax + deltay*deltay); // axis of rotation (normalized to length == 1; z == 0) double axis_x = -deltay/angle; double axis_y = deltax/angle; angle *= ROTATION_MAGNIFICATION_FACTOR/m_mag; // precompute sin, cos, products double sinangle = Math.sin(angle); double cosangle = Math.cos(angle); double x2 = axis_x*axis_x; double y2 = axis_y*axis_y; double xy = axis_x*axis_y; // update rotation matrix (Rogers67) m_mat.setElement(0, 0, x2 + (1.0 - x2)*cosangle); m_mat.setElement(0, 1, xy*(1.0 - cosangle)); m_mat.setElement(0, 2, axis_y*sinangle); m_mat.setElement(1, 0, xy*(1.0 - cosangle)); m_mat.setElement(1, 1, y2 + (1.0 - y2)*cosangle); m_mat.setElement(1, 2, -axis_x*sinangle); m_mat.setElement(2, 0, -axis_y*sinangle); m_mat.setElement(2, 1, axis_x*sinangle); m_mat.setElement(2, 2, cosangle); m_transform.set(m_mat); m_rot.getTransform(m_transform2); m_transform.mul(m_transform2); m_rot.setTransform(m_transform); } /** Set the magnification value. @param value magnification value */ public void setMagnification(double value) { if (m_magX != null) { m_transform.setIdentity(); m_transform.setScale(value); m_magX.setTransform(m_transform); m_mag = value; } } /** Set the shift vector. Applied after rotation, but before magnification. @param x x component of shift vector @param y y component of shift vector (each clipped to between -m_md.m_radius and m_md.m_radius) */ public void setShift(double x, double y) { if (x < -m_md.m_radius) x = -m_md.m_radius; else if (x > m_md.m_radius) x = m_md.m_radius; if (y < -m_md.m_radius) y = -m_md.m_radius; else if (y > m_md.m_radius) y = m_md.m_radius; m_shiftX = x; m_shiftY = y; m_transform.setIdentity(); m_vec.set(m_shiftX, m_shiftY, 0.0); m_transform.setTranslation(m_vec); m_shift.setTransform(m_transform); } /** Update the shift depending on new mouse coordinates and the current magnification. @param x x coordinate of mouse @param y y coordinate of mouse */ public void updateShift(int x, int y) { if (x == m_x && y == m_y) return; double shiftx = 2.0*(x - m_x)/(m_dim*m_mag) + m_shiftX; double shifty = -2.0*(y - m_y)/(m_dim*m_mag) + m_shiftY; m_x = x; m_y = y; setShift(shiftx, shifty); } /** Create a scene graph for the current model. */ void createSceneGraph() { BranchGroup root = new BranchGroup(); root.setCapability(BranchGroup.ALLOW_DETACH); BoundingSphere bounds = new BoundingSphere(new Point3d(), 2.0*m_maxMagnification); // background color Background background = new Background(BACKGROUND_COLOR); background.setApplicationBounds(bounds); root.addChild(background); // ambient light AmbientLight ambientLight = new AmbientLight(AMBIENT_LIGHT_COLOR); ambientLight.setInfluencingBounds(bounds); root.addChild(ambientLight); // directional light DirectionalLight directionalLight = new DirectionalLight(DIRECTIONAL_LIGHT_COLOR, DIRECTIONAL_LIGHT_DIRECTION); directionalLight.setInfluencingBounds(bounds); root.addChild(directionalLight); // transforms applying to the whole structure (organized like // a linked list) // magnification transform m_magX = new TransformGroup(); m_magX.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); root.addChild(m_magX); // shift transform m_shift = new TransformGroup(); m_shift.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); m_magX.addChild(m_shift); // rotation transform m_rot = new TransformGroup(); m_rot.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); m_rot.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); m_shift.addChild(m_rot); // scale model to fit in picture TransformGroup scale = new TransformGroup(); scale.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); m_transform.setIdentity(); m_transform.setScale(JThreeD.MARGIN_SCALE_ADJUST/m_md.m_radius); scale.setTransform(m_transform); m_rot.addChild(scale); // translate the model so it's centered on the origin TransformGroup translate = new TransformGroup(); translate.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); m_transform.setIdentity(); m_vec.sub(m_md.m_origin, m_md.m_center); m_transform.setTranslation(m_vec); translate.setTransform(m_transform); scale.addChild(translate); // and finally, the structure itself // find average strut length and use to set strut and tendon radii float avgsl = 0; int nstruts = 0; for (int i = 0; i < m_md.m_nlineSegments; i++) { Java3DModel.LineSegment lineSegment = m_md.m_lineSegments[i]; if (lineSegment.m_type%8 == 1) { // a strut m_vec.sub(lineSegment.p2(), lineSegment.p1()); avgsl += m_vec.length(); nstruts++; } } if (nstruts > 0) avgsl /= nstruts; else avgsl = 3.0f; float sradius = avgsl/60; float tradius = sradius/5; // create members for (int i = 0; i < m_md.m_nlineSegments; i++) { Java3DModel.LineSegment lineSegment = m_md.m_lineSegments[i]; Point3d p1 = lineSegment.p1(); m_vec.sub(lineSegment.p2(), p1); double mradius = (lineSegment.m_type%8 == 1) ? sradius : tradius; double mlength = m_vec.length(); Shape3D shape; if (lineSegment.m_type%8 == 1) { // a strut // map strut type to appearance index int strut_type = (lineSegment.m_type - 1)/8 + 1; if (strut_type > 3) strut_type = 3; if (strut_type > 1) strut_type += 6; // create strut Strut strut = new Strut(mradius, mlength, 12, 0.6f, m_appearances[strut_type]); shape = strut.getShape(); } else { // a tendon Cylinder cyl = new Cylinder(mradius, mlength, 12, m_appearances[lineSegment.m_type%8]); shape = cyl.getShape(); } /* move member from default position and orientation (starts at origin and stretches along the x-axis) to structure position and orientation */ // create a transformation TransformGroup member_xform = new TransformGroup(); member_xform.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); // orient the member double phi = -Math.asin(m_vec.z/mlength); double theta = Math.atan2(m_vec.y, m_vec.x); m_transform2.rotY(phi); // this is applied first m_transform.rotZ(theta); // then this m_transform.mul(m_transform2); // move the member into position m_vec.set(p1); m_transform.setTranslation(m_vec); // install the transformation member_xform.setTransform(m_transform); member_xform.addChild(shape); // add the member to the model translate.addChild(member_xform); } if (m_root != null) m_root.detach(); m_root = root; m_universe.addBranchGraph(m_root); } public void destroy() { m_universe.cleanup(); } }