/* ThreeD.java A set of classes to parse, represent and display tensegrity models -- see comments at Model3D.load(InputStream) for description of data file format The colors for negative transformed z-values are lighter. History: 12 Jun 08 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. Do not report model errors by drawing into display area. In paintBuffer() clear the buffer before testing for empty model and possibly exiting. 10 Jun 08 Extract Model3D class and rename it CompactModel. 05 Jun 08 Delete "token" variable for use with StreamTokenizer. Not needed since value is stored in a publicly accessible field. 13 Jul 06 Change name of paint() to paintBuffer() and typ to type. Fix comments. Print a stack trace for all exceptions. 12 Jul 06 Move all transformation and rendering from Model3D to ThreeD. Remove repaint(0) from updateBuffer() and call it explicitly when necessary. Remove some useless synchronization. 12 Jun 06 Remove doUpdate arg from setMagnification() and needless java.awt.event.* import. 06 Jun 06 Split the color of the lines that straddle the z == 0 plane. 01 Jun 06 Delete incrementMagnification() and incrementShift(). Eliminate unused z component for shift. Add doUpdate arg to setMagnification(). Have resetShift() just call setShift(). Add resetMagnification(). Adjust rotation magnification by the model magnification (slow it down as model gets bigger). 31 May 06 Rename startRotation as startDrag. Add updateShift and resetShift. 30 May 06 Change name of findBB to findBoundingSphere. Use float constant instead of casting double constant to float. Change name of updateMagnification to incrementMagnification and let the client set maximimum and minimum values for magnification. Add facilities for setting a shift value. 29 May 06 Take advantage of new arot() in matrix3d to implement drag rotation. m_rotUpdate no longer needed. 26 May 06 Add support for drag rotation. 25 May 06 Use bounding sphere instead of bounding box. Rename m_bufferImage as m_graphicsBuffer. Stub rotation for now. 21 Sep 05 Replace deprecated API constructs. 20 Sep 05 Remove import directive for ThreeDClient. Apparently not necessary, and javac 1.4.2 doesn't like it. Use static initialization for Model3D.m_colors[]. 06 Sep 05 In the initial comment for this file, indicate where a description of the data file format can be found below. 22 Jul 05 Remove user messages regarding buffer update. Machines are fast enough now this probably slows things down more than it helps cope with a slow machine. Add a message to indicate that model data loaded successfully. 24 Mar 99 Add javadoc comments. Get rid of unused zmid() member functions. Make updateBuffer more bullet proof. 24 Feb 99 Embed ThreeDClient interface in ThreeD class. Qualify several functions with "synchronized". 06 Oct 98 Improve modularity: abstract ThreeDClient provides messaging capabilities; ThreeD.load() works from InputStream rather than URL. Modify to allow response to new Canvas size. 26 Aug 98 Make initial magnification and rotation explicitly setable. ThreeD.load() no longer initializes these parameters. Make interfaces to rotation and magnification comparable: set... sets parameter values without triggering redraw; update... increments parameter values and triggers redraw. Redid paint() and update() logic so file format exceptions displayed. Privatized Model3D items that ThreeD doesn't use. 04 Aug 98 Raise initial rotation about x-axis from 20 to 200 degrees. 08 Jul 98 Add double buffering. To do: Generate faded colors by averaging in background color rather than White. */ import java.awt.*; import java.io.*; /** Manage the way an instance of CompactModel is drawn on Canvas. Much of the rendering technique and geometry management came originally from the JDK 1.1.1 wireframe example. @author Bob Burkhardt @see CompactModel */ public class ThreeD extends Canvas { /** The wireframe model. */ CompactModel m_md; /** Array of indices into the m_md's line array so lines can be accessed by z-value of midpoints. */ private int m_lines[]; /** Where all graphics are rendered. This needs to be maintained at the same size as the Canvas and is drawn onto the Canvas any time the image needs updating. */ Image m_graphicsBuffer; /** Graphics context used for rendering the graphics. @see #m_graphicsBuffer */ Graphics m_gc; /** Width of the graphics buffer (needs to match that of Canvas). */ int m_width; /** Height of the graphics buffer (needs to match that of Canvas). */ int m_height; /** Scale value which multiplies model coordinates to get the pixel coordinates and z values used to render the wireframe on Canvas. */ float m_scale; /** Margin adjustment for the scale value so there's a little space around the model at magnification 1.0. */ final static float MARGIN_SCALE_ADJUST = 0.96f; /** Transformation matrix applied to the model coordinates to get device coordinates. */ matrix3d m_mat = new matrix3d(); /** Transformed coordinates for all model points (pixel locations + z value) @see matrix3d */ int m_tverts[]; /** z-value (doubled for convenience) of midpoint for each transformed line. */ int m_zmids[]; /** Additional magnification value applied on top of m_scale. Ranges from 1.0 to MAX_VIEW_MAGNIFICATION. @see #m_scale */ float m_mag; /** Adjustment so dragging moves the front of the figure by more than the length of the drag by the given adjustment factor. */ final static float ROTATION_MAGNIFICATION_FACTOR = 3.0f; /** Rotation matrix which determines the orientation of the model display. Used by updateBuffer as one component for m_mat, the model's transformation matrix. @see #updateBuffer @see #m_mat */ matrix3d m_rot = new matrix3d(); /** Matrix to store shift factor which is an offset applied to the location of the model after any rotation. @see #updateBuffer @see #m_mat */ matrix3d m_shift = new matrix3d(); /** X coordinate of current shift vector. Ranges from -m_md.m_radius to m_md.m_radius. Applied after rotation and before magnification. */ float 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. */ float 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 = 0; /** Colors used by paintBuffer to render the lines. Lines in the back half of the figure are rendered with faded versions of the colors used to render the lines in the front half of the figure. @see paintBuffer */ private static final Color m_colors[] = new Color[8]; /** Create an intance of this GUI component. */ public ThreeD() { m_md = new CompactModel(); resetTransform(); } static { m_colors[0] = new Color(255, 140, 0); m_colors[1] = new Color(0, 0, 0); m_colors[2] = new Color(255, 0, 0); m_colors[3] = new Color(0, 128, 0); m_colors[4] = new Color(255, 192, 128); m_colors[5] = new Color(128, 128, 128); m_colors[6] = new Color(255, 128, 128); m_colors[7] = new Color(96, 192, 96); } /** Initialize the model geometry from data contained in a stream. Set scaling. @param is stream of data compatible with Model3D format @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 { resetTransform(); m_md.load(is); float f1 = getWidth()/(2.0f*m_md.m_radius); float f2 = getHeight()/(2.0f*m_md.m_radius); m_scale = MARGIN_SCALE_ADJUST*((f1 < f2) ? f1 : f2); updateBuffer(); repaint(0); } /** 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; } /** 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; float deltax = 2.0f*(x - m_x)/m_dim; float deltay = 2.0f*(y - m_y)/m_dim; m_x = x; m_y = y; // angle of rotation (radians) double angle = (float)Math.sqrt(deltax*deltax + deltay*deltay); // axis of rotation (normalized to length == 1; z == 0) float axis_x = (float)(-deltay/angle); float axis_y = (float)(deltax/angle); angle *= (180.0/Math.PI)*ROTATION_MAGNIFICATION_FACTOR/m_mag; // update rotation matrix m_rot.arot(angle, axis_x, axis_y, 0.0f); updateBuffer(); repaint(0); } /** Reset transform to initial state. */ private void resetTransform() { // rotation m_rot.unit(); m_rot.zrot(180.0); // so y-axis points up instead of down // shift setShift(0.0f, 0.0f); // magnification m_mag = 1.0f; } /** Reset transform and model to initial state. */ public void reset() { resetTransform(); m_md.reset(); updateBuffer(); repaint(0); } /** Set the magnification value. @param mag magnification value (1.0 is no magnification; negative values will result in reflection) */ public void setMagnification(float value) { m_mag = value; updateBuffer(); repaint(0); } /** 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(float x, float 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_shift.unit(); m_shift.translate(x, y, 0.0f); } /** 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; float shiftx = (x - m_x)/(m_scale*m_mag) + m_shiftX; float shifty = (y - m_y)/(m_scale*m_mag) + m_shiftY; m_x = x; m_y = y; setShift(shiftx, shifty); updateBuffer(); repaint(0); } /** Redraw m_graphicsBuffer according to current model geometry and transformations. This function basically sets things up. Most of the actual rendering is done by paintBuffer(). @see #m_graphicsBuffer @see #paintBuffer */ public void updateBuffer() { if (m_md != null) { m_mat.unit(); // center about the origin m_mat.translate(-m_md.m_xmean, -m_md.m_ymean, -m_md.m_zmean); // rotate about the origin m_mat.mult(m_rot); // apply shift m_mat.mult(m_shift); // do reality check on graphics buffer dimensions if (m_graphicsBuffer == null || m_width != getWidth() || m_height != getHeight()) { m_width = getWidth(); m_height = getHeight(); m_dim = m_width < m_height ? m_width : m_height; m_graphicsBuffer = createImage(m_width, m_height); if (m_graphicsBuffer != null) m_gc = m_graphicsBuffer.getGraphics(); m_scale = MARGIN_SCALE_ADJUST*m_dim/(2.0f*m_md.m_radius); } if (m_graphicsBuffer != null && m_gc == null) m_gc = m_graphicsBuffer.getGraphics(); // scale graphics according to magnification and viewport m_mat.scale(m_scale*m_mag, m_scale*m_mag, m_scale*m_mag); // move graphics to center of viewport m_mat.translate(m_width/2, m_height/2, 0); // clear buffer and draw graphics in it if (m_graphicsBuffer != null && m_gc != null) paintBuffer(); } } /** Heap sort m_zmids array into increasing order. Adapted from S. Dvorak and A. Musset, BASIC in Action, p. 170. Transforms to m_zmids array are mirrored in m_lines array. */ private synchronized void sort() { int i; int shift_index, zmid_value, line_value; // Organize array into descending heap (largest value at root) for (i = (m_md.nlines() - 1)/2; i >= 0; i--) shift_up(m_zmids[i], m_lines[i], i, m_md.nlines()); /* Create array sorted in increasing order (starting from end). As for loop progresses, array gets filled from the end, and the remainder of the array is maintained as a descending heap which steadily becomes smaller. v[0] always contains the largest value of the remaining heap. */ for (i = m_md.nlines() - 1; i > 0; i--) { /* grab value at and index of current array position being filled (it will be inserted into remaining heap) */ zmid_value = m_zmids[i]; line_value = m_lines[i]; // put largest value of remaining heap in final position m_zmids[i] = m_zmids[0]; m_lines[i] = m_lines[0]; // shift value up into remaining descending heap shift_up(zmid_value, line_value, 0, i); } } /** Shift zmid_value up into heap until it's not less than successors. Transforms to m_zmids array are mirrored in m_lines array. @see #sort */ private void shift_up (int zmid_value, int line_value, int start_index, int max_index) { int j; // initially index of "left" successor of v[start_index] while ((j = start_index + start_index + 1) < max_index) { // grab "right" successor of m_zmids[start_index] if it has larger value if (j + 1 < max_index && m_zmids[j] < m_zmids[j + 1]) j++; if (zmid_value < m_zmids[j]) // shift value less than a successor { // put this successor in shift_value's old position m_zmids[start_index] = m_zmids[j]; m_lines[start_index] = m_lines[j]; // consider shift_value to now occupy successor's position start_index = j; } else break; /* shift_value not less than successors */ } // put value in place m_zmids[start_index] = zmid_value; m_lines[start_index] = line_value; } /** Transform all the points in this model using m_mat. @see #m_mat @see matrix3d */ private void transform() { if (m_md.nverts() <= 0) return; if (m_tverts == null || m_tverts.length < m_md.nverts() * 3) m_tverts = new int[m_md.nverts() * 3]; m_mat.transform(m_md.verts(), m_tverts, m_md.nverts()); } /** Render m_md in m_graphicsBuffer. m_mat is used to map from model space to screen space. The colors for lines with negative transformed midpoint z-values are lighter than those with positive z-values. */ synchronized void paintBuffer() { m_gc.setColor(Color.lightGray); m_gc.fillRect(0, 0, m_width, m_height); if (m_md.nlines() <= 0 || m_md.nverts() <= 0) return; transform(); if (m_lines == null || m_lines.length < m_md.nlines()) { m_lines = new int[m_md.nlines()]; m_zmids = new int[m_md.nlines()]; } for (int i = 0; i < m_md.nlines(); i++) { m_zmids[i] = m_tverts[m_md.p1(i) + 2] + m_tverts[m_md.p2(i) + 2]; m_lines[i] = i; } sort(); for (int i = 0; i < m_md.nlines(); i++) { int p1 = m_md.p1(m_lines[i]); int p2 = m_md.p2(m_lines[i]); int type = m_md.type(m_lines[i]); // untransformed z values for endpoints int z1 = m_tverts[p1 + 2]; int z2 = m_tverts[p2 + 2]; if (z1 >= 0 && z2 >= 0) { m_gc.setColor(m_colors[type]); m_gc.drawLine(m_tverts[p1], m_tverts[p1 + 1], m_tverts[p2], m_tverts[p2 + 1]); } else if (z1 < 0 && z2 < 0) { m_gc.setColor(m_colors[type + 4]); m_gc.drawLine(m_tverts[p1], m_tverts[p1 + 1], m_tverts[p2], m_tverts[p2 + 1]); } else { // line straddles z == 0 plane; draw it in two colors int x1 = m_tverts[p1]; int x2 = m_tverts[p2]; int y1 = m_tverts[p1 + 1]; int y2 = m_tverts[p2 + 1]; if (z2 < z1) { // make it so the first point is behind the z == 0 plane int itemp; itemp = x1; x1 = x2; x2 = itemp; itemp = y1; y1 = y2; y2 = itemp; itemp = z1; z1 = z2; z2 = itemp; } int zdiff = z2 - z1; int xsplit = z2*(x1 - x2)/zdiff; int ysplit = z2*(y1 - y2)/zdiff; m_gc.setColor(m_colors[type + 4]); m_gc.drawLine(x2 + xsplit, y2 + ysplit, x1, y1); m_gc.setColor(m_colors[type]); m_gc.drawLine(x2, y2, x2 + xsplit, y2 + ysplit); } } } public void update(Graphics g) { if (m_md != null) { if (m_graphicsBuffer == null || m_width != getWidth() || m_height != getHeight()) updateBuffer(); g.drawImage(m_graphicsBuffer, 0, 0, this); } } public void paint(Graphics g) { update(g); } }