package net.sf.openrocket.simulation; import net.sf.openrocket.masscalc.MassCalculator; import net.sf.openrocket.models.atmosphere.AtmosphericConditions; import net.sf.openrocket.motor.MotorId; import net.sf.openrocket.motor.MotorInstance; import net.sf.openrocket.motor.MotorInstanceConfiguration; import net.sf.openrocket.rocketcomponent.Configuration; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.simulation.listeners.SimulationListenerHelper; import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.Quaternion; public abstract class AbstractSimulationStepper implements SimulationStepper { /** * Compute the atmospheric conditions, allowing listeners to override. * * @param status the simulation status * @return the atmospheric conditions to use * @throws SimulationException if a listener throws SimulationException */ protected AtmosphericConditions modelAtmosphericConditions(SimulationStatus status) throws SimulationException { AtmosphericConditions conditions; // Call pre-listener conditions = SimulationListenerHelper.firePreAtmosphericModel(status); if (conditions != null) { return conditions; } // Compute conditions double altitude = status.getRocketPosition().z + status.getSimulationConditions().getLaunchSite().getAltitude(); conditions = status.getSimulationConditions().getAtmosphericModel().getConditions(altitude); // Call post-listener conditions = SimulationListenerHelper.firePostAtmosphericModel(status, conditions); checkNaN(conditions.getPressure()); checkNaN(conditions.getTemperature()); return conditions; } /** * Compute the wind to use, allowing listeners to override. * * @param status the simulation status * @return the wind conditions to use * @throws SimulationException if a listener throws SimulationException */ protected Coordinate modelWindVelocity(SimulationStatus status) throws SimulationException { Coordinate wind; // Call pre-listener wind = SimulationListenerHelper.firePreWindModel(status); if (wind != null) { return wind; } // Compute conditions double altitude = status.getRocketPosition().z + status.getSimulationConditions().getLaunchSite().getAltitude(); wind = status.getSimulationConditions().getWindModel().getWindVelocity(status.getSimulationTime(), altitude); // Call post-listener wind = SimulationListenerHelper.firePostWindModel(status, wind); checkNaN(wind); return wind; } /** * Compute the gravity to use, allowing listeners to override. * * @param status the simulation status * @return the gravitational acceleration to use * @throws SimulationException if a listener throws SimulationException */ protected double modelGravity(SimulationStatus status) throws SimulationException { double gravity; // Call pre-listener gravity = SimulationListenerHelper.firePreGravityModel(status); if (!Double.isNaN(gravity)) { return gravity; } // Compute conditions gravity = status.getSimulationConditions().getGravityModel().getGravity(status.getRocketWorldPosition()); // Call post-listener gravity = SimulationListenerHelper.firePostGravityModel(status, gravity); checkNaN(gravity); return gravity; } /** * Compute the mass data to use, allowing listeners to override. * * @param status the simulation status * @return the mass data to use * @throws SimulationException if a listener throws SimulationException */ protected MassData calculateMassData(SimulationStatus status) throws SimulationException { MassData mass; Coordinate cg; double longitudinalInertia, rotationalInertia, propellantMass; // Call pre-listener mass = SimulationListenerHelper.firePreMassCalculation(status); if (mass != null) { return mass; } MassCalculator calc = status.getSimulationConditions().getMassCalculator(); cg = calc.getCG(status.getConfiguration(), status.getMotorConfiguration()); longitudinalInertia = calc.getLongitudinalInertia(status.getConfiguration(), status.getMotorConfiguration()); rotationalInertia = calc.getRotationalInertia(status.getConfiguration(), status.getMotorConfiguration()); propellantMass = calc.getPropellantMass(status.getConfiguration(), status.getMotorConfiguration()); mass = new MassData(cg, longitudinalInertia, rotationalInertia, propellantMass); // Call post-listener mass = SimulationListenerHelper.firePostMassCalculation(status, mass); checkNaN(mass.getCG()); checkNaN(mass.getLongitudinalInertia()); checkNaN(mass.getRotationalInertia()); checkNaN(mass.getPropellantMass()); return mass; } /** * Calculate the average thrust produced by the motors in the current configuration, allowing * listeners to override. The average is taken between <code>status.time</code> and * <code>status.time + timestep</code>. * <p> * TODO: HIGH: This method does not take into account any moments generated by off-center motors. * * @param status the current simulation status. * @param timestep the time step of the current iteration. * @param acceleration the current (approximate) acceleration * @param atmosphericConditions the current atmospheric conditions * @param stepMotors whether to step the motors forward or work on a clone object * @return the average thrust during the time step. */ protected double calculateThrust(SimulationStatus status, double timestep, double acceleration, AtmosphericConditions atmosphericConditions, boolean stepMotors) throws SimulationException { double thrust; // Pre-listeners thrust = SimulationListenerHelper.firePreThrustCalculation(status); if (!Double.isNaN(thrust)) { return thrust; } Configuration configuration = status.getConfiguration(); // Iterate over the motors and calculate combined thrust MotorInstanceConfiguration mic = status.getMotorConfiguration(); if (!stepMotors) { mic = mic.clone(); } mic.step(status.getSimulationTime() + timestep, acceleration, atmosphericConditions); thrust = 0; for (MotorId id : mic.getMotorIDs()) { if (configuration.isComponentActive((RocketComponent) mic.getMotorMount(id))) { MotorInstance motor = mic.getMotorInstance(id); thrust += motor.getThrust(); } } // Post-listeners thrust = SimulationListenerHelper.firePostThrustCalculation(status, thrust); checkNaN(thrust); return thrust; } /** * Check that the provided value is not NaN. * * @param d the double value to check. * @throws BugException if the value is NaN. */ protected void checkNaN(double d) { if (Double.isNaN(d)) { throw new BugException("Simulation resulted in not-a-number (NaN) value, please report a bug."); } } /** * Check that the provided coordinate is not NaN. * * @param c the coordinate value to check. * @throws BugException if the value is NaN. */ protected void checkNaN(Coordinate c) { if (c.isNaN()) { throw new BugException("Simulation resulted in not-a-number (NaN) value, please report a bug, c=" + c); } } /** * Check that the provided quaternion is not NaN. * * @param q the quaternion value to check. * @throws BugException if the value is NaN. */ protected void checkNaN(Quaternion q) { if (q.isNaN()) { throw new BugException("Simulation resulted in not-a-number (NaN) value, please report a bug, q=" + q); } } }