/* TViewJ2D.java Java 2D 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 TViewJ2D.load() and TViewJ2DPgm.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". Use JFrame.setVisible() instead of deprecated show(). 29 Jun 2007 Change "Val Gómez module" to "Gómez Jáuregui module". 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. 03 Aug 2006 Use getContentPane() to setLayout() and add() for JApplet. 01 Aug 2006 Convert GUI to Swing. Decrease initial size from 700x700 to 600x600. 24 Jul 2006 Add Checkbox to turn color on and off. 14 Jun 2006 Add Checkboxes to turn anti-aliasing and two-tone rendering on and off. 13 Jul 2006 Created from TViewJ3D.java. */ 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 single-point hubs using Java2D. @author Bob Burkhardt */ public class TViewJ2D extends JApplet implements ItemListener, AdjustmentListener, MouseListener, MouseMotionListener { JTwoD m_threed; JScrollBar m_magnifybar; JCheckBox m_do_anti_aliasing, m_do_two_tone, m_do_color; 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 TViewJ2D applet, not by TViewJ2DPgm. */ static final String MODEL_DATA_DIR = "models"; /** How many integer units to divide the zoom JScrollBar's range into. JScrollBar is set so its output value ranges from 0 to -SCROLL_RESOLUTION. */ static final int SCROLL_RESOLUTION = 1000; /** How wide to make the zoom JScrollBar 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) { getContentPane().setLayout(new BorderLayout()); // parameters are set so the top of the JScrollBar 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(JScrollBar.VERTICAL, 0, SCROLL_BUBBLE_WIDTH, -SCROLL_RESOLUTION, SCROLL_BUBBLE_WIDTH); m_magnifybar.addAdjustmentListener(this); getContentPane().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()); m_do_anti_aliasing = new JCheckBox("Anti-aliasing"); m_do_anti_aliasing.setSelected(true); m_do_anti_aliasing.addItemListener(this); p.add(m_do_anti_aliasing); p.add(m_select_model); m_do_two_tone = new JCheckBox("Depth Shading"); m_do_two_tone.addItemListener(this); p.add(m_do_two_tone); m_do_two_tone.setSelected(true); m_do_color = new JCheckBox("Color"); m_do_color.addItemListener(this); p.add(m_do_color); m_do_color.setSelected(true); getContentPane().add("North", p); m_threed = new JTwoD(); m_threed.addMouseListener(this); m_threed.addMouseMotionListener(this); m_threed.doAntiAliasing(true); m_threed.doTwoTone(true); getContentPane().add("Center", m_threed); resetMagnifyBar(); } /** 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(getContentPane(), 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(), MODEL_DATA_DIR + "/" + 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() { resetMagnifyBar(); load(m_select_model.getSelectedFilename()); } /** Handle JComboBox/JModelMenu and JCheckBox events. Implements ItemListener. @param evt Data pertaining to this event. */ public void itemStateChanged(ItemEvent evt) { if (m_threed == null) return; if (evt.getSource() == m_select_model) { if (evt.getStateChange() == ItemEvent.SELECTED) handleModelSelection(); } else if (evt.getSource() == m_do_anti_aliasing) { m_threed.doAntiAliasing(m_do_anti_aliasing.isSelected()); } else if (evt.getSource() == m_do_two_tone) { m_threed.doTwoTone(m_do_two_tone.isSelected()); } else if (evt.getSource() == m_do_color) { m_threed.doColor(m_do_color.isSelected()); } } /** 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 magnification Scrollbar into initial state. */ 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 TViewJ2DPgm extends TViewJ2D { 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 TViewJ2DPgm(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: TViewJ2DPgm MODEL_DIR"); System.exit(1); } JFrame f = new JFrame("Tensegrity Viewer"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); TViewJ2D viewer = new TViewJ2DPgm(args[0]); viewer.init(); f.getContentPane().add(viewer); f.setSize(INITIAL_DIM, INITIAL_DIM); f.setVisible(true); viewer.start(); } }