package net.sf.openrocket.simulation;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.motor.MotorId;
import net.sf.openrocket.motor.MotorInstanceConfiguration;
import net.sf.openrocket.rocketcomponent.Configuration;
import net.sf.openrocket.rocketcomponent.LaunchLug;
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.Monitorable;
import net.sf.openrocket.util.MonitorableSet;
import net.sf.openrocket.util.Quaternion;
import net.sf.openrocket.util.WorldCoordinate;
/**
* A holder class for the dynamic status during the rocket's flight.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class SimulationStatus implements Monitorable {
private SimulationConditions simulationConditions;
private Configuration configuration;
private MotorInstanceConfiguration motorConfiguration;
private FlightDataBranch flightData;
private double time;
private double previousTimeStep;
private Coordinate position;
private WorldCoordinate worldPosition;
private Coordinate velocity;
private Quaternion orientation;
private Coordinate rotationVelocity;
private double effectiveLaunchRodLength;
// Set of burnt out motors
Set<MotorId> motorBurntOut = new HashSet<MotorId>();
/** Nanosecond time when the simulation was started. */
private long simulationStartWallTime = Long.MIN_VALUE;
/** Set to true when a motor has ignited. */
private boolean motorIgnited = false;
/** Set to true when the rocket has risen from the ground. */
private boolean liftoff = false;
/** Set to true when the launch rod has been cleared. */
private boolean launchRodCleared = false;
/** Set to true when apogee has been detected. */
private boolean apogeeReached = false;
/** Set to true to indicate the rocket is tumbling. */
private boolean tumbling = false;
/** Contains a list of deployed recovery devices. */
private MonitorableSet<RecoveryDevice> deployedRecoveryDevices = new MonitorableSet<RecoveryDevice>();
/** The flight event queue */
private final EventQueue eventQueue = new EventQueue();
private WarningSet warnings;
/** Available for special purposes by the listeners. */
private final Map<String, Object> extraData = new HashMap<String, Object>();
double maxAlt = Double.NEGATIVE_INFINITY;
double maxAltTime = 0;
private int modID = 0;
private int modIDadd = 0;
public SimulationStatus(Configuration configuration,
MotorInstanceConfiguration motorConfiguration,
SimulationConditions simulationConditions) {
this.simulationConditions = simulationConditions;
this.configuration = configuration;
this.motorConfiguration = motorConfiguration;
this.time = 0;
this.previousTimeStep = this.simulationConditions.getTimeStep();
this.position = this.simulationConditions.getLaunchPosition();
this.velocity = this.simulationConditions.getLaunchVelocity();
this.worldPosition = this.simulationConditions.getLaunchSite();
// Initialize to roll angle with least stability w.r.t. the wind
Quaternion o;
FlightConditions cond = new FlightConditions(this.configuration);
this.simulationConditions.getAerodynamicCalculator().getWorstCP(this.configuration, cond, null);
double angle = -cond.getTheta() - (Math.PI / 2.0 - this.simulationConditions.getLaunchRodDirection());
o = Quaternion.rotation(new Coordinate(0, 0, angle));
// Launch rod angle and direction
o = o.multiplyLeft(Quaternion.rotation(new Coordinate(0, this.simulationConditions.getLaunchRodAngle(), 0)));
o = o.multiplyLeft(Quaternion.rotation(new Coordinate(0, 0, Math.PI / 2.0 - this.simulationConditions.getLaunchRodDirection())));
this.orientation = o;
this.rotationVelocity = Coordinate.NUL;
/*
* Calculate the effective launch rod length taking into account launch lugs.
* If no lugs are found, assume a tower launcher of full length.
*/
double length = this.simulationConditions.getLaunchRodLength();
double lugPosition = Double.NaN;
for (RocketComponent c : this.configuration) {
if (c instanceof LaunchLug) {
double pos = c.toAbsolute(new Coordinate(c.getLength()))[0].x;
if (Double.isNaN(lugPosition) || pos > lugPosition) {
lugPosition = pos;
}
}
}
if (!Double.isNaN(lugPosition)) {
double maxX = 0;
for (Coordinate c : this.configuration.getBounds()) {
if (c.x > maxX)
maxX = c.x;
}
if (maxX >= lugPosition) {
length = Math.max(0, length - (maxX - lugPosition));
}
}
this.effectiveLaunchRodLength = length;
this.simulationStartWallTime = System.nanoTime();
this.motorIgnited = false;
this.liftoff = false;
this.launchRodCleared = false;
this.apogeeReached = false;
this.warnings = new WarningSet();
}
/**
* Performs a deep copy of the on SimulationStatus object.
* Most included object are deep-cloned, except for the flight data object (which is shallow copied)
* and the WarningSet (which is initialized to a new WarningSet).
* The intention of this constructor is to be used for conversion from one type
* of SimulationStatus to another, or when simulating multiple stages.
* When used for simulating multiple stages, a new FlightDataBranch object
* needs to be associated with the new object.
*
* @param orig the object from which to copy
*/
public SimulationStatus(SimulationStatus orig) {
this.simulationConditions = orig.simulationConditions.clone();
this.configuration = orig.configuration.clone();
this.motorConfiguration = orig.motorConfiguration.clone();
// FlightData is not cloned.
this.flightData = orig.flightData;
this.time = orig.time;
this.previousTimeStep = orig.previousTimeStep;
this.position = orig.position;
this.worldPosition = orig.worldPosition;
this.velocity = orig.velocity;
this.orientation = orig.orientation;
this.rotationVelocity = orig.rotationVelocity;
this.effectiveLaunchRodLength = orig.effectiveLaunchRodLength;
this.simulationStartWallTime = orig.simulationStartWallTime;
this.motorIgnited = orig.motorIgnited;
this.liftoff = orig.liftoff;
this.launchRodCleared = orig.launchRodCleared;
this.apogeeReached = orig.apogeeReached;
this.tumbling = orig.tumbling;
this.motorBurntOut = orig.motorBurntOut;
this.deployedRecoveryDevices.clear();
this.deployedRecoveryDevices.addAll(orig.deployedRecoveryDevices);
this.eventQueue.clear();
this.eventQueue.addAll(orig.eventQueue);
// WarningSet is not cloned.
this.warnings = new WarningSet();
this.extraData.clear();
this.extraData.putAll(orig.extraData);
this.modID = orig.modID;
this.modIDadd = orig.modIDadd;
}
public void setSimulationTime(double time) {
this.time = time;
this.modID++;
}
public double getSimulationTime() {
return time;
}
public void setConfiguration(Configuration configuration) {
if (this.configuration != null)
this.modIDadd += this.configuration.getModID();
this.modID++;
this.configuration = configuration;
}
public Configuration getConfiguration() {
return configuration;
}
public void setMotorConfiguration(MotorInstanceConfiguration motorConfiguration) {
if (this.motorConfiguration != null)
this.modIDadd += this.motorConfiguration.getModID();
this.modID++;
this.motorConfiguration = motorConfiguration;
}
public MotorInstanceConfiguration getMotorConfiguration() {
return motorConfiguration;
}
public void setFlightData(FlightDataBranch flightData) {
if (this.flightData != null)
this.modIDadd += this.flightData.getModID();
this.modID++;
this.flightData = flightData;
}
public FlightDataBranch getFlightData() {
return flightData;
}
public double getPreviousTimeStep() {
return previousTimeStep;
}
public void setPreviousTimeStep(double previousTimeStep) {
this.previousTimeStep = previousTimeStep;
this.modID++;
}
public void setRocketPosition(Coordinate position) {
this.position = position;
this.modID++;
}
public Coordinate getRocketPosition() {
return position;
}
public void setRocketWorldPosition(WorldCoordinate wc) {
this.worldPosition = wc;
this.modID++;
}
public WorldCoordinate getRocketWorldPosition() {
return worldPosition;
}
public void setRocketVelocity(Coordinate velocity) {
this.velocity = velocity;
this.modID++;
}
public Coordinate getRocketVelocity() {
return velocity;
}
public boolean addBurntOutMotor(MotorId motor) {
return motorBurntOut.add(motor);
}
public Quaternion getRocketOrientationQuaternion() {
return orientation;
}
public void setRocketOrientationQuaternion(Quaternion orientation) {
this.orientation = orientation;
this.modID++;
}
public Coordinate getRocketRotationVelocity() {
return rotationVelocity;
}
public void setRocketRotationVelocity(Coordinate rotation) {
this.rotationVelocity = rotation;
}
public void setEffectiveLaunchRodLength(double effectiveLaunchRodLength) {
this.effectiveLaunchRodLength = effectiveLaunchRodLength;
this.modID++;
}
public double getEffectiveLaunchRodLength() {
return effectiveLaunchRodLength;
}
public void setSimulationStartWallTime(long simulationStartWallTime) {
this.simulationStartWallTime = simulationStartWallTime;
this.modID++;
}
public long getSimulationStartWallTime() {
return simulationStartWallTime;
}
public void setMotorIgnited(boolean motorIgnited) {
this.motorIgnited = motorIgnited;
this.modID++;
}
public boolean isMotorIgnited() {
return motorIgnited;
}
public void setLiftoff(boolean liftoff) {
this.liftoff = liftoff;
this.modID++;
}
public boolean isLiftoff() {
return liftoff;
}
public void setLaunchRodCleared(boolean launchRod) {
this.launchRodCleared = launchRod;
this.modID++;
}
public boolean isLaunchRodCleared() {
return launchRodCleared;
}
public void setApogeeReached(boolean apogeeReached) {
this.apogeeReached = apogeeReached;
this.modID++;
}
public boolean isApogeeReached() {
return apogeeReached;
}
public void setTumbling(boolean tumbling) {
this.tumbling = tumbling;
this.modID++;
}
public boolean isTumbling() {
return tumbling;
}
public double getMaxAlt() {
return maxAlt;
}
public void setMaxAlt(double maxAlt) {
this.maxAlt = maxAlt;
this.modID++;
}
public double getMaxAltTime() {
return maxAltTime;
}
public void setMaxAltTime(double maxAltTime) {
this.maxAltTime = maxAltTime;
this.modID++;
}
public Set<RecoveryDevice> getDeployedRecoveryDevices() {
return deployedRecoveryDevices;
}
public void setWarnings(WarningSet warnings) {
if (this.warnings != null)
this.modIDadd += this.warnings.getModID();
this.modID++;
this.warnings = warnings;
}
public WarningSet getWarnings() {
return warnings;
}
public EventQueue getEventQueue() {
return eventQueue;
}
public void setSimulationConditions(SimulationConditions simulationConditions) {
if (this.simulationConditions != null)
this.modIDadd += this.simulationConditions.getModID();
this.modID++;
this.simulationConditions = simulationConditions;
}
public SimulationConditions getSimulationConditions() {
return simulationConditions;
}
/**
* Store extra data available for use by simulation listeners. The data can be retrieved
* using {@link #getExtraData(String)}.
*
* @param key the data key
* @param value the value to store
*/
public void putExtraData(String key, Object value) {
extraData.put(key, value);
}
/**
* Retrieve extra data stored by simulation listeners. This data map is initially empty.
* Data can be stored using {@link #putExtraData(String, Object)}.
*
* @param key the data key to retrieve
* @return the data, or <code>null</code> if nothing has been set for the key
*/
public Object getExtraData(String key) {
return extraData.get(key);
}
/**
* Returns a copy of this object. The general purpose is that the conditions,
* rocket configuration, flight data etc. point to the same objects. However,
* subclasses are allowed to deep-clone specific objects, such as those pertaining
* to the current orientation of the rocket. The purpose is to allow creating intermediate
* copies of this object used during step computation.
*
*/
@Override
public SimulationStatus clone() {
try {
SimulationStatus clone = (SimulationStatus) super.clone();
return clone;
} catch (CloneNotSupportedException e) {
throw new BugException("CloneNotSupportedException?!?", e);
}
}
@Override
public int getModID() {
return (modID + modIDadd + simulationConditions.getModID() + configuration.getModID() +
motorConfiguration.getModID() + flightData.getModID() + deployedRecoveryDevices.getModID() +
eventQueue.getModID() + warnings.getModID());
}
}