JOGL Based support for a 3D view of the rocket.
authorbkuker <bkuker@180e2498-e6e9-4542-8430-84ac67f01cd8>
Mon, 11 Jun 2012 20:25:41 +0000 (20:25 +0000)
committerbkuker <bkuker@180e2498-e6e9-4542-8430-84ac67f01cd8>
Mon, 11 Jun 2012 20:25:41 +0000 (20:25 +0000)
Change to RocketPanel to add 3D option.
Change to build process to use a Jar-in-Jar technique, not Fat Jar, using the Eclipse projects jar-in-jar loader. (Does not require eclipse!)

git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@772 180e2498-e6e9-4542-8430-84ac67f01cd8

24 files changed:
core/.classpath
core/build.xml
core/lib/jar-in-jar-loader.jar [new file with mode: 0644]
core/lib/native/gluegen-rt-natives-linux-amd64.jar [new file with mode: 0644]
core/lib/native/gluegen-rt-natives-linux-i586.jar [new file with mode: 0644]
core/lib/native/gluegen-rt-natives-macosx-universal.jar [new file with mode: 0644]
core/lib/native/gluegen-rt-natives-windows-amd64.jar [new file with mode: 0644]
core/lib/native/gluegen-rt-natives-windows-i586.jar [new file with mode: 0644]
core/lib/native/gluegen-rt.jar [new file with mode: 0644]
core/lib/native/jogl-all-natives-linux-amd64.jar [new file with mode: 0644]
core/lib/native/jogl-all-natives-linux-i586.jar [new file with mode: 0644]
core/lib/native/jogl-all-natives-macosx-universal.jar [new file with mode: 0644]
core/lib/native/jogl-all-natives-windows-amd64.jar [new file with mode: 0644]
core/lib/native/jogl-all-natives-windows-i586.jar [new file with mode: 0644]
core/lib/native/jogl.all.jar [new file with mode: 0644]
core/src/net/sf/openrocket/gui/figure3d/ComponentRenderer.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/figure3d/MassObjectRenderer.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/figure3d/Quick3dMain.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/figure3d/RocketRenderer.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/figure3d/TransitionRenderer.java [new file with mode: 0644]
core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java
core/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java
core/src/net/sf/openrocket/util/JarUtil.java

index 002d4561b6f234889558c1c3f76a17efe7013eaf..d50734aa2ea6bcb033bf1546c0542cd26de0e95c 100644 (file)
@@ -26,7 +26,9 @@
        <classpathentry kind="lib" path="lib-test/uispec4j-2.3-jdk16.jar"/>
        <classpathentry kind="lib" path="resources"/>
        <classpathentry kind="lib" path="lib/jspf.core-1.0.2.jar" sourcepath="/home/sampo/Projects/lib/jspf/documentation/api"/>
-       <classpathentry kind="lib" path="lib/opencsv-2.3.jar"/>
-       <classpathentry kind="lib" path="lib/exp4j-0.2.9.jar"/>
+       <classpathentry kind="lib" path="lib/opencsv-2.3.jar"/>\r
+       <classpathentry kind="lib" path="lib/native/gluegen-rt.jar"/>
+       <classpathentry kind="lib" path="lib/native/jogl.all.jar"/>\r
+       <classpathentry kind="lib" path="lib/exp4j-0.2.9.jar"/>\r
        <classpathentry kind="output" path="bin"/>
 </classpath>
index af9ed8a4cf1b6035302aeefa32089baf74560855..7f3ad8d0b5e698671a844e8d11b153b7ca8df47f 100644 (file)
                <javac debug="true" srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath"/>
        </target>
        
-       
-       <!-- JAR -->
-       <target name="jar" depends="build" description="Create the OpenRocket jar file">
-               <copy todir="${dist.dir}/">
-                       <fileset dir="." includes="LICENSE.TXT README.TXT ChangeLog ReleaseNotes fileformat.txt" />
-                       <fileset dir="resources/"/>
+       <!-- Executible Eclipse-Jar-In-Jar style JAR -->
+       <target name="jar" depends="core-jar" description="Create the OpenRocket jar-in-jar Executable">
+               <mkdir dir="${jar.dir}" />
+               <jar destfile="${jar.file}">
+                       <manifest>
+                               <attribute name="Main-Class" value="org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader" />
+                               <attribute name="Rsrc-Main-Class" value="${main-class}" />
+                               <attribute name="SplashScreen-Image" value="pix/splashscreen.png" />
+                               <attribute name="Class-Path" value="." />
+                               <attribute name="Rsrc-Class-Path" value="./ main/${ant.project.name}-Core.jar lib/jfreechart-1.0.13.jar lib/jcommon-1.0.16.jar lib/gluegen-rt.jar lib/miglayout15-swing.jar lib/iText-5.0.2.jar lib/jogl.all.jar lib/opencsv-2.3.jar" />
+                       </manifest>
+
+                       <!-- Unzip the Eclipse JIJ Loader -->
+                       <zipfileset src="lib/jar-in-jar-loader.jar" />
+                       
+                       <!-- Include, in the root of the JAR, the resources needed by OR -->
                        <fileset dir="src/" includes="META-INF/" />
-               </copy>
-               <mkdir dir="${jar.dir}"/>
-               <jar destfile="${jar.file}" basedir="${dist.dir}">
+                       <fileset dir="resources/" />
+                       
+                       <!-- Include the core OpenRocket JAR -->
+                       <zipfileset dir="${build.dir}/" prefix="main">
+                               <include name="${ant.project.name}-Core.jar"/>
+                       </zipfileset>
+                       
+                       <!-- Include libraries needed by OR -->
+                       <zipfileset dir="${lib.dir}" prefix="lib">
+                               <include name="*.jar"/>
+                       </zipfileset>
+                       <zipfileset dir="${lib.dir}/native" prefix="lib">
+                               <include name="*.jar"/>
+                       </zipfileset>
+                       
+                       <!-- Include metafiles about OR -->
+                       <fileset dir="." includes="LICENSE.TXT README.TXT ChangeLog ReleaseNotes fileformat.txt" />
+               </jar>
+       </target>
+       
+       
+       <!-- Core OpenRocket JAR -->
+       <target name="core-jar" depends="build" description="Create the OpenRocket code-only jar file">
+               <jar destfile="${build.dir}/${ant.project.name}-Core.jar" basedir="${dist.dir}">
                        <manifest>
                                <attribute name="Main-Class" value="${main-class}"/>
                                <attribute name="SplashScreen-Image" value="pix/splashscreen.png"/>
                        </manifest>
-                       <zipfileset src="lib/miglayout15-swing.jar" />
-                       <zipfileset src="lib/jcommon-1.0.16.jar" />
-                       <zipfileset src="lib/jfreechart-1.0.13.jar" />
-                       <zipfileset src="lib/iText-5.0.2.jar" />
-                       <zipfileset src="lib/opencsv-2.3.jar" />
                        <zipfileset src="lib/exp4j-0.2.9.jar" />
                </jar>
        </target>
diff --git a/core/lib/jar-in-jar-loader.jar b/core/lib/jar-in-jar-loader.jar
new file mode 100644 (file)
index 0000000..ebb1287
Binary files /dev/null and b/core/lib/jar-in-jar-loader.jar differ
diff --git a/core/lib/native/gluegen-rt-natives-linux-amd64.jar b/core/lib/native/gluegen-rt-natives-linux-amd64.jar
new file mode 100644 (file)
index 0000000..2beef19
Binary files /dev/null and b/core/lib/native/gluegen-rt-natives-linux-amd64.jar differ
diff --git a/core/lib/native/gluegen-rt-natives-linux-i586.jar b/core/lib/native/gluegen-rt-natives-linux-i586.jar
new file mode 100644 (file)
index 0000000..e0bc10f
Binary files /dev/null and b/core/lib/native/gluegen-rt-natives-linux-i586.jar differ
diff --git a/core/lib/native/gluegen-rt-natives-macosx-universal.jar b/core/lib/native/gluegen-rt-natives-macosx-universal.jar
new file mode 100644 (file)
index 0000000..05d72f8
Binary files /dev/null and b/core/lib/native/gluegen-rt-natives-macosx-universal.jar differ
diff --git a/core/lib/native/gluegen-rt-natives-windows-amd64.jar b/core/lib/native/gluegen-rt-natives-windows-amd64.jar
new file mode 100644 (file)
index 0000000..a77bcf1
Binary files /dev/null and b/core/lib/native/gluegen-rt-natives-windows-amd64.jar differ
diff --git a/core/lib/native/gluegen-rt-natives-windows-i586.jar b/core/lib/native/gluegen-rt-natives-windows-i586.jar
new file mode 100644 (file)
index 0000000..39fbfb2
Binary files /dev/null and b/core/lib/native/gluegen-rt-natives-windows-i586.jar differ
diff --git a/core/lib/native/gluegen-rt.jar b/core/lib/native/gluegen-rt.jar
new file mode 100644 (file)
index 0000000..de5c815
Binary files /dev/null and b/core/lib/native/gluegen-rt.jar differ
diff --git a/core/lib/native/jogl-all-natives-linux-amd64.jar b/core/lib/native/jogl-all-natives-linux-amd64.jar
new file mode 100644 (file)
index 0000000..951175b
Binary files /dev/null and b/core/lib/native/jogl-all-natives-linux-amd64.jar differ
diff --git a/core/lib/native/jogl-all-natives-linux-i586.jar b/core/lib/native/jogl-all-natives-linux-i586.jar
new file mode 100644 (file)
index 0000000..dd846ac
Binary files /dev/null and b/core/lib/native/jogl-all-natives-linux-i586.jar differ
diff --git a/core/lib/native/jogl-all-natives-macosx-universal.jar b/core/lib/native/jogl-all-natives-macosx-universal.jar
new file mode 100644 (file)
index 0000000..3edc295
Binary files /dev/null and b/core/lib/native/jogl-all-natives-macosx-universal.jar differ
diff --git a/core/lib/native/jogl-all-natives-windows-amd64.jar b/core/lib/native/jogl-all-natives-windows-amd64.jar
new file mode 100644 (file)
index 0000000..5eb5a76
Binary files /dev/null and b/core/lib/native/jogl-all-natives-windows-amd64.jar differ
diff --git a/core/lib/native/jogl-all-natives-windows-i586.jar b/core/lib/native/jogl-all-natives-windows-i586.jar
new file mode 100644 (file)
index 0000000..aee77f2
Binary files /dev/null and b/core/lib/native/jogl-all-natives-windows-i586.jar differ
diff --git a/core/lib/native/jogl.all.jar b/core/lib/native/jogl.all.jar
new file mode 100644 (file)
index 0000000..59a3eff
Binary files /dev/null and b/core/lib/native/jogl.all.jar differ
diff --git a/core/src/net/sf/openrocket/gui/figure3d/ComponentRenderer.java b/core/src/net/sf/openrocket/gui/figure3d/ComponentRenderer.java
new file mode 100644 (file)
index 0000000..80cdbfc
--- /dev/null
@@ -0,0 +1,340 @@
+package net.sf.openrocket.gui.figure3d;\r
+\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+\r
+import javax.media.opengl.GL;\r
+import javax.media.opengl.GL2;\r
+import javax.media.opengl.GLAutoDrawable;\r
+import javax.media.opengl.fixedfunc.GLLightingFunc;\r
+import javax.media.opengl.fixedfunc.GLMatrixFunc;\r
+import javax.media.opengl.glu.GLU;\r
+import javax.media.opengl.glu.GLUquadric;\r
+import javax.media.opengl.glu.GLUtessellator;\r
+import javax.media.opengl.glu.GLUtessellatorCallback;\r
+import javax.media.opengl.glu.GLUtessellatorCallbackAdapter;\r
+\r
+import net.sf.openrocket.logging.LogHelper;\r
+import net.sf.openrocket.rocketcomponent.BodyTube;\r
+import net.sf.openrocket.rocketcomponent.EllipticalFinSet;\r
+import net.sf.openrocket.rocketcomponent.FinSet;\r
+import net.sf.openrocket.rocketcomponent.LaunchLug;\r
+import net.sf.openrocket.rocketcomponent.MassObject;\r
+import net.sf.openrocket.rocketcomponent.RingComponent;\r
+import net.sf.openrocket.rocketcomponent.RocketComponent;\r
+import net.sf.openrocket.rocketcomponent.Transition;\r
+import net.sf.openrocket.startup.Application;\r
+import net.sf.openrocket.util.Coordinate;\r
+\r
+/*\r
+ * @author Bill Kuker <bkuker@billkuker.com>\r
+ */\r
+public class ComponentRenderer {\r
+       private static final LogHelper log = Application.getLogger();\r
+       \r
+       private int LOD = 80;\r
+\r
+       GLU glu;\r
+       GLUquadric q;\r
+       GLUtessellator tobj;\r
+\r
+       public ComponentRenderer() {\r
+\r
+       }\r
+\r
+       public void init(GLAutoDrawable drawable) {\r
+               glu = new GLU();\r
+               q = glu.gluNewQuadric();\r
+               tobj = GLU.gluNewTess();\r
+               glu.gluQuadricTexture(q, true);\r
+       }\r
+\r
+       private Map<RocketComponent, Integer> lists = new HashMap<RocketComponent, Integer>();\r
+       private boolean clearDisplayLists = false;\r
+       public void updateFigure() {\r
+               clearDisplayLists = true;\r
+       }\r
+       \r
+       public void renderGeometry(GL2 gl, RocketComponent c) {\r
+               if (glu == null)\r
+                       throw new IllegalStateException(this + " Not Initialized");\r
+\r
+               glu.gluQuadricNormals(q, GLU.GLU_SMOOTH);\r
+               \r
+               if ( clearDisplayLists ){\r
+                       log.debug("Clearing Display Lists");\r
+                       for ( int i : lists.values() ){\r
+                               gl.glDeleteLists(i,1);\r
+                       }\r
+                       lists.clear();\r
+                       clearDisplayLists = false;\r
+               }\r
+               if ( lists.containsKey(c) ){\r
+                       gl.glCallList(lists.get(c));\r
+               } else {\r
+                       int list = gl.glGenLists(1);\r
+                       gl.glNewList(list, GL2.GL_COMPILE_AND_EXECUTE);\r
+\r
+                       Coordinate[] oo = c.toAbsolute(new Coordinate(0, 0, 0));\r
+\r
+                       for (Coordinate o : oo) {\r
+                               gl.glPushMatrix();\r
+\r
+                               gl.glTranslated(o.x, o.y, o.z);\r
+\r
+                               if (c instanceof BodyTube) {\r
+                                       renderTube(gl, (BodyTube) c);\r
+                               } else if (c instanceof LaunchLug) {\r
+                                       renderLug(gl, (LaunchLug) c);\r
+                               } else if (c instanceof RingComponent) {\r
+                                       renderRing(gl, (RingComponent) c);\r
+                               } else if (c instanceof Transition) {\r
+                                       renderTransition(gl, (Transition) c);\r
+                               } else if (c instanceof MassObject) {\r
+                                       renderMassObject(gl, (MassObject) c);\r
+                               } else if (c instanceof FinSet) {\r
+                                       renderFinSet(gl, (FinSet) c);\r
+                               } else {\r
+                                       renderOther(gl, c);\r
+                               }\r
+                               gl.glPopMatrix();\r
+                       }\r
+                       \r
+                       gl.glEndList();\r
+                       lists.put(c, list);\r
+               }\r
+       }\r
+\r
+       private void renderOther(GL2 gl, RocketComponent c) {\r
+               gl.glBegin(GL.GL_LINES);\r
+               for (Coordinate cc : c.getComponentBounds()) {\r
+                       for (Coordinate ccc : c.getComponentBounds()) {\r
+                               gl.glVertex3d(cc.x, cc.y, cc.z);\r
+                               gl.glVertex3d(ccc.x, ccc.y, ccc.z);\r
+                       }\r
+               }\r
+               gl.glEnd();\r
+       }\r
+\r
+       private void renderTransition(GL2 gl, Transition t) {\r
+               gl.glRotated(90, 0, 1.0, 0);\r
+\r
+               if (t.getType() == Transition.Shape.CONICAL) {\r
+                       glu.gluCylinder(q, t.getForeRadius(), t.getAftRadius(),\r
+                                       t.getLength(), LOD, 1);\r
+               } else {\r
+                       TransitionRenderer.drawTransition(gl, t, LOD, LOD);\r
+               }\r
+\r
+               // Render AFT shoulder\r
+               gl.glPushMatrix();\r
+               gl.glTranslated(0, 0, t.getLength());\r
+\r
+               glu.gluCylinder(q, t.getAftShoulderRadius(), t.getAftShoulderRadius(),\r
+                               t.getAftShoulderLength(), LOD, 1);\r
+\r
+               gl.glRotated(180, 0, 1.0, 0);\r
+\r
+               glu.gluDisk(q, t.getAftRadius(), t.getAftShoulderRadius(), LOD, 2);\r
+\r
+               gl.glTranslated(0, 0, -t.getAftShoulderLength());\r
+\r
+               if (t.isFilled() || t.isAftShoulderCapped()) {\r
+                       glu.gluDisk(q, t.getAftShoulderRadius(), 0, LOD, 2);\r
+               }\r
+               gl.glPopMatrix();\r
+\r
+               // Render Fore Shoulder\r
+               gl.glPushMatrix();\r
+               gl.glRotated(180, 0, 1.0, 0);\r
+\r
+               glu.gluCylinder(q, t.getForeShoulderRadius(),\r
+                               t.getForeShoulderRadius(), t.getForeShoulderLength(), LOD, 1);\r
+\r
+               gl.glRotated(180, 0, 1.0, 0);\r
+\r
+               glu.gluDisk(q, t.getForeRadius(), t.getForeShoulderRadius(), LOD, 2);\r
+\r
+               gl.glTranslated(0, 0, -t.getForeShoulderLength());\r
+\r
+               if (t.isFilled() || t.isForeShoulderCapped()) {\r
+                       glu.gluDisk(q, t.getForeShoulderRadius(), 0, LOD, 2);\r
+               }\r
+               gl.glPopMatrix();\r
+\r
+       }\r
+\r
+       private void renderTube(GL2 gl, BodyTube t) {\r
+               gl.glRotated(90, 0, 1.0, 0);\r
+               glu.gluCylinder(q, t.getOuterRadius(), t.getOuterRadius(),\r
+                               t.getLength(), LOD, 1);\r
+       }\r
+\r
+       private void renderRing(GL2 gl, RingComponent r) {\r
+               gl.glRotated(90, 0, 1.0, 0);\r
+               glu.gluCylinder(q, r.getOuterRadius(), r.getOuterRadius(),\r
+                               r.getLength(), LOD, 1);\r
+\r
+               gl.glRotated(180, 0, 1.0, 0);\r
+               glu.gluDisk(q, r.getInnerRadius(), r.getOuterRadius(), LOD, 2);\r
+\r
+               gl.glRotated(180, 0, 1.0, 0);\r
+               gl.glTranslated(0, 0, r.getLength());\r
+               glu.gluDisk(q, r.getInnerRadius(), r.getOuterRadius(), LOD, 2);\r
+\r
+               gl.glTranslated(0, 0, -r.getLength());\r
+               glu.gluCylinder(q, r.getInnerRadius(), r.getInnerRadius(),\r
+                               r.getLength(), LOD, 1);\r
+\r
+       }\r
+\r
+       private void renderLug(GL2 gl, LaunchLug t) {\r
+\r
+               gl.glRotated(90, 0, 1.0, 0);\r
+               glu.gluCylinder(q, t.getOuterRadius(), t.getOuterRadius(),\r
+                               t.getLength(), LOD, 1);\r
+       }\r
+\r
+       private void renderMassObject(GL2 gl, MassObject o) {\r
+               gl.glRotated(90, 0, 1.0, 0);\r
+\r
+               MassObjectRenderer.drawMassObject(gl, o, LOD, LOD);\r
+       }\r
+\r
+       private void renderFinSet(final GL2 gl, FinSet fs) {\r
+               \r
+               Coordinate finPoints[] = fs.getFinPointsWithTab();\r
+               \r
+               double minX = Double.MAX_VALUE;\r
+               double minY = Double.MAX_VALUE;\r
+               double maxX = Double.MIN_VALUE;\r
+               double maxY = Double.MIN_VALUE;\r
+       \r
+               for (int i = 0; i < finPoints.length; i++) {\r
+                       Coordinate c = finPoints[i];\r
+                       minX = Math.min(c.x, minX);     \r
+                       minY = Math.min(c.y, minY);\r
+                       maxX = Math.max(c.x, maxX);\r
+                       maxY = Math.max(c.y, maxY);     \r
+               }\r
+               \r
+               gl.glMatrixMode(GL.GL_TEXTURE);\r
+               gl.glPushMatrix();\r
+               gl.glScaled(1/(maxX-minX), 1/(maxY-minY), 0);\r
+               gl.glTranslated(-minX, -minY - fs.getBodyRadius(), 0);\r
+               gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);\r
+               \r
+               gl.glRotated(fs.getBaseRotation() * (180.0 / Math.PI), 1, 0, 0);\r
+               \r
+               for (int fin = 0; fin < fs.getFinCount(); fin++) {\r
+\r
+                       gl.glPushMatrix();\r
+\r
+                       gl.glTranslated(fs.getLength() / 2, 0, 0);\r
+                       gl.glRotated(fs.getCantAngle() * (180.0 / Math.PI), 0, 1, 0);\r
+                       gl.glTranslated(-fs.getLength() / 2, 0, 0);\r
+\r
+                       GLUtessellatorCallback cb = new GLUtessellatorCallbackAdapter() {\r
+                               @Override\r
+                               public void vertex(Object vertexData) {\r
+                                       double d[] = (double[]) vertexData;\r
+                                       gl.glTexCoord2d(d[0], d[1]);\r
+                                       gl.glVertex3dv(d, 0);\r
+                               }\r
+\r
+                               @Override\r
+                               public void begin(int type) {\r
+                                       gl.glBegin(type);\r
+                               }\r
+\r
+                               @Override\r
+                               public void end() {\r
+                                       gl.glEnd();\r
+                               }\r
+                       };\r
+\r
+                       GLU.gluTessCallback(tobj, GLU.GLU_TESS_VERTEX, cb);\r
+                       GLU.gluTessCallback(tobj, GLU.GLU_TESS_BEGIN, cb);\r
+                       GLU.gluTessCallback(tobj, GLU.GLU_TESS_END, cb);\r
+\r
+                       GLU.gluTessBeginPolygon(tobj, null);\r
+                       GLU.gluTessBeginContour(tobj);\r
+                       gl.glNormal3f(0, 0, 1);\r
+                       for (int i = finPoints.length - 1; i >= 0; i--) {\r
+                               Coordinate c = finPoints[i];\r
+                               double[] p = new double[] { c.x, c.y + fs.getBodyRadius(),\r
+                                               c.z + fs.getThickness() / 2.0 };\r
+                               GLU.gluTessVertex(tobj, p, 0, p);\r
+\r
+                       }\r
+                       GLU.gluTessEndContour(tobj);\r
+                       GLU.gluTessEndPolygon(tobj);\r
+\r
+                       GLU.gluTessBeginPolygon(tobj, null);\r
+                       GLU.gluTessBeginContour(tobj);\r
+                       gl.glNormal3f(0, 0, -1);\r
+                       for (int i = 0; i < finPoints.length; i++) {\r
+                               Coordinate c = finPoints[i];\r
+                               double[] p = new double[] { c.x, c.y + fs.getBodyRadius(),\r
+                                               c.z - fs.getThickness() / 2.0 };\r
+                               GLU.gluTessVertex(tobj, p, 0, p);\r
+\r
+                       }\r
+                       GLU.gluTessEndContour(tobj);\r
+                       GLU.gluTessEndPolygon(tobj);\r
+\r
+                       // Strip around the edge\r
+                       if (!(fs instanceof EllipticalFinSet))\r
+                               gl.glShadeModel(GLLightingFunc.GL_FLAT);\r
+                       gl.glBegin(GL.GL_TRIANGLE_STRIP);\r
+                       for (int i = 0; i <= finPoints.length; i++) {\r
+                               Coordinate c = finPoints[i % finPoints.length];\r
+                               // if ( i > 1 ){\r
+                               Coordinate c2 = finPoints[(i - 1 + finPoints.length)\r
+                                               % finPoints.length];\r
+                               gl.glNormal3d(c2.y - c.y, c.x - c2.x, 0);\r
+                               // }\r
+                               gl.glTexCoord2d(c.x, c.y + fs.getBodyRadius());\r
+                               gl.glVertex3d(c.x, c.y + fs.getBodyRadius(),\r
+                                               c.z - fs.getThickness() / 2.0);\r
+                               gl.glVertex3d(c.x, c.y + fs.getBodyRadius(),\r
+                                               c.z + fs.getThickness() / 2.0);\r
+                       }\r
+                       gl.glEnd();\r
+                       if (!(fs instanceof EllipticalFinSet))\r
+                               gl.glShadeModel(GLLightingFunc.GL_SMOOTH);\r
+\r
+                       gl.glPopMatrix();\r
+\r
+                       gl.glRotated(360.0 / fs.getFinCount(), 1, 0, 0);\r
+               }\r
+               \r
+               gl.glMatrixMode(GL.GL_TEXTURE);\r
+               gl.glPopMatrix();\r
+               gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);\r
+               \r
+       }\r
+\r
+       public void renderMotor(final GL2 gl, final Coordinate c, double l, double r) {\r
+               final float outside[] = { 0.2f, 0.2f, 0.2f, 1.0f };\r
+               gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_DIFFUSE, outside, 0);\r
+               gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_AMBIENT, outside, 0);\r
+\r
+               gl.glPushMatrix();\r
+\r
+               gl.glTranslated(c.x, c.y, c.z);\r
+\r
+               gl.glRotated(90, 0, 1.0, 0);\r
+\r
+               glu.gluCylinder(q, r, r, l, LOD, 1);\r
+\r
+               glu.gluDisk(q, r, 0, LOD, 2);\r
+\r
+               gl.glTranslated(0, 0, l);\r
+               gl.glRotated(180, 0, 1.0, 0);\r
+\r
+               glu.gluDisk(q, r, 0, LOD, 2);\r
+\r
+               gl.glPopMatrix();\r
+       }\r
+}\r
diff --git a/core/src/net/sf/openrocket/gui/figure3d/MassObjectRenderer.java b/core/src/net/sf/openrocket/gui/figure3d/MassObjectRenderer.java
new file mode 100644 (file)
index 0000000..4c1d934
--- /dev/null
@@ -0,0 +1,263 @@
+/*\r
+ ** License Applicability. Except to the extent portions of this file are\r
+ ** made subject to an alternative license as permitted in the SGI Free\r
+ ** Software License B, Version 2.0 (the "License"), the contents of this\r
+ ** file are subject only to the provisions of the License. You may not use\r
+ ** this file except in compliance with the License. You may obtain a copy\r
+ ** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600\r
+ ** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at:\r
+ ** \r
+ ** http://oss.sgi.com/projects/FreeB\r
+ ** \r
+ ** Note that, as provided in the License, the Software is distributed on an\r
+ ** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS\r
+ ** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND\r
+ ** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A\r
+ ** PARTICULAR PURPOSE, AND NON-INFRINGEMENT.\r
+ ** \r
+ ** NOTE:  The Original Code (as defined below) has been licensed to Sun\r
+ ** Microsystems, Inc. ("Sun") under the SGI Free Software License B\r
+ ** (Version 1.1), shown above ("SGI License").   Pursuant to Section\r
+ ** 3.2(3) of the SGI License, Sun is distributing the Covered Code to\r
+ ** you under an alternative license ("Alternative License").  This\r
+ ** Alternative License includes all of the provisions of the SGI License\r
+ ** except that Section 2.2 and 11 are omitted.  Any differences between\r
+ ** the Alternative License and the SGI License are offered solely by Sun\r
+ ** and not by SGI.\r
+ **\r
+ ** Original Code. The Original Code is: OpenGL Sample Implementation,\r
+ ** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics,\r
+ ** Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc.\r
+ ** Copyright in any portions created by third parties is as indicated\r
+ ** elsewhere herein. All Rights Reserved.\r
+ ** \r
+ ** Additional Notice Provisions: The application programming interfaces\r
+ ** established by SGI in conjunction with the Original Code are The\r
+ ** OpenGL(R) Graphics System: A Specification (Version 1.2.1), released\r
+ ** April 1, 1999; The OpenGL(R) Graphics System Utility Library (Version\r
+ ** 1.3), released November 4, 1998; and OpenGL(R) Graphics with the X\r
+ ** Window System(R) (Version 1.3), released October 19, 1998. This software\r
+ ** was created using the OpenGL(R) version 1.2.1 Sample Implementation\r
+ ** published by SGI, but has not been independently verified as being\r
+ ** compliant with the OpenGL(R) version 1.2.1 Specification.\r
+ **\r
+ ** $Date: 2009-03-04 17:23:34 -0800 (Wed, 04 Mar 2009) $ $Revision: 1856 $\r
+ ** $Header$\r
+ */\r
+\r
+/* \r
+ * Copyright (c) 2002-2004 LWJGL Project\r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are \r
+ * met:\r
+ * \r
+ * * Redistributions of source code must retain the above copyright \r
+ *   notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * * Redistributions in binary form must reproduce the above copyright\r
+ *   notice, this list of conditions and the following disclaimer in the\r
+ *   documentation and/or other materials provided with the distribution.\r
+ *\r
+ * * Neither the name of 'LWJGL' nor the names of \r
+ *   its contributors may be used to endorse or promote products derived \r
+ *   from this software without specific prior written permission.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\r
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR \r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, \r
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, \r
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \r
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\r
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING \r
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+/*\r
+ * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are\r
+ * met:\r
+ * \r
+ * - Redistribution of source code must retain the above copyright\r
+ *   notice, this list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistribution in binary form must reproduce the above copyright\r
+ *   notice, this list of conditions and the following disclaimer in the\r
+ *   documentation and/or other materials provided with the distribution.\r
+ * \r
+ * Neither the name of Sun Microsystems, Inc. or the names of\r
+ * contributors may be used to endorse or promote products derived from\r
+ * this software without specific prior written permission.\r
+ * \r
+ * This software is provided "AS IS," without a warranty of any kind. ALL\r
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,\r
+ * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A\r
+ * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN\r
+ * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR\r
+ * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR\r
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR\r
+ * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR\r
+ * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE\r
+ * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,\r
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF\r
+ * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.\r
+ * \r
+ * You acknowledge that this software is not designed or intended for use\r
+ * in the design, construction, operation or maintenance of any nuclear\r
+ * facility.\r
+ */\r
+package net.sf.openrocket.gui.figure3d;\r
+\r
+import javax.media.opengl.GL;\r
+import javax.media.opengl.GL2;\r
+\r
+import net.sf.openrocket.rocketcomponent.MassObject;\r
+\r
+public final class MassObjectRenderer {\r
+       private static final boolean textureFlag = true;\r
+\r
+       private MassObjectRenderer() {\r
+       }\r
+\r
+       public static final void drawMassObject(final GL2 gl, final MassObject o,\r
+                       final int slices, final int stacks) {\r
+\r
+               double da, r, dz;\r
+               double x, y, z, nz, nsign;\r
+               int i, j;\r
+\r
+               nsign = 1.0f;\r
+\r
+               da = 2.0f * PI / slices;\r
+               dz = o.getLength() / stacks;\r
+\r
+               double ds = 1.0f / slices;\r
+               double dt = 1.0f / stacks;\r
+               double t = 0.0f;\r
+               z = 0.0f;\r
+               for (j = 0; j < stacks; j++) {\r
+                       r = getRadius(o, z);\r
+                       double rNext = getRadius(o, z + dz);\r
+                       if (j == stacks - 1)\r
+                               rNext = 0;\r
+\r
+                       if (j == stacks - 1)\r
+                               rNext = 0;\r
+\r
+                       // Z component of normal vectors\r
+                       nz = -(rNext - r) / dz;\r
+\r
+                       double s = 0.0f;\r
+                       glBegin(gl, GL2.GL_QUAD_STRIP);\r
+                       for (i = 0; i <= slices; i++) {\r
+                               if (i == slices) {\r
+                                       x = sin(0.0f);\r
+                                       y = cos(0.0f);\r
+                               } else {\r
+                                       x = sin((i * da));\r
+                                       y = cos((i * da));\r
+                               }\r
+                               if (nsign == 1.0f) {\r
+                                       normal3d(gl, (x * nsign), (y * nsign), (nz * nsign));\r
+                                       TXTR_COORD(gl, s, t);\r
+                                       glVertex3d(gl, (x * r), (y * r), z);\r
+                                       normal3d(gl, (x * nsign), (y * nsign), (nz * nsign));\r
+                                       TXTR_COORD(gl, s, t + dt);\r
+                                       glVertex3d(gl, (x * rNext), (y * rNext), (z + dz));\r
+                               } else {\r
+                                       normal3d(gl, x * nsign, y * nsign, nz * nsign);\r
+                                       TXTR_COORD(gl, s, t);\r
+                                       glVertex3d(gl, (x * r), (y * r), z);\r
+                                       normal3d(gl, x * nsign, y * nsign, nz * nsign);\r
+                                       TXTR_COORD(gl, s, t + dt);\r
+                                       glVertex3d(gl, (x * rNext), (y * rNext), (z + dz));\r
+                               }\r
+                               s += ds;\r
+                       } // for slices\r
+                       glEnd(gl);\r
+                       // r += dr;\r
+                       t += dt;\r
+                       z += dz;\r
+               } // for stacks\r
+       }\r
+\r
+       private static final double getRadius(MassObject o, double z) {\r
+               double arc = Math.min(o.getLength(), 2 * o.getRadius()) * 0.35f;\r
+               double r = o.getRadius();\r
+               if (z == 0 || z == o.getLength())\r
+                       return 0;\r
+               if (z < arc) {\r
+                       double zz = z - arc;\r
+                       return (r - arc) + Math.sqrt(arc * arc - zz * zz);\r
+               }\r
+               if (z > o.getLength() - arc) {\r
+                       double zz = (z - o.getLength() + arc);\r
+                       return (r - arc) + Math.sqrt(arc * arc - zz * zz);\r
+               }\r
+               return o.getRadius();\r
+       }\r
+\r
+       // ----------------------------------------------------------------------\r
+       // Internals only below this point\r
+       //\r
+\r
+       private static final double PI = Math.PI;\r
+\r
+       private static final void glBegin(GL gl, int mode) {\r
+               gl.getGL2().glBegin(mode);\r
+       }\r
+\r
+       private static final void glEnd(GL gl) {\r
+               gl.getGL2().glEnd();\r
+       }\r
+\r
+       private static final void glVertex3d(GL gl, double x, double y, double z) {\r
+               gl.getGL2().glVertex3d(x, y, z);\r
+       }\r
+\r
+       private static final void glNormal3d(GL gl, double x, double y, double z) {\r
+               gl.getGL2().glNormal3d(x, y, z);\r
+       }\r
+\r
+       private static final void glTexCoord2d(GL gl, double x, double y) {\r
+               gl.getGL2().glTexCoord2d(x, y);\r
+       }\r
+\r
+       /**\r
+        * Call glNormal3f after scaling normal to unit length.\r
+        * \r
+        * @param x\r
+        * @param y\r
+        * @param z\r
+        */\r
+       private static final void normal3d(GL gl, double x, double y, double z) {\r
+               double mag;\r
+\r
+               mag = Math.sqrt(x * x + y * y + z * z);\r
+               if (mag > 0.00001F) {\r
+                       x /= mag;\r
+                       y /= mag;\r
+                       z /= mag;\r
+               }\r
+               glNormal3d(gl, x, y, z);\r
+       }\r
+\r
+       private static final void TXTR_COORD(GL gl, double x, double y) {\r
+               if (textureFlag)\r
+                       glTexCoord2d(gl, x, y);\r
+       }\r
+\r
+       private static final double sin(double r) {\r
+               return Math.sin(r);\r
+       }\r
+\r
+       private static final double cos(double r) {\r
+               return Math.cos(r);\r
+       }\r
+}\r
diff --git a/core/src/net/sf/openrocket/gui/figure3d/Quick3dMain.java b/core/src/net/sf/openrocket/gui/figure3d/Quick3dMain.java
new file mode 100644 (file)
index 0000000..49dbbc9
--- /dev/null
@@ -0,0 +1,70 @@
+package net.sf.openrocket.gui.figure3d;\r
+import java.awt.BorderLayout;\r
+\r
+import javax.swing.JFrame;\r
+import javax.swing.JPanel;\r
+\r
+import net.sf.openrocket.database.ComponentPresetDatabase;\r
+import net.sf.openrocket.database.ThrustCurveMotorSetDatabase;\r
+import net.sf.openrocket.document.OpenRocketDocument;\r
+import net.sf.openrocket.file.DatabaseMotorFinder;\r
+import net.sf.openrocket.file.openrocket.importt.OpenRocketLoader;\r
+import net.sf.openrocket.gui.main.componenttree.ComponentTree;\r
+import net.sf.openrocket.gui.scalefigure.RocketPanel;\r
+import net.sf.openrocket.gui.util.SwingPreferences;\r
+import net.sf.openrocket.l10n.ResourceBundleTranslator;\r
+import net.sf.openrocket.startup.Application;\r
+\r
+/**\r
+ * An application for quickly testing 3d figure witout all the OpenRocket user interface\r
+ * \r
+ * @author bkuker\r
+ *\r
+ */\r
+public class Quick3dMain {\r
+\r
+       /**\r
+        * @param args\r
+        */\r
+       public static void main(String[] args) throws Exception {\r
+               Application.setBaseTranslator(new ResourceBundleTranslator(\r
+                               "l10n.messages"));\r
+               Application.setMotorSetDatabase(new ThrustCurveMotorSetDatabase(false) {\r
+                       {\r
+                               startLoading();\r
+                       }\r
+\r
+                       @Override\r
+                       protected void loadMotors() {\r
+                       }\r
+               });\r
+               Application.setPreferences(new SwingPreferences());\r
+               \r
+               // Must be done after localization is initialized\r
+               ComponentPresetDatabase componentPresetDao = new ComponentPresetDatabase();\r
+               componentPresetDao.load("datafiles", ".*csv");\r
+               Application.setComponentPresetDao( componentPresetDao );\r
+\r
+               OpenRocketDocument doc = new OpenRocketLoader().loadFromStream(\r
+                               Quick3dMain.class.getResourceAsStream("/datafiles/examples/Clustered rocket design.ork"),\r
+                               new DatabaseMotorFinder());\r
+\r
+               JFrame ff = new JFrame();\r
+               ff.setSize(1200, 400);\r
+               ff.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);\r
+\r
+               RocketPanel panel;\r
+\r
+               panel = new RocketPanel(doc);\r
+\r
+               ComponentTree ct = new ComponentTree(doc);\r
+               panel.setSelectionModel(ct.getSelectionModel());\r
+\r
+               JPanel p = new JPanel();\r
+               p.setLayout(new BorderLayout());\r
+               p.add(ct, BorderLayout.WEST);\r
+               p.add(panel, BorderLayout.CENTER);\r
+               ff.setContentPane(p);\r
+               ff.setVisible(true);\r
+       }\r
+}\r
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..3402f36
--- /dev/null
@@ -0,0 +1,586 @@
+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.event.HierarchyEvent;\r
+import java.awt.event.HierarchyListener;\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
+               addHierarchyListener(new HierarchyListener() {\r
+                       @Override\r
+                       public void hierarchyChanged(HierarchyEvent e) {\r
+                               initGLCanvas();\r
+                               RocketFigure3d.this.removeHierarchyListener(this);\r
+                       }\r
+               });\r
+       }\r
+       \r
+       private void initGLCanvas(){\r
+               log.debug("Initializing RocketFigure3D OpenGL Canvas");\r
+               try {\r
+                       log.debug("Setting up GL capabilities...");\r
+                       GLProfile glp = GLProfile.getDefault();\r
+                       GLCapabilities caps = new GLCapabilities(glp);\r
+                       caps.setSampleBuffers(true);\r
+                       caps.setNumSamples(6);\r
+                       caps.setStencilBits(1);\r
+\r
+                       log.debug("Creating OpenGL Canvas");\r
+                       canvas = new GLCanvas(caps);\r
+\r
+                       canvas.addGLEventListener(this);\r
+                       this.add(canvas, BorderLayout.CENTER);\r
+\r
+                       setupMouseListeners();\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
+       }\r
+\r
+       @Override\r
+       public void init(GLAutoDrawable drawable) {\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
+\r
+       @Override\r
+       public void reshape(GLAutoDrawable drawable, int x, int y, int w, int h) {\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
+       }\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
+               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
+\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
+               super.repaint();\r
+               if (canvas != null)\r
+                       canvas.display();\r
+       }\r
+       \r
+       @Override\r
+       public void repaint() {\r
+               redrawExtras = true;\r
+               internalRepaint();\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
+               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
+               return new Coordinate(out[0], out[1], out[2]);\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
diff --git a/core/src/net/sf/openrocket/gui/figure3d/RocketRenderer.java b/core/src/net/sf/openrocket/gui/figure3d/RocketRenderer.java
new file mode 100644 (file)
index 0000000..4934102
--- /dev/null
@@ -0,0 +1,303 @@
+package net.sf.openrocket.gui.figure3d;\r
+\r
+import java.awt.Point;\r
+import java.nio.ByteBuffer;\r
+import java.util.HashMap;\r
+import java.util.Iterator;\r
+import java.util.Set;\r
+import java.util.Vector;\r
+\r
+import javax.media.opengl.GL;\r
+import javax.media.opengl.GL2;\r
+import javax.media.opengl.GL2ES1;\r
+import javax.media.opengl.GL2GL3;\r
+import javax.media.opengl.GLAutoDrawable;\r
+import javax.media.opengl.fixedfunc.GLLightingFunc;\r
+\r
+import net.sf.openrocket.motor.Motor;\r
+import net.sf.openrocket.rocketcomponent.BodyTube;\r
+import net.sf.openrocket.rocketcomponent.Configuration;\r
+import net.sf.openrocket.rocketcomponent.ExternalComponent;\r
+import net.sf.openrocket.rocketcomponent.MotorMount;\r
+import net.sf.openrocket.rocketcomponent.NoseCone;\r
+import net.sf.openrocket.rocketcomponent.RocketComponent;\r
+import net.sf.openrocket.rocketcomponent.SymmetricComponent;\r
+import net.sf.openrocket.rocketcomponent.Transition;\r
+import net.sf.openrocket.startup.Application;\r
+import net.sf.openrocket.util.Color;\r
+import net.sf.openrocket.util.Coordinate;\r
+\r
+/*\r
+ * @author Bill Kuker <bkuker@billkuker.com>\r
+ */\r
+public class RocketRenderer {\r
+       ComponentRenderer cr;\r
+\r
+       private final float[] selectedEmissive = { 1, 0, 0, 1 };\r
+       private final float[] colorBlack = { 0, 0, 0, 1 };\r
+       private final float[] color = new float[4];\r
+\r
+       public void init(GLAutoDrawable drawable) {\r
+               cr = new ComponentRenderer();\r
+               cr.init(drawable);\r
+       }\r
+       \r
+       public void updateFigure() {\r
+               cr.updateFigure();\r
+       }\r
+\r
+       private boolean isDrawn(RocketComponent c) {\r
+               return true;\r
+       }\r
+\r
+       private boolean isDrawnTransparent(RocketComponent c) {\r
+               if (c instanceof BodyTube)\r
+                       return true;\r
+               if (c instanceof NoseCone)\r
+                       return false;\r
+               if (c instanceof SymmetricComponent) {\r
+                       if (((SymmetricComponent) c).isFilled())\r
+                               return false;\r
+               }\r
+               if (c instanceof Transition) {\r
+                       Transition t = (Transition) c;\r
+                       return !t.isAftShoulderCapped() && !t.isForeShoulderCapped();\r
+               }\r
+               return false;\r
+       }\r
+\r
+       public RocketComponent pick(GLAutoDrawable drawable,\r
+                       Configuration configuration, Point p, Set<RocketComponent> ignore) {\r
+               final GL2 gl = drawable.getGL().getGL2();\r
+               gl.glEnable(GL.GL_DEPTH_TEST);\r
+               \r
+               //Store a vector of pickable parts.\r
+               final Vector<RocketComponent> pickParts = new Vector<RocketComponent>();\r
+               \r
+               for (RocketComponent c : configuration) {\r
+                       if ( ignore != null && ignore.contains(c) )\r
+                               continue;\r
+\r
+                       //Encode the index of the part as a color\r
+                       //if index is 0x0ABC the color ends up as\r
+                       //0xA0B0C000 with each nibble in the coresponding\r
+                       //high bits of the RG and B channels.\r
+                       gl.glColor4ub((byte) ((pickParts.size() >> 4) & 0xF0),\r
+                                       (byte) ((pickParts.size() << 0) & 0xF0),\r
+                                       (byte) ((pickParts.size() << 4) & 0xF0), (byte) 1);\r
+                       pickParts.add(c);\r
+                       \r
+                       if (isDrawnTransparent(c)) {\r
+                               gl.glEnable(GL.GL_CULL_FACE);\r
+                               gl.glCullFace(GL.GL_FRONT);\r
+                               cr.renderGeometry(gl, c);\r
+                               gl.glDisable(GL.GL_CULL_FACE);\r
+                       } else {\r
+                               cr.renderGeometry(gl, c);\r
+                       }\r
+               }\r
+\r
+               ByteBuffer bb = ByteBuffer.allocateDirect(4);\r
+\r
+               gl.glReadPixels(p.x, p.y, 1, 1, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, bb);\r
+\r
+               final int pickColor = bb.getInt();\r
+               final int pickIndex = ((pickColor >> 20) & 0xF00) | ((pickColor >> 16) & 0x0F0)\r
+                               | ((pickColor >> 12) & 0x00F);\r
+\r
+               if ( pickIndex < 0 || pickIndex > pickParts.size() - 1 )\r
+                       return null;\r
+               \r
+               return pickParts.get(pickIndex);\r
+       }\r
+\r
+       public void render(GLAutoDrawable drawable, Configuration configuration,\r
+                       Set<RocketComponent> selection) {\r
+               if (cr == null)\r
+                       throw new IllegalStateException(this + " Not Initialized");\r
+\r
+               GL2 gl = drawable.getGL().getGL2();\r
+\r
+               gl.glEnable(GL.GL_DEPTH_TEST); // enables depth testing\r
+               gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);\r
+\r
+               // Draw all inner components\r
+               for (RocketComponent c : configuration) {\r
+                       if (isDrawn(c)) {\r
+                               if (!isDrawnTransparent(c)) {\r
+                                       renderComponent(gl, c, 1.0f);\r
+                               }\r
+                       }\r
+               }\r
+\r
+               renderMotors(gl, configuration);\r
+\r
+               // Draw Tube and Transition back faces, blended with depth test\r
+               // so that they show up behind.\r
+               gl.glEnable(GL.GL_CULL_FACE);\r
+               gl.glCullFace(GL.GL_FRONT);\r
+               for (RocketComponent c : configuration) {\r
+                       if (isDrawn(c)) {\r
+                               if (isDrawnTransparent(c)) {\r
+                                       renderComponent(gl, c, 1.0f);\r
+                               }\r
+                       }\r
+               }\r
+               gl.glDisable(GL.GL_CULL_FACE);\r
+\r
+               // Draw T&T front faces blended, without depth test\r
+               gl.glEnable(GL.GL_BLEND);\r
+               gl.glEnable(GL.GL_CULL_FACE);\r
+               gl.glCullFace(GL.GL_BACK);\r
+               for (RocketComponent c : configuration) {\r
+                       if (isDrawn(c)) {\r
+                               if (isDrawnTransparent(c)) {\r
+                                       renderComponent(gl, c, 0.2f);\r
+                               }\r
+                       }\r
+               }\r
+               gl.glDisable(GL.GL_BLEND);\r
+               gl.glDisable(GL.GL_CULL_FACE);\r
+\r
+               gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_EMISSION,\r
+                               selectedEmissive, 0);\r
+               gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_DIFFUSE, colorBlack, 0);\r
+               gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_AMBIENT, colorBlack, 0);\r
+               gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_SPECULAR, colorBlack, 0);\r
+               \r
+               gl.glDepthMask(false);\r
+               gl.glDisable(GL.GL_DEPTH_TEST);\r
+               gl.glEnable(GL.GL_STENCIL_TEST);\r
+\r
+               for (RocketComponent c : configuration) {\r
+                       if (selection.contains(c)) {\r
+                               // So it is faster to do this once before the loop,\r
+                               // but then the outlines are not as good if you multi-select.\r
+                               // Not sure which to do.\r
+\r
+                               gl.glStencilMask(1);\r
+                               gl.glDisable(GL.GL_SCISSOR_TEST);\r
+                               gl.glClearStencil(0);\r
+                               gl.glClear(GL.GL_STENCIL_BUFFER_BIT);\r
+                               gl.glStencilMask(0);\r
+\r
+                               gl.glStencilFunc(GL.GL_ALWAYS, 1, 1);\r
+                               gl.glStencilMask(1);\r
+                               gl.glColorMask(false, false, false, false);\r
+                               gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2GL3.GL_FILL);\r
+                               gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_REPLACE);\r
+                               cr.renderGeometry(gl, c);\r
+                               gl.glStencilMask(0);\r
+\r
+                               gl.glColorMask(true, true, true, true);\r
+                               gl.glStencilFunc(GL.GL_NOTEQUAL, 1, 1);\r
+                               gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2GL3.GL_LINE);\r
+                               gl.glLineWidth(5.0f);\r
+                               cr.renderGeometry(gl, c);\r
+                       }\r
+               }\r
+               gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2GL3.GL_FILL);\r
+               gl.glDepthMask(true);\r
+               gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_EMISSION,\r
+                               colorBlack, 0);\r
+               gl.glDisable(GL.GL_STENCIL_TEST);\r
+               gl.glEnable(GL.GL_DEPTH_TEST);\r
+       }\r
+\r
+       private void renderMotors(GL2 gl, Configuration configuration) {\r
+               String motorID = configuration.getMotorConfigurationID();\r
+               Iterator<MotorMount> iterator = configuration.motorIterator();\r
+               while (iterator.hasNext()) {\r
+                       MotorMount mount = iterator.next();\r
+                       Motor motor = mount.getMotor(motorID);\r
+                       double length = motor.getLength();\r
+                       double radius = motor.getDiameter() / 2;\r
+\r
+                       Coordinate[] position = ((RocketComponent) mount)\r
+                                       .toAbsolute(new Coordinate(((RocketComponent) mount)\r
+                                                       .getLength() + mount.getMotorOverhang() - length));\r
+\r
+                       for (int i = 0; i < position.length; i++) {\r
+                               cr.renderMotor(gl, position[i], length, radius);\r
+                       }\r
+               }\r
+\r
+       }\r
+\r
+       \r
+       public void renderComponent(GL2 gl, RocketComponent c, float alpha) {\r
+               gl.glLightModeli(GL2ES1.GL_LIGHT_MODEL_TWO_SIDE, 1);\r
+\r
+               getOutsideColor(c, alpha, color);\r
+               gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_DIFFUSE, color, 0);\r
+               gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_AMBIENT, color, 0);\r
+\r
+               getSpecularColor(c, alpha, color);\r
+               gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_SPECULAR, color, 0);\r
+               gl.glMateriali(GL.GL_FRONT, GLLightingFunc.GL_SHININESS,\r
+                               getShininess(c));\r
+\r
+               getInsideColor(c, alpha, color);\r
+               gl.glMaterialfv(GL.GL_BACK, GLLightingFunc.GL_DIFFUSE, color, 0);\r
+               gl.glMaterialfv(GL.GL_BACK, GLLightingFunc.GL_AMBIENT, color, 0);\r
+\r
+               cr.renderGeometry(gl, c);\r
+       }\r
+\r
+       private int getShininess(RocketComponent c) {\r
+               if (c instanceof ExternalComponent) {\r
+                       switch (((ExternalComponent) c).getFinish()) {\r
+                       case ROUGH:\r
+                               return 10;\r
+                       case UNFINISHED:\r
+                               return 30;\r
+                       case NORMAL:\r
+                               return 40;\r
+                       case SMOOTH:\r
+                               return 80;\r
+                       case POLISHED:\r
+                               return 128;\r
+                       }\r
+                       return 100;\r
+               } else {\r
+                       return 20;\r
+               }\r
+       }\r
+\r
+       private void getSpecularColor(RocketComponent c, float alpha, float[] out) {\r
+               int shine = getShininess(c);\r
+               float m = (float) shine / 128.0f;\r
+               float d = 0.9f;\r
+               getOutsideColor(c, alpha, out);\r
+               out[0] = Math.max(out[0], d) * m;\r
+               out[1] = Math.max(out[1], d) * m;\r
+               out[2] = Math.max(out[2], d) * m;\r
+       }\r
+\r
+       private void getInsideColor(RocketComponent c, float alpha, float[] out) {\r
+               float d = 0.4f;\r
+               getOutsideColor(c, alpha, out);\r
+               out[0] *= d;\r
+               out[1] *=  d;\r
+               out[2] *= d;\r
+       }\r
+\r
+       private HashMap<Class<?>, Color> defaultColorCache = new HashMap<Class<?>, Color>();\r
+       private void getOutsideColor(RocketComponent c, float alpha, float[] out) {\r
+               Color col;\r
+               col = c.getColor();\r
+               if (col == null){\r
+                       if ( defaultColorCache.containsKey(c.getClass()) ){\r
+                               col = defaultColorCache.get(c.getClass());\r
+                       } else {\r
+                               col = Application.getPreferences().getDefaultColor(c.getClass());\r
+                               defaultColorCache.put(c.getClass(), col);\r
+                       }\r
+               }\r
+                       \r
+               out[0] = Math.max(0.2f, (float) col.getRed() / 255f);\r
+               out[1] = Math.max(0.2f, (float) col.getGreen() / 255f);\r
+               out[2] = Math.max(0.2f, (float) col.getBlue() / 255f);\r
+               out[3] = alpha;\r
+       }\r
+}\r
diff --git a/core/src/net/sf/openrocket/gui/figure3d/TransitionRenderer.java b/core/src/net/sf/openrocket/gui/figure3d/TransitionRenderer.java
new file mode 100644 (file)
index 0000000..3789a78
--- /dev/null
@@ -0,0 +1,247 @@
+/*\r
+ ** License Applicability. Except to the extent portions of this file are\r
+ ** made subject to an alternative license as permitted in the SGI Free\r
+ ** Software License B, Version 2.0 (the "License"), the contents of this\r
+ ** file are subject only to the provisions of the License. You may not use\r
+ ** this file except in compliance with the License. You may obtain a copy\r
+ ** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600\r
+ ** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at:\r
+ ** \r
+ ** http://oss.sgi.com/projects/FreeB\r
+ ** \r
+ ** Note that, as provided in the License, the Software is distributed on an\r
+ ** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS\r
+ ** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND\r
+ ** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A\r
+ ** PARTICULAR PURPOSE, AND NON-INFRINGEMENT.\r
+ ** \r
+ ** NOTE:  The Original Code (as defined below) has been licensed to Sun\r
+ ** Microsystems, Inc. ("Sun") under the SGI Free Software License B\r
+ ** (Version 1.1), shown above ("SGI License").   Pursuant to Section\r
+ ** 3.2(3) of the SGI License, Sun is distributing the Covered Code to\r
+ ** you under an alternative license ("Alternative License").  This\r
+ ** Alternative License includes all of the provisions of the SGI License\r
+ ** except that Section 2.2 and 11 are omitted.  Any differences between\r
+ ** the Alternative License and the SGI License are offered solely by Sun\r
+ ** and not by SGI.\r
+ **\r
+ ** Original Code. The Original Code is: OpenGL Sample Implementation,\r
+ ** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics,\r
+ ** Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc.\r
+ ** Copyright in any portions created by third parties is as indicated\r
+ ** elsewhere herein. All Rights Reserved.\r
+ ** \r
+ ** Additional Notice Provisions: The application programming interfaces\r
+ ** established by SGI in conjunction with the Original Code are The\r
+ ** OpenGL(R) Graphics System: A Specification (Version 1.2.1), released\r
+ ** April 1, 1999; The OpenGL(R) Graphics System Utility Library (Version\r
+ ** 1.3), released November 4, 1998; and OpenGL(R) Graphics with the X\r
+ ** Window System(R) (Version 1.3), released October 19, 1998. This software\r
+ ** was created using the OpenGL(R) version 1.2.1 Sample Implementation\r
+ ** published by SGI, but has not been independently verified as being\r
+ ** compliant with the OpenGL(R) version 1.2.1 Specification.\r
+ **\r
+ ** $Date: 2009-03-04 17:23:34 -0800 (Wed, 04 Mar 2009) $ $Revision: 1856 $\r
+ ** $Header$\r
+ */\r
+\r
+/* \r
+ * Copyright (c) 2002-2004 LWJGL Project\r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are \r
+ * met:\r
+ * \r
+ * * Redistributions of source code must retain the above copyright \r
+ *   notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * * Redistributions in binary form must reproduce the above copyright\r
+ *   notice, this list of conditions and the following disclaimer in the\r
+ *   documentation and/or other materials provided with the distribution.\r
+ *\r
+ * * Neither the name of 'LWJGL' nor the names of \r
+ *   its contributors may be used to endorse or promote products derived \r
+ *   from this software without specific prior written permission.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\r
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR \r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, \r
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, \r
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \r
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\r
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING \r
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+\r
+/*\r
+ * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are\r
+ * met:\r
+ * \r
+ * - Redistribution of source code must retain the above copyright\r
+ *   notice, this list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistribution in binary form must reproduce the above copyright\r
+ *   notice, this list of conditions and the following disclaimer in the\r
+ *   documentation and/or other materials provided with the distribution.\r
+ * \r
+ * Neither the name of Sun Microsystems, Inc. or the names of\r
+ * contributors may be used to endorse or promote products derived from\r
+ * this software without specific prior written permission.\r
+ * \r
+ * This software is provided "AS IS," without a warranty of any kind. ALL\r
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,\r
+ * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A\r
+ * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN\r
+ * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR\r
+ * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR\r
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR\r
+ * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR\r
+ * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE\r
+ * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,\r
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF\r
+ * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.\r
+ * \r
+ * You acknowledge that this software is not designed or intended for use\r
+ * in the design, construction, operation or maintenance of any nuclear\r
+ * facility.\r
+ */\r
+package net.sf.openrocket.gui.figure3d;\r
+\r
+import javax.media.opengl.GL;\r
+import javax.media.opengl.GL2;\r
+\r
+import net.sf.openrocket.rocketcomponent.Transition;\r
+\r
+public final class TransitionRenderer {\r
+       private static final boolean textureFlag = true;\r
+\r
+       private TransitionRenderer() {\r
+       }\r
+\r
+       public static final void drawTransition(final GL2 gl, final Transition tr,\r
+                       final int slices, final int stacks) {\r
+\r
+               double da, r, dz;\r
+               double x, y, z, nz, nsign;\r
+               int i, j;\r
+\r
+               nsign = 1.0f;\r
+\r
+               da = 2.0f * PI / slices;\r
+               dz = (double) tr.getLength() / stacks;\r
+\r
+               double ds = 1.0f / slices;\r
+               double dt = 1.0f / stacks;\r
+               double t = 0.0f;\r
+               z = 0.0f;\r
+               r = (double) tr.getForeRadius();\r
+               for (j = 0; j < stacks; j++) {\r
+                       r = (double) tr.getRadius(z);\r
+                       double rNext = (double) tr.getRadius(z + dz);\r
+\r
+                       if (j == stacks - 1)\r
+                               rNext = (double) tr.getRadius(tr.getLength());\r
+\r
+                       // Z component of normal vectors\r
+                       nz = -(rNext - r) / dz;\r
+\r
+                       double s = 0.0f;\r
+                       glBegin(gl, GL2.GL_QUAD_STRIP);\r
+                       for (i = 0; i <= slices; i++) {\r
+                               if (i == slices) {\r
+                                       x = sin(0.0f);\r
+                                       y = cos(0.0f);\r
+                               } else {\r
+                                       x = sin((i * da));\r
+                                       y = cos((i * da));\r
+                               }\r
+                               if (nsign == 1.0f) {\r
+                                       normal3d(gl, (x * nsign), (y * nsign), (nz * nsign));\r
+                                       TXTR_COORD(gl, s, t);\r
+                                       glVertex3d(gl, (x * r), (y * r), z);\r
+                                       normal3d(gl, (x * nsign), (y * nsign), (nz * nsign));\r
+                                       TXTR_COORD(gl, s, t + dt);\r
+                                       glVertex3d(gl, (x * rNext), (y * rNext), (z + dz));\r
+                               } else {\r
+                                       normal3d(gl, x * nsign, y * nsign, nz * nsign);\r
+                                       TXTR_COORD(gl, s, t);\r
+                                       glVertex3d(gl, (x * r), (y * r), z);\r
+                                       normal3d(gl, x * nsign, y * nsign, nz * nsign);\r
+                                       TXTR_COORD(gl, s, t + dt);\r
+                                       glVertex3d(gl, (x * rNext), (y * rNext), (z + dz));\r
+                               }\r
+                               s += ds;\r
+                       } // for slices\r
+                       glEnd(gl);\r
+                       // r += dr;\r
+                       t += dt;\r
+                       z += dz;\r
+               } // for stacks\r
+\r
+       }\r
+\r
+       // ----------------------------------------------------------------------\r
+       // Internals only below this point\r
+       //\r
+\r
+       private static final double PI = (double) Math.PI;\r
+\r
+       private static final void glBegin(GL gl, int mode) {\r
+               gl.getGL2().glBegin(mode);\r
+       }\r
+\r
+       private static final void glEnd(GL gl) {\r
+               gl.getGL2().glEnd();\r
+       }\r
+\r
+       private static final void glVertex3d(GL gl, double x, double y, double z) {\r
+               gl.getGL2().glVertex3d(x, y, z);\r
+       }\r
+\r
+       private static final void glNormal3d(GL gl, double x, double y, double z) {\r
+               gl.getGL2().glNormal3d(x, y, z);\r
+       }\r
+\r
+       private static final void glTexCoord2d(GL gl, double x, double y) {\r
+               gl.getGL2().glTexCoord2d(x, y);\r
+       }\r
+\r
+       /**\r
+        * Call glNormal3f after scaling normal to unit length.\r
+        * \r
+        * @param x\r
+        * @param y\r
+        * @param z\r
+        */\r
+       private static final void normal3d(GL gl, double x, double y, double z) {\r
+               double mag;\r
+\r
+               mag = (double) Math.sqrt(x * x + y * y + z * z);\r
+               if (mag > 0.00001F) {\r
+                       x /= mag;\r
+                       y /= mag;\r
+                       z /= mag;\r
+               }\r
+               glNormal3d(gl, x, y, z);\r
+       }\r
+\r
+       private static final void TXTR_COORD(GL gl, double x, double y) {\r
+               if (textureFlag)\r
+                       glTexCoord2d(gl, x, y);\r
+       }\r
+\r
+       private static final double sin(double r) {\r
+               return (double) Math.sin(r);\r
+       }\r
+\r
+       private static final double cos(double r) {\r
+               return (double) Math.cos(r);\r
+       }\r
+}\r
index cf458724344b770483d96a12decd627fff37285e..e82c77ee0bee693010b061aeaa7d49c4efeb1cbc 100644 (file)
@@ -1,6 +1,7 @@
 package net.sf.openrocket.gui.scalefigure;
 
 
+import java.awt.BorderLayout;
 import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.Point;
@@ -18,6 +19,7 @@ import java.util.concurrent.ThreadFactory;
 
 import javax.swing.AbstractAction;
 import javax.swing.Action;
+import javax.swing.ButtonGroup;
 import javax.swing.JComboBox;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
@@ -43,6 +45,7 @@ import net.sf.openrocket.gui.components.BasicSlider;
 import net.sf.openrocket.gui.components.StageSelector;
 import net.sf.openrocket.gui.components.UnitSelector;
 import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
+import net.sf.openrocket.gui.figure3d.RocketFigure3d;
 import net.sf.openrocket.gui.figureelements.CGCaret;
 import net.sf.openrocket.gui.figureelements.CPCaret;
 import net.sf.openrocket.gui.figureelements.Caret;
@@ -74,17 +77,29 @@ import net.sf.openrocket.util.StateChangeListener;
  * A JPanel that contains a RocketFigure and buttons to manipulate the figure. 
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ * @author Bill Kuker <bkuker@billkuker.com>
  */
 public class RocketPanel extends JPanel implements TreeSelectionListener, ChangeSource {
-       
+       private static final long serialVersionUID = 1L;
+
        private static final Translator trans = Application.getTranslator();
+
+       private boolean is3d;
        private final RocketFigure figure;
+       private final RocketFigure3d figure3d;
+       
+       
        private final ScaleScrollPane scrollPane;
        
+       private final JPanel figureHolder;
+       
        private JLabel infoMessage;
        
        private TreeSelectionModel selectionModel = null;
        
+       private BasicSlider rotationSlider;
+       ScaleSelector scaleSelector;
+       
 
        /* Calculation of CP and CG */
        private AerodynamicCalculator aerodynamicCalculator;
@@ -147,8 +162,13 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                
                // Create figure and custom scroll pane
                figure = new RocketFigure(configuration);
+               figure3d = new RocketFigure3d(configuration);
+               
+               figureHolder = new JPanel(new BorderLayout());
                
                scrollPane = new ScaleScrollPane(figure) {
+                       private static final long serialVersionUID = 1L;
+
                        @Override
                        public void mouseClicked(MouseEvent event) {
                                handleMouseClick(event);
@@ -159,16 +179,60 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                
                createPanel();
                
+               is3d = true;
+               go2D();
+               
                configuration.addChangeListener(new StateChangeListener() {
                        @Override
                        public void stateChanged(EventObject e) {
                                // System.out.println("Configuration changed, calling updateFigure");
                                updateExtras();
-                               figure.updateFigure();
+                               updateFigures();
+                       }
+               });
+               
+               figure3d.addComponentSelectionListener(new RocketFigure3d.ComponentSelectionListener() {
+                       @Override
+                       public void componentClicked(RocketComponent clicked[], MouseEvent event) {
+                               handleComponentClick(clicked, event);
                        }
                });
        }
        
+       private void updateFigures() {
+               if (!is3d)
+                       figure.updateFigure();
+               else
+                       figure3d.updateFigure();
+       }
+
+       private void go3D() {
+               if (is3d)
+                       return;
+               is3d = true;
+               figureHolder.remove(scrollPane);
+               figureHolder.add(figure3d, BorderLayout.CENTER);
+               rotationSlider.setEnabled(false);
+               scaleSelector.setEnabled(false);
+
+               revalidate();
+               figureHolder.revalidate();
+
+               figure3d.repaint();
+       }
+
+       private void go2D() {
+               if (!is3d)
+                       return;
+               is3d = false;
+               figureHolder.remove(figure3d);
+               figureHolder.add(scrollPane, BorderLayout.CENTER);
+               rotationSlider.setEnabled(true);
+               scaleSelector.setEnabled(true);
+               revalidate();
+               figureHolder.revalidate();
+               figure.repaint();
+       }
        
        /**
         * Creates the layout and components of the panel.
@@ -181,6 +245,8 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
 
                //// Create toolbar
                
+               ButtonGroup bg = new ButtonGroup();
+               
                // Side/back buttons
                FigureTypeAction action = new FigureTypeAction(RocketFigure.TYPE_SIDE);
                //// Side view
@@ -188,6 +254,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                //// Side view
                action.putValue(Action.SHORT_DESCRIPTION, trans.get("RocketPanel.FigTypeAct.ttip.Sideview"));
                JToggleButton toggle = new JToggleButton(action);
+               bg.add(toggle);
                add(toggle, "spanx, split");
                
                action = new FigureTypeAction(RocketFigure.TYPE_BACK);
@@ -196,11 +263,31 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                //// Back view
                action.putValue(Action.SHORT_DESCRIPTION, trans.get("RocketPanel.FigTypeAct.ttip.Backview"));
                toggle = new JToggleButton(action);
+               bg.add(toggle);
                add(toggle, "gap rel");
                
+               //// 3d Toggle
+               final JToggleButton toggle3d = new JToggleButton(new AbstractAction("3D") {
+                       private static final long serialVersionUID = 1L;
+                       {
+                               putValue(Action.NAME, "3D");//TODO
+                               putValue(Action.SHORT_DESCRIPTION, "3D"); //TODO
+                       }
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               if ( ((JToggleButton)e.getSource()).isSelected() ){
+                                       go3D();
+                               } else {
+                                       go2D();
+                               }
+                       }
+               });
+               bg.add(toggle3d);
+               add(toggle3d, "gap rel");
+               
 
                // Zoom level selector
-               ScaleSelector scaleSelector = new ScaleSelector(scrollPane);
+               scaleSelector = new ScaleSelector(scrollPane);
                add(scaleSelector);
                
 
@@ -231,7 +318,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                add(us, "alignx 50%, growx");
                
                // Add the rocket figure
-               add(scrollPane, "grow, spany 2, wmin 300lp, hmin 100lp, wrap");
+               add(figureHolder, "grow, spany 2, wmin 300lp, hmin 100lp, wrap");
                
 
                // Add rotation slider
@@ -239,7 +326,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                JLabel l = new JLabel("360" + Chars.DEGREE);
                Dimension d = l.getPreferredSize();
                
-               add(new BasicSlider(theta.getSliderModel(0, 2 * Math.PI), JSlider.VERTICAL, true),
+               add(rotationSlider = new BasicSlider(theta.getSliderModel(0, 2 * Math.PI), JSlider.VERTICAL, true),
                                "ax 50%, wrap, width " + (d.width + 6) + "px:null:null, growy");
                
 
@@ -324,7 +411,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                        return;
                cpAOA = aoa;
                updateExtras();
-               figure.updateFigure();
+               updateFigures();
                fireChangeEvent();
        }
        
@@ -340,7 +427,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                if (!Double.isNaN(theta))
                        figure.setRotation(theta);
                updateExtras();
-               figure.updateFigure();
+               updateFigures();
                fireChangeEvent();
        }
        
@@ -354,7 +441,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                        return;
                cpMach = mach;
                updateExtras();
-               figure.updateFigure();
+               updateFigures();
                fireChangeEvent();
        }
        
@@ -368,7 +455,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                        return;
                cpRoll = roll;
                updateExtras();
-               figure.updateFigure();
+               updateFigures();
                fireChangeEvent();
        }
        
@@ -417,6 +504,11 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                
                RocketComponent[] clicked = figure.getComponentsByPoint(x, y);
                
+               handleComponentClick(clicked, event);
+       }
+       
+       private void handleComponentClick(RocketComponent[] clicked, MouseEvent event){
+               
                // If no component is clicked, do nothing
                if (clicked.length == 0)
                        return;
@@ -517,6 +609,9 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                else
                        cgx = Double.NaN;
                
+               figure3d.setCG(cg);
+               figure3d.setCP(cp);
+               
                // Length bound is assumed to be tight
                double length = 0, diameter = 0;
                Collection<Coordinate> bounds = configuration.getBounds();
@@ -648,6 +743,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                        extraText.setFlightData(simulation.getSimulatedData());
                        extraText.setCalculatingData(false);
                        figure.repaint();
+                       figure3d.repaint();
                }
                
                @Override
@@ -667,6 +763,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                        extraText.setFlightData(FlightData.NaN_DATA);
                        extraText.setCalculatingData(false);
                        figure.repaint();
+                       figure3d.repaint();
                }
        }
        
@@ -676,14 +773,22 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
         * Adds the extra data to the figure.  Currently this includes the CP and CG carets.
         */
        private void addExtras() {
-               figure.clearRelativeExtra();
                extraCG = new CGCaret(0, 0);
                extraCP = new CPCaret(0, 0);
                extraText = new RocketInfo(configuration);
                updateExtras();
+               
+               figure.clearRelativeExtra();
                figure.addRelativeExtra(extraCP);
                figure.addRelativeExtra(extraCG);
                figure.addAbsoluteExtra(extraText);
+               
+               
+               figure3d.clearRelativeExtra();
+               //figure3d.addRelativeExtra(extraCP);
+               //figure3d.addRelativeExtra(extraCG);
+               figure3d.addAbsoluteExtra(extraText);
+               
        }
        
        
@@ -703,6 +808,8 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                for (int i = 0; i < paths.length; i++)
                        components[i] = (RocketComponent) paths[i].getLastPathComponent();
                figure.setSelection(components);
+               
+               figure3d.setSelection(components);
        }
        
        
@@ -714,6 +821,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
         * @author Sampo Niskanen <sampo.niskanen@iki.fi>
         */
        private class FigureTypeAction extends AbstractAction implements StateChangeListener {
+               private static final long serialVersionUID = 1L;
                private final int type;
                
                public FigureTypeAction(int type) {
@@ -728,6 +836,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                        if (state == true) {
                                // This view has been selected
                                figure.setType(type);
+                               go2D();
                                updateExtras();
                        }
                        stateChanged(null);
@@ -735,7 +844,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
                
                @Override
                public void stateChanged(EventObject e) {
-                       putValue(Action.SELECTED_KEY, figure.getType() == type);
+                       putValue(Action.SELECTED_KEY, figure.getType() == type && !is3d);
                }
        }
        
index d8a0b7634106174d941126b525c5311cebeed140..fba7aaa794d07ca30d12122481b6a81acf4275f7 100644 (file)
@@ -1,5 +1,6 @@
 package net.sf.openrocket.gui.scalefigure;
 
+import java.awt.Component;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.text.DecimalFormat;
@@ -156,4 +157,12 @@ public class ScaleSelector extends JPanel {
                return scale * 1.5;
        }
        
+       @Override
+       public void setEnabled(boolean b){
+               for ( Component c : getComponents() ){
+                       c.setEnabled(b);
+               }
+               super.setEnabled(b);
+       }
+       
 }
index b73438ed10bf8714dc80808de2206338ad4ba9c2..545cfa581d9e4f8e09c53b11d937a6f2de41c15a 100644 (file)
@@ -18,8 +18,15 @@ public class JarUtil {
         */
        public static File getCurrentJarFile() {
                // Find the jar file this class is contained in
+               
                URL jarUrl = null;
-               CodeSource codeSource = Database.class.getProtectionDomain().getCodeSource();
+               CodeSource codeSource;
+               try {
+                       codeSource = new URL("rsrc:.").openConnection().getClass().getProtectionDomain().getCodeSource();
+               } catch (Throwable e) {
+                       codeSource = Database.class.getProtectionDomain().getCodeSource();
+               }
+               
                if (codeSource != null)
                        jarUrl = codeSource.getLocation();