create changelog entry
[debian/openrocket] / core / src / net / sf / openrocket / gui / figure3d / RocketRenderer.java
1 package net.sf.openrocket.gui.figure3d;\r
2 \r
3 import java.awt.Point;\r
4 import java.nio.ByteBuffer;\r
5 import java.util.HashMap;\r
6 import java.util.Iterator;\r
7 import java.util.Set;\r
8 import java.util.Vector;\r
9 \r
10 import javax.media.opengl.GL;\r
11 import javax.media.opengl.GL2;\r
12 import javax.media.opengl.GL2ES1;\r
13 import javax.media.opengl.GL2GL3;\r
14 import javax.media.opengl.GLAutoDrawable;\r
15 import javax.media.opengl.fixedfunc.GLLightingFunc;\r
16 \r
17 import net.sf.openrocket.motor.Motor;\r
18 import net.sf.openrocket.rocketcomponent.BodyTube;\r
19 import net.sf.openrocket.rocketcomponent.Configuration;\r
20 import net.sf.openrocket.rocketcomponent.ExternalComponent;\r
21 import net.sf.openrocket.rocketcomponent.MotorMount;\r
22 import net.sf.openrocket.rocketcomponent.NoseCone;\r
23 import net.sf.openrocket.rocketcomponent.RocketComponent;\r
24 import net.sf.openrocket.rocketcomponent.SymmetricComponent;\r
25 import net.sf.openrocket.rocketcomponent.Transition;\r
26 import net.sf.openrocket.startup.Application;\r
27 import net.sf.openrocket.util.Color;\r
28 import net.sf.openrocket.util.Coordinate;\r
29 \r
30 /*\r
31  * @author Bill Kuker <bkuker@billkuker.com>\r
32  */\r
33 public class RocketRenderer {\r
34         ComponentRenderer cr;\r
35 \r
36         private final float[] selectedEmissive = { 1, 0, 0, 1 };\r
37         private final float[] colorBlack = { 0, 0, 0, 1 };\r
38         private final float[] color = new float[4];\r
39 \r
40         public void init(GLAutoDrawable drawable) {\r
41                 cr = new ComponentRenderer();\r
42                 cr.init(drawable);\r
43         }\r
44         \r
45         public void updateFigure() {\r
46                 cr.updateFigure();\r
47         }\r
48 \r
49         private boolean isDrawn(RocketComponent c) {\r
50                 return true;\r
51         }\r
52 \r
53         private boolean isDrawnTransparent(RocketComponent c) {\r
54                 if (c instanceof BodyTube)\r
55                         return true;\r
56                 if (c instanceof NoseCone)\r
57                         return false;\r
58                 if (c instanceof SymmetricComponent) {\r
59                         if (((SymmetricComponent) c).isFilled())\r
60                                 return false;\r
61                 }\r
62                 if (c instanceof Transition) {\r
63                         Transition t = (Transition) c;\r
64                         return !t.isAftShoulderCapped() && !t.isForeShoulderCapped();\r
65                 }\r
66                 return false;\r
67         }\r
68 \r
69         public RocketComponent pick(GLAutoDrawable drawable,\r
70                         Configuration configuration, Point p, Set<RocketComponent> ignore) {\r
71                 final GL2 gl = drawable.getGL().getGL2();\r
72                 gl.glEnable(GL.GL_DEPTH_TEST);\r
73                 \r
74                 //Store a vector of pickable parts.\r
75                 final Vector<RocketComponent> pickParts = new Vector<RocketComponent>();\r
76                 \r
77                 for (RocketComponent c : configuration) {\r
78                         if ( ignore != null && ignore.contains(c) )\r
79                                 continue;\r
80 \r
81                         //Encode the index of the part as a color\r
82                         //if index is 0x0ABC the color ends up as\r
83                         //0xA0B0C000 with each nibble in the coresponding\r
84                         //high bits of the RG and B channels.\r
85                         gl.glColor4ub((byte) ((pickParts.size() >> 4) & 0xF0),\r
86                                         (byte) ((pickParts.size() << 0) & 0xF0),\r
87                                         (byte) ((pickParts.size() << 4) & 0xF0), (byte) 1);\r
88                         pickParts.add(c);\r
89                         \r
90                         if (isDrawnTransparent(c)) {\r
91                                 gl.glEnable(GL.GL_CULL_FACE);\r
92                                 gl.glCullFace(GL.GL_FRONT);\r
93                                 cr.renderGeometry(gl, c);\r
94                                 gl.glDisable(GL.GL_CULL_FACE);\r
95                         } else {\r
96                                 cr.renderGeometry(gl, c);\r
97                         }\r
98                 }\r
99 \r
100                 ByteBuffer bb = ByteBuffer.allocateDirect(4);\r
101 \r
102                 gl.glReadPixels(p.x, p.y, 1, 1, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, bb);\r
103 \r
104                 final int pickColor = bb.getInt();\r
105                 final int pickIndex = ((pickColor >> 20) & 0xF00) | ((pickColor >> 16) & 0x0F0)\r
106                                 | ((pickColor >> 12) & 0x00F);\r
107 \r
108                 if ( pickIndex < 0 || pickIndex > pickParts.size() - 1 )\r
109                         return null;\r
110                 \r
111                 return pickParts.get(pickIndex);\r
112         }\r
113 \r
114         public void render(GLAutoDrawable drawable, Configuration configuration,\r
115                         Set<RocketComponent> selection) {\r
116                 if (cr == null)\r
117                         throw new IllegalStateException(this + " Not Initialized");\r
118 \r
119                 GL2 gl = drawable.getGL().getGL2();\r
120 \r
121                 gl.glEnable(GL.GL_DEPTH_TEST); // enables depth testing\r
122                 gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);\r
123 \r
124                 // Draw all inner components\r
125                 for (RocketComponent c : configuration) {\r
126                         if (isDrawn(c)) {\r
127                                 if (!isDrawnTransparent(c)) {\r
128                                         renderComponent(gl, c, 1.0f);\r
129                                 }\r
130                         }\r
131                 }\r
132 \r
133                 renderMotors(gl, configuration);\r
134 \r
135                 // Draw Tube and Transition back faces, blended with depth test\r
136                 // so that they show up behind.\r
137                 gl.glEnable(GL.GL_CULL_FACE);\r
138                 gl.glCullFace(GL.GL_FRONT);\r
139                 for (RocketComponent c : configuration) {\r
140                         if (isDrawn(c)) {\r
141                                 if (isDrawnTransparent(c)) {\r
142                                         renderComponent(gl, c, 1.0f);\r
143                                 }\r
144                         }\r
145                 }\r
146                 gl.glDisable(GL.GL_CULL_FACE);\r
147 \r
148                 // Draw T&T front faces blended, without depth test\r
149                 gl.glEnable(GL.GL_BLEND);\r
150                 gl.glEnable(GL.GL_CULL_FACE);\r
151                 gl.glCullFace(GL.GL_BACK);\r
152                 for (RocketComponent c : configuration) {\r
153                         if (isDrawn(c)) {\r
154                                 if (isDrawnTransparent(c)) {\r
155                                         renderComponent(gl, c, 0.2f);\r
156                                 }\r
157                         }\r
158                 }\r
159                 gl.glDisable(GL.GL_BLEND);\r
160                 gl.glDisable(GL.GL_CULL_FACE);\r
161 \r
162                 gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_EMISSION,\r
163                                 selectedEmissive, 0);\r
164                 gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_DIFFUSE, colorBlack, 0);\r
165                 gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_AMBIENT, colorBlack, 0);\r
166                 gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_SPECULAR, colorBlack, 0);\r
167                 \r
168                 gl.glDepthMask(false);\r
169                 gl.glDisable(GL.GL_DEPTH_TEST);\r
170                 gl.glEnable(GL.GL_STENCIL_TEST);\r
171 \r
172                 for (RocketComponent c : configuration) {\r
173                         if (selection.contains(c)) {\r
174                                 // So it is faster to do this once before the loop,\r
175                                 // but then the outlines are not as good if you multi-select.\r
176                                 // Not sure which to do.\r
177 \r
178                                 gl.glStencilMask(1);\r
179                                 gl.glDisable(GL.GL_SCISSOR_TEST);\r
180                                 gl.glClearStencil(0);\r
181                                 gl.glClear(GL.GL_STENCIL_BUFFER_BIT);\r
182                                 gl.glStencilMask(0);\r
183 \r
184                                 gl.glStencilFunc(GL.GL_ALWAYS, 1, 1);\r
185                                 gl.glStencilMask(1);\r
186                                 gl.glColorMask(false, false, false, false);\r
187                                 gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2GL3.GL_FILL);\r
188                                 gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_REPLACE);\r
189                                 cr.renderGeometry(gl, c);\r
190                                 gl.glStencilMask(0);\r
191 \r
192                                 gl.glColorMask(true, true, true, true);\r
193                                 gl.glStencilFunc(GL.GL_NOTEQUAL, 1, 1);\r
194                                 gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2GL3.GL_LINE);\r
195                                 gl.glLineWidth(5.0f);\r
196                                 cr.renderGeometry(gl, c);\r
197                         }\r
198                 }\r
199                 gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2GL3.GL_FILL);\r
200                 gl.glDepthMask(true);\r
201                 gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GLLightingFunc.GL_EMISSION,\r
202                                 colorBlack, 0);\r
203                 gl.glDisable(GL.GL_STENCIL_TEST);\r
204                 gl.glEnable(GL.GL_DEPTH_TEST);\r
205         }\r
206 \r
207         private void renderMotors(GL2 gl, Configuration configuration) {\r
208                 String motorID = configuration.getMotorConfigurationID();\r
209                 Iterator<MotorMount> iterator = configuration.motorIterator();\r
210                 while (iterator.hasNext()) {\r
211                         MotorMount mount = iterator.next();\r
212                         Motor motor = mount.getMotor(motorID);\r
213                         double length = motor.getLength();\r
214                         double radius = motor.getDiameter() / 2;\r
215 \r
216                         Coordinate[] position = ((RocketComponent) mount)\r
217                                         .toAbsolute(new Coordinate(((RocketComponent) mount)\r
218                                                         .getLength() + mount.getMotorOverhang() - length));\r
219 \r
220                         for (int i = 0; i < position.length; i++) {\r
221                                 cr.renderMotor(gl, position[i], length, radius);\r
222                         }\r
223                 }\r
224 \r
225         }\r
226 \r
227         \r
228         public void renderComponent(GL2 gl, RocketComponent c, float alpha) {\r
229                 gl.glLightModeli(GL2ES1.GL_LIGHT_MODEL_TWO_SIDE, 1);\r
230 \r
231                 getOutsideColor(c, alpha, color);\r
232                 gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_DIFFUSE, color, 0);\r
233                 gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_AMBIENT, color, 0);\r
234 \r
235                 getSpecularColor(c, alpha, color);\r
236                 gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_SPECULAR, color, 0);\r
237                 gl.glMateriali(GL.GL_FRONT, GLLightingFunc.GL_SHININESS,\r
238                                 getShininess(c));\r
239 \r
240                 getInsideColor(c, alpha, color);\r
241                 gl.glMaterialfv(GL.GL_BACK, GLLightingFunc.GL_DIFFUSE, color, 0);\r
242                 gl.glMaterialfv(GL.GL_BACK, GLLightingFunc.GL_AMBIENT, color, 0);\r
243 \r
244                 cr.renderGeometry(gl, c);\r
245         }\r
246 \r
247         private int getShininess(RocketComponent c) {\r
248                 if (c instanceof ExternalComponent) {\r
249                         switch (((ExternalComponent) c).getFinish()) {\r
250                         case ROUGH:\r
251                                 return 10;\r
252                         case UNFINISHED:\r
253                                 return 30;\r
254                         case NORMAL:\r
255                                 return 40;\r
256                         case SMOOTH:\r
257                                 return 80;\r
258                         case POLISHED:\r
259                                 return 128;\r
260                         }\r
261                         return 100;\r
262                 } else {\r
263                         return 20;\r
264                 }\r
265         }\r
266 \r
267         private void getSpecularColor(RocketComponent c, float alpha, float[] out) {\r
268                 int shine = getShininess(c);\r
269                 float m = (float) shine / 128.0f;\r
270                 float d = 0.9f;\r
271                 getOutsideColor(c, alpha, out);\r
272                 out[0] = Math.max(out[0], d) * m;\r
273                 out[1] = Math.max(out[1], d) * m;\r
274                 out[2] = Math.max(out[2], d) * m;\r
275         }\r
276 \r
277         private void getInsideColor(RocketComponent c, float alpha, float[] out) {\r
278                 float d = 0.4f;\r
279                 getOutsideColor(c, alpha, out);\r
280                 out[0] *= d;\r
281                 out[1] *=  d;\r
282                 out[2] *= d;\r
283         }\r
284 \r
285         private HashMap<Class<?>, Color> defaultColorCache = new HashMap<Class<?>, Color>();\r
286         private void getOutsideColor(RocketComponent c, float alpha, float[] out) {\r
287                 Color col;\r
288                 col = c.getColor();\r
289                 if (col == null){\r
290                         if ( defaultColorCache.containsKey(c.getClass()) ){\r
291                                 col = defaultColorCache.get(c.getClass());\r
292                         } else {\r
293                                 col = Application.getPreferences().getDefaultColor(c.getClass());\r
294                                 defaultColorCache.put(c.getClass(), col);\r
295                         }\r
296                 }\r
297                         \r
298                 out[0] = Math.max(0.2f, (float) col.getRed() / 255f);\r
299                 out[1] = Math.max(0.2f, (float) col.getGreen() / 255f);\r
300                 out[2] = Math.max(0.2f, (float) col.getBlue() / 255f);\r
301                 out[3] = alpha;\r
302         }\r
303 }\r