diff --git a/core/src/main/java/info/openrocket/core/aerodynamics/AbstractAerodynamicCalculator.java b/core/src/main/java/info/openrocket/core/aerodynamics/AbstractAerodynamicCalculator.java index 59538069c..e82b982d8 100644 --- a/core/src/main/java/info/openrocket/core/aerodynamics/AbstractAerodynamicCalculator.java +++ b/core/src/main/java/info/openrocket/core/aerodynamics/AbstractAerodynamicCalculator.java @@ -32,6 +32,18 @@ public abstract class AbstractAerodynamicCalculator implements AerodynamicCalcul private ModID rocketAeroModID = new ModID(); private ModID rocketTreeModID = new ModID(); + /** + * Determine whether calculations are suspect because we are stalling + * + * @return whether we are stalling, and the margin + * between our AOA and a stall + * If the return is positive we aren't; + * If it's negative we are. + * + */ + @Override + public abstract double getStallMargin(); + //////////////// Aerodynamic calculators //////////////// @Override diff --git a/core/src/main/java/info/openrocket/core/aerodynamics/AerodynamicCalculator.java b/core/src/main/java/info/openrocket/core/aerodynamics/AerodynamicCalculator.java index 982f248ae..79d6878b9 100644 --- a/core/src/main/java/info/openrocket/core/aerodynamics/AerodynamicCalculator.java +++ b/core/src/main/java/info/openrocket/core/aerodynamics/AerodynamicCalculator.java @@ -15,6 +15,17 @@ import info.openrocket.core.util.Monitorable; */ public interface AerodynamicCalculator extends Monitorable { + /** + * Determine whether calculations are suspect because we are stalling + * + * @return whether we are stalling, and the margin + * between our AOA and a stall + * If the return is positive we aren't; + * If it's negative we are. + * + */ + public double getStallMargin(); + /** * Calculate the CP of the specified configuration. * diff --git a/core/src/main/java/info/openrocket/core/aerodynamics/BarrowmanCalculator.java b/core/src/main/java/info/openrocket/core/aerodynamics/BarrowmanCalculator.java index d4369374c..9f1cb46a4 100644 --- a/core/src/main/java/info/openrocket/core/aerodynamics/BarrowmanCalculator.java +++ b/core/src/main/java/info/openrocket/core/aerodynamics/BarrowmanCalculator.java @@ -56,6 +56,9 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { private double cacheDiameter = -1; private double cacheLength = -1; + private final double stallAngle = 17.5 * Math.PI / 180; + private double stallMargin; + public BarrowmanCalculator() { } @@ -65,7 +68,20 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { public BarrowmanCalculator newInstance() { return new BarrowmanCalculator(); } - + + /** + * Determine whether calculations are suspect because we are stalling + * + * @return whether we are stalling, and the margin + * between our AOA and a stall + * If the return is positive we aren't; + * If it's negative we are. + * + */ + @Override + public double getStallMargin() { + return stallMargin; + } /** * Calculate the CP according to the extended Barrowman method. @@ -215,6 +231,9 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { total.setCm(total.getCm() - total.getPitchDampingMoment()); total.setCyaw(total.getCyaw() - total.getYawDampingMoment()); + // How far are we from stalling? + stallMargin = stallAngle - conditions.getAOA(); + return total; } @@ -258,9 +277,6 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { if (warnings == null) warnings = ignoreWarningSet; - if (conditions.getAOA() > 17.5 * Math.PI / 180) - warnings.add(new Warning.LargeAOA(conditions.getAOA())); - if (calcMap == null) buildCalcMap(configuration); diff --git a/core/src/main/java/info/openrocket/core/simulation/AbstractSimulationStepper.java b/core/src/main/java/info/openrocket/core/simulation/AbstractSimulationStepper.java index 474d6c474..5875025ae 100644 --- a/core/src/main/java/info/openrocket/core/simulation/AbstractSimulationStepper.java +++ b/core/src/main/java/info/openrocket/core/simulation/AbstractSimulationStepper.java @@ -246,9 +246,6 @@ public abstract class AbstractSimulationStepper implements SimulationStepper { public Coordinate launchRodDirection = null; - public double maxZvelocity = Double.NaN; - public double startWarningTime = Double.NaN; - // set by calculateFlightConditions and calculateAcceleration: public AerodynamicForces forces; public Coordinate windVelocity = new Coordinate(Double.NaN, Double.NaN, Double.NaN); diff --git a/core/src/main/java/info/openrocket/core/simulation/BasicEventSimulationEngine.java b/core/src/main/java/info/openrocket/core/simulation/BasicEventSimulationEngine.java index 17b3a0a01..902a9986e 100644 --- a/core/src/main/java/info/openrocket/core/simulation/BasicEventSimulationEngine.java +++ b/core/src/main/java/info/openrocket/core/simulation/BasicEventSimulationEngine.java @@ -41,10 +41,6 @@ public class BasicEventSimulationEngine implements SimulationEngine { private final SimulationStepper landingStepper = new BasicLandingStepper(); private final SimulationStepper tumbleStepper = new BasicTumbleStepper(); private final SimulationStepper groundStepper = new GroundStepper(); - - // Constant holding 20 degrees in radians. This is the AOA condition - // necessary to transition to tumbling. - private final static double AOA_TUMBLE_CONDITION = Math.PI / 9.0; // The thrust must be below this value for the transition to tumbling. // TODO HIGH: this is an arbitrary value @@ -265,16 +261,30 @@ public class BasicEventSimulationEngine implements SimulationEngine { // Conditions for transition are: // is not already tumbling // and not stable (cg > cp) - // and aoa > AOA_TUMBLE_CONDITION threshold + // and stallMargin() < 0 if (!currentStatus.isTumbling()) { final double cp = currentStatus.getFlightDataBranch().getLast(FlightDataType.TYPE_CP_LOCATION); final double cg = currentStatus.getFlightDataBranch().getLast(FlightDataType.TYPE_CG_LOCATION); final double aoa = currentStatus.getFlightDataBranch().getLast(FlightDataType.TYPE_AOA); - - if (cg > cp && aoa > AOA_TUMBLE_CONDITION) { - currentStatus.addEvent(new FlightEvent(FlightEvent.Type.TUMBLE, currentStatus.getSimulationTime())); - } + final double margin = + currentStatus.getSimulationConditions().getAerodynamicCalculator().getStallMargin(); + + // large AOA -- stalling. + if (margin < 0) { + // If we're stable, put a warning about large AOA + // note -- if cp is NaN (which it is while on the rod) cg > cp is false + if (cg > cp) { + // Not stable, so transition to tumbling + currentStatus.addEvent(new FlightEvent(FlightEvent.Type.TUMBLE, currentStatus.getSimulationTime())); + } else { + // Stable, so warning about AOA + if (currentStatus.recordWarnings()) { + currentStatus.addEvent(new FlightEvent(FlightEvent.Type.SIM_WARN, currentStatus.getSimulationTime(), null, + new Warning.LargeAOA(aoa))); + } + } + } } // If I'm on the ground and have no events in the queue, I'm done diff --git a/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java b/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java index b37733e48..b3872aab4 100644 --- a/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java +++ b/core/src/main/java/info/openrocket/core/simulation/RK4SimulationStepper.java @@ -401,22 +401,7 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { * launch rod or 0.25 seconds after departure, and when the velocity has dropped * below 20% of the max. velocity. */ - WarningSet warnings = new WarningSet(); - store.maxZvelocity = MathUtil.max(store.maxZvelocity, status.getRocketVelocity().z); - - if (!status.isLaunchRodCleared()) { - warnings = null; - } else { - if (status.getRocketVelocity().z < 0.2 * store.maxZvelocity) { - warnings = null; - } - if (Double.isNaN(store.startWarningTime)) { - store.startWarningTime = status.getSimulationTime() + 0.25; - } - } - - if (!(status.getSimulationTime() > store.startWarningTime)) - warnings = null; + WarningSet warnings = status.recordWarnings() ? new WarningSet() : null; // Calculate aerodynamic forces store.forces = status.getSimulationConditions().getAerodynamicCalculator() diff --git a/core/src/main/java/info/openrocket/core/simulation/SimulationStatus.java b/core/src/main/java/info/openrocket/core/simulation/SimulationStatus.java index 54c279bfc..597f25f7e 100644 --- a/core/src/main/java/info/openrocket/core/simulation/SimulationStatus.java +++ b/core/src/main/java/info/openrocket/core/simulation/SimulationStatus.java @@ -38,6 +38,13 @@ import org.slf4j.LoggerFactory; public class SimulationStatus implements Cloneable, Monitorable { + // time after leaving launch rod before recording flight event warnings + private final double WARNINGS_WAIT = 0.25; + + // when our z velocity decreases to this proportion of max z velocity, stop recording + // most flight event warnings + private final double WARNINGS_VEL = 0.2; + private static final Logger log = LoggerFactory.getLogger(BasicEventSimulationEngine.class); private SimulationConditions simulationConditions; @@ -54,6 +61,9 @@ public class SimulationStatus implements Cloneable, Monitorable { private Quaternion orientation; private Coordinate rotationVelocity; + private double maxZVelocity = Double.NEGATIVE_INFINITY; + private double startWarningsTime = 1200; + private double effectiveLaunchRodLength; // Set of all motors @@ -189,7 +199,9 @@ public class SimulationStatus implements Cloneable, Monitorable { this.apogeeReached = orig.apogeeReached; this.tumbling = orig.tumbling; this.landed = orig.landed; - + this.maxZVelocity = orig.maxZVelocity; + this.startWarningsTime = orig.startWarningsTime; + this.configuration.copyStages(orig.configuration); this.deployedRecoveryDevices.clear(); @@ -357,6 +369,9 @@ public class SimulationStatus implements Cloneable, Monitorable { public void setLaunchRodCleared(boolean launchRod) { this.launchRodCleared = launchRod; + if (launchRod) { + startWarningsTime = getSimulationTime() + WARNINGS_WAIT; + } modID = new ModID(); } @@ -550,6 +565,8 @@ public class SimulationStatus implements Cloneable, Monitorable { flightDataBranch.setValue(FlightDataType.TYPE_VELOCITY_XY, MathUtil.hypot(getRocketVelocity().x, getRocketVelocity().y)); flightDataBranch.setValue(FlightDataType.TYPE_VELOCITY_Z, getRocketVelocity().z); + setMaxZVelocity(Math.max(getRocketVelocity().z, getMaxZVelocity())); + flightDataBranch.setValue(FlightDataType.TYPE_VELOCITY_TOTAL, getRocketVelocity().length()); Coordinate c = getRocketOrientationQuaternion().rotateZ(); @@ -563,6 +580,46 @@ public class SimulationStatus implements Cloneable, Monitorable { (System.nanoTime() - getSimulationStartWallTime()) / 1000000000.0); } + /** + * Get max Z velocity so far in flight + * @return max Z velocity so far + */ + public double getMaxZVelocity() { + return maxZVelocity; + } + + /** + * Set max Z velocity so far + * @param zVel current z velocity + */ + private void setMaxZVelocity(double zVel) { + if (zVel > maxZVelocity) { + maxZVelocity = zVel; + modID = new ModID(); + } + } + + /** + * Determine whether (most) flight event warnings are currently being saved. + * Warnings are not saved until 0.25 seconds after leaving the rail, and again + * after Z velocity is reduced to 20% of the max. + */ + boolean recordWarnings() { + if (!launchRodCleared) { + return false; + } + + if (getSimulationTime() < startWarningsTime) { + return false; + } + + if (getRocketVelocity().z < getMaxZVelocity() * 0.2) { + return false; + } + + return true; + } + /** * Add a flight event to the event queue unless a listener aborts adding it. * @@ -570,6 +627,7 @@ public class SimulationStatus implements Cloneable, Monitorable { */ public void addEvent(FlightEvent event) throws SimulationException { if (SimulationListenerHelper.fireAddFlightEvent(this, event)) { + if (event.getType() != FlightEvent.Type.ALTITUDE) { log.trace("Adding event to queue: " + event); }