1 package com.billkuker.rocketry.motorsim.grain;
\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
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
36 import org.jscience.physics.amount.Amount;
\r
38 import com.billkuker.rocketry.motorsim.Grain;
\r
39 import com.billkuker.rocketry.motorsim.visual.Chart;
\r
41 public class ExtrudedGrain implements Grain, Grain.Graphical {
\r
43 Set<Shape> plus = new HashSet<Shape>();
\r
45 Set<Shape> minus = new HashSet<Shape>();
\r
47 Set<Shape> inhibited = new HashSet<Shape>();
\r
49 Amount<Length> length = Amount.valueOf(25, SI.MILLIMETER);
\r
51 Amount<Length> rStep;
\r
53 private class RegEntry {
\r
54 Amount<Area> surfaceArea;
\r
56 Amount<Volume> volume;
\r
59 Amount<Length> webThickness;
\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
69 Shape outside = new Ellipse2D.Double(0, 0, 30, 30);
\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
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
89 public Amount<Area> surfaceArea(Amount<Length> regression) {
\r
90 Amount<Area> zero = Amount.valueOf(0, Area.UNIT);
\r
92 if (regression.isGreaterThan(webThickness))
\r
95 Amount<Length> rLen = length.minus(regression.times(2));
\r
96 if (rLen.isLessThan(Amount.valueOf(0, SI.MILLIMETER)))
\r
99 java.awt.geom.Area burn = getCrossSection(regression);
\r
101 if (burn.isEmpty())
\r
104 burn.subtract(getCrossSection(regression.plus(Amount.valueOf(.001,
\r
107 Amount<Area> xSection = crossSectionArea(regression);
\r
109 return perimeter(burn).divide(2).times(rLen).plus(
\r
110 xSection.times(2)).to(Area.UNIT);
\r
114 public Amount<Volume> volume(Amount<Length> regression) {
\r
115 Amount<Volume> zero = Amount.valueOf(0, Volume.UNIT);
\r
117 Amount<Length> rLen = length.minus(regression.times(2));
\r
118 if (rLen.isLessThan(Amount.valueOf(0, SI.MILLIMETER)))
\r
121 Amount<Area> xSection = crossSectionArea(regression);
\r
123 return xSection.times(rLen).to(Volume.UNIT);
\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
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
147 //Subtract PLUS from MINUS and return
\r
148 Amount<Area> area = plusArea.minus(minusArea);
\r
154 public Amount<Length> webThickness() {
\r
155 return webThickness;
\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
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
174 } else if (type == PathIterator.SEG_MOVETO) {
\r
175 // System.out.println("Move");
\r
179 // System.err.println("Got " + type);
\r
183 return Amount.valueOf(len, SI.MILLIMETER);
\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
194 guess = min + (max - min) / 2; // Guess halfway through
\r
195 System.out.println("Min: " + min + " Guess: " + guess + " Max: "
\r
197 a = getCrossSection(Amount.valueOf(guess, SI.MILLIMETER));
\r
199 // guess is too big
\r
205 if ((max - min) < .01)
\r
208 webThickness = Amount.valueOf(guess, SI.MILLIMETER);
\r
209 if (webThickness.isGreaterThan(length.divide(2)))
\r
210 webThickness = length.divide(2);
\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
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
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
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
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
250 private Shape regress(Shape s, double mm, boolean plus) {
\r
251 if (inhibited.contains(s))
\r
253 if (s instanceof Ellipse2D) {
\r
254 Ellipse2D e = (Ellipse2D) s;
\r
256 double d = plus ? -2 * mm : 2 * mm;
\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
263 return new Ellipse2D.Double(x, y, w, h);
\r
264 } else if (s instanceof Rectangle2D) {
\r
265 Rectangle2D r = (Rectangle2D) s;
\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
275 //A rectangular hole gets rounded corners as it grows
\r
276 java.awt.geom.Area a = new java.awt.geom.Area();
\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
288 h = r.getHeight() + d;
\r
290 y = r.getY() - d / 2;
\r
291 a.add( new java.awt.geom.Area(new Rectangle2D.Double(x, y, w, h)));
\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
306 public static void main(String args[]) throws Exception {
\r
307 ExtrudedGrain e = new ExtrudedGrain();
\r
308 new GrainPanel(e).show();
\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
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
322 while (!i.isDone()) {
\r
323 double coords[] = new double[6];
\r
324 int type = i.currentSegment(coords);
\r
326 case PathIterator.SEG_CLOSE:
\r
329 java.awt.geom.Area area = new java.awt.geom.Area(cur);
\r
330 if ( !a.isEmpty() )
\r
333 cur = new GeneralPath(i.getWindingRule());
\r
335 case PathIterator.SEG_MOVETO:
\r
337 java.awt.geom.Area area = new java.awt.geom.Area(cur);
\r
338 if ( !a.isEmpty() )
\r
341 cur = new GeneralPath(i.getWindingRule());
\r
342 cur.moveTo(coords[0], coords[1]);
\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
348 case PathIterator.SEG_LINETO:
\r
349 cur.lineTo(coords[0], coords[1]);
\r
351 case PathIterator.SEG_QUADTO:
\r
352 cur.quadTo(coords[0], coords[1], coords[2], coords[3]);
\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
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
375 double x = 0, y = 0, sx = 0, sy = 0;
\r
378 while (!i.isDone()) {
\r
379 double coords[] = new double[6];
\r
380 int type = i.currentSegment(coords);
\r
382 case PathIterator.SEG_CLOSE:
\r
383 //Go back to the start
\r
389 case PathIterator.SEG_LINETO:
\r
395 //Remember the last points
\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
406 throw new Error("Bad segment type from Flattening Path Iterator");
\r
411 area = area / 2.0; // Result so far is double the signed area
\r
413 if ( area < 0 ) //Depending on winding it could be negative
\r
414 area = area * -1.0;
\r
417 return Amount.valueOf(area, SI.MILLIMETER.pow(2)).to(Area.UNIT);
\r