Add a great amount of verbose debugging
[debian/openrocket] / core / src / net / sf / openrocket / gui / figure3d / RocketFigure3d.java
1 package net.sf.openrocket.gui.figure3d;\r
2 \r
3 import java.awt.BorderLayout;\r
4 import java.awt.Color;\r
5 import java.awt.Graphics2D;\r
6 import java.awt.Point;\r
7 import java.awt.Rectangle;\r
8 import java.awt.RenderingHints;\r
9 import java.awt.event.HierarchyEvent;\r
10 import java.awt.event.HierarchyListener;\r
11 import java.awt.event.MouseEvent;\r
12 import java.awt.geom.AffineTransform;\r
13 import java.awt.image.BufferedImage;\r
14 import java.util.ArrayList;\r
15 import java.util.Collection;\r
16 import java.util.HashSet;\r
17 import java.util.Set;\r
18 \r
19 import javax.media.opengl.GL;\r
20 import javax.media.opengl.GL2;\r
21 import javax.media.opengl.GLAutoDrawable;\r
22 import javax.media.opengl.GLCapabilities;\r
23 import javax.media.opengl.GLEventListener;\r
24 import javax.media.opengl.GLProfile;\r
25 import javax.media.opengl.awt.GLCanvas;\r
26 import javax.media.opengl.fixedfunc.GLLightingFunc;\r
27 import javax.media.opengl.fixedfunc.GLMatrixFunc;\r
28 import javax.media.opengl.glu.GLU;\r
29 import javax.swing.JLabel;\r
30 import javax.swing.JPanel;\r
31 import javax.swing.JPopupMenu;\r
32 import javax.swing.SwingUtilities;\r
33 import javax.swing.event.MouseInputAdapter;\r
34 \r
35 import net.sf.openrocket.gui.figureelements.CGCaret;\r
36 import net.sf.openrocket.gui.figureelements.CPCaret;\r
37 import net.sf.openrocket.gui.figureelements.FigureElement;\r
38 import net.sf.openrocket.logging.LogHelper;\r
39 import net.sf.openrocket.rocketcomponent.Configuration;\r
40 import net.sf.openrocket.rocketcomponent.RocketComponent;\r
41 import net.sf.openrocket.startup.Application;\r
42 import net.sf.openrocket.util.Coordinate;\r
43 import net.sf.openrocket.util.MathUtil;\r
44 \r
45 import com.jogamp.opengl.util.awt.Overlay;\r
46 \r
47 /*\r
48  * @author Bill Kuker <bkuker@billkuker.com>\r
49  */\r
50 public class RocketFigure3d extends JPanel implements GLEventListener {\r
51         private static final long serialVersionUID = 1L;\r
52         private static final LogHelper log = Application.getLogger();\r
53         \r
54         static {\r
55                 //this allows the GL canvas and things like the motor selection\r
56                 //drop down to z-order themselves.\r
57                 JPopupMenu.setDefaultLightWeightPopupEnabled(false);\r
58         }\r
59 \r
60         private static final double fovY = 15.0;\r
61         private static double fovX = Double.NaN;\r
62         private static final int CARET_SIZE = 20;\r
63         \r
64         private Configuration configuration;\r
65         private GLCanvas canvas;\r
66 \r
67 \r
68         \r
69         private Overlay extrasOverlay, caretOverlay;\r
70         private BufferedImage cgCaretRaster, cpCaretRaster;\r
71         private volatile boolean redrawExtras = true;\r
72 \r
73         private final ArrayList<FigureElement> relativeExtra = new ArrayList<FigureElement>();\r
74         private final ArrayList<FigureElement> absoluteExtra = new ArrayList<FigureElement>();\r
75 \r
76         private double roll = 0;\r
77         private double yaw = 0;\r
78 \r
79         Point pickPoint = null;\r
80         MouseEvent pickEvent;\r
81 \r
82         float[] lightPosition = new float[] { 1, 4, 1, 0 };\r
83 \r
84         RocketRenderer rr = new RocketRenderer();\r
85 \r
86         public RocketFigure3d(Configuration config) {\r
87                 this.configuration = config;\r
88                 this.setLayout(new BorderLayout());\r
89 \r
90                 /**\r
91                  * This achieves late GL initialization, so that if there is a crash\r
92                  * it does not occur until user selects 3D view, so as to not render\r
93                  * the application unusable.\r
94                  */\r
95                 addHierarchyListener(new HierarchyListener() {\r
96                         @Override\r
97                         public void hierarchyChanged(HierarchyEvent e) {\r
98                                 log.verbose("GL - Calling initGLCanvas on hierarchy change");\r
99                                 initGLCanvas();\r
100                                 RocketFigure3d.this.removeHierarchyListener(this);\r
101                         }\r
102                 });\r
103         }\r
104         \r
105         private void initGLCanvas(){\r
106                 log.debug("Initializing RocketFigure3D OpenGL Canvas");\r
107                 try {\r
108                         log.debug("Setting up GL capabilities...");\r
109                         \r
110                         log.verbose("GL - Getting Default Profile");\r
111                         GLProfile glp = GLProfile.getDefault();\r
112                         \r
113                         log.verbose("GL - creating GLCapabilities");\r
114                         GLCapabilities caps = new GLCapabilities(glp);\r
115                         \r
116                         log.verbose("GL - setSampleBuffers");\r
117                         caps.setSampleBuffers(true);\r
118                         \r
119                         log.verbose("GL - setNumSamples");\r
120                         caps.setNumSamples(6);\r
121                         \r
122                         log.verbose("GL - setStencilBits");\r
123                         caps.setStencilBits(1);\r
124 \r
125                         log.verbose("GL - Creating Canvas");\r
126                         canvas = new GLCanvas(caps);\r
127 \r
128                         log.verbose("GL - Registering as GLEventListener on canvas");\r
129                         canvas.addGLEventListener(this);\r
130                         \r
131                         log.verbose("GL - Adding canvas to this JPanel");\r
132                         this.add(canvas, BorderLayout.CENTER);\r
133 \r
134                         log.verbose("GL - Setting up mouse listeners");\r
135                         setupMouseListeners();\r
136                         \r
137                         log.verbose("GL - Rasterizine Carets"); //reticulating splines?\r
138                         rasterizeCarets();\r
139                         \r
140                 } catch (Throwable t) {\r
141                         log.error("An error occurred creating 3d View", t);\r
142                         canvas = null;\r
143                         this.add(new JLabel("Unable to load 3d Libraries: "\r
144                                         + t.getMessage()));\r
145                 }\r
146         }\r
147         \r
148         /**\r
149          * Set up the standard rendering hints on the Graphics2D\r
150          */\r
151         private static void setRenderingHints(Graphics2D g){\r
152                 g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,\r
153                                 RenderingHints.VALUE_STROKE_NORMALIZE);\r
154                 g.setRenderingHint(RenderingHints.KEY_RENDERING,\r
155                                 RenderingHints.VALUE_RENDER_QUALITY);\r
156                 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,\r
157                                 RenderingHints.VALUE_ANTIALIAS_ON);\r
158         }\r
159         \r
160         /**\r
161          * Rasterize the carets into 2 buffered images that I can blit onto the\r
162          * 3d display every redraw without all of the caret shape rendering overhead\r
163          */\r
164         private void rasterizeCarets(){\r
165                 Graphics2D g2d;\r
166                 \r
167                 //Rasterize a CG Caret\r
168                 cgCaretRaster = new BufferedImage(CARET_SIZE, CARET_SIZE, BufferedImage.TYPE_4BYTE_ABGR);\r
169                 g2d = cgCaretRaster.createGraphics();\r
170                 setRenderingHints(g2d);\r
171                 \r
172                 g2d.setBackground(new Color(0, 0, 0, 0));\r
173                 g2d.clearRect(0, 0, CARET_SIZE, CARET_SIZE);\r
174                 \r
175                 new CGCaret(CARET_SIZE/2,CARET_SIZE/2).paint(g2d, 1.0);\r
176                 \r
177                 g2d.dispose();\r
178 \r
179                 //Rasterize a CP Caret\r
180                 cpCaretRaster = new BufferedImage(CARET_SIZE, CARET_SIZE, BufferedImage.TYPE_4BYTE_ABGR);\r
181                 g2d = cpCaretRaster.createGraphics();\r
182                 setRenderingHints(g2d);\r
183                 \r
184                 g2d.setBackground(new Color(0, 0, 0, 0));\r
185                 g2d.clearRect(0, 0, CARET_SIZE, CARET_SIZE);\r
186                 \r
187                 new CPCaret(CARET_SIZE/2,CARET_SIZE/2).paint(g2d, 1.0);\r
188                 \r
189                 g2d.dispose();\r
190                 \r
191         }\r
192 \r
193         private void setupMouseListeners() {\r
194                 MouseInputAdapter a = new MouseInputAdapter() {\r
195                         int lastX;\r
196                         int lastY;\r
197                         MouseEvent pressEvent;\r
198 \r
199                         @Override\r
200                         public void mousePressed(MouseEvent e) {\r
201                                 lastX = e.getX();\r
202                                 lastY = e.getY();\r
203                                 pressEvent = e;\r
204                         }\r
205 \r
206                         @Override\r
207                         public void mouseClicked(MouseEvent e) {\r
208                                 pickPoint = new Point(lastX, canvas.getHeight() - lastY);\r
209                                 pickEvent = e;\r
210                                 internalRepaint();\r
211                         }\r
212 \r
213                         @Override\r
214                         public void mouseDragged(MouseEvent e) {\r
215                                 int dx = lastX - e.getX();\r
216                                 int dy = lastY - e.getY();\r
217                                 lastX = e.getX();\r
218                                 lastY = e.getY();\r
219 \r
220                                 if (pressEvent.getButton() == MouseEvent.BUTTON1) {\r
221                                         if (Math.abs(dx) > Math.abs(dy)) {\r
222                                                 setYaw(yaw - (float) dx / 100.0);\r
223                                         } else {\r
224                                                 if ( yaw > Math.PI/2.0 && yaw < 3.0*Math.PI/2.0 ){\r
225                                                         dy = -dy;\r
226                                                 }\r
227                                                 setRoll(roll - (float) dy / 100.0);\r
228                                         }\r
229                                 } else {\r
230                                         lightPosition[0] -= 0.1f * dx;\r
231                                         lightPosition[1] += 0.1f * dy;\r
232                                         internalRepaint();\r
233                                 }\r
234                         }\r
235                 };\r
236                 canvas.addMouseMotionListener(a);\r
237                 canvas.addMouseListener(a);\r
238         }\r
239 \r
240         public void setConfiguration(Configuration configuration) {\r
241                 this.configuration = configuration;\r
242                 updateFigure();\r
243         }\r
244 \r
245         @Override\r
246         public void display(GLAutoDrawable drawable) {\r
247                 GL2 gl = drawable.getGL().getGL2();\r
248                 GLU glu = new GLU();\r
249 \r
250                 gl.glEnable(GL.GL_MULTISAMPLE);\r
251 \r
252                 gl.glClearColor(1, 1, 1, 1);\r
253                 gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);\r
254 \r
255                 setupView(gl, glu);\r
256 \r
257                 if (pickPoint != null) {\r
258                         gl.glDisable(GLLightingFunc.GL_LIGHTING);\r
259                         final RocketComponent picked = rr.pick(drawable, configuration,\r
260                                         pickPoint, pickEvent.isShiftDown()?selection:null );\r
261                         if (csl != null && picked != null) {\r
262                                 final MouseEvent e = pickEvent;\r
263                                 SwingUtilities.invokeLater(new Runnable() {\r
264                                         @Override\r
265                                         public void run() {\r
266                                                 csl.componentClicked(new RocketComponent[] { picked },\r
267                                                                 e);\r
268                                         }\r
269                                 });\r
270 \r
271                         }\r
272                         pickPoint = null;\r
273 \r
274                         gl.glClearColor(1, 1, 1, 1);\r
275                         gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);\r
276 \r
277                         gl.glEnable(GLLightingFunc.GL_LIGHTING);\r
278                 }\r
279                 rr.render(drawable, configuration, selection);\r
280                 \r
281                 drawExtras(gl, glu);\r
282                 drawCarets(gl, glu);\r
283         }\r
284 \r
285         \r
286         private void drawCarets(GL2 gl, GLU glu) {\r
287                 final Graphics2D og2d = caretOverlay.createGraphics();\r
288                 setRenderingHints(og2d);\r
289                 \r
290                 og2d.setBackground(new Color(0, 0, 0, 0));\r
291                 og2d.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());\r
292                 caretOverlay.markDirty(0, 0, canvas.getWidth(), canvas.getHeight());\r
293 \r
294                 // The existing relative Extras don't really work right for 3d.\r
295                 Coordinate pCP = project(cp, gl, glu);\r
296                 Coordinate pCG = project(cg, gl, glu);\r
297 \r
298                 final int d = CARET_SIZE/2;\r
299                 \r
300                 //z order the carets \r
301                 if (pCG.z < pCP.z) {\r
302                         //Subtract half of the caret size, so they are centered ( The +/- d in each translate)\r
303                         //Flip the sense of the Y coordinate from GL to normal (Y+ up/down)\r
304                         og2d.drawRenderedImage(\r
305                                         cpCaretRaster,\r
306                                         AffineTransform.getTranslateInstance((pCP.x - d),\r
307                                                         canvas.getHeight() - (pCP.y + d)));\r
308                         og2d.drawRenderedImage(\r
309                                         cgCaretRaster,\r
310                                         AffineTransform.getTranslateInstance((pCG.x - d),\r
311                                                         canvas.getHeight() - (pCG.y + d)));\r
312                 } else {\r
313                         og2d.drawRenderedImage(\r
314                                         cgCaretRaster,\r
315                                         AffineTransform.getTranslateInstance((pCG.x - d),\r
316                                                         canvas.getHeight() - (pCG.y + d)));\r
317                         og2d.drawRenderedImage(\r
318                                         cpCaretRaster,\r
319                                         AffineTransform.getTranslateInstance((pCP.x - d),\r
320                                                         canvas.getHeight() - (pCP.y + d)));\r
321                 }\r
322                 og2d.dispose();\r
323                 \r
324                 gl.glEnable(GL.GL_BLEND);\r
325                 caretOverlay.drawAll();\r
326                 gl.glDisable(GL.GL_BLEND);\r
327         }\r
328         \r
329         /**\r
330          * Draw the extras overlay to the gl canvas.\r
331          * Re-blits the overlay every frame. Only re-renders the overlay\r
332          * when needed.\r
333          */\r
334         private void drawExtras(GL2 gl, GLU glu){\r
335                 //Only re-render if needed\r
336                 //      redrawExtras: Some external change (new simulation data) means\r
337                 //              the data is out of date.\r
338                 //      extrasOverlay.contentsLost(): For some reason the buffer with this\r
339                 //              data is lost.\r
340                 if ( redrawExtras || extrasOverlay.contentsLost() ){\r
341                         log.debug("Redrawing Overlay");\r
342                         \r
343                         final Graphics2D og2d = extrasOverlay.createGraphics(); \r
344                         setRenderingHints(og2d);\r
345 \r
346                         og2d.setBackground(new Color(0, 0, 0, 0));\r
347                         og2d.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());\r
348                         extrasOverlay.markDirty(0, 0, canvas.getWidth(), canvas.getHeight());\r
349                         \r
350                         for (FigureElement e : relativeExtra) {\r
351                                 e.paint(og2d, 1);\r
352                         }\r
353                         Rectangle rect = this.getVisibleRect();\r
354         \r
355                         for (FigureElement e : absoluteExtra) {\r
356                                 e.paint(og2d, 1.0, rect);\r
357                         }\r
358                         og2d.dispose();\r
359                         \r
360                         redrawExtras = false;\r
361                 }\r
362 \r
363                 //Re-blit to gl canvas every time\r
364                 gl.glEnable(GL.GL_BLEND);\r
365                 extrasOverlay.drawAll();\r
366                 gl.glDisable(GL.GL_BLEND);\r
367         }\r
368 \r
369         @Override\r
370         public void dispose(GLAutoDrawable drawable) {\r
371                 log.verbose("GL - dispose() called");\r
372         }\r
373 \r
374         @Override\r
375         public void init(GLAutoDrawable drawable) {\r
376                 log.verbose("GL - init() called");\r
377                 rr.init(drawable);\r
378 \r
379                 GL2 gl = drawable.getGL().getGL2();\r
380                 gl.glClearDepth(1.0f); // clear z-buffer to the farthest\r
381 \r
382                 gl.glDepthFunc(GL.GL_LEQUAL); // the type of depth test to do\r
383 \r
384                 float amb = 0.5f;\r
385                 float dif = 1.0f;\r
386                 gl.glLightfv(GLLightingFunc.GL_LIGHT1, GLLightingFunc.GL_AMBIENT,\r
387                                 new float[] { amb, amb, amb, 1 }, 0);\r
388                 gl.glLightfv(GLLightingFunc.GL_LIGHT1, GLLightingFunc.GL_DIFFUSE,\r
389                                 new float[] { dif, dif, dif, 1 }, 0);\r
390                 gl.glLightfv(GLLightingFunc.GL_LIGHT1, GLLightingFunc.GL_SPECULAR,\r
391                                 new float[] { dif, dif, dif, 1 }, 0);\r
392 \r
393                 gl.glEnable(GLLightingFunc.GL_LIGHT1);\r
394                 gl.glEnable(GLLightingFunc.GL_LIGHTING);\r
395                 gl.glShadeModel(GLLightingFunc.GL_SMOOTH);\r
396 \r
397                 gl.glEnable(GLLightingFunc.GL_NORMALIZE);\r
398 \r
399                 extrasOverlay = new Overlay(drawable);\r
400                 caretOverlay = new Overlay(drawable);\r
401                 \r
402                 log.verbose("GL - init() complete");\r
403         }\r
404 \r
405         @Override\r
406         public void reshape(GLAutoDrawable drawable, int x, int y, int w, int h) {\r
407                 log.verbose("GL - reshape() called");\r
408                 GL2 gl = drawable.getGL().getGL2();\r
409                 GLU glu = new GLU();\r
410 \r
411                 double ratio = (double) w / (double) h;\r
412                 fovX = fovY * ratio;\r
413 \r
414                 gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);\r
415                 gl.glLoadIdentity();\r
416                 glu.gluPerspective(fovY, ratio, 0.05f, 100f);\r
417                 gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);\r
418                 \r
419                 redrawExtras = true;\r
420                 log.verbose("GL - reshape() complete");\r
421         }\r
422 \r
423         @SuppressWarnings("unused")\r
424         private static class Bounds {\r
425                 double xMin, xMax, xSize;\r
426                 double yMin, yMax, ySize;\r
427                 double zMin, zMax, zSize;\r
428                 double rMax;\r
429         }\r
430 \r
431         /**\r
432          * Calculates the bounds for the current configuration\r
433          * \r
434          * @return\r
435          */\r
436         private Bounds calculateBounds() {\r
437                 Bounds ret = new Bounds();\r
438                 Collection<Coordinate> bounds = configuration.getBounds();\r
439                 for (Coordinate c : bounds) {\r
440                         ret.xMax = Math.max(ret.xMax, c.x);\r
441                         ret.xMin = Math.min(ret.xMin, c.x);\r
442 \r
443                         ret.yMax = Math.max(ret.yMax, c.y);\r
444                         ret.yMin = Math.min(ret.yMin, c.y);\r
445 \r
446                         ret.zMax = Math.max(ret.zMax, c.z);\r
447                         ret.zMin = Math.min(ret.zMin, c.z);\r
448 \r
449                         double r = MathUtil.hypot(c.y, c.z);\r
450                         ret.rMax = Math.max(ret.rMax, r);\r
451                 }\r
452                 ret.xSize = ret.xMax - ret.xMin;\r
453                 ret.ySize = ret.yMax - ret.yMin;\r
454                 ret.zSize = ret.zMax - ret.zMin;\r
455                 return ret;\r
456         }\r
457 \r
458         private void setupView(GL2 gl, GLU glu) {\r
459                 log.verbose("GL - setupView() called");\r
460                 gl.glLoadIdentity();\r
461 \r
462                 gl.glLightfv(GLLightingFunc.GL_LIGHT1, GLLightingFunc.GL_POSITION,\r
463                                 lightPosition, 0);\r
464 \r
465                 // Get the bounds\r
466                 Bounds b = calculateBounds();\r
467 \r
468                 // Calculate the distance needed to fit the bounds in both the X and Y\r
469                 // direction\r
470                 // Add 10% for space around it.\r
471                 double dX = (b.xSize * 1.2 / 2.0)\r
472                                 / Math.tan(Math.toRadians(fovX / 2.0));\r
473                 double dY = (b.rMax * 2.0 * 1.2 / 2.0)\r
474                                 / Math.tan(Math.toRadians(fovY / 2.0));\r
475 \r
476                 // Move back the greater of the 2 distances\r
477                 glu.gluLookAt(0, 0, Math.max(dX, dY), 0, 0, 0, 0, 1, 0);\r
478 \r
479                 gl.glRotated(yaw * (180.0 / Math.PI), 0, 1, 0);\r
480                 gl.glRotated(roll * (180.0 / Math.PI), 1, 0, 0);\r
481 \r
482                 // Center the rocket in the view.\r
483                 gl.glTranslated(-b.xMin - b.xSize / 2.0, 0, 0);\r
484                 \r
485                 //Change to LEFT Handed coordinates\r
486                 gl.glScaled(1, 1, -1);\r
487                 gl.glFrontFace(GL.GL_CW);\r
488                 \r
489                 //Flip textures for LEFT handed coords\r
490                 gl.glMatrixMode(GL.GL_TEXTURE);\r
491                 gl.glLoadIdentity();\r
492                 gl.glScaled(-1,1,1);\r
493                 gl.glTranslated(-1,0,0);\r
494                 gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);\r
495                 \r
496                 log.verbose("GL - setupView() complete");\r
497         }\r
498 \r
499         /**\r
500          * Call when the rocket has changed\r
501          */\r
502         public void updateFigure() {\r
503                 log.debug("3D Figure Updated");\r
504                 rr.updateFigure();\r
505                 internalRepaint();\r
506         }\r
507 \r
508         private void internalRepaint(){\r
509                 log.verbose("GL - internalRepaint() called");\r
510                 super.repaint();\r
511                 if (canvas != null)\r
512                         canvas.display();\r
513                 log.verbose("GL - internalRepaint() complete");\r
514         }\r
515         \r
516         @Override\r
517         public void repaint() {\r
518                 log.verbose("GL - repaint() called");\r
519                 redrawExtras = true;\r
520                 internalRepaint();\r
521                 log.verbose("GL - repaint() complete");\r
522         }\r
523 \r
524         private Set<RocketComponent> selection = new HashSet<RocketComponent>();\r
525 \r
526         public void setSelection(RocketComponent[] selection) {\r
527                 this.selection.clear();\r
528                 if (selection != null) {\r
529                         for (RocketComponent c : selection)\r
530                                 this.selection.add(c);\r
531                 }\r
532                 internalRepaint();\r
533         }\r
534 \r
535         private void setRoll(double rot) {\r
536                 if (MathUtil.equals(roll, rot))\r
537                         return;\r
538                 this.roll = MathUtil.reduce360(rot);\r
539                 internalRepaint();\r
540         }\r
541 \r
542         private void setYaw(double rot) {\r
543                 if (MathUtil.equals(yaw, rot))\r
544                         return;\r
545                 this.yaw = MathUtil.reduce360(rot);\r
546                 internalRepaint();\r
547         }\r
548 \r
549         // ///////////// Extra methods\r
550 \r
551         private Coordinate project(Coordinate c, GL2 gl, GLU glu) {\r
552                 log.verbose("GL - project() called");\r
553                 double[] mvmatrix = new double[16];\r
554                 double[] projmatrix = new double[16];\r
555                 int[] viewport = new int[4];\r
556 \r
557                 gl.glGetIntegerv(GL.GL_VIEWPORT, viewport, 0);\r
558                 gl.glGetDoublev(GLMatrixFunc.GL_MODELVIEW_MATRIX, mvmatrix, 0);\r
559                 gl.glGetDoublev(GLMatrixFunc.GL_PROJECTION_MATRIX, projmatrix, 0);\r
560 \r
561                 double out[] = new double[4];\r
562                 glu.gluProject(c.x, c.y, c.z, mvmatrix, 0, projmatrix, 0, viewport, 0,\r
563                                 out, 0);\r
564                 \r
565                 log.verbose("GL - peoject() complete");\r
566                 return new Coordinate(out[0], out[1], out[2]);\r
567                 \r
568         }\r
569 \r
570         private Coordinate cp = new Coordinate(0, 0, 0);\r
571         private Coordinate cg = new Coordinate(0, 0, 0);\r
572 \r
573         public void setCG(Coordinate cg) {\r
574                 this.cg = cg;\r
575                 redrawExtras = true;\r
576         }\r
577 \r
578         public void setCP(Coordinate cp) {\r
579                 this.cp = cp;\r
580                 redrawExtras = true;\r
581         }\r
582 \r
583         public void addRelativeExtra(FigureElement p) {\r
584                 relativeExtra.add(p);\r
585                 redrawExtras = true;\r
586         }\r
587 \r
588         public void removeRelativeExtra(FigureElement p) {\r
589                 relativeExtra.remove(p);\r
590                 redrawExtras = true;\r
591         }\r
592 \r
593         public void clearRelativeExtra() {\r
594                 relativeExtra.clear();\r
595                 redrawExtras = true;\r
596         }\r
597 \r
598         public void addAbsoluteExtra(FigureElement p) {\r
599                 absoluteExtra.add(p);\r
600                 redrawExtras = true;\r
601         }\r
602 \r
603         public void removeAbsoluteExtra(FigureElement p) {\r
604                 absoluteExtra.remove(p);\r
605                 redrawExtras = true;\r
606         }\r
607 \r
608         public void clearAbsoluteExtra() {\r
609                 absoluteExtra.clear();\r
610                 redrawExtras = true;\r
611         }\r
612 \r
613         private ComponentSelectionListener csl;\r
614 \r
615         public static interface ComponentSelectionListener {\r
616                 public void componentClicked(RocketComponent[] components, MouseEvent e);\r
617         }\r
618 \r
619         public void addComponentSelectionListener(\r
620                         ComponentSelectionListener newListener) {\r
621                 this.csl = newListener;\r
622         }\r
623 \r
624 }\r