/* TViewJ3D.java Java 3D version of tensegrity viewer History: 13 Jun 2008 Only act on SELECTED state changes for the JModelMenu. 12 Jun 2008 Do not implement ThreeDClient. ThreeD constructor no longer requires client as arg. Fine tune exception handling in TViewJ3D.load() and TViewJ3DPgm.load() by resetting ThreeD if IOException occurs when stream is opened. Replace all reset functions with resetMagnifyBar, and now it does not do anything with ThreeD. Calling ThreeD.load handles resetting there. Implement userMessage() as popup dialog and eliminate special handling for TViewJ2DPgm. 06 Jun 2008 Improve comments. JComboBox eliminated in favor of separate JModelMenu class which reads the label-filename pairs out of a file called "models.txt" in the directory where the model data is stored. 03 Jun 2008 Change "Aspension Tower" label to "Aspension Tower (Marcus)" so it is clearer that Jan Marcus is the author of this design. Re-order the aspensions so the towers come first. Label the tetrahedrons with their authors, and re-order to match order in VRML viewer. 19 May 2008 Add "Aspension Tower II" and "Aspension Canary". Change "Aspension Skylon" to "Aspension Skylon II" and refer to another data file. "Aspension Tower" now refers to data for Jan's original structure, and "Aspension Tower II" refers to my variation. 10 Mar 2008 Add "Aspension Skylon". 26 Dec 2007 Add "Split-Level Prism II" and "Tetrahedral Prisms". Rename "Diamond-Shaped Tensegrity" as "Diamond-Shaped Prisms" and "Split-Level Prism" as "Split-Level Prism I". 07 Dec 2007 Use different data files for "Wishbone Tensegrity" and "Diamond-Shaped Tensegrity". 21 Nov 2007 Add "Split-Level Prism", "Wishbone Tensegrity" and "Diamond-Shaped Tensegrity". 11 Sep 2007 Add "Aspension Tower". 15 Aug 2007 Add "3-Stage X-Module Column". 13 Aug 2007 Add "Dodecahedron with X-Column Edges". 29 Jun 2007 Change "Val Gómez module" to "Gómez Jáuregui module". Change f.show() to f.setVisible(true). 16 Feb 2007 Add "Perspective Prism". 10 Aug 2006 Close the URL InputStream after reading the data. If an exception occurs, send a user message. Close the FileInputStream after reading the data. 01 Aug 2006 Reduce INITIAL_DIM from 700 to 600. Use Swing GUI. 16 Jun 2006 Add "2v Octa Sphere (variation)". 12 Jun 2006 Delete version of init() with width and height args. Make load() and handleModelSelection() public. Add another two non-tensegrity data files that were in the plain Java version but not in the 3D version: v10octa.dat and v10octas.dat. With the changes to this file, TView.java and ThreeD.java, TView.java and TViewJ3D.java are now more similar. 06 Jun 2006 Now that the strut colors have been changed to 7, include the three non-tensegrity data files that were in the plain Java version but not in the 3D version: tetra.dat, helix.dat and dblhelix.dat. Reduce max magnification to 8X. 01 Jun 2006 Fix glitch in Scrollbar (didn't account for bubble). Add drag rotation and scroll. 26 May 2006 Put the magnify scrollbar on the right. 17 Apr 2006 Add "2v Icosa Sphere" and put "Octa" adjective on labels for all the other double-layer domes and spheres. 15 Mar 2006 Add "Emmerich's Prism" and a to-do item. Change "3-Fold Emmerich-Style Prism" to "Emmerich's Prism (variation)". 13 Mar 2006 Add "Tensegrity Tetrahedron 2", "T-Tetra 2 (variation)" and "Tensegrity Tetrahedron 3". 08 Feb 2006 Add "Skew Prism Arch". 07 Feb 2006 Add "Bouncy Mast (spiraling)" and "Bouncy Mast (alternating)". 31 Jan 2006 Add "T-Cuboctahedron". 27 Jan 2006 Add "Skew Tensegrity". 30 Dec 2005 Add "Octahelix". 27 Dec 2005 Add "Thirteen Great Circles". 06 Dec 2005 Add a couple skew 5-prisms. 21 Nov 2005 Add 12-Stage Torus. 20 Oct 2005 Add Eight-Stage Zig-Zag Arch and Tensegrity Tetrahedron. 04 Oct 2005 Remove non-tensegrity structures from menu. Remove import com.sun.j3d.utils.applet.MainFrame. 21 Sep 2005 Replace deprecated API constructs. 20 Sep 2005 Created from TView.java. Comment: Using JApplet instead of Applet doesn't work. JComboBox isn't able to draw over Canvas3D area!? */ import java.awt.*; import javax.swing.*; import javax.swing.event.*; import java.awt.event.*; import java.io.*; import java.net.URL; /** GUI for viewing tensegrities with simple hubs using Java3D extension. @author Bob Burkhardt */ public class TViewJ3D extends java.applet.Applet implements ItemListener, AdjustmentListener, MouseListener, MouseMotionListener { JThreeD m_threed; JScrollBar m_magnifybar; JModelMenu m_select_model; /** Name of file where label-filename data is stored. */ static final String LABEL_FILENAME = "models.txt"; /** Name of (default) directory where model data files are stored. Only used by TView applet, not by TViewPgm. */ static final String MODEL_DATA_DIR = "models"; /** How many integer units to divide the zoom Scrollbar's range into. Scrollbar is set so its output value ranges from 0 to -SCROLL_RESOLUTION. */ static final int SCROLL_RESOLUTION = 1000; /** How wide to make the zoom Scrollbar bubble. Should be some smaller percentage of the SCROLL_RESOLUTION value and definitely not bigger than it. */ static final int SCROLL_BUBBLE_WIDTH = 50; /** The maximum magnification that is allowed. The minimum is none (1.0f). */ static final float MAX_MAGNIFICATION = 8.0f; /** Amount the figure is currently being magnified. Varies from 0 (no magnification) to -SCROLL_RESOLUTION (MAX_MAGNIFICATION). */ int m_magnify_value; /** Initial dimension of Canvas3D square. */ static final int INITIAL_DIM = 600; /** Indicate whether left-mouse-button drag is in progress. MOUSE_PRESSED indicates drag in progress; otherwise, MOUSE_RELEASED. */ int m_drag = MouseEvent.MOUSE_RELEASED; /** Overload standard Applet initialization procedure. */ public void init() { try { URL url = new URL(getDocumentBase(), MODEL_DATA_DIR + "/" + LABEL_FILENAME); InputStream is = url.openStream(); init(is); } catch (Exception e) { userMessage(e.toString()); } } /** Initialize the GUI. @param is InputStream containing data for JModelMenu. */ public void init(InputStream is) { setLayout(new BorderLayout()); // parameters are set so the top of the scrollbar represents the // maximimum magnification rather than the usual scrolling situation // where the top represents minimum scroll -- Scrollbar value is // negated before use. The max value must be adjusted for the // size of the thumb. m_magnifybar = new JScrollBar(Scrollbar.VERTICAL, 0, SCROLL_BUBBLE_WIDTH, -SCROLL_RESOLUTION, SCROLL_BUBBLE_WIDTH); m_magnifybar.addAdjustmentListener(this); add("East", m_magnifybar); try { m_select_model = new JModelMenu(is); m_select_model.addItemListener(this); } catch (Exception e) { userMessage(e.toString()); } JPanel p = new JPanel(); p.setLayout(new FlowLayout()); p.add(m_select_model); add("North", p); m_threed = new JThreeD(MAX_MAGNIFICATION, INITIAL_DIM, INITIAL_DIM); m_threed.addMouseListener(this); m_threed.addMouseMotionListener(this); add("Center", m_threed); } /** Overload standard Applet start procedure. */ public void start() { handleModelSelection(); } /** Display a status message to the user. @param msg String containing message to be displayed. */ public void userMessage(String msg) { JOptionPane.showMessageDialog(this, msg, "Problem Report", JOptionPane.ERROR_MESSAGE); } /** Create data stream for specified model and send to m_threed. Applet version. @param model_name unqualified name of file containing model data */ public void load(String model_name) { InputStream is = null; try { URL url = new URL(getDocumentBase(), "models/" + model_name); is = url.openStream(); } catch (Exception e) { m_threed.reset(); userMessage(e.toString()); return; } if (is != null) try { m_threed.load(is); is.close(); } catch (Exception e) { userMessage(e.toString()); } } /** Load the model corresponding to currently selected model. */ public void handleModelSelection() { load(m_select_model.getSelectedFilename()); resetMagnifyBar(); } /** Handle JComboBox/JModelMenu and JCheckBox events. Implements ItemListener. @param evt Data pertaining to this event. */ public void itemStateChanged(ItemEvent evt) { if (evt.getStateChange() == ItemEvent.SELECTED) handleModelSelection(); } /** Handle Scrollbar events. Implements AdjustmentListener. @param evt Data pertaining to this event. */ public void adjustmentValueChanged(AdjustmentEvent evt) { int old_value = m_magnify_value; m_magnify_value = m_magnifybar.getValue(); float max = (float)-SCROLL_RESOLUTION; float int_mag = (float)m_magnify_value; if (old_value != m_magnify_value) m_threed.setMagnification(1.0f + (MAX_MAGNIFICATION - 1.0f)*int_mag/max); } /** Watch for presses of the left mouse button, and update state accordingly (part of MouseListener interface). @param evt Data pertaining to this event. */ public void mousePressed(MouseEvent evt) { if (evt.getButton() == MouseEvent.BUTTON1) { m_drag = MouseEvent.MOUSE_PRESSED; m_threed.startDrag(evt.getX(), evt.getY()); } } /** Watch for presses of the left mouse button, and update state accordingly (part of MouseListener interface). @param evt Data pertaining to this event. */ public void mouseReleased(MouseEvent evt) { if (evt.getButton() == MouseEvent.BUTTON1) m_drag = MouseEvent.MOUSE_RELEASED; } /** Implement this for the MouseListener interface, but ignore the events. */ public void mouseClicked(MouseEvent evt) { } /** Implement this for the MouseListener interface, but ignore the events. */ public void mouseEntered(MouseEvent evt) { } /** Implement this for the MouseListener interface, but ignore the events. */ public void mouseExited(MouseEvent evt) { } /** Watch for mouse movements and adjust rotation accordingly (part of MouseMotionListener interface). @param evt Data pertaining to this event. */ public void mouseDragged(MouseEvent evt) { if (m_drag == MouseEvent.MOUSE_PRESSED) if ((evt.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0) m_threed.updateShift(evt.getX(), evt.getY()); else m_threed.updateRotation(evt.getX(), evt.getY()); } /** Implement this for the MouseMotionListener interface, but ignore the events. */ public void mouseMoved(MouseEvent evt) { } /** Put magnify Scrollbar at its minimum-magnification value (0). This is actually the maximum as far as the Scrollbar's internals are concerned. The maximum-magnification value is -SCROLL_RESOLUTION. This method also communicates with m_threed to make sure the magnification gets reset to 1.0 there. */ private void resetMagnifyBar() { m_magnifybar.setValue(0); m_magnify_value = 0; } } /** Provides for a stand-alone (app rather than applet) instance of the tensegrity viewer. It builds on the Applet overriding in the few situations where different facilities are required. */ class TViewJ3DPgm extends TViewJ3D { String m_model_dir; /** Create a tensegrity viewer instance. @param model_dir name of directory where model data files and text file containing label-filename list can be found. @see #LABEL_FILENAME */ public TViewJ3DPgm(String model_dir) { m_model_dir = model_dir; } /** Override the design for Applet with something that gets streams from Files rather than URLs. */ public void init() { FileInputStream is = null; File label_file = new File(m_model_dir + File.separator + LABEL_FILENAME); try { is = new FileInputStream(label_file); } catch (Exception e) { userMessage("init: " + e.toString()); } super.init(is); } /** Create data stream for specified model and send to m_threed. Stand-alone app version. @param model_name unqualified name of file containing model data */ public void load(String model_name) { FileInputStream is = null; File model_file = new File(m_model_dir + File.separator + model_name); try { is = new FileInputStream(model_file); } catch (Exception e) { m_threed.reset(); userMessage(e.toString()); return; } if (is != null) try { m_threed.load(is); is.close(); } catch (Exception e) { userMessage(e.toString()); } } /** Requires the path to the directory containing the model data as an argument. */ public static void main(String args[]) { if (args.length != 1) { System.out.println("Usage: TViewJ3DPgm MODEL_DIR"); System.exit(1); } JFrame f = new JFrame("Tensegrity Viewer"); TViewJ3D viewer = new TViewJ3DPgm(args[0]); viewer.init(); f.getContentPane().add(viewer); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setSize(INITIAL_DIM, INITIAL_DIM); f.setVisible(true); viewer.start(); } }