diff --git a/core/.classpath b/core/.classpath
index 002d4561b..d50734aa2 100644
--- a/core/.classpath
+++ b/core/.classpath
@@ -26,7 +26,9 @@
-
-
+
+
+
+
diff --git a/core/build.xml b/core/build.xml
index af9ed8a4c..7f3ad8d0b 100644
--- a/core/build.xml
+++ b/core/build.xml
@@ -67,25 +67,51 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
diff --git a/core/lib/jar-in-jar-loader.jar b/core/lib/jar-in-jar-loader.jar
new file mode 100644
index 000000000..ebb128790
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
index 000000000..2beef199a
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
index 000000000..e0bc10f40
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
index 000000000..05d72f812
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
index 000000000..a77bcf115
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
index 000000000..39fbfb27f
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
index 000000000..de5c81508
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
index 000000000..951175ba3
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
index 000000000..dd846ace0
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
index 000000000..3edc29505
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
index 000000000..5eb5a76b6
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
index 000000000..aee77f2d8
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
index 000000000..59a3effc7
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
index 000000000..80cdbfc02
--- /dev/null
+++ b/core/src/net/sf/openrocket/gui/figure3d/ComponentRenderer.java
@@ -0,0 +1,340 @@
+package net.sf.openrocket.gui.figure3d;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.media.opengl.GL;
+import javax.media.opengl.GL2;
+import javax.media.opengl.GLAutoDrawable;
+import javax.media.opengl.fixedfunc.GLLightingFunc;
+import javax.media.opengl.fixedfunc.GLMatrixFunc;
+import javax.media.opengl.glu.GLU;
+import javax.media.opengl.glu.GLUquadric;
+import javax.media.opengl.glu.GLUtessellator;
+import javax.media.opengl.glu.GLUtessellatorCallback;
+import javax.media.opengl.glu.GLUtessellatorCallbackAdapter;
+
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.rocketcomponent.BodyTube;
+import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
+import net.sf.openrocket.rocketcomponent.FinSet;
+import net.sf.openrocket.rocketcomponent.LaunchLug;
+import net.sf.openrocket.rocketcomponent.MassObject;
+import net.sf.openrocket.rocketcomponent.RingComponent;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.Transition;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.Coordinate;
+
+/*
+ * @author Bill Kuker
+ */
+public class ComponentRenderer {
+ private static final LogHelper log = Application.getLogger();
+
+ private int LOD = 80;
+
+ GLU glu;
+ GLUquadric q;
+ GLUtessellator tobj;
+
+ public ComponentRenderer() {
+
+ }
+
+ public void init(GLAutoDrawable drawable) {
+ glu = new GLU();
+ q = glu.gluNewQuadric();
+ tobj = GLU.gluNewTess();
+ glu.gluQuadricTexture(q, true);
+ }
+
+ private Map lists = new HashMap();
+ private boolean clearDisplayLists = false;
+ public void updateFigure() {
+ clearDisplayLists = true;
+ }
+
+ public void renderGeometry(GL2 gl, RocketComponent c) {
+ if (glu == null)
+ throw new IllegalStateException(this + " Not Initialized");
+
+ glu.gluQuadricNormals(q, GLU.GLU_SMOOTH);
+
+ if ( clearDisplayLists ){
+ log.debug("Clearing Display Lists");
+ for ( int i : lists.values() ){
+ gl.glDeleteLists(i,1);
+ }
+ lists.clear();
+ clearDisplayLists = false;
+ }
+ if ( lists.containsKey(c) ){
+ gl.glCallList(lists.get(c));
+ } else {
+ int list = gl.glGenLists(1);
+ gl.glNewList(list, GL2.GL_COMPILE_AND_EXECUTE);
+
+ Coordinate[] oo = c.toAbsolute(new Coordinate(0, 0, 0));
+
+ for (Coordinate o : oo) {
+ gl.glPushMatrix();
+
+ gl.glTranslated(o.x, o.y, o.z);
+
+ if (c instanceof BodyTube) {
+ renderTube(gl, (BodyTube) c);
+ } else if (c instanceof LaunchLug) {
+ renderLug(gl, (LaunchLug) c);
+ } else if (c instanceof RingComponent) {
+ renderRing(gl, (RingComponent) c);
+ } else if (c instanceof Transition) {
+ renderTransition(gl, (Transition) c);
+ } else if (c instanceof MassObject) {
+ renderMassObject(gl, (MassObject) c);
+ } else if (c instanceof FinSet) {
+ renderFinSet(gl, (FinSet) c);
+ } else {
+ renderOther(gl, c);
+ }
+ gl.glPopMatrix();
+ }
+
+ gl.glEndList();
+ lists.put(c, list);
+ }
+ }
+
+ private void renderOther(GL2 gl, RocketComponent c) {
+ gl.glBegin(GL.GL_LINES);
+ for (Coordinate cc : c.getComponentBounds()) {
+ for (Coordinate ccc : c.getComponentBounds()) {
+ gl.glVertex3d(cc.x, cc.y, cc.z);
+ gl.glVertex3d(ccc.x, ccc.y, ccc.z);
+ }
+ }
+ gl.glEnd();
+ }
+
+ private void renderTransition(GL2 gl, Transition t) {
+ gl.glRotated(90, 0, 1.0, 0);
+
+ if (t.getType() == Transition.Shape.CONICAL) {
+ glu.gluCylinder(q, t.getForeRadius(), t.getAftRadius(),
+ t.getLength(), LOD, 1);
+ } else {
+ TransitionRenderer.drawTransition(gl, t, LOD, LOD);
+ }
+
+ // Render AFT shoulder
+ gl.glPushMatrix();
+ gl.glTranslated(0, 0, t.getLength());
+
+ glu.gluCylinder(q, t.getAftShoulderRadius(), t.getAftShoulderRadius(),
+ t.getAftShoulderLength(), LOD, 1);
+
+ gl.glRotated(180, 0, 1.0, 0);
+
+ glu.gluDisk(q, t.getAftRadius(), t.getAftShoulderRadius(), LOD, 2);
+
+ gl.glTranslated(0, 0, -t.getAftShoulderLength());
+
+ if (t.isFilled() || t.isAftShoulderCapped()) {
+ glu.gluDisk(q, t.getAftShoulderRadius(), 0, LOD, 2);
+ }
+ gl.glPopMatrix();
+
+ // Render Fore Shoulder
+ gl.glPushMatrix();
+ gl.glRotated(180, 0, 1.0, 0);
+
+ glu.gluCylinder(q, t.getForeShoulderRadius(),
+ t.getForeShoulderRadius(), t.getForeShoulderLength(), LOD, 1);
+
+ gl.glRotated(180, 0, 1.0, 0);
+
+ glu.gluDisk(q, t.getForeRadius(), t.getForeShoulderRadius(), LOD, 2);
+
+ gl.glTranslated(0, 0, -t.getForeShoulderLength());
+
+ if (t.isFilled() || t.isForeShoulderCapped()) {
+ glu.gluDisk(q, t.getForeShoulderRadius(), 0, LOD, 2);
+ }
+ gl.glPopMatrix();
+
+ }
+
+ private void renderTube(GL2 gl, BodyTube t) {
+ gl.glRotated(90, 0, 1.0, 0);
+ glu.gluCylinder(q, t.getOuterRadius(), t.getOuterRadius(),
+ t.getLength(), LOD, 1);
+ }
+
+ private void renderRing(GL2 gl, RingComponent r) {
+ gl.glRotated(90, 0, 1.0, 0);
+ glu.gluCylinder(q, r.getOuterRadius(), r.getOuterRadius(),
+ r.getLength(), LOD, 1);
+
+ gl.glRotated(180, 0, 1.0, 0);
+ glu.gluDisk(q, r.getInnerRadius(), r.getOuterRadius(), LOD, 2);
+
+ gl.glRotated(180, 0, 1.0, 0);
+ gl.glTranslated(0, 0, r.getLength());
+ glu.gluDisk(q, r.getInnerRadius(), r.getOuterRadius(), LOD, 2);
+
+ gl.glTranslated(0, 0, -r.getLength());
+ glu.gluCylinder(q, r.getInnerRadius(), r.getInnerRadius(),
+ r.getLength(), LOD, 1);
+
+ }
+
+ private void renderLug(GL2 gl, LaunchLug t) {
+
+ gl.glRotated(90, 0, 1.0, 0);
+ glu.gluCylinder(q, t.getOuterRadius(), t.getOuterRadius(),
+ t.getLength(), LOD, 1);
+ }
+
+ private void renderMassObject(GL2 gl, MassObject o) {
+ gl.glRotated(90, 0, 1.0, 0);
+
+ MassObjectRenderer.drawMassObject(gl, o, LOD, LOD);
+ }
+
+ private void renderFinSet(final GL2 gl, FinSet fs) {
+
+ Coordinate finPoints[] = fs.getFinPointsWithTab();
+
+ double minX = Double.MAX_VALUE;
+ double minY = Double.MAX_VALUE;
+ double maxX = Double.MIN_VALUE;
+ double maxY = Double.MIN_VALUE;
+
+ for (int i = 0; i < finPoints.length; i++) {
+ Coordinate c = finPoints[i];
+ minX = Math.min(c.x, minX);
+ minY = Math.min(c.y, minY);
+ maxX = Math.max(c.x, maxX);
+ maxY = Math.max(c.y, maxY);
+ }
+
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glPushMatrix();
+ gl.glScaled(1/(maxX-minX), 1/(maxY-minY), 0);
+ gl.glTranslated(-minX, -minY - fs.getBodyRadius(), 0);
+ gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
+
+ gl.glRotated(fs.getBaseRotation() * (180.0 / Math.PI), 1, 0, 0);
+
+ for (int fin = 0; fin < fs.getFinCount(); fin++) {
+
+ gl.glPushMatrix();
+
+ gl.glTranslated(fs.getLength() / 2, 0, 0);
+ gl.glRotated(fs.getCantAngle() * (180.0 / Math.PI), 0, 1, 0);
+ gl.glTranslated(-fs.getLength() / 2, 0, 0);
+
+ GLUtessellatorCallback cb = new GLUtessellatorCallbackAdapter() {
+ @Override
+ public void vertex(Object vertexData) {
+ double d[] = (double[]) vertexData;
+ gl.glTexCoord2d(d[0], d[1]);
+ gl.glVertex3dv(d, 0);
+ }
+
+ @Override
+ public void begin(int type) {
+ gl.glBegin(type);
+ }
+
+ @Override
+ public void end() {
+ gl.glEnd();
+ }
+ };
+
+ GLU.gluTessCallback(tobj, GLU.GLU_TESS_VERTEX, cb);
+ GLU.gluTessCallback(tobj, GLU.GLU_TESS_BEGIN, cb);
+ GLU.gluTessCallback(tobj, GLU.GLU_TESS_END, cb);
+
+ GLU.gluTessBeginPolygon(tobj, null);
+ GLU.gluTessBeginContour(tobj);
+ gl.glNormal3f(0, 0, 1);
+ for (int i = finPoints.length - 1; i >= 0; i--) {
+ Coordinate c = finPoints[i];
+ double[] p = new double[] { c.x, c.y + fs.getBodyRadius(),
+ c.z + fs.getThickness() / 2.0 };
+ GLU.gluTessVertex(tobj, p, 0, p);
+
+ }
+ GLU.gluTessEndContour(tobj);
+ GLU.gluTessEndPolygon(tobj);
+
+ GLU.gluTessBeginPolygon(tobj, null);
+ GLU.gluTessBeginContour(tobj);
+ gl.glNormal3f(0, 0, -1);
+ for (int i = 0; i < finPoints.length; i++) {
+ Coordinate c = finPoints[i];
+ double[] p = new double[] { c.x, c.y + fs.getBodyRadius(),
+ c.z - fs.getThickness() / 2.0 };
+ GLU.gluTessVertex(tobj, p, 0, p);
+
+ }
+ GLU.gluTessEndContour(tobj);
+ GLU.gluTessEndPolygon(tobj);
+
+ // Strip around the edge
+ if (!(fs instanceof EllipticalFinSet))
+ gl.glShadeModel(GLLightingFunc.GL_FLAT);
+ gl.glBegin(GL.GL_TRIANGLE_STRIP);
+ for (int i = 0; i <= finPoints.length; i++) {
+ Coordinate c = finPoints[i % finPoints.length];
+ // if ( i > 1 ){
+ Coordinate c2 = finPoints[(i - 1 + finPoints.length)
+ % finPoints.length];
+ gl.glNormal3d(c2.y - c.y, c.x - c2.x, 0);
+ // }
+ gl.glTexCoord2d(c.x, c.y + fs.getBodyRadius());
+ gl.glVertex3d(c.x, c.y + fs.getBodyRadius(),
+ c.z - fs.getThickness() / 2.0);
+ gl.glVertex3d(c.x, c.y + fs.getBodyRadius(),
+ c.z + fs.getThickness() / 2.0);
+ }
+ gl.glEnd();
+ if (!(fs instanceof EllipticalFinSet))
+ gl.glShadeModel(GLLightingFunc.GL_SMOOTH);
+
+ gl.glPopMatrix();
+
+ gl.glRotated(360.0 / fs.getFinCount(), 1, 0, 0);
+ }
+
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glPopMatrix();
+ gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
+
+ }
+
+ public void renderMotor(final GL2 gl, final Coordinate c, double l, double r) {
+ final float outside[] = { 0.2f, 0.2f, 0.2f, 1.0f };
+ gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_DIFFUSE, outside, 0);
+ gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_AMBIENT, outside, 0);
+
+ gl.glPushMatrix();
+
+ gl.glTranslated(c.x, c.y, c.z);
+
+ gl.glRotated(90, 0, 1.0, 0);
+
+ glu.gluCylinder(q, r, r, l, LOD, 1);
+
+ glu.gluDisk(q, r, 0, LOD, 2);
+
+ gl.glTranslated(0, 0, l);
+ gl.glRotated(180, 0, 1.0, 0);
+
+ glu.gluDisk(q, r, 0, LOD, 2);
+
+ gl.glPopMatrix();
+ }
+}
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
index 000000000..4c1d93443
--- /dev/null
+++ b/core/src/net/sf/openrocket/gui/figure3d/MassObjectRenderer.java
@@ -0,0 +1,263 @@
+/*
+ ** License Applicability. Except to the extent portions of this file are
+ ** made subject to an alternative license as permitted in the SGI Free
+ ** Software License B, Version 2.0 (the "License"), the contents of this
+ ** file are subject only to the provisions of the License. You may not use
+ ** this file except in compliance with the License. You may obtain a copy
+ ** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600
+ ** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at:
+ **
+ ** http://oss.sgi.com/projects/FreeB
+ **
+ ** Note that, as provided in the License, the Software is distributed on an
+ ** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS
+ ** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND
+ ** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A
+ ** PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
+ **
+ ** NOTE: The Original Code (as defined below) has been licensed to Sun
+ ** Microsystems, Inc. ("Sun") under the SGI Free Software License B
+ ** (Version 1.1), shown above ("SGI License"). Pursuant to Section
+ ** 3.2(3) of the SGI License, Sun is distributing the Covered Code to
+ ** you under an alternative license ("Alternative License"). This
+ ** Alternative License includes all of the provisions of the SGI License
+ ** except that Section 2.2 and 11 are omitted. Any differences between
+ ** the Alternative License and the SGI License are offered solely by Sun
+ ** and not by SGI.
+ **
+ ** Original Code. The Original Code is: OpenGL Sample Implementation,
+ ** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics,
+ ** Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc.
+ ** Copyright in any portions created by third parties is as indicated
+ ** elsewhere herein. All Rights Reserved.
+ **
+ ** Additional Notice Provisions: The application programming interfaces
+ ** established by SGI in conjunction with the Original Code are The
+ ** OpenGL(R) Graphics System: A Specification (Version 1.2.1), released
+ ** April 1, 1999; The OpenGL(R) Graphics System Utility Library (Version
+ ** 1.3), released November 4, 1998; and OpenGL(R) Graphics with the X
+ ** Window System(R) (Version 1.3), released October 19, 1998. This software
+ ** was created using the OpenGL(R) version 1.2.1 Sample Implementation
+ ** published by SGI, but has not been independently verified as being
+ ** compliant with the OpenGL(R) version 1.2.1 Specification.
+ **
+ ** $Date: 2009-03-04 17:23:34 -0800 (Wed, 04 Mar 2009) $ $Revision: 1856 $
+ ** $Header$
+ */
+
+/*
+ * Copyright (c) 2002-2004 LWJGL Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'LWJGL' nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
+ * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
+ * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
+ * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
+ * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
+ * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
+ * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
+ * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed or intended for use
+ * in the design, construction, operation or maintenance of any nuclear
+ * facility.
+ */
+package net.sf.openrocket.gui.figure3d;
+
+import javax.media.opengl.GL;
+import javax.media.opengl.GL2;
+
+import net.sf.openrocket.rocketcomponent.MassObject;
+
+public final class MassObjectRenderer {
+ private static final boolean textureFlag = true;
+
+ private MassObjectRenderer() {
+ }
+
+ public static final void drawMassObject(final GL2 gl, final MassObject o,
+ final int slices, final int stacks) {
+
+ double da, r, dz;
+ double x, y, z, nz, nsign;
+ int i, j;
+
+ nsign = 1.0f;
+
+ da = 2.0f * PI / slices;
+ dz = o.getLength() / stacks;
+
+ double ds = 1.0f / slices;
+ double dt = 1.0f / stacks;
+ double t = 0.0f;
+ z = 0.0f;
+ for (j = 0; j < stacks; j++) {
+ r = getRadius(o, z);
+ double rNext = getRadius(o, z + dz);
+ if (j == stacks - 1)
+ rNext = 0;
+
+ if (j == stacks - 1)
+ rNext = 0;
+
+ // Z component of normal vectors
+ nz = -(rNext - r) / dz;
+
+ double s = 0.0f;
+ glBegin(gl, GL2.GL_QUAD_STRIP);
+ for (i = 0; i <= slices; i++) {
+ if (i == slices) {
+ x = sin(0.0f);
+ y = cos(0.0f);
+ } else {
+ x = sin((i * da));
+ y = cos((i * da));
+ }
+ if (nsign == 1.0f) {
+ normal3d(gl, (x * nsign), (y * nsign), (nz * nsign));
+ TXTR_COORD(gl, s, t);
+ glVertex3d(gl, (x * r), (y * r), z);
+ normal3d(gl, (x * nsign), (y * nsign), (nz * nsign));
+ TXTR_COORD(gl, s, t + dt);
+ glVertex3d(gl, (x * rNext), (y * rNext), (z + dz));
+ } else {
+ normal3d(gl, x * nsign, y * nsign, nz * nsign);
+ TXTR_COORD(gl, s, t);
+ glVertex3d(gl, (x * r), (y * r), z);
+ normal3d(gl, x * nsign, y * nsign, nz * nsign);
+ TXTR_COORD(gl, s, t + dt);
+ glVertex3d(gl, (x * rNext), (y * rNext), (z + dz));
+ }
+ s += ds;
+ } // for slices
+ glEnd(gl);
+ // r += dr;
+ t += dt;
+ z += dz;
+ } // for stacks
+ }
+
+ private static final double getRadius(MassObject o, double z) {
+ double arc = Math.min(o.getLength(), 2 * o.getRadius()) * 0.35f;
+ double r = o.getRadius();
+ if (z == 0 || z == o.getLength())
+ return 0;
+ if (z < arc) {
+ double zz = z - arc;
+ return (r - arc) + Math.sqrt(arc * arc - zz * zz);
+ }
+ if (z > o.getLength() - arc) {
+ double zz = (z - o.getLength() + arc);
+ return (r - arc) + Math.sqrt(arc * arc - zz * zz);
+ }
+ return o.getRadius();
+ }
+
+ // ----------------------------------------------------------------------
+ // Internals only below this point
+ //
+
+ private static final double PI = Math.PI;
+
+ private static final void glBegin(GL gl, int mode) {
+ gl.getGL2().glBegin(mode);
+ }
+
+ private static final void glEnd(GL gl) {
+ gl.getGL2().glEnd();
+ }
+
+ private static final void glVertex3d(GL gl, double x, double y, double z) {
+ gl.getGL2().glVertex3d(x, y, z);
+ }
+
+ private static final void glNormal3d(GL gl, double x, double y, double z) {
+ gl.getGL2().glNormal3d(x, y, z);
+ }
+
+ private static final void glTexCoord2d(GL gl, double x, double y) {
+ gl.getGL2().glTexCoord2d(x, y);
+ }
+
+ /**
+ * Call glNormal3f after scaling normal to unit length.
+ *
+ * @param x
+ * @param y
+ * @param z
+ */
+ private static final void normal3d(GL gl, double x, double y, double z) {
+ double mag;
+
+ mag = Math.sqrt(x * x + y * y + z * z);
+ if (mag > 0.00001F) {
+ x /= mag;
+ y /= mag;
+ z /= mag;
+ }
+ glNormal3d(gl, x, y, z);
+ }
+
+ private static final void TXTR_COORD(GL gl, double x, double y) {
+ if (textureFlag)
+ glTexCoord2d(gl, x, y);
+ }
+
+ private static final double sin(double r) {
+ return Math.sin(r);
+ }
+
+ private static final double cos(double r) {
+ return Math.cos(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
index 000000000..49dbbc9fd
--- /dev/null
+++ b/core/src/net/sf/openrocket/gui/figure3d/Quick3dMain.java
@@ -0,0 +1,70 @@
+package net.sf.openrocket.gui.figure3d;
+import java.awt.BorderLayout;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import net.sf.openrocket.database.ComponentPresetDatabase;
+import net.sf.openrocket.database.ThrustCurveMotorSetDatabase;
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.file.DatabaseMotorFinder;
+import net.sf.openrocket.file.openrocket.importt.OpenRocketLoader;
+import net.sf.openrocket.gui.main.componenttree.ComponentTree;
+import net.sf.openrocket.gui.scalefigure.RocketPanel;
+import net.sf.openrocket.gui.util.SwingPreferences;
+import net.sf.openrocket.l10n.ResourceBundleTranslator;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * An application for quickly testing 3d figure witout all the OpenRocket user interface
+ *
+ * @author bkuker
+ *
+ */
+public class Quick3dMain {
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) throws Exception {
+ Application.setBaseTranslator(new ResourceBundleTranslator(
+ "l10n.messages"));
+ Application.setMotorSetDatabase(new ThrustCurveMotorSetDatabase(false) {
+ {
+ startLoading();
+ }
+
+ @Override
+ protected void loadMotors() {
+ }
+ });
+ Application.setPreferences(new SwingPreferences());
+
+ // Must be done after localization is initialized
+ ComponentPresetDatabase componentPresetDao = new ComponentPresetDatabase();
+ componentPresetDao.load("datafiles", ".*csv");
+ Application.setComponentPresetDao( componentPresetDao );
+
+ OpenRocketDocument doc = new OpenRocketLoader().loadFromStream(
+ Quick3dMain.class.getResourceAsStream("/datafiles/examples/Clustered rocket design.ork"),
+ new DatabaseMotorFinder());
+
+ JFrame ff = new JFrame();
+ ff.setSize(1200, 400);
+ ff.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ RocketPanel panel;
+
+ panel = new RocketPanel(doc);
+
+ ComponentTree ct = new ComponentTree(doc);
+ panel.setSelectionModel(ct.getSelectionModel());
+
+ JPanel p = new JPanel();
+ p.setLayout(new BorderLayout());
+ p.add(ct, BorderLayout.WEST);
+ p.add(panel, BorderLayout.CENTER);
+ ff.setContentPane(p);
+ ff.setVisible(true);
+ }
+}
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
index 000000000..3402f361c
--- /dev/null
+++ b/core/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java
@@ -0,0 +1,586 @@
+package net.sf.openrocket.gui.figure3d;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+import java.awt.event.MouseEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.media.opengl.GL;
+import javax.media.opengl.GL2;
+import javax.media.opengl.GLAutoDrawable;
+import javax.media.opengl.GLCapabilities;
+import javax.media.opengl.GLEventListener;
+import javax.media.opengl.GLProfile;
+import javax.media.opengl.awt.GLCanvas;
+import javax.media.opengl.fixedfunc.GLLightingFunc;
+import javax.media.opengl.fixedfunc.GLMatrixFunc;
+import javax.media.opengl.glu.GLU;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.SwingUtilities;
+import javax.swing.event.MouseInputAdapter;
+
+import net.sf.openrocket.gui.figureelements.CGCaret;
+import net.sf.openrocket.gui.figureelements.CPCaret;
+import net.sf.openrocket.gui.figureelements.FigureElement;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.rocketcomponent.Configuration;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
+
+import com.jogamp.opengl.util.awt.Overlay;
+
+/*
+ * @author Bill Kuker
+ */
+public class RocketFigure3d extends JPanel implements GLEventListener {
+ private static final long serialVersionUID = 1L;
+ private static final LogHelper log = Application.getLogger();
+
+ static {
+ //this allows the GL canvas and things like the motor selection
+ //drop down to z-order themselves.
+ JPopupMenu.setDefaultLightWeightPopupEnabled(false);
+ }
+
+ private static final double fovY = 15.0;
+ private static double fovX = Double.NaN;
+ private static final int CARET_SIZE = 20;
+
+ private Configuration configuration;
+ private GLCanvas canvas;
+
+
+
+ private Overlay extrasOverlay, caretOverlay;
+ private BufferedImage cgCaretRaster, cpCaretRaster;
+ private volatile boolean redrawExtras = true;
+
+ private final ArrayList relativeExtra = new ArrayList();
+ private final ArrayList absoluteExtra = new ArrayList();
+
+ private double roll = 0;
+ private double yaw = 0;
+
+ Point pickPoint = null;
+ MouseEvent pickEvent;
+
+ float[] lightPosition = new float[] { 1, 4, 1, 0 };
+
+ RocketRenderer rr = new RocketRenderer();
+
+ public RocketFigure3d(Configuration config) {
+ this.configuration = config;
+ this.setLayout(new BorderLayout());
+
+ addHierarchyListener(new HierarchyListener() {
+ @Override
+ public void hierarchyChanged(HierarchyEvent e) {
+ initGLCanvas();
+ RocketFigure3d.this.removeHierarchyListener(this);
+ }
+ });
+ }
+
+ private void initGLCanvas(){
+ log.debug("Initializing RocketFigure3D OpenGL Canvas");
+ try {
+ log.debug("Setting up GL capabilities...");
+ GLProfile glp = GLProfile.getDefault();
+ GLCapabilities caps = new GLCapabilities(glp);
+ caps.setSampleBuffers(true);
+ caps.setNumSamples(6);
+ caps.setStencilBits(1);
+
+ log.debug("Creating OpenGL Canvas");
+ canvas = new GLCanvas(caps);
+
+ canvas.addGLEventListener(this);
+ this.add(canvas, BorderLayout.CENTER);
+
+ setupMouseListeners();
+ rasterizeCarets();
+
+ } catch (Throwable t) {
+ log.error("An error occurred creating 3d View", t);
+ canvas = null;
+ this.add(new JLabel("Unable to load 3d Libraries: "
+ + t.getMessage()));
+ }
+ }
+
+ /**
+ * Set up the standard rendering hints on the Graphics2D
+ */
+ private static void setRenderingHints(Graphics2D g){
+ g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ RenderingHints.VALUE_STROKE_NORMALIZE);
+ g.setRenderingHint(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_QUALITY);
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ }
+
+ /**
+ * Rasterize the carets into 2 buffered images that I can blit onto the
+ * 3d display every redraw without all of the caret shape rendering overhead
+ */
+ private void rasterizeCarets(){
+ Graphics2D g2d;
+
+ //Rasterize a CG Caret
+ cgCaretRaster = new BufferedImage(CARET_SIZE, CARET_SIZE, BufferedImage.TYPE_4BYTE_ABGR);
+ g2d = cgCaretRaster.createGraphics();
+ setRenderingHints(g2d);
+
+ g2d.setBackground(new Color(0, 0, 0, 0));
+ g2d.clearRect(0, 0, CARET_SIZE, CARET_SIZE);
+
+ new CGCaret(CARET_SIZE/2,CARET_SIZE/2).paint(g2d, 1.0);
+
+ g2d.dispose();
+
+ //Rasterize a CP Caret
+ cpCaretRaster = new BufferedImage(CARET_SIZE, CARET_SIZE, BufferedImage.TYPE_4BYTE_ABGR);
+ g2d = cpCaretRaster.createGraphics();
+ setRenderingHints(g2d);
+
+ g2d.setBackground(new Color(0, 0, 0, 0));
+ g2d.clearRect(0, 0, CARET_SIZE, CARET_SIZE);
+
+ new CPCaret(CARET_SIZE/2,CARET_SIZE/2).paint(g2d, 1.0);
+
+ g2d.dispose();
+
+ }
+
+ private void setupMouseListeners() {
+ MouseInputAdapter a = new MouseInputAdapter() {
+ int lastX;
+ int lastY;
+ MouseEvent pressEvent;
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ lastX = e.getX();
+ lastY = e.getY();
+ pressEvent = e;
+ }
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ pickPoint = new Point(lastX, canvas.getHeight() - lastY);
+ pickEvent = e;
+ internalRepaint();
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ int dx = lastX - e.getX();
+ int dy = lastY - e.getY();
+ lastX = e.getX();
+ lastY = e.getY();
+
+ if (pressEvent.getButton() == MouseEvent.BUTTON1) {
+ if (Math.abs(dx) > Math.abs(dy)) {
+ setYaw(yaw - (float) dx / 100.0);
+ } else {
+ if ( yaw > Math.PI/2.0 && yaw < 3.0*Math.PI/2.0 ){
+ dy = -dy;
+ }
+ setRoll(roll - (float) dy / 100.0);
+ }
+ } else {
+ lightPosition[0] -= 0.1f * dx;
+ lightPosition[1] += 0.1f * dy;
+ internalRepaint();
+ }
+ }
+ };
+ canvas.addMouseMotionListener(a);
+ canvas.addMouseListener(a);
+ }
+
+ public void setConfiguration(Configuration configuration) {
+ this.configuration = configuration;
+ updateFigure();
+ }
+
+ @Override
+ public void display(GLAutoDrawable drawable) {
+ GL2 gl = drawable.getGL().getGL2();
+ GLU glu = new GLU();
+
+ gl.glEnable(GL.GL_MULTISAMPLE);
+
+ gl.glClearColor(1, 1, 1, 1);
+ gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
+
+ setupView(gl, glu);
+
+ if (pickPoint != null) {
+ gl.glDisable(GLLightingFunc.GL_LIGHTING);
+ final RocketComponent picked = rr.pick(drawable, configuration,
+ pickPoint, pickEvent.isShiftDown()?selection:null );
+ if (csl != null && picked != null) {
+ final MouseEvent e = pickEvent;
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ csl.componentClicked(new RocketComponent[] { picked },
+ e);
+ }
+ });
+
+ }
+ pickPoint = null;
+
+ gl.glClearColor(1, 1, 1, 1);
+ gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
+
+ gl.glEnable(GLLightingFunc.GL_LIGHTING);
+ }
+ rr.render(drawable, configuration, selection);
+
+ drawExtras(gl, glu);
+ drawCarets(gl, glu);
+ }
+
+
+ private void drawCarets(GL2 gl, GLU glu) {
+ final Graphics2D og2d = caretOverlay.createGraphics();
+ setRenderingHints(og2d);
+
+ og2d.setBackground(new Color(0, 0, 0, 0));
+ og2d.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
+ caretOverlay.markDirty(0, 0, canvas.getWidth(), canvas.getHeight());
+
+ // The existing relative Extras don't really work right for 3d.
+ Coordinate pCP = project(cp, gl, glu);
+ Coordinate pCG = project(cg, gl, glu);
+
+ final int d = CARET_SIZE/2;
+
+ //z order the carets
+ if (pCG.z < pCP.z) {
+ //Subtract half of the caret size, so they are centered ( The +/- d in each translate)
+ //Flip the sense of the Y coordinate from GL to normal (Y+ up/down)
+ og2d.drawRenderedImage(
+ cpCaretRaster,
+ AffineTransform.getTranslateInstance((pCP.x - d),
+ canvas.getHeight() - (pCP.y + d)));
+ og2d.drawRenderedImage(
+ cgCaretRaster,
+ AffineTransform.getTranslateInstance((pCG.x - d),
+ canvas.getHeight() - (pCG.y + d)));
+ } else {
+ og2d.drawRenderedImage(
+ cgCaretRaster,
+ AffineTransform.getTranslateInstance((pCG.x - d),
+ canvas.getHeight() - (pCG.y + d)));
+ og2d.drawRenderedImage(
+ cpCaretRaster,
+ AffineTransform.getTranslateInstance((pCP.x - d),
+ canvas.getHeight() - (pCP.y + d)));
+ }
+ og2d.dispose();
+
+ gl.glEnable(GL.GL_BLEND);
+ caretOverlay.drawAll();
+ gl.glDisable(GL.GL_BLEND);
+ }
+
+ /**
+ * Draw the extras overlay to the gl canvas.
+ * Re-blits the overlay every frame. Only re-renders the overlay
+ * when needed.
+ */
+ private void drawExtras(GL2 gl, GLU glu){
+ //Only re-render if needed
+ // redrawExtras: Some external change (new simulation data) means
+ // the data is out of date.
+ // extrasOverlay.contentsLost(): For some reason the buffer with this
+ // data is lost.
+ if ( redrawExtras || extrasOverlay.contentsLost() ){
+ log.debug("Redrawing Overlay");
+
+ final Graphics2D og2d = extrasOverlay.createGraphics();
+ setRenderingHints(og2d);
+
+ og2d.setBackground(new Color(0, 0, 0, 0));
+ og2d.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
+ extrasOverlay.markDirty(0, 0, canvas.getWidth(), canvas.getHeight());
+
+ for (FigureElement e : relativeExtra) {
+ e.paint(og2d, 1);
+ }
+ Rectangle rect = this.getVisibleRect();
+
+ for (FigureElement e : absoluteExtra) {
+ e.paint(og2d, 1.0, rect);
+ }
+ og2d.dispose();
+
+ redrawExtras = false;
+ }
+
+ //Re-blit to gl canvas every time
+ gl.glEnable(GL.GL_BLEND);
+ extrasOverlay.drawAll();
+ gl.glDisable(GL.GL_BLEND);
+ }
+
+ @Override
+ public void dispose(GLAutoDrawable drawable) {
+ }
+
+ @Override
+ public void init(GLAutoDrawable drawable) {
+ rr.init(drawable);
+
+ GL2 gl = drawable.getGL().getGL2();
+ gl.glClearDepth(1.0f); // clear z-buffer to the farthest
+
+ gl.glDepthFunc(GL.GL_LEQUAL); // the type of depth test to do
+
+ float amb = 0.5f;
+ float dif = 1.0f;
+ gl.glLightfv(GLLightingFunc.GL_LIGHT1, GLLightingFunc.GL_AMBIENT,
+ new float[] { amb, amb, amb, 1 }, 0);
+ gl.glLightfv(GLLightingFunc.GL_LIGHT1, GLLightingFunc.GL_DIFFUSE,
+ new float[] { dif, dif, dif, 1 }, 0);
+ gl.glLightfv(GLLightingFunc.GL_LIGHT1, GLLightingFunc.GL_SPECULAR,
+ new float[] { dif, dif, dif, 1 }, 0);
+
+ gl.glEnable(GLLightingFunc.GL_LIGHT1);
+ gl.glEnable(GLLightingFunc.GL_LIGHTING);
+ gl.glShadeModel(GLLightingFunc.GL_SMOOTH);
+
+ gl.glEnable(GLLightingFunc.GL_NORMALIZE);
+
+ extrasOverlay = new Overlay(drawable);
+ caretOverlay = new Overlay(drawable);
+ }
+
+ @Override
+ public void reshape(GLAutoDrawable drawable, int x, int y, int w, int h) {
+ GL2 gl = drawable.getGL().getGL2();
+ GLU glu = new GLU();
+
+ double ratio = (double) w / (double) h;
+ fovX = fovY * ratio;
+
+ gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
+ gl.glLoadIdentity();
+ glu.gluPerspective(fovY, ratio, 0.05f, 100f);
+ gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
+
+ redrawExtras = true;
+ }
+
+ @SuppressWarnings("unused")
+ private static class Bounds {
+ double xMin, xMax, xSize;
+ double yMin, yMax, ySize;
+ double zMin, zMax, zSize;
+ double rMax;
+ }
+
+ /**
+ * Calculates the bounds for the current configuration
+ *
+ * @return
+ */
+ private Bounds calculateBounds() {
+ Bounds ret = new Bounds();
+ Collection bounds = configuration.getBounds();
+ for (Coordinate c : bounds) {
+ ret.xMax = Math.max(ret.xMax, c.x);
+ ret.xMin = Math.min(ret.xMin, c.x);
+
+ ret.yMax = Math.max(ret.yMax, c.y);
+ ret.yMin = Math.min(ret.yMin, c.y);
+
+ ret.zMax = Math.max(ret.zMax, c.z);
+ ret.zMin = Math.min(ret.zMin, c.z);
+
+ double r = MathUtil.hypot(c.y, c.z);
+ ret.rMax = Math.max(ret.rMax, r);
+ }
+ ret.xSize = ret.xMax - ret.xMin;
+ ret.ySize = ret.yMax - ret.yMin;
+ ret.zSize = ret.zMax - ret.zMin;
+ return ret;
+ }
+
+ private void setupView(GL2 gl, GLU glu) {
+ gl.glLoadIdentity();
+
+ gl.glLightfv(GLLightingFunc.GL_LIGHT1, GLLightingFunc.GL_POSITION,
+ lightPosition, 0);
+
+ // Get the bounds
+ Bounds b = calculateBounds();
+
+ // Calculate the distance needed to fit the bounds in both the X and Y
+ // direction
+ // Add 10% for space around it.
+ double dX = (b.xSize * 1.2 / 2.0)
+ / Math.tan(Math.toRadians(fovX / 2.0));
+ double dY = (b.rMax * 2.0 * 1.2 / 2.0)
+ / Math.tan(Math.toRadians(fovY / 2.0));
+
+ // Move back the greater of the 2 distances
+ glu.gluLookAt(0, 0, Math.max(dX, dY), 0, 0, 0, 0, 1, 0);
+
+ gl.glRotated(yaw * (180.0 / Math.PI), 0, 1, 0);
+ gl.glRotated(roll * (180.0 / Math.PI), 1, 0, 0);
+
+ // Center the rocket in the view.
+ gl.glTranslated(-b.xMin - b.xSize / 2.0, 0, 0);
+
+ //Change to LEFT Handed coordinates
+ gl.glScaled(1, 1, -1);
+ gl.glFrontFace(GL.GL_CW);
+
+ //Flip textures for LEFT handed coords
+ gl.glMatrixMode(GL.GL_TEXTURE);
+ gl.glLoadIdentity();
+ gl.glScaled(-1,1,1);
+ gl.glTranslated(-1,0,0);
+ gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
+ }
+
+ /**
+ * Call when the rocket has changed
+ */
+ public void updateFigure() {
+ log.debug("3D Figure Updated");
+ rr.updateFigure();
+ internalRepaint();
+ }
+
+ private void internalRepaint(){
+ super.repaint();
+ if (canvas != null)
+ canvas.display();
+ }
+
+ @Override
+ public void repaint() {
+ redrawExtras = true;
+ internalRepaint();
+ }
+
+ private Set selection = new HashSet();
+
+ public void setSelection(RocketComponent[] selection) {
+ this.selection.clear();
+ if (selection != null) {
+ for (RocketComponent c : selection)
+ this.selection.add(c);
+ }
+ internalRepaint();
+ }
+
+ private void setRoll(double rot) {
+ if (MathUtil.equals(roll, rot))
+ return;
+ this.roll = MathUtil.reduce360(rot);
+ internalRepaint();
+ }
+
+ private void setYaw(double rot) {
+ if (MathUtil.equals(yaw, rot))
+ return;
+ this.yaw = MathUtil.reduce360(rot);
+ internalRepaint();
+ }
+
+ // ///////////// Extra methods
+
+ private Coordinate project(Coordinate c, GL2 gl, GLU glu) {
+ double[] mvmatrix = new double[16];
+ double[] projmatrix = new double[16];
+ int[] viewport = new int[4];
+
+ gl.glGetIntegerv(GL.GL_VIEWPORT, viewport, 0);
+ gl.glGetDoublev(GLMatrixFunc.GL_MODELVIEW_MATRIX, mvmatrix, 0);
+ gl.glGetDoublev(GLMatrixFunc.GL_PROJECTION_MATRIX, projmatrix, 0);
+
+ double out[] = new double[4];
+ glu.gluProject(c.x, c.y, c.z, mvmatrix, 0, projmatrix, 0, viewport, 0,
+ out, 0);
+
+ return new Coordinate(out[0], out[1], out[2]);
+ }
+
+ private Coordinate cp = new Coordinate(0, 0, 0);
+ private Coordinate cg = new Coordinate(0, 0, 0);
+
+ public void setCG(Coordinate cg) {
+ this.cg = cg;
+ redrawExtras = true;
+ }
+
+ public void setCP(Coordinate cp) {
+ this.cp = cp;
+ redrawExtras = true;
+ }
+
+ public void addRelativeExtra(FigureElement p) {
+ relativeExtra.add(p);
+ redrawExtras = true;
+ }
+
+ public void removeRelativeExtra(FigureElement p) {
+ relativeExtra.remove(p);
+ redrawExtras = true;
+ }
+
+ public void clearRelativeExtra() {
+ relativeExtra.clear();
+ redrawExtras = true;
+ }
+
+ public void addAbsoluteExtra(FigureElement p) {
+ absoluteExtra.add(p);
+ redrawExtras = true;
+ }
+
+ public void removeAbsoluteExtra(FigureElement p) {
+ absoluteExtra.remove(p);
+ redrawExtras = true;
+ }
+
+ public void clearAbsoluteExtra() {
+ absoluteExtra.clear();
+ redrawExtras = true;
+ }
+
+ private ComponentSelectionListener csl;
+
+ public static interface ComponentSelectionListener {
+ public void componentClicked(RocketComponent[] components, MouseEvent e);
+ }
+
+ public void addComponentSelectionListener(
+ ComponentSelectionListener newListener) {
+ this.csl = newListener;
+ }
+
+}
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
index 000000000..49341020c
--- /dev/null
+++ b/core/src/net/sf/openrocket/gui/figure3d/RocketRenderer.java
@@ -0,0 +1,303 @@
+package net.sf.openrocket.gui.figure3d;
+
+import java.awt.Point;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.media.opengl.GL;
+import javax.media.opengl.GL2;
+import javax.media.opengl.GL2ES1;
+import javax.media.opengl.GL2GL3;
+import javax.media.opengl.GLAutoDrawable;
+import javax.media.opengl.fixedfunc.GLLightingFunc;
+
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.rocketcomponent.BodyTube;
+import net.sf.openrocket.rocketcomponent.Configuration;
+import net.sf.openrocket.rocketcomponent.ExternalComponent;
+import net.sf.openrocket.rocketcomponent.MotorMount;
+import net.sf.openrocket.rocketcomponent.NoseCone;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.SymmetricComponent;
+import net.sf.openrocket.rocketcomponent.Transition;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.Color;
+import net.sf.openrocket.util.Coordinate;
+
+/*
+ * @author Bill Kuker
+ */
+public class RocketRenderer {
+ ComponentRenderer cr;
+
+ private final float[] selectedEmissive = { 1, 0, 0, 1 };
+ private final float[] colorBlack = { 0, 0, 0, 1 };
+ private final float[] color = new float[4];
+
+ public void init(GLAutoDrawable drawable) {
+ cr = new ComponentRenderer();
+ cr.init(drawable);
+ }
+
+ public void updateFigure() {
+ cr.updateFigure();
+ }
+
+ private boolean isDrawn(RocketComponent c) {
+ return true;
+ }
+
+ private boolean isDrawnTransparent(RocketComponent c) {
+ if (c instanceof BodyTube)
+ return true;
+ if (c instanceof NoseCone)
+ return false;
+ if (c instanceof SymmetricComponent) {
+ if (((SymmetricComponent) c).isFilled())
+ return false;
+ }
+ if (c instanceof Transition) {
+ Transition t = (Transition) c;
+ return !t.isAftShoulderCapped() && !t.isForeShoulderCapped();
+ }
+ return false;
+ }
+
+ public RocketComponent pick(GLAutoDrawable drawable,
+ Configuration configuration, Point p, Set ignore) {
+ final GL2 gl = drawable.getGL().getGL2();
+ gl.glEnable(GL.GL_DEPTH_TEST);
+
+ //Store a vector of pickable parts.
+ final Vector pickParts = new Vector();
+
+ for (RocketComponent c : configuration) {
+ if ( ignore != null && ignore.contains(c) )
+ continue;
+
+ //Encode the index of the part as a color
+ //if index is 0x0ABC the color ends up as
+ //0xA0B0C000 with each nibble in the coresponding
+ //high bits of the RG and B channels.
+ gl.glColor4ub((byte) ((pickParts.size() >> 4) & 0xF0),
+ (byte) ((pickParts.size() << 0) & 0xF0),
+ (byte) ((pickParts.size() << 4) & 0xF0), (byte) 1);
+ pickParts.add(c);
+
+ if (isDrawnTransparent(c)) {
+ gl.glEnable(GL.GL_CULL_FACE);
+ gl.glCullFace(GL.GL_FRONT);
+ cr.renderGeometry(gl, c);
+ gl.glDisable(GL.GL_CULL_FACE);
+ } else {
+ cr.renderGeometry(gl, c);
+ }
+ }
+
+ ByteBuffer bb = ByteBuffer.allocateDirect(4);
+
+ gl.glReadPixels(p.x, p.y, 1, 1, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, bb);
+
+ final int pickColor = bb.getInt();
+ final int pickIndex = ((pickColor >> 20) & 0xF00) | ((pickColor >> 16) & 0x0F0)
+ | ((pickColor >> 12) & 0x00F);
+
+ if ( pickIndex < 0 || pickIndex > pickParts.size() - 1 )
+ return null;
+
+ return pickParts.get(pickIndex);
+ }
+
+ public void render(GLAutoDrawable drawable, Configuration configuration,
+ Set selection) {
+ if (cr == null)
+ throw new IllegalStateException(this + " Not Initialized");
+
+ GL2 gl = drawable.getGL().getGL2();
+
+ gl.glEnable(GL.GL_DEPTH_TEST); // enables depth testing
+ gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
+
+ // Draw all inner components
+ for (RocketComponent c : configuration) {
+ if (isDrawn(c)) {
+ if (!isDrawnTransparent(c)) {
+ renderComponent(gl, c, 1.0f);
+ }
+ }
+ }
+
+ renderMotors(gl, configuration);
+
+ // Draw Tube and Transition back faces, blended with depth test
+ // so that they show up behind.
+ gl.glEnable(GL.GL_CULL_FACE);
+ gl.glCullFace(GL.GL_FRONT);
+ for (RocketComponent c : configuration) {
+ if (isDrawn(c)) {
+ if (isDrawnTransparent(c)) {
+ renderComponent(gl, c, 1.0f);
+ }
+ }
+ }
+ gl.glDisable(GL.GL_CULL_FACE);
+
+ // Draw T&T front faces blended, without depth test
+ gl.glEnable(GL.GL_BLEND);
+ gl.glEnable(GL.GL_CULL_FACE);
+ gl.glCullFace(GL.GL_BACK);
+ for (RocketComponent c : configuration) {
+ if (isDrawn(c)) {
+ if (isDrawnTransparent(c)) {
+ renderComponent(gl, c, 0.2f);
+ }
+ }
+ }
+ gl.glDisable(GL.GL_BLEND);
+ gl.glDisable(GL.GL_CULL_FACE);
+
+ gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_EMISSION,
+ selectedEmissive, 0);
+ gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_DIFFUSE, colorBlack, 0);
+ gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_AMBIENT, colorBlack, 0);
+ gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_SPECULAR, colorBlack, 0);
+
+ gl.glDepthMask(false);
+ gl.glDisable(GL.GL_DEPTH_TEST);
+ gl.glEnable(GL.GL_STENCIL_TEST);
+
+ for (RocketComponent c : configuration) {
+ if (selection.contains(c)) {
+ // So it is faster to do this once before the loop,
+ // but then the outlines are not as good if you multi-select.
+ // Not sure which to do.
+
+ gl.glStencilMask(1);
+ gl.glDisable(GL.GL_SCISSOR_TEST);
+ gl.glClearStencil(0);
+ gl.glClear(GL.GL_STENCIL_BUFFER_BIT);
+ gl.glStencilMask(0);
+
+ gl.glStencilFunc(GL.GL_ALWAYS, 1, 1);
+ gl.glStencilMask(1);
+ gl.glColorMask(false, false, false, false);
+ gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2GL3.GL_FILL);
+ gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_REPLACE);
+ cr.renderGeometry(gl, c);
+ gl.glStencilMask(0);
+
+ gl.glColorMask(true, true, true, true);
+ gl.glStencilFunc(GL.GL_NOTEQUAL, 1, 1);
+ gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2GL3.GL_LINE);
+ gl.glLineWidth(5.0f);
+ cr.renderGeometry(gl, c);
+ }
+ }
+ gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2GL3.GL_FILL);
+ gl.glDepthMask(true);
+ gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_EMISSION,
+ colorBlack, 0);
+ gl.glDisable(GL.GL_STENCIL_TEST);
+ gl.glEnable(GL.GL_DEPTH_TEST);
+ }
+
+ private void renderMotors(GL2 gl, Configuration configuration) {
+ String motorID = configuration.getMotorConfigurationID();
+ Iterator iterator = configuration.motorIterator();
+ while (iterator.hasNext()) {
+ MotorMount mount = iterator.next();
+ Motor motor = mount.getMotor(motorID);
+ double length = motor.getLength();
+ double radius = motor.getDiameter() / 2;
+
+ Coordinate[] position = ((RocketComponent) mount)
+ .toAbsolute(new Coordinate(((RocketComponent) mount)
+ .getLength() + mount.getMotorOverhang() - length));
+
+ for (int i = 0; i < position.length; i++) {
+ cr.renderMotor(gl, position[i], length, radius);
+ }
+ }
+
+ }
+
+
+ public void renderComponent(GL2 gl, RocketComponent c, float alpha) {
+ gl.glLightModeli(GL2ES1.GL_LIGHT_MODEL_TWO_SIDE, 1);
+
+ getOutsideColor(c, alpha, color);
+ gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_DIFFUSE, color, 0);
+ gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_AMBIENT, color, 0);
+
+ getSpecularColor(c, alpha, color);
+ gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_SPECULAR, color, 0);
+ gl.glMateriali(GL.GL_FRONT, GLLightingFunc.GL_SHININESS,
+ getShininess(c));
+
+ getInsideColor(c, alpha, color);
+ gl.glMaterialfv(GL.GL_BACK, GLLightingFunc.GL_DIFFUSE, color, 0);
+ gl.glMaterialfv(GL.GL_BACK, GLLightingFunc.GL_AMBIENT, color, 0);
+
+ cr.renderGeometry(gl, c);
+ }
+
+ private int getShininess(RocketComponent c) {
+ if (c instanceof ExternalComponent) {
+ switch (((ExternalComponent) c).getFinish()) {
+ case ROUGH:
+ return 10;
+ case UNFINISHED:
+ return 30;
+ case NORMAL:
+ return 40;
+ case SMOOTH:
+ return 80;
+ case POLISHED:
+ return 128;
+ }
+ return 100;
+ } else {
+ return 20;
+ }
+ }
+
+ private void getSpecularColor(RocketComponent c, float alpha, float[] out) {
+ int shine = getShininess(c);
+ float m = (float) shine / 128.0f;
+ float d = 0.9f;
+ getOutsideColor(c, alpha, out);
+ out[0] = Math.max(out[0], d) * m;
+ out[1] = Math.max(out[1], d) * m;
+ out[2] = Math.max(out[2], d) * m;
+ }
+
+ private void getInsideColor(RocketComponent c, float alpha, float[] out) {
+ float d = 0.4f;
+ getOutsideColor(c, alpha, out);
+ out[0] *= d;
+ out[1] *= d;
+ out[2] *= d;
+ }
+
+ private HashMap, Color> defaultColorCache = new HashMap, Color>();
+ private void getOutsideColor(RocketComponent c, float alpha, float[] out) {
+ Color col;
+ col = c.getColor();
+ if (col == null){
+ if ( defaultColorCache.containsKey(c.getClass()) ){
+ col = defaultColorCache.get(c.getClass());
+ } else {
+ col = Application.getPreferences().getDefaultColor(c.getClass());
+ defaultColorCache.put(c.getClass(), col);
+ }
+ }
+
+ out[0] = Math.max(0.2f, (float) col.getRed() / 255f);
+ out[1] = Math.max(0.2f, (float) col.getGreen() / 255f);
+ out[2] = Math.max(0.2f, (float) col.getBlue() / 255f);
+ out[3] = alpha;
+ }
+}
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
index 000000000..3789a7809
--- /dev/null
+++ b/core/src/net/sf/openrocket/gui/figure3d/TransitionRenderer.java
@@ -0,0 +1,247 @@
+/*
+ ** License Applicability. Except to the extent portions of this file are
+ ** made subject to an alternative license as permitted in the SGI Free
+ ** Software License B, Version 2.0 (the "License"), the contents of this
+ ** file are subject only to the provisions of the License. You may not use
+ ** this file except in compliance with the License. You may obtain a copy
+ ** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600
+ ** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at:
+ **
+ ** http://oss.sgi.com/projects/FreeB
+ **
+ ** Note that, as provided in the License, the Software is distributed on an
+ ** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS
+ ** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND
+ ** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A
+ ** PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
+ **
+ ** NOTE: The Original Code (as defined below) has been licensed to Sun
+ ** Microsystems, Inc. ("Sun") under the SGI Free Software License B
+ ** (Version 1.1), shown above ("SGI License"). Pursuant to Section
+ ** 3.2(3) of the SGI License, Sun is distributing the Covered Code to
+ ** you under an alternative license ("Alternative License"). This
+ ** Alternative License includes all of the provisions of the SGI License
+ ** except that Section 2.2 and 11 are omitted. Any differences between
+ ** the Alternative License and the SGI License are offered solely by Sun
+ ** and not by SGI.
+ **
+ ** Original Code. The Original Code is: OpenGL Sample Implementation,
+ ** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics,
+ ** Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc.
+ ** Copyright in any portions created by third parties is as indicated
+ ** elsewhere herein. All Rights Reserved.
+ **
+ ** Additional Notice Provisions: The application programming interfaces
+ ** established by SGI in conjunction with the Original Code are The
+ ** OpenGL(R) Graphics System: A Specification (Version 1.2.1), released
+ ** April 1, 1999; The OpenGL(R) Graphics System Utility Library (Version
+ ** 1.3), released November 4, 1998; and OpenGL(R) Graphics with the X
+ ** Window System(R) (Version 1.3), released October 19, 1998. This software
+ ** was created using the OpenGL(R) version 1.2.1 Sample Implementation
+ ** published by SGI, but has not been independently verified as being
+ ** compliant with the OpenGL(R) version 1.2.1 Specification.
+ **
+ ** $Date: 2009-03-04 17:23:34 -0800 (Wed, 04 Mar 2009) $ $Revision: 1856 $
+ ** $Header$
+ */
+
+/*
+ * Copyright (c) 2002-2004 LWJGL Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'LWJGL' nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
+ * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
+ * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
+ * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
+ * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
+ * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
+ * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
+ * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed or intended for use
+ * in the design, construction, operation or maintenance of any nuclear
+ * facility.
+ */
+package net.sf.openrocket.gui.figure3d;
+
+import javax.media.opengl.GL;
+import javax.media.opengl.GL2;
+
+import net.sf.openrocket.rocketcomponent.Transition;
+
+public final class TransitionRenderer {
+ private static final boolean textureFlag = true;
+
+ private TransitionRenderer() {
+ }
+
+ public static final void drawTransition(final GL2 gl, final Transition tr,
+ final int slices, final int stacks) {
+
+ double da, r, dz;
+ double x, y, z, nz, nsign;
+ int i, j;
+
+ nsign = 1.0f;
+
+ da = 2.0f * PI / slices;
+ dz = (double) tr.getLength() / stacks;
+
+ double ds = 1.0f / slices;
+ double dt = 1.0f / stacks;
+ double t = 0.0f;
+ z = 0.0f;
+ r = (double) tr.getForeRadius();
+ for (j = 0; j < stacks; j++) {
+ r = (double) tr.getRadius(z);
+ double rNext = (double) tr.getRadius(z + dz);
+
+ if (j == stacks - 1)
+ rNext = (double) tr.getRadius(tr.getLength());
+
+ // Z component of normal vectors
+ nz = -(rNext - r) / dz;
+
+ double s = 0.0f;
+ glBegin(gl, GL2.GL_QUAD_STRIP);
+ for (i = 0; i <= slices; i++) {
+ if (i == slices) {
+ x = sin(0.0f);
+ y = cos(0.0f);
+ } else {
+ x = sin((i * da));
+ y = cos((i * da));
+ }
+ if (nsign == 1.0f) {
+ normal3d(gl, (x * nsign), (y * nsign), (nz * nsign));
+ TXTR_COORD(gl, s, t);
+ glVertex3d(gl, (x * r), (y * r), z);
+ normal3d(gl, (x * nsign), (y * nsign), (nz * nsign));
+ TXTR_COORD(gl, s, t + dt);
+ glVertex3d(gl, (x * rNext), (y * rNext), (z + dz));
+ } else {
+ normal3d(gl, x * nsign, y * nsign, nz * nsign);
+ TXTR_COORD(gl, s, t);
+ glVertex3d(gl, (x * r), (y * r), z);
+ normal3d(gl, x * nsign, y * nsign, nz * nsign);
+ TXTR_COORD(gl, s, t + dt);
+ glVertex3d(gl, (x * rNext), (y * rNext), (z + dz));
+ }
+ s += ds;
+ } // for slices
+ glEnd(gl);
+ // r += dr;
+ t += dt;
+ z += dz;
+ } // for stacks
+
+ }
+
+ // ----------------------------------------------------------------------
+ // Internals only below this point
+ //
+
+ private static final double PI = (double) Math.PI;
+
+ private static final void glBegin(GL gl, int mode) {
+ gl.getGL2().glBegin(mode);
+ }
+
+ private static final void glEnd(GL gl) {
+ gl.getGL2().glEnd();
+ }
+
+ private static final void glVertex3d(GL gl, double x, double y, double z) {
+ gl.getGL2().glVertex3d(x, y, z);
+ }
+
+ private static final void glNormal3d(GL gl, double x, double y, double z) {
+ gl.getGL2().glNormal3d(x, y, z);
+ }
+
+ private static final void glTexCoord2d(GL gl, double x, double y) {
+ gl.getGL2().glTexCoord2d(x, y);
+ }
+
+ /**
+ * Call glNormal3f after scaling normal to unit length.
+ *
+ * @param x
+ * @param y
+ * @param z
+ */
+ private static final void normal3d(GL gl, double x, double y, double z) {
+ double mag;
+
+ mag = (double) Math.sqrt(x * x + y * y + z * z);
+ if (mag > 0.00001F) {
+ x /= mag;
+ y /= mag;
+ z /= mag;
+ }
+ glNormal3d(gl, x, y, z);
+ }
+
+ private static final void TXTR_COORD(GL gl, double x, double y) {
+ if (textureFlag)
+ glTexCoord2d(gl, x, y);
+ }
+
+ private static final double sin(double r) {
+ return (double) Math.sin(r);
+ }
+
+ private static final double cos(double r) {
+ return (double) Math.cos(r);
+ }
+}
diff --git a/core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java
index cf4587243..e82c77ee0 100644
--- a/core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java
+++ b/core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java
@@ -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
+ * @author Bill Kuker
*/
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 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
*/
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);
}
}
diff --git a/core/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java b/core/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java
index d8a0b7634..fba7aaa79 100644
--- a/core/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java
+++ b/core/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java
@@ -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);
+ }
+
}
diff --git a/core/src/net/sf/openrocket/util/JarUtil.java b/core/src/net/sf/openrocket/util/JarUtil.java
index b73438ed1..545cfa581 100644
--- a/core/src/net/sf/openrocket/util/JarUtil.java
+++ b/core/src/net/sf/openrocket/util/JarUtil.java
@@ -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();