Organized grain package
authorBill Kuker <bkuker@billkuker.com>
Thu, 23 Apr 2009 15:54:14 +0000 (15:54 +0000)
committerBill Kuker <bkuker@billkuker.com>
Thu, 23 Apr 2009 15:54:14 +0000 (15:54 +0000)
added moonburner

12 files changed:
src/com/billkuker/rocketry/motorsim/grain/BurningShape.java [deleted file]
src/com/billkuker/rocketry/motorsim/grain/ExtrudedShapeGrain.java [deleted file]
src/com/billkuker/rocketry/motorsim/grain/Moonburner.java [new file with mode: 0644]
src/com/billkuker/rocketry/motorsim/grain/RotatedShapeGrain.java [deleted file]
src/com/billkuker/rocketry/motorsim/grain/ShapeUtil.java [deleted file]
src/com/billkuker/rocketry/motorsim/grain/util/BurningShape.java [new file with mode: 0644]
src/com/billkuker/rocketry/motorsim/grain/util/ExtrudedShapeGrain.java [new file with mode: 0644]
src/com/billkuker/rocketry/motorsim/grain/util/RotatedShapeGrain.java [new file with mode: 0644]
src/com/billkuker/rocketry/motorsim/grain/util/ShapeUtil.java [new file with mode: 0644]
src/com/billkuker/rocketry/motorsim/motors/example/CSlot.java
src/com/billkuker/rocketry/motorsim/motors/example/EndBurner.java
src/com/billkuker/rocketry/motorsim/test/ShapeUtilTest.java

diff --git a/src/com/billkuker/rocketry/motorsim/grain/BurningShape.java b/src/com/billkuker/rocketry/motorsim/grain/BurningShape.java
deleted file mode 100644 (file)
index c31a9ee..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-package com.billkuker.rocketry.motorsim.grain;\r
-\r
-import java.awt.Shape;\r
-import java.awt.geom.Area;\r
-import java.awt.geom.Ellipse2D;\r
-import java.awt.geom.Rectangle2D;\r
-import java.awt.geom.RoundRectangle2D;\r
-import java.util.HashSet;\r
-import java.util.Set;\r
-\r
-import javax.measure.quantity.Length;\r
-import javax.measure.unit.SI;\r
-\r
-import org.jscience.physics.amount.Amount;\r
-\r
-public class BurningShape {\r
-       Set<Shape> plus = new HashSet<Shape>();\r
-       Set<Shape> minus = new HashSet<Shape>();\r
-       Set<Shape> inhibited = new HashSet<Shape>();\r
-       \r
-       public void add(Shape s){\r
-               plus.add(s);\r
-       }\r
-       \r
-       public void subtract(Shape s){\r
-               minus.add(s);\r
-       }\r
-       \r
-       public void inhibit(Shape s){\r
-               inhibited.add(s);\r
-       }\r
-\r
-       /*\r
-        * 9 times out of 10 we get asked for the same thing\r
-        * 2x in a row, for volume and for area\r
-        */\r
-       private Amount<Length> lastRegression = null;\r
-       private Area lastArea = null;\r
-       \r
-       public java.awt.geom.Area getShape(Amount<Length> regression) {\r
-               if ( regression.equals(lastRegression) ){\r
-                       return lastArea;\r
-               }\r
-               lastRegression = regression;\r
-\r
-               java.awt.geom.Area a = new java.awt.geom.Area();\r
-               for (Shape s : plus)\r
-                       a.add(new java.awt.geom.Area(regress(s, regression\r
-                                       .doubleValue(SI.MILLIMETER), true)));\r
-               for (Shape s : minus)\r
-                       a.subtract(new java.awt.geom.Area(regress(s, regression\r
-                                       .doubleValue(SI.MILLIMETER), false)));\r
-               \r
-               return lastArea = a;\r
-       }\r
-       \r
-       private Shape regress(Shape s, double mm, boolean plus) {\r
-               if (inhibited.contains(s))\r
-                       return s;\r
-               if (s instanceof Ellipse2D) {\r
-                       Ellipse2D e = (Ellipse2D) s;\r
-\r
-                       double d = plus ? -2 * mm : 2 * mm;\r
-\r
-                       double w = e.getWidth() + d;\r
-                       double h = e.getHeight() + d;\r
-                       double x = e.getX() - d / 2;\r
-                       double y = e.getY() - d / 2;\r
-\r
-                       return new Ellipse2D.Double(x, y, w, h);\r
-               } else if (s instanceof Rectangle2D) {\r
-                       Rectangle2D r = (Rectangle2D) s;\r
-                       \r
-                       if ( plus ){\r
-                               double d = -2 * mm;\r
-                               double w = r.getWidth() + d;\r
-                               double h = r.getHeight() + d;\r
-                               double x = r.getX() - d / 2;\r
-                               double y = r.getY() - d / 2;\r
-                               return new Rectangle2D.Double(x, y, w, h);\r
-                       } else {\r
-                               //A rectangular hole gets rounded corners as it grows\r
-                               java.awt.geom.Area a = new java.awt.geom.Area();\r
-                               double d = 2 * mm;\r
-                               \r
-                               a.add(new Area(new RoundRectangle2D.Double(\r
-                                               r.getX() - d / 2,\r
-                                               r.getY() - d / 2,\r
-                                               r.getWidth() + d,\r
-                                               r.getHeight() + d,\r
-                                               d,\r
-                                               d\r
-                                               )));\r
-\r
-                               return a;\r
-                       }\r
-\r
-               }\r
-               return null;\r
-       }\r
-\r
-       \r
-}\r
diff --git a/src/com/billkuker/rocketry/motorsim/grain/ExtrudedShapeGrain.java b/src/com/billkuker/rocketry/motorsim/grain/ExtrudedShapeGrain.java
deleted file mode 100644 (file)
index ede26b6..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-package com.billkuker.rocketry.motorsim.grain;\r
-\r
-import java.awt.Rectangle;\r
-import java.awt.Shape;\r
-import java.awt.geom.Ellipse2D;\r
-import java.awt.geom.Rectangle2D;\r
-import java.beans.PropertyVetoException;\r
-\r
-import javax.measure.quantity.Area;\r
-import javax.measure.quantity.Length;\r
-import javax.measure.quantity.Volume;\r
-import javax.measure.unit.SI;\r
-\r
-import org.jscience.physics.amount.Amount;\r
-\r
-import sun.reflect.ReflectionFactory.GetReflectionFactoryAction;\r
-\r
-import com.billkuker.rocketry.motorsim.Grain;\r
-import com.billkuker.rocketry.motorsim.MotorPart;\r
-import com.billkuker.rocketry.motorsim.visual.Editor;\r
-import com.billkuker.rocketry.motorsim.visual.GrainPanel;\r
-\r
-public class ExtrudedShapeGrain extends ExtrudedGrain {\r
-       \r
-       public static ExtrudedShapeGrain DEFAULT_GRAIN = new ExtrudedShapeGrain(){\r
-               {\r
-                       try{\r
-                               Shape outside = new Ellipse2D.Double(0, 0, 30, 30);\r
-                               xsection.add(outside);\r
-                               xsection.inhibit(outside);\r
-                               xsection.subtract(new Ellipse2D.Double(10,10, 10, 10));\r
-                               setLength(Amount.valueOf(70, SI.MILLIMETER));\r
-                               setForeEndInhibited(false);\r
-                               setAftEndInhibited(false);\r
-                       } catch ( Exception e ){\r
-                               throw new Error(e);\r
-                       }\r
-               }\r
-       };\r
-\r
-       protected BurningShape xsection = new BurningShape();\r
-       \r
-       Amount<Length> rStep;\r
-\r
-       Amount<Length> webThickness;\r
-\r
-       public Amount<Area> surfaceArea(Amount<Length> regression) {\r
-               Amount<Area> zero = Amount.valueOf(0, Area.UNIT);\r
-               \r
-               if (regression.isGreaterThan(webThickness()))\r
-                       return zero;\r
-               \r
-               Amount<Length> rLen = regressedLength(regression);\r
-               \r
-               if (rLen.isLessThan(Amount.valueOf(0, SI.MILLIMETER)))\r
-                       return zero;\r
-\r
-               java.awt.geom.Area burn = getCrossSection(regression);\r
-               \r
-               if (burn.isEmpty())\r
-                       return zero;\r
-               \r
-               burn.subtract(getCrossSection(regression.plus(Amount.valueOf(.001,\r
-                               SI.MILLIMETER))));\r
-       \r
-               Amount<Area> xSection = ShapeUtil.area(xsection.getShape(regression));\r
-               \r
-               Amount<Area> sides = ShapeUtil.perimeter(burn).divide(2).times(rLen).to(Area.UNIT);\r
-               Amount<Area> ends = xSection.times(numberOfBurningEnds(regression));\r
-               \r
-               return sides.plus(ends);\r
-\r
-       }\r
-\r
-       public Amount<Volume> volume(Amount<Length> regression) {\r
-               Amount<Volume> zero = Amount.valueOf(0, Volume.UNIT);\r
-               \r
-               Amount<Length> rLen = regressedLength(regression);\r
-               \r
-               if (rLen.isLessThan(Amount.valueOf(0, SI.MILLIMETER)))\r
-                       return zero;\r
-               \r
-               Amount<Area> xSection = ShapeUtil.area(xsection.getShape(regression));\r
-\r
-               return xSection.times(rLen).to(Volume.UNIT);\r
-\r
-       }\r
-\r
-\r
-       public Amount<Length> webThickness() {\r
-               if ( webThickness != null )\r
-                       return webThickness;\r
-               java.awt.geom.Area a = getCrossSection(Amount.valueOf(0, SI.MILLIMETER));\r
-               Rectangle r = a.getBounds();\r
-               double max = r.getWidth() < r.getHeight() ? r.getHeight() : r\r
-                               .getWidth(); // The max size\r
-               double min = 0;\r
-               double guess;\r
-               while (true) {\r
-                       guess = min + (max - min) / 2; // Guess halfway through\r
-                       System.out.println("Min: " + min + " Guess: " + guess + " Max: "\r
-                                       + max);\r
-                       a = getCrossSection(Amount.valueOf(guess, SI.MILLIMETER));\r
-                       if (a.isEmpty()) {\r
-                               // guess is too big\r
-                               max = guess;\r
-                       } else {\r
-                               // min is too big\r
-                               min = guess;\r
-                       }\r
-                       if ((max - min) < .01)\r
-                               break;\r
-               }\r
-               webThickness = Amount.valueOf(guess, SI.MILLIMETER);\r
-               \r
-               //TODO Need to check # of burning ends!\r
-               if (webThickness.isGreaterThan(getLength().divide(2)))\r
-                       webThickness = getLength().divide(2);\r
-               \r
-               return webThickness;\r
-       }\r
-\r
-       public java.awt.geom.Area getCrossSection(Amount<Length> regression) {\r
-               return xsection.getShape(regression);\r
-       }\r
-       \r
-       public java.awt.geom.Area getSideView(Amount<Length> regression) {\r
-               java.awt.geom.Area res = new java.awt.geom.Area();\r
-               \r
-               Amount<Length> rLen = regressedLength(regression);\r
-               \r
-               double rLenmm = rLen.doubleValue(SI.MILLIMETER);\r
-               \r
-               //TODO Shift up or down based on burning ends\r
-               for( java.awt.geom.Area a : ShapeUtil.separate(getCrossSection(regression))){\r
-                       Rectangle2D bounds = a.getBounds2D();\r
-                       Rectangle2D side = new Rectangle2D.Double(bounds.getMinX(), -rLenmm/2.0, bounds.getWidth(), rLenmm);\r
-                       res.add(new java.awt.geom.Area(side));\r
-               }\r
-               return res;\r
-       }\r
-       \r
-       public static void main(String args[]) throws Exception {\r
-               ExtrudedShapeGrain e = DEFAULT_GRAIN;\r
-               new Editor(e).showAsWindow();\r
-               new GrainPanel(e).showAsWindow();\r
-       }\r
-\r
-}\r
diff --git a/src/com/billkuker/rocketry/motorsim/grain/Moonburner.java b/src/com/billkuker/rocketry/motorsim/grain/Moonburner.java
new file mode 100644 (file)
index 0000000..66f61ab
--- /dev/null
@@ -0,0 +1,94 @@
+package com.billkuker.rocketry.motorsim.grain;\r
+\r
+import java.awt.Shape;\r
+import java.awt.geom.Ellipse2D;\r
+import java.beans.PropertyVetoException;\r
+\r
+import javax.measure.quantity.Length;\r
+import javax.measure.unit.SI;\r
+\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.grain.util.BurningShape;\r
+import com.billkuker.rocketry.motorsim.grain.util.ExtrudedShapeGrain;\r
+import com.billkuker.rocketry.motorsim.visual.Editor;\r
+import com.billkuker.rocketry.motorsim.visual.GrainPanel;\r
+\r
+public class Moonburner extends ExtrudedShapeGrain {\r
+\r
+       private Amount<Length> oD = Amount.valueOf(30, SI.MILLIMETER);\r
+       private Amount<Length> iD = Amount.valueOf(10, SI.MILLIMETER);\r
+       private Amount<Length> coreOffset = Amount.valueOf(0, SI.MILLIMETER);\r
+       \r
+       public Moonburner(){\r
+               try {\r
+                       setLength(Amount.valueOf(70, SI.MILLIMETER));\r
+               } catch (PropertyVetoException e) {\r
+                       e.printStackTrace();\r
+               }\r
+               generateGeometry();\r
+       }\r
+\r
+       public Amount<Length> getOD() {\r
+               return oD;\r
+       }\r
+\r
+       public void setOD(Amount<Length> od) throws PropertyVetoException {\r
+               if (od.equals(this.oD))\r
+                       return;\r
+               fireVetoableChange("od", this.oD, od);\r
+               Amount<Length> old = this.oD;\r
+               this.oD = od;\r
+               generateGeometry();\r
+               firePropertyChange("OD", old, oD);\r
+       }\r
+\r
+       public Amount<Length> getID() {\r
+               return iD;\r
+       }\r
+\r
+       public void setID(Amount<Length> id) throws PropertyVetoException {\r
+               if (id.equals(this.iD))\r
+                       return;\r
+               fireVetoableChange("id", this.iD, id);\r
+               Amount<Length> old = this.iD;\r
+               iD = id;\r
+               generateGeometry();\r
+               firePropertyChange("ID", old, iD);\r
+       }\r
+\r
+       public Amount<Length> getCoreOffset() {\r
+               return coreOffset;\r
+       }\r
+\r
+       public void setCoreOffset(Amount<Length> coreOffset)\r
+                       throws PropertyVetoException {\r
+               if (coreOffset.equals(this.coreOffset))\r
+                       return;\r
+               fireVetoableChange("coreOffset", this.coreOffset, coreOffset);\r
+               Amount<Length> old = this.coreOffset;\r
+               this.coreOffset = coreOffset;\r
+               generateGeometry();\r
+               firePropertyChange("coreOffset", old, this.coreOffset);\r
+       }\r
+\r
+       private void generateGeometry() {\r
+               double odmm = oD.doubleValue(SI.MILLIMETER);\r
+               double idmm = iD.doubleValue(SI.MILLIMETER);\r
+               double offmm = coreOffset.doubleValue(SI.MILLIMETER);\r
+               xsection = new BurningShape();\r
+               Shape outside = new Ellipse2D.Double(0, 0, odmm, odmm);\r
+               xsection.add(outside);\r
+               xsection.inhibit(outside);\r
+\r
+               xsection.subtract(new Ellipse2D.Double(odmm/2 - idmm/2 + offmm, odmm/2 - idmm/2 + offmm, idmm, idmm));\r
+               webThickness = null;\r
+       }\r
+       \r
+       public static void main(String args[]) throws Exception {\r
+               Moonburner e = new Moonburner();\r
+               new Editor(e).showAsWindow();\r
+               new GrainPanel(e).showAsWindow();\r
+       }\r
+\r
+}\r
diff --git a/src/com/billkuker/rocketry/motorsim/grain/RotatedShapeGrain.java b/src/com/billkuker/rocketry/motorsim/grain/RotatedShapeGrain.java
deleted file mode 100644 (file)
index 0e45060..0000000
+++ /dev/null
@@ -1,258 +0,0 @@
-package com.billkuker.rocketry.motorsim.grain;\r
-\r
-import java.awt.Rectangle;\r
-import java.awt.Shape;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Area;\r
-import java.awt.geom.Ellipse2D;\r
-import java.awt.geom.GeneralPath;\r
-import java.awt.geom.PathIterator;\r
-import java.awt.geom.Rectangle2D;\r
-\r
-import javax.measure.quantity.Length;\r
-import javax.measure.quantity.Volume;\r
-import javax.measure.unit.SI;\r
-\r
-import org.apache.log4j.Logger;\r
-import org.jscience.physics.amount.Amount;\r
-\r
-import com.billkuker.rocketry.motorsim.Grain;\r
-import com.billkuker.rocketry.motorsim.visual.Editor;\r
-import com.billkuker.rocketry.motorsim.visual.GrainPanel;\r
-\r
-public class RotatedShapeGrain implements Grain {\r
-       \r
-       private static Logger log = Logger.getLogger(RotatedShapeGrain.class);\r
-       \r
-       public static RotatedShapeGrain DEFAULT_GRAIN = new RotatedShapeGrain(){\r
-               {\r
-                       try{\r
-                               Shape outside = new Rectangle2D.Double(0,0,15,70);\r
-                               shape.add( outside );\r
-                               shape.inhibit( outside );\r
-                               shape.subtract( new Rectangle2D.Double(0,0,5,70));\r
-                               shape.subtract(new Rectangle2D.Double(0, -10, 15, 10));\r
-                               shape.subtract(new Rectangle2D.Double(0, 70, 15, 10));\r
-                       } catch ( Exception e ){\r
-                               throw new Error(e);\r
-                       }\r
-               }\r
-       };\r
-       \r
-\r
-       \r
-       public enum Quality {\r
-               High()\r
-                       {{\r
-                                surfaceAreaStep = .001;\r
-                                squareFlatteningError = 0.001;\r
-                                squareSubdivide = .01;\r
-                                areaFlatteningError = .001;\r
-                       }},\r
-               Low()                   {{\r
-                        surfaceAreaStep = .001;\r
-                        squareFlatteningError = .1;\r
-                        squareSubdivide = .1;\r
-                        areaFlatteningError = .1;\r
-               }};\r
-               \r
-               double surfaceAreaStep = .001;\r
-               double squareFlatteningError = 0.001;\r
-               double squareSubdivide = .01;\r
-               double areaFlatteningError = .001;\r
-       }\r
-       \r
-       Quality quality = Quality.Low;\r
-       \r
-       protected BurningShape shape = new BurningShape();\r
-       \r
-       Amount<Length> web = null;\r
-       \r
-       public Area getCrossSection(Amount<Length> regression) {\r
-               Area ret = new Area();\r
-               for( Area a : ShapeUtil.separate(shape.getShape(regression))){\r
-                       Rectangle2D b = a.getBounds2D();\r
-                       Ellipse2D inner = new Ellipse2D.Double(-b.getMinX(), -b.getMinX(), b.getMinX()*2, b.getMinX()*2);\r
-                       Ellipse2D outer = new Ellipse2D.Double(-b.getMaxX(), -b.getMaxX(), b.getMaxX()*2, b.getMaxX()*2);\r
-                       Area aa = new Area(outer);\r
-                       aa.subtract(new Area(inner));\r
-                       ret.add(aa);\r
-               }\r
-               return ret;\r
-       }\r
-\r
-       public Area getSideView(Amount<Length> regression) {\r
-               Area a = new Area();\r
-               Area reg = shape.getShape(regression);\r
-               a.add(reg);\r
-               a.transform(AffineTransform.getScaleInstance(-1, 1));\r
-               a.add(reg);\r
-               return a;\r
-       }\r
-\r
-       public Amount<javax.measure.quantity.Area> surfaceArea(\r
-                       Amount<Length> regression) {\r
-               Amount<javax.measure.quantity.Area> zero = Amount.valueOf(0, javax.measure.quantity.Area.UNIT);\r
-               \r
-               if (regression.isGreaterThan(webThickness()))\r
-                       return zero;\r
-\r
-               java.awt.geom.Area burn = shape.getShape(regression);\r
-               \r
-               if (burn.isEmpty())\r
-                       return zero;\r
-               \r
-               burn.subtract(shape.getShape(regression.plus(Amount.valueOf(quality.surfaceAreaStep,\r
-                               SI.MILLIMETER))));\r
-       \r
-               double sqmm = yRotatedSurfaceArea(burn);\r
-\r
-               \r
-               return Amount.valueOf(sqmm, SI.MILLIMETER.pow(2).asType(javax.measure.quantity.Area.class)).divide(2);\r
-\r
-       }\r
-\r
-       public Amount<Volume> volume(Amount<Length> regression) {\r
-               Shape squared = square(shape.getShape(regression));\r
-               Amount<javax.measure.quantity.Area> sum = Amount.valueOf(0, SI.SQUARE_METRE);\r
-               //for( Area a: ShapeUtil.separate(squared) ){\r
-               //      sum = sum.plus( ShapeUtil.area(a) );\r
-               //}\r
-               sum = ShapeUtil.area(squared);\r
-               Amount<Volume> v = sum.times(Amount.valueOf(Math.PI, SI.MILLIMETER)).to(Volume.UNIT);\r
-               return v;\r
-       }\r
-\r
-       public Amount<Length> webThickness() {\r
-               if (web != null)\r
-                       return web;\r
-\r
-               java.awt.geom.Area a = shape.getShape(Amount.valueOf(0, SI.MILLIMETER));\r
-               Rectangle r = a.getBounds();\r
-               double max = r.getWidth() < r.getHeight() ? r.getHeight() : r\r
-                               .getWidth(); // The max size\r
-               double min = 0;\r
-               double guess;\r
-               while (true) {\r
-                       guess = min + (max - min) / 2; // Guess halfway through\r
-                       log.debug("Min: " + min + " Guess: " + guess + " Max: " + max);\r
-                       a = shape.getShape(Amount.valueOf(guess, SI.MILLIMETER));\r
-                       if (a.isEmpty()) {\r
-                               // guess is too big\r
-                               max = guess;\r
-                       } else {\r
-                               // min is too big\r
-                               min = guess;\r
-                       }\r
-                       if ((max - min) < .01)\r
-                               break;\r
-               }\r
-               web = Amount.valueOf(guess, SI.MILLIMETER);\r
-\r
-               return web;\r
-\r
-       }\r
-\r
-       private Shape square(Shape a) {\r
-               PathIterator i = a.getPathIterator(new AffineTransform(), quality.squareFlatteningError);\r
-               GeneralPath cur = new GeneralPath();\r
-\r
-               double last[] = {0,0};\r
-               while (!i.isDone()) {\r
-                       double coords[] = new double[6];\r
-                       int type = i.currentSegment(coords);\r
-                       switch (type) {\r
-                       case PathIterator.SEG_CLOSE:\r
-                               cur.closePath();\r
-                               break;\r
-                       case PathIterator.SEG_MOVETO:\r
-                               cur.moveTo(Math.pow(coords[0],2), coords[1]);\r
-                               last[0] = coords[0];\r
-                               last[1] = coords[1];\r
-                               break;\r
-                       case PathIterator.SEG_CUBICTO:\r
-                               throw new Error("Non-flattened geometry!");\r
-                       case PathIterator.SEG_LINETO:\r
-                               double x = last[0];\r
-                               double y = last[1];\r
-                               double len = Math.sqrt(Math.pow(last[0]-coords[0], 2) + Math.pow(last[1]-coords[1], 2));\r
-                               int steps = (int)(len / quality.squareSubdivide) + 5;\r
-                               for (int s = 0; s < steps; s++) {\r
-                                       x += (coords[0] - last[0]) / steps;\r
-                                       y += (coords[1] - last[1]) / steps;\r
-                                       cur.lineTo(Math.pow(x, 2), y);\r
-                               }\r
-                               last[0] = coords[0];\r
-                               last[1] = coords[1];\r
-                               break;\r
-                       case PathIterator.SEG_QUADTO:\r
-                               throw new Error("Non-flattened geometry!");\r
-\r
-                       }\r
-                       i.next();\r
-               }\r
-               return cur;\r
-       }\r
-       \r
-       private double yRotatedSurfaceArea(Shape a) {\r
-               PathIterator i = a.getPathIterator(new AffineTransform(), quality.areaFlatteningError);\r
-               double x = 0, y = 0;\r
-               double mx = 0, my = 0;\r
-               double len = 0;\r
-               while (!i.isDone()) {\r
-                       double coords[] = new double[6];\r
-                       int type = i.currentSegment(coords);\r
-                       if (type == PathIterator.SEG_LINETO || type == PathIterator.SEG_CLOSE) {\r
-\r
-\r
-                               double nx = coords[0];\r
-                               double ny = coords[1];\r
-                               \r
-                               if ( type == PathIterator.SEG_CLOSE ){\r
-                                       nx = mx;\r
-                                       ny = my;\r
-                               }\r
-                               \r
-                               double dy = Math.abs(y-ny);\r
-                               double dx = Math.abs(x-nx);\r
-                               double xl = x>nx?x:nx;\r
-                               double xs = x<nx?x:nx;  \r
-                               \r
-                               double add = 0;\r
-                               if ( dx == 0 ){\r
-                                       //Cylender\r
-                                       add = 2 * Math.PI * xl * dy;\r
-                               } else if ( dy == 0 ){\r
-                                       //disk\r
-                                        add = Math.PI * xl * xl - Math.PI * xs * xs;\r
-                               }else{\r
-                                       double h = xl/dx * dy;\r
-                                       double s1 = Math.sqrt(xl*xl + h*h);\r
-                                       double s2 = Math.sqrt(xs*xs + (h-dy)*(h-dy));\r
-                                       add = Math.PI * (xl*s1 - xs*s2);\r
-                               }\r
-                               \r
-                               len += add;\r
-\r
-                               x = nx;\r
-                               y = ny;\r
-                       } else if (type == PathIterator.SEG_MOVETO) {\r
-                               mx = x = coords[0];\r
-                               my = y = coords[1];\r
-                       } else {\r
-                               throw new Error("Non-flattened geometry!");\r
-                       }\r
-                       i.next();\r
-               }\r
-               return len;\r
-       }\r
-\r
-       \r
-\r
-       public static void main(String args[]) throws Exception {\r
-               RotatedShapeGrain e = DEFAULT_GRAIN;\r
-               new Editor(e).showAsWindow();\r
-               new GrainPanel(e).showAsWindow();\r
-       }\r
-       \r
-}\r
diff --git a/src/com/billkuker/rocketry/motorsim/grain/ShapeUtil.java b/src/com/billkuker/rocketry/motorsim/grain/ShapeUtil.java
deleted file mode 100644 (file)
index 5dbb1ec..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-package com.billkuker.rocketry.motorsim.grain;\r
-import java.awt.Shape;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.GeneralPath;\r
-import java.awt.geom.PathIterator;\r
-import java.util.HashSet;\r
-import java.util.Set;\r
-\r
-import javax.measure.quantity.Area;\r
-import javax.measure.quantity.Length;\r
-import javax.measure.unit.SI;\r
-\r
-import org.jscience.physics.amount.Amount;\r
-public class ShapeUtil {\r
-       private ShapeUtil(){}\r
-\r
-       /*\r
-        * Return the Area of a singular polygon (NO HOLES OR DISJOINT PARTS).\r
-        * Coordinates assumed to be in MM.\r
-        * http://valis.cs.uiuc.edu/~sariel/research/CG/compgeom/msg00831.html\r
-        * http://stackoverflow.com/questions/451426/how-do-i-calculate-the-surface-area-of-a-2d-polygon\r
-        * http://www.wikihow.com/Calculate-the-Area-of-a-Polygon\r
-        * According to http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u33.html\r
-        * this algorithm works OK with holes, and it seems to (see test)\r
-        */\r
-       public static Amount<Area> area(Shape a) {\r
-               //if ( !a.isSingular() )\r
-                       //throw new IllegalArgumentException("Can not calculate area of non-singular shape!");\r
-               PathIterator i = a.getPathIterator(new AffineTransform(), .001);\r
-               \r
-               \r
-               double x = 0, y = 0, sx = 0, sy = 0;\r
-               double nx, ny;\r
-               double area = 0;\r
-               while (!i.isDone()) {\r
-                       double coords[] = new double[6];\r
-                       int type = i.currentSegment(coords);\r
-                       switch( type ){\r
-                       case PathIterator.SEG_CLOSE:\r
-                               //Go back to the start\r
-                               nx = sx;\r
-                               ny = sy;\r
-                               area += x * ny;\r
-                               area -= y * nx;\r
-                               break;\r
-                       case PathIterator.SEG_LINETO:\r
-                               nx = coords[0];\r
-                               ny = coords[1];\r
-                               area += x * ny;\r
-                               area -= y * nx;\r
-       \r
-                               //Remember the last points\r
-                               x = nx;\r
-                               y = ny;\r
-                               \r
-                               break;\r
-                       case PathIterator.SEG_MOVETO:\r
-                               //Remember the starting point\r
-                               x = sx = coords[0];\r
-                               y = sy = coords[1];\r
-                               break;\r
-                       default:\r
-                               throw new Error("Bad segment type from Flattening Path Iterator");\r
-                       }\r
-                       i.next();\r
-               }\r
-               \r
-               area = area / 2.0; // Result so far is double the signed area\r
-               \r
-               if ( area < 0 ){ //Depending on winding it could be negative\r
-                       area = area * -1.0;\r
-               }\r
-               \r
-               \r
-               return Amount.valueOf(area, SI.MILLIMETER.pow(2)).to(Area.UNIT);\r
-       }\r
-\r
-       public static Amount<Length> perimeter(Shape a) {\r
-               //TODO: I think I need to handle seg_close!!\r
-               PathIterator i = a.getPathIterator(new AffineTransform(), .001);\r
-               double x = 0, y = 0;\r
-               double len = 0;\r
-               while (!i.isDone()) {\r
-                       double coords[] = new double[6];\r
-                       int type = i.currentSegment(coords);\r
-                       if (type == PathIterator.SEG_LINETO) {\r
-                               // System.out.println("Line");\r
-                               double nx = coords[0];\r
-                               double ny = coords[1];\r
-                               // System.out.println(x+","+y+ " to " + nx+"," + ny);\r
-                               len += Math.sqrt(Math.pow(x - nx, 2) + Math.pow(y - ny, 2));\r
-                               x = nx;\r
-                               y = ny;\r
-                       } else if (type == PathIterator.SEG_MOVETO) {\r
-                               // System.out.println("Move");\r
-                               x = coords[0];\r
-                               y = coords[1];\r
-                       } else {\r
-                               // System.err.println("Got " + type);\r
-                       }\r
-                       i.next();\r
-               }\r
-               return Amount.valueOf(len, SI.MILLIMETER);\r
-       }\r
-\r
-       /*\r
-        * Separate an area into multiple distinct area.\r
-        * Area CAN NOT HAVE HOLES. HOLES WILL BE RETURNED AS AREAS,\r
-        * SO A DONUT WILL TURN INTO TWO CIRCLES.\r
-        */\r
-       public static Set<java.awt.geom.Area> separate(java.awt.geom.Area a) {\r
-               Set<java.awt.geom.Area> res = new HashSet<java.awt.geom.Area>();\r
-               PathIterator i = a.getPathIterator(new AffineTransform());\r
-               GeneralPath cur = null;\r
-       \r
-               while (!i.isDone()) {\r
-                       double coords[] = new double[6];\r
-                       int type = i.currentSegment(coords);\r
-                       switch (type) {\r
-                       case PathIterator.SEG_CLOSE:\r
-                               cur.closePath();\r
-                               if (cur != null ){\r
-                                       java.awt.geom.Area area = new java.awt.geom.Area(cur);\r
-                                       if ( !a.isEmpty() )\r
-                                               res.add(area);\r
-                               }\r
-                               cur = new GeneralPath(i.getWindingRule());\r
-                               break;\r
-                       case PathIterator.SEG_MOVETO:\r
-                               if (cur != null ){\r
-                                       java.awt.geom.Area area = new java.awt.geom.Area(cur);\r
-                                       if ( !a.isEmpty() )\r
-                                               res.add(area);\r
-                               }\r
-                               cur = new GeneralPath(i.getWindingRule());\r
-                               cur.moveTo(coords[0], coords[1]);\r
-                               break;\r
-                       case PathIterator.SEG_CUBICTO:\r
-                               cur.curveTo(coords[0], coords[1], coords[2], coords[3],\r
-                                               coords[4], coords[5]);\r
-                               break;\r
-                       case PathIterator.SEG_LINETO:\r
-                               cur.lineTo(coords[0], coords[1]);\r
-                               break;\r
-                       case PathIterator.SEG_QUADTO:\r
-                               cur.quadTo(coords[0], coords[1], coords[2], coords[3]);\r
-                               break;\r
-       \r
-                       }\r
-                       i.next();\r
-               }\r
-       \r
-               return res;\r
-       }\r
-       \r
-       \r
-}\r
diff --git a/src/com/billkuker/rocketry/motorsim/grain/util/BurningShape.java b/src/com/billkuker/rocketry/motorsim/grain/util/BurningShape.java
new file mode 100644 (file)
index 0000000..c17c667
--- /dev/null
@@ -0,0 +1,103 @@
+package com.billkuker.rocketry.motorsim.grain.util;\r
+\r
+import java.awt.Shape;\r
+import java.awt.geom.Area;\r
+import java.awt.geom.Ellipse2D;\r
+import java.awt.geom.Rectangle2D;\r
+import java.awt.geom.RoundRectangle2D;\r
+import java.util.HashSet;\r
+import java.util.Set;\r
+\r
+import javax.measure.quantity.Length;\r
+import javax.measure.unit.SI;\r
+\r
+import org.jscience.physics.amount.Amount;\r
+\r
+public class BurningShape {\r
+       Set<Shape> plus = new HashSet<Shape>();\r
+       Set<Shape> minus = new HashSet<Shape>();\r
+       Set<Shape> inhibited = new HashSet<Shape>();\r
+       \r
+       public void add(Shape s){\r
+               plus.add(s);\r
+       }\r
+       \r
+       public void subtract(Shape s){\r
+               minus.add(s);\r
+       }\r
+       \r
+       public void inhibit(Shape s){\r
+               inhibited.add(s);\r
+       }\r
+\r
+       /*\r
+        * 9 times out of 10 we get asked for the same thing\r
+        * 2x in a row, for volume and for area\r
+        */\r
+       private Amount<Length> lastRegression = null;\r
+       private Area lastArea = null;\r
+       \r
+       public java.awt.geom.Area getShape(Amount<Length> regression) {\r
+               if ( regression.equals(lastRegression) ){\r
+                       return lastArea;\r
+               }\r
+               lastRegression = regression;\r
+\r
+               java.awt.geom.Area a = new java.awt.geom.Area();\r
+               for (Shape s : plus)\r
+                       a.add(new java.awt.geom.Area(regress(s, regression\r
+                                       .doubleValue(SI.MILLIMETER), true)));\r
+               for (Shape s : minus)\r
+                       a.subtract(new java.awt.geom.Area(regress(s, regression\r
+                                       .doubleValue(SI.MILLIMETER), false)));\r
+               \r
+               return lastArea = a;\r
+       }\r
+       \r
+       private Shape regress(Shape s, double mm, boolean plus) {\r
+               if (inhibited.contains(s))\r
+                       return s;\r
+               if (s instanceof Ellipse2D) {\r
+                       Ellipse2D e = (Ellipse2D) s;\r
+\r
+                       double d = plus ? -2 * mm : 2 * mm;\r
+\r
+                       double w = e.getWidth() + d;\r
+                       double h = e.getHeight() + d;\r
+                       double x = e.getX() - d / 2;\r
+                       double y = e.getY() - d / 2;\r
+\r
+                       return new Ellipse2D.Double(x, y, w, h);\r
+               } else if (s instanceof Rectangle2D) {\r
+                       Rectangle2D r = (Rectangle2D) s;\r
+                       \r
+                       if ( plus ){\r
+                               double d = -2 * mm;\r
+                               double w = r.getWidth() + d;\r
+                               double h = r.getHeight() + d;\r
+                               double x = r.getX() - d / 2;\r
+                               double y = r.getY() - d / 2;\r
+                               return new Rectangle2D.Double(x, y, w, h);\r
+                       } else {\r
+                               //A rectangular hole gets rounded corners as it grows\r
+                               java.awt.geom.Area a = new java.awt.geom.Area();\r
+                               double d = 2 * mm;\r
+                               \r
+                               a.add(new Area(new RoundRectangle2D.Double(\r
+                                               r.getX() - d / 2,\r
+                                               r.getY() - d / 2,\r
+                                               r.getWidth() + d,\r
+                                               r.getHeight() + d,\r
+                                               d,\r
+                                               d\r
+                                               )));\r
+\r
+                               return a;\r
+                       }\r
+\r
+               }\r
+               return null;\r
+       }\r
+\r
+       \r
+}\r
diff --git a/src/com/billkuker/rocketry/motorsim/grain/util/ExtrudedShapeGrain.java b/src/com/billkuker/rocketry/motorsim/grain/util/ExtrudedShapeGrain.java
new file mode 100644 (file)
index 0000000..133ac71
--- /dev/null
@@ -0,0 +1,148 @@
+package com.billkuker.rocketry.motorsim.grain.util;\r
+\r
+import java.awt.Rectangle;\r
+import java.awt.Shape;\r
+import java.awt.geom.Ellipse2D;\r
+import java.awt.geom.Rectangle2D;\r
+import java.beans.PropertyVetoException;\r
+\r
+import javax.measure.quantity.Area;\r
+import javax.measure.quantity.Length;\r
+import javax.measure.quantity.Volume;\r
+import javax.measure.unit.SI;\r
+\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import sun.reflect.ReflectionFactory.GetReflectionFactoryAction;\r
+\r
+import com.billkuker.rocketry.motorsim.Grain;\r
+import com.billkuker.rocketry.motorsim.MotorPart;\r
+import com.billkuker.rocketry.motorsim.grain.ExtrudedGrain;\r
+import com.billkuker.rocketry.motorsim.visual.Editor;\r
+import com.billkuker.rocketry.motorsim.visual.GrainPanel;\r
+\r
+public abstract class ExtrudedShapeGrain extends ExtrudedGrain {\r
+       \r
+       public static ExtrudedShapeGrain DEFAULT_GRAIN = new ExtrudedShapeGrain(){\r
+               {\r
+                       try{\r
+                               Shape outside = new Ellipse2D.Double(0, 0, 30, 30);\r
+                               xsection.add(outside);\r
+                               xsection.inhibit(outside);\r
+                               xsection.subtract(new Ellipse2D.Double(10,10, 10, 10));\r
+                               setLength(Amount.valueOf(70, SI.MILLIMETER));\r
+                               setForeEndInhibited(false);\r
+                               setAftEndInhibited(false);\r
+                       } catch ( Exception e ){\r
+                               throw new Error(e);\r
+                       }\r
+               }\r
+       };\r
+\r
+       protected BurningShape xsection = new BurningShape();\r
+\r
+       protected Amount<Length> webThickness;\r
+\r
+       public Amount<Area> surfaceArea(Amount<Length> regression) {\r
+               Amount<Area> zero = Amount.valueOf(0, Area.UNIT);\r
+               \r
+               if (regression.isGreaterThan(webThickness()))\r
+                       return zero;\r
+               \r
+               Amount<Length> rLen = regressedLength(regression);\r
+               \r
+               if (rLen.isLessThan(Amount.valueOf(0, SI.MILLIMETER)))\r
+                       return zero;\r
+\r
+               java.awt.geom.Area burn = getCrossSection(regression);\r
+               \r
+               if (burn.isEmpty())\r
+                       return zero;\r
+               \r
+               burn.subtract(getCrossSection(regression.plus(Amount.valueOf(.001,\r
+                               SI.MILLIMETER))));\r
+       \r
+               Amount<Area> xSection = ShapeUtil.area(xsection.getShape(regression));\r
+               \r
+               Amount<Area> sides = ShapeUtil.perimeter(burn).divide(2).times(rLen).to(Area.UNIT);\r
+               Amount<Area> ends = xSection.times(numberOfBurningEnds(regression));\r
+               \r
+               return sides.plus(ends);\r
+\r
+       }\r
+\r
+       public Amount<Volume> volume(Amount<Length> regression) {\r
+               Amount<Volume> zero = Amount.valueOf(0, Volume.UNIT);\r
+               \r
+               Amount<Length> rLen = regressedLength(regression);\r
+               \r
+               if (rLen.isLessThan(Amount.valueOf(0, SI.MILLIMETER)))\r
+                       return zero;\r
+               \r
+               Amount<Area> xSection = ShapeUtil.area(xsection.getShape(regression));\r
+\r
+               return xSection.times(rLen).to(Volume.UNIT);\r
+\r
+       }\r
+\r
+\r
+       public Amount<Length> webThickness() {\r
+               if ( webThickness != null )\r
+                       return webThickness;\r
+               java.awt.geom.Area a = getCrossSection(Amount.valueOf(0, SI.MILLIMETER));\r
+               Rectangle r = a.getBounds();\r
+               double max = r.getWidth() < r.getHeight() ? r.getHeight() : r\r
+                               .getWidth(); // The max size\r
+               double min = 0;\r
+               double guess;\r
+               while (true) {\r
+                       guess = min + (max - min) / 2; // Guess halfway through\r
+                       System.out.println("Min: " + min + " Guess: " + guess + " Max: "\r
+                                       + max);\r
+                       a = getCrossSection(Amount.valueOf(guess, SI.MILLIMETER));\r
+                       if (a.isEmpty()) {\r
+                               // guess is too big\r
+                               max = guess;\r
+                       } else {\r
+                               // min is too big\r
+                               min = guess;\r
+                       }\r
+                       if ((max - min) < .01)\r
+                               break;\r
+               }\r
+               webThickness = Amount.valueOf(guess, SI.MILLIMETER);\r
+               \r
+               //TODO Need to check # of burning ends!\r
+               if (webThickness.isGreaterThan(getLength().divide(2)))\r
+                       webThickness = getLength().divide(2);\r
+               \r
+               return webThickness;\r
+       }\r
+\r
+       public java.awt.geom.Area getCrossSection(Amount<Length> regression) {\r
+               return xsection.getShape(regression);\r
+       }\r
+       \r
+       public java.awt.geom.Area getSideView(Amount<Length> regression) {\r
+               java.awt.geom.Area res = new java.awt.geom.Area();\r
+               \r
+               Amount<Length> rLen = regressedLength(regression);\r
+               \r
+               double rLenmm = rLen.doubleValue(SI.MILLIMETER);\r
+               \r
+               //TODO Shift up or down based on burning ends\r
+               for( java.awt.geom.Area a : ShapeUtil.separate(getCrossSection(regression))){\r
+                       Rectangle2D bounds = a.getBounds2D();\r
+                       Rectangle2D side = new Rectangle2D.Double(bounds.getMinX(), -rLenmm/2.0, bounds.getWidth(), rLenmm);\r
+                       res.add(new java.awt.geom.Area(side));\r
+               }\r
+               return res;\r
+       }\r
+       \r
+       public static void main(String args[]) throws Exception {\r
+               ExtrudedShapeGrain e = DEFAULT_GRAIN;\r
+               new Editor(e).showAsWindow();\r
+               new GrainPanel(e).showAsWindow();\r
+       }\r
+\r
+}\r
diff --git a/src/com/billkuker/rocketry/motorsim/grain/util/RotatedShapeGrain.java b/src/com/billkuker/rocketry/motorsim/grain/util/RotatedShapeGrain.java
new file mode 100644 (file)
index 0000000..18f0f62
--- /dev/null
@@ -0,0 +1,258 @@
+package com.billkuker.rocketry.motorsim.grain.util;\r
+\r
+import java.awt.Rectangle;\r
+import java.awt.Shape;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Area;\r
+import java.awt.geom.Ellipse2D;\r
+import java.awt.geom.GeneralPath;\r
+import java.awt.geom.PathIterator;\r
+import java.awt.geom.Rectangle2D;\r
+\r
+import javax.measure.quantity.Length;\r
+import javax.measure.quantity.Volume;\r
+import javax.measure.unit.SI;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.jscience.physics.amount.Amount;\r
+\r
+import com.billkuker.rocketry.motorsim.Grain;\r
+import com.billkuker.rocketry.motorsim.visual.Editor;\r
+import com.billkuker.rocketry.motorsim.visual.GrainPanel;\r
+\r
+public abstract class RotatedShapeGrain implements Grain {\r
+       \r
+       private static Logger log = Logger.getLogger(RotatedShapeGrain.class);\r
+       \r
+       public static RotatedShapeGrain DEFAULT_GRAIN = new RotatedShapeGrain(){\r
+               {\r
+                       try{\r
+                               Shape outside = new Rectangle2D.Double(0,0,15,70);\r
+                               shape.add( outside );\r
+                               shape.inhibit( outside );\r
+                               shape.subtract( new Rectangle2D.Double(0,0,5,70));\r
+                               shape.subtract(new Rectangle2D.Double(0, -10, 15, 10));\r
+                               shape.subtract(new Rectangle2D.Double(0, 70, 15, 10));\r
+                       } catch ( Exception e ){\r
+                               throw new Error(e);\r
+                       }\r
+               }\r
+       };\r
+       \r
+\r
+       \r
+       public enum Quality {\r
+               High()\r
+                       {{\r
+                                surfaceAreaStep = .001;\r
+                                squareFlatteningError = 0.001;\r
+                                squareSubdivide = .01;\r
+                                areaFlatteningError = .001;\r
+                       }},\r
+               Low()                   {{\r
+                        surfaceAreaStep = .001;\r
+                        squareFlatteningError = .1;\r
+                        squareSubdivide = .1;\r
+                        areaFlatteningError = .1;\r
+               }};\r
+               \r
+               double surfaceAreaStep = .001;\r
+               double squareFlatteningError = 0.001;\r
+               double squareSubdivide = .01;\r
+               double areaFlatteningError = .001;\r
+       }\r
+       \r
+       Quality quality = Quality.Low;\r
+       \r
+       protected BurningShape shape = new BurningShape();\r
+       \r
+       Amount<Length> web = null;\r
+       \r
+       public Area getCrossSection(Amount<Length> regression) {\r
+               Area ret = new Area();\r
+               for( Area a : ShapeUtil.separate(shape.getShape(regression))){\r
+                       Rectangle2D b = a.getBounds2D();\r
+                       Ellipse2D inner = new Ellipse2D.Double(-b.getMinX(), -b.getMinX(), b.getMinX()*2, b.getMinX()*2);\r
+                       Ellipse2D outer = new Ellipse2D.Double(-b.getMaxX(), -b.getMaxX(), b.getMaxX()*2, b.getMaxX()*2);\r
+                       Area aa = new Area(outer);\r
+                       aa.subtract(new Area(inner));\r
+                       ret.add(aa);\r
+               }\r
+               return ret;\r
+       }\r
+\r
+       public Area getSideView(Amount<Length> regression) {\r
+               Area a = new Area();\r
+               Area reg = shape.getShape(regression);\r
+               a.add(reg);\r
+               a.transform(AffineTransform.getScaleInstance(-1, 1));\r
+               a.add(reg);\r
+               return a;\r
+       }\r
+\r
+       public Amount<javax.measure.quantity.Area> surfaceArea(\r
+                       Amount<Length> regression) {\r
+               Amount<javax.measure.quantity.Area> zero = Amount.valueOf(0, javax.measure.quantity.Area.UNIT);\r
+               \r
+               if (regression.isGreaterThan(webThickness()))\r
+                       return zero;\r
+\r
+               java.awt.geom.Area burn = shape.getShape(regression);\r
+               \r
+               if (burn.isEmpty())\r
+                       return zero;\r
+               \r
+               burn.subtract(shape.getShape(regression.plus(Amount.valueOf(quality.surfaceAreaStep,\r
+                               SI.MILLIMETER))));\r
+       \r
+               double sqmm = yRotatedSurfaceArea(burn);\r
+\r
+               \r
+               return Amount.valueOf(sqmm, SI.MILLIMETER.pow(2).asType(javax.measure.quantity.Area.class)).divide(2);\r
+\r
+       }\r
+\r
+       public Amount<Volume> volume(Amount<Length> regression) {\r
+               Shape squared = square(shape.getShape(regression));\r
+               Amount<javax.measure.quantity.Area> sum = Amount.valueOf(0, SI.SQUARE_METRE);\r
+               //for( Area a: ShapeUtil.separate(squared) ){\r
+               //      sum = sum.plus( ShapeUtil.area(a) );\r
+               //}\r
+               sum = ShapeUtil.area(squared);\r
+               Amount<Volume> v = sum.times(Amount.valueOf(Math.PI, SI.MILLIMETER)).to(Volume.UNIT);\r
+               return v;\r
+       }\r
+\r
+       public Amount<Length> webThickness() {\r
+               if (web != null)\r
+                       return web;\r
+\r
+               java.awt.geom.Area a = shape.getShape(Amount.valueOf(0, SI.MILLIMETER));\r
+               Rectangle r = a.getBounds();\r
+               double max = r.getWidth() < r.getHeight() ? r.getHeight() : r\r
+                               .getWidth(); // The max size\r
+               double min = 0;\r
+               double guess;\r
+               while (true) {\r
+                       guess = min + (max - min) / 2; // Guess halfway through\r
+                       log.debug("Min: " + min + " Guess: " + guess + " Max: " + max);\r
+                       a = shape.getShape(Amount.valueOf(guess, SI.MILLIMETER));\r
+                       if (a.isEmpty()) {\r
+                               // guess is too big\r
+                               max = guess;\r
+                       } else {\r
+                               // min is too big\r
+                               min = guess;\r
+                       }\r
+                       if ((max - min) < .01)\r
+                               break;\r
+               }\r
+               web = Amount.valueOf(guess, SI.MILLIMETER);\r
+\r
+               return web;\r
+\r
+       }\r
+\r
+       private Shape square(Shape a) {\r
+               PathIterator i = a.getPathIterator(new AffineTransform(), quality.squareFlatteningError);\r
+               GeneralPath cur = new GeneralPath();\r
+\r
+               double last[] = {0,0};\r
+               while (!i.isDone()) {\r
+                       double coords[] = new double[6];\r
+                       int type = i.currentSegment(coords);\r
+                       switch (type) {\r
+                       case PathIterator.SEG_CLOSE:\r
+                               cur.closePath();\r
+                               break;\r
+                       case PathIterator.SEG_MOVETO:\r
+                               cur.moveTo(Math.pow(coords[0],2), coords[1]);\r
+                               last[0] = coords[0];\r
+                               last[1] = coords[1];\r
+                               break;\r
+                       case PathIterator.SEG_CUBICTO:\r
+                               throw new Error("Non-flattened geometry!");\r
+                       case PathIterator.SEG_LINETO:\r
+                               double x = last[0];\r
+                               double y = last[1];\r
+                               double len = Math.sqrt(Math.pow(last[0]-coords[0], 2) + Math.pow(last[1]-coords[1], 2));\r
+                               int steps = (int)(len / quality.squareSubdivide) + 5;\r
+                               for (int s = 0; s < steps; s++) {\r
+                                       x += (coords[0] - last[0]) / steps;\r
+                                       y += (coords[1] - last[1]) / steps;\r
+                                       cur.lineTo(Math.pow(x, 2), y);\r
+                               }\r
+                               last[0] = coords[0];\r
+                               last[1] = coords[1];\r
+                               break;\r
+                       case PathIterator.SEG_QUADTO:\r
+                               throw new Error("Non-flattened geometry!");\r
+\r
+                       }\r
+                       i.next();\r
+               }\r
+               return cur;\r
+       }\r
+       \r
+       private double yRotatedSurfaceArea(Shape a) {\r
+               PathIterator i = a.getPathIterator(new AffineTransform(), quality.areaFlatteningError);\r
+               double x = 0, y = 0;\r
+               double mx = 0, my = 0;\r
+               double len = 0;\r
+               while (!i.isDone()) {\r
+                       double coords[] = new double[6];\r
+                       int type = i.currentSegment(coords);\r
+                       if (type == PathIterator.SEG_LINETO || type == PathIterator.SEG_CLOSE) {\r
+\r
+\r
+                               double nx = coords[0];\r
+                               double ny = coords[1];\r
+                               \r
+                               if ( type == PathIterator.SEG_CLOSE ){\r
+                                       nx = mx;\r
+                                       ny = my;\r
+                               }\r
+                               \r
+                               double dy = Math.abs(y-ny);\r
+                               double dx = Math.abs(x-nx);\r
+                               double xl = x>nx?x:nx;\r
+                               double xs = x<nx?x:nx;  \r
+                               \r
+                               double add = 0;\r
+                               if ( dx == 0 ){\r
+                                       //Cylender\r
+                                       add = 2 * Math.PI * xl * dy;\r
+                               } else if ( dy == 0 ){\r
+                                       //disk\r
+                                        add = Math.PI * xl * xl - Math.PI * xs * xs;\r
+                               }else{\r
+                                       double h = xl/dx * dy;\r
+                                       double s1 = Math.sqrt(xl*xl + h*h);\r
+                                       double s2 = Math.sqrt(xs*xs + (h-dy)*(h-dy));\r
+                                       add = Math.PI * (xl*s1 - xs*s2);\r
+                               }\r
+                               \r
+                               len += add;\r
+\r
+                               x = nx;\r
+                               y = ny;\r
+                       } else if (type == PathIterator.SEG_MOVETO) {\r
+                               mx = x = coords[0];\r
+                               my = y = coords[1];\r
+                       } else {\r
+                               throw new Error("Non-flattened geometry!");\r
+                       }\r
+                       i.next();\r
+               }\r
+               return len;\r
+       }\r
+\r
+       \r
+\r
+       public static void main(String args[]) throws Exception {\r
+               RotatedShapeGrain e = DEFAULT_GRAIN;\r
+               new Editor(e).showAsWindow();\r
+               new GrainPanel(e).showAsWindow();\r
+       }\r
+       \r
+}\r
diff --git a/src/com/billkuker/rocketry/motorsim/grain/util/ShapeUtil.java b/src/com/billkuker/rocketry/motorsim/grain/util/ShapeUtil.java
new file mode 100644 (file)
index 0000000..6c8945b
--- /dev/null
@@ -0,0 +1,157 @@
+package com.billkuker.rocketry.motorsim.grain.util;\r
+import java.awt.Shape;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.GeneralPath;\r
+import java.awt.geom.PathIterator;\r
+import java.util.HashSet;\r
+import java.util.Set;\r
+\r
+import javax.measure.quantity.Area;\r
+import javax.measure.quantity.Length;\r
+import javax.measure.unit.SI;\r
+\r
+import org.jscience.physics.amount.Amount;\r
+public class ShapeUtil {\r
+       private ShapeUtil(){}\r
+\r
+       /*\r
+        * Return the Area of a singular polygon (NO HOLES OR DISJOINT PARTS).\r
+        * Coordinates assumed to be in MM.\r
+        * http://valis.cs.uiuc.edu/~sariel/research/CG/compgeom/msg00831.html\r
+        * http://stackoverflow.com/questions/451426/how-do-i-calculate-the-surface-area-of-a-2d-polygon\r
+        * http://www.wikihow.com/Calculate-the-Area-of-a-Polygon\r
+        * According to http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u33.html\r
+        * this algorithm works OK with holes, and it seems to (see test)\r
+        */\r
+       public static Amount<Area> area(Shape a) {\r
+               //if ( !a.isSingular() )\r
+                       //throw new IllegalArgumentException("Can not calculate area of non-singular shape!");\r
+               PathIterator i = a.getPathIterator(new AffineTransform(), .001);\r
+               \r
+               \r
+               double x = 0, y = 0, sx = 0, sy = 0;\r
+               double nx, ny;\r
+               double area = 0;\r
+               while (!i.isDone()) {\r
+                       double coords[] = new double[6];\r
+                       int type = i.currentSegment(coords);\r
+                       switch( type ){\r
+                       case PathIterator.SEG_CLOSE:\r
+                               //Go back to the start\r
+                               nx = sx;\r
+                               ny = sy;\r
+                               area += x * ny;\r
+                               area -= y * nx;\r
+                               break;\r
+                       case PathIterator.SEG_LINETO:\r
+                               nx = coords[0];\r
+                               ny = coords[1];\r
+                               area += x * ny;\r
+                               area -= y * nx;\r
+       \r
+                               //Remember the last points\r
+                               x = nx;\r
+                               y = ny;\r
+                               \r
+                               break;\r
+                       case PathIterator.SEG_MOVETO:\r
+                               //Remember the starting point\r
+                               x = sx = coords[0];\r
+                               y = sy = coords[1];\r
+                               break;\r
+                       default:\r
+                               throw new Error("Bad segment type from Flattening Path Iterator");\r
+                       }\r
+                       i.next();\r
+               }\r
+               \r
+               area = area / 2.0; // Result so far is double the signed area\r
+               \r
+               if ( area < 0 ){ //Depending on winding it could be negative\r
+                       area = area * -1.0;\r
+               }\r
+               \r
+               \r
+               return Amount.valueOf(area, SI.MILLIMETER.pow(2)).to(Area.UNIT);\r
+       }\r
+\r
+       public static Amount<Length> perimeter(Shape a) {\r
+               //TODO: I think I need to handle seg_close!!\r
+               PathIterator i = a.getPathIterator(new AffineTransform(), .001);\r
+               double x = 0, y = 0;\r
+               double len = 0;\r
+               while (!i.isDone()) {\r
+                       double coords[] = new double[6];\r
+                       int type = i.currentSegment(coords);\r
+                       if (type == PathIterator.SEG_LINETO) {\r
+                               // System.out.println("Line");\r
+                               double nx = coords[0];\r
+                               double ny = coords[1];\r
+                               // System.out.println(x+","+y+ " to " + nx+"," + ny);\r
+                               len += Math.sqrt(Math.pow(x - nx, 2) + Math.pow(y - ny, 2));\r
+                               x = nx;\r
+                               y = ny;\r
+                       } else if (type == PathIterator.SEG_MOVETO) {\r
+                               // System.out.println("Move");\r
+                               x = coords[0];\r
+                               y = coords[1];\r
+                       } else {\r
+                               // System.err.println("Got " + type);\r
+                       }\r
+                       i.next();\r
+               }\r
+               return Amount.valueOf(len, SI.MILLIMETER);\r
+       }\r
+\r
+       /*\r
+        * Separate an area into multiple distinct area.\r
+        * Area CAN NOT HAVE HOLES. HOLES WILL BE RETURNED AS AREAS,\r
+        * SO A DONUT WILL TURN INTO TWO CIRCLES.\r
+        */\r
+       public static Set<java.awt.geom.Area> separate(java.awt.geom.Area a) {\r
+               Set<java.awt.geom.Area> res = new HashSet<java.awt.geom.Area>();\r
+               PathIterator i = a.getPathIterator(new AffineTransform());\r
+               GeneralPath cur = null;\r
+       \r
+               while (!i.isDone()) {\r
+                       double coords[] = new double[6];\r
+                       int type = i.currentSegment(coords);\r
+                       switch (type) {\r
+                       case PathIterator.SEG_CLOSE:\r
+                               cur.closePath();\r
+                               if (cur != null ){\r
+                                       java.awt.geom.Area area = new java.awt.geom.Area(cur);\r
+                                       if ( !a.isEmpty() )\r
+                                               res.add(area);\r
+                               }\r
+                               cur = new GeneralPath(i.getWindingRule());\r
+                               break;\r
+                       case PathIterator.SEG_MOVETO:\r
+                               if (cur != null ){\r
+                                       java.awt.geom.Area area = new java.awt.geom.Area(cur);\r
+                                       if ( !a.isEmpty() )\r
+                                               res.add(area);\r
+                               }\r
+                               cur = new GeneralPath(i.getWindingRule());\r
+                               cur.moveTo(coords[0], coords[1]);\r
+                               break;\r
+                       case PathIterator.SEG_CUBICTO:\r
+                               cur.curveTo(coords[0], coords[1], coords[2], coords[3],\r
+                                               coords[4], coords[5]);\r
+                               break;\r
+                       case PathIterator.SEG_LINETO:\r
+                               cur.lineTo(coords[0], coords[1]);\r
+                               break;\r
+                       case PathIterator.SEG_QUADTO:\r
+                               cur.quadTo(coords[0], coords[1], coords[2], coords[3]);\r
+                               break;\r
+       \r
+                       }\r
+                       i.next();\r
+               }\r
+       \r
+               return res;\r
+       }\r
+       \r
+       \r
+}\r
index bcb71eb8734fd77c1ecc51690e50d3de95b1a864..ec8b03786ab3fc1cc89f735d3192da32a2de9b27 100644 (file)
@@ -13,7 +13,7 @@ import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;
 import com.billkuker.rocketry.motorsim.CylindricalChamber;\r
 import com.billkuker.rocketry.motorsim.Motor;\r
 import com.billkuker.rocketry.motorsim.fuel.KNSU;\r
-import com.billkuker.rocketry.motorsim.grain.ExtrudedShapeGrain;\r
+import com.billkuker.rocketry.motorsim.grain.util.ExtrudedShapeGrain;\r
 \r
 public class CSlot extends Motor {\r
        public CSlot() {\r
index 180b3d5461e950d0953071a947b33b4dff25bddb..86a132b1fcf7bce8f9933309fe52f9be4e2930c0 100644 (file)
@@ -11,7 +11,7 @@ import com.billkuker.rocketry.motorsim.ConvergentDivergentNozzle;
 import com.billkuker.rocketry.motorsim.CylindricalChamber;\r
 import com.billkuker.rocketry.motorsim.Motor;\r
 import com.billkuker.rocketry.motorsim.fuel.KNSU;\r
-import com.billkuker.rocketry.motorsim.grain.RotatedShapeGrain;\r
+import com.billkuker.rocketry.motorsim.grain.util.RotatedShapeGrain;\r
 \r
 public class EndBurner extends Motor {\r
        public EndBurner() {\r
index 8a413b01c32e8d10a15006fabc04d476cc87081d..7971220fe90f72c23babcf981c0ffbcbbda35958 100644 (file)
@@ -7,7 +7,7 @@ import java.awt.geom.Rectangle2D;
 import org.jscience.physics.amount.Amount;\r
 import org.junit.Test;\r
 \r
-import com.billkuker.rocketry.motorsim.grain.ShapeUtil;\r
+import com.billkuker.rocketry.motorsim.grain.util.ShapeUtil;\r
 \r
 \r
 public class ShapeUtilTest extends RocketTest{\r