diff --git a/core/src/net/sf/openrocket/document/Simulation.java b/core/src/net/sf/openrocket/document/Simulation.java index edb093c3b..92cdf367f 100644 --- a/core/src/net/sf/openrocket/document/Simulation.java +++ b/core/src/net/sf/openrocket/document/Simulation.java @@ -34,12 +34,12 @@ import net.sf.openrocket.util.StateChangeListener; *

* This class is not thread-safe and enforces single-threaded access with a * SafetyMutex. - * + * * @author Sampo Niskanen */ public class Simulation implements ChangeSource, Cloneable { private static final LogHelper log = Application.getLogger(); - + public static enum Status { /** Up-to-date */ UPTODATE, @@ -56,21 +56,21 @@ public class Simulation implements ChangeSource, Cloneable { /** Not yet simulated */ NOT_SIMULATED } - + private SafetyMutex mutex = SafetyMutex.newInstance(); - + private final Rocket rocket; - + private String name = ""; - + private Status status = Status.NOT_SIMULATED; - + /** The conditions to use */ // TODO: HIGH: Change to use actual conditions class?? private SimulationOptions options; - + private ArrayList simulationListeners = new ArrayList(); - + private final Class simulationEngineClass = BasicEventSimulationEngine.class; private Class simulationStepperClass = RK4SimulationStepper.class; private Class aerodynamicCalculatorClass = BarrowmanCalculator.class; @@ -78,35 +78,35 @@ public class Simulation implements ChangeSource, Cloneable { /** Listeners for this object */ private List listeners = new ArrayList(); - + /** The conditions actually used in the previous simulation, or null */ private SimulationOptions simulatedConditions = null; private String simulatedMotors = null; private FlightData simulatedData = null; private int simulatedRocketID = -1; - - + + /** * Create a new simulation for the rocket. Parent document should also be provided. * The initial motor configuration is taken from the default rocket configuration. - * + * * @param rocket the rocket associated with the simulation. */ public Simulation(Rocket rocket) { this.rocket = rocket; this.status = Status.NOT_SIMULATED; - + options = new SimulationOptions(rocket); options.setMotorConfigurationID( rocket.getDefaultConfiguration().getMotorConfigurationID()); options.addChangeListener(new ConditionListener()); } - - + + public Simulation(Rocket rocket, Status status, String name, SimulationOptions options, List listeners, FlightData data) { - + if (rocket == null) throw new IllegalArgumentException("rocket cannot be null"); if (status == null) @@ -115,9 +115,9 @@ public class Simulation implements ChangeSource, Cloneable { throw new IllegalArgumentException("name cannot be null"); if (options == null) throw new IllegalArgumentException("options cannot be null"); - + this.rocket = rocket; - + if (status == Status.UPTODATE) { this.status = Status.LOADED; } else if (data == null) { @@ -125,16 +125,16 @@ public class Simulation implements ChangeSource, Cloneable { } else { this.status = status; } - + this.name = name; - + this.options = options; options.addChangeListener(new ConditionListener()); - + if (listeners != null) { this.simulationListeners.addAll(listeners); } - + if (data != null && this.status != Status.NOT_SIMULATED) { simulatedData = data; @@ -143,24 +143,24 @@ public class Simulation implements ChangeSource, Cloneable { simulatedRocketID = rocket.getModID(); } } - + } - + /** * Return the rocket associated with this simulation. - * + * * @return the rocket. */ public Rocket getRocket() { mutex.verify(); return rocket; } - - + + /** * Return a newly created Configuration for this simulation. The configuration * has the motor ID set and all stages active. - * + * * @return a newly created Configuration of the launch conditions. */ public Configuration getConfiguration() { @@ -170,46 +170,46 @@ public class Simulation implements ChangeSource, Cloneable { c.setAllStages(); return c; } - + /** * Returns the simulation options attached to this simulation. The options * may be modified freely, and the status of the simulation will change to reflect * the changes. - * + * * @return the simulation conditions. */ public SimulationOptions getOptions() { mutex.verify(); return options; } - - + + /** * 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. - * + * * @return the actual list of simulation listeners. */ public List getSimulationListeners() { mutex.verify(); return simulationListeners; } - - + + /** * Return the user-defined name of the simulation. - * + * * @return the name for the simulation. */ public String getName() { mutex.verify(); return name; } - + /** * Set the user-defined name of the simulation. Setting the name to * null yields an empty name. - * + * * @param name the name of the simulation. */ public void setName(String name) { @@ -217,43 +217,43 @@ public class Simulation implements ChangeSource, Cloneable { try { if (this.name.equals(name)) return; - + if (name == null) this.name = ""; else this.name = name; - + fireChangeEvent(); } finally { mutex.unlock("setName"); } } - - + + /** * Returns the status of this simulation. This method examines whether the * simulation has been outdated and returns {@link Status#OUTDATED} accordingly. - * + * * @return the status * @see Status */ public Status getStatus() { mutex.verify(); - + if (status == Status.UPTODATE || status == Status.LOADED) { if (rocket.getFunctionalModID() != simulatedRocketID || !options.equals(simulatedConditions)) return Status.OUTDATED; } - + return status; } - - + + /** * Simulate the flight. - * + * * @param additionalListeners additional simulation listeners (those defined by the simulation are used in any case) * @throws SimulationException if a problem occurs during simulation */ @@ -261,13 +261,13 @@ public class Simulation implements ChangeSource, Cloneable { throws SimulationException { mutex.lock("simulate"); try { - + if (this.status == Status.EXTERNAL) { throw new SimulationException("Cannot simulate imported simulation."); } - + SimulationEngine simulator; - + try { simulator = simulationEngineClass.newInstance(); } catch (InstantiationException e) { @@ -275,13 +275,13 @@ public class Simulation implements ChangeSource, Cloneable { } catch (IllegalAccessException e) { throw new IllegalStateException("Cannot access simulator instance?! BUG!", e); } - + SimulationConditions simulationConditions = options.toSimulationConditions(); simulationConditions.setSimulation(this); for (SimulationListener l : additionalListeners) { simulationConditions.getSimulationListenerList().add(l); } - + for (String className : simulationListeners) { SimulationListener l = null; try { @@ -293,43 +293,45 @@ public class Simulation implements ChangeSource, Cloneable { } simulationConditions.getSimulationListenerList().add(l); } - + long t1, t2; log.debug("Simulation: calling simulator"); t1 = System.currentTimeMillis(); simulatedData = simulator.simulate(simulationConditions); t2 = System.currentTimeMillis(); log.debug("Simulation: returning from simulator, simulation took " + (t2 - t1) + "ms"); - + // Set simulated info after simulation, will not be set in case of exception simulatedConditions = options.clone(); - simulatedMotors = getConfiguration().getMotorConfigurationDescription(); + final Configuration configuration = getConfiguration(); + simulatedMotors = configuration.getMotorConfigurationDescription(); simulatedRocketID = rocket.getFunctionalModID(); - + status = Status.UPTODATE; fireChangeEvent(); + configuration.release(); } finally { mutex.unlock("simulate"); } } - - + + /** * Return the conditions used in the previous simulation, or null * if this simulation has not been run. - * + * * @return the conditions used in the previous simulation, or null. */ public SimulationOptions getSimulatedConditions() { mutex.verify(); return simulatedConditions; } - + /** * Return the warnings generated in the previous simulation, or * null if this simulation has not been run. This is the same * warning set as contained in the FlightData object. - * + * * @return the warnings during the previous simulation, or null. * @see FlightData#getWarningSet() */ @@ -339,12 +341,12 @@ public class Simulation implements ChangeSource, Cloneable { return null; return simulatedData.getWarningSet(); } - - + + /** * Return a string describing the motor configuration of the previous simulation, * or null if this simulation has not been run. - * + * * @return a description of the motor configuration of the previous simulation, or * null. * @see Rocket#getMotorConfigurationNameOrDescription(String) @@ -353,33 +355,33 @@ public class Simulation implements ChangeSource, Cloneable { mutex.verify(); return simulatedMotors; } - + /** * Return the flight data of the previous simulation, or null if * this simulation has not been run. - * + * * @return the flight data of the previous simulation, or null. */ public FlightData getSimulatedData() { mutex.verify(); 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. * This excludes any simulated data. - * + * * @return a copy of this simulation and its conditions. */ public Simulation copy() { mutex.lock("copy"); try { - + Simulation copy = (Simulation) super.clone(); - + copy.mutex = SafetyMutex.newInstance(); copy.status = Status.NOT_SIMULATED; copy.options = this.options.clone(); @@ -389,21 +391,21 @@ public class Simulation implements ChangeSource, Cloneable { copy.simulatedMotors = null; copy.simulatedData = null; copy.simulatedRocketID = -1; - + return copy; - + } catch (CloneNotSupportedException e) { throw new BugException("Clone not supported, BUG", e); } finally { mutex.unlock("copy"); } } - - + + /** * Create a duplicate of this simulation with the specified rocket. The new * simulation is in non-simulated state. - * + * * @param newRocket the rocket for the new simulation. * @return a new simulation with the same conditions and properties. */ @@ -411,33 +413,33 @@ public class Simulation implements ChangeSource, Cloneable { mutex.lock("duplicateSimulation"); try { Simulation copy = new Simulation(newRocket); - + copy.name = this.name; copy.options.copyFrom(this.options); copy.simulationListeners = this.simulationListeners.clone(); copy.simulationStepperClass = this.simulationStepperClass; copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass; - + return copy; } finally { mutex.unlock("duplicateSimulation"); } } - - + + @Override public void addChangeListener(EventListener listener) { mutex.verify(); listeners.add(listener); } - + @Override public void removeChangeListener(EventListener listener) { mutex.verify(); listeners.remove(listener); } - + protected void fireChangeEvent() { EventObject e = new EventObject(this); // 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 Status oldStatus = null; - + @Override public void stateChanged(EventObject e) { if (getStatus() != oldStatus) { diff --git a/core/src/net/sf/openrocket/file/rocksim/export/RocksimSaver.java b/core/src/net/sf/openrocket/file/rocksim/export/RocksimSaver.java index 9d601efb2..36f44d527 100644 --- a/core/src/net/sf/openrocket/file/rocksim/export/RocksimSaver.java +++ b/core/src/net/sf/openrocket/file/rocksim/export/RocksimSaver.java @@ -94,8 +94,10 @@ public class RocksimSaver extends RocketSaver { 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; + configuration.release(); int stageCount = rocket.getStageCount(); if (stageCount == 3) { result.setStage321CG(cg); diff --git a/core/src/net/sf/openrocket/gui/print/DesignReport.java b/core/src/net/sf/openrocket/gui/print/DesignReport.java index 1f0152fc0..a2a1ab40d 100644 --- a/core/src/net/sf/openrocket/gui/print/DesignReport.java +++ b/core/src/net/sf/openrocket/gui/print/DesignReport.java @@ -74,33 +74,33 @@ import com.itextpdf.text.pdf.PdfWriter; * */ public class DesignReport { - + /** * The logger. */ private static final LogHelper log = Application.getLogger(); public static final double SCALE_FUDGE_FACTOR = 0.4d; - + /** * The OR Document. */ private OpenRocketDocument rocketDocument; - + /** * A panel used for rendering of the design diagram. */ final RocketPanel panel; - + /** * The iText document. */ protected Document document; - + /** * The figure rotation. */ private double rotation = 0d; - + /** The displayed strings. */ private static final String STAGES = "Stages: "; 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 ROCKET_DESIGN = "Rocket Design"; private static final double GRAVITY_CONSTANT = 9.80665d; - + /** * Constructor. * @@ -140,7 +140,7 @@ public class DesignReport { panel = new RocketPanel(rocketDocument); rotation = figureRotation; } - + /** * Main entry point. Prints the rocket drawing and design data. * @@ -153,23 +153,23 @@ public class DesignReport { com.itextpdf.text.Rectangle pageSize = document.getPageSize(); int pageImageableWidth = (int) pageSize.getWidth() - (int) pageSize.getBorderWidth() * 2; int pageImageableHeight = (int) pageSize.getHeight() / 2 - (int) pageSize.getBorderWidthTop(); - + PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, ROCKET_DESIGN); - + Rocket rocket = rocketDocument.getRocket(); final Configuration configuration = rocket.getDefaultConfiguration().clone(); configuration.setAllStages(); PdfContentByte canvas = writer.getDirectContent(); - + final PrintFigure figure = new PrintFigure(configuration); figure.setRotation(rotation); - + FigureElement cp = panel.getExtraCP(); FigureElement cg = panel.getExtraCG(); RocketInfo text = panel.getExtraText(); - + double scale = paintRocketDiagram(pageImageableWidth, pageImageableHeight, canvas, figure, cp, cg); - + canvas.beginText(); canvas.setFontAndSize(ITextHelper.getBaseFont(), PrintUtilities.NORMAL_FONT_SIZE); 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); canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight); canvas.moveTextWithLeading(0, -16); - + float initialY = canvas.getYTLM(); - + canvas.showText(rocketDocument.getRocket().getName()); - + canvas.newlineShowText(STAGES); canvas.showText("" + rocket.getStageCount()); - - + + if (configuration.hasMotors()) { if (configuration.getStageCount() > 1) { canvas.newlineShowText(MASS_WITH_MOTORS); @@ -196,29 +196,29 @@ public class DesignReport { canvas.newlineShowText(MASS_EMPTY); } canvas.showText(text.getMass(UnitGroup.UNITS_MASS.getDefaultUnit())); - + canvas.newlineShowText(STABILITY); canvas.showText(text.getStability()); - + canvas.newlineShowText(CG); canvas.showText(text.getCg()); - + canvas.newlineShowText(CP); canvas.showText(text.getCp()); canvas.endText(); - + try { //Move the internal pointer of the document below that of what was just written using the direct byte buffer. Paragraph paragraph = new Paragraph(); float finalY = canvas.getYTLM(); int heightOfDiagramAndText = (int) (pageSize.getHeight() - (finalY - initialY + diagramHeight)); - + paragraph.setSpacingAfter(heightOfDiagramAndText); document.add(paragraph); - + String[] motorIds = rocket.getMotorConfigurationIDs(); List simulations = rocketDocument.getSimulations(); - + for (int j = 0; j < motorIds.length; j++) { String motorId = motorIds[j]; if (motorId != null) { @@ -242,8 +242,8 @@ public class DesignReport { log.error("Could not modify document.", e); } } - - + + /** * Paint a diagram of the rocket into the PDF document. * @@ -264,7 +264,7 @@ public class DesignReport { theFigure.addRelativeExtra(theCp); theFigure.addRelativeExtra(theCg); theFigure.updateFigure(); - + double scale = (thePageImageableWidth * 2.2) / theFigure.getFigureWidth(); theFigure.setScale(scale); @@ -273,7 +273,7 @@ public class DesignReport { */ theFigure.setSize(thePageImageableWidth, thePageImageableHeight); theFigure.updateFigure(); - + final DefaultFontMapper mapper = new DefaultFontMapper(); Graphics2D g2d = theCanvas.createGraphics(thePageImageableWidth, thePageImageableHeight * 2, mapper); final double halfFigureHeight = SCALE_FUDGE_FACTOR * theFigure.getFigureHeightPx() / 2; @@ -284,13 +284,13 @@ public class DesignReport { y += (int) halfFigureHeight; } g2d.translate(20, y); - + g2d.scale(SCALE_FUDGE_FACTOR, SCALE_FUDGE_FACTOR); theFigure.paint(g2d); g2d.dispose(); return scale; } - + /** * 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 */ private void addMotorData(Rocket rocket, String motorId, final PdfPTable parent) { - + PdfPTable motorTable = new PdfPTable(8); motorTable.setWidthPercentage(68); motorTable.setHorizontalAlignment(Element.ALIGN_LEFT); - + final PdfPCell motorCell = ITextHelper.createCell(MOTOR, PdfPCell.BOTTOM, PrintUtilities.SMALL); final int mPad = 10; 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(PROPELLANT_WT, PdfPCell.BOTTOM, PrintUtilities.SMALL)); motorTable.addCell(ITextHelper.createCell(SIZE, PdfPCell.BOTTOM, PrintUtilities.SMALL)); - + DecimalFormat ttwFormat = new DecimalFormat("0.00"); - + MassCalculator massCalc = new BasicMassCalculator(); - + Configuration config = new Configuration(rocket); config.setMotorConfigurationID(motorId); - + int totalMotorCount = 0; double totalPropMass = 0; double totalImpulse = 0; double totalTTW = 0; - + int stage = 0; double stageMass = 0; - + boolean topBorder = false; for (RocketComponent c : rocket) { - + if (c instanceof Stage) { config.setToStage(stage); stage++; @@ -342,26 +342,26 @@ public class DesignReport { totalTTW = 0; topBorder = true; } - + if (c instanceof MotorMount && ((MotorMount) c).isMotorMount()) { MotorMount mount = (MotorMount) c; - + if (mount.isMotorMount() && mount.getMotor(motorId) != null) { Motor motor = mount.getMotor(motorId); int motorCount = c.toAbsolute(Coordinate.NUL).length; - - + + int border = Rectangle.NO_BORDER; if (topBorder) { border = Rectangle.TOP; topBorder = false; } - + String name = motor.getDesignation(); if (motorCount > 1) { name += " (" + Chars.TIMES + motorCount + ")"; } - + final PdfPCell motorVCell = ITextHelper.createCell(name, border); motorVCell.setPaddingLeft(mPad); motorTable.addCell(motorVCell); @@ -373,21 +373,21 @@ public class DesignReport { UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(motor.getMaxThrustEstimate()), border)); motorTable.addCell(ITextHelper.createCell( UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(motor.getTotalImpulseEstimate()), border)); - + double ttw = motor.getAverageThrustEstimate() / (stageMass * GRAVITY_CONSTANT); motorTable.addCell(ITextHelper.createCell( ttwFormat.format(ttw) + ":1", border)); - + double propMass = (motor.getLaunchCG().weight - motor.getEmptyCG().weight); motorTable.addCell(ITextHelper.createCell( UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(propMass), border)); - + final Unit motorUnit = UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit(); motorTable.addCell(ITextHelper.createCell(motorUnit.toString(motor.getDiameter()) + "/" + motorUnit.toString(motor.getLength()) + " " + motorUnit.toString(), border)); - + // Sum up total count totalMotorCount += motorCount; totalPropMass += propMass * motorCount; @@ -396,7 +396,7 @@ public class DesignReport { } } } - + if (totalMotorCount > 1) { int border = Rectangle.TOP; final PdfPCell motorVCell = ITextHelper.createCell("Total:", border); @@ -412,16 +412,17 @@ public class DesignReport { motorTable.addCell(ITextHelper.createCell( UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(totalPropMass), border)); motorTable.addCell(ITextHelper.createCell("", border)); - + } - + PdfPCell c = new PdfPCell(motorTable); c.setBorder(PdfPCell.LEFT); c.setBorderWidthTop(0f); parent.addCell(c); + config.release(); } - - + + /** * 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 */ private void addFlightData(final FlightData flight, final Rocket theRocket, final String motorId, final PdfPTable parent, int leading) { - + // Output the flight data if (flight != null) { try { final Unit distanceUnit = UnitGroup.UNITS_DISTANCE.getDefaultUnit(); final Unit velocityUnit = UnitGroup.UNITS_VELOCITY.getDefaultUnit(); final Unit flightTimeUnit = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit(); - + PdfPTable labelTable = new PdfPTable(2); labelTable.setWidths(new int[] { 3, 2 }); final Paragraph chunk = ITextHelper.createParagraph(stripBrackets( theRocket.getMotorConfigurationNameOrDescription(motorId)), PrintUtilities.BOLD); chunk.setLeading(leading); chunk.setSpacingAfter(3f); - + document.add(chunk); - + final PdfPCell cell = ITextHelper.createCell(ALTITUDE, 2, 2); cell.setUseBorderPadding(false); cell.setBorderWidthTop(0f); labelTable.addCell(cell); labelTable.addCell(ITextHelper.createCell(distanceUnit.toStringUnit(flight.getMaxAltitude()), 2, 2)); - + labelTable.addCell(ITextHelper.createCell(FLIGHT_TIME, 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(flightTimeUnit.toStringUnit(flight.getTimeToApogee()), 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(MAX_VELOCITY, 2, 2)); labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getMaxVelocity()), 2, 2)); - + labelTable.addCell(ITextHelper.createCell(DEPLOYMENT_VELOCITY, 2, 2)); labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getDeploymentVelocity()), 2, 2)); - + labelTable.addCell(ITextHelper.createCell(LANDING_VELOCITY, 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 PdfPCell c = new PdfPCell(labelTable); 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 * flight data. @@ -512,7 +513,7 @@ public class DesignReport { } return flight; } - + /** * Strip [] brackets from a string. * @@ -523,7 +524,7 @@ public class DesignReport { private String stripBrackets(String target) { return stripLeftBracket(stripRightBracket(target)); } - + /** * Strip [ from a string. * @@ -534,7 +535,7 @@ public class DesignReport { private String stripLeftBracket(String target) { return target.replace("[", ""); } - + /** * Strip ] from a string. * @@ -545,5 +546,5 @@ public class DesignReport { private String stripRightBracket(String target) { return target.replace("]", ""); } - + } diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java index 173520624..538e85c9d 100644 --- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java +++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java @@ -31,53 +31,53 @@ import net.sf.openrocket.util.Quaternion; public class BasicEventSimulationEngine implements SimulationEngine { - + private static final Translator trans = Application.getTranslator(); private static final LogHelper log = Application.getLogger(); - + // TODO: MEDIUM: Allow selecting steppers private SimulationStepper flightStepper = new RK4SimulationStepper(); private SimulationStepper landingStepper = new BasicLandingStepper(); - + private SimulationStepper currentStepper; - + private SimulationStatus status; - - + + @Override public FlightData simulate(SimulationConditions simulationConditions) throws SimulationException { Set motorBurntOut = new HashSet(); - + // Set up flight data FlightData flightData = new FlightData(); - + // Set up rocket configuration Configuration configuration = setupConfiguration(simulationConditions); MotorInstanceConfiguration motorConfiguration = setupMotorConfiguration(configuration); if (motorConfiguration.getMotorIDs().isEmpty()) { throw new MotorIgnitionException("No motors defined in the simulation."); } - + // Initialize the simulation currentStepper = flightStepper; status = initialStatus(configuration, motorConfiguration, simulationConditions, flightData); status = currentStepper.initialize(status); - - + + SimulationListenerHelper.fireStartSimulation(status); // Get originating position (in case listener has modified launch position) Coordinate origin = status.getRocketPosition(); Coordinate originVelocity = status.getRocketVelocity(); - + try { double maxAlt = Double.NEGATIVE_INFINITY; - + // Start the simulation while (handleEvents()) { - + // Take the step double oldAlt = status.getRocketPosition().z; - + if (SimulationListenerHelper.firePreStep(status)) { // Step at most to the next event double maxStepTime = Double.MAX_VALUE; @@ -89,27 +89,27 @@ public class BasicEventSimulationEngine implements SimulationEngine { currentStepper.step(status, maxStepTime); } SimulationListenerHelper.firePostStep(status); - - + + // Check for NaN values in the simulation status checkNaN(); - + // Add altitude event addEvent(new FlightEvent(FlightEvent.Type.ALTITUDE, status.getSimulationTime(), status.getConfiguration().getRocket(), new Pair(oldAlt, status.getRocketPosition().z))); - + if (status.getRocketPosition().z > maxAlt) { maxAlt = status.getRocketPosition().z; } - - + + // Position relative to start location Coordinate relativePosition = status.getRocketPosition().sub(origin); - + // Add appropriate events if (!status.isLiftoff()) { - + // Avoid sinking into ground before liftoff if (relativePosition.z < 0) { status.setRocketPosition(origin); @@ -119,32 +119,32 @@ public class BasicEventSimulationEngine implements SimulationEngine { if (relativePosition.z > 0.02) { addEvent(new FlightEvent(FlightEvent.Type.LIFTOFF, status.getSimulationTime())); } - + } else { - + // Check ground hit after liftoff if (status.getRocketPosition().z < 0) { status.setRocketPosition(status.getRocketPosition().setZ(0)); addEvent(new FlightEvent(FlightEvent.Type.GROUND_HIT, status.getSimulationTime())); addEvent(new FlightEvent(FlightEvent.Type.SIMULATION_END, status.getSimulationTime())); } - + } - + // Check for launch guide clearance if (!status.isLaunchRodCleared() && relativePosition.length() > status.getSimulationConditions().getLaunchRodLength()) { addEvent(new FlightEvent(FlightEvent.Type.LAUNCHROD, status.getSimulationTime(), null)); } - - + + // Check for apogee if (!status.isApogeeReached() && status.getRocketPosition().z < maxAlt - 0.01) { addEvent(new FlightEvent(FlightEvent.Type.APOGEE, status.getSimulationTime(), status.getConfiguration().getRocket())); } - - + + // Check for burnt out motors for (MotorId motorId : status.getMotorConfiguration().getMotorIDs()) { MotorInstance motor = status.getMotorConfiguration().getMotorInstance(motorId); @@ -153,58 +153,59 @@ public class BasicEventSimulationEngine implements SimulationEngine { (RocketComponent) status.getMotorConfiguration().getMotorMount(motorId), motorId)); } } - + } - + } catch (SimulationException e) { SimulationListenerHelper.fireEndSimulation(status, e); throw e; } - + SimulationListenerHelper.fireEndSimulation(status, null); - + flightData.addBranch(status.getFlightData()); - + if (!flightData.getWarningSet().isEmpty()) { log.info("Warnings at the end of simulation: " + flightData.getWarningSet()); } - + + configuration.release(); // TODO: HIGH: Simulate branches return flightData; } - - - + + + private SimulationStatus initialStatus(Configuration configuration, MotorInstanceConfiguration motorConfiguration, SimulationConditions simulationConditions, FlightData flightData) { - + SimulationStatus init = new SimulationStatus(); init.setSimulationConditions(simulationConditions); init.setConfiguration(configuration); init.setMotorConfiguration(motorConfiguration); - + init.setSimulationTime(0); init.setPreviousTimeStep(simulationConditions.getTimeStep()); init.setRocketPosition(Coordinate.NUL); init.setRocketVelocity(Coordinate.NUL); init.setRocketWorldPosition(simulationConditions.getLaunchSite()); - + // Initialize to roll angle with least stability w.r.t. the wind Quaternion o; FlightConditions cond = new FlightConditions(configuration); simulationConditions.getAerodynamicCalculator().getWorstCP(configuration, cond, null); double angle = -cond.getTheta() - simulationConditions.getLaunchRodDirection(); o = Quaternion.rotation(new Coordinate(0, 0, angle)); - + // Launch rod angle and direction o = o.multiplyLeft(Quaternion.rotation(new Coordinate(0, simulationConditions.getLaunchRodAngle(), 0))); o = o.multiplyLeft(Quaternion.rotation(new Coordinate(0, 0, simulationConditions.getLaunchRodDirection()))); - + init.setRocketOrientationQuaternion(o); init.setRocketRotationVelocity(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. @@ -230,29 +231,29 @@ public class BasicEventSimulationEngine implements SimulationEngine { } } init.setEffectiveLaunchRodLength(length); - - - + + + init.setSimulationStartWallTime(System.nanoTime()); - + init.setMotorIgnited(false); init.setLiftoff(false); init.setLaunchRodCleared(false); init.setApogeeReached(false); - + init.getEventQueue().add(new FlightEvent(FlightEvent.Type.LAUNCH, 0, simulationConditions.getRocket())); - + init.setFlightData(new FlightDataBranch("MAIN", FlightDataType.TYPE_TIME)); init.setWarnings(flightData.getWarningSet()); - + return init; } - - - + + + /** * Create a rocket configuration from the launch conditions. - * + * * @param simulation the launch conditions. * @return a rocket configuration with all stages attached. */ @@ -260,28 +261,28 @@ public class BasicEventSimulationEngine implements SimulationEngine { Configuration configuration = new Configuration(simulation.getRocket()); configuration.setAllStages(); configuration.setMotorConfigurationID(simulation.getMotorConfigurationID()); - + return configuration; } - - - + + + /** * Create a new motor instance configuration for the rocket configuration. - * + * * @param configuration the rocket configuration. * @return a new motor instance configuration with all motors in place. */ private MotorInstanceConfiguration setupMotorConfiguration(Configuration configuration) { MotorInstanceConfiguration motors = new MotorInstanceConfiguration(); final String motorId = configuration.getMotorConfigurationID(); - + Iterator iterator = configuration.motorIterator(); while (iterator.hasNext()) { MotorMount mount = iterator.next(); RocketComponent component = (RocketComponent) mount; Motor motor = mount.getMotor(motorId); - + if (motor != null) { Coordinate[] positions = component.toAbsolute(mount.getMotorPosition(motorId)); for (int i = 0; i < positions.length; i++) { @@ -293,7 +294,7 @@ public class BasicEventSimulationEngine implements SimulationEngine { } return motors; } - + /** * Handles events occurring during the flight from the event queue. * 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 { boolean ret = true; FlightEvent event; - + for (event = nextEvent(); event != null; event = nextEvent()) { - + // Ignore events for components that are no longer attached to the rocket if (event.getSource() != null && event.getSource().getParent() != null && !status.getConfiguration().isStageActive(event.getSource().getStageNumber())) { continue; } - + // Call simulation listeners, allow aborting event handling if (!SimulationListenerHelper.fireHandleFlightEvent(status, event)) { continue; } - + if (event.getType() != FlightEvent.Type.ALTITUDE) { log.verbose("BasicEventSimulationEngine: Handling event " + event); } - + if (event.getType() == FlightEvent.Type.IGNITION) { MotorMount mount = (MotorMount) event.getSource(); MotorId motorId = (MotorId) event.getData(); @@ -328,42 +329,42 @@ public class BasicEventSimulationEngine implements SimulationEngine { continue; } } - + if (event.getType() == FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT) { RecoveryDevice device = (RecoveryDevice) event.getSource(); if (!SimulationListenerHelper.fireRecoveryDeviceDeployment(status, device)) { continue; } } - - - + + + // Check for motor ignition events, add ignition events to queue for (MotorId id : status.getMotorConfiguration().getMotorIDs()) { MotorMount mount = status.getMotorConfiguration().getMotorMount(id); RocketComponent component = (RocketComponent) mount; - + if (mount.getIgnitionEvent().isActivationEvent(event, component)) { addEvent(new FlightEvent(FlightEvent.Type.IGNITION, status.getSimulationTime() + mount.getIgnitionDelay(), component, id)); } } - - + + // Check for stage separation event for (int stageNo : status.getConfiguration().getActiveStages()) { if (stageNo == 0) continue; - + Stage stage = (Stage) status.getConfiguration().getRocket().getChild(stageNo); if (stage.getSeparationEvent().isSeparationEvent(event, stage)) { addEvent(new FlightEvent(FlightEvent.Type.STAGE_SEPARATION, event.getTime() + stage.getSeparationDelay(), stage)); } } - - + + // Check for recovery device deployment, add events to queue Iterator rci = status.getConfiguration().iterator(); while (rci.hasNext()) { @@ -376,16 +377,16 @@ public class BasicEventSimulationEngine implements SimulationEngine { event.getTime() + Math.max(0.001, ((RecoveryDevice) c).getDeployDelay()), c)); } } - - + + // Handle event switch (event.getType()) { - + case LAUNCH: { status.getFlightData().addEvent(event); break; } - + case IGNITION: { // Ignite the motor MotorMount mount = (MotorMount) event.getSource(); @@ -395,24 +396,24 @@ public class BasicEventSimulationEngine implements SimulationEngine { config.setMotorIgnitionTime(motorId, event.getTime()); status.setMotorIgnited(true); status.getFlightData().addEvent(event); - + break; } - + case LIFTOFF: { // Mark lift-off as occurred status.setLiftoff(true); status.getFlightData().addEvent(event); break; } - + case LAUNCHROD: { // Mark launch rod as cleared status.setLaunchRodCleared(true); status.getFlightData().addEvent(event); break; } - + case BURNOUT: { // If motor burnout occurs without lift-off, abort if (!status.isLiftoff()) { @@ -429,12 +430,12 @@ public class BasicEventSimulationEngine implements SimulationEngine { status.getFlightData().addEvent(event); break; } - + case EJECTION_CHARGE: { status.getFlightData().addEvent(event); break; } - + case STAGE_SEPARATION: { // TODO: HIGH: Store lower stages to be simulated later RocketComponent stage = event.getSource(); @@ -443,20 +444,20 @@ public class BasicEventSimulationEngine implements SimulationEngine { status.getFlightData().addEvent(event); break; } - + case APOGEE: // Mark apogee as reached status.setApogeeReached(true); status.getFlightData().addEvent(event); break; - + case RECOVERY_DEVICE_DEPLOYMENT: RocketComponent c = event.getSource(); int n = c.getStageNumber(); // Ignore event if stage not active if (status.getConfiguration().isStageActive(n)) { // TODO: HIGH: Check stage activeness for other events as well? - + // Check whether any motor in the active stages is active anymore for (MotorId motorId : status.getMotorConfiguration().getMotorIDs()) { int stage = ((RocketComponent) status.getMotorConfiguration(). @@ -467,12 +468,12 @@ public class BasicEventSimulationEngine implements SimulationEngine { continue; status.getWarnings().add(Warning.RECOVERY_DEPLOYMENT_WHILE_BURNING); } - + // Check for launch rod if (!status.isLaunchRodCleared()) { status.getWarnings().add(Warning.RECOVERY_LAUNCH_ROD); } - + // Check current velocity if (status.getRocketVelocity().length() > 20) { // TODO: LOW: Custom warning. @@ -481,44 +482,44 @@ public class BasicEventSimulationEngine implements SimulationEngine { + UnitGroup.UNITS_VELOCITY.toStringUnit(status.getRocketVelocity().length()) + ").")); } - + status.setLiftoff(true); status.getDeployedRecoveryDevices().add((RecoveryDevice) c); - + this.currentStepper = this.landingStepper; this.status = currentStepper.initialize(status); - + status.getFlightData().addEvent(event); } break; - + case GROUND_HIT: status.getFlightData().addEvent(event); break; - + case SIMULATION_END: ret = false; status.getFlightData().addEvent(event); break; - + case ALTITUDE: break; } - + } - - + + // If no motor has ignited, abort if (!status.isMotorIgnited()) { throw new MotorIgnitionException("No motors ignited."); } - + return ret; } - + /** * Add a flight event to the event queue unless a listener aborts adding it. - * + * * @param event the event to add to the queue. */ private void addEvent(FlightEvent event) throws SimulationException { @@ -526,15 +527,14 @@ public class BasicEventSimulationEngine implements SimulationEngine { status.getEventQueue().add(event); } } - - - + + + /** * 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. * The flight event is removed from the event queue. - * - * @param status the simulation status + * * @return the flight event to handle, or null */ private FlightEvent nextEvent() { @@ -542,7 +542,7 @@ public class BasicEventSimulationEngine implements SimulationEngine { FlightEvent event = queue.peek(); if (event == null) return null; - + // Jump to event if no motors have been ignited if (!status.isMotorIgnited() && event.getTime() > status.getSimulationTime()) { status.setSimulationTime(event.getTime()); @@ -553,9 +553,9 @@ public class BasicEventSimulationEngine implements SimulationEngine { return null; } } - - - + + + private void checkNaN() throws SimulationException { double d = 0; boolean b = false; @@ -566,7 +566,7 @@ public class BasicEventSimulationEngine implements SimulationEngine { b |= status.getRocketOrientationQuaternion().isNaN(); b |= status.getRocketRotationVelocity().isNaN(); d += status.getEffectiveLaunchRodLength(); - + if (Double.isNaN(d) || b) { log.error("Simulation resulted in NaN value:" + " 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."); } } - - + + }