]> git.gag.com Git - sw/motorsim/commitdiff
Changed to non-sampled x-section area
authorBill Kuker <bkuker@billkuker.com>
Fri, 10 Apr 2009 15:08:12 +0000 (15:08 +0000)
committerBill Kuker <bkuker@billkuker.com>
Fri, 10 Apr 2009 15:08:12 +0000 (15:08 +0000)
src/com/billkuker/rocketry/motorsim/grain/ExtrudedGrain.java

index 70ae575058a871990cddab56c87075767aac215d..092430048186b41fff1e72b7356775ff3f75a4b7 100644 (file)
@@ -10,9 +10,11 @@ import java.awt.Rectangle;
 import java.awt.Shape;\r
 import java.awt.geom.AffineTransform;\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
 import java.util.HashSet;\r
+import java.util.Iterator;\r
 import java.util.NoSuchElementException;\r
 import java.util.Set;\r
 import java.util.SortedMap;\r
@@ -37,267 +39,292 @@ import com.billkuker.rocketry.motorsim.Grain;
 import com.billkuker.rocketry.motorsim.visual.Chart;\r
 \r
 public class ExtrudedGrain implements Grain, Grain.Graphical {\r
-       \r
-       Set<Shape> plus =  new HashSet<Shape>();\r
-       Set<Shape> minus =  new HashSet<Shape>();\r
+\r
+       Set<Shape> plus = new HashSet<Shape>();\r
+\r
+       Set<Shape> minus = new HashSet<Shape>();\r
+\r
        Set<Shape> inhibited = new HashSet<Shape>();\r
-       \r
+\r
        Amount<Length> length = Amount.valueOf(25, SI.MILLIMETER);\r
-       \r
+\r
        Amount<Length> rStep;\r
-       \r
-       private class RegEntry{\r
+\r
+       private class RegEntry {\r
                Amount<Area> surfaceArea;\r
+\r
                Amount<Volume> volume;\r
        }\r
-       \r
+\r
        SortedMap<Amount<Length>, RegEntry> data = new TreeMap<Amount<Length>, RegEntry>();\r
-       \r
+\r
        Amount<Length> webThickness;\r
-       \r
+\r
        {\r
-               /* Similar test grain*/\r
-               Shape outside = new Ellipse2D.Double(50,50,30,30);\r
-               plus.add(outside);\r
-               minus.add(new Ellipse2D.Double(50,60,10,10));\r
-               inhibited.add(outside);\r
-               length = Amount.valueOf(70, SI.MILLIMETER);\r
-               /*/\r
-               \r
-               /*Big c-slot\r
-               Shape outside = new Ellipse2D.Double(0,0,30,30);\r
+               /*\r
+                * Similar test grain Shape outside = new Ellipse2D.Double(50,50,30,30);\r
+                * plus.add(outside); minus.add(new Ellipse2D.Double(50,60,10,10));\r
+                * inhibited.add(outside); length = Amount.valueOf(70, SI.MILLIMETER); /\r
+                */\r
+\r
+               /* Big c-slot */\r
+               Shape outside = new Ellipse2D.Double(0, 0, 30, 30);\r
                plus.add(outside);\r
                inhibited.add(outside);\r
-               minus.add(new Rectangle2D.Double(12,12,6,20));\r
+               minus.add(new Rectangle2D.Double(12, 25, 5, 10));\r
+               minus.add(new Ellipse2D.Double(10, 4, 5, 5));\r
                length = Amount.valueOf(70, SI.MILLIMETER);\r
-               */\r
-               \r
-               /*Plus sign\r
-               Shape outside = new Ellipse2D.Double(0,0,200,200);\r
-               plus.add(outside);\r
-               inhibited.add(outside);\r
-               minus.add(new Rectangle2D.Double(90,40,20,120));\r
-               minus.add(new Rectangle2D.Double(40,90,120,20));\r
-               */\r
-               \r
+               /**/\r
+\r
+               /*\r
+                * Plus sign Shape outside = new Ellipse2D.Double(0,0,200,200);\r
+                * plus.add(outside); inhibited.add(outside); minus.add(new\r
+                * Rectangle2D.Double(90,40,20,120)); minus.add(new\r
+                * Rectangle2D.Double(40,90,120,20));\r
+                */\r
+\r
                findWebThickness();\r
-               \r
+\r
                fillInData();\r
        }\r
-       \r
+\r
        @Override\r
        public Amount<Area> surfaceArea(Amount<Length> regression) {\r
-               if ( regression.isGreaterThan(webThickness) )\r
+               if (regression.isGreaterThan(webThickness))\r
                        return Amount.valueOf(0, Area.UNIT);\r
-               Amount<Length> highKey =  data.tailMap(regression).firstKey();\r
+               Amount<Length> highKey = data.tailMap(regression).firstKey();\r
                Amount<Length> lowKey;\r
-               try{\r
-                       lowKey =  data.headMap(regression).lastKey();\r
-               } catch ( NoSuchElementException e ){\r
+               try {\r
+                       lowKey = data.headMap(regression).lastKey();\r
+               } catch (NoSuchElementException e) {\r
                        return data.get(highKey).surfaceArea;\r
                }\r
-               \r
-               double lp = regression.minus(lowKey).divide(highKey.minus(lowKey)).to(Dimensionless.UNIT).doubleValue(Dimensionless.UNIT);\r
-               \r
+\r
+               double lp = regression.minus(lowKey).divide(highKey.minus(lowKey)).to(\r
+                               Dimensionless.UNIT).doubleValue(Dimensionless.UNIT);\r
+\r
                Amount<Area> lowVal = data.get(lowKey).surfaceArea;\r
                Amount<Area> highVal = data.get(highKey).surfaceArea;\r
-               \r
-               return lowVal.times(1-lp).plus(highVal.times(lp));\r
+\r
+               return lowVal.times(1 - lp).plus(highVal.times(lp));\r
        }\r
-       \r
+\r
        @Override\r
        public Amount<Volume> volume(Amount<Length> regression) {\r
-               if ( regression.isGreaterThan(webThickness) )\r
+               if (regression.isGreaterThan(webThickness))\r
                        return Amount.valueOf(0, Volume.UNIT);\r
-               Amount<Length> highKey =  data.tailMap(regression).firstKey();\r
+               Amount<Length> highKey = data.tailMap(regression).firstKey();\r
                Amount<Length> lowKey;\r
-               try{\r
-                       lowKey =  data.headMap(regression).lastKey();\r
-               } catch ( NoSuchElementException e ){\r
+               try {\r
+                       lowKey = data.headMap(regression).lastKey();\r
+               } catch (NoSuchElementException e) {\r
                        return data.get(highKey).volume;\r
                }\r
-               \r
-               double lp = regression.minus(lowKey).divide(highKey.minus(lowKey)).to(Dimensionless.UNIT).doubleValue(Dimensionless.UNIT);\r
-               \r
+\r
+               double lp = regression.minus(lowKey).divide(highKey.minus(lowKey)).to(\r
+                               Dimensionless.UNIT).doubleValue(Dimensionless.UNIT);\r
+\r
                Amount<Volume> lowVal = data.get(lowKey).volume;\r
                Amount<Volume> highVal = data.get(highKey).volume;\r
-               \r
-               return lowVal.times(1-lp).plus(highVal.times(lp));\r
+\r
+               return lowVal.times(1 - lp).plus(highVal.times(lp));\r
        }\r
-       \r
-       private void fillInData(){\r
+\r
+       private void fillInData() {\r
                double max = webThickness().doubleValue(SI.MILLIMETER);\r
-               double delta = max / 5;\r
+               double delta = max / 100;\r
                rStep = Amount.valueOf(delta, SI.MILLIMETER).divide(10);\r
-               for( double r = 0; r <= max+1; r += delta){\r
+               for (double r = 0; r <= max + 1; r += delta) {\r
                        RegEntry e = new RegEntry();\r
                        Amount<Length> regression = Amount.valueOf(r, SI.MILLIMETER);\r
 \r
-                       \r
                        Amount<Length> rLen = length.minus(regression.times(2));\r
-                       if ( rLen.isLessThan(Amount.valueOf(0, SI.MILLIMETER)))\r
+                       if (rLen.isLessThan(Amount.valueOf(0, SI.MILLIMETER)))\r
                                break;\r
-                       \r
+\r
                        System.out.println("Calculating area for regression " + regression);\r
-                       \r
+\r
                        java.awt.geom.Area burn = getArea(regression);\r
-                       if ( burn.isEmpty() )\r
+                       if (burn.isEmpty())\r
                                break;\r
-                       burn.subtract(getArea(regression.plus(Amount.valueOf(.001, SI.MILLIMETER))));\r
-                       \r
-                       Amount<Area> xSection = crossSectionArea(getArea(regression));\r
+                       burn.subtract(getArea(regression.plus(Amount.valueOf(.001,\r
+                                       SI.MILLIMETER))));\r
+\r
+                       //Amount<Area> xSection = crossSectionArea(getArea(regression));\r
                        \r
+                       Amount<Area> xSection = crossSectionArea(regression);\r
+\r
                        e.volume = xSection.times(rLen).to(Volume.UNIT);\r
-                       \r
-                       e.surfaceArea = perimeter(burn).divide(2).times(rLen).plus(xSection.times(2)).to(Area.UNIT);\r
-                       \r
+\r
+                       e.surfaceArea = perimeter(burn).divide(2).times(rLen).plus(\r
+                                       xSection.times(2)).to(Area.UNIT);\r
+\r
                        data.put(regression, e);\r
 \r
                }\r
-               \r
+\r
                RegEntry e = new RegEntry();\r
                e.surfaceArea = Amount.valueOf(0, Area.UNIT);\r
                e.volume = Amount.valueOf(0, Volume.UNIT);\r
-               data.put( webThickness(), e );\r
+               data.put(webThickness(), e);\r
        }\r
+\r
        \r
-       private Amount<Area> crossSectionArea(java.awt.geom.Area a ) {\r
-               Rectangle r = a.getBounds();\r
-               int cnt = 0;\r
-               int total = 0;\r
-               double e = .1;\r
-               for( double x = r.getMinX(); x < r.getMaxX(); x += e){\r
-                       for( double y = r.getMinY(); y < r.getMaxY(); y += e){\r
-                               if ( a.contains(x, y) )\r
-                                       cnt++;\r
-                               total++;\r
-                       }\r
+       private Amount<Area> crossSectionArea(Amount<Length> regression) {\r
+               //Get the PLUS shape and sum its area\r
+               java.awt.geom.Area plus = getPlus(regression);\r
+               Amount<Area> plusArea = Amount.valueOf(0, SI.SQUARE_METRE);\r
+               for (java.awt.geom.Area a : separate(plus)) {\r
+                       plusArea = plusArea.plus(area(a));\r
                }\r
-               System.out.println(cnt + " out of " + total);\r
-               return Amount.valueOf(r.getWidth() * r.getHeight() * cnt / total, SI.MILLIMETER.pow(2)).to(Area.UNIT);\r
-\r
+               \r
+               //Get the MINUS shape, intersect it with PLUS to get just the parts\r
+               //that are removed, sum it's area\r
+               java.awt.geom.Area minus = getMinus(regression);\r
+               minus.intersect(plus);\r
+               Amount<Area> minusArea = Amount.valueOf(0, SI.SQUARE_METRE);\r
+               for (java.awt.geom.Area a : separate(minus)) {\r
+                       minusArea = minusArea.plus(area(a));\r
+               }\r
+               \r
+               //Subtract PLUS from MINUS and return\r
+               Amount<Area> area = plusArea.minus(minusArea);\r
+               System.out.println(area.to(SI.MILLIMETER.pow(2)));\r
+               return area;\r
        }\r
-       \r
-       \r
-\r
-\r
 \r
        @Override\r
        public Amount<Length> webThickness() {\r
                return webThickness;\r
        }\r
-       \r
-       private Amount<Length> perimeter(java.awt.geom.Area a){\r
+\r
+       private Amount<Length> perimeter(java.awt.geom.Area 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 x = 0, y = 0;\r
                double len = 0;\r
-               while ( !i.isDone() ){\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
+                       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
+                               // 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
+                       } 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
+                               // System.err.println("Got " + type);\r
                        }\r
                        i.next();\r
                }\r
                return Amount.valueOf(len, SI.MILLIMETER);\r
        }\r
-       \r
-       private void findWebThickness(){\r
+\r
+       private void findWebThickness() {\r
                java.awt.geom.Area a = getArea(Amount.valueOf(0, SI.MILLIMETER));\r
                Rectangle r = a.getBounds();\r
-               double max = r.getWidth()<r.getHeight()?r.getHeight():r.getWidth(); //The max size\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: " + max);\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 = getArea(Amount.valueOf(guess, SI.MILLIMETER));\r
-                       if ( a.isEmpty() ){\r
-                               //guess is too big\r
+                       if (a.isEmpty()) {\r
+                               // guess is too big\r
                                max = guess;\r
                        } else {\r
-                               //min is too big\r
+                               // min is too big\r
                                min = guess;\r
                        }\r
-                       if ( (max-min) < .01 )\r
+                       if ((max - min) < .01)\r
                                break;\r
                }\r
                webThickness = Amount.valueOf(guess, SI.MILLIMETER);\r
-               if ( webThickness.isGreaterThan(length.divide(2)))\r
+               if (webThickness.isGreaterThan(length.divide(2)))\r
                        webThickness = length.divide(2);\r
        }\r
-       private java.awt.geom.Area getArea(Amount<Length> regression){\r
+\r
+       private java.awt.geom.Area getArea(Amount<Length> regression) {\r
+               java.awt.geom.Area res = getPlus(regression);\r
+               res.subtract(getMinus(regression));\r
+               return res;\r
+       }\r
+       \r
+       private java.awt.geom.Area getPlus(Amount<Length> regression) {\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
+               return a;\r
+       }\r
+       \r
+       private java.awt.geom.Area getMinus(Amount<Length> regression) {\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.doubleValue(SI.MILLIMETER), true)));\r
-               for ( Shape s: minus )\r
-                       a.subtract(new java.awt.geom.Area(regress(s, regression.doubleValue(SI.MILLIMETER), false)));\r
+               for (Shape s : minus)\r
+                       a.add(new java.awt.geom.Area(regress(s, regression\r
+                                       .doubleValue(SI.MILLIMETER), false)));\r
                return a;\r
        }\r
        \r
-       private Shape regress(Shape s, double mm, boolean plus){\r
-               if ( inhibited.contains(s) )\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
+               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
-                       double d = plus?-2*mm:2*mm;\r
-                       \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
+                       double d = plus ? -2 * mm : 2 * mm;\r
+\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
-                       \r
-                       return new Rectangle2D.Double(x,y,w,h);\r
+                       double x = r.getX() - d / 2;\r
+                       double y = r.getY() - d / 2;\r
+\r
+                       return new Rectangle2D.Double(x, y, w, h);\r
                }\r
                return null;\r
        }\r
-       \r
 \r
        public static void main(String args[]) throws Exception {\r
                ExtrudedGrain e = new ExtrudedGrain();\r
-                       new GrainPanel(e).show();\r
-               \r
+               new GrainPanel(e).show();\r
        }\r
 \r
        @Override\r
        public void draw(Graphics2D g2d, Amount<Length> regression) {\r
-               \r
+\r
                java.awt.geom.Area reg = getArea(regression);\r
                java.awt.geom.Area burn = getArea(regression);\r
-               burn.subtract(getArea(regression.plus(Amount.valueOf(.001, SI.MILLIMETER))));\r
-               java.awt.geom.Area noreg = getArea(Amount.valueOf(0,SI.MILLIMETER));\r
-               \r
+               burn.subtract(getArea(regression.plus(Amount.valueOf(.001,\r
+                               SI.MILLIMETER))));\r
+               java.awt.geom.Area noreg = getArea(Amount.valueOf(0, SI.MILLIMETER));\r
+\r
                Rectangle bounds = noreg.getBounds();\r
-               g2d.scale(200/bounds.getWidth(), 200/bounds.getHeight());\r
+               g2d.scale(200 / bounds.getWidth(), 200 / bounds.getHeight());\r
                g2d.translate(-bounds.getX(), -bounds.getY());\r
-               \r
 \r
                g2d.setStroke(new BasicStroke(0.5f));\r
-               \r
+\r
                g2d.setColor(Color.GRAY);\r
                g2d.fill(reg);\r
                g2d.setColor(Color.RED);\r
@@ -305,7 +332,116 @@ public class ExtrudedGrain implements Grain, Grain.Graphical {
                g2d.setColor(Color.BLACK);\r
                g2d.draw(noreg);\r
                \r
+\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
+       private 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
+        * 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
+        */\r
+       private Amount<Area> area(java.awt.geom.Area 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
+                               System.err.println("Got " + type);\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
+               System.out.println(area);\r
+               \r
+               return Amount.valueOf(area, SI.MILLIMETER.pow(2)).to(Area.UNIT);\r
+       }\r
 \r
 }\r