diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/RailButtonCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/RailButtonCalc.java new file mode 100644 index 000000000..a94bd76e5 --- /dev/null +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/RailButtonCalc.java @@ -0,0 +1,102 @@ +package net.sf.openrocket.aerodynamics.barrowman; + +import java.util.List; +import java.lang.Math; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.sf.openrocket.aerodynamics.AerodynamicForces; +import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.aerodynamics.WarningSet; +import net.sf.openrocket.rocketcomponent.RailButton; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Transformation; + +public class RailButtonCalc extends RocketComponentCalc { + private final static Logger log = LoggerFactory.getLogger(RailButtonCalc.class); + + // values transcribed from Gowen and Perkins, "Drag of Circular Cylinders for a Wide Range + // of Reynolds Numbers and Mach Numbers", NACA Technical Note 2960, Figure 7 + private static final List cdDomain = List.of(0.0, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 1.0, 1.6, 2.0, 2.8, 100.0); + private static final List cdRange = List.of(1.2, 1.22, 1.25, 1.3, 1.4, 1.5, 1.6, 2.1, 1.5, 1.45, 1.33, 1.33); + + private final RailButton button; + + public RailButtonCalc(RocketComponent component) { + super(component); + + // need to stash the button + button = (RailButton) component; + } + + @Override + public double calculateFrictionCD(FlightConditions conditions, double componentCf, WarningSet warnings) { + // very small relative surface area, and slick + return 0.0; + } + + @Override + public void calculateNonaxialForces(FlightConditions conditions, Transformation transform, + AerodynamicForces forces, WarningSet warnings) { + // Nothing to be done + } + + @Override + public double calculatePressureCD(FlightConditions conditions, + double stagnationCD, double baseCD, WarningSet warnings) { + + // grab relevant button params + final int instanceCount = button.getInstanceCount(); + final Coordinate[] instanceOffsets = button.getInstanceOffsets(); + + // compute button reference area + final double buttonHt = button.getTotalHeight(); + final double outerArea = buttonHt * button.getOuterDiameter(); + final double notchArea = (button.getOuterDiameter() - button.getInnerDiameter()) * button.getInnerHeight(); + final double refArea = outerArea - notchArea; + + // accumulate Cd contribution from each rail button + double CDmul = 0.0; + for (int i = 0; i < button.getInstanceCount(); i++) { + + // compute boundary layer height at button location. I can't find a good reference for the + // formula, e.g. https://aerospaceengineeringblog.com/boundary-layers/ simply says it's the + // "scientific consensus". + double x = (button.toAbsolute(instanceOffsets[i]))[0].x; // location of button + double rex = calculateReynoldsNumber(x, conditions); // Reynolds number of button location + double del = 0.37 * x / Math.pow(rex, 0.2); // Boundary layer thickness + + // compute mean airspeed over button + // this assumes airspeed changes linearly through boundary layer + // and that all parts of the railbutton contribute equally to Cd, + // neither of which is true but both are plenty close enough for our purposes + + double mach; + if (buttonHt > del) { + // Case 1: button extends beyond boundary layer + // Mean velocity is 1/2 rocket velocity up to limit of boundary layer, + // full velocity after that + mach = (buttonHt - 0.5*del) * conditions.getMach()/buttonHt; + } else { + // Case 2: button is entirely within boundary layer + mach = MathUtil.map(buttonHt/2.0, 0, del, 0, conditions.getMach()); + } + + // look up Cd as function of speed. It's pretty constant as a function of Reynolds + // number when slow, so we can just use a function of Mach number + double cd = MathUtil.interpolate(cdDomain, cdRange, mach); + + // Since later drag force calculations don't consider boundary layer, compute "effective Cd" + // based on rocket velocity + cd = cd * MathUtil.pow2(mach)/MathUtil.pow2(conditions.getMach()); + + // add to CDmul + CDmul += cd; + } + + return CDmul * stagnationCD * refArea / conditions.getRefArea(); + } +} diff --git a/core/src/net/sf/openrocket/rocketcomponent/RailButton.java b/core/src/net/sf/openrocket/rocketcomponent/RailButton.java index 504de01e3..a25e616a0 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RailButton.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RailButton.java @@ -207,12 +207,6 @@ public class RailButton extends ExternalComponent implements AnglePositionable, clearPreset(); fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); } - - @Override - public boolean isAerodynamic(){ - // TODO: implement aerodynamics - return false; - } @Override public double getAngleOffset(){ diff --git a/core/test/net/sf/openrocket/aerodynamics/RailButtonCalcTest.java b/core/test/net/sf/openrocket/aerodynamics/RailButtonCalcTest.java new file mode 100644 index 000000000..513b0c922 --- /dev/null +++ b/core/test/net/sf/openrocket/aerodynamics/RailButtonCalcTest.java @@ -0,0 +1,101 @@ +package net.sf.openrocket.aerodynamics; + +import static org.junit.Assert.assertEquals; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; + +import net.sf.openrocket.ServicesForTesting; +import net.sf.openrocket.aerodynamics.BarrowmanCalculator; +import net.sf.openrocket.aerodynamics.barrowman.RailButtonCalc; +import net.sf.openrocket.plugin.PluginModule; +import net.sf.openrocket.rocketcomponent.FlightConfiguration; +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.RailButton; +import net.sf.openrocket.rocketcomponent.LaunchLug; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.rocketcomponent.position.AxialMethod; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.TestRockets; +import net.sf.openrocket.util.Transformation; + +public class RailButtonCalcTest { + protected final double EPSILON = 0.0001; + + private static Injector injector; + @BeforeClass + public static void setup() { + Module applicationModule = new ServicesForTesting(); + Module pluginModule = new PluginModule(); + + injector = Guice.createInjector( applicationModule, pluginModule); + Application.setInjector(injector); + } + + @Test + public void testRailButtons() { + + Rocket rocket = TestRockets.makeEstesAlphaIII(); + FlightConfiguration config = rocket.getSelectedConfiguration(); + + // Get the body tube... + BodyTube tube = (BodyTube)rocket.getChild(0).getChild(1); + + // Replace the launch lug with a (single) railbutton + LaunchLug lug = (LaunchLug)tube.getChild(1); + rocket.removeChild(lug); + + RailButton button = new RailButton(); + tube.addChild(button); + + // Button parameters from Binder Design standard 1010 + button.setOuterDiameter(0.011); + button.setInnerDiameter(0.006); + + button.setBaseHeight(0.002); + button.setFlangeHeight(0.002); + button.setTotalHeight(0.008); + + button.setAxialMethod(AxialMethod.ABSOLUTE); + button.setAxialOffset(1.0); + + // Set up flight conditions + FlightConditions conditions = new FlightConditions(config); + conditions.setMach(1.0); + + BarrowmanCalculator barrowmanObj = new BarrowmanCalculator(); + RailButtonCalc calcObj = new RailButtonCalc(button); + + // Calculate effective CD for rail button + // Boundary layer height + double rex = calcObj.calculateReynoldsNumber(1.0, conditions); // Reynolds number of button location + double del = 0.37 * 1.0 / Math.pow(rex, 0.2); // Boundary layer height + + // Interpolate velocity at midpoint of railbutton + double mach = MathUtil.map(0.008/2.0, 0, del, 0, 1.0); + + // Interpolate to get CD + double cd = MathUtil.map(mach, 0.2, 0.3, 1.22, 1.25); + + // Reference area of rail button + final double outerArea = button.getTotalHeight() * button.getOuterDiameter(); + final double notchArea = (button.getOuterDiameter() - button.getInnerDiameter()) * button.getInnerHeight(); + final double refArea = outerArea - notchArea; + + // Get "effective" CD + double calccd = cd * MathUtil.pow2(mach) * barrowmanObj.calculateStagnationCD(conditions.getMach()) * refArea / conditions.getRefArea() ; + + // Now compare with value from RailButtonCalc + WarningSet warnings = new WarningSet(); + AerodynamicForces assemblyForces = new AerodynamicForces().zero(); + AerodynamicForces componentForces = new AerodynamicForces(); + + double testcd = calcObj.calculatePressureCD(conditions, barrowmanObj.calculateStagnationCD(conditions.getMach()), 0, warnings); + + assertEquals("Calculated rail button CD incorrect", calccd, testcd, EPSILON); + } +}