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;