Cleanup configuration as component change listener.

This commit is contained in:
Doug Pedrick 2012-09-25 03:24:22 +00:00
parent 9ce96bdc05
commit d48a2cb400
4 changed files with 287 additions and 282 deletions

View File

@ -34,12 +34,12 @@ import net.sf.openrocket.util.StateChangeListener;
* <p> * <p>
* This class is not thread-safe and enforces single-threaded access with a * This class is not thread-safe and enforces single-threaded access with a
* SafetyMutex. * SafetyMutex.
* *
* @author Sampo Niskanen <sampo.niskanen@iki.fi> * @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/ */
public class Simulation implements ChangeSource, Cloneable { public class Simulation implements ChangeSource, Cloneable {
private static final LogHelper log = Application.getLogger(); private static final LogHelper log = Application.getLogger();
public static enum Status { public static enum Status {
/** Up-to-date */ /** Up-to-date */
UPTODATE, UPTODATE,
@ -56,21 +56,21 @@ public class Simulation implements ChangeSource, Cloneable {
/** Not yet simulated */ /** Not yet simulated */
NOT_SIMULATED NOT_SIMULATED
} }
private SafetyMutex mutex = SafetyMutex.newInstance(); private SafetyMutex mutex = SafetyMutex.newInstance();
private final Rocket rocket; private final Rocket rocket;
private String name = ""; private String name = "";
private Status status = Status.NOT_SIMULATED; private Status status = Status.NOT_SIMULATED;
/** The conditions to use */ /** The conditions to use */
// TODO: HIGH: Change to use actual conditions class?? // TODO: HIGH: Change to use actual conditions class??
private SimulationOptions options; private SimulationOptions options;
private ArrayList<String> simulationListeners = new ArrayList<String>(); private ArrayList<String> simulationListeners = new ArrayList<String>();
private final Class<? extends SimulationEngine> simulationEngineClass = BasicEventSimulationEngine.class; private final Class<? extends SimulationEngine> simulationEngineClass = BasicEventSimulationEngine.class;
private Class<? extends SimulationStepper> simulationStepperClass = RK4SimulationStepper.class; private Class<? extends SimulationStepper> simulationStepperClass = RK4SimulationStepper.class;
private Class<? extends AerodynamicCalculator> aerodynamicCalculatorClass = BarrowmanCalculator.class; private Class<? extends AerodynamicCalculator> aerodynamicCalculatorClass = BarrowmanCalculator.class;
@ -78,35 +78,35 @@ public class Simulation implements ChangeSource, Cloneable {
/** Listeners for this object */ /** Listeners for this object */
private List<EventListener> listeners = new ArrayList<EventListener>(); private List<EventListener> listeners = new ArrayList<EventListener>();
/** The conditions actually used in the previous simulation, or null */ /** The conditions actually used in the previous simulation, or null */
private SimulationOptions simulatedConditions = null; private SimulationOptions simulatedConditions = null;
private String simulatedMotors = null; private String simulatedMotors = null;
private FlightData simulatedData = null; private FlightData simulatedData = null;
private int simulatedRocketID = -1; private int simulatedRocketID = -1;
/** /**
* Create a new simulation for the rocket. Parent document should also be provided. * Create a new simulation for the rocket. Parent document should also be provided.
* The initial motor configuration is taken from the default rocket configuration. * The initial motor configuration is taken from the default rocket configuration.
* *
* @param rocket the rocket associated with the simulation. * @param rocket the rocket associated with the simulation.
*/ */
public Simulation(Rocket rocket) { public Simulation(Rocket rocket) {
this.rocket = rocket; this.rocket = rocket;
this.status = Status.NOT_SIMULATED; this.status = Status.NOT_SIMULATED;
options = new SimulationOptions(rocket); options = new SimulationOptions(rocket);
options.setMotorConfigurationID( options.setMotorConfigurationID(
rocket.getDefaultConfiguration().getMotorConfigurationID()); rocket.getDefaultConfiguration().getMotorConfigurationID());
options.addChangeListener(new ConditionListener()); options.addChangeListener(new ConditionListener());
} }
public Simulation(Rocket rocket, Status status, String name, SimulationOptions options, public Simulation(Rocket rocket, Status status, String name, SimulationOptions options,
List<String> listeners, FlightData data) { List<String> listeners, FlightData data) {
if (rocket == null) if (rocket == null)
throw new IllegalArgumentException("rocket cannot be null"); throw new IllegalArgumentException("rocket cannot be null");
if (status == null) if (status == null)
@ -115,9 +115,9 @@ public class Simulation implements ChangeSource, Cloneable {
throw new IllegalArgumentException("name cannot be null"); throw new IllegalArgumentException("name cannot be null");
if (options == null) if (options == null)
throw new IllegalArgumentException("options cannot be null"); throw new IllegalArgumentException("options cannot be null");
this.rocket = rocket; this.rocket = rocket;
if (status == Status.UPTODATE) { if (status == Status.UPTODATE) {
this.status = Status.LOADED; this.status = Status.LOADED;
} else if (data == null) { } else if (data == null) {
@ -125,16 +125,16 @@ public class Simulation implements ChangeSource, Cloneable {
} else { } else {
this.status = status; this.status = status;
} }
this.name = name; this.name = name;
this.options = options; this.options = options;
options.addChangeListener(new ConditionListener()); options.addChangeListener(new ConditionListener());
if (listeners != null) { if (listeners != null) {
this.simulationListeners.addAll(listeners); this.simulationListeners.addAll(listeners);
} }
if (data != null && this.status != Status.NOT_SIMULATED) { if (data != null && this.status != Status.NOT_SIMULATED) {
simulatedData = data; simulatedData = data;
@ -143,24 +143,24 @@ public class Simulation implements ChangeSource, Cloneable {
simulatedRocketID = rocket.getModID(); simulatedRocketID = rocket.getModID();
} }
} }
} }
/** /**
* Return the rocket associated with this simulation. * Return the rocket associated with this simulation.
* *
* @return the rocket. * @return the rocket.
*/ */
public Rocket getRocket() { public Rocket getRocket() {
mutex.verify(); mutex.verify();
return rocket; return rocket;
} }
/** /**
* Return a newly created Configuration for this simulation. The configuration * Return a newly created Configuration for this simulation. The configuration
* has the motor ID set and all stages active. * has the motor ID set and all stages active.
* *
* @return a newly created Configuration of the launch conditions. * @return a newly created Configuration of the launch conditions.
*/ */
public Configuration getConfiguration() { public Configuration getConfiguration() {
@ -170,46 +170,46 @@ public class Simulation implements ChangeSource, Cloneable {
c.setAllStages(); c.setAllStages();
return c; return c;
} }
/** /**
* Returns the simulation options attached to this simulation. The options * Returns the simulation options attached to this simulation. The options
* may be modified freely, and the status of the simulation will change to reflect * may be modified freely, and the status of the simulation will change to reflect
* the changes. * the changes.
* *
* @return the simulation conditions. * @return the simulation conditions.
*/ */
public SimulationOptions getOptions() { public SimulationOptions getOptions() {
mutex.verify(); mutex.verify();
return options; return options;
} }
/** /**
* Get the list of simulation listeners. The returned list is the one used by * Get the list of simulation listeners. The returned list is the one used by
* this object; changes to it will reflect changes in the simulation. * this object; changes to it will reflect changes in the simulation.
* *
* @return the actual list of simulation listeners. * @return the actual list of simulation listeners.
*/ */
public List<String> getSimulationListeners() { public List<String> getSimulationListeners() {
mutex.verify(); mutex.verify();
return simulationListeners; return simulationListeners;
} }
/** /**
* Return the user-defined name of the simulation. * Return the user-defined name of the simulation.
* *
* @return the name for the simulation. * @return the name for the simulation.
*/ */
public String getName() { public String getName() {
mutex.verify(); mutex.verify();
return name; return name;
} }
/** /**
* Set the user-defined name of the simulation. Setting the name to * Set the user-defined name of the simulation. Setting the name to
* null yields an empty name. * null yields an empty name.
* *
* @param name the name of the simulation. * @param name the name of the simulation.
*/ */
public void setName(String name) { public void setName(String name) {
@ -217,43 +217,43 @@ public class Simulation implements ChangeSource, Cloneable {
try { try {
if (this.name.equals(name)) if (this.name.equals(name))
return; return;
if (name == null) if (name == null)
this.name = ""; this.name = "";
else else
this.name = name; this.name = name;
fireChangeEvent(); fireChangeEvent();
} finally { } finally {
mutex.unlock("setName"); mutex.unlock("setName");
} }
} }
/** /**
* Returns the status of this simulation. This method examines whether the * Returns the status of this simulation. This method examines whether the
* simulation has been outdated and returns {@link Status#OUTDATED} accordingly. * simulation has been outdated and returns {@link Status#OUTDATED} accordingly.
* *
* @return the status * @return the status
* @see Status * @see Status
*/ */
public Status getStatus() { public Status getStatus() {
mutex.verify(); mutex.verify();
if (status == Status.UPTODATE || status == Status.LOADED) { if (status == Status.UPTODATE || status == Status.LOADED) {
if (rocket.getFunctionalModID() != simulatedRocketID || if (rocket.getFunctionalModID() != simulatedRocketID ||
!options.equals(simulatedConditions)) !options.equals(simulatedConditions))
return Status.OUTDATED; return Status.OUTDATED;
} }
return status; return status;
} }
/** /**
* Simulate the flight. * Simulate the flight.
* *
* @param additionalListeners additional simulation listeners (those defined by the simulation are used in any case) * @param additionalListeners additional simulation listeners (those defined by the simulation are used in any case)
* @throws SimulationException if a problem occurs during simulation * @throws SimulationException if a problem occurs during simulation
*/ */
@ -261,13 +261,13 @@ public class Simulation implements ChangeSource, Cloneable {
throws SimulationException { throws SimulationException {
mutex.lock("simulate"); mutex.lock("simulate");
try { try {
if (this.status == Status.EXTERNAL) { if (this.status == Status.EXTERNAL) {
throw new SimulationException("Cannot simulate imported simulation."); throw new SimulationException("Cannot simulate imported simulation.");
} }
SimulationEngine simulator; SimulationEngine simulator;
try { try {
simulator = simulationEngineClass.newInstance(); simulator = simulationEngineClass.newInstance();
} catch (InstantiationException e) { } catch (InstantiationException e) {
@ -275,13 +275,13 @@ public class Simulation implements ChangeSource, Cloneable {
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
throw new IllegalStateException("Cannot access simulator instance?! BUG!", e); throw new IllegalStateException("Cannot access simulator instance?! BUG!", e);
} }
SimulationConditions simulationConditions = options.toSimulationConditions(); SimulationConditions simulationConditions = options.toSimulationConditions();
simulationConditions.setSimulation(this); simulationConditions.setSimulation(this);
for (SimulationListener l : additionalListeners) { for (SimulationListener l : additionalListeners) {
simulationConditions.getSimulationListenerList().add(l); simulationConditions.getSimulationListenerList().add(l);
} }
for (String className : simulationListeners) { for (String className : simulationListeners) {
SimulationListener l = null; SimulationListener l = null;
try { try {
@ -293,43 +293,45 @@ public class Simulation implements ChangeSource, Cloneable {
} }
simulationConditions.getSimulationListenerList().add(l); simulationConditions.getSimulationListenerList().add(l);
} }
long t1, t2; long t1, t2;
log.debug("Simulation: calling simulator"); log.debug("Simulation: calling simulator");
t1 = System.currentTimeMillis(); t1 = System.currentTimeMillis();
simulatedData = simulator.simulate(simulationConditions); simulatedData = simulator.simulate(simulationConditions);
t2 = System.currentTimeMillis(); t2 = System.currentTimeMillis();
log.debug("Simulation: returning from simulator, simulation took " + (t2 - t1) + "ms"); log.debug("Simulation: returning from simulator, simulation took " + (t2 - t1) + "ms");
// Set simulated info after simulation, will not be set in case of exception // Set simulated info after simulation, will not be set in case of exception
simulatedConditions = options.clone(); simulatedConditions = options.clone();
simulatedMotors = getConfiguration().getMotorConfigurationDescription(); final Configuration configuration = getConfiguration();
simulatedMotors = configuration.getMotorConfigurationDescription();
simulatedRocketID = rocket.getFunctionalModID(); simulatedRocketID = rocket.getFunctionalModID();
status = Status.UPTODATE; status = Status.UPTODATE;
fireChangeEvent(); fireChangeEvent();
configuration.release();
} finally { } finally {
mutex.unlock("simulate"); mutex.unlock("simulate");
} }
} }
/** /**
* Return the conditions used in the previous simulation, or <code>null</code> * Return the conditions used in the previous simulation, or <code>null</code>
* if this simulation has not been run. * if this simulation has not been run.
* *
* @return the conditions used in the previous simulation, or <code>null</code>. * @return the conditions used in the previous simulation, or <code>null</code>.
*/ */
public SimulationOptions getSimulatedConditions() { public SimulationOptions getSimulatedConditions() {
mutex.verify(); mutex.verify();
return simulatedConditions; return simulatedConditions;
} }
/** /**
* Return the warnings generated in the previous simulation, or * Return the warnings generated in the previous simulation, or
* <code>null</code> if this simulation has not been run. This is the same * <code>null</code> if this simulation has not been run. This is the same
* warning set as contained in the <code>FlightData</code> object. * warning set as contained in the <code>FlightData</code> object.
* *
* @return the warnings during the previous simulation, or <code>null</code>. * @return the warnings during the previous simulation, or <code>null</code>.
* @see FlightData#getWarningSet() * @see FlightData#getWarningSet()
*/ */
@ -339,12 +341,12 @@ public class Simulation implements ChangeSource, Cloneable {
return null; return null;
return simulatedData.getWarningSet(); return simulatedData.getWarningSet();
} }
/** /**
* Return a string describing the motor configuration of the previous simulation, * Return a string describing the motor configuration of the previous simulation,
* or <code>null</code> if this simulation has not been run. * or <code>null</code> if this simulation has not been run.
* *
* @return a description of the motor configuration of the previous simulation, or * @return a description of the motor configuration of the previous simulation, or
* <code>null</code>. * <code>null</code>.
* @see Rocket#getMotorConfigurationNameOrDescription(String) * @see Rocket#getMotorConfigurationNameOrDescription(String)
@ -353,33 +355,33 @@ public class Simulation implements ChangeSource, Cloneable {
mutex.verify(); mutex.verify();
return simulatedMotors; return simulatedMotors;
} }
/** /**
* Return the flight data of the previous simulation, or <code>null</code> if * Return the flight data of the previous simulation, or <code>null</code> if
* this simulation has not been run. * this simulation has not been run.
* *
* @return the flight data of the previous simulation, or <code>null</code>. * @return the flight data of the previous simulation, or <code>null</code>.
*/ */
public FlightData getSimulatedData() { public FlightData getSimulatedData() {
mutex.verify(); mutex.verify();
return simulatedData; return simulatedData;
} }
/** /**
* Returns a copy of this simulation suitable for cut/copy/paste operations. * Returns a copy of this simulation suitable for cut/copy/paste operations.
* The rocket refers to the same instance as the original simulation. * The rocket refers to the same instance as the original simulation.
* This excludes any simulated data. * This excludes any simulated data.
* *
* @return a copy of this simulation and its conditions. * @return a copy of this simulation and its conditions.
*/ */
public Simulation copy() { public Simulation copy() {
mutex.lock("copy"); mutex.lock("copy");
try { try {
Simulation copy = (Simulation) super.clone(); Simulation copy = (Simulation) super.clone();
copy.mutex = SafetyMutex.newInstance(); copy.mutex = SafetyMutex.newInstance();
copy.status = Status.NOT_SIMULATED; copy.status = Status.NOT_SIMULATED;
copy.options = this.options.clone(); copy.options = this.options.clone();
@ -389,21 +391,21 @@ public class Simulation implements ChangeSource, Cloneable {
copy.simulatedMotors = null; copy.simulatedMotors = null;
copy.simulatedData = null; copy.simulatedData = null;
copy.simulatedRocketID = -1; copy.simulatedRocketID = -1;
return copy; return copy;
} catch (CloneNotSupportedException e) { } catch (CloneNotSupportedException e) {
throw new BugException("Clone not supported, BUG", e); throw new BugException("Clone not supported, BUG", e);
} finally { } finally {
mutex.unlock("copy"); mutex.unlock("copy");
} }
} }
/** /**
* Create a duplicate of this simulation with the specified rocket. The new * Create a duplicate of this simulation with the specified rocket. The new
* simulation is in non-simulated state. * simulation is in non-simulated state.
* *
* @param newRocket the rocket for the new simulation. * @param newRocket the rocket for the new simulation.
* @return a new simulation with the same conditions and properties. * @return a new simulation with the same conditions and properties.
*/ */
@ -411,33 +413,33 @@ public class Simulation implements ChangeSource, Cloneable {
mutex.lock("duplicateSimulation"); mutex.lock("duplicateSimulation");
try { try {
Simulation copy = new Simulation(newRocket); Simulation copy = new Simulation(newRocket);
copy.name = this.name; copy.name = this.name;
copy.options.copyFrom(this.options); copy.options.copyFrom(this.options);
copy.simulationListeners = this.simulationListeners.clone(); copy.simulationListeners = this.simulationListeners.clone();
copy.simulationStepperClass = this.simulationStepperClass; copy.simulationStepperClass = this.simulationStepperClass;
copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass; copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
return copy; return copy;
} finally { } finally {
mutex.unlock("duplicateSimulation"); mutex.unlock("duplicateSimulation");
} }
} }
@Override @Override
public void addChangeListener(EventListener listener) { public void addChangeListener(EventListener listener) {
mutex.verify(); mutex.verify();
listeners.add(listener); listeners.add(listener);
} }
@Override @Override
public void removeChangeListener(EventListener listener) { public void removeChangeListener(EventListener listener) {
mutex.verify(); mutex.verify();
listeners.remove(listener); listeners.remove(listener);
} }
protected void fireChangeEvent() { protected void fireChangeEvent() {
EventObject e = new EventObject(this); EventObject e = new EventObject(this);
// Copy the list before iterating to prevent concurrent modification exceptions. // Copy the list before iterating to prevent concurrent modification exceptions.
@ -448,14 +450,14 @@ public class Simulation implements ChangeSource, Cloneable {
} }
} }
} }
private class ConditionListener implements StateChangeListener { private class ConditionListener implements StateChangeListener {
private Status oldStatus = null; private Status oldStatus = null;
@Override @Override
public void stateChanged(EventObject e) { public void stateChanged(EventObject e) {
if (getStatus() != oldStatus) { if (getStatus() != oldStatus) {

View File

@ -94,8 +94,10 @@ public class RocksimSaver extends RocketSaver {
MassCalculator massCalc = new BasicMassCalculator(); MassCalculator massCalc = new BasicMassCalculator();
final double cg = massCalc.getCG(new Configuration(rocket), MassCalculator.MassCalcType.NO_MOTORS).x * final Configuration configuration = new Configuration(rocket);
final double cg = massCalc.getCG(configuration, MassCalculator.MassCalcType.NO_MOTORS).x *
RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH; RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH;
configuration.release();
int stageCount = rocket.getStageCount(); int stageCount = rocket.getStageCount();
if (stageCount == 3) { if (stageCount == 3) {
result.setStage321CG(cg); result.setStage321CG(cg);

View File

@ -74,33 +74,33 @@ import com.itextpdf.text.pdf.PdfWriter;
* </pre> * </pre>
*/ */
public class DesignReport { public class DesignReport {
/** /**
* The logger. * The logger.
*/ */
private static final LogHelper log = Application.getLogger(); private static final LogHelper log = Application.getLogger();
public static final double SCALE_FUDGE_FACTOR = 0.4d; public static final double SCALE_FUDGE_FACTOR = 0.4d;
/** /**
* The OR Document. * The OR Document.
*/ */
private OpenRocketDocument rocketDocument; private OpenRocketDocument rocketDocument;
/** /**
* A panel used for rendering of the design diagram. * A panel used for rendering of the design diagram.
*/ */
final RocketPanel panel; final RocketPanel panel;
/** /**
* The iText document. * The iText document.
*/ */
protected Document document; protected Document document;
/** /**
* The figure rotation. * The figure rotation.
*/ */
private double rotation = 0d; private double rotation = 0d;
/** The displayed strings. */ /** The displayed strings. */
private static final String STAGES = "Stages: "; private static final String STAGES = "Stages: ";
private static final String MASS_WITH_MOTORS = "Mass (with motors): "; private static final String MASS_WITH_MOTORS = "Mass (with motors): ";
@ -126,7 +126,7 @@ public class DesignReport {
private static final String LANDING_VELOCITY = "Landing Velocity"; private static final String LANDING_VELOCITY = "Landing Velocity";
private static final String ROCKET_DESIGN = "Rocket Design"; private static final String ROCKET_DESIGN = "Rocket Design";
private static final double GRAVITY_CONSTANT = 9.80665d; private static final double GRAVITY_CONSTANT = 9.80665d;
/** /**
* Constructor. * Constructor.
* *
@ -140,7 +140,7 @@ public class DesignReport {
panel = new RocketPanel(rocketDocument); panel = new RocketPanel(rocketDocument);
rotation = figureRotation; rotation = figureRotation;
} }
/** /**
* Main entry point. Prints the rocket drawing and design data. * Main entry point. Prints the rocket drawing and design data.
* *
@ -153,23 +153,23 @@ public class DesignReport {
com.itextpdf.text.Rectangle pageSize = document.getPageSize(); com.itextpdf.text.Rectangle pageSize = document.getPageSize();
int pageImageableWidth = (int) pageSize.getWidth() - (int) pageSize.getBorderWidth() * 2; int pageImageableWidth = (int) pageSize.getWidth() - (int) pageSize.getBorderWidth() * 2;
int pageImageableHeight = (int) pageSize.getHeight() / 2 - (int) pageSize.getBorderWidthTop(); int pageImageableHeight = (int) pageSize.getHeight() / 2 - (int) pageSize.getBorderWidthTop();
PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, ROCKET_DESIGN); PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, ROCKET_DESIGN);
Rocket rocket = rocketDocument.getRocket(); Rocket rocket = rocketDocument.getRocket();
final Configuration configuration = rocket.getDefaultConfiguration().clone(); final Configuration configuration = rocket.getDefaultConfiguration().clone();
configuration.setAllStages(); configuration.setAllStages();
PdfContentByte canvas = writer.getDirectContent(); PdfContentByte canvas = writer.getDirectContent();
final PrintFigure figure = new PrintFigure(configuration); final PrintFigure figure = new PrintFigure(configuration);
figure.setRotation(rotation); figure.setRotation(rotation);
FigureElement cp = panel.getExtraCP(); FigureElement cp = panel.getExtraCP();
FigureElement cg = panel.getExtraCG(); FigureElement cg = panel.getExtraCG();
RocketInfo text = panel.getExtraText(); RocketInfo text = panel.getExtraText();
double scale = paintRocketDiagram(pageImageableWidth, pageImageableHeight, canvas, figure, cp, cg); double scale = paintRocketDiagram(pageImageableWidth, pageImageableHeight, canvas, figure, cp, cg);
canvas.beginText(); canvas.beginText();
canvas.setFontAndSize(ITextHelper.getBaseFont(), PrintUtilities.NORMAL_FONT_SIZE); canvas.setFontAndSize(ITextHelper.getBaseFont(), PrintUtilities.NORMAL_FONT_SIZE);
int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS
@ -177,15 +177,15 @@ public class DesignReport {
final int diagramHeight = pageImageableHeight * 2 - 70 - (figHeightPts); final int diagramHeight = pageImageableHeight * 2 - 70 - (figHeightPts);
canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight); canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight);
canvas.moveTextWithLeading(0, -16); canvas.moveTextWithLeading(0, -16);
float initialY = canvas.getYTLM(); float initialY = canvas.getYTLM();
canvas.showText(rocketDocument.getRocket().getName()); canvas.showText(rocketDocument.getRocket().getName());
canvas.newlineShowText(STAGES); canvas.newlineShowText(STAGES);
canvas.showText("" + rocket.getStageCount()); canvas.showText("" + rocket.getStageCount());
if (configuration.hasMotors()) { if (configuration.hasMotors()) {
if (configuration.getStageCount() > 1) { if (configuration.getStageCount() > 1) {
canvas.newlineShowText(MASS_WITH_MOTORS); canvas.newlineShowText(MASS_WITH_MOTORS);
@ -196,29 +196,29 @@ public class DesignReport {
canvas.newlineShowText(MASS_EMPTY); canvas.newlineShowText(MASS_EMPTY);
} }
canvas.showText(text.getMass(UnitGroup.UNITS_MASS.getDefaultUnit())); canvas.showText(text.getMass(UnitGroup.UNITS_MASS.getDefaultUnit()));
canvas.newlineShowText(STABILITY); canvas.newlineShowText(STABILITY);
canvas.showText(text.getStability()); canvas.showText(text.getStability());
canvas.newlineShowText(CG); canvas.newlineShowText(CG);
canvas.showText(text.getCg()); canvas.showText(text.getCg());
canvas.newlineShowText(CP); canvas.newlineShowText(CP);
canvas.showText(text.getCp()); canvas.showText(text.getCp());
canvas.endText(); canvas.endText();
try { try {
//Move the internal pointer of the document below that of what was just written using the direct byte buffer. //Move the internal pointer of the document below that of what was just written using the direct byte buffer.
Paragraph paragraph = new Paragraph(); Paragraph paragraph = new Paragraph();
float finalY = canvas.getYTLM(); float finalY = canvas.getYTLM();
int heightOfDiagramAndText = (int) (pageSize.getHeight() - (finalY - initialY + diagramHeight)); int heightOfDiagramAndText = (int) (pageSize.getHeight() - (finalY - initialY + diagramHeight));
paragraph.setSpacingAfter(heightOfDiagramAndText); paragraph.setSpacingAfter(heightOfDiagramAndText);
document.add(paragraph); document.add(paragraph);
String[] motorIds = rocket.getMotorConfigurationIDs(); String[] motorIds = rocket.getMotorConfigurationIDs();
List<Simulation> simulations = rocketDocument.getSimulations(); List<Simulation> simulations = rocketDocument.getSimulations();
for (int j = 0; j < motorIds.length; j++) { for (int j = 0; j < motorIds.length; j++) {
String motorId = motorIds[j]; String motorId = motorIds[j];
if (motorId != null) { if (motorId != null) {
@ -242,8 +242,8 @@ public class DesignReport {
log.error("Could not modify document.", e); log.error("Could not modify document.", e);
} }
} }
/** /**
* Paint a diagram of the rocket into the PDF document. * Paint a diagram of the rocket into the PDF document.
* *
@ -264,7 +264,7 @@ public class DesignReport {
theFigure.addRelativeExtra(theCp); theFigure.addRelativeExtra(theCp);
theFigure.addRelativeExtra(theCg); theFigure.addRelativeExtra(theCg);
theFigure.updateFigure(); theFigure.updateFigure();
double scale = double scale =
(thePageImageableWidth * 2.2) / theFigure.getFigureWidth(); (thePageImageableWidth * 2.2) / theFigure.getFigureWidth();
theFigure.setScale(scale); theFigure.setScale(scale);
@ -273,7 +273,7 @@ public class DesignReport {
*/ */
theFigure.setSize(thePageImageableWidth, thePageImageableHeight); theFigure.setSize(thePageImageableWidth, thePageImageableHeight);
theFigure.updateFigure(); theFigure.updateFigure();
final DefaultFontMapper mapper = new DefaultFontMapper(); final DefaultFontMapper mapper = new DefaultFontMapper();
Graphics2D g2d = theCanvas.createGraphics(thePageImageableWidth, thePageImageableHeight * 2, mapper); Graphics2D g2d = theCanvas.createGraphics(thePageImageableWidth, thePageImageableHeight * 2, mapper);
final double halfFigureHeight = SCALE_FUDGE_FACTOR * theFigure.getFigureHeightPx() / 2; final double halfFigureHeight = SCALE_FUDGE_FACTOR * theFigure.getFigureHeightPx() / 2;
@ -284,13 +284,13 @@ public class DesignReport {
y += (int) halfFigureHeight; y += (int) halfFigureHeight;
} }
g2d.translate(20, y); g2d.translate(20, y);
g2d.scale(SCALE_FUDGE_FACTOR, SCALE_FUDGE_FACTOR); g2d.scale(SCALE_FUDGE_FACTOR, SCALE_FUDGE_FACTOR);
theFigure.paint(g2d); theFigure.paint(g2d);
g2d.dispose(); g2d.dispose();
return scale; return scale;
} }
/** /**
* Add the motor data for a motor configuration to the table. * Add the motor data for a motor configuration to the table.
* *
@ -299,11 +299,11 @@ public class DesignReport {
* @param parent the parent to which the motor data will be added * @param parent the parent to which the motor data will be added
*/ */
private void addMotorData(Rocket rocket, String motorId, final PdfPTable parent) { private void addMotorData(Rocket rocket, String motorId, final PdfPTable parent) {
PdfPTable motorTable = new PdfPTable(8); PdfPTable motorTable = new PdfPTable(8);
motorTable.setWidthPercentage(68); motorTable.setWidthPercentage(68);
motorTable.setHorizontalAlignment(Element.ALIGN_LEFT); motorTable.setHorizontalAlignment(Element.ALIGN_LEFT);
final PdfPCell motorCell = ITextHelper.createCell(MOTOR, PdfPCell.BOTTOM, PrintUtilities.SMALL); final PdfPCell motorCell = ITextHelper.createCell(MOTOR, PdfPCell.BOTTOM, PrintUtilities.SMALL);
final int mPad = 10; final int mPad = 10;
motorCell.setPaddingLeft(mPad); motorCell.setPaddingLeft(mPad);
@ -315,25 +315,25 @@ public class DesignReport {
motorTable.addCell(ITextHelper.createCell(THRUST_TO_WT, PdfPCell.BOTTOM, PrintUtilities.SMALL)); motorTable.addCell(ITextHelper.createCell(THRUST_TO_WT, PdfPCell.BOTTOM, PrintUtilities.SMALL));
motorTable.addCell(ITextHelper.createCell(PROPELLANT_WT, PdfPCell.BOTTOM, PrintUtilities.SMALL)); motorTable.addCell(ITextHelper.createCell(PROPELLANT_WT, PdfPCell.BOTTOM, PrintUtilities.SMALL));
motorTable.addCell(ITextHelper.createCell(SIZE, PdfPCell.BOTTOM, PrintUtilities.SMALL)); motorTable.addCell(ITextHelper.createCell(SIZE, PdfPCell.BOTTOM, PrintUtilities.SMALL));
DecimalFormat ttwFormat = new DecimalFormat("0.00"); DecimalFormat ttwFormat = new DecimalFormat("0.00");
MassCalculator massCalc = new BasicMassCalculator(); MassCalculator massCalc = new BasicMassCalculator();
Configuration config = new Configuration(rocket); Configuration config = new Configuration(rocket);
config.setMotorConfigurationID(motorId); config.setMotorConfigurationID(motorId);
int totalMotorCount = 0; int totalMotorCount = 0;
double totalPropMass = 0; double totalPropMass = 0;
double totalImpulse = 0; double totalImpulse = 0;
double totalTTW = 0; double totalTTW = 0;
int stage = 0; int stage = 0;
double stageMass = 0; double stageMass = 0;
boolean topBorder = false; boolean topBorder = false;
for (RocketComponent c : rocket) { for (RocketComponent c : rocket) {
if (c instanceof Stage) { if (c instanceof Stage) {
config.setToStage(stage); config.setToStage(stage);
stage++; stage++;
@ -342,26 +342,26 @@ public class DesignReport {
totalTTW = 0; totalTTW = 0;
topBorder = true; topBorder = true;
} }
if (c instanceof MotorMount && ((MotorMount) c).isMotorMount()) { if (c instanceof MotorMount && ((MotorMount) c).isMotorMount()) {
MotorMount mount = (MotorMount) c; MotorMount mount = (MotorMount) c;
if (mount.isMotorMount() && mount.getMotor(motorId) != null) { if (mount.isMotorMount() && mount.getMotor(motorId) != null) {
Motor motor = mount.getMotor(motorId); Motor motor = mount.getMotor(motorId);
int motorCount = c.toAbsolute(Coordinate.NUL).length; int motorCount = c.toAbsolute(Coordinate.NUL).length;
int border = Rectangle.NO_BORDER; int border = Rectangle.NO_BORDER;
if (topBorder) { if (topBorder) {
border = Rectangle.TOP; border = Rectangle.TOP;
topBorder = false; topBorder = false;
} }
String name = motor.getDesignation(); String name = motor.getDesignation();
if (motorCount > 1) { if (motorCount > 1) {
name += " (" + Chars.TIMES + motorCount + ")"; name += " (" + Chars.TIMES + motorCount + ")";
} }
final PdfPCell motorVCell = ITextHelper.createCell(name, border); final PdfPCell motorVCell = ITextHelper.createCell(name, border);
motorVCell.setPaddingLeft(mPad); motorVCell.setPaddingLeft(mPad);
motorTable.addCell(motorVCell); motorTable.addCell(motorVCell);
@ -373,21 +373,21 @@ public class DesignReport {
UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(motor.getMaxThrustEstimate()), border)); UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(motor.getMaxThrustEstimate()), border));
motorTable.addCell(ITextHelper.createCell( motorTable.addCell(ITextHelper.createCell(
UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(motor.getTotalImpulseEstimate()), border)); UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(motor.getTotalImpulseEstimate()), border));
double ttw = motor.getAverageThrustEstimate() / (stageMass * GRAVITY_CONSTANT); double ttw = motor.getAverageThrustEstimate() / (stageMass * GRAVITY_CONSTANT);
motorTable.addCell(ITextHelper.createCell( motorTable.addCell(ITextHelper.createCell(
ttwFormat.format(ttw) + ":1", border)); ttwFormat.format(ttw) + ":1", border));
double propMass = (motor.getLaunchCG().weight - motor.getEmptyCG().weight); double propMass = (motor.getLaunchCG().weight - motor.getEmptyCG().weight);
motorTable.addCell(ITextHelper.createCell( motorTable.addCell(ITextHelper.createCell(
UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(propMass), border)); UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(propMass), border));
final Unit motorUnit = UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit(); final Unit motorUnit = UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit();
motorTable.addCell(ITextHelper.createCell(motorUnit.toString(motor.getDiameter()) + motorTable.addCell(ITextHelper.createCell(motorUnit.toString(motor.getDiameter()) +
"/" + "/" +
motorUnit.toString(motor.getLength()) + " " + motorUnit.toString(motor.getLength()) + " " +
motorUnit.toString(), border)); motorUnit.toString(), border));
// Sum up total count // Sum up total count
totalMotorCount += motorCount; totalMotorCount += motorCount;
totalPropMass += propMass * motorCount; totalPropMass += propMass * motorCount;
@ -396,7 +396,7 @@ public class DesignReport {
} }
} }
} }
if (totalMotorCount > 1) { if (totalMotorCount > 1) {
int border = Rectangle.TOP; int border = Rectangle.TOP;
final PdfPCell motorVCell = ITextHelper.createCell("Total:", border); final PdfPCell motorVCell = ITextHelper.createCell("Total:", border);
@ -412,16 +412,17 @@ public class DesignReport {
motorTable.addCell(ITextHelper.createCell( motorTable.addCell(ITextHelper.createCell(
UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(totalPropMass), border)); UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(totalPropMass), border));
motorTable.addCell(ITextHelper.createCell("", border)); motorTable.addCell(ITextHelper.createCell("", border));
} }
PdfPCell c = new PdfPCell(motorTable); PdfPCell c = new PdfPCell(motorTable);
c.setBorder(PdfPCell.LEFT); c.setBorder(PdfPCell.LEFT);
c.setBorderWidthTop(0f); c.setBorderWidthTop(0f);
parent.addCell(c); parent.addCell(c);
config.release();
} }
/** /**
* Add the flight data for a simulation configuration to the table. * Add the flight data for a simulation configuration to the table.
* *
@ -432,47 +433,47 @@ public class DesignReport {
* @param leading the number of points for the leading * @param leading the number of points for the leading
*/ */
private void addFlightData(final FlightData flight, final Rocket theRocket, final String motorId, final PdfPTable parent, int leading) { private void addFlightData(final FlightData flight, final Rocket theRocket, final String motorId, final PdfPTable parent, int leading) {
// Output the flight data // Output the flight data
if (flight != null) { if (flight != null) {
try { try {
final Unit distanceUnit = UnitGroup.UNITS_DISTANCE.getDefaultUnit(); final Unit distanceUnit = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
final Unit velocityUnit = UnitGroup.UNITS_VELOCITY.getDefaultUnit(); final Unit velocityUnit = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
final Unit flightTimeUnit = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit(); final Unit flightTimeUnit = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
PdfPTable labelTable = new PdfPTable(2); PdfPTable labelTable = new PdfPTable(2);
labelTable.setWidths(new int[] { 3, 2 }); labelTable.setWidths(new int[] { 3, 2 });
final Paragraph chunk = ITextHelper.createParagraph(stripBrackets( final Paragraph chunk = ITextHelper.createParagraph(stripBrackets(
theRocket.getMotorConfigurationNameOrDescription(motorId)), PrintUtilities.BOLD); theRocket.getMotorConfigurationNameOrDescription(motorId)), PrintUtilities.BOLD);
chunk.setLeading(leading); chunk.setLeading(leading);
chunk.setSpacingAfter(3f); chunk.setSpacingAfter(3f);
document.add(chunk); document.add(chunk);
final PdfPCell cell = ITextHelper.createCell(ALTITUDE, 2, 2); final PdfPCell cell = ITextHelper.createCell(ALTITUDE, 2, 2);
cell.setUseBorderPadding(false); cell.setUseBorderPadding(false);
cell.setBorderWidthTop(0f); cell.setBorderWidthTop(0f);
labelTable.addCell(cell); labelTable.addCell(cell);
labelTable.addCell(ITextHelper.createCell(distanceUnit.toStringUnit(flight.getMaxAltitude()), 2, 2)); labelTable.addCell(ITextHelper.createCell(distanceUnit.toStringUnit(flight.getMaxAltitude()), 2, 2));
labelTable.addCell(ITextHelper.createCell(FLIGHT_TIME, 2, 2)); labelTable.addCell(ITextHelper.createCell(FLIGHT_TIME, 2, 2));
labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getFlightTime()), 2, 2)); labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getFlightTime()), 2, 2));
labelTable.addCell(ITextHelper.createCell(TIME_TO_APOGEE, 2, 2)); labelTable.addCell(ITextHelper.createCell(TIME_TO_APOGEE, 2, 2));
labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getTimeToApogee()), 2, 2)); labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getTimeToApogee()), 2, 2));
labelTable.addCell(ITextHelper.createCell(VELOCITY_OFF_PAD, 2, 2)); labelTable.addCell(ITextHelper.createCell(VELOCITY_OFF_PAD, 2, 2));
labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getLaunchRodVelocity()), 2, 2)); labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getLaunchRodVelocity()), 2, 2));
labelTable.addCell(ITextHelper.createCell(MAX_VELOCITY, 2, 2)); labelTable.addCell(ITextHelper.createCell(MAX_VELOCITY, 2, 2));
labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getMaxVelocity()), 2, 2)); labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getMaxVelocity()), 2, 2));
labelTable.addCell(ITextHelper.createCell(DEPLOYMENT_VELOCITY, 2, 2)); labelTable.addCell(ITextHelper.createCell(DEPLOYMENT_VELOCITY, 2, 2));
labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getDeploymentVelocity()), 2, 2)); labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getDeploymentVelocity()), 2, 2));
labelTable.addCell(ITextHelper.createCell(LANDING_VELOCITY, 2, 2)); labelTable.addCell(ITextHelper.createCell(LANDING_VELOCITY, 2, 2));
labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getGroundHitVelocity()), 2, 2)); labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getGroundHitVelocity()), 2, 2));
//Add the table to the parent; have to wrap it in a cell //Add the table to the parent; have to wrap it in a cell
PdfPCell c = new PdfPCell(labelTable); PdfPCell c = new PdfPCell(labelTable);
c.setBorder(PdfPCell.RIGHT); c.setBorder(PdfPCell.RIGHT);
@ -484,7 +485,7 @@ public class DesignReport {
} }
} }
} }
/** /**
* Locate the simulation based on the motor id. Copy the simulation and execute it, then return the resulting * Locate the simulation based on the motor id. Copy the simulation and execute it, then return the resulting
* flight data. * flight data.
@ -512,7 +513,7 @@ public class DesignReport {
} }
return flight; return flight;
} }
/** /**
* Strip [] brackets from a string. * Strip [] brackets from a string.
* *
@ -523,7 +524,7 @@ public class DesignReport {
private String stripBrackets(String target) { private String stripBrackets(String target) {
return stripLeftBracket(stripRightBracket(target)); return stripLeftBracket(stripRightBracket(target));
} }
/** /**
* Strip [ from a string. * Strip [ from a string.
* *
@ -534,7 +535,7 @@ public class DesignReport {
private String stripLeftBracket(String target) { private String stripLeftBracket(String target) {
return target.replace("[", ""); return target.replace("[", "");
} }
/** /**
* Strip ] from a string. * Strip ] from a string.
* *
@ -545,5 +546,5 @@ public class DesignReport {
private String stripRightBracket(String target) { private String stripRightBracket(String target) {
return target.replace("]", ""); return target.replace("]", "");
} }
} }

View File

@ -31,53 +31,53 @@ import net.sf.openrocket.util.Quaternion;
public class BasicEventSimulationEngine implements SimulationEngine { public class BasicEventSimulationEngine implements SimulationEngine {
private static final Translator trans = Application.getTranslator(); private static final Translator trans = Application.getTranslator();
private static final LogHelper log = Application.getLogger(); private static final LogHelper log = Application.getLogger();
// TODO: MEDIUM: Allow selecting steppers // TODO: MEDIUM: Allow selecting steppers
private SimulationStepper flightStepper = new RK4SimulationStepper(); private SimulationStepper flightStepper = new RK4SimulationStepper();
private SimulationStepper landingStepper = new BasicLandingStepper(); private SimulationStepper landingStepper = new BasicLandingStepper();
private SimulationStepper currentStepper; private SimulationStepper currentStepper;
private SimulationStatus status; private SimulationStatus status;
@Override @Override
public FlightData simulate(SimulationConditions simulationConditions) throws SimulationException { public FlightData simulate(SimulationConditions simulationConditions) throws SimulationException {
Set<MotorId> motorBurntOut = new HashSet<MotorId>(); Set<MotorId> motorBurntOut = new HashSet<MotorId>();
// Set up flight data // Set up flight data
FlightData flightData = new FlightData(); FlightData flightData = new FlightData();
// Set up rocket configuration // Set up rocket configuration
Configuration configuration = setupConfiguration(simulationConditions); Configuration configuration = setupConfiguration(simulationConditions);
MotorInstanceConfiguration motorConfiguration = setupMotorConfiguration(configuration); MotorInstanceConfiguration motorConfiguration = setupMotorConfiguration(configuration);
if (motorConfiguration.getMotorIDs().isEmpty()) { if (motorConfiguration.getMotorIDs().isEmpty()) {
throw new MotorIgnitionException("No motors defined in the simulation."); throw new MotorIgnitionException("No motors defined in the simulation.");
} }
// Initialize the simulation // Initialize the simulation
currentStepper = flightStepper; currentStepper = flightStepper;
status = initialStatus(configuration, motorConfiguration, simulationConditions, flightData); status = initialStatus(configuration, motorConfiguration, simulationConditions, flightData);
status = currentStepper.initialize(status); status = currentStepper.initialize(status);
SimulationListenerHelper.fireStartSimulation(status); SimulationListenerHelper.fireStartSimulation(status);
// Get originating position (in case listener has modified launch position) // Get originating position (in case listener has modified launch position)
Coordinate origin = status.getRocketPosition(); Coordinate origin = status.getRocketPosition();
Coordinate originVelocity = status.getRocketVelocity(); Coordinate originVelocity = status.getRocketVelocity();
try { try {
double maxAlt = Double.NEGATIVE_INFINITY; double maxAlt = Double.NEGATIVE_INFINITY;
// Start the simulation // Start the simulation
while (handleEvents()) { while (handleEvents()) {
// Take the step // Take the step
double oldAlt = status.getRocketPosition().z; double oldAlt = status.getRocketPosition().z;
if (SimulationListenerHelper.firePreStep(status)) { if (SimulationListenerHelper.firePreStep(status)) {
// Step at most to the next event // Step at most to the next event
double maxStepTime = Double.MAX_VALUE; double maxStepTime = Double.MAX_VALUE;
@ -89,27 +89,27 @@ public class BasicEventSimulationEngine implements SimulationEngine {
currentStepper.step(status, maxStepTime); currentStepper.step(status, maxStepTime);
} }
SimulationListenerHelper.firePostStep(status); SimulationListenerHelper.firePostStep(status);
// Check for NaN values in the simulation status // Check for NaN values in the simulation status
checkNaN(); checkNaN();
// Add altitude event // Add altitude event
addEvent(new FlightEvent(FlightEvent.Type.ALTITUDE, status.getSimulationTime(), addEvent(new FlightEvent(FlightEvent.Type.ALTITUDE, status.getSimulationTime(),
status.getConfiguration().getRocket(), status.getConfiguration().getRocket(),
new Pair<Double, Double>(oldAlt, status.getRocketPosition().z))); new Pair<Double, Double>(oldAlt, status.getRocketPosition().z)));
if (status.getRocketPosition().z > maxAlt) { if (status.getRocketPosition().z > maxAlt) {
maxAlt = status.getRocketPosition().z; maxAlt = status.getRocketPosition().z;
} }
// Position relative to start location // Position relative to start location
Coordinate relativePosition = status.getRocketPosition().sub(origin); Coordinate relativePosition = status.getRocketPosition().sub(origin);
// Add appropriate events // Add appropriate events
if (!status.isLiftoff()) { if (!status.isLiftoff()) {
// Avoid sinking into ground before liftoff // Avoid sinking into ground before liftoff
if (relativePosition.z < 0) { if (relativePosition.z < 0) {
status.setRocketPosition(origin); status.setRocketPosition(origin);
@ -119,32 +119,32 @@ public class BasicEventSimulationEngine implements SimulationEngine {
if (relativePosition.z > 0.02) { if (relativePosition.z > 0.02) {
addEvent(new FlightEvent(FlightEvent.Type.LIFTOFF, status.getSimulationTime())); addEvent(new FlightEvent(FlightEvent.Type.LIFTOFF, status.getSimulationTime()));
} }
} else { } else {
// Check ground hit after liftoff // Check ground hit after liftoff
if (status.getRocketPosition().z < 0) { if (status.getRocketPosition().z < 0) {
status.setRocketPosition(status.getRocketPosition().setZ(0)); status.setRocketPosition(status.getRocketPosition().setZ(0));
addEvent(new FlightEvent(FlightEvent.Type.GROUND_HIT, status.getSimulationTime())); addEvent(new FlightEvent(FlightEvent.Type.GROUND_HIT, status.getSimulationTime()));
addEvent(new FlightEvent(FlightEvent.Type.SIMULATION_END, status.getSimulationTime())); addEvent(new FlightEvent(FlightEvent.Type.SIMULATION_END, status.getSimulationTime()));
} }
} }
// Check for launch guide clearance // Check for launch guide clearance
if (!status.isLaunchRodCleared() && if (!status.isLaunchRodCleared() &&
relativePosition.length() > status.getSimulationConditions().getLaunchRodLength()) { relativePosition.length() > status.getSimulationConditions().getLaunchRodLength()) {
addEvent(new FlightEvent(FlightEvent.Type.LAUNCHROD, status.getSimulationTime(), null)); addEvent(new FlightEvent(FlightEvent.Type.LAUNCHROD, status.getSimulationTime(), null));
} }
// Check for apogee // Check for apogee
if (!status.isApogeeReached() && status.getRocketPosition().z < maxAlt - 0.01) { if (!status.isApogeeReached() && status.getRocketPosition().z < maxAlt - 0.01) {
addEvent(new FlightEvent(FlightEvent.Type.APOGEE, status.getSimulationTime(), addEvent(new FlightEvent(FlightEvent.Type.APOGEE, status.getSimulationTime(),
status.getConfiguration().getRocket())); status.getConfiguration().getRocket()));
} }
// Check for burnt out motors // Check for burnt out motors
for (MotorId motorId : status.getMotorConfiguration().getMotorIDs()) { for (MotorId motorId : status.getMotorConfiguration().getMotorIDs()) {
MotorInstance motor = status.getMotorConfiguration().getMotorInstance(motorId); MotorInstance motor = status.getMotorConfiguration().getMotorInstance(motorId);
@ -153,58 +153,59 @@ public class BasicEventSimulationEngine implements SimulationEngine {
(RocketComponent) status.getMotorConfiguration().getMotorMount(motorId), motorId)); (RocketComponent) status.getMotorConfiguration().getMotorMount(motorId), motorId));
} }
} }
} }
} catch (SimulationException e) { } catch (SimulationException e) {
SimulationListenerHelper.fireEndSimulation(status, e); SimulationListenerHelper.fireEndSimulation(status, e);
throw e; throw e;
} }
SimulationListenerHelper.fireEndSimulation(status, null); SimulationListenerHelper.fireEndSimulation(status, null);
flightData.addBranch(status.getFlightData()); flightData.addBranch(status.getFlightData());
if (!flightData.getWarningSet().isEmpty()) { if (!flightData.getWarningSet().isEmpty()) {
log.info("Warnings at the end of simulation: " + flightData.getWarningSet()); log.info("Warnings at the end of simulation: " + flightData.getWarningSet());
} }
configuration.release();
// TODO: HIGH: Simulate branches // TODO: HIGH: Simulate branches
return flightData; return flightData;
} }
private SimulationStatus initialStatus(Configuration configuration, private SimulationStatus initialStatus(Configuration configuration,
MotorInstanceConfiguration motorConfiguration, MotorInstanceConfiguration motorConfiguration,
SimulationConditions simulationConditions, FlightData flightData) { SimulationConditions simulationConditions, FlightData flightData) {
SimulationStatus init = new SimulationStatus(); SimulationStatus init = new SimulationStatus();
init.setSimulationConditions(simulationConditions); init.setSimulationConditions(simulationConditions);
init.setConfiguration(configuration); init.setConfiguration(configuration);
init.setMotorConfiguration(motorConfiguration); init.setMotorConfiguration(motorConfiguration);
init.setSimulationTime(0); init.setSimulationTime(0);
init.setPreviousTimeStep(simulationConditions.getTimeStep()); init.setPreviousTimeStep(simulationConditions.getTimeStep());
init.setRocketPosition(Coordinate.NUL); init.setRocketPosition(Coordinate.NUL);
init.setRocketVelocity(Coordinate.NUL); init.setRocketVelocity(Coordinate.NUL);
init.setRocketWorldPosition(simulationConditions.getLaunchSite()); init.setRocketWorldPosition(simulationConditions.getLaunchSite());
// Initialize to roll angle with least stability w.r.t. the wind // Initialize to roll angle with least stability w.r.t. the wind
Quaternion o; Quaternion o;
FlightConditions cond = new FlightConditions(configuration); FlightConditions cond = new FlightConditions(configuration);
simulationConditions.getAerodynamicCalculator().getWorstCP(configuration, cond, null); simulationConditions.getAerodynamicCalculator().getWorstCP(configuration, cond, null);
double angle = -cond.getTheta() - simulationConditions.getLaunchRodDirection(); double angle = -cond.getTheta() - simulationConditions.getLaunchRodDirection();
o = Quaternion.rotation(new Coordinate(0, 0, angle)); o = Quaternion.rotation(new Coordinate(0, 0, angle));
// Launch rod angle and direction // Launch rod angle and direction
o = o.multiplyLeft(Quaternion.rotation(new Coordinate(0, simulationConditions.getLaunchRodAngle(), 0))); o = o.multiplyLeft(Quaternion.rotation(new Coordinate(0, simulationConditions.getLaunchRodAngle(), 0)));
o = o.multiplyLeft(Quaternion.rotation(new Coordinate(0, 0, simulationConditions.getLaunchRodDirection()))); o = o.multiplyLeft(Quaternion.rotation(new Coordinate(0, 0, simulationConditions.getLaunchRodDirection())));
init.setRocketOrientationQuaternion(o); init.setRocketOrientationQuaternion(o);
init.setRocketRotationVelocity(Coordinate.NUL); init.setRocketRotationVelocity(Coordinate.NUL);
/* /*
* Calculate the effective launch rod length taking into account launch lugs. * Calculate the effective launch rod length taking into account launch lugs.
* If no lugs are found, assume a tower launcher of full length. * If no lugs are found, assume a tower launcher of full length.
@ -230,29 +231,29 @@ public class BasicEventSimulationEngine implements SimulationEngine {
} }
} }
init.setEffectiveLaunchRodLength(length); init.setEffectiveLaunchRodLength(length);
init.setSimulationStartWallTime(System.nanoTime()); init.setSimulationStartWallTime(System.nanoTime());
init.setMotorIgnited(false); init.setMotorIgnited(false);
init.setLiftoff(false); init.setLiftoff(false);
init.setLaunchRodCleared(false); init.setLaunchRodCleared(false);
init.setApogeeReached(false); init.setApogeeReached(false);
init.getEventQueue().add(new FlightEvent(FlightEvent.Type.LAUNCH, 0, simulationConditions.getRocket())); init.getEventQueue().add(new FlightEvent(FlightEvent.Type.LAUNCH, 0, simulationConditions.getRocket()));
init.setFlightData(new FlightDataBranch("MAIN", FlightDataType.TYPE_TIME)); init.setFlightData(new FlightDataBranch("MAIN", FlightDataType.TYPE_TIME));
init.setWarnings(flightData.getWarningSet()); init.setWarnings(flightData.getWarningSet());
return init; return init;
} }
/** /**
* Create a rocket configuration from the launch conditions. * Create a rocket configuration from the launch conditions.
* *
* @param simulation the launch conditions. * @param simulation the launch conditions.
* @return a rocket configuration with all stages attached. * @return a rocket configuration with all stages attached.
*/ */
@ -260,28 +261,28 @@ public class BasicEventSimulationEngine implements SimulationEngine {
Configuration configuration = new Configuration(simulation.getRocket()); Configuration configuration = new Configuration(simulation.getRocket());
configuration.setAllStages(); configuration.setAllStages();
configuration.setMotorConfigurationID(simulation.getMotorConfigurationID()); configuration.setMotorConfigurationID(simulation.getMotorConfigurationID());
return configuration; return configuration;
} }
/** /**
* Create a new motor instance configuration for the rocket configuration. * Create a new motor instance configuration for the rocket configuration.
* *
* @param configuration the rocket configuration. * @param configuration the rocket configuration.
* @return a new motor instance configuration with all motors in place. * @return a new motor instance configuration with all motors in place.
*/ */
private MotorInstanceConfiguration setupMotorConfiguration(Configuration configuration) { private MotorInstanceConfiguration setupMotorConfiguration(Configuration configuration) {
MotorInstanceConfiguration motors = new MotorInstanceConfiguration(); MotorInstanceConfiguration motors = new MotorInstanceConfiguration();
final String motorId = configuration.getMotorConfigurationID(); final String motorId = configuration.getMotorConfigurationID();
Iterator<MotorMount> iterator = configuration.motorIterator(); Iterator<MotorMount> iterator = configuration.motorIterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
MotorMount mount = iterator.next(); MotorMount mount = iterator.next();
RocketComponent component = (RocketComponent) mount; RocketComponent component = (RocketComponent) mount;
Motor motor = mount.getMotor(motorId); Motor motor = mount.getMotor(motorId);
if (motor != null) { if (motor != null) {
Coordinate[] positions = component.toAbsolute(mount.getMotorPosition(motorId)); Coordinate[] positions = component.toAbsolute(mount.getMotorPosition(motorId));
for (int i = 0; i < positions.length; i++) { for (int i = 0; i < positions.length; i++) {
@ -293,7 +294,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
} }
return motors; return motors;
} }
/** /**
* Handles events occurring during the flight from the event queue. * Handles events occurring during the flight from the event queue.
* Each event that has occurred before or at the current simulation time is * Each event that has occurred before or at the current simulation time is
@ -302,24 +303,24 @@ public class BasicEventSimulationEngine implements SimulationEngine {
private boolean handleEvents() throws SimulationException { private boolean handleEvents() throws SimulationException {
boolean ret = true; boolean ret = true;
FlightEvent event; FlightEvent event;
for (event = nextEvent(); event != null; event = nextEvent()) { for (event = nextEvent(); event != null; event = nextEvent()) {
// Ignore events for components that are no longer attached to the rocket // Ignore events for components that are no longer attached to the rocket
if (event.getSource() != null && event.getSource().getParent() != null && if (event.getSource() != null && event.getSource().getParent() != null &&
!status.getConfiguration().isStageActive(event.getSource().getStageNumber())) { !status.getConfiguration().isStageActive(event.getSource().getStageNumber())) {
continue; continue;
} }
// Call simulation listeners, allow aborting event handling // Call simulation listeners, allow aborting event handling
if (!SimulationListenerHelper.fireHandleFlightEvent(status, event)) { if (!SimulationListenerHelper.fireHandleFlightEvent(status, event)) {
continue; continue;
} }
if (event.getType() != FlightEvent.Type.ALTITUDE) { if (event.getType() != FlightEvent.Type.ALTITUDE) {
log.verbose("BasicEventSimulationEngine: Handling event " + event); log.verbose("BasicEventSimulationEngine: Handling event " + event);
} }
if (event.getType() == FlightEvent.Type.IGNITION) { if (event.getType() == FlightEvent.Type.IGNITION) {
MotorMount mount = (MotorMount) event.getSource(); MotorMount mount = (MotorMount) event.getSource();
MotorId motorId = (MotorId) event.getData(); MotorId motorId = (MotorId) event.getData();
@ -328,42 +329,42 @@ public class BasicEventSimulationEngine implements SimulationEngine {
continue; continue;
} }
} }
if (event.getType() == FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT) { if (event.getType() == FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT) {
RecoveryDevice device = (RecoveryDevice) event.getSource(); RecoveryDevice device = (RecoveryDevice) event.getSource();
if (!SimulationListenerHelper.fireRecoveryDeviceDeployment(status, device)) { if (!SimulationListenerHelper.fireRecoveryDeviceDeployment(status, device)) {
continue; continue;
} }
} }
// Check for motor ignition events, add ignition events to queue // Check for motor ignition events, add ignition events to queue
for (MotorId id : status.getMotorConfiguration().getMotorIDs()) { for (MotorId id : status.getMotorConfiguration().getMotorIDs()) {
MotorMount mount = status.getMotorConfiguration().getMotorMount(id); MotorMount mount = status.getMotorConfiguration().getMotorMount(id);
RocketComponent component = (RocketComponent) mount; RocketComponent component = (RocketComponent) mount;
if (mount.getIgnitionEvent().isActivationEvent(event, component)) { if (mount.getIgnitionEvent().isActivationEvent(event, component)) {
addEvent(new FlightEvent(FlightEvent.Type.IGNITION, addEvent(new FlightEvent(FlightEvent.Type.IGNITION,
status.getSimulationTime() + mount.getIgnitionDelay(), status.getSimulationTime() + mount.getIgnitionDelay(),
component, id)); component, id));
} }
} }
// Check for stage separation event // Check for stage separation event
for (int stageNo : status.getConfiguration().getActiveStages()) { for (int stageNo : status.getConfiguration().getActiveStages()) {
if (stageNo == 0) if (stageNo == 0)
continue; continue;
Stage stage = (Stage) status.getConfiguration().getRocket().getChild(stageNo); Stage stage = (Stage) status.getConfiguration().getRocket().getChild(stageNo);
if (stage.getSeparationEvent().isSeparationEvent(event, stage)) { if (stage.getSeparationEvent().isSeparationEvent(event, stage)) {
addEvent(new FlightEvent(FlightEvent.Type.STAGE_SEPARATION, addEvent(new FlightEvent(FlightEvent.Type.STAGE_SEPARATION,
event.getTime() + stage.getSeparationDelay(), stage)); event.getTime() + stage.getSeparationDelay(), stage));
} }
} }
// Check for recovery device deployment, add events to queue // Check for recovery device deployment, add events to queue
Iterator<RocketComponent> rci = status.getConfiguration().iterator(); Iterator<RocketComponent> rci = status.getConfiguration().iterator();
while (rci.hasNext()) { while (rci.hasNext()) {
@ -376,16 +377,16 @@ public class BasicEventSimulationEngine implements SimulationEngine {
event.getTime() + Math.max(0.001, ((RecoveryDevice) c).getDeployDelay()), c)); event.getTime() + Math.max(0.001, ((RecoveryDevice) c).getDeployDelay()), c));
} }
} }
// Handle event // Handle event
switch (event.getType()) { switch (event.getType()) {
case LAUNCH: { case LAUNCH: {
status.getFlightData().addEvent(event); status.getFlightData().addEvent(event);
break; break;
} }
case IGNITION: { case IGNITION: {
// Ignite the motor // Ignite the motor
MotorMount mount = (MotorMount) event.getSource(); MotorMount mount = (MotorMount) event.getSource();
@ -395,24 +396,24 @@ public class BasicEventSimulationEngine implements SimulationEngine {
config.setMotorIgnitionTime(motorId, event.getTime()); config.setMotorIgnitionTime(motorId, event.getTime());
status.setMotorIgnited(true); status.setMotorIgnited(true);
status.getFlightData().addEvent(event); status.getFlightData().addEvent(event);
break; break;
} }
case LIFTOFF: { case LIFTOFF: {
// Mark lift-off as occurred // Mark lift-off as occurred
status.setLiftoff(true); status.setLiftoff(true);
status.getFlightData().addEvent(event); status.getFlightData().addEvent(event);
break; break;
} }
case LAUNCHROD: { case LAUNCHROD: {
// Mark launch rod as cleared // Mark launch rod as cleared
status.setLaunchRodCleared(true); status.setLaunchRodCleared(true);
status.getFlightData().addEvent(event); status.getFlightData().addEvent(event);
break; break;
} }
case BURNOUT: { case BURNOUT: {
// If motor burnout occurs without lift-off, abort // If motor burnout occurs without lift-off, abort
if (!status.isLiftoff()) { if (!status.isLiftoff()) {
@ -429,12 +430,12 @@ public class BasicEventSimulationEngine implements SimulationEngine {
status.getFlightData().addEvent(event); status.getFlightData().addEvent(event);
break; break;
} }
case EJECTION_CHARGE: { case EJECTION_CHARGE: {
status.getFlightData().addEvent(event); status.getFlightData().addEvent(event);
break; break;
} }
case STAGE_SEPARATION: { case STAGE_SEPARATION: {
// TODO: HIGH: Store lower stages to be simulated later // TODO: HIGH: Store lower stages to be simulated later
RocketComponent stage = event.getSource(); RocketComponent stage = event.getSource();
@ -443,20 +444,20 @@ public class BasicEventSimulationEngine implements SimulationEngine {
status.getFlightData().addEvent(event); status.getFlightData().addEvent(event);
break; break;
} }
case APOGEE: case APOGEE:
// Mark apogee as reached // Mark apogee as reached
status.setApogeeReached(true); status.setApogeeReached(true);
status.getFlightData().addEvent(event); status.getFlightData().addEvent(event);
break; break;
case RECOVERY_DEVICE_DEPLOYMENT: case RECOVERY_DEVICE_DEPLOYMENT:
RocketComponent c = event.getSource(); RocketComponent c = event.getSource();
int n = c.getStageNumber(); int n = c.getStageNumber();
// Ignore event if stage not active // Ignore event if stage not active
if (status.getConfiguration().isStageActive(n)) { if (status.getConfiguration().isStageActive(n)) {
// TODO: HIGH: Check stage activeness for other events as well? // TODO: HIGH: Check stage activeness for other events as well?
// Check whether any motor in the active stages is active anymore // Check whether any motor in the active stages is active anymore
for (MotorId motorId : status.getMotorConfiguration().getMotorIDs()) { for (MotorId motorId : status.getMotorConfiguration().getMotorIDs()) {
int stage = ((RocketComponent) status.getMotorConfiguration(). int stage = ((RocketComponent) status.getMotorConfiguration().
@ -467,12 +468,12 @@ public class BasicEventSimulationEngine implements SimulationEngine {
continue; continue;
status.getWarnings().add(Warning.RECOVERY_DEPLOYMENT_WHILE_BURNING); status.getWarnings().add(Warning.RECOVERY_DEPLOYMENT_WHILE_BURNING);
} }
// Check for launch rod // Check for launch rod
if (!status.isLaunchRodCleared()) { if (!status.isLaunchRodCleared()) {
status.getWarnings().add(Warning.RECOVERY_LAUNCH_ROD); status.getWarnings().add(Warning.RECOVERY_LAUNCH_ROD);
} }
// Check current velocity // Check current velocity
if (status.getRocketVelocity().length() > 20) { if (status.getRocketVelocity().length() > 20) {
// TODO: LOW: Custom warning. // TODO: LOW: Custom warning.
@ -481,44 +482,44 @@ public class BasicEventSimulationEngine implements SimulationEngine {
+ UnitGroup.UNITS_VELOCITY.toStringUnit(status.getRocketVelocity().length()) + UnitGroup.UNITS_VELOCITY.toStringUnit(status.getRocketVelocity().length())
+ ").")); + ")."));
} }
status.setLiftoff(true); status.setLiftoff(true);
status.getDeployedRecoveryDevices().add((RecoveryDevice) c); status.getDeployedRecoveryDevices().add((RecoveryDevice) c);
this.currentStepper = this.landingStepper; this.currentStepper = this.landingStepper;
this.status = currentStepper.initialize(status); this.status = currentStepper.initialize(status);
status.getFlightData().addEvent(event); status.getFlightData().addEvent(event);
} }
break; break;
case GROUND_HIT: case GROUND_HIT:
status.getFlightData().addEvent(event); status.getFlightData().addEvent(event);
break; break;
case SIMULATION_END: case SIMULATION_END:
ret = false; ret = false;
status.getFlightData().addEvent(event); status.getFlightData().addEvent(event);
break; break;
case ALTITUDE: case ALTITUDE:
break; break;
} }
} }
// If no motor has ignited, abort // If no motor has ignited, abort
if (!status.isMotorIgnited()) { if (!status.isMotorIgnited()) {
throw new MotorIgnitionException("No motors ignited."); throw new MotorIgnitionException("No motors ignited.");
} }
return ret; return ret;
} }
/** /**
* Add a flight event to the event queue unless a listener aborts adding it. * Add a flight event to the event queue unless a listener aborts adding it.
* *
* @param event the event to add to the queue. * @param event the event to add to the queue.
*/ */
private void addEvent(FlightEvent event) throws SimulationException { private void addEvent(FlightEvent event) throws SimulationException {
@ -526,15 +527,14 @@ public class BasicEventSimulationEngine implements SimulationEngine {
status.getEventQueue().add(event); status.getEventQueue().add(event);
} }
} }
/** /**
* Return the next flight event to handle, or null if no more events should be handled. * 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. * The flight event is removed from the event queue.
* *
* @param status the simulation status
* @return the flight event to handle, or null * @return the flight event to handle, or null
*/ */
private FlightEvent nextEvent() { private FlightEvent nextEvent() {
@ -542,7 +542,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
FlightEvent event = queue.peek(); FlightEvent event = queue.peek();
if (event == null) if (event == null)
return null; return null;
// Jump to event if no motors have been ignited // Jump to event if no motors have been ignited
if (!status.isMotorIgnited() && event.getTime() > status.getSimulationTime()) { if (!status.isMotorIgnited() && event.getTime() > status.getSimulationTime()) {
status.setSimulationTime(event.getTime()); status.setSimulationTime(event.getTime());
@ -553,9 +553,9 @@ public class BasicEventSimulationEngine implements SimulationEngine {
return null; return null;
} }
} }
private void checkNaN() throws SimulationException { private void checkNaN() throws SimulationException {
double d = 0; double d = 0;
boolean b = false; boolean b = false;
@ -566,7 +566,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
b |= status.getRocketOrientationQuaternion().isNaN(); b |= status.getRocketOrientationQuaternion().isNaN();
b |= status.getRocketRotationVelocity().isNaN(); b |= status.getRocketRotationVelocity().isNaN();
d += status.getEffectiveLaunchRodLength(); d += status.getEffectiveLaunchRodLength();
if (Double.isNaN(d) || b) { if (Double.isNaN(d) || b) {
log.error("Simulation resulted in NaN value:" + log.error("Simulation resulted in NaN value:" +
" simulationTime=" + status.getSimulationTime() + " simulationTime=" + status.getSimulationTime() +
@ -579,6 +579,6 @@ public class BasicEventSimulationEngine implements SimulationEngine {
throw new SimulationException("Simulation resulted in not-a-number (NaN) value, please report a bug."); throw new SimulationException("Simulation resulted in not-a-number (NaN) value, please report a bug.");
} }
} }
} }