/* TView.java tensegrity viewer History: 13 Jun 08 Only act on SELECTED state changes for the ModelMenu. 12 Jun 08 Do not implement ThreeDClient. ThreeD constructor no longer requires client as arg. Fine tune exception handling in TView.load() and TViewPgm.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 TViewPgm. 06 Jun 08 Improve comments. 05 Jun 08 Extract Choice information to separate ModelMenu class. It now reads the label-filename pairs out of a file called "models.txt" in the directory where the model data is stored. 03 Jun 08 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 08 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 08 Add "Aspension Skylon". 26 Dec 07 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 07 Use different data files for "Wishbone Tensegrity" and "Diamond-Shaped Tensegrity". 21 Nov 07 Add "Split-Level Prism", "Wishbone Tensegrity" and "Diamond-Shaped Tensegrity". 11 Sep 07 Add "Aspension Tower". 15 Aug 07 Add "3-Stage X-Module Column". Use Frame.setVisible() instead of deprecated show(). 13 Aug 07 Add "Dodecahedron with X-Column Edges". 29 Jun 07 Change "Val Gómez module" to "Gómez Jáuregui module". 16 Feb 07 Add "Perspective Prism". 10 Aug 06 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 06 Reduce initial size from 700x700 to 600x600. Give the value a label, INITIAL_DIM. 12 Jul 06 Remove comment about "puzzle" which doesn't make sense to me anymore: "Would like to remove updateBuffer() from ThreeD.load() so I can just load data in init() using load() and paint the picture in start() using ThreeD.updateBuffer(). This would save on the seemingly unnecessary task of loading the model data when the applet is restarted. All I get is a blank picture though." 12 Jun 06 Remove last arg to setMagnification -- no longer needed. Don't check the source in itemStateChanged() since there's only one. Make load() and handleModelSelection() public. With the changes to this file, ThreeD.java and TViewJ3D.java, TView.java and TViewJ3D.java are now more similar. 06 Jun 06 Decrease max magnification to 8X. Feel less power but better control over the power that's there. 01 Jun 06 Increase max magnification to 12X. Feel the power. Use new doUpdate arg for ThreeD.setMagnification(). Simplify magnification updates. 31 May 06 ThreeD.startRotation() is now startDrag(). Implement drag scrolling. 30 May 06 Use float constant instead of casting double constant to float. updateMagnification has been renamed to incrementMagnification. Change resetMagnifyBar to resetMagnification, and have it take care of talking to m_threed. Create resetRotation with similar functionality in rotation domain. 26 May 06 Add drag rotation. 25 May 06 Remove rotation components and adjust others to accommodate. 17 Apr 06 Add "2v Icosa Sphere" and put "Octa" adjective on labels for all the other double-layer domes and spheres. 17 Mar 06 Add a to-do item. 15 Mar 06 Add "Emmerich's Prism" and a to-do item. Change "3-Fold Emmerich-Style Prism" to "Emmerich's Prism (variation)". 13 Mar 06 Add "Tensegrity Tetrahedron 2", "T-Tetra 2 (variation)" and "Tensegrity Tetrahedron 3". 08 Feb 06 Add "Skew Prism Arch". 07 Feb 06 Add "Bouncy Mast (spiraling)" and "Bouncy Mast (alternating)". 31 Jan 06 Add "T-Cuboctahedron". 27 Jan 06 Add "Skew Tensegrity". 30 Dec 05 Add "Octahelix". 27 Dec 05 Add "Thirteen Great Circles". 06 Dec 05 Add a couple skew 5-prisms. Rename "Helix" and "Double Helix" to "Tetrahelix" and "Double Tetrahelix" respectively (thanks to Tim Tyler for pointing out the terminology deficiency). 21 Nov 05 Add 12-Stage Torus. 20 Oct 05 Add Eight-Stage Zig-Zag Arch and Tensegrity Tetrahedron. 21 Sep 05 Replace deprecated API constructs. 20 Sep 05 Incorporate TViewPgm.java and rename from TViewApplet.java to TView.java. 28 Mar 05 Add 3-Fold Orthogonal Prism, 3-Fold Emmerich-Style Prism, 6-Fold Ortho-Girdled Prism, Three 6-Fold Prisms Merged, Skew Eight-Prism, 4-Fold Obelisk, X-Module Bean Teepee, X-Module Torus 1, X-Module Torus 2 and X-Module Arch. 23 Nov 00 Add 4v Tetrahedron Aligned Diamond Tensegrity. 12 Oct 99 Add single helix. 21 Jun 99 Remove tomato cage. Add second obelisk. 15 Jun 99 Added equi-tendoned tensegrity prisms (3x, 6x and 9x). 24 Mar 99 Added javadoc comments. Changed name of m_dim to m_axis. Centered rotate Scrollbar on 0. Changed name from TView to TViewApplet. 06 Mar 99 Check evt.evt for Event on same object when fielding Scrollbar events. 24 Feb 99 Substitute ThreeD.Client for ThreeDClient. 20 Oct 98 Reorganize model labels/filenames. Sup up equality tests. 07 Oct 98 Added userMessage() and changed load() to accommodate new interface to ThreeD. 26 Aug 98 Rewrite to incorporate restructuring of ThreeD class. Init rotate/magnify value in init() and not in start(). Now if user returns to page without reloading, model and GUI are same state as when they left. 21 Aug 98 Use currently selected model in start() rather than always using tetra. 17 Jul 98 Don't process Scrollbar events when value hasn't changed. 29 Jun 98 Created. */ import java.awt.*; import java.awt.event.*; // for WindowAdapter and WindowEvent import java.io.*; import java.net.URL; /** GUI for viewing wireframe structures -- mostly tensegrities with single-point hubs @author Bob Burkhardt */ public class TView extends java.applet.Applet implements ItemListener, AdjustmentListener, MouseListener, MouseMotionListener { ThreeD m_threed; Scrollbar m_magnifybar; ModelMenu 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 Canvas 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 ModelMenu. */ 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 Scrollbar(Scrollbar.VERTICAL, 0, SCROLL_BUBBLE_WIDTH, -SCROLL_RESOLUTION, SCROLL_BUBBLE_WIDTH); m_magnifybar.addAdjustmentListener(this); add("East", m_magnifybar); try { m_select_model = new ModelMenu(is); m_select_model.addItemListener(this); } catch (Exception e) { userMessage(e.toString()); } Panel p = new Panel(); p.setLayout(new FlowLayout()); if (m_select_model != null) p.add(m_select_model); add("North", p); m_threed = new ThreeD(); m_threed.addMouseListener(this); m_threed.addMouseMotionListener(this); 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) { NotifyDialog dialog = new NotifyDialog(msg); dialog.setSize(INITIAL_DIM, INITIAL_DIM/4); dialog.setVisible(true); } /** 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 Choice/ModelMenu 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 magnification Scrollbar into initial state. */ private void resetMagnifyBar() { m_magnifybar.setValue(0); m_magnify_value = 0; } } class NotifyDialog extends Frame implements ActionListener { Label m_message; Button m_dismiss; NotifyDialog(String message) { super("Problem Report"); setLayout(new BorderLayout()); m_message = new Label(message); m_dismiss = new Button("Dismiss"); m_dismiss.addActionListener(this); add("Center", m_message); Panel p = new Panel(); p.add(m_dismiss); add("South", p); } public void actionPerformed(ActionEvent evt) { setVisible(false); } } /** 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 TViewPgm extends TView { 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 TViewPgm(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("load: " + e.toString()); return; } if (is != null) try { m_threed.load(is); is.close(); } catch (Exception e) { userMessage("load: " + 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: TViewPgm MODEL_DIR"); System.exit(1); } Frame f = new Frame("Tensegrity Viewer"); TView viewer = new TViewPgm(args[0]); viewer.init(); f.add(viewer); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setSize(INITIAL_DIM, INITIAL_DIM); f.setVisible(true); viewer.start(); } }