diff --git a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java index 4667476c8..865335609 100644 --- a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java @@ -388,90 +388,100 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { double finFriction = 0; double bodyFriction = 0; - double maxR = 0, len = 0; + double maxR = 0, minX = Double.MAX_VALUE, maxX = 0; double[] roughnessLimited = new double[Finish.values().length]; Arrays.fill(roughnessLimited, Double.NaN); - - for (RocketComponent c : configuration.getActiveComponents()) { + + final InstanceMap imap = configuration.getActiveInstances(); + for(Map.Entry> entry: imap.entrySet() ) { + final RocketComponent c = entry.getKey(); // Consider only SymmetricComponents and FinSets: if (!(c instanceof SymmetricComponent) && !(c instanceof FinSet)) continue; - - // Calculate the roughness-limited friction coefficient - Finish finish = ((ExternalComponent) c).getFinish(); - if (Double.isNaN(roughnessLimited[finish.ordinal()])) { - roughnessLimited[finish.ordinal()] = - 0.032 * Math.pow(finish.getRoughnessSize() / configuration.getLength(), 0.2) * - roughnessCorrection; - } - - /* - * Actual Cf is maximum of Cf and the roughness-limited value. - * For perfect finish require additionally that Re > 1e6 - */ - double componentCf; - if (configuration.getRocket().isPerfectFinish()) { - - // For perfect finish require Re > 1e6 - if ((Re > 1.0e6) && (roughnessLimited[finish.ordinal()] > Cf)) { - componentCf = roughnessLimited[finish.ordinal()]; - } else { - componentCf = Cf; - } - - } else { - - // For fully turbulent use simple max - componentCf = Math.max(Cf, roughnessLimited[finish.ordinal()]); - - } - - //Handle Overriden CD for Whole Rocket - if(c.isCDOverridden()) { - continue; - } - - - // Calculate the friction drag: - if (c instanceof SymmetricComponent) { - - SymmetricComponent s = (SymmetricComponent) c; - - bodyFriction += componentCf * s.getComponentWetArea(); - - if (map != null) { - // Corrected later - map.get(c).setFrictionCD(componentCf * s.getComponentWetArea() - / conditions.getRefArea()); - } - - double r = Math.max(s.getForeRadius(), s.getAftRadius()); - if (r > maxR) - maxR = r; - len += c.getLength(); - - } else if (c instanceof FinSet) { - - FinSet f = (FinSet) c; - double mac = ((FinSetCalc) calcMap.get(c)).getMACLength(); - double cd = componentCf * (1 + 2 * f.getThickness() / mac) * - 2 * f.getFinCount() * f.getPlanformArea(); - finFriction += cd; - - if (map != null) { - map.get(c).setFrictionCD(cd / conditions.getRefArea()); - } - - } - + // iterate across component instances + final ArrayList contextList = entry.getValue(); + for(InstanceContext context: contextList ) { + // Calculate the roughness-limited friction coefficient + Finish finish = ((ExternalComponent) c).getFinish(); + if (Double.isNaN(roughnessLimited[finish.ordinal()])) { + roughnessLimited[finish.ordinal()] = + 0.032 * Math.pow(finish.getRoughnessSize() / configuration.getLength(), 0.2) * + roughnessCorrection; + } + + /* + * Actual Cf is maximum of Cf and the roughness-limited value. + * For perfect finish require additionally that Re > 1e6 + */ + double componentCf; + if (configuration.getRocket().isPerfectFinish()) { + + // For perfect finish require Re > 1e6 + if ((Re > 1.0e6) && (roughnessLimited[finish.ordinal()] > Cf)) { + componentCf = roughnessLimited[finish.ordinal()]; + } else { + componentCf = Cf; + } + + } else { + + // For fully turbulent use simple max + componentCf = Math.max(Cf, roughnessLimited[finish.ordinal()]); + + } + + //Handle Overriden CD for Whole Rocket + if(c.isCDOverridden()) { + continue; + } + + + // Calculate the friction drag: + if (c instanceof SymmetricComponent) { + + SymmetricComponent s = (SymmetricComponent) c; + + bodyFriction += componentCf * s.getComponentWetArea(); + + if (map != null) { + // Corrected later + map.get(c).setFrictionCD(componentCf * s.getComponentWetArea() + / conditions.getRefArea()); + } + + final double componentMinX = context.getLocation().x; + minX = Math.min(minX, componentMinX); + + final double componentMaxX = componentMinX + c.getLength(); + maxX = Math.max(maxX, componentMaxX); + + final double componentMaxR = Math.max(s.getForeRadius(), s.getAftRadius()); + maxR = Math.max(maxR, componentMaxR); + + } else if (c instanceof FinSet) { + + FinSet f = (FinSet) c; + double mac = ((FinSetCalc) calcMap.get(c)).getMACLength(); + double cd = componentCf * (1 + 2 * f.getThickness() / mac) * + 2 * f.getPlanformArea(); + finFriction += cd; + + if (map != null) { + map.get(c).setFrictionCD(cd / conditions.getRefArea()); + } + + } + + } } + // fB may be POSITIVE_INFINITY, but that's ok for us - double fB = (len + 0.0001) / maxR; + double fB = (maxX - minX + 0.0001) / maxR; double correction = (1 + 1.0 / (2 * fB)); // Correct body data in map @@ -482,8 +492,6 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { } } } - - return (finFriction + correction * bodyFriction) / conditions.getRefArea(); } @@ -502,7 +510,6 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { Map map, WarningSet warnings) { double stagnation, base, total; - double radius = 0; if (calcMap == null) buildCalcMap(configuration); @@ -511,43 +518,54 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { base = calculateBaseCD(conditions.getMach()); total = 0; - for (RocketComponent c : configuration.getActiveComponents()) { + final InstanceMap imap = configuration.getActiveInstances(); + for(Map.Entry> entry: imap.entrySet() ) { + final RocketComponent c = entry.getKey(); if (!c.isAerodynamic()) continue; - - // Pressure fore drag - double cd = calcMap.get(c).calculatePressureDragForce(conditions, stagnation, base, - warnings); - total += cd; - - if (map != null) { - map.get(c).setPressureCD(cd); - } - - if(c.isCDOverridden()) continue; - - // Stagnation drag - if (c instanceof SymmetricComponent) { - SymmetricComponent s = (SymmetricComponent) c; - - if (radius < s.getForeRadius()) { - double area = Math.PI * (pow2(s.getForeRadius()) - pow2(radius)); - cd = stagnation * area / conditions.getRefArea(); - total += cd; - if (map != null) { - map.get(c).setPressureCD(map.get(c).getPressureCD() + cd); - } + // iterate across component instances + final ArrayList contextList = entry.getValue(); + for(InstanceContext context: contextList ) { + + // Pressure fore drag + double cd = calcMap.get(c).calculatePressureDragForce(conditions, stagnation, base, + warnings); + total += cd; + + if (map != null) { + map.get(c).setPressureCD(cd); } - radius = s.getAftRadius(); + if(c.isCDOverridden()) + continue; + + // Stagnation drag + if (c instanceof SymmetricComponent) { + SymmetricComponent s = (SymmetricComponent) c; + + double radius = 0; + final SymmetricComponent prevComponent = s.getPreviousSymmetricComponent(); + if (prevComponent != null) + radius = prevComponent.getAftRadius(); + + if (radius < s.getForeRadius()) { + double area = Math.PI * (pow2(s.getForeRadius()) - pow2(radius)); + cd = stagnation * area / conditions.getRefArea(); + total += cd; + + if (map != null) { + map.get(c).setPressureCD(map.get(c).getPressureCD() + cd); + } + } + } } } - + return total; } - + /** * Calculation of drag coefficient due to base * @@ -558,11 +576,9 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { * @return */ private double calculateBaseDrag(FlightConfiguration configuration, FlightConditions conditions, - Map map, WarningSet warnings) { + Map map, WarningSet warnings) { double base, total; - double radius = 0; - RocketComponent prevComponent = null; if (calcMap == null) buildCalcMap(configuration); @@ -570,36 +586,51 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { base = calculateBaseCD(conditions.getMach()); total = 0; - for (RocketComponent c : configuration.getActiveComponents()) { + final InstanceMap imap = configuration.getActiveInstances(); + for(Map.Entry> entry: imap.entrySet() ) { + final RocketComponent c = entry.getKey(); + if (!(c instanceof SymmetricComponent)) continue; SymmetricComponent s = (SymmetricComponent) c; - - if(c.isCDOverridden()) { - total += c.getOverrideCD(); - continue; - } - if (radius > s.getForeRadius()) { - double area = Math.PI * (pow2(radius) - pow2(s.getForeRadius())); - double cd = base * area / conditions.getRefArea(); - total += cd; - if (map != null) { - map.get(prevComponent).setBaseCD(cd); + // iterate across component instances + final ArrayList contextList = entry.getValue(); + for(InstanceContext context: contextList ) { + if(c.isCDOverridden()) { + total += c.getOverrideCD(); + continue; + } + + // if aft radius of previous component is greater than my forward radius, set + // its aft CD + double radius = 0; + final SymmetricComponent prevComponent = s.getPreviousSymmetricComponent(); + if (prevComponent != null) { + radius = prevComponent.getAftRadius(); + } + + if (radius > s.getForeRadius()) { + double area = Math.PI * (pow2(radius) - pow2(s.getForeRadius())); + double cd = base * area / conditions.getRefArea(); + total += cd; + if ((map != null) && (prevComponent != null)) { + map.get(prevComponent).setBaseCD(cd); + } + } + + // if I'm the last componenet, set my base CD + // note I can't depend on the iterator serving up components in order, + // so I can't just do this after the last iteration. + if (s.getNextSymmetricComponent() == null) { + double area = Math.PI * pow2(s.getAftRadius()); + double cd = base * area / conditions.getRefArea(); + total += cd; + if (map != null) { + map.get(s).setBaseCD(cd); + } } - } - - radius = s.getAftRadius(); - prevComponent = c; - } - - if (radius > 0) { - double area = Math.PI * pow2(radius); - double cd = base * area / conditions.getRefArea(); - total += cd; - if (map != null) { - map.get(prevComponent).setBaseCD(cd); } } diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java index 8f7b80c51..c7d2b9754 100644 --- a/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java @@ -645,7 +645,7 @@ public class FinSetCalc extends RocketComponentCalc { // Airfoil assumed to have zero base drag // Scale to correct reference area - drag *= finCount * span * thickness / conditions.getRefArea(); + drag *= span * thickness / conditions.getRefArea(); return drag; } diff --git a/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java b/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java index a530bfe94..64958596e 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java +++ b/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java @@ -7,6 +7,7 @@ import java.util.Collection; import java.util.List; import net.sf.openrocket.preset.ComponentPreset; +import net.sf.openrocket.rocketcomponent.PodSet; import net.sf.openrocket.rocketcomponent.position.AxialMethod; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; @@ -561,34 +562,37 @@ public abstract class SymmetricComponent extends BodyComponent implements Radial /** * Return the previous symmetric component, or null if none exists. - * NOTE: This method currently assumes that there are no external - * "pods". * * @return the previous SymmetricComponent, or null. */ - protected final SymmetricComponent getPreviousSymmetricComponent() { + public final SymmetricComponent getPreviousSymmetricComponent() { RocketComponent c; for (c = this.getPreviousComponent(); c != null; c = c.getPreviousComponent()) { + if (c instanceof PodSet) { + return null; + } if (c instanceof SymmetricComponent) { return (SymmetricComponent) c; } if (!(c instanceof AxialStage) && - (c.axialMethod == AxialMethod.AFTER)) + (c.axialMethod == AxialMethod.AFTER)) { return null; // Bad component type as "parent" + } } return null; } /** * Return the next symmetric component, or null if none exists. - * NOTE: This method currently assumes that there are no external - * "pods". * * @return the next SymmetricComponent, or null. */ - protected final SymmetricComponent getNextSymmetricComponent() { + public final SymmetricComponent getNextSymmetricComponent() { RocketComponent c; for (c = this.getNextComponent(); c != null; c = c.getNextComponent()) { + if (c instanceof PodSet) { + return null; + } if (c instanceof SymmetricComponent) { return (SymmetricComponent) c; } diff --git a/core/src/net/sf/openrocket/util/TestRockets.java b/core/src/net/sf/openrocket/util/TestRockets.java index 6c97eeb5c..be0c2e7f1 100644 --- a/core/src/net/sf/openrocket/util/TestRockets.java +++ b/core/src/net/sf/openrocket/util/TestRockets.java @@ -1,13 +1,16 @@ package net.sf.openrocket.util; +import java.util.ArrayList; +import java.io.FileOutputStream; +import java.util.Map; import java.util.Random; import net.sf.openrocket.appearance.Appearance; -import net.sf.openrocket.file.openrocket.OpenRocketSaver; import net.sf.openrocket.database.Databases; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.OpenRocketDocumentFactory; import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.file.openrocket.OpenRocketSaver; import net.sf.openrocket.material.Material; import net.sf.openrocket.material.Material.Type; import net.sf.openrocket.motor.Manufacturer; @@ -34,6 +37,8 @@ import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.FlightConfigurationId; import net.sf.openrocket.rocketcomponent.FreeformFinSet; import net.sf.openrocket.rocketcomponent.InnerTube; +import net.sf.openrocket.rocketcomponent.InstanceContext; +import net.sf.openrocket.rocketcomponent.InstanceMap; import net.sf.openrocket.rocketcomponent.InternalComponent; import net.sf.openrocket.rocketcomponent.LaunchLug; import net.sf.openrocket.rocketcomponent.MassComponent; @@ -1629,5 +1634,54 @@ public class TestRockets { rocket.enableEvents(); return rocketDoc; } + + // Alpha III modified to put fins on "phantom" pods + public static final Rocket makeEstesAlphaIIIWithPods() { + Rocket rocket = TestRockets.makeEstesAlphaIII(); + + // find the body and fins + final InstanceMap imap = rocket.getSelectedConfiguration().getActiveInstances(); + for(Map.Entry> entry: imap.entrySet() ) { + RocketComponent c = entry.getKey(); + if (c instanceof TrapezoidFinSet) { + final TrapezoidFinSet fins = (TrapezoidFinSet) c; + final BodyTube body = (BodyTube) fins.getParent(); + body.removeChild(fins); + + // create a PodSet to hook the fins to + PodSet podset = new PodSet(); + podset.setInstanceCount(fins.getFinCount()); + + body.addChild(podset); + + // put a phantom body tube on the pods + BodyTube podBody = new BodyTube(fins.getRootChord(), 0); + podBody.setName("Pod Body"); + podset.addChild(podBody); + + // change the number of fins to 1 and put the revised + // finset on the podbody + fins.setFinCount(1); + podBody.addChild(fins); + } + } + + return rocket; + } + /** + * dump a test rocket to a file, so we can open it in OR + */ + static void dumpRocket(Rocket rocket, String filename) { + + OpenRocketDocument doc = OpenRocketDocumentFactory.createDocumentFromRocket(rocket); + OpenRocketSaver saver = new OpenRocketSaver(); + try { + FileOutputStream str = new FileOutputStream(filename); + saver.save(str, doc, null); + } + catch (Exception e) { + System.err.println("exception " + e); + } + } } diff --git a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java index ebcc07d78..56a992836 100644 --- a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java +++ b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java @@ -231,4 +231,50 @@ public class BarrowmanCalculatorTest { assertFalse(" Missed discontinuity in Falcon 9 Heavy:", calc.isContinuous( rocket)); } + + @Test + public void testPhantomTubes() { + Rocket rocketNoPods = TestRockets.makeEstesAlphaIII(); + FlightConfiguration configNoPods = rocketNoPods.getSelectedConfiguration(); + FlightConditions conditionsNoPods = new FlightConditions(configNoPods); + WarningSet warningsNoPods = new WarningSet(); + + Rocket rocketWithPods = TestRockets.makeEstesAlphaIIIWithPods(); + FlightConfiguration configPods = rocketWithPods.getSelectedConfiguration(); + FlightConditions conditionsPods = new FlightConditions(configPods); + WarningSet warningsPods = new WarningSet(); + AerodynamicCalculator calcPods = new BarrowmanCalculator(); + AerodynamicCalculator calcNoPods = new BarrowmanCalculator(); + + final AerodynamicForces forcesNoPods = calcPods.getAerodynamicForces(configNoPods, conditionsNoPods, warningsNoPods); + final AerodynamicForces forcesPods = calcPods.getAerodynamicForces(configPods, conditionsPods, warningsPods); + assertEquals(" Estes Alpha III With Pods rocket CD value is incorrect:", forcesPods.getCD(), forcesNoPods.getCD(), EPSILON); + + // The "with pods" version has no way of seeing the fins are + // on the actual body tube rather than the phantom tubes, + // so CD won't take fin-body interference into consideration. + // So we'll adjust our CD in these tests. The magic numbers + // in x and w come from temporarily disabling the + // interference calculation in FinSetCalc and comparing + // results with and without it + // cpNoPods (0.34125,0.00000,0.00000,w=16.20502) -- interference disabled + // cpNoPods (0.34797,0.00000,0.00000,w=19.34773) -- interference enabled + + // another note: the fact that this is seen as three one-fin + // FinSets instead of a single three-fin FinSet means the CP + // will be off-axis (one of the fins is taken as having an + // angle of 0 to the airstream, so it has no contribution). + // This doesn't turn out to cause a problem in an actual + // simulation. + + final Coordinate cpNoPods = calcNoPods.getCP(configNoPods, conditionsNoPods, warningsNoPods); + final Coordinate cpPods = calcPods.getCP(configPods, conditionsPods, warningsPods); + System.out.printf("with pods %s\n", cpPods.toString()); + System.out.printf("without pods %s\n", cpNoPods.toString()); + assertEquals(" Alpha III With Pods rocket cp x value is incorrect:", cpNoPods.x - 0.002788761352, cpPods.x, EPSILON); + assertEquals(" Alpha III With Pods rocket cp y value is incorrect:", cpNoPods.y - 0.005460218430206499, cpPods.y, EPSILON); + assertEquals(" Alpha III With Pods rocket cp z value is incorrect:", cpNoPods.z, cpPods.z, EPSILON); + assertEquals(" Alpha III With Pods rocket CNa value is incorrect:", cpPods.weight, cpNoPods.weight - 3.91572, EPSILON); + } + }