New panels.
[sw/motorsim] / src / com / billkuker / rocketry / motorsim / grain / ExtrudedGrain.java
1 package com.billkuker.rocketry.motorsim.grain;\r
2 \r
3 import java.awt.BasicStroke;\r
4 import java.awt.BorderLayout;\r
5 import java.awt.Color;\r
6 import java.awt.Dimension;\r
7 import java.awt.Graphics;\r
8 import java.awt.Graphics2D;\r
9 import java.awt.Rectangle;\r
10 import java.awt.Shape;\r
11 import java.awt.geom.AffineTransform;\r
12 import java.awt.geom.Ellipse2D;\r
13 import java.awt.geom.GeneralPath;\r
14 import java.awt.geom.PathIterator;\r
15 import java.awt.geom.Rectangle2D;\r
16 import java.util.HashSet;\r
17 import java.util.Iterator;\r
18 import java.util.NoSuchElementException;\r
19 import java.util.Set;\r
20 import java.util.SortedMap;\r
21 import java.util.TreeMap;\r
22 \r
23 import javax.measure.quantity.Area;\r
24 import javax.measure.quantity.Dimensionless;\r
25 import javax.measure.quantity.Length;\r
26 import javax.measure.quantity.Volume;\r
27 import javax.measure.unit.SI;\r
28 import javax.swing.JFrame;\r
29 import javax.swing.JLabel;\r
30 import javax.swing.JPanel;\r
31 import javax.swing.JSlider;\r
32 import javax.swing.JSplitPane;\r
33 import javax.swing.event.ChangeEvent;\r
34 import javax.swing.event.ChangeListener;\r
35 \r
36 import org.jscience.physics.amount.Amount;\r
37 \r
38 import com.billkuker.rocketry.motorsim.Grain;\r
39 import com.billkuker.rocketry.motorsim.visual.Chart;\r
40 \r
41 public class ExtrudedGrain implements Grain, Grain.Graphical {\r
42 \r
43         Set<Shape> plus = new HashSet<Shape>();\r
44 \r
45         Set<Shape> minus = new HashSet<Shape>();\r
46 \r
47         Set<Shape> inhibited = new HashSet<Shape>();\r
48 \r
49         Amount<Length> length = Amount.valueOf(25, SI.MILLIMETER);\r
50 \r
51         Amount<Length> rStep;\r
52 \r
53         private class RegEntry {\r
54                 Amount<Area> surfaceArea;\r
55 \r
56                 Amount<Volume> volume;\r
57         }\r
58 \r
59         Amount<Length> webThickness;\r
60 \r
61         {\r
62                 /*\r
63                  * Similar test grain Shape outside = new Ellipse2D.Double(50,50,30,30);\r
64                  * plus.add(outside); minus.add(new Ellipse2D.Double(50,60,10,10));\r
65                  * inhibited.add(outside); length = Amount.valueOf(70, SI.MILLIMETER); /\r
66                  */\r
67 \r
68                 /* Big c-slot */\r
69                 Shape outside = new Ellipse2D.Double(0, 0, 30, 30);\r
70                 plus.add(outside);\r
71                 inhibited.add(outside);\r
72                 minus.add(new Rectangle2D.Double(13, 13, 4, 30));\r
73                 //minus.add(new Ellipse2D.Double(12, 12, 6, 6));\r
74                 length = Amount.valueOf(70, SI.MILLIMETER);\r
75                 /**/\r
76 \r
77                 /*\r
78                  * Plus sign Shape outside = new Ellipse2D.Double(0,0,200,200);\r
79                  * plus.add(outside); inhibited.add(outside); minus.add(new\r
80                  * Rectangle2D.Double(90,40,20,120)); minus.add(new\r
81                  * Rectangle2D.Double(40,90,120,20));\r
82                  */\r
83 \r
84                 findWebThickness();\r
85 \r
86         }\r
87 \r
88         @Override\r
89         public Amount<Area> surfaceArea(Amount<Length> regression) {\r
90                 Amount<Area> zero = Amount.valueOf(0, Area.UNIT);\r
91                 \r
92                 if (regression.isGreaterThan(webThickness))\r
93                         return zero;\r
94                 \r
95                 Amount<Length> rLen = length.minus(regression.times(2));\r
96                 if (rLen.isLessThan(Amount.valueOf(0, SI.MILLIMETER)))\r
97                         return zero;\r
98 \r
99                 java.awt.geom.Area burn = getCrossSection(regression);\r
100                 \r
101                 if (burn.isEmpty())\r
102                         return zero;\r
103                 \r
104                 burn.subtract(getCrossSection(regression.plus(Amount.valueOf(.001,\r
105                                 SI.MILLIMETER))));\r
106         \r
107                 Amount<Area> xSection = crossSectionArea(regression);\r
108 \r
109                 return perimeter(burn).divide(2).times(rLen).plus(\r
110                                 xSection.times(2)).to(Area.UNIT);\r
111         }\r
112 \r
113         @Override\r
114         public Amount<Volume> volume(Amount<Length> regression) {\r
115                 Amount<Volume> zero = Amount.valueOf(0, Volume.UNIT);\r
116                 \r
117                 Amount<Length> rLen = length.minus(regression.times(2));\r
118                 if (rLen.isLessThan(Amount.valueOf(0, SI.MILLIMETER)))\r
119                         return zero;\r
120                 \r
121                 Amount<Area> xSection = crossSectionArea(regression);\r
122 \r
123                 return xSection.times(rLen).to(Volume.UNIT);\r
124 \r
125         }\r
126 \r
127 \r
128 \r
129         \r
130         private Amount<Area> crossSectionArea(Amount<Length> regression) {\r
131                 //Get the PLUS shape and sum its area\r
132                 java.awt.geom.Area plus = getPlus(regression);\r
133                 Amount<Area> plusArea = Amount.valueOf(0, SI.SQUARE_METRE);\r
134                 for (java.awt.geom.Area a : separate(plus)) {\r
135                         plusArea = plusArea.plus(area(a));\r
136                 }\r
137                 \r
138                 //Get the MINUS shape, intersect it with PLUS to get just the parts\r
139                 //that are removed, sum it's area\r
140                 java.awt.geom.Area minus = getMinus(regression);\r
141                 minus.intersect(plus);\r
142                 Amount<Area> minusArea = Amount.valueOf(0, SI.SQUARE_METRE);\r
143                 for (java.awt.geom.Area a : separate(minus)) {\r
144                         minusArea = minusArea.plus(area(a));\r
145                 }\r
146                 \r
147                 //Subtract PLUS from MINUS and return\r
148                 Amount<Area> area = plusArea.minus(minusArea);\r
149 \r
150                 return area;\r
151         }\r
152 \r
153         @Override\r
154         public Amount<Length> webThickness() {\r
155                 return webThickness;\r
156         }\r
157 \r
158         private Amount<Length> perimeter(java.awt.geom.Area a) {\r
159                 //TODO: I think I need to handle seg_close!!\r
160                 PathIterator i = a.getPathIterator(new AffineTransform(), .001);\r
161                 double x = 0, y = 0;\r
162                 double len = 0;\r
163                 while (!i.isDone()) {\r
164                         double coords[] = new double[6];\r
165                         int type = i.currentSegment(coords);\r
166                         if (type == PathIterator.SEG_LINETO) {\r
167                                 // System.out.println("Line");\r
168                                 double nx = coords[0];\r
169                                 double ny = coords[1];\r
170                                 // System.out.println(x+","+y+ " to " + nx+"," + ny);\r
171                                 len += Math.sqrt(Math.pow(x - nx, 2) + Math.pow(y - ny, 2));\r
172                                 x = nx;\r
173                                 y = ny;\r
174                         } else if (type == PathIterator.SEG_MOVETO) {\r
175                                 // System.out.println("Move");\r
176                                 x = coords[0];\r
177                                 y = coords[1];\r
178                         } else {\r
179                                 // System.err.println("Got " + type);\r
180                         }\r
181                         i.next();\r
182                 }\r
183                 return Amount.valueOf(len, SI.MILLIMETER);\r
184         }\r
185 \r
186         private void findWebThickness() {\r
187                 java.awt.geom.Area a = getCrossSection(Amount.valueOf(0, SI.MILLIMETER));\r
188                 Rectangle r = a.getBounds();\r
189                 double max = r.getWidth() < r.getHeight() ? r.getHeight() : r\r
190                                 .getWidth(); // The max size\r
191                 double min = 0;\r
192                 double guess;\r
193                 while (true) {\r
194                         guess = min + (max - min) / 2; // Guess halfway through\r
195                         System.out.println("Min: " + min + " Guess: " + guess + " Max: "\r
196                                         + max);\r
197                         a = getCrossSection(Amount.valueOf(guess, SI.MILLIMETER));\r
198                         if (a.isEmpty()) {\r
199                                 // guess is too big\r
200                                 max = guess;\r
201                         } else {\r
202                                 // min is too big\r
203                                 min = guess;\r
204                         }\r
205                         if ((max - min) < .01)\r
206                                 break;\r
207                 }\r
208                 webThickness = Amount.valueOf(guess, SI.MILLIMETER);\r
209                 if (webThickness.isGreaterThan(length.divide(2)))\r
210                         webThickness = length.divide(2);\r
211         }\r
212 \r
213         @Override\r
214         public java.awt.geom.Area getCrossSection(Amount<Length> regression) {\r
215                 java.awt.geom.Area res = getPlus(regression);\r
216                 res.subtract(getMinus(regression));\r
217                 return res;\r
218         }\r
219         \r
220         @Override\r
221         public java.awt.geom.Area getSideView(Amount<Length> regression) {\r
222                 java.awt.geom.Area res = new java.awt.geom.Area();\r
223                 double rLenmm = length.minus(regression.times(2)).doubleValue(SI.MILLIMETER);\r
224                 \r
225                 for( java.awt.geom.Area a : separate(getCrossSection(regression))){\r
226                         Rectangle2D bounds = a.getBounds2D();\r
227                         Rectangle2D side = new Rectangle2D.Double(bounds.getMinX(), -rLenmm/2.0, bounds.getWidth(), rLenmm);\r
228                         res.add(new java.awt.geom.Area(side));\r
229                 }\r
230                 return res;\r
231         }\r
232         \r
233         private java.awt.geom.Area getPlus(Amount<Length> regression) {\r
234                 java.awt.geom.Area a = new java.awt.geom.Area();\r
235                 for (Shape s : plus)\r
236                         a.add(new java.awt.geom.Area(regress(s, regression\r
237                                         .doubleValue(SI.MILLIMETER), true)));\r
238                 return a;\r
239         }\r
240         \r
241         private java.awt.geom.Area getMinus(Amount<Length> regression) {\r
242                 java.awt.geom.Area a = new java.awt.geom.Area();\r
243                 for (Shape s : minus)\r
244                         a.add(new java.awt.geom.Area(regress(s, regression\r
245                                         .doubleValue(SI.MILLIMETER), false)));\r
246                 return a;\r
247         }\r
248         \r
249 \r
250         private Shape regress(Shape s, double mm, boolean plus) {\r
251                 if (inhibited.contains(s))\r
252                         return s;\r
253                 if (s instanceof Ellipse2D) {\r
254                         Ellipse2D e = (Ellipse2D) s;\r
255 \r
256                         double d = plus ? -2 * mm : 2 * mm;\r
257 \r
258                         double w = e.getWidth() + d;\r
259                         double h = e.getHeight() + d;\r
260                         double x = e.getX() - d / 2;\r
261                         double y = e.getY() - d / 2;\r
262 \r
263                         return new Ellipse2D.Double(x, y, w, h);\r
264                 } else if (s instanceof Rectangle2D) {\r
265                         Rectangle2D r = (Rectangle2D) s;\r
266                         \r
267                         if ( plus ){\r
268                                 double d = -2 * mm;\r
269                                 double w = r.getWidth() + d;\r
270                                 double h = r.getHeight() + d;\r
271                                 double x = r.getX() - d / 2;\r
272                                 double y = r.getY() - d / 2;\r
273                                 return new Rectangle2D.Double(x, y, w, h);\r
274                         } else {\r
275                                 //A rectangular hole gets rounded corners as it grows\r
276                                 java.awt.geom.Area a = new java.awt.geom.Area();\r
277                                 double d = 2 * mm;\r
278                                 \r
279                                 //Make it wider\r
280                                 double w = r.getWidth() + d;\r
281                                 double h = r.getHeight();\r
282                                 double x = r.getX() - d / 2;\r
283                                 double y = r.getY();\r
284                                 a.add( new java.awt.geom.Area(new Rectangle2D.Double(x, y, w, h)));\r
285                                 \r
286                                 //Make it taller\r
287                                 w = r.getWidth();\r
288                                 h = r.getHeight() + d;\r
289                                 x = r.getX();\r
290                                 y = r.getY() - d / 2;\r
291                                 a.add( new java.awt.geom.Area(new Rectangle2D.Double(x, y, w, h)));\r
292                                 \r
293                                 //Add rounded corners\r
294                                 a.add( new java.awt.geom.Area(new Ellipse2D.Double(r.getX()-mm, r.getY()-mm, mm*2, mm*2)));\r
295                                 a.add( new java.awt.geom.Area(new Ellipse2D.Double(r.getX()+r.getWidth()-mm, r.getY()-mm, mm*2, mm*2)));\r
296                                 a.add( new java.awt.geom.Area(new Ellipse2D.Double(r.getX()+r.getWidth()-mm, r.getY()+r.getHeight()-mm, mm*2, mm*2)));\r
297                                 a.add( new java.awt.geom.Area(new Ellipse2D.Double(r.getX()-mm, r.getY()+r.getHeight()-mm, mm*2, mm*2)));\r
298                                 \r
299                                 return a;\r
300                         }\r
301 \r
302                 }\r
303                 return null;\r
304         }\r
305 \r
306         public static void main(String args[]) throws Exception {\r
307                 ExtrudedGrain e = new ExtrudedGrain();\r
308                 new GrainPanel(e).show();\r
309         }\r
310 \r
311 \r
312         /*\r
313          * Separate an area into multiple distinct area.\r
314          * Area CAN NOT HAVE HOLES. HOLES WILL BE RETURNED AS AREAS,\r
315          * SO A DONUT WILL TURN INTO TWO CIRCLES.\r
316          */\r
317         private Set<java.awt.geom.Area> separate(java.awt.geom.Area a) {\r
318                 Set<java.awt.geom.Area> res = new HashSet<java.awt.geom.Area>();\r
319                 PathIterator i = a.getPathIterator(new AffineTransform());\r
320                 GeneralPath cur = null;\r
321 \r
322                 while (!i.isDone()) {\r
323                         double coords[] = new double[6];\r
324                         int type = i.currentSegment(coords);\r
325                         switch (type) {\r
326                         case PathIterator.SEG_CLOSE:\r
327                                 cur.closePath();\r
328                                 if (cur != null ){\r
329                                         java.awt.geom.Area area = new java.awt.geom.Area(cur);\r
330                                         if ( !a.isEmpty() )\r
331                                                 res.add(area);\r
332                                 }\r
333                                 cur = new GeneralPath(i.getWindingRule());\r
334                                 break;\r
335                         case PathIterator.SEG_MOVETO:\r
336                                 if (cur != null ){\r
337                                         java.awt.geom.Area area = new java.awt.geom.Area(cur);\r
338                                         if ( !a.isEmpty() )\r
339                                                 res.add(area);\r
340                                 }\r
341                                 cur = new GeneralPath(i.getWindingRule());\r
342                                 cur.moveTo(coords[0], coords[1]);\r
343                                 break;\r
344                         case PathIterator.SEG_CUBICTO:\r
345                                 cur.curveTo(coords[0], coords[1], coords[2], coords[3],\r
346                                                 coords[4], coords[5]);\r
347                                 break;\r
348                         case PathIterator.SEG_LINETO:\r
349                                 cur.lineTo(coords[0], coords[1]);\r
350                                 break;\r
351                         case PathIterator.SEG_QUADTO:\r
352                                 cur.quadTo(coords[0], coords[1], coords[2], coords[3]);\r
353                                 break;\r
354 \r
355                         }\r
356                         i.next();\r
357                 }\r
358 \r
359                 return res;\r
360         }\r
361         \r
362         /*\r
363          * Return the Area of a singular polygon (NO HOLES OR DISJOINT PARTS).\r
364          * Coordinates assumed to be in MM.\r
365          * http://valis.cs.uiuc.edu/~sariel/research/CG/compgeom/msg00831.html\r
366          * http://stackoverflow.com/questions/451426/how-do-i-calculate-the-surface-area-of-a-2d-polygon\r
367          * http://www.wikihow.com/Calculate-the-Area-of-a-Polygon\r
368          */\r
369         private Amount<Area> area(java.awt.geom.Area a) {\r
370                 if ( !a.isSingular() )\r
371                         throw new IllegalArgumentException("Can not calculate area of non-singular shape!");\r
372                 PathIterator i = a.getPathIterator(new AffineTransform(), .001);\r
373                 \r
374                 \r
375                 double x = 0, y = 0, sx = 0, sy = 0;\r
376                 double nx, ny;\r
377                 double area = 0;\r
378                 while (!i.isDone()) {\r
379                         double coords[] = new double[6];\r
380                         int type = i.currentSegment(coords);\r
381                         switch( type ){\r
382                         case PathIterator.SEG_CLOSE:\r
383                                 //Go back to the start\r
384                                 nx = sx;\r
385                                 ny = sy;\r
386                                 area += x * ny;\r
387                                 area -= y * nx;\r
388                                 break;\r
389                         case PathIterator.SEG_LINETO:\r
390                                 nx = coords[0];\r
391                                 ny = coords[1];\r
392                                 area += x * ny;\r
393                                 area -= y * nx;\r
394 \r
395                                 //Remember the last points\r
396                                 x = nx;\r
397                                 y = ny;\r
398                                 \r
399                                 break;\r
400                         case PathIterator.SEG_MOVETO:\r
401                                 //Remember the starting point\r
402                                 x = sx = coords[0];\r
403                                 y = sy = coords[1];\r
404                                 break;\r
405                         default:\r
406                                 throw new Error("Bad segment type from Flattening Path Iterator");\r
407                         }\r
408                         i.next();\r
409                 }\r
410                 \r
411                 area = area / 2.0; // Result so far is double the signed area\r
412                 \r
413                 if ( area < 0 ) //Depending on winding it could be negative\r
414                         area = area * -1.0;\r
415                 \r
416                 \r
417                 return Amount.valueOf(area, SI.MILLIMETER.pow(2)).to(Area.UNIT);\r
418         }\r
419 \r
420 }\r