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