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