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