Merge commit '42b2e5ca519766e37ce6941ba4faecc9691cc403' into upstream
[debian/openrocket] / core / src / net / sf / openrocket / gui / figure3d / RocketFigure3d.java
diff --git a/core/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java b/core/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java
new file mode 100644 (file)
index 0000000..8df1e85
--- /dev/null
@@ -0,0 +1,628 @@
+package net.sf.openrocket.gui.figure3d;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Color;\r
+import java.awt.Graphics2D;\r
+import java.awt.Point;\r
+import java.awt.Rectangle;\r
+import java.awt.RenderingHints;\r
+import java.awt.SplashScreen;\r
+import java.awt.event.MouseEvent;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.image.BufferedImage;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.HashSet;\r
+import java.util.Set;\r
+\r
+import javax.media.opengl.GL;\r
+import javax.media.opengl.GL2;\r
+import javax.media.opengl.GLAutoDrawable;\r
+import javax.media.opengl.GLCapabilities;\r
+import javax.media.opengl.GLEventListener;\r
+import javax.media.opengl.GLProfile;\r
+import javax.media.opengl.awt.GLCanvas;\r
+import javax.media.opengl.fixedfunc.GLLightingFunc;\r
+import javax.media.opengl.fixedfunc.GLMatrixFunc;\r
+import javax.media.opengl.glu.GLU;\r
+import javax.swing.JLabel;\r
+import javax.swing.JPanel;\r
+import javax.swing.JPopupMenu;\r
+import javax.swing.SwingUtilities;\r
+import javax.swing.event.MouseInputAdapter;\r
+\r
+import net.sf.openrocket.gui.figureelements.CGCaret;\r
+import net.sf.openrocket.gui.figureelements.CPCaret;\r
+import net.sf.openrocket.gui.figureelements.FigureElement;\r
+import net.sf.openrocket.logging.LogHelper;\r
+import net.sf.openrocket.rocketcomponent.Configuration;\r
+import net.sf.openrocket.rocketcomponent.RocketComponent;\r
+import net.sf.openrocket.startup.Application;\r
+import net.sf.openrocket.util.Coordinate;\r
+import net.sf.openrocket.util.MathUtil;\r
+\r
+import com.jogamp.opengl.util.awt.Overlay;\r
+\r
+/*\r
+ * @author Bill Kuker <bkuker@billkuker.com>\r
+ */\r
+public class RocketFigure3d extends JPanel implements GLEventListener {\r
+       private static final long serialVersionUID = 1L;\r
+       private static final LogHelper log = Application.getLogger();\r
+       \r
+       static {\r
+               //this allows the GL canvas and things like the motor selection\r
+               //drop down to z-order themselves.\r
+               JPopupMenu.setDefaultLightWeightPopupEnabled(false);\r
+       }\r
+\r
+       private static final double fovY = 15.0;\r
+       private static double fovX = Double.NaN;\r
+       private static final int CARET_SIZE = 20;\r
+       \r
+       private Configuration configuration;\r
+       private GLCanvas canvas;\r
+\r
+\r
+       \r
+       private Overlay extrasOverlay, caretOverlay;\r
+       private BufferedImage cgCaretRaster, cpCaretRaster;\r
+       private volatile boolean redrawExtras = true;\r
+\r
+       private final ArrayList<FigureElement> relativeExtra = new ArrayList<FigureElement>();\r
+       private final ArrayList<FigureElement> absoluteExtra = new ArrayList<FigureElement>();\r
+\r
+       private double roll = 0;\r
+       private double yaw = 0;\r
+\r
+       Point pickPoint = null;\r
+       MouseEvent pickEvent;\r
+\r
+       float[] lightPosition = new float[] { 1, 4, 1, 0 };\r
+\r
+       RocketRenderer rr = new RocketRenderer();\r
+\r
+       public RocketFigure3d(Configuration config) {\r
+               this.configuration = config;\r
+               this.setLayout(new BorderLayout());\r
+               \r
+               //Only initizlize GL if 3d is enabled.\r
+               if ( is3dEnabled() ){\r
+                       //Fixes a linux / X bug: Splash must be closed before GL Init\r
+                       SplashScreen splash = SplashScreen.getSplashScreen();\r
+                       if ( splash != null )\r
+                               splash.close();\r
+                       \r
+                       initGLCanvas();\r
+               }\r
+       }\r
+       \r
+       /**\r
+        * Return true if 3d view is enabled. This may be toggled by the user at\r
+        * launch time.\r
+        * @return\r
+        */\r
+       public static boolean is3dEnabled(){\r
+               return System.getProperty("openrocket.3d.disable") == null;\r
+       }\r
+       \r
+       private void initGLCanvas(){\r
+               log.debug("Initializing RocketFigure3D OpenGL Canvas");\r
+               try {\r
+                       log.debug("Setting up GL capabilities...");\r
+                       \r
+                       log.verbose("GL - Getting Default Profile");\r
+                       GLProfile glp = GLProfile.getDefault();\r
+                       \r
+                       log.verbose("GL - creating GLCapabilities");\r
+                       GLCapabilities caps = new GLCapabilities(glp);\r
+                       \r
+                       log.verbose("GL - setSampleBuffers");\r
+                       caps.setSampleBuffers(true);\r
+                       \r
+                       log.verbose("GL - setNumSamples");\r
+                       caps.setNumSamples(6);\r
+                       \r
+                       log.verbose("GL - setStencilBits");\r
+                       caps.setStencilBits(1);\r
+\r
+                       log.verbose("GL - Creating Canvas");\r
+                       canvas = new GLCanvas(caps);\r
+\r
+                       log.verbose("GL - Registering as GLEventListener on canvas");\r
+                       canvas.addGLEventListener(this);\r
+                       \r
+                       log.verbose("GL - Adding canvas to this JPanel");\r
+                       this.add(canvas, BorderLayout.CENTER);\r
+\r
+                       log.verbose("GL - Setting up mouse listeners");\r
+                       setupMouseListeners();\r
+                       \r
+                       log.verbose("GL - Rasterizine Carets"); //reticulating splines?\r
+                       rasterizeCarets();\r
+                       \r
+               } catch (Throwable t) {\r
+                       log.error("An error occurred creating 3d View", t);\r
+                       canvas = null;\r
+                       this.add(new JLabel("Unable to load 3d Libraries: "\r
+                                       + t.getMessage()));\r
+               }\r
+       }\r
+       \r
+       /**\r
+        * Set up the standard rendering hints on the Graphics2D\r
+        */\r
+       private static void setRenderingHints(Graphics2D g){\r
+               g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,\r
+                               RenderingHints.VALUE_STROKE_NORMALIZE);\r
+               g.setRenderingHint(RenderingHints.KEY_RENDERING,\r
+                               RenderingHints.VALUE_RENDER_QUALITY);\r
+               g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,\r
+                               RenderingHints.VALUE_ANTIALIAS_ON);\r
+       }\r
+       \r
+       /**\r
+        * Rasterize the carets into 2 buffered images that I can blit onto the\r
+        * 3d display every redraw without all of the caret shape rendering overhead\r
+        */\r
+       private void rasterizeCarets(){\r
+               Graphics2D g2d;\r
+               \r
+               //Rasterize a CG Caret\r
+               cgCaretRaster = new BufferedImage(CARET_SIZE, CARET_SIZE, BufferedImage.TYPE_4BYTE_ABGR);\r
+               g2d = cgCaretRaster.createGraphics();\r
+               setRenderingHints(g2d);\r
+               \r
+               g2d.setBackground(new Color(0, 0, 0, 0));\r
+               g2d.clearRect(0, 0, CARET_SIZE, CARET_SIZE);\r
+               \r
+               new CGCaret(CARET_SIZE/2,CARET_SIZE/2).paint(g2d, 1.0);\r
+               \r
+               g2d.dispose();\r
+\r
+               //Rasterize a CP Caret\r
+               cpCaretRaster = new BufferedImage(CARET_SIZE, CARET_SIZE, BufferedImage.TYPE_4BYTE_ABGR);\r
+               g2d = cpCaretRaster.createGraphics();\r
+               setRenderingHints(g2d);\r
+               \r
+               g2d.setBackground(new Color(0, 0, 0, 0));\r
+               g2d.clearRect(0, 0, CARET_SIZE, CARET_SIZE);\r
+               \r
+               new CPCaret(CARET_SIZE/2,CARET_SIZE/2).paint(g2d, 1.0);\r
+               \r
+               g2d.dispose();\r
+               \r
+       }\r
+\r
+       private void setupMouseListeners() {\r
+               MouseInputAdapter a = new MouseInputAdapter() {\r
+                       int lastX;\r
+                       int lastY;\r
+                       MouseEvent pressEvent;\r
+\r
+                       @Override\r
+                       public void mousePressed(MouseEvent e) {\r
+                               lastX = e.getX();\r
+                               lastY = e.getY();\r
+                               pressEvent = e;\r
+                       }\r
+\r
+                       @Override\r
+                       public void mouseClicked(MouseEvent e) {\r
+                               pickPoint = new Point(lastX, canvas.getHeight() - lastY);\r
+                               pickEvent = e;\r
+                               internalRepaint();\r
+                       }\r
+\r
+                       @Override\r
+                       public void mouseDragged(MouseEvent e) {\r
+                               int dx = lastX - e.getX();\r
+                               int dy = lastY - e.getY();\r
+                               lastX = e.getX();\r
+                               lastY = e.getY();\r
+\r
+                               if (pressEvent.getButton() == MouseEvent.BUTTON1) {\r
+                                       if (Math.abs(dx) > Math.abs(dy)) {\r
+                                               setYaw(yaw - (float) dx / 100.0);\r
+                                       } else {\r
+                                               if ( yaw > Math.PI/2.0 && yaw < 3.0*Math.PI/2.0 ){\r
+                                                       dy = -dy;\r
+                                               }\r
+                                               setRoll(roll - (float) dy / 100.0);\r
+                                       }\r
+                               } else {\r
+                                       lightPosition[0] -= 0.1f * dx;\r
+                                       lightPosition[1] += 0.1f * dy;\r
+                                       internalRepaint();\r
+                               }\r
+                       }\r
+               };\r
+               canvas.addMouseMotionListener(a);\r
+               canvas.addMouseListener(a);\r
+       }\r
+\r
+       public void setConfiguration(Configuration configuration) {\r
+               this.configuration = configuration;\r
+               updateFigure();\r
+       }\r
+\r
+       @Override\r
+       public void display(GLAutoDrawable drawable) {\r
+               GL2 gl = drawable.getGL().getGL2();\r
+               GLU glu = new GLU();\r
+\r
+               gl.glEnable(GL.GL_MULTISAMPLE);\r
+\r
+               gl.glClearColor(1, 1, 1, 1);\r
+               gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);\r
+\r
+               setupView(gl, glu);\r
+\r
+               if (pickPoint != null) {\r
+                       gl.glDisable(GLLightingFunc.GL_LIGHTING);\r
+                       final RocketComponent picked = rr.pick(drawable, configuration,\r
+                                       pickPoint, pickEvent.isShiftDown()?selection:null );\r
+                       if (csl != null && picked != null) {\r
+                               final MouseEvent e = pickEvent;\r
+                               SwingUtilities.invokeLater(new Runnable() {\r
+                                       @Override\r
+                                       public void run() {\r
+                                               csl.componentClicked(new RocketComponent[] { picked },\r
+                                                               e);\r
+                                       }\r
+                               });\r
+\r
+                       }\r
+                       pickPoint = null;\r
+\r
+                       gl.glClearColor(1, 1, 1, 1);\r
+                       gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);\r
+\r
+                       gl.glEnable(GLLightingFunc.GL_LIGHTING);\r
+               }\r
+               rr.render(drawable, configuration, selection);\r
+               \r
+               drawExtras(gl, glu);\r
+               drawCarets(gl, glu);\r
+       }\r
+\r
+       \r
+       private void drawCarets(GL2 gl, GLU glu) {\r
+               final Graphics2D og2d = caretOverlay.createGraphics();\r
+               setRenderingHints(og2d);\r
+               \r
+               og2d.setBackground(new Color(0, 0, 0, 0));\r
+               og2d.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());\r
+               caretOverlay.markDirty(0, 0, canvas.getWidth(), canvas.getHeight());\r
+\r
+               // The existing relative Extras don't really work right for 3d.\r
+               Coordinate pCP = project(cp, gl, glu);\r
+               Coordinate pCG = project(cg, gl, glu);\r
+\r
+               final int d = CARET_SIZE/2;\r
+               \r
+               //z order the carets \r
+               if (pCG.z < pCP.z) {\r
+                       //Subtract half of the caret size, so they are centered ( The +/- d in each translate)\r
+                       //Flip the sense of the Y coordinate from GL to normal (Y+ up/down)\r
+                       og2d.drawRenderedImage(\r
+                                       cpCaretRaster,\r
+                                       AffineTransform.getTranslateInstance((pCP.x - d),\r
+                                                       canvas.getHeight() - (pCP.y + d)));\r
+                       og2d.drawRenderedImage(\r
+                                       cgCaretRaster,\r
+                                       AffineTransform.getTranslateInstance((pCG.x - d),\r
+                                                       canvas.getHeight() - (pCG.y + d)));\r
+               } else {\r
+                       og2d.drawRenderedImage(\r
+                                       cgCaretRaster,\r
+                                       AffineTransform.getTranslateInstance((pCG.x - d),\r
+                                                       canvas.getHeight() - (pCG.y + d)));\r
+                       og2d.drawRenderedImage(\r
+                                       cpCaretRaster,\r
+                                       AffineTransform.getTranslateInstance((pCP.x - d),\r
+                                                       canvas.getHeight() - (pCP.y + d)));\r
+               }\r
+               og2d.dispose();\r
+               \r
+               gl.glEnable(GL.GL_BLEND);\r
+               caretOverlay.drawAll();\r
+               gl.glDisable(GL.GL_BLEND);\r
+       }\r
+       \r
+       /**\r
+        * Draw the extras overlay to the gl canvas.\r
+        * Re-blits the overlay every frame. Only re-renders the overlay\r
+        * when needed.\r
+        */\r
+       private void drawExtras(GL2 gl, GLU glu){\r
+               //Only re-render if needed\r
+               //      redrawExtras: Some external change (new simulation data) means\r
+               //              the data is out of date.\r
+               //      extrasOverlay.contentsLost(): For some reason the buffer with this\r
+               //              data is lost.\r
+               if ( redrawExtras || extrasOverlay.contentsLost() ){\r
+                       log.debug("Redrawing Overlay");\r
+                       \r
+                       final Graphics2D og2d = extrasOverlay.createGraphics(); \r
+                       setRenderingHints(og2d);\r
+\r
+                       og2d.setBackground(new Color(0, 0, 0, 0));\r
+                       og2d.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());\r
+                       extrasOverlay.markDirty(0, 0, canvas.getWidth(), canvas.getHeight());\r
+                       \r
+                       for (FigureElement e : relativeExtra) {\r
+                               e.paint(og2d, 1);\r
+                       }\r
+                       Rectangle rect = this.getVisibleRect();\r
+       \r
+                       for (FigureElement e : absoluteExtra) {\r
+                               e.paint(og2d, 1.0, rect);\r
+                       }\r
+                       og2d.dispose();\r
+                       \r
+                       redrawExtras = false;\r
+               }\r
+\r
+               //Re-blit to gl canvas every time\r
+               gl.glEnable(GL.GL_BLEND);\r
+               extrasOverlay.drawAll();\r
+               gl.glDisable(GL.GL_BLEND);\r
+       }\r
+\r
+       @Override\r
+       public void dispose(GLAutoDrawable drawable) {\r
+               log.verbose("GL - dispose() called");\r
+       }\r
+\r
+       @Override\r
+       public void init(GLAutoDrawable drawable) {\r
+               log.verbose("GL - init() called");\r
+               rr.init(drawable);\r
+\r
+               GL2 gl = drawable.getGL().getGL2();\r
+               gl.glClearDepth(1.0f); // clear z-buffer to the farthest\r
+\r
+               gl.glDepthFunc(GL.GL_LEQUAL); // the type of depth test to do\r
+\r
+               float amb = 0.5f;\r
+               float dif = 1.0f;\r
+               gl.glLightfv(GLLightingFunc.GL_LIGHT1, GLLightingFunc.GL_AMBIENT,\r
+                               new float[] { amb, amb, amb, 1 }, 0);\r
+               gl.glLightfv(GLLightingFunc.GL_LIGHT1, GLLightingFunc.GL_DIFFUSE,\r
+                               new float[] { dif, dif, dif, 1 }, 0);\r
+               gl.glLightfv(GLLightingFunc.GL_LIGHT1, GLLightingFunc.GL_SPECULAR,\r
+                               new float[] { dif, dif, dif, 1 }, 0);\r
+\r
+               gl.glEnable(GLLightingFunc.GL_LIGHT1);\r
+               gl.glEnable(GLLightingFunc.GL_LIGHTING);\r
+               gl.glShadeModel(GLLightingFunc.GL_SMOOTH);\r
+\r
+               gl.glEnable(GLLightingFunc.GL_NORMALIZE);\r
+\r
+               extrasOverlay = new Overlay(drawable);\r
+               caretOverlay = new Overlay(drawable);\r
+               \r
+               log.verbose("GL - init() complete");\r
+       }\r
+\r
+       @Override\r
+       public void reshape(GLAutoDrawable drawable, int x, int y, int w, int h) {\r
+               log.verbose("GL - reshape() called");\r
+               GL2 gl = drawable.getGL().getGL2();\r
+               GLU glu = new GLU();\r
+\r
+               double ratio = (double) w / (double) h;\r
+               fovX = fovY * ratio;\r
+\r
+               gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);\r
+               gl.glLoadIdentity();\r
+               glu.gluPerspective(fovY, ratio, 0.05f, 100f);\r
+               gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);\r
+               \r
+               redrawExtras = true;\r
+               log.verbose("GL - reshape() complete");\r
+       }\r
+\r
+       @SuppressWarnings("unused")\r
+       private static class Bounds {\r
+               double xMin, xMax, xSize;\r
+               double yMin, yMax, ySize;\r
+               double zMin, zMax, zSize;\r
+               double rMax;\r
+       }\r
+\r
+       /**\r
+        * Calculates the bounds for the current configuration\r
+        * \r
+        * @return\r
+        */\r
+       private Bounds calculateBounds() {\r
+               Bounds ret = new Bounds();\r
+               Collection<Coordinate> bounds = configuration.getBounds();\r
+               for (Coordinate c : bounds) {\r
+                       ret.xMax = Math.max(ret.xMax, c.x);\r
+                       ret.xMin = Math.min(ret.xMin, c.x);\r
+\r
+                       ret.yMax = Math.max(ret.yMax, c.y);\r
+                       ret.yMin = Math.min(ret.yMin, c.y);\r
+\r
+                       ret.zMax = Math.max(ret.zMax, c.z);\r
+                       ret.zMin = Math.min(ret.zMin, c.z);\r
+\r
+                       double r = MathUtil.hypot(c.y, c.z);\r
+                       ret.rMax = Math.max(ret.rMax, r);\r
+               }\r
+               ret.xSize = ret.xMax - ret.xMin;\r
+               ret.ySize = ret.yMax - ret.yMin;\r
+               ret.zSize = ret.zMax - ret.zMin;\r
+               return ret;\r
+       }\r
+\r
+       private void setupView(GL2 gl, GLU glu) {\r
+               log.verbose("GL - setupView() called");\r
+               gl.glLoadIdentity();\r
+\r
+               gl.glLightfv(GLLightingFunc.GL_LIGHT1, GLLightingFunc.GL_POSITION,\r
+                               lightPosition, 0);\r
+\r
+               // Get the bounds\r
+               Bounds b = calculateBounds();\r
+\r
+               // Calculate the distance needed to fit the bounds in both the X and Y\r
+               // direction\r
+               // Add 10% for space around it.\r
+               double dX = (b.xSize * 1.2 / 2.0)\r
+                               / Math.tan(Math.toRadians(fovX / 2.0));\r
+               double dY = (b.rMax * 2.0 * 1.2 / 2.0)\r
+                               / Math.tan(Math.toRadians(fovY / 2.0));\r
+\r
+               // Move back the greater of the 2 distances\r
+               glu.gluLookAt(0, 0, Math.max(dX, dY), 0, 0, 0, 0, 1, 0);\r
+\r
+               gl.glRotated(yaw * (180.0 / Math.PI), 0, 1, 0);\r
+               gl.glRotated(roll * (180.0 / Math.PI), 1, 0, 0);\r
+\r
+               // Center the rocket in the view.\r
+               gl.glTranslated(-b.xMin - b.xSize / 2.0, 0, 0);\r
+               \r
+               //Change to LEFT Handed coordinates\r
+               gl.glScaled(1, 1, -1);\r
+               gl.glFrontFace(GL.GL_CW);\r
+               \r
+               //Flip textures for LEFT handed coords\r
+               gl.glMatrixMode(GL.GL_TEXTURE);\r
+               gl.glLoadIdentity();\r
+               gl.glScaled(-1,1,1);\r
+               gl.glTranslated(-1,0,0);\r
+               gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);\r
+               \r
+               log.verbose("GL - setupView() complete");\r
+       }\r
+\r
+       /**\r
+        * Call when the rocket has changed\r
+        */\r
+       public void updateFigure() {\r
+               log.debug("3D Figure Updated");\r
+               rr.updateFigure();\r
+               internalRepaint();\r
+       }\r
+\r
+       private void internalRepaint(){\r
+               log.verbose("GL - internalRepaint() called");\r
+               super.repaint();\r
+               if (canvas != null)\r
+                       canvas.display();\r
+               log.verbose("GL - internalRepaint() complete");\r
+       }\r
+       \r
+       @Override\r
+       public void repaint() {\r
+               log.verbose("GL - repaint() called");\r
+               redrawExtras = true;\r
+               internalRepaint();\r
+               log.verbose("GL - repaint() complete");\r
+       }\r
+\r
+       private Set<RocketComponent> selection = new HashSet<RocketComponent>();\r
+\r
+       public void setSelection(RocketComponent[] selection) {\r
+               this.selection.clear();\r
+               if (selection != null) {\r
+                       for (RocketComponent c : selection)\r
+                               this.selection.add(c);\r
+               }\r
+               internalRepaint();\r
+       }\r
+\r
+       private void setRoll(double rot) {\r
+               if (MathUtil.equals(roll, rot))\r
+                       return;\r
+               this.roll = MathUtil.reduce360(rot);\r
+               internalRepaint();\r
+       }\r
+\r
+       private void setYaw(double rot) {\r
+               if (MathUtil.equals(yaw, rot))\r
+                       return;\r
+               this.yaw = MathUtil.reduce360(rot);\r
+               internalRepaint();\r
+       }\r
+\r
+       // ///////////// Extra methods\r
+\r
+       private Coordinate project(Coordinate c, GL2 gl, GLU glu) {\r
+               log.verbose("GL - project() called");\r
+               double[] mvmatrix = new double[16];\r
+               double[] projmatrix = new double[16];\r
+               int[] viewport = new int[4];\r
+\r
+               gl.glGetIntegerv(GL.GL_VIEWPORT, viewport, 0);\r
+               gl.glGetDoublev(GLMatrixFunc.GL_MODELVIEW_MATRIX, mvmatrix, 0);\r
+               gl.glGetDoublev(GLMatrixFunc.GL_PROJECTION_MATRIX, projmatrix, 0);\r
+\r
+               double out[] = new double[4];\r
+               glu.gluProject(c.x, c.y, c.z, mvmatrix, 0, projmatrix, 0, viewport, 0,\r
+                               out, 0);\r
+               \r
+               log.verbose("GL - peoject() complete");\r
+               return new Coordinate(out[0], out[1], out[2]);\r
+               \r
+       }\r
+\r
+       private Coordinate cp = new Coordinate(0, 0, 0);\r
+       private Coordinate cg = new Coordinate(0, 0, 0);\r
+\r
+       public void setCG(Coordinate cg) {\r
+               this.cg = cg;\r
+               redrawExtras = true;\r
+       }\r
+\r
+       public void setCP(Coordinate cp) {\r
+               this.cp = cp;\r
+               redrawExtras = true;\r
+       }\r
+\r
+       public void addRelativeExtra(FigureElement p) {\r
+               relativeExtra.add(p);\r
+               redrawExtras = true;\r
+       }\r
+\r
+       public void removeRelativeExtra(FigureElement p) {\r
+               relativeExtra.remove(p);\r
+               redrawExtras = true;\r
+       }\r
+\r
+       public void clearRelativeExtra() {\r
+               relativeExtra.clear();\r
+               redrawExtras = true;\r
+       }\r
+\r
+       public void addAbsoluteExtra(FigureElement p) {\r
+               absoluteExtra.add(p);\r
+               redrawExtras = true;\r
+       }\r
+\r
+       public void removeAbsoluteExtra(FigureElement p) {\r
+               absoluteExtra.remove(p);\r
+               redrawExtras = true;\r
+       }\r
+\r
+       public void clearAbsoluteExtra() {\r
+               absoluteExtra.clear();\r
+               redrawExtras = true;\r
+       }\r
+\r
+       private ComponentSelectionListener csl;\r
+\r
+       public static interface ComponentSelectionListener {\r
+               public void componentClicked(RocketComponent[] components, MouseEvent e);\r
+       }\r
+\r
+       public void addComponentSelectionListener(\r
+                       ComponentSelectionListener newListener) {\r
+               this.csl = newListener;\r
+       }\r
+\r
+}\r