From: bkuker Date: Mon, 11 Jun 2012 20:25:41 +0000 (+0000) Subject: JOGL Based support for a 3D view of the rocket. X-Git-Tag: upstream/12.09^2~187 X-Git-Url: https://git.gag.com/?a=commitdiff_plain;h=6c394293bc3f3e275c1a77919214914876d27e8a;hp=109b95aae3323e9c51b58d33d61dfe0a6c5bb7bc;p=debian%2Fopenrocket JOGL Based support for a 3D view of the rocket. 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 --- diff --git a/core/.classpath b/core/.classpath index 002d4561..d50734aa 100644 --- a/core/.classpath +++ b/core/.classpath @@ -26,7 +26,9 @@ - - + + + + diff --git a/core/build.xml b/core/build.xml index af9ed8a4..7f3ad8d0 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 00000000..ebb12879 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 00000000..2beef199 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 00000000..e0bc10f4 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 00000000..05d72f81 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 00000000..a77bcf11 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 00000000..39fbfb27 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 00000000..de5c8150 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 00000000..951175ba 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 00000000..dd846ace 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 00000000..3edc2950 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 00000000..5eb5a76b 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 00000000..aee77f2d 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 00000000..59a3effc 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 00000000..80cdbfc0 --- /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 00000000..4c1d9344 --- /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 00000000..49dbbc9f --- /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 00000000..3402f361 --- /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 00000000..49341020 --- /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 00000000..3789a780 --- /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 cf458724..e82c77ee 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 d8a0b763..fba7aaa7 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 b73438ed..545cfa58 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();