/* JitterbugsController.java History: 28 Feb 07 Use nested Stepper class instead of Toggle and use interrupt feature to stop it. Synchronize more of the code to avoid choppiness at max speed. 08 Aug 06 JitterbugsGeometry.Line and JitterbugsGeometry.Triangle are not static nested classes, so deviceCoords arg to the member functions is needless. In Line.draw(), add a few more comments and don't bother with intermediate computation of coordinate values since they are only used in one place. 05 Aug 06 For clarity, rename toggle() as step() and write a new toggle() function which just calls step(). Remove MIN_STAGE since DEFAULT_STAGE will serve as well. 03 Aug 06 Use JComponent instead of Canvas as display surface, BufferedImage instead of Image for the graphics buffer, and Graphics2D instead of Graphics for rendering into the buffer. Add INITIAL_TIME_DELAY label. Fix comments. 29 May 06 Substitute drag rotation for clunky scrollbar-driven rotation. Rename m_bufferImage as m_graphicsBuffer. Add some comments. 31 Oct 05 Remove semi-useless magnify facility. Add extra lines for STAGE_A and STAGE_E. 27 Oct 05 Remove wait() after requestStop() call in suspend(). 17 Oct 05 Encapsulate more stage information in JitterbugController rather than have JitterbugViewer deal with it. Hard code radius and don't bother with center (it's the origin). Remove M_ prefix from all constants. Rename FUDGE_FACTOR to more specific REDUCTION_FACTOR. Construct figures with triangles rather than lines. Extensive reworking of code to simplify it and separate it more logically between JitterbugsController and JitterbugsGeometry. JitterbugsPicture renamed to JitterbugsController. 13 Oct 05 Replace needlessly complex masks and store line data in struct instead of a single integer. Replace deprecated size().width etc. calls. 24 Mar 99 Added javadoc comments. Added constants for various angle values. Get rid of m_nverts - use m_verts.length/3 instead. Get rid of unused zmid() member functions. 11 Mar 99 Created from JitterbugPicture.java. */ import java.awt.*; import java.awt.image.*; import java.lang.Math; import java.lang.Thread; /** Manage the way the JitterbugGeometry is drawn on Canvas. Much of the rendering technique and geometry handling was originally adapted from the JDK 1.1.1 wireframe example. @author Bob Burkhardt @see JitterbugsGeometry */ public class JitterbugsController extends javax.swing.JComponent { /** Utility to periodically "step" the model during animations. */ class Stepper extends Thread { /** Amount of time to sleep between steps. */ long m_ms_delay; /** @param ms_delay milliseconds to sleep between invocation of client's toggle member function */ Stepper(long ms_delay) { m_ms_delay = ms_delay; start(); } void setInterval(long ms_delay) { m_ms_delay = ms_delay; } long getInterval() { return m_ms_delay; } public void run() { for (;;) { try { sleep(m_ms_delay); } catch(InterruptedException e) { interrupt(); } if (isInterrupted()) { return; } else step(); } } } /** A reduction factor to make sure all of both jitterbugs is visible. */ public static final float REDUCTION_FACTOR = 0.95f; /** Minimum value (in degrees) for angle of rotation of jitterbugs' triangles. */ public static final int MIN_ANGLE = 0; /** Maximum value (in degrees) for angle of rotation of jitterbugs' triangles. */ public static final int MAX_ANGLE = 60; /** Value (in degrees) for angle of rotation of jitterbugs' triangles when it is in the octahedron state. */ public static final int OCTA_ANGLE = 0; /** Value (in degrees) for angle of rotation of jitterbugs' triangles when it is in the t-icosahedron state. */ public static final int TICOSA_ANGLE = 30; /** Value (in degrees) for angle of rotation of jitterbugs' triangles when it is in the icosahedron state. This value is an integral approximation within about a fifth of a degree of the floating-point representation. */ public static final int ICOSA_ANGLE = 38; /** Value (in degrees) for angle of rotation of jitterbugs' triangles when it is in the vector equilibrium state. */ public static final int VE_ANGLE = 60; /** Greatest common multiple of TICOSA_ANGLE, ICOSA_ANGLE and VE_ANGLE. This factor is used to increment the angle as the model cycles. */ public static final int ANGLE_GCM = 2; /** Value to indicate when jitterbug is in default stage. The stage value is used to indicate when the jitterbug is in specific geometrical configurations. The default value is used when it's in none of these configurations. This is the smallest stage index. */ public static final int DEFAULT_STAGE = 1; /** Value to indicate when jitterbug is in A stage. @see #DEFAULT_STAGE */ public static final int STAGE_A = 2; /** Value to indicate when jitterbug is in first tensegrity icosa stage. @see #DEFAULT_STAGE */ public static final int STAGE_B = 3; /** Value to indicate when jitterbug is in first icosahedral stage. @see #DEFAULT_STAGE */ public static final int STAGE_C = 4; /** Value to indicate when jitterbug is in the vector equilibrium stage. @see #DEFAULT_STAGE */ public static final int STAGE_D = 5; /** Value to indicate when jitterbug is in second icosahedral stage. @see #DEFAULT_STAGE */ public static final int STAGE_E = 6; /** Greatest value for a named stage. @see #DEFAULT_STAGE */ public static final int MAX_STAGE = STAGE_E; /** Initial time (in ms) between animated steps. */ static final int INITIAL_TIME_DELAY = 100; /** The current rotational angle of the jitterbugs' triangles in degrees. */ int m_angle; /** The amount the jitterbugs' triangles are rotated in each animation step. The value is negated whenever the jitterbugs reaches either end of its range of permissible angle values. */ int m_increment; /** Indicates whether the jitterbugs is currently being animated. */ boolean m_is_suspended; /** The jitterbugs model. */ JitterbugsGeometry m_md; /** Where all graphics are rendered. This is the same size as the Canvas and is drawn onto the Canvas any time the image needs updating. */ BufferedImage m_graphicsBuffer; /** Graphics context used for rendering the graphics. @see #m_graphicsBuffer */ Graphics2D m_bufferGC; /** Last recorded width of the Canvas. */ int m_width; /** Last recorded height of the Canvas. */ int m_height; /** Scale value which multiplies model coordinates to get the device coordinates used to render the jitterbugs on Canvas. */ float m_scale; /** Transformation matrix which is passed to JitterbugsGeometry to compute device coordinates from the model coordinates. @see #updateBuffer */ matrix3d m_deviceXform = new matrix3d(); /** 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 jitterbugs. Used by updateBuffer as one component for computing m_deviceXform. @see #updateBuffer @see #m_deviceXform */ matrix3d m_rot = new matrix3d(); /** 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; /** Animates the jitterbug using the step() function. It is created anew when the animation starts, and interrupted when step mode is entered. */ Stepper m_stepper; public JitterbugsController() { m_increment = ANGLE_GCM; m_angle = ICOSA_ANGLE; m_is_suspended = false; m_md = new JitterbugsGeometry(this); float f1 = REDUCTION_FACTOR*0.25f*m_width/JitterbugsGeometry.RADIUS; float f2 = REDUCTION_FACTOR*0.25f*m_height/JitterbugsGeometry.RADIUS; m_scale = (f1 < f2) ? f1 : f2 ; resetRotation(); m_stepper = new Stepper(INITIAL_TIME_DELAY); } /** Implements ToggleClient interface. This is what should happen every time a toggle action happens. For this application, nothing is really toggling, and it is just serving a timer function. @see ToggleClient */ public void toggle() { step(); } /** Increments the angle of the jitterbugs' triangles and redraws it with the new coordinates. */ public synchronized void step() { m_angle += m_increment; if (m_angle <= MIN_ANGLE || m_angle >= MAX_ANGLE) m_increment = -m_increment; m_md.setModelCoords(); if (m_graphicsBuffer != null) updateBuffer(); } /** Stops the jitterbugs animation. */ public void suspend() { if (m_stepper != null) m_stepper.interrupt(); m_is_suspended = true; m_stepper = null; } /** Resumes the jitterbugs animation. */ public void resume() { if (m_stepper == null) m_stepper = new Stepper(INITIAL_TIME_DELAY); m_is_suspended = false; } /** Accelerate the animation by halving the sleep time. */ public void accelerate() { if (!m_is_suspended && m_stepper != null) m_stepper.setInterval(m_stepper.getInterval()/2); } /** Set the rotation angle (in degrees) for the jitterbugs' triangles. */ public void setAngle(int angle) { if (angle < MIN_ANGLE) angle = MIN_ANGLE; else if (angle > MAX_ANGLE) angle = MAX_ANGLE; m_angle = angle; if ((m_angle == MIN_ANGLE && m_increment < 0) || (m_angle == MAX_ANGLE && m_increment > 0)) m_increment = -m_increment; m_md.setModelCoords(); updateBuffer(); } /** Set the angle to correspond to a particular stage (stage value indicates when the jitterbug is in specific geometrical configurations). */ public void setStage(int stage) { switch (stage) { case STAGE_A: setAngle(OCTA_ANGLE); break; case STAGE_B: setAngle(MAX_ANGLE - ICOSA_ANGLE); break; case STAGE_C: setAngle(TICOSA_ANGLE); break; case STAGE_D: setAngle(ICOSA_ANGLE); break; case STAGE_E: setAngle(VE_ANGLE); break; default: break; } } /** Get the current rotation angle (in degrees) for the jitterbugs' triangles. */ public int getAngle() { return m_angle; } /** Get the current stage (stage value indicates when the jitterbug is in specific geometrical configurations). */ public int getStage() { switch (m_angle) { case OCTA_ANGLE: return STAGE_A; case MAX_ANGLE - ICOSA_ANGLE: return STAGE_B; case TICOSA_ANGLE: return STAGE_C; case ICOSA_ANGLE: return STAGE_D; case VE_ANGLE: return STAGE_E; default: return DEFAULT_STAGE; } } /** Get stage label (stage value indicates when the jitterbug is in specific geometrical configurations). */ public static String getStageLabel(int stage) { switch (stage) { case STAGE_A: return "StageA"; case STAGE_B: return "StageB"; case STAGE_C: return "StageC"; case STAGE_D: return "StageD"; case STAGE_E: return "StageE"; default: return "Default"; } } /** Indicates when the jitterbugs' animation is stopped. */ public boolean isSuspended() { return m_is_suspended; } /** Set initial mouse coordinates for start of mouse-drag rotation. @param x x coordinate of mouse @param y y coordinate of mouse */ public void startRotation(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; // angle of rotation (radians) double angle = 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; // update rotation matrix synchronized (this) { m_rot.arot(angle, axis_x, axis_y, 0.0f); } m_x = x; m_y = y; if (m_is_suspended) updateBuffer(); } public void resetRotation() { m_rot.unit(); m_rot.arot(15.0, 1.0f, 1.0f, 0.0f); } /** Redraw m_graphicsBuffer according to current jitterbugs geometry and transformations. This function basically sets things up. Most of the actual rendering is done by JitterbugsGeometry.paint(). @see #m_graphicsBuffer @see JitterbugsGeometry#paint */ public synchronized void updateBuffer() { if (m_md != null) { m_deviceXform.unit(); // rotate about the origin m_deviceXform.mult(m_rot); // 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 = (BufferedImage)createImage(m_width, m_height); if (m_graphicsBuffer != null) m_bufferGC = m_graphicsBuffer.createGraphics(); m_scale = REDUCTION_FACTOR*m_dim/(4.0f*JitterbugsGeometry.RADIUS); } if (m_graphicsBuffer != null && m_bufferGC == null) m_bufferGC = m_graphicsBuffer.createGraphics(); // scale graphics according to viewport m_deviceXform.scale(m_scale, m_scale, m_scale); // move graphics to center of viewport m_deviceXform.translate(m_width/2, m_height/2, 0); // clear buffer and draw graphics in it if (m_graphicsBuffer != null && m_bufferGC != null) { m_bufferGC.setColor(Color.lightGray); m_bufferGC.fillRect(0, 0, m_width, m_height); m_md.paint(m_bufferGC, m_deviceXform); repaint(0); } } } public synchronized 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 synchronized void paint(Graphics g) { update(g); } } /** The representation of the jitterbugs. Used by JitterbugsController. @see JitterbugsController */ class JitterbugsGeometry { public final static int NPOINTS = 23; public final static int NDRAWABLES = 63; public final static float RADIUS = 0.5f*(float)Math.sqrt(8.0/3.0); /** Model coordinates for all points. */ private float m_modelCoords[] = new float[3*NPOINTS]; /** Device coordinates for all points. */ private int m_deviceCoords[] = new int[3*NPOINTS]; /** A graphic component of a Jitterbugs. */ private interface Drawable { /** What is Z rank? */ public int zmid(); /** Draw this object. */ public void draw(Graphics2D g, JitterbugsController controller); } /** All the objects which can be drawn. */ private Drawable m_drawables[] = new Drawable[NDRAWABLES]; /** The thing that's pulling all the strings. */ private JitterbugsController m_controller; /** Colors used to render the drawables. @see paint */ private final static Color m_colors[] = new Color[8]; /** For each Triangle there are three integers indicating the indices of the vertices (0 based index). There are two colors, one of which is used to render depending on the sign of the z value of the Triangle's normal. */ private class Triangle implements Drawable { public final int m_p1, m_p2, m_p3; public final Color m_color1, m_color2; Triangle(int p1, int p2, int p3, Color color1, Color color2) { m_p1 = p1; m_p2 = p2; m_p3 = p3; m_color1 = color1; m_color2 = color2; } public int zmid() { return (m_deviceCoords[3*m_p1 + 2] + m_deviceCoords[3*m_p2 + 2] + m_deviceCoords[3*m_p3 + 2])/3; } public void draw(Graphics2D g, JitterbugsController controller) { int xs[] = new int[3]; int ys[] = new int[3]; xs[0] = m_deviceCoords[3*m_p1]; ys[0] = m_deviceCoords[3*m_p1 + 1]; xs[1] = m_deviceCoords[3*m_p2]; ys[1] = m_deviceCoords[3*m_p2 + 1]; xs[2] = m_deviceCoords[3*m_p3]; ys[2] = m_deviceCoords[3*m_p3 + 1]; int dx1 = xs[1] - xs[0]; int dy1 = ys[1] - ys[0]; int dx2 = xs[2] - xs[1]; int dy2 = ys[2] - ys[1]; int znorm = dx1*dy2 - dy1*dx2; Color color = (znorm > 0) ? m_color1 : m_color2; g.setColor(color); g.fillPolygon(xs, ys, 3); g.setColor(Color.black); g.drawPolygon(xs, ys, 3); } } /** For each line there are two integers indicating the indices of the vertices (0 based index). A type value indicates at what stage it is to be drawn. */ private class Line implements Drawable { public final int m_p1, m_p2, m_type; Line(int p1, int p2, int typ) { m_p1 = p1; m_p2 = p2; m_type = typ; } public int zmid() { return (m_deviceCoords[3*m_p1 + 2] + m_deviceCoords[3*m_p2 + 2])/2; } public void draw(Graphics2D g, JitterbugsController controller) { // Lines only get drawn for static views of the Jitterbug if (!controller.isSuspended()) return; // if this type of Line *shouldn't* be drawn at this stage, then // return -- for each type of Line, there is only one // stage when it should be drawn int stage = controller.getStage(); switch (m_type) { case 0: if (stage != JitterbugsController.STAGE_C) return; break; case 1: if (stage != JitterbugsController.STAGE_D) return; break; case 2: if (stage != JitterbugsController.STAGE_B) return; break; case 3: if (stage != JitterbugsController.STAGE_E) return; break; case 4: if (stage != JitterbugsController.STAGE_A) return; break; } // this Line is OK to draw, so draw it g.setColor(Color.black); g.drawLine(m_deviceCoords[3*m_p1], m_deviceCoords[3*m_p1 + 1], m_deviceCoords[3*m_p2], m_deviceCoords[3*m_p2 + 1]); } } /** Initialize colors. */ static { m_colors[0] = new Color(255, 0, 0); m_colors[1] = new Color(255, 178, 178); m_colors[2] = new Color(0, 255, 0); m_colors[3] = new Color(178, 255, 178); } /** @param controller the JitterbugsController using this object */ JitterbugsGeometry(JitterbugsController controller) { m_controller = controller; setModelCoords(); // Jitterbug1 m_drawables[ 0] = new Triangle( 0, 2, 1, m_colors[0], m_colors[1]); m_drawables[ 1] = new Triangle( 2, 6, 10, m_colors[2], m_colors[3]); m_drawables[ 2] = new Triangle( 4, 6, 11, m_colors[0], m_colors[1]); m_drawables[ 3] = new Triangle( 1, 8, 9, m_colors[2], m_colors[3]); m_drawables[ 4] = new Triangle( 0, 7, 11, m_colors[2], m_colors[3]); m_drawables[ 5] = new Triangle( 3, 8, 10, m_colors[0], m_colors[1]); m_drawables[ 6] = new Triangle( 5, 7, 9, m_colors[0], m_colors[1]); m_drawables[ 7] = new Triangle( 5, 3, 4, m_colors[3], m_colors[3]); m_drawables[ 8] = new Line(11, 2, 0); m_drawables[ 9] = new Line( 6, 0, 1); m_drawables[10] = new Line( 5, 8, 0); m_drawables[11] = new Line( 3, 9, 1); m_drawables[12] = new Line( 0, 9, 0); m_drawables[13] = new Line( 1, 7, 1); m_drawables[14] = new Line( 6, 3, 0); m_drawables[15] = new Line( 4, 10, 1); m_drawables[16] = new Line( 4, 7, 0); m_drawables[17] = new Line( 5, 11, 1); m_drawables[18] = new Line(10, 1, 0); m_drawables[19] = new Line( 2, 8, 1); m_drawables[20] = new Line(12, 0, 3); m_drawables[21] = new Line(12, 1, 3); m_drawables[22] = new Line(12, 2, 3); m_drawables[23] = new Line(12, 3, 3); m_drawables[24] = new Line(12, 4, 3); m_drawables[25] = new Line(12, 5, 3); m_drawables[26] = new Line(12, 6, 3); m_drawables[27] = new Line(12, 7, 3); m_drawables[28] = new Line(12, 8, 3); m_drawables[29] = new Line(12, 9, 3); m_drawables[30] = new Line(12, 10, 3); m_drawables[31] = new Line(12, 11, 3); // Jitterbug2 (one triangle shared with Jitterbug1) m_drawables[32] = new Triangle( 4, 16, 20, m_colors[0], m_colors[1]); m_drawables[33] = new Triangle(14, 16, 21, m_colors[2], m_colors[3]); m_drawables[34] = new Triangle( 3, 18, 19, m_colors[0], m_colors[1]); m_drawables[35] = new Triangle( 5, 17, 21, m_colors[0], m_colors[1]); m_drawables[36] = new Triangle(13, 18, 20, m_colors[2], m_colors[3]); m_drawables[37] = new Triangle(15, 17, 19, m_colors[2], m_colors[3]); m_drawables[38] = new Triangle(15, 13, 14, m_colors[0], m_colors[1]); m_drawables[39] = new Line(21, 4, 2); m_drawables[40] = new Line(16, 5, 0); m_drawables[41] = new Line(15, 18, 2); m_drawables[42] = new Line(13, 19, 0); m_drawables[43] = new Line( 5, 19, 2); m_drawables[44] = new Line( 3, 17, 0); m_drawables[45] = new Line(16, 13, 2); m_drawables[46] = new Line(14, 20, 0); m_drawables[47] = new Line(14, 17, 2); m_drawables[48] = new Line(15, 21, 0); m_drawables[49] = new Line(20, 3, 2); m_drawables[50] = new Line( 4, 18, 0); m_drawables[51] = new Line(22, 13, 4); m_drawables[52] = new Line(22, 14, 4); m_drawables[53] = new Line(22, 15, 4); m_drawables[54] = new Line(22, 16, 4); m_drawables[55] = new Line(22, 17, 4); m_drawables[56] = new Line(22, 18, 4); m_drawables[57] = new Line(22, 19, 4); m_drawables[58] = new Line(22, 20, 4); m_drawables[59] = new Line(22, 21, 4); m_drawables[60] = new Line(22, 3, 4); m_drawables[61] = new Line(22, 4, 4); m_drawables[62] = new Line(22, 5, 4); } /** Compute coordinate values for the first point's coordinates from the angle value; then compute values of the other 11 points' coordinates from coordinate values for the first point. */ public synchronized void setModelCoords() { double radian_angle = m_controller.getAngle()*Math.PI/180.0; // set Jitterbug1 coordinates m_modelCoords[0] = (float)(RADIUS*Math.sin(radian_angle)); m_modelCoords[1] = (float)(RADIUS*Math.sin(2.0*Math.PI/3.0 - radian_angle)); m_modelCoords[2] = (float)(0.0); m_modelCoords[ 1*3 + 0] = +m_modelCoords[1]; m_modelCoords[ 1*3 + 1] = +m_modelCoords[2]; m_modelCoords[ 1*3 + 2] = +m_modelCoords[0]; m_modelCoords[ 2*3 + 0] = +m_modelCoords[2]; m_modelCoords[ 2*3 + 1] = +m_modelCoords[0]; m_modelCoords[ 2*3 + 2] = +m_modelCoords[1]; m_modelCoords[ 3*3 + 0] = -m_modelCoords[0]; m_modelCoords[ 3*3 + 1] = -m_modelCoords[1]; m_modelCoords[ 3*3 + 2] = +m_modelCoords[2]; m_modelCoords[ 4*3 + 0] = -m_modelCoords[1]; m_modelCoords[ 4*3 + 1] = +m_modelCoords[2]; m_modelCoords[ 4*3 + 2] = -m_modelCoords[0]; m_modelCoords[ 5*3 + 0] = +m_modelCoords[2]; m_modelCoords[ 5*3 + 1] = -m_modelCoords[0]; m_modelCoords[ 5*3 + 2] = -m_modelCoords[1]; m_modelCoords[ 6*3 + 0] = -m_modelCoords[0]; m_modelCoords[ 6*3 + 1] = +m_modelCoords[1]; m_modelCoords[ 6*3 + 2] = -m_modelCoords[2]; m_modelCoords[ 7*3 + 0] = +m_modelCoords[1]; m_modelCoords[ 7*3 + 1] = -m_modelCoords[2]; m_modelCoords[ 7*3 + 2] = -m_modelCoords[0]; m_modelCoords[ 8*3 + 0] = -m_modelCoords[2]; m_modelCoords[ 8*3 + 1] = -m_modelCoords[0]; m_modelCoords[ 8*3 + 2] = +m_modelCoords[1]; m_modelCoords[ 9*3 + 0] = +m_modelCoords[0]; m_modelCoords[ 9*3 + 1] = -m_modelCoords[1]; m_modelCoords[ 9*3 + 2] = -m_modelCoords[2]; m_modelCoords[10*3 + 0] = -m_modelCoords[1]; m_modelCoords[10*3 + 1] = -m_modelCoords[2]; m_modelCoords[10*3 + 2] = +m_modelCoords[0]; m_modelCoords[11*3 + 0] = -m_modelCoords[2]; m_modelCoords[11*3 + 1] = +m_modelCoords[0]; m_modelCoords[11*3 + 2] = -m_modelCoords[1]; m_modelCoords[12*3 + 0] = 0.0f; m_modelCoords[12*3 + 1] = 0.0f; m_modelCoords[12*3 + 2] = 0.0f; // set Jitterbug2 coordinates (temporarily change first vertex of #1) m_modelCoords[0] = (float)(RADIUS*Math.sin(Math.PI/3.0 + radian_angle)); m_modelCoords[1] = (float)(RADIUS*Math.sin(Math.PI/3.0 - radian_angle)); m_modelCoords[2] = (float)(0.0); m_modelCoords[13*3 + 0] = -m_modelCoords[0]; m_modelCoords[13*3 + 1] = -m_modelCoords[1]; m_modelCoords[13*3 + 2] = +m_modelCoords[2]; m_modelCoords[14*3 + 0] = -m_modelCoords[1]; m_modelCoords[14*3 + 1] = +m_modelCoords[2]; m_modelCoords[14*3 + 2] = -m_modelCoords[0]; m_modelCoords[15*3 + 0] = +m_modelCoords[2]; m_modelCoords[15*3 + 1] = -m_modelCoords[0]; m_modelCoords[15*3 + 2] = -m_modelCoords[1]; m_modelCoords[16*3 + 0] = -m_modelCoords[0]; m_modelCoords[16*3 + 1] = +m_modelCoords[1]; m_modelCoords[16*3 + 2] = -m_modelCoords[2]; m_modelCoords[17*3 + 0] = +m_modelCoords[1]; m_modelCoords[17*3 + 1] = -m_modelCoords[2]; m_modelCoords[17*3 + 2] = -m_modelCoords[0]; m_modelCoords[18*3 + 0] = -m_modelCoords[2]; m_modelCoords[18*3 + 1] = -m_modelCoords[0]; m_modelCoords[18*3 + 2] = +m_modelCoords[1]; m_modelCoords[19*3 + 0] = +m_modelCoords[0]; m_modelCoords[19*3 + 1] = -m_modelCoords[1]; m_modelCoords[19*3 + 2] = -m_modelCoords[2]; m_modelCoords[20*3 + 0] = -m_modelCoords[1]; m_modelCoords[20*3 + 1] = -m_modelCoords[2]; m_modelCoords[20*3 + 2] = +m_modelCoords[0]; m_modelCoords[21*3 + 0] = -m_modelCoords[2]; m_modelCoords[21*3 + 1] = +m_modelCoords[0]; m_modelCoords[21*3 + 2] = -m_modelCoords[1]; m_modelCoords[22*3 + 0] = 0.0f; m_modelCoords[22*3 + 1] = 0.0f; m_modelCoords[22*3 + 2] = 0.0f; // restore first points values m_modelCoords[0] = m_modelCoords[ 1*3 + 2]; m_modelCoords[1] = m_modelCoords[ 1*3 + 0]; m_modelCoords[2] = m_modelCoords[ 1*3 + 1]; // apply shifts to both Jitterbugs so the origin coincides with the // midpoint of the line segment joining their centers double shift = (Math.sin(radian_angle) + Math.sqrt(3.0)*Math.cos(radian_angle))/ (2.0*Math.sqrt(2.0)); shift = shift/Math.sqrt(3.0); int i; for (i = 0; i < 39; i++) m_modelCoords[i] += shift; for (; i < 69; i++) m_modelCoords[i] -= shift; } /** Heap sort drawables so z-value of transformed midpoints increase. Adapted from S. Dvorak and A. Musset, BASIC in Action, p. 170. */ private synchronized void sort(int zmids[]) { int i; int shift_index, zmid_value; Drawable drawable; // Organize array into descending heap (largest value at root) for (i = (m_drawables.length - 1)/2; i >= 0; i--) shift_up(zmids, zmids[i], m_drawables[i], i, m_drawables.length); /* 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_drawables.length - 1; i > 0; i--) { /* grab value at and index of current array position being filled (it will be inserted into remaining heap) */ zmid_value = zmids[i]; drawable = m_drawables[i]; // put largest value of remaining heap in final position zmids[i] = zmids[0]; m_drawables[i] = m_drawables[0]; // shift value up into remaining descending heap shift_up(zmids, zmid_value, drawable, 0, i); } } /** Shift value up into heap until it's not less than successors (used by sort). @see #sort */ private void shift_up (int zmids[], int zmid_value, Drawable drawable, 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 zmids[start_index] if it has larger value if (j + 1 < max_index && zmids[j] < zmids[j + 1]) j++; if (zmid_value < zmids[j]) // shift value less than a successor { // put this successor in shift_value's old position zmids[start_index] = zmids[j]; m_drawables[start_index] = m_drawables[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 zmids[start_index] = zmid_value; m_drawables[start_index] = drawable; } /** Render this model. It uses the matrix associated with this model to map from model space to device space. @param g Graphics object to use for rendering @param deviceXform device transformation */ synchronized void paint(Graphics2D g, matrix3d deviceXform) { if (m_drawables[m_drawables.length - 1] == null) return; deviceXform.transform(m_modelCoords, m_deviceCoords, m_modelCoords.length/3); int zmids[] = new int[m_drawables.length]; for (int i = 0; i < m_drawables.length; i++) zmids[i] = m_drawables[i].zmid(); sort(zmids); // g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // RenderingHints.VALUE_ANTIALIAS_ON); for (int i = 0; i < m_drawables.length; i++) m_drawables[i].draw(g, m_controller); } }