/* * Open Source Physics software is free software as described near the bottom of this code file. * * For additional information and documentation on Open Source Physics please see: * <http://www.opensourcephysics.org/> */ package org.opensourcephysics.display3d.simple3d; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.LayoutManager; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.awt.print.PageFormat; import java.awt.print.Printable; import java.awt.print.PrinterException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import javax.swing.JViewport; import javax.swing.SwingUtilities; import javax.swing.event.MouseInputAdapter; import org.opensourcephysics.controls.XML; import org.opensourcephysics.controls.XMLControl; import org.opensourcephysics.display.OSPLayout; import org.opensourcephysics.display.TextPanel; import org.opensourcephysics.display3d.core.interaction.InteractionEvent; import org.opensourcephysics.display3d.core.interaction.InteractionListener; import org.opensourcephysics.tools.VideoTool; /** * * <p>Title: DrawingPanel3D</p> * * <p>Description: The simple3D implementation of a DrawingPanel3D.</p> * * <p>Interaction: The panel has only one target, the panel itself. * If enabled, the panel issues MOUSE_ENTER, MOUSE_EXIT, * MOUSE_MOVED, and MOUSE_DRAGGED InteractionEvents with target=null. * When the ALT key is held, the panel also issues MOUSE_PRESSED, * MOUSE_DRAGGED (again), and MOUSE_RELEASED InteractionEvents. * In this second case, the getInfo() method of the event returns a double[3] * with the coordinates of the point selected.</p> * <p>Even if the panel is disabled, the panel can be panned, zoomed and (in 3D * modes) rotated and the elements in it can be enabled.</p> * <p>The interaction capabilities are not XML serialized.</p> * * <p>Copyright: Open Source Physics project</p> * @author Francisco Esquembre * @version June 2005 */ public class DrawingPanel3D extends javax.swing.JPanel implements org.opensourcephysics.display.Renderable, org.opensourcephysics.display3d.core.DrawingPanel3D, Printable, ActionListener { static private final int AXIS_DIVISIONS = 10; static private final Color bgColor = new Color(239, 239, 255); // Configuration variables private double xmin, xmax, ymin, ymax, zmin, zmax; private VisualizationHints visHints = null; private Camera camera = null; private String imageFile = null; // Implementation variables private boolean quickRedrawOn = false, squareAspect = true; private double centerX, centerY, centerZ, maximumSize; private double aconstant, bconstant; private int acenter, bcenter; private ArrayList<Object3D> list3D = new ArrayList<Object3D>(); private ArrayList<org.opensourcephysics.display3d.core.Element> decorationList = new ArrayList<org.opensourcephysics.display3d.core.Element>(); private ArrayList<org.opensourcephysics.display3d.simple3d.Element> elementList = new ArrayList<org.opensourcephysics.display3d.simple3d.Element>(); private Object3D.Comparator3D comparator = new Object3D.Comparator3D(); // see class Comparator3D below // Variables for decoration private ElementArrow xAxis, yAxis, zAxis; private ElementText xText, yText, zText; private ElementSegment[] boxSides = new ElementSegment[12]; // Variables for interaction private final InteractionTarget myTarget = new InteractionTarget(null, 0); private int trackersVisible, keyPressed = -1; private int lastX = 0, lastY = 0; private InteractionTarget targetHit = null, targetEntered = null; private double[] trackerPoint = null; private ArrayList<InteractionListener> listeners = new ArrayList<InteractionListener>(); private ElementSegment[] trackerLines = null; // Variables for painting volatile private boolean dirtyImage = true; // offscreenImage needs to be recomputed // the image that will be copied to the screen volatile private BufferedImage offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); // the image into which we will draw private BufferedImage workingImage = offscreenImage; private javax.swing.Timer updateTimer = new javax.swing.Timer(100, this); // delay before updating the panel private boolean needResize = true, needsToRecompute = true; // Variables for Messages protected TextPanel trMessageBox = new TextPanel(); // text box in top right hand corner for message protected TextPanel tlMessageBox = new TextPanel(); // text box in top left hand corner for message protected TextPanel brMessageBox = new TextPanel(); // text box in lower right hand corner for message protected TextPanel blMessageBox = new TextPanel(); // text box in lower left hand corner for mouse coordinates protected GlassPanel glassPanel = new GlassPanel(); protected OSPLayout glassPanelLayout = new OSPLayout(); protected Rectangle viewRect = null; // the clipping rectangle within a scroll pane viewport //CJB //Scale factor private double factorX = 1.0; private double factorY = 1.0; private double factorZ = 1.0; private static int axisMode = MODE_XYZ; /** * The video capture tool for this panel. */ protected VideoTool vidCap; //CJB private void BuildAxesPanel(int mode) { if((axisMode==mode)&&(xAxis!=null)) { return; } axisMode = mode; if(xAxis==null) { /* Decoration of the scene */ // Create the bounding box Resolution axesRes = new Resolution(AXIS_DIVISIONS, 1, 1); for(int i = 0, n = boxSides.length; i<n; i++) { boxSides[i] = new ElementSegment(); boxSides[i].getRealStyle().setResolution(axesRes); boxSides[i].setPanel(this); // Because I don't add it to the panel in the standard way decorationList.add(boxSides[i]); } boxSides[0].getStyle().setLineColor(new Color(128, 0, 0)); boxSides[3].getStyle().setLineColor(new Color(0, 128, 0)); boxSides[8].getStyle().setLineColor(new Color(0, 0, 255)); // Create the axes String[] axesLabels = visHints.getAxesLabels(); xAxis = new ElementArrow(); xAxis.getRealStyle().setResolution(axesRes); xAxis.getStyle().setFillColor(new Color(128, 0, 0)); xAxis.setPanel(this); decorationList.add(xAxis); xText = new ElementText(); xText.setText(axesLabels[0]); xText.setJustification(org.opensourcephysics.display3d.core.ElementText.JUSTIFICATION_CENTER); xText.getRealStyle().setLineColor(Color.BLACK); xText.setFont(new Font("Dialog", Font.PLAIN, 12)); //$NON-NLS-1$ xText.setPanel(this); decorationList.add(xText); yAxis = new ElementArrow(); yAxis.getRealStyle().setResolution(axesRes); yAxis.getStyle().setFillColor(new Color(0, 128, 0)); yAxis.setPanel(this); decorationList.add(yAxis); yText = new ElementText(); yText.setText(axesLabels[1]); yText.setJustification(org.opensourcephysics.display3d.core.ElementText.JUSTIFICATION_CENTER); yText.getRealStyle().setLineColor(Color.BLACK); yText.setFont(new Font("Dialog", Font.PLAIN, 12)); //$NON-NLS-1$ yText.setPanel(this); decorationList.add(yText); zAxis = new ElementArrow(); zAxis.getRealStyle().setResolution(axesRes); zAxis.getStyle().setFillColor(new Color(0, 0, 255)); zAxis.setPanel(this); decorationList.add(zAxis); zText = new ElementText(); zText.setText(axesLabels[2]); zText.setJustification(org.opensourcephysics.display3d.core.ElementText.JUSTIFICATION_CENTER); zText.getRealStyle().setLineColor(Color.BLACK); zText.setFont(new Font("Dialog", Font.PLAIN, 12)); //$NON-NLS-1$ zText.setPanel(this); decorationList.add(zText); // Create the trackers trackerLines = new ElementSegment[9]; for(int i = 0, n = trackerLines.length; i<n; i++) { trackerLines[i] = new ElementSegment(); trackerLines[i].getRealStyle().setResolution(axesRes); trackerLines[i].setVisible(false); trackerLines[i].setPanel(this); decorationList.add(trackerLines[i]); } setCursorMode(); // compute the correct value for trackersVisible /* End of decoration */ } else { resetDecoration(xmax-xmin, ymax-ymin, zmax-zmin); } } //CJB /** * Constructor DrawingPanel3D */ public DrawingPanel3D() { // GlassPanel for messages glassPanel.setLayout(glassPanelLayout); super.setLayout(new BorderLayout()); glassPanel.add(trMessageBox, OSPLayout.TOP_RIGHT_CORNER); glassPanel.add(tlMessageBox, OSPLayout.TOP_LEFT_CORNER); glassPanel.add(brMessageBox, OSPLayout.BOTTOM_RIGHT_CORNER); glassPanel.add(blMessageBox, OSPLayout.BOTTOM_LEFT_CORNER); glassPanel.setOpaque(false); super.add(glassPanel, BorderLayout.CENTER); setBackground(bgColor); setPreferredSize(new Dimension(300, 300)); visHints = new VisualizationHints(this); camera = new Camera(this); addComponentListener(new java.awt.event.ComponentAdapter() { public void componentResized(java.awt.event.ComponentEvent e) { needResize = true; dirtyImage = true; } }); IADMouseController mouseController = new IADMouseController(); addMouseListener(mouseController); addMouseMotionListener(mouseController); addKeyListener(new java.awt.event.KeyAdapter() { public void keyPressed(java.awt.event.KeyEvent _e) { keyPressed = _e.getKeyCode(); // System.out.println("Key = "+keyPressed); } public void keyReleased(java.awt.event.KeyEvent _e) { keyPressed = -1; } }); this.setFocusable(true); //CJB //Build the axes BuildAxesPanel(axisMode); //CJB // Set default for displayMode if(camera.is3dMode()) { visHints.setDecorationType(org.opensourcephysics.display3d.core.VisualizationHints.DECORATION_CUBE); visHints.setUseColorDepth(true); } else { visHints.setDecorationType(org.opensourcephysics.display3d.core.VisualizationHints.DECORATION_NONE); visHints.setUseColorDepth(false); } setPreferredMinMax(-1, 1, -1, 1, -1, 1); } // --------------------------------- // Begin W. Christian additions and changes // --------------------------------- /** * Performs an action for the update timer by rendering a new background image * @param evt */ public void actionPerformed(ActionEvent evt) { // render a new image if the current image is dirty if(dirtyImage||needsUpdate()) { render(); // renders the scene from within the timer thread } } public void setIgnoreRepaint(boolean ignoreRepaint) { super.setIgnoreRepaint(ignoreRepaint); glassPanel.setIgnoreRepaint(ignoreRepaint); } /** * Update the panel's buffered image from within a separate timer thread. */ private void updatePanel() { if(getIgnoreRepaint()) { return; // the animation thread will take care of the update } updateTimer.setRepeats(false); // perform only one render event updateTimer.setCoalesce(true); // coalesce render events updateTimer.start(); // start update timer } /** * Paints the component by copying the offscreen image into the graphics context. * @param g Graphics */ public void paintComponent(Graphics g) { // find the clipping rectangle within a scroll pane viewport viewRect = null; Container c = getParent(); while(c!=null) { if(c instanceof JViewport) { viewRect = ((JViewport) c).getViewRect(); glassPanel.setBounds(viewRect); glassPanelLayout.checkLayoutRect(glassPanel, viewRect); break; } c = c.getParent(); } int xoff = (getWidth()-offscreenImage.getWidth())/2; int yoff = (getHeight()-offscreenImage.getHeight())/2; g.drawImage(offscreenImage, xoff, yoff, null); // copy image to the center of the panel if(dirtyImage||needsUpdate()) { // Paco : can this be commented out? updatePanel(); // starts an update timer event } } /** * Invalidates this component. This component and all parents * above it are marked as needing to be laid out. This method can * be called often, so it needs to execute quickly. * @see #validate * @see #doLayout * @see LayoutManager * @since JDK1.0 */ public void invalidate() { needResize = true; super.invalidate(); } public BufferedImage render(BufferedImage image) { Graphics g = image.getGraphics(); paintEverything(g, image.getWidth(null), image.getHeight(null)); Rectangle viewRect = this.viewRect; // reference for thread safety if(viewRect!=null) { Rectangle r = new Rectangle(0, 0, image.getWidth(null), image.getHeight(null)); glassPanel.setBounds(r); glassPanelLayout.checkLayoutRect(glassPanel, r); glassPanel.render(g); glassPanel.setBounds(viewRect); glassPanelLayout.checkLayoutRect(glassPanel, viewRect); } else { glassPanel.render(g); } g.dispose(); // Disposes of the graphics context and releases any system resources that it is using. return image; } public BufferedImage render() { if(!isShowing()||isIconified()) { // don't render if panel cannot be seen needsToRecompute = true; // make sure we recompute later when we are showing return null; // no need to render if the frame is not visible } BufferedImage workingImage = checkImageSize(this.workingImage); synchronized(workingImage) { // do not let threads access workingImage while it is being painted if(needResize) { computeConstants(workingImage.getWidth(), workingImage.getHeight()); needResize = false; } render(workingImage); // swap the images this.workingImage = offscreenImage; // use current offscreen image for the next drawing offscreenImage = workingImage; // recently drawn image is now the offscreenImage dirtyImage = false; // offscreenImage is up to date } // the offscreenImage is now ready to be copied to the screen // always update a Swing component from the event thread if(SwingUtilities.isEventDispatchThread()) { paintImmediately(getVisibleRect()); // we are already within the event thread so DO IT! } else { // paint within the event thread Runnable doNow = new Runnable() { // runnable object will be called by invokeAndWait public void run() { paintImmediately(getVisibleRect()); } }; try { SwingUtilities.invokeAndWait(doNow); } // wait for the paint operation to finish; should be fast catch(InvocationTargetException ex) {} catch(InterruptedException ex) {} } if((vidCap!=null)&&(offscreenImage!=null)&&vidCap.isRecording()) { // buffered image should exists so use it. vidCap.addFrame(offscreenImage); } return workingImage; } /** * Whether the image is dirty or any of the elements has changed * @return boolean */ private final boolean needsUpdate() { for(Iterator<Element> it = elementList.iterator(); it.hasNext(); ) { if((it.next()).getElementChanged()) { return true; } } return false; } /** * Checks the image to see if the working image has the correct Dimension. * @return <code>true <\code> if the offscreen image matches the panel; <code>false <\code> otherwise */ private BufferedImage checkImageSize(BufferedImage image) { int width = getWidth(), height = getHeight(); if((width<=2)||(height<=2)) { // image is too small to draw anything useful return new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); } if((image==null)||(width!=image.getWidth())||(height!=image.getHeight())) { // a new image with the correct size will be created return getGraphicsConfiguration().createCompatibleImage(width, height); } return image; // given image is the correct size } /** * Gets the iconified flag from the top level frame. * @return boolean true if frame is iconified; false otherwise */ private boolean isIconified() { Component c = getTopLevelAncestor(); if(c instanceof Frame) { return(((Frame) c).getExtendedState()&Frame.ICONIFIED)==1; } return false; } // --------------------------------- // end of W. Christian additions and changes // --------------------------------- // --------------------------------- // Implementation of core.DrawingPanel3D // --------------------------------- public java.awt.Component getComponent() { return this; } public void setBackgroundImage(String _imageFile) { this.imageFile = _imageFile; } public String getBackgroundImage() { return this.imageFile; } public void setPreferredMinMax(double minX, double maxX, double minY, double maxY, double minZ, double maxZ) { this.xmin = minX; this.xmax = maxX; this.ymin = minY; this.ymax = maxY; this.zmin = minZ; this.zmax = maxZ; centerX = (xmax+xmin)/2.0; centerY = (ymax+ymin)/2.0; centerZ = (zmax+zmin)/2.0; maximumSize = getMaximum3DSize(); resetDecoration(xmax-xmin, ymax-ymin, zmax-zmin); camera.reset(); needsToRecompute = true; dirtyImage = true; } final public double getPreferredMinX() { return this.xmin; } final public double getPreferredMaxX() { return this.xmax; } final public double getPreferredMinY() { return this.ymin; } final public double getPreferredMaxY() { return this.ymax; } final public double getPreferredMinZ() { return this.zmin; } final public double getPreferredMaxZ() { return this.zmax; } final double[] getCenter() { return new double[] {centerX, centerY, centerZ}; } final double getMaximum3DSize() { double dx = xmax-xmin, dy = ymax-ymin, dz = zmax-zmin; switch(camera.getProjectionMode()) { case org.opensourcephysics.display3d.core.Camera.MODE_PLANAR_XY : return Math.max(dx, dy); case org.opensourcephysics.display3d.core.Camera.MODE_PLANAR_XZ : return Math.max(dx, dz); case org.opensourcephysics.display3d.core.Camera.MODE_PLANAR_YZ : return Math.max(dy, dz); default : return Math.max(Math.max(dx, dy), dz); /* 3D */ } } public void zoomToFit() { double minX = Double.POSITIVE_INFINITY, maxX = Double.NEGATIVE_INFINITY; double minY = Double.POSITIVE_INFINITY, maxY = Double.NEGATIVE_INFINITY; double minZ = Double.POSITIVE_INFINITY, maxZ = Double.NEGATIVE_INFINITY; double[] firstPoint = new double[3], secondPoint = new double[3]; Iterator<org.opensourcephysics.display3d.core.Element> it = getElements().iterator(); while(it.hasNext()) { ((org.opensourcephysics.display3d.simple3d.Element) it.next()).getExtrema(firstPoint, secondPoint); minX = Math.min(Math.min(minX, firstPoint[0]), secondPoint[0]); maxX = Math.max(Math.max(maxX, firstPoint[0]), secondPoint[0]); minY = Math.min(Math.min(minY, firstPoint[1]), secondPoint[1]); maxY = Math.max(Math.max(maxY, firstPoint[1]), secondPoint[1]); minZ = Math.min(Math.min(minZ, firstPoint[2]), secondPoint[2]); maxZ = Math.max(Math.max(maxZ, firstPoint[2]), secondPoint[2]); } double max = Math.max(Math.max(maxX-minX, maxY-minY), maxZ-minZ); if(max==0.0) { max = 2; } if(minX>=maxX) { minX = maxX-max/2; maxX = minX+max; } if(minY>=maxY) { minY = maxY-max/2; maxY = minY+max; } if(minZ>=maxZ) { minZ = maxZ-max/2; maxZ = minZ+max; } setPreferredMinMax(minX, maxX, minY, maxY, minZ, maxZ); } public void setSquareAspect(boolean square) { // added by W. Christian if(squareAspect!=square) { // only recompute if there is a change needsToRecompute = true; updatePanel(); } squareAspect = square; // computeConstants(); // removed by W. Christian } public boolean isSquareAspect() { return squareAspect; } public org.opensourcephysics.display3d.core.VisualizationHints getVisualizationHints() { return visHints; } public org.opensourcephysics.display3d.core.Camera getCamera() { return camera; } /** * Gets the video capture tool. May be null. * * @return the video capture tool */ public VideoTool getVideoTool() { return vidCap; } /** * Sets the video capture tool. May be set to null. * * @param videoCap the video capture tool */ public void setVideoTool(VideoTool videoCap) { if(vidCap!=null) { vidCap.setVisible(false); // hide the current video capture tool } vidCap = videoCap; } public void addElement(org.opensourcephysics.display3d.core.Element element) { if(!(element instanceof org.opensourcephysics.display3d.simple3d.Element)) { throw new UnsupportedOperationException("Can't add element to panel (incorrect implementation)"); //$NON-NLS-1$ } if(!elementList.contains(element)) { elementList.add((org.opensourcephysics.display3d.simple3d.Element) element); } //CJB //Scale factor switch(DrawingPanel3D.axisMode) { case org.opensourcephysics.display3d.core.DrawingPanel3D.MODE_XZY : ((Element) element).setSizeX(((Element) element).getSizeX()*this.factorX); ((Element) element).setSizeZ(((Element) element).getSizeY()*this.factorZ); ((Element) element).setSizeY(((Element) element).getSizeZ()*this.factorY); ((Element) element).setX(((Element) element).getX()*this.factorX); ((Element) element).setZ(((Element) element).getY()*this.factorZ); ((Element) element).setY(((Element) element).getZ()*this.factorY); break; case org.opensourcephysics.display3d.core.DrawingPanel3D.MODE_YXZ : ((Element) element).setSizeY(((Element) element).getSizeX()*this.factorY); ((Element) element).setSizeX(((Element) element).getSizeY()*this.factorX); ((Element) element).setSizeZ(((Element) element).getSizeZ()*this.factorZ); ((Element) element).setY(((Element) element).getX()*this.factorY); ((Element) element).setX(((Element) element).getY()*this.factorX); ((Element) element).setZ(((Element) element).getZ()*this.factorZ); break; case org.opensourcephysics.display3d.core.DrawingPanel3D.MODE_YZX : ((Element) element).setSizeZ(((Element) element).getSizeX()*this.factorZ); ((Element) element).setSizeX(((Element) element).getSizeY()*this.factorX); ((Element) element).setSizeY(((Element) element).getSizeZ()*this.factorY); ((Element) element).setZ(((Element) element).getX()*this.factorZ); ((Element) element).setX(((Element) element).getY()*this.factorX); ((Element) element).setY(((Element) element).getZ()*this.factorY); break; case org.opensourcephysics.display3d.core.DrawingPanel3D.MODE_ZXY : ((Element) element).setSizeY(((Element) element).getSizeX()*this.factorY); ((Element) element).setSizeZ(((Element) element).getSizeY()*this.factorZ); ((Element) element).setSizeX(((Element) element).getSizeZ()*this.factorX); ((Element) element).setY(((Element) element).getX()*this.factorY); ((Element) element).setZ(((Element) element).getY()*this.factorZ); ((Element) element).setX(((Element) element).getZ()*this.factorX); break; case org.opensourcephysics.display3d.core.DrawingPanel3D.MODE_ZYX : ((Element) element).setSizeZ(((Element) element).getSizeX()*this.factorZ); ((Element) element).setSizeY(((Element) element).getSizeY()*this.factorY); ((Element) element).setSizeX(((Element) element).getSizeZ()*this.factorX); ((Element) element).setZ(((Element) element).getX()*this.factorZ); ((Element) element).setY(((Element) element).getY()*this.factorY); ((Element) element).setX(((Element) element).getZ()*this.factorX); break; default : ((Element) element).setSizeX(((Element) element).getSizeX()*this.factorX); ((Element) element).setSizeY(((Element) element).getSizeY()*this.factorY); ((Element) element).setSizeZ(((Element) element).getSizeZ()*this.factorZ); ((Element) element).setX(((Element) element).getX()*this.factorX); ((Element) element).setY(((Element) element).getY()*this.factorY); ((Element) element).setZ(((Element) element).getZ()*this.factorZ); //CJB } ((Element) element).setPanel(this); dirtyImage = true; // element has been added so image is dirty } public void removeElement(org.opensourcephysics.display3d.core.Element element) { elementList.remove(element); dirtyImage = true; // element has been added so image is dirty } public void removeAllElements() { elementList.clear(); dirtyImage = true; // element has been added so image is dirty } public synchronized java.util.List<org.opensourcephysics.display3d.core.Element> getElements() { return new ArrayList<org.opensourcephysics.display3d.core.Element>(elementList); } //CJB public void setScaleFactor(double factorX, double factorY, double factorZ) { this.factorX = factorX; this.factorY = factorY; this.factorZ = factorZ; } public double getScaleFactorX() { return factorX; } public double getScaleFactorY() { return factorY; } public double getScaleFactorZ() { return factorZ; } public void setAxesMode(int mode) { BuildAxesPanel(mode); for(int i = 0; i<elementList.size(); i++) { Element el = elementList.get(i); el.setXYZ(el.getX(), el.getY(), el.getZ()); el.setSizeXYZ(el.getSizeX(), el.getSizeY(), el.getSizeZ()); } } public int getAxesMode() { return axisMode; } //CJB /** * Shows a message in a yellow text box in the lower right hand corner. * * @param msg */ public void setMessage(String msg) { brMessageBox.setText(msg); // the default message box } /** * Shows a message in a yellow text box. * The location must be one of the following: * <ul> * <li> DrawingPanel3D.BOTTOM_LEFT; * <li> DrawingPanel3D.BOTTOM_RIGHT; * <li> DrawingPanel3D.TOP_RIGHT; * <li> DrawingPanel3D.TOP_LEFT; * </ul> * @param msg * @param location */ public void setMessage(String msg, int location) { switch(location) { case BOTTOM_LEFT : blMessageBox.setText(msg); break; default : case BOTTOM_RIGHT : brMessageBox.setText(msg); break; case TOP_RIGHT : trMessageBox.setText(msg); break; case TOP_LEFT : tlMessageBox.setText(msg); break; } } // --------------------------------- // Implementation of core.InteractionSource // --------------------------------- public org.opensourcephysics.display3d.core.interaction.InteractionTarget getInteractionTarget(int target) { return myTarget; } public void addInteractionListener(InteractionListener listener) { if((listener==null)||listeners.contains(listener)) { return; } listeners.add(listener); } public void removeInteractionListener(InteractionListener listener) { listeners.remove(listener); } /** * Invokes the interactionPerformed() method of all registered * interaction listeners * @param event InteractionEvent */ private void invokeActions(InteractionEvent event) { Iterator<InteractionListener> it = listeners.iterator(); while(it.hasNext()) { it.next().interactionPerformed(event); } } // ---------------------------------------------------- // All the painting stuff // ---------------------------------------------------- /** * Paints everyting assuming an object of the given width and height in pixels. * @param g Graphics * @param width int * @param height int */ private synchronized void paintEverything(Graphics g, int width, int height) { // W. Christian recompute scale if something has changed if(needsToRecompute||(width!=getWidth())||(height!=getHeight())) { computeConstants(width, height); } java.util.List<org.opensourcephysics.display3d.core.Element> tempList = getElements(); tempList.addAll(decorationList); g.setColor(getBackground()); g.fillRect(0, 0, width, height); // fill the component with the background color paintDrawableList(g, tempList); } private void paintDrawableList(Graphics g, java.util.List<org.opensourcephysics.display3d.core.Element> tempList) { Graphics2D g2 = (Graphics2D) g; Iterator<org.opensourcephysics.display3d.core.Element> it = tempList.iterator(); if(quickRedrawOn||!visHints.isRemoveHiddenLines()) { // Do a quick sketch of the scene while(it.hasNext()) { ((Element) it.next()).drawQuickly(g2); } return; } // Collect objects, sort and draw them one by one. Takes time!!! list3D.clear(); while(it.hasNext()) { // Collect all Objects3D Object3D[] objects = ((Element) it.next()).getObjects3D(); if(objects==null) { continue; } for(int i = 0, n = objects.length; i<n; i++) { // providing NaN as distance can be used by Drawables3D to hide a given Object3D if(!Double.isNaN(objects[i].getDistance())) { list3D.add(objects[i]); } } } if(list3D.size()<=0) { return; } Object3D[] objects = list3D.toArray(new Object3D[0]); Arrays.sort(objects, comparator); for(int i = 0, n = objects.length; i<n; i++) { Object3D obj = objects[i]; obj.getElement().draw(g2, obj.getIndex()); } } // ---------------------------------------------------- // Printable interface // ---------------------------------------------------- public int print(Graphics g, PageFormat pageFormat, int pageIndex) throws PrinterException { if(pageIndex>=1) { return Printable.NO_SUCH_PAGE; } if(g==null) { return Printable.NO_SUCH_PAGE; } Graphics2D g2 = (Graphics2D) g; double scalex = pageFormat.getImageableWidth()/getWidth(); double scaley = pageFormat.getImageableHeight()/getHeight(); double scale = Math.min(scalex, scaley); g2.translate((int) pageFormat.getImageableX(), (int) pageFormat.getImageableY()); g2.scale(scale, scale); paintEverything(g2, getWidth(), getHeight()); return Printable.PAGE_EXISTS; } // ---------------------------------------------------- // Projection, package and private methods // ---------------------------------------------------- /** * This will be called by VisualizationHints whenever hints change. * @see VisualizationHints */ void hintChanged(int hintThatChanged) { switch(hintThatChanged) { case VisualizationHints.HINT_ANY : { String[] labels = visHints.getAxesLabels(); xText.setText(labels[0]); yText.setText(labels[1]); zText.setText(labels[2]); setCursorMode(); } // do not break! case VisualizationHints.HINT_DECORATION_TYPE : switch(visHints.getDecorationType()) { case org.opensourcephysics.display3d.core.VisualizationHints.DECORATION_NONE : for(int i = 0, n = boxSides.length; i<n; i++) { boxSides[i].setVisible(false); } xAxis.setVisible(false); yAxis.setVisible(false); zAxis.setVisible(false); xText.setVisible(false); yText.setVisible(false); zText.setVisible(false); break; case org.opensourcephysics.display3d.core.VisualizationHints.DECORATION_CUBE : for(int i = 0, n = boxSides.length; i<n; i++) { boxSides[i].setVisible(true); } xAxis.setVisible(false); yAxis.setVisible(false); zAxis.setVisible(false); xText.setVisible(false); yText.setVisible(false); zText.setVisible(false); break; case org.opensourcephysics.display3d.core.VisualizationHints.DECORATION_AXES : for(int i = 0, n = boxSides.length; i<n; i++) { boxSides[i].setVisible(false); } xAxis.setVisible(true); yAxis.setVisible(true); zAxis.setVisible(true); xText.setVisible(true); yText.setVisible(true); zText.setVisible(true); break; } break; case VisualizationHints.HINT_AXES_LABELS : String[] labels = visHints.getAxesLabels(); xText.setText(labels[0]); yText.setText(labels[1]); zText.setText(labels[2]); break; case VisualizationHints.HINT_CURSOR_TYPE : setCursorMode(); break; case VisualizationHints.HINT_SHOW_COORDINATES : break; // Actually no dirtyImage is needed... } dirtyImage = true; // hint has changed so image is dirtry } /** * This will be called by Camera whenever it changes. * @see Camera */ void cameraChanged(int howItChanged) { // System.out.println ("Camera mode is now "+camera.getProjectionMode()); switch(howItChanged) { case Camera.CHANGE_MODE : double dx = xmax-xmin, dy = ymax-ymin, dz = zmax-zmin; maximumSize = getMaximum3DSize(); resetDecoration(dx, dy, dz); // W. Christian constants should be computed when everything is painted // computeConstants(); needsToRecompute = true; updatePanel(); // always update if we change display mode break; } reportTheNeedToProject(); dirtyImage = true; // camera has changed so image is dirtry } /** * Converts a 3D point of the scene into a 2D point of the screen. * It also provides a number measuring the relative distance of the point * to the camera. * distance = 1.0 means at the center of the scene, * distance > 1.0 means farther than the center of the scene, * distance < 1.0 means closer than the center of the scene, * @param coordinate The coordinates of the point of the scene * The input coordinates are not modified. * @param pixel A place-holder for the coordinates of the point of the screen. * It returns a,b and the distance * @return The coordinates of the point of the screen and a number * which reports about the distance to us */ double[] project(double[] p, double[] pixel) { double[] projected = camera.getTransformation().direct(p.clone()); double factor = 1.8; switch(camera.getProjectionMode()) { case org.opensourcephysics.display3d.core.Camera.MODE_NO_PERSPECTIVE : case org.opensourcephysics.display3d.core.Camera.MODE_PERSPECTIVE_OFF : factor = 1.3; break; case org.opensourcephysics.display3d.core.Camera.MODE_PERSPECTIVE : case org.opensourcephysics.display3d.core.Camera.MODE_PERSPECTIVE_ON : factor = 1; break; } pixel[0] = acenter+projected[0]*factor*aconstant; pixel[1] = bcenter-projected[1]*factor*bconstant; pixel[2] = projected[2]; return pixel; } /** * Converts a world size at a given point into a size in the screen * @param p double[] The coordinates of the point at which the 3D * size was measured. * @param size double[] The size in the X,Y,Z coordinates * @param pixelSize double[] A place-holder for the result * @return double[] returns the same input pixelSize */ double[] projectSize(double[] p, double[] size, double[] pixelSize) { camera.projectSize(p, size, pixelSize); double factor = 1.8; switch(camera.getProjectionMode()) { case org.opensourcephysics.display3d.core.Camera.MODE_NO_PERSPECTIVE : case org.opensourcephysics.display3d.core.Camera.MODE_PERSPECTIVE_OFF : factor = 1.3; break; case org.opensourcephysics.display3d.core.Camera.MODE_PERSPECTIVE : case org.opensourcephysics.display3d.core.Camera.MODE_PERSPECTIVE_ON : factor = 1; break; } pixelSize[0] *= factor*aconstant; pixelSize[1] *= factor*bconstant; return pixelSize; } /** * Computes the display color of a given drawable3D based on its original color and its depth. * Transparency of the original color is not affected. * @param _aColor the original color * @param _depth the depth value of the color */ Color projectColor(Color _aColor, double _depth) { if(!visHints.isUseColorDepth()) { return _aColor; } // if (_depth<0.9) return _aColor.brighter().brighter(); // else if (_depth>1.1) return _aColor.darker().darker(); // else return _aColor; float[] crc = new float[4]; // Stands for ColorRGBComponent try { _aColor.getRGBComponents(crc); // Do not affect transparency for(int i = 0; i<3; i++) { crc[i] /= _depth; crc[i] = (float) Math.max(Math.min(crc[i], 1.0), 0.0); } return new Color(crc[0], crc[1], crc[2], crc[3]); } catch(Exception _exc) { return _aColor; } } /** * Converts a point on the screen into a world point * It only works properly for planar display modes */ private double[] worldPoint(int a, int b) { double factor = 1.8; switch(camera.getProjectionMode()) { case org.opensourcephysics.display3d.core.Camera.MODE_PLANAR_XY : return new double[] {centerX+(a-acenter)/(factor*aconstant), centerY+(bcenter-b)/(factor*bconstant), zmax}; case org.opensourcephysics.display3d.core.Camera.MODE_PLANAR_XZ : return new double[] {centerX+(a-acenter)/(factor*aconstant), ymax, centerZ+(bcenter-b)/(factor*bconstant)}; case org.opensourcephysics.display3d.core.Camera.MODE_PLANAR_YZ : return new double[] {xmax, centerY+(a-acenter)/(factor*aconstant), centerZ+(bcenter-b)/(factor*bconstant)}; default : /* 3D */ return new double[] {centerX, centerY, centerZ}; } } /** * Converts into a world distance a distance on the screen * It only works properly for planar display modes */ private double[] worldDistance(int dx, int dy) { double factor = 1.8; switch(camera.getProjectionMode()) { case org.opensourcephysics.display3d.core.Camera.MODE_PLANAR_XY : return new double[] {dx/(factor*aconstant), -dy/(factor*bconstant), 0.0}; case org.opensourcephysics.display3d.core.Camera.MODE_PLANAR_XZ : return new double[] {dx/(factor*aconstant), 0.0, -dy/(factor*bconstant)}; case org.opensourcephysics.display3d.core.Camera.MODE_PLANAR_YZ : return new double[] {0.0, dx/(factor*aconstant), -dy/(factor*bconstant)}; default : /* 3D */ return new double[] {dx/(1.3*aconstant), dy/(1.3*bconstant), 0.0}; } } /** * Computes the constants for the given size in pixels. * @param width int * @param height int */ private void computeConstants(int width, int height) { acenter = width/2; bcenter = height/2; if(squareAspect) { width = height = Math.min(width, height); } aconstant = 0.5*width/maximumSize; bconstant = 0.5*height/maximumSize; reportTheNeedToProject(); needsToRecompute = false; } private void reportTheNeedToProject() { Iterator<org.opensourcephysics.display3d.core.Element> it = getElements().iterator(); while(it.hasNext()) { ((Element) it.next()).setNeedToProject(true); } it = new ArrayList<org.opensourcephysics.display3d.core.Element>(decorationList).iterator(); while(it.hasNext()) { ((Element) it.next()).setNeedToProject(true); } } private void resetDecoration(double _dx, double _dy, double _dz) { boxSides[0].setXYZ(xmin, ymin, zmin); boxSides[0].setSizeXYZ(_dx, 0.0, 0.0); boxSides[1].setXYZ(xmax, ymin, zmin); boxSides[1].setSizeXYZ(0.0, _dy, 0.0); boxSides[2].setXYZ(xmin, ymax, zmin); boxSides[2].setSizeXYZ(_dx, 0.0, 0.0); boxSides[3].setXYZ(xmin, ymin, zmin); boxSides[3].setSizeXYZ(0.0, _dy, 0.0); boxSides[4].setXYZ(xmin, ymin, zmax); boxSides[4].setSizeXYZ(_dx, 0.0, 0.0); boxSides[5].setXYZ(xmax, ymin, zmax); boxSides[5].setSizeXYZ(0.0, _dy, 0.0); boxSides[6].setXYZ(xmin, ymax, zmax); boxSides[6].setSizeXYZ(_dx, 0.0, 0.0); boxSides[7].setXYZ(xmin, ymin, zmax); boxSides[7].setSizeXYZ(0.0, _dy, 0.0); boxSides[8].setXYZ(xmin, ymin, zmin); boxSides[8].setSizeXYZ(0.0, 0.0, _dz); boxSides[9].setXYZ(xmax, ymin, zmin); boxSides[9].setSizeXYZ(0.0, 0.0, _dz); boxSides[10].setXYZ(xmax, ymax, zmin); boxSides[10].setSizeXYZ(0.0, 0.0, _dz); boxSides[11].setXYZ(xmin, ymax, zmin); boxSides[11].setSizeXYZ(0.0, 0.0, _dz); xAxis.setXYZ(xmin, ymin, zmin); xAxis.setSizeXYZ(_dx, 0.0, 0.0); xText.setXYZ(xmax+_dx*0.02, ymin, zmin); yAxis.setXYZ(xmin, ymin, zmin); yAxis.setSizeXYZ(0.0, _dy, 0.0); yText.setXYZ(xmin, ymax+_dy*0.02, zmin); zAxis.setXYZ(xmin, ymin, zmin); zAxis.setSizeXYZ(0.0, 0.0, _dz); zText.setXYZ(xmin, ymin, zmax+_dz*0.02); } // ---------------------------------------------------- // Private methods for the cursor // ---------------------------------------------------- private void setCursorMode() { switch(visHints.getCursorType()) { case org.opensourcephysics.display3d.core.VisualizationHints.CURSOR_NONE : trackersVisible = 0; break; case org.opensourcephysics.display3d.core.VisualizationHints.CURSOR_CUBE : trackersVisible = 9; break; default : case org.opensourcephysics.display3d.core.VisualizationHints.CURSOR_XYZ : trackersVisible = 3; break; case org.opensourcephysics.display3d.core.VisualizationHints.CURSOR_CROSSHAIR : trackersVisible = 3; break; } } private void showTrackers(boolean value) { for(int i = 0, n = trackerLines.length; i<n; i++) { if(i<trackersVisible) { trackerLines[i].setVisible(value); } else { trackerLines[i].setVisible(false); } } } private void positionTrackers() { switch(visHints.getCursorType()) { case org.opensourcephysics.display3d.core.VisualizationHints.CURSOR_NONE : return; default : case org.opensourcephysics.display3d.core.VisualizationHints.CURSOR_XYZ : trackerLines[0].setXYZ(trackerPoint[0], ymin, zmin); trackerLines[0].setSizeXYZ(0, trackerPoint[1]-ymin, 0); trackerLines[1].setXYZ(xmin, trackerPoint[1], zmin); trackerLines[1].setSizeXYZ(trackerPoint[0]-xmin, 0, 0); trackerLines[2].setXYZ(trackerPoint[0], trackerPoint[1], zmin); trackerLines[2].setSizeXYZ(0, 0, trackerPoint[2]-zmin); break; case org.opensourcephysics.display3d.core.VisualizationHints.CURSOR_CUBE : trackerLines[0].setXYZ(xmin, trackerPoint[1], trackerPoint[2]); trackerLines[0].setSizeXYZ(trackerPoint[0]-xmin, 0, 0); trackerLines[1].setXYZ(trackerPoint[0], ymin, trackerPoint[2]); trackerLines[1].setSizeXYZ(0, trackerPoint[1]-ymin, 0); trackerLines[2].setXYZ(trackerPoint[0], trackerPoint[1], zmin); trackerLines[2].setSizeXYZ(0, 0, trackerPoint[2]-zmin); trackerLines[3].setXYZ(trackerPoint[0], ymin, zmin); trackerLines[3].setSizeXYZ(0, trackerPoint[1]-ymin, 0); trackerLines[4].setXYZ(xmin, trackerPoint[1], zmin); trackerLines[4].setSizeXYZ(trackerPoint[0]-xmin, 0, 0); trackerLines[5].setXYZ(trackerPoint[0], ymin, zmin); trackerLines[5].setSizeXYZ(0, 0, trackerPoint[2]-zmin); trackerLines[6].setXYZ(xmin, ymin, trackerPoint[2]); trackerLines[6].setSizeXYZ(trackerPoint[0]-xmin, 0, 0); trackerLines[7].setXYZ(xmin, trackerPoint[1], zmin); trackerLines[7].setSizeXYZ(0, 0, trackerPoint[2]-zmin); trackerLines[8].setXYZ(xmin, ymin, trackerPoint[2]); trackerLines[8].setSizeXYZ(0, trackerPoint[1]-ymin, 0); break; case org.opensourcephysics.display3d.core.VisualizationHints.CURSOR_CROSSHAIR : trackerLines[0].setXYZ(xmin, trackerPoint[1], trackerPoint[2]); trackerLines[0].setSizeXYZ(xmax-xmin, 0.0, 0.0); trackerLines[1].setXYZ(trackerPoint[0], ymin, trackerPoint[2]); trackerLines[1].setSizeXYZ(0.0, ymax-ymin, 0.0); trackerLines[2].setXYZ(trackerPoint[0], trackerPoint[1], zmin); trackerLines[2].setSizeXYZ(0.0, 0.0, zmax-zmin); break; } } // ---------------------------------------------------- // Interaction // ---------------------------------------------------- private InteractionTarget getTargetHit(int x, int y) { Iterator<org.opensourcephysics.display3d.core.Element> it = getElements().iterator(); InteractionTarget target = null; while(it.hasNext()) { target = ((Element) it.next()).getTargetHit(x, y); if(target!=null) { return target; } } return null; } private void setMouseCursor(Cursor cursor) { Container c = getTopLevelAncestor(); setCursor(cursor); if(c!=null) { c.setCursor(cursor); } } private void displayPosition(double[] _point) { visHints.displayPosition(camera.getProjectionMode(), _point); } // returns true if the tracker was moved private boolean mouseDraggedComputations(java.awt.event.MouseEvent e) { if(e.isControlDown()) { // Panning if(camera.is3dMode()) { double fx = camera.getFocusX(), fy = camera.getFocusY(), fz = camera.getFocusZ(); double dx = (e.getX()-lastX)*maximumSize*0.01, dy = (e.getY()-lastY)*maximumSize*0.01; switch(keyPressed) { case 88 : // X is pressed if((camera.cosAlpha>=0)&&(Math.abs(camera.sinAlpha)<camera.cosAlpha)) { camera.setFocusXYZ(fx+dy, fy, fz); } else if((camera.sinAlpha>=0)&&(Math.abs(camera.cosAlpha)<camera.sinAlpha)) { camera.setFocusXYZ(fx+dx, fy, fz); } else if((camera.cosAlpha<0)&&(Math.abs(camera.sinAlpha)<-camera.cosAlpha)) { camera.setFocusXYZ(fx-dy, fy, fz); } else { camera.setFocusXYZ(fx-dx, fy, fz); } break; case 89 : // Y is pressed if((camera.cosAlpha>=0)&&(Math.abs(camera.sinAlpha)<camera.cosAlpha)) { camera.setFocusXYZ(fx, fy-dx, fz); } else if((camera.sinAlpha>=0)&&(Math.abs(camera.cosAlpha)<camera.sinAlpha)) { camera.setFocusXYZ(fx, fy+dy, fz); } else if((camera.cosAlpha<0)&&(Math.abs(camera.sinAlpha)<-camera.cosAlpha)) { camera.setFocusXYZ(fx, fy+dx, fz); } else { camera.setFocusXYZ(fx, fy-dy, fz); } break; case 90 : // Z is pressed if(camera.cosBeta>=0) { camera.setFocusXYZ(fx, fy, fz+dy); } else { camera.setFocusXYZ(fx, fy, fz-dy); } break; default : if(camera.cosBeta<0) { dy = -dy; } if((camera.cosAlpha>=0)&&(Math.abs(camera.sinAlpha)<camera.cosAlpha)) { camera.setFocusXYZ(fx, fy-dx, fz+dy); } else if((camera.sinAlpha>=0)&&(Math.abs(camera.cosAlpha)<camera.sinAlpha)) { camera.setFocusXYZ(fx+dx, fy, fz+dy); } else if((camera.cosAlpha<0)&&(Math.abs(camera.sinAlpha)<-camera.cosAlpha)) { camera.setFocusXYZ(fx, fy+dx, fz-dy); } else { camera.setFocusXYZ(fx-dx, fy, fz-dy); } break; } } return false; } // End of panning if(e.isShiftDown()) { // Zooming camera.setDistanceToScreen(camera.getDistanceToScreen()-(e.getY()-lastY)*maximumSize*0.01); return false; } if(camera.is3dMode()&&(targetHit==null)&&!e.isAltDown()) { // Rotating (in 3D) camera.setAzimuthAndAltitude(camera.getAzimuth()-(e.getX()-lastX)*0.01, camera.getAltitude()+(e.getY()-lastY)*0.005); return false; } if(trackerPoint==null) { return true; } // In all other cases, you are moving the tracker double[] point = worldDistance(e.getX()-lastX, e.getY()-lastY); if(!camera.is3dMode()) { // 2D modes switch(keyPressed) { case 88 : trackerPoint[0] += point[0]; break; // X is pressed case 89 : trackerPoint[1] += point[1]; break; // Y is pressed case 90 : trackerPoint[2] += point[2]; break; // Z is pressed default : trackerPoint[0] += point[0]; trackerPoint[1] += point[1]; trackerPoint[2] += point[2]; break; // No key is pressed } } // End of 2D modes else { switch(keyPressed) { case 88 : // X is pressed if((camera.cosAlpha>=0)&&(Math.abs(camera.sinAlpha)<camera.cosAlpha)) { trackerPoint[0] += point[1]; } else if((camera.sinAlpha>=0)&&(Math.abs(camera.cosAlpha)<camera.sinAlpha)) { trackerPoint[0] -= point[0]; } else if((camera.cosAlpha<0)&&(Math.abs(camera.sinAlpha)<-camera.cosAlpha)) { trackerPoint[0] -= point[1]; } else { trackerPoint[0] += point[0]; } break; case 89 : // Y is pressed if((camera.cosAlpha>=0)&&(Math.abs(camera.sinAlpha)<camera.cosAlpha)) { trackerPoint[1] += point[0]; } else if((camera.sinAlpha>=0)&&(Math.abs(camera.cosAlpha)<camera.sinAlpha)) { trackerPoint[1] += point[1]; } else if((camera.cosAlpha<0)&&(Math.abs(camera.sinAlpha)<-camera.cosAlpha)) { trackerPoint[1] -= point[0]; } else { trackerPoint[1] -= point[1]; } break; case 90 : // Z is pressed if(camera.cosBeta>=0) { trackerPoint[2] -= point[1]; } else { trackerPoint[2] -= point[2]; } break; default : // No key is pressed if(camera.cosBeta>=0) { trackerPoint[2] -= point[1]; } else { trackerPoint[2] += point[1]; } if((camera.cosAlpha>=0)&&(Math.abs(camera.sinAlpha)<camera.cosAlpha)) { trackerPoint[1] += point[0]; } else if((camera.sinAlpha>=0)&&(Math.abs(camera.cosAlpha)<camera.sinAlpha)) { trackerPoint[0] -= point[0]; } else if((camera.cosAlpha<0)&&(Math.abs(camera.sinAlpha)<-camera.cosAlpha)) { trackerPoint[1] -= point[0]; } else { trackerPoint[0] += point[0]; } break; } } // End of 3D modes return true; } private void resetInteraction() { targetHit = null; showTrackers(false); displayPosition(null); // blMessageBox.setText(null); // repaint(); removed by W. Christian dirtyImage = true; updatePanel(); } /** * The inner class that will handle all mouse related events. */ private class IADMouseController extends MouseInputAdapter { public void mousePressed(MouseEvent _evt) { requestFocus(); if(_evt.isPopupTrigger()||(_evt.getModifiers()==InputEvent.BUTTON3_MASK)) { return; } // quickRedrawOn = visHints.isAllowQuickRedraw() || keyPressed==83; // 's' is pressed /* if(visHints.isAllowQuickRedraw()&&((_evt.getModifiers()&InputEvent.BUTTON1_MASK)!=0)) { quickRedrawOn = true; } else { quickRedrawOn = false; } */ lastX = _evt.getX(); lastY = _evt.getY(); targetHit = getTargetHit(lastX, lastY); if(targetHit!=null) { Element el = targetHit.getElement(); trackerPoint = el.getHotSpot(targetHit); el.invokeActions(new InteractionEvent(el, InteractionEvent.MOUSE_PRESSED, targetHit.getActionCommand(), targetHit, _evt)); trackerPoint = el.getHotSpot(targetHit); // because the listener may change the position of the element } else if(myTarget.isEnabled()) { // No interactive has been hit if((!camera.is3dMode())||_evt.isAltDown()) { // In 2D by default, in 3D only if you hold ALT down // You are trying to track a given point trackerPoint = worldPoint(_evt.getX(), _evt.getY()); invokeActions(new InteractionEvent(DrawingPanel3D.this, InteractionEvent.MOUSE_PRESSED, myTarget.getActionCommand(), trackerPoint, _evt)); } else { invokeActions(new InteractionEvent(DrawingPanel3D.this, InteractionEvent.MOUSE_PRESSED, myTarget.getActionCommand(), null, _evt)); resetInteraction(); return; } } else { resetInteraction(); return; } displayPosition(trackerPoint); positionTrackers(); showTrackers(true); // repaint(); removed by W. Christian dirtyImage = true; updatePanel(); } public void mouseReleased(MouseEvent _evt) { if(_evt.isPopupTrigger()||(_evt.getModifiers()==InputEvent.BUTTON3_MASK)) { return; } if(targetHit!=null) { Element el = targetHit.getElement(); el.invokeActions(new InteractionEvent(el, InteractionEvent.MOUSE_RELEASED, targetHit.getActionCommand(), targetHit, _evt)); } else if(myTarget.isEnabled()) { if((!camera.is3dMode())||_evt.isAltDown()) { // In 2D by default, in 3D only if you hold ALT down invokeActions(new InteractionEvent(DrawingPanel3D.this, InteractionEvent.MOUSE_RELEASED, myTarget.getActionCommand(), trackerPoint, _evt)); } else { invokeActions(new InteractionEvent(DrawingPanel3D.this, InteractionEvent.MOUSE_RELEASED, myTarget.getActionCommand(), null, _evt)); } } quickRedrawOn = false; resetInteraction(); // setMouseCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); } public void mouseDragged(MouseEvent _evt) { if(_evt.isPopupTrigger()||(_evt.getModifiers()==InputEvent.BUTTON3_MASK)) { return; } quickRedrawOn = visHints.isAllowQuickRedraw()&&(keyPressed!=83); boolean trackerMoved = mouseDraggedComputations(_evt); lastX = _evt.getX(); lastY = _evt.getY(); if(!trackerMoved) { // Report any listener that the projection has changed. Data is NULL! invokeActions(new InteractionEvent(DrawingPanel3D.this, InteractionEvent.MOUSE_DRAGGED, myTarget.getActionCommand(), null, _evt)); resetInteraction(); return; } if(targetHit!=null) { Element el = targetHit.getElement(); el.updateHotSpot(targetHit, trackerPoint); el.invokeActions(new InteractionEvent(el, InteractionEvent.MOUSE_DRAGGED, targetHit.getActionCommand(), targetHit, _evt)); trackerPoint = el.getHotSpot(targetHit); // The listener may change the position of the element displayPosition(trackerPoint); positionTrackers(); showTrackers(true); // should trackers appear only in 3D mode? } else if(myTarget.isEnabled()) { invokeActions(new InteractionEvent(DrawingPanel3D.this, InteractionEvent.MOUSE_DRAGGED, myTarget.getActionCommand(), trackerPoint, _evt)); displayPosition(trackerPoint); positionTrackers(); showTrackers(true); // should trackers appear only in 3D mode? } // repaint(); removed by W. Christian dirtyImage = true; updatePanel(); } public void mouseEntered(MouseEvent _evt) { setMouseCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); if(myTarget.isEnabled()) { invokeActions(new InteractionEvent(DrawingPanel3D.this, InteractionEvent.MOUSE_ENTERED, myTarget.getActionCommand(), null, _evt)); } targetHit = targetEntered = null; } public void mouseExited(MouseEvent _evt) { setMouseCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); if(myTarget.isEnabled()) { invokeActions(new InteractionEvent(DrawingPanel3D.this, InteractionEvent.MOUSE_EXITED, myTarget.getActionCommand(), null, _evt)); } targetHit = targetEntered = null; } public void mouseClicked(MouseEvent _evt) {} public void mouseMoved(MouseEvent _evt) { InteractionTarget target = getTargetHit(_evt.getX(), _evt.getY()); if(target!=null) { if(targetEntered==null) { target.getElement().invokeActions(new InteractionEvent(target.getElement(), InteractionEvent.MOUSE_ENTERED, target.getActionCommand(), target, _evt)); } setMouseCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } else { // No target under the cursor if(targetEntered!=null) { targetEntered.getElement().invokeActions(new InteractionEvent(targetEntered.getElement(), InteractionEvent.MOUSE_EXITED, targetEntered.getActionCommand(), targetEntered, _evt)); } else if(myTarget.isEnabled()) { invokeActions(new InteractionEvent(DrawingPanel3D.this, InteractionEvent.MOUSE_MOVED, myTarget.getActionCommand(), null, _evt)); } setMouseCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); } targetEntered = target; } } private class GlassPanel extends javax.swing.JPanel { public void render(Graphics g) { Component[] c = glassPanelLayout.getComponents(); for(int i = 0, n = c.length; i<n; i++) { if(c[i]==null) { continue; } g.translate(c[i].getX(), c[i].getY()); c[i].print(g); g.translate(-c[i].getX(), -c[i].getY()); } } } // ---------------------------------------------------- // Lights // ---------------------------------------------------- public void setLightEnabled(boolean _state, int nlight) {} // simple3d supports no light control // ---------------------------------------------------- // XML loader // ---------------------------------------------------- /** * Returns an XML.ObjectLoader to save and load object data. * @return the XML.ObjectLoader */ public static XML.ObjectLoader getLoader() { return new DrawingPanel3DLoader(); } static private class DrawingPanel3DLoader extends org.opensourcephysics.display3d.core.DrawingPanel3D.Loader { public Object createObject(XMLControl control) { return new DrawingPanel3D(); } public Object loadObject(XMLControl control, Object obj) { super.loadObject(control, obj); DrawingPanel3D panel = (DrawingPanel3D) obj; // Load the visualization hints VisualizationHints hints = (VisualizationHints) control.getObject("visualization hints"); //$NON-NLS-1$ hints.setPanel(panel); panel.visHints = hints; panel.hintChanged(VisualizationHints.HINT_DECORATION_TYPE); // Load the camera Camera cam = (Camera) control.getObject("camera"); //$NON-NLS-1$ cam.setPanel(panel); panel.camera = cam; panel.cameraChanged(Camera.CHANGE_ANY); // Order a render() panel.needsToRecompute = true; panel.dirtyImage = true; // new data so image is dirtry panel.updatePanel(); return obj; } } // End of static class DrawingPanel3DLoader } /* * Open Source Physics software is free software; you can redistribute * it and/or modify it under the terms of the GNU General Public License (GPL) as * published by the Free Software Foundation; either version 2 of the License, * or(at your option) any later version. * * Code that uses any portion of the code in the org.opensourcephysics package * or any subpackage (subdirectory) of this package must must also be be released * under the GNU GPL license. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA * or view the license online at http://www.gnu.org/copyleft/gpl.html * * Copyright (c) 2007 The Open Source Physics project * http://www.opensourcephysics.org */