diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 23ec515b3..32268fa3f 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1693,7 +1693,7 @@ Warning.SUPERSONIC = Body calculations may not be entirely accurate at supersoni Warning.RECOVERY_LAUNCH_ROD = Recovery device device deployed while on the launch guide. Warning.RECOVERY_HIGH_SPEED = Recovery device deployment at high speed Warning.TUMBLE_UNDER_THRUST = Stage began to tumble under thrust. - +Warning.EVENT_AFTER_LANDING = Flight Event occurred after landing: ! Scale dialog ScaleDialog.lbl.scaleRocket = Entire rocket diff --git a/core/src/net/sf/openrocket/aerodynamics/Warning.java b/core/src/net/sf/openrocket/aerodynamics/Warning.java index 7f93654fa..f29507c26 100644 --- a/core/src/net/sf/openrocket/aerodynamics/Warning.java +++ b/core/src/net/sf/openrocket/aerodynamics/Warning.java @@ -3,6 +3,7 @@ package net.sf.openrocket.aerodynamics; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.startup.Application; +import net.sf.openrocket.simulation.FlightEvent; import net.sf.openrocket.unit.UnitGroup; public abstract class Warning { @@ -123,6 +124,41 @@ public abstract class Warning { return false; } } + + /** + * A Warning indicating flight events occurred after ground hit + * + */ + public static class EventAfterLanding extends Warning { + private FlightEvent event; + + /** + * Sole constructor. The argument is an event which has occurred after landing + * + * @param event the event that caused this warning + */ + public EventAfterLanding(FlightEvent _event) { + this.event = _event; + } + + // I want a warning on every event that occurs after we land, + // so severity of problem is clear to the user + @Override + public boolean equals(Object o) { + return false; + } + + + @Override + public String toString() { + return trans.get("Warning.EVENT_AFTER_LANDING") + event.getType(); + } + + @Override + public boolean replaceBy(Warning other) { + return false; + } + } public static class MissingMotor extends Warning { @@ -350,5 +386,7 @@ public abstract class Warning { public static final Warning RECOVERY_LAUNCH_ROD = new Other(trans.get("Warning.RECOVERY_LAUNCH_ROD")); public static final Warning TUMBLE_UNDER_THRUST = new Other(trans.get("Warning.TUMBLE_UNDER_THRUST")); + + public static final Warning EVENT_AFTER_LANDING = new Other(trans.get("Warning.EVENT_AFTER_LANDING")); } diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java index f64a164c3..da54cc483 100644 --- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java +++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java @@ -35,9 +35,10 @@ public class BasicEventSimulationEngine implements SimulationEngine { private static final Logger log = LoggerFactory.getLogger(BasicEventSimulationEngine.class); // TODO: MEDIUM: Allow selecting steppers - private SimulationStepper flightStepper = new RK4SimulationStepper(); + private SimulationStepper flightStepper = new RK4SimulationStepper(); private SimulationStepper landingStepper = new BasicLandingStepper(); - private SimulationStepper tumbleStepper = new BasicTumbleStepper(); + private SimulationStepper tumbleStepper = new BasicTumbleStepper(); + private SimulationStepper groundStepper = new GroundStepper(); // Constant holding 20 degrees in radians. This is the AOA condition // necessary to transition to tumbling. @@ -107,8 +108,12 @@ public class BasicEventSimulationEngine implements SimulationEngine { private FlightDataBranch simulateLoop() { - // Initialize the simulation - currentStepper = flightStepper; + // Initialize the simulation. We'll use the flight stepper unless we're already on the ground + if (currentStatus.isLanded()) + currentStepper = groundStepper; + else + currentStepper = flightStepper; + currentStatus = currentStepper.initialize(currentStatus); // Get originating position (in case listener has modified launch position) @@ -138,16 +143,16 @@ public class BasicEventSimulationEngine implements SimulationEngine { // Check for NaN values in the simulation status checkNaN(); - // Add altitude event - addEvent(new FlightEvent(FlightEvent.Type.ALTITUDE, currentStatus.getSimulationTime(), - currentStatus.getConfiguration().getRocket(), - new Pair(oldAlt, currentStatus.getRocketPosition().z))); + // If we haven't hit the ground, add altitude event + if (!currentStatus.isLanded()) + addEvent(new FlightEvent(FlightEvent.Type.ALTITUDE, currentStatus.getSimulationTime(), + currentStatus.getConfiguration().getRocket(), + new Pair(oldAlt, currentStatus.getRocketPosition().z))); if (currentStatus.getRocketPosition().z > currentStatus.getMaxAlt()) { currentStatus.setMaxAlt(currentStatus.getRocketPosition().z); } - // Position relative to start location Coordinate relativePosition = currentStatus.getRocketPosition().sub(origin); @@ -167,10 +172,10 @@ public class BasicEventSimulationEngine implements SimulationEngine { } else { // Check ground hit after liftoff - if (currentStatus.getRocketPosition().z < 0) { - currentStatus.setRocketPosition(currentStatus.getRocketPosition().setZ(0)); + if ((currentStatus.getRocketPosition().z < 0) && !currentStatus.isLanded()) { addEvent(new FlightEvent(FlightEvent.Type.GROUND_HIT, currentStatus.getSimulationTime())); - addEvent(new FlightEvent(FlightEvent.Type.SIMULATION_END, currentStatus.getSimulationTime())); + + // addEvent(new FlightEvent(FlightEvent.Type.SIMULATION_END, currentStatus.getSimulationTime())); } } @@ -228,7 +233,10 @@ public class BasicEventSimulationEngine implements SimulationEngine { } } - + + // If I'm on the ground and have no events in the queue, I'm done + if (currentStatus.isLanded() && currentStatus.getEventQueue().isEmpty()) + addEvent(new FlightEvent(FlightEvent.Type.SIMULATION_END, currentStatus.getSimulationTime())); } } catch (SimulationException e) { @@ -254,6 +262,12 @@ public class BasicEventSimulationEngine implements SimulationEngine { for (event = nextEvent(); event != null; event = nextEvent()) { log.trace("Obtained event from queue: " + event.toString()); log.trace("Remaining EventQueue = " + currentStatus.getEventQueue().toString()); + + // If I get an event other than ALTITUDE and SIMULATION_END after I'm on the ground, there's a problem + if (currentStatus.isLanded() && + (event.getType() != FlightEvent.Type.ALTITUDE) && + (event.getType() != FlightEvent.Type.SIMULATION_END)) + currentStatus.getWarnings().add(new Warning.EventAfterLanding(event)); // Check for motor ignition events, add ignition events to queue for (MotorClusterState state : currentStatus.getActiveMotors() ){ @@ -326,7 +340,7 @@ public class BasicEventSimulationEngine implements SimulationEngine { event.getTime() + Math.max(0.001, deployConfig.getDeployDelay()), c)); } } - + // Handle event log.trace("Handling event " + event); switch (event.getType()) { @@ -391,7 +405,6 @@ public class BasicEventSimulationEngine implements SimulationEngine { //log.debug( " adding EJECTION_CHARGE event for motor "+motorState.getMotor().getDesignation()+" on stage "+stage.getStageNumber()+": "+stage.getName()); log.debug( " detected Motor Burnout for motor "+motorState.getMotor().getDesignation()+"@ "+event.getTime()+" on stage "+stage.getStageNumber()+": "+stage.getName()); - double delay = motorState.getEjectionDelay(); if ( motorState.hasEjectionCharge() ){ addEvent(new FlightEvent(FlightEvent.Type.EJECTION_CHARGE, currentStatus.getSimulationTime() + delay, @@ -478,15 +491,23 @@ public class BasicEventSimulationEngine implements SimulationEngine { currentStatus.getFlightData().setOptimumAltitude(coastStatus.getMaxAltitude()); currentStatus.getFlightData().setTimeToOptimumAltitude(coastStatus.getTimeToApogee()); } - - this.currentStepper = this.landingStepper; - this.currentStatus = currentStepper.initialize(currentStatus); + + // switch to landing stepper (unless we're already on the ground) + if (!currentStatus.isLanded()) { + currentStepper = landingStepper; + currentStatus = currentStepper.initialize(currentStatus); + } currentStatus.getFlightData().addEvent(event); } break; case GROUND_HIT: + currentStatus.setLanded(true); + + currentStepper = groundStepper; + currentStatus = currentStepper.initialize(currentStatus); + currentStatus.getFlightData().addEvent(event); break; @@ -500,8 +521,10 @@ public class BasicEventSimulationEngine implements SimulationEngine { break; case TUMBLE: - this.currentStepper = this.tumbleStepper; - this.currentStatus = currentStepper.initialize(currentStatus); + if (!currentStatus.isLanded()) { + currentStepper = tumbleStepper; + currentStatus = currentStepper.initialize(currentStatus); + } currentStatus.getFlightData().addEvent(event); break; } @@ -538,7 +561,7 @@ public class BasicEventSimulationEngine implements SimulationEngine { /** * Return the next flight event to handle, or null if no more events should be handled. - * This method jumps the simulation time forward in case no motors have been ignited. + * This method jumps the simulation time forward in case no motors have been ignited * The flight event is removed from the event queue. * * @return the flight event to handle, or null @@ -550,7 +573,8 @@ public class BasicEventSimulationEngine implements SimulationEngine { return null; // Jump to event if no motors have been ignited - if (!currentStatus.isMotorIgnited() && event.getTime() > currentStatus.getSimulationTime()) { + if (!currentStatus.isMotorIgnited() && + event.getTime() > currentStatus.getSimulationTime()) { currentStatus.setSimulationTime(event.getTime()); } if (event.getTime() <= currentStatus.getSimulationTime()) { diff --git a/core/src/net/sf/openrocket/simulation/GroundStepper.java b/core/src/net/sf/openrocket/simulation/GroundStepper.java new file mode 100644 index 000000000..28f965982 --- /dev/null +++ b/core/src/net/sf/openrocket/simulation/GroundStepper.java @@ -0,0 +1,85 @@ +package net.sf.openrocket.simulation; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.sf.openrocket.models.atmosphere.AtmosphericConditions; +import net.sf.openrocket.simulation.exception.SimulationException; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Coordinate; + +public class GroundStepper extends AbstractSimulationStepper { + private static final Logger log = LoggerFactory.getLogger(GroundStepper.class); + + @Override + public SimulationStatus initialize(SimulationStatus original) { + log.trace("initializing GroundStepper"); + SimulationStatus status = new SimulationStatus(original); + + return status; + } + + @Override + public void step(SimulationStatus status, double timeStep) throws SimulationException { + log.trace("step: position=" + status.getRocketPosition() + ", velocity=" + status.getRocketVelocity()); + + status.setRocketVelocity(Coordinate.ZERO); + status.setRocketRotationVelocity(Coordinate.ZERO); + status.setRocketPosition(status.getRocketPosition().setZ(0)); + + // Store data + FlightDataBranch data = status.getFlightData(); + boolean extra = status.getSimulationConditions().isCalculateExtras(); + data.addPoint(); + + data.setValue(FlightDataType.TYPE_TIME, status.getSimulationTime()); + data.setValue(FlightDataType.TYPE_ALTITUDE, status.getRocketPosition().z); + data.setValue(FlightDataType.TYPE_POSITION_X, status.getRocketPosition().x); + data.setValue(FlightDataType.TYPE_POSITION_Y, status.getRocketPosition().y); + if (extra) { + data.setValue(FlightDataType.TYPE_POSITION_XY, + MathUtil.hypot(status.getRocketPosition().x, status.getRocketPosition().y)); + data.setValue(FlightDataType.TYPE_POSITION_DIRECTION, + Math.atan2(status.getRocketPosition().y, status.getRocketPosition().x)); + + data.setValue(FlightDataType.TYPE_VELOCITY_XY, + MathUtil.hypot(status.getRocketVelocity().x, status.getRocketVelocity().y)); + data.setValue(FlightDataType.TYPE_ACCELERATION_XY, 0.0); + + data.setValue(FlightDataType.TYPE_ACCELERATION_TOTAL, 0.0); + + data.setValue(FlightDataType.TYPE_REYNOLDS_NUMBER, Double.POSITIVE_INFINITY); + } + + data.setValue(FlightDataType.TYPE_LATITUDE, status.getRocketWorldPosition().getLatitudeRad()); + data.setValue(FlightDataType.TYPE_LONGITUDE, status.getRocketWorldPosition().getLongitudeRad()); + data.setValue(FlightDataType.TYPE_GRAVITY, modelGravity(status)); + + data.setValue(FlightDataType.TYPE_CORIOLIS_ACCELERATION, 0.0); + + data.setValue(FlightDataType.TYPE_VELOCITY_Z, status.getRocketVelocity().z); + data.setValue(FlightDataType.TYPE_ACCELERATION_Z, 0.0); + + data.setValue(FlightDataType.TYPE_VELOCITY_TOTAL, 0.0); + data.setValue(FlightDataType.TYPE_MACH_NUMBER, 0.0); + + data.setValue(FlightDataType.TYPE_MASS, calculateStructureMass(status).getMass()); + data.setValue(FlightDataType.TYPE_PROPELLANT_MASS, 0.0); // Is this a reasonable assumption? Probably. + + data.setValue(FlightDataType.TYPE_THRUST_FORCE, 0.0); + data.setValue(FlightDataType.TYPE_DRAG_FORCE, 0.0); + + data.setValue(FlightDataType.TYPE_WIND_VELOCITY, modelWindVelocity(status).length()); + + AtmosphericConditions atmosphere = modelAtmosphericConditions(status); + data.setValue(FlightDataType.TYPE_AIR_TEMPERATURE, atmosphere.getTemperature()); + data.setValue(FlightDataType.TYPE_AIR_PRESSURE, atmosphere.getPressure()); + data.setValue(FlightDataType.TYPE_SPEED_OF_SOUND, atmosphere.getMachSpeed()); + + data.setValue(FlightDataType.TYPE_TIME_STEP, timeStep); + data.setValue(FlightDataType.TYPE_COMPUTATION_TIME, + (System.nanoTime() - status.getSimulationStartWallTime()) / 1000000000.0); + + status.setSimulationTime(status.getSimulationTime() + timeStep); + } +} diff --git a/core/src/net/sf/openrocket/simulation/SimulationStatus.java b/core/src/net/sf/openrocket/simulation/SimulationStatus.java index 1420b4166..7831ed4b3 100644 --- a/core/src/net/sf/openrocket/simulation/SimulationStatus.java +++ b/core/src/net/sf/openrocket/simulation/SimulationStatus.java @@ -70,6 +70,9 @@ public class SimulationStatus implements Monitorable { /** Set to true to indicate the rocket is tumbling. */ private boolean tumbling = false; + + /** Set to true to indicate rocket has landed */ + private boolean landed = false; /** Contains a list of deployed recovery devices. */ private MonitorableSet deployedRecoveryDevices = new MonitorableSet(); @@ -175,6 +178,7 @@ public class SimulationStatus implements Monitorable { this.launchRodCleared = orig.launchRodCleared; this.apogeeReached = orig.apogeeReached; this.tumbling = orig.tumbling; + this.landed = orig.landed; this.configuration.copyStages(orig.configuration); @@ -392,6 +396,15 @@ public class SimulationStatus implements Monitorable { public boolean isTumbling() { return tumbling; } + + public void setLanded(boolean landed) { + this.landed = landed; + this.modID++; + } + + public boolean isLanded() { + return landed; + } public double getMaxAlt() { return maxAlt;