+++ /dev/null
-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
+++ /dev/null
-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
--- /dev/null
+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
+++ /dev/null
-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
+++ /dev/null
-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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
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
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
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