--- /dev/null
+package net.sf.openrocket.gui.figure3d;\r
+\r
+import java.awt.Point;\r
+import java.nio.ByteBuffer;\r
+import java.util.HashMap;\r
+import java.util.Iterator;\r
+import java.util.Set;\r
+import java.util.Vector;\r
+\r
+import javax.media.opengl.GL;\r
+import javax.media.opengl.GL2;\r
+import javax.media.opengl.GL2ES1;\r
+import javax.media.opengl.GL2GL3;\r
+import javax.media.opengl.GLAutoDrawable;\r
+import javax.media.opengl.fixedfunc.GLLightingFunc;\r
+\r
+import net.sf.openrocket.motor.Motor;\r
+import net.sf.openrocket.rocketcomponent.BodyTube;\r
+import net.sf.openrocket.rocketcomponent.Configuration;\r
+import net.sf.openrocket.rocketcomponent.ExternalComponent;\r
+import net.sf.openrocket.rocketcomponent.MotorMount;\r
+import net.sf.openrocket.rocketcomponent.NoseCone;\r
+import net.sf.openrocket.rocketcomponent.RocketComponent;\r
+import net.sf.openrocket.rocketcomponent.SymmetricComponent;\r
+import net.sf.openrocket.rocketcomponent.Transition;\r
+import net.sf.openrocket.startup.Application;\r
+import net.sf.openrocket.util.Color;\r
+import net.sf.openrocket.util.Coordinate;\r
+\r
+/*\r
+ * @author Bill Kuker <bkuker@billkuker.com>\r
+ */\r
+public class RocketRenderer {\r
+ ComponentRenderer cr;\r
+\r
+ private final float[] selectedEmissive = { 1, 0, 0, 1 };\r
+ private final float[] colorBlack = { 0, 0, 0, 1 };\r
+ private final float[] color = new float[4];\r
+\r
+ public void init(GLAutoDrawable drawable) {\r
+ cr = new ComponentRenderer();\r
+ cr.init(drawable);\r
+ }\r
+ \r
+ public void updateFigure() {\r
+ cr.updateFigure();\r
+ }\r
+\r
+ private boolean isDrawn(RocketComponent c) {\r
+ return true;\r
+ }\r
+\r
+ private boolean isDrawnTransparent(RocketComponent c) {\r
+ if (c instanceof BodyTube)\r
+ return true;\r
+ if (c instanceof NoseCone)\r
+ return false;\r
+ if (c instanceof SymmetricComponent) {\r
+ if (((SymmetricComponent) c).isFilled())\r
+ return false;\r
+ }\r
+ if (c instanceof Transition) {\r
+ Transition t = (Transition) c;\r
+ return !t.isAftShoulderCapped() && !t.isForeShoulderCapped();\r
+ }\r
+ return false;\r
+ }\r
+\r
+ public RocketComponent pick(GLAutoDrawable drawable,\r
+ Configuration configuration, Point p, Set<RocketComponent> ignore) {\r
+ final GL2 gl = drawable.getGL().getGL2();\r
+ gl.glEnable(GL.GL_DEPTH_TEST);\r
+ \r
+ //Store a vector of pickable parts.\r
+ final Vector<RocketComponent> pickParts = new Vector<RocketComponent>();\r
+ \r
+ for (RocketComponent c : configuration) {\r
+ if ( ignore != null && ignore.contains(c) )\r
+ continue;\r
+\r
+ //Encode the index of the part as a color\r
+ //if index is 0x0ABC the color ends up as\r
+ //0xA0B0C000 with each nibble in the coresponding\r
+ //high bits of the RG and B channels.\r
+ gl.glColor4ub((byte) ((pickParts.size() >> 4) & 0xF0),\r
+ (byte) ((pickParts.size() << 0) & 0xF0),\r
+ (byte) ((pickParts.size() << 4) & 0xF0), (byte) 1);\r
+ pickParts.add(c);\r
+ \r
+ if (isDrawnTransparent(c)) {\r
+ gl.glEnable(GL.GL_CULL_FACE);\r
+ gl.glCullFace(GL.GL_FRONT);\r
+ cr.renderGeometry(gl, c);\r
+ gl.glDisable(GL.GL_CULL_FACE);\r
+ } else {\r
+ cr.renderGeometry(gl, c);\r
+ }\r
+ }\r
+\r
+ ByteBuffer bb = ByteBuffer.allocateDirect(4);\r
+\r
+ gl.glReadPixels(p.x, p.y, 1, 1, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, bb);\r
+\r
+ final int pickColor = bb.getInt();\r
+ final int pickIndex = ((pickColor >> 20) & 0xF00) | ((pickColor >> 16) & 0x0F0)\r
+ | ((pickColor >> 12) & 0x00F);\r
+\r
+ if ( pickIndex < 0 || pickIndex > pickParts.size() - 1 )\r
+ return null;\r
+ \r
+ return pickParts.get(pickIndex);\r
+ }\r
+\r
+ public void render(GLAutoDrawable drawable, Configuration configuration,\r
+ Set<RocketComponent> selection) {\r
+ if (cr == null)\r
+ throw new IllegalStateException(this + " Not Initialized");\r
+\r
+ GL2 gl = drawable.getGL().getGL2();\r
+\r
+ gl.glEnable(GL.GL_DEPTH_TEST); // enables depth testing\r
+ gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);\r
+\r
+ // Draw all inner components\r
+ for (RocketComponent c : configuration) {\r
+ if (isDrawn(c)) {\r
+ if (!isDrawnTransparent(c)) {\r
+ renderComponent(gl, c, 1.0f);\r
+ }\r
+ }\r
+ }\r
+\r
+ renderMotors(gl, configuration);\r
+\r
+ // Draw Tube and Transition back faces, blended with depth test\r
+ // so that they show up behind.\r
+ gl.glEnable(GL.GL_CULL_FACE);\r
+ gl.glCullFace(GL.GL_FRONT);\r
+ for (RocketComponent c : configuration) {\r
+ if (isDrawn(c)) {\r
+ if (isDrawnTransparent(c)) {\r
+ renderComponent(gl, c, 1.0f);\r
+ }\r
+ }\r
+ }\r
+ gl.glDisable(GL.GL_CULL_FACE);\r
+\r
+ // Draw T&T front faces blended, without depth test\r
+ gl.glEnable(GL.GL_BLEND);\r
+ gl.glEnable(GL.GL_CULL_FACE);\r
+ gl.glCullFace(GL.GL_BACK);\r
+ for (RocketComponent c : configuration) {\r
+ if (isDrawn(c)) {\r
+ if (isDrawnTransparent(c)) {\r
+ renderComponent(gl, c, 0.2f);\r
+ }\r
+ }\r
+ }\r
+ gl.glDisable(GL.GL_BLEND);\r
+ gl.glDisable(GL.GL_CULL_FACE);\r
+\r
+ gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_EMISSION,\r
+ selectedEmissive, 0);\r
+ gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_DIFFUSE, colorBlack, 0);\r
+ gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_AMBIENT, colorBlack, 0);\r
+ gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_SPECULAR, colorBlack, 0);\r
+ \r
+ gl.glDepthMask(false);\r
+ gl.glDisable(GL.GL_DEPTH_TEST);\r
+ gl.glEnable(GL.GL_STENCIL_TEST);\r
+\r
+ for (RocketComponent c : configuration) {\r
+ if (selection.contains(c)) {\r
+ // So it is faster to do this once before the loop,\r
+ // but then the outlines are not as good if you multi-select.\r
+ // Not sure which to do.\r
+\r
+ gl.glStencilMask(1);\r
+ gl.glDisable(GL.GL_SCISSOR_TEST);\r
+ gl.glClearStencil(0);\r
+ gl.glClear(GL.GL_STENCIL_BUFFER_BIT);\r
+ gl.glStencilMask(0);\r
+\r
+ gl.glStencilFunc(GL.GL_ALWAYS, 1, 1);\r
+ gl.glStencilMask(1);\r
+ gl.glColorMask(false, false, false, false);\r
+ gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2GL3.GL_FILL);\r
+ gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_REPLACE);\r
+ cr.renderGeometry(gl, c);\r
+ gl.glStencilMask(0);\r
+\r
+ gl.glColorMask(true, true, true, true);\r
+ gl.glStencilFunc(GL.GL_NOTEQUAL, 1, 1);\r
+ gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2GL3.GL_LINE);\r
+ gl.glLineWidth(5.0f);\r
+ cr.renderGeometry(gl, c);\r
+ }\r
+ }\r
+ gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2GL3.GL_FILL);\r
+ gl.glDepthMask(true);\r
+ gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_EMISSION,\r
+ colorBlack, 0);\r
+ gl.glDisable(GL.GL_STENCIL_TEST);\r
+ gl.glEnable(GL.GL_DEPTH_TEST);\r
+ }\r
+\r
+ private void renderMotors(GL2 gl, Configuration configuration) {\r
+ String motorID = configuration.getMotorConfigurationID();\r
+ Iterator<MotorMount> iterator = configuration.motorIterator();\r
+ while (iterator.hasNext()) {\r
+ MotorMount mount = iterator.next();\r
+ Motor motor = mount.getMotor(motorID);\r
+ double length = motor.getLength();\r
+ double radius = motor.getDiameter() / 2;\r
+\r
+ Coordinate[] position = ((RocketComponent) mount)\r
+ .toAbsolute(new Coordinate(((RocketComponent) mount)\r
+ .getLength() + mount.getMotorOverhang() - length));\r
+\r
+ for (int i = 0; i < position.length; i++) {\r
+ cr.renderMotor(gl, position[i], length, radius);\r
+ }\r
+ }\r
+\r
+ }\r
+\r
+ \r
+ public void renderComponent(GL2 gl, RocketComponent c, float alpha) {\r
+ gl.glLightModeli(GL2ES1.GL_LIGHT_MODEL_TWO_SIDE, 1);\r
+\r
+ getOutsideColor(c, alpha, color);\r
+ gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_DIFFUSE, color, 0);\r
+ gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_AMBIENT, color, 0);\r
+\r
+ getSpecularColor(c, alpha, color);\r
+ gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_SPECULAR, color, 0);\r
+ gl.glMateriali(GL.GL_FRONT, GLLightingFunc.GL_SHININESS,\r
+ getShininess(c));\r
+\r
+ getInsideColor(c, alpha, color);\r
+ gl.glMaterialfv(GL.GL_BACK, GLLightingFunc.GL_DIFFUSE, color, 0);\r
+ gl.glMaterialfv(GL.GL_BACK, GLLightingFunc.GL_AMBIENT, color, 0);\r
+\r
+ cr.renderGeometry(gl, c);\r
+ }\r
+\r
+ private int getShininess(RocketComponent c) {\r
+ if (c instanceof ExternalComponent) {\r
+ switch (((ExternalComponent) c).getFinish()) {\r
+ case ROUGH:\r
+ return 10;\r
+ case UNFINISHED:\r
+ return 30;\r
+ case NORMAL:\r
+ return 40;\r
+ case SMOOTH:\r
+ return 80;\r
+ case POLISHED:\r
+ return 128;\r
+ }\r
+ return 100;\r
+ } else {\r
+ return 20;\r
+ }\r
+ }\r
+\r
+ private void getSpecularColor(RocketComponent c, float alpha, float[] out) {\r
+ int shine = getShininess(c);\r
+ float m = (float) shine / 128.0f;\r
+ float d = 0.9f;\r
+ getOutsideColor(c, alpha, out);\r
+ out[0] = Math.max(out[0], d) * m;\r
+ out[1] = Math.max(out[1], d) * m;\r
+ out[2] = Math.max(out[2], d) * m;\r
+ }\r
+\r
+ private void getInsideColor(RocketComponent c, float alpha, float[] out) {\r
+ float d = 0.4f;\r
+ getOutsideColor(c, alpha, out);\r
+ out[0] *= d;\r
+ out[1] *= d;\r
+ out[2] *= d;\r
+ }\r
+\r
+ private HashMap<Class<?>, Color> defaultColorCache = new HashMap<Class<?>, Color>();\r
+ private void getOutsideColor(RocketComponent c, float alpha, float[] out) {\r
+ Color col;\r
+ col = c.getColor();\r
+ if (col == null){\r
+ if ( defaultColorCache.containsKey(c.getClass()) ){\r
+ col = defaultColorCache.get(c.getClass());\r
+ } else {\r
+ col = Application.getPreferences().getDefaultColor(c.getClass());\r
+ defaultColorCache.put(c.getClass(), col);\r
+ }\r
+ }\r
+ \r
+ out[0] = Math.max(0.2f, (float) col.getRed() / 255f);\r
+ out[1] = Math.max(0.2f, (float) col.getGreen() / 255f);\r
+ out[2] = Math.max(0.2f, (float) col.getBlue() / 255f);\r
+ out[3] = alpha;\r
+ }\r
+}\r