1 package net.sf.openrocket.simulation;
3 import java.util.ArrayList;
5 import java.util.Random;
7 import javax.swing.event.ChangeEvent;
8 import javax.swing.event.ChangeListener;
10 import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
11 import net.sf.openrocket.masscalc.BasicMassCalculator;
12 import net.sf.openrocket.models.atmosphere.AtmosphericModel;
13 import net.sf.openrocket.models.atmosphere.ExtendedISAModel;
14 import net.sf.openrocket.models.gravity.BasicGravityModel;
15 import net.sf.openrocket.models.wind.PinkNoiseWindModel;
16 import net.sf.openrocket.rocketcomponent.Rocket;
17 import net.sf.openrocket.util.BugException;
18 import net.sf.openrocket.util.ChangeSource;
19 import net.sf.openrocket.util.MathUtil;
20 import net.sf.openrocket.util.Utils;
23 * A class holding simulation options in basic parameter form and which functions
24 * as a ChangeSource. A SimulationConditions instance is generated from this class
25 * using {@link #toSimulationConditions()}.
27 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
29 public class SimulationOptions implements ChangeSource, Cloneable {
31 public static final double MAX_LAUNCH_ROD_ANGLE = Math.PI / 3;
34 * The ISA standard atmosphere.
36 private static final AtmosphericModel ISA_ATMOSPHERIC_MODEL = new ExtendedISAModel();
39 private final Rocket rocket;
40 private String motorID = null;
44 * NOTE: When adding/modifying parameters, they must also be added to the
45 * equals and copyFrom methods!!
48 // TODO: HIGH: Fetch default values from Prefs!
50 private double launchRodLength = 1;
52 /** Launch rod angle > 0, radians from vertical */
53 private double launchRodAngle = 0;
55 /** Launch rod direction, 0 = upwind, PI = downwind. */
56 private double launchRodDirection = 0;
59 private double windAverage = 2.0;
60 private double windTurbulence = 0.1;
62 private double launchAltitude = 0;
63 private double launchLatitude = 45;
65 private boolean useISA = true;
66 private double launchTemperature = ExtendedISAModel.STANDARD_TEMPERATURE;
67 private double launchPressure = ExtendedISAModel.STANDARD_PRESSURE;
70 private double timeStep = RK4SimulationStepper.RECOMMENDED_TIME_STEP;
71 private double maximumAngle = RK4SimulationStepper.RECOMMENDED_ANGLE_STEP;
73 private int randomSeed = new Random().nextInt();
75 private boolean calculateExtras = true;
78 private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
82 public SimulationOptions(Rocket rocket) {
88 public Rocket getRocket() {
93 public String getMotorConfigurationID() {
98 * Set the motor configuration ID. This must be a valid motor configuration ID of
99 * the rocket, otherwise the configuration is set to <code>null</code>.
101 * @param id the configuration to set.
103 public void setMotorConfigurationID(String id) {
106 if (!rocket.isMotorConfigurationID(id))
115 public double getLaunchRodLength() {
116 return launchRodLength;
119 public void setLaunchRodLength(double launchRodLength) {
120 if (MathUtil.equals(this.launchRodLength, launchRodLength))
122 this.launchRodLength = launchRodLength;
127 public double getLaunchRodAngle() {
128 return launchRodAngle;
131 public void setLaunchRodAngle(double launchRodAngle) {
132 launchRodAngle = MathUtil.clamp(launchRodAngle, 0, MAX_LAUNCH_ROD_ANGLE);
133 if (MathUtil.equals(this.launchRodAngle, launchRodAngle))
135 this.launchRodAngle = launchRodAngle;
140 public double getLaunchRodDirection() {
141 return launchRodDirection;
144 public void setLaunchRodDirection(double launchRodDirection) {
145 launchRodDirection = MathUtil.reduce180(launchRodDirection);
146 if (MathUtil.equals(this.launchRodDirection, launchRodDirection))
148 this.launchRodDirection = launchRodDirection;
154 public double getWindSpeedAverage() {
158 public void setWindSpeedAverage(double windAverage) {
159 if (MathUtil.equals(this.windAverage, windAverage))
161 this.windAverage = MathUtil.max(windAverage, 0);
166 public double getWindSpeedDeviation() {
167 return windAverage * windTurbulence;
170 public void setWindSpeedDeviation(double windDeviation) {
171 if (windAverage < 0.1) {
174 setWindTurbulenceIntensity(windDeviation / windAverage);
179 * Return the wind turbulence intensity (standard deviation / average).
181 * @return the turbulence intensity
183 public double getWindTurbulenceIntensity() {
184 return windTurbulence;
188 * Set the wind standard deviation to match the given turbulence intensity.
190 * @param intensity the turbulence intensity
192 public void setWindTurbulenceIntensity(double intensity) {
193 // Does not check equality so that setWindSpeedDeviation can be sure of event firing
194 this.windTurbulence = intensity;
202 public double getLaunchAltitude() {
203 return launchAltitude;
206 public void setLaunchAltitude(double altitude) {
207 if (MathUtil.equals(this.launchAltitude, altitude))
209 this.launchAltitude = altitude;
214 public double getLaunchLatitude() {
215 return launchLatitude;
218 public void setLaunchLatitude(double launchLatitude) {
219 launchLatitude = MathUtil.clamp(launchLatitude, -90, 90);
220 if (MathUtil.equals(this.launchLatitude, launchLatitude))
222 this.launchLatitude = launchLatitude;
230 public boolean isISAAtmosphere() {
234 public void setISAAtmosphere(boolean isa) {
242 public double getLaunchTemperature() {
243 return launchTemperature;
248 public void setLaunchTemperature(double launchTemperature) {
249 if (MathUtil.equals(this.launchTemperature, launchTemperature))
251 this.launchTemperature = launchTemperature;
257 public double getLaunchPressure() {
258 return launchPressure;
263 public void setLaunchPressure(double launchPressure) {
264 if (MathUtil.equals(this.launchPressure, launchPressure))
266 this.launchPressure = launchPressure;
272 * Returns an atmospheric model corresponding to the launch conditions. The
273 * atmospheric models may be shared between different calls.
275 * @return an AtmosphericModel object.
277 private AtmosphericModel getAtmosphericModel() {
279 return ISA_ATMOSPHERIC_MODEL;
281 return new ExtendedISAModel(launchAltitude, launchTemperature, launchPressure);
285 public double getTimeStep() {
289 public void setTimeStep(double timeStep) {
290 if (MathUtil.equals(this.timeStep, timeStep))
292 this.timeStep = timeStep;
296 public double getMaximumStepAngle() {
300 public void setMaximumStepAngle(double maximumAngle) {
301 maximumAngle = MathUtil.clamp(maximumAngle, 1 * Math.PI / 180, 20 * Math.PI / 180);
302 if (MathUtil.equals(this.maximumAngle, maximumAngle))
304 this.maximumAngle = maximumAngle;
310 public boolean getCalculateExtras() {
311 return calculateExtras;
316 public void setCalculateExtras(boolean calculateExtras) {
317 if (this.calculateExtras == calculateExtras)
319 this.calculateExtras = calculateExtras;
325 public int getRandomSeed() {
329 public void setRandomSeed(int randomSeed) {
330 if (this.randomSeed == randomSeed) {
333 this.randomSeed = randomSeed;
335 * This does not fire an event since we don't want to invalidate simulation results
336 * due to changing the seed value. This needs to be revisited if the user is ever
337 * allowed to select the seed value.
339 // fireChangeEvent();
343 * Randomize the random seed value.
345 public void randomizeSeed() {
346 this.randomSeed = new Random().nextInt();
347 // fireChangeEvent();
353 public SimulationOptions clone() {
355 SimulationOptions copy = (SimulationOptions) super.clone();
356 copy.listeners = new ArrayList<ChangeListener>();
358 } catch (CloneNotSupportedException e) {
359 throw new BugException(e);
364 public void copyFrom(SimulationOptions src) {
366 if (this.rocket == src.rocket) {
368 this.motorID = src.motorID;
372 if (src.rocket.hasMotors(src.motorID)) {
373 // Try to find a matching motor ID
374 String motorDesc = src.rocket.getMotorConfigurationDescription(src.motorID);
375 String matchID = null;
377 for (String id : this.rocket.getMotorConfigurationIDs()) {
378 if (motorDesc.equals(this.rocket.getMotorConfigurationDescription(id))) {
384 this.motorID = matchID;
390 this.launchAltitude = src.launchAltitude;
391 this.launchLatitude = src.launchLatitude;
392 this.launchPressure = src.launchPressure;
393 this.launchRodAngle = src.launchRodAngle;
394 this.launchRodDirection = src.launchRodDirection;
395 this.launchRodLength = src.launchRodLength;
396 this.launchTemperature = src.launchTemperature;
397 this.maximumAngle = src.maximumAngle;
398 this.timeStep = src.timeStep;
399 this.windAverage = src.windAverage;
400 this.windTurbulence = src.windTurbulence;
401 this.calculateExtras = src.calculateExtras;
402 this.randomSeed = src.randomSeed;
410 * Compares whether the two simulation conditions are equal. The two are considered
411 * equal if the rocket, motor id and all variables are equal.
414 public boolean equals(Object other) {
415 if (!(other instanceof SimulationOptions))
417 SimulationOptions o = (SimulationOptions) other;
418 return ((this.rocket == o.rocket) &&
419 Utils.equals(this.motorID, o.motorID) &&
420 MathUtil.equals(this.launchAltitude, o.launchAltitude) &&
421 MathUtil.equals(this.launchLatitude, o.launchLatitude) &&
422 MathUtil.equals(this.launchPressure, o.launchPressure) &&
423 MathUtil.equals(this.launchRodAngle, o.launchRodAngle) &&
424 MathUtil.equals(this.launchRodDirection, o.launchRodDirection) &&
425 MathUtil.equals(this.launchRodLength, o.launchRodLength) &&
426 MathUtil.equals(this.launchTemperature, o.launchTemperature) &&
427 MathUtil.equals(this.maximumAngle, o.maximumAngle) &&
428 MathUtil.equals(this.timeStep, o.timeStep) &&
429 MathUtil.equals(this.windAverage, o.windAverage) &&
430 MathUtil.equals(this.windTurbulence, o.windTurbulence) &&
431 this.calculateExtras == o.calculateExtras && this.randomSeed == o.randomSeed);
435 * Hashcode method compatible with {@link #equals(Object)}.
438 public int hashCode() {
440 return rocket.hashCode();
441 return rocket.hashCode() + motorID.hashCode();
445 public void addChangeListener(ChangeListener listener) {
446 listeners.add(listener);
450 public void removeChangeListener(ChangeListener listener) {
451 listeners.remove(listener);
454 private final ChangeEvent event = new ChangeEvent(this);
456 private void fireChangeEvent() {
457 ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
459 for (int i = array.length - 1; i >= 0; i--) {
460 array[i].stateChanged(event);
465 // TODO: HIGH: Clean up
466 public SimulationConditions toSimulationConditions() {
467 SimulationConditions conditions = new SimulationConditions();
469 conditions.setRocket((Rocket) getRocket().copy());
470 conditions.setMotorConfigurationID(getMotorConfigurationID());
471 conditions.setLaunchRodLength(getLaunchRodLength());
472 conditions.setLaunchRodAngle(getLaunchRodAngle());
473 conditions.setLaunchRodDirection(getLaunchRodDirection());
474 conditions.setLaunchAltitude(getLaunchAltitude());
475 conditions.setLaunchLatitude(getLaunchLatitude());
476 conditions.setRandomSeed(randomSeed);
478 PinkNoiseWindModel windModel = new PinkNoiseWindModel(randomSeed);
479 windModel.setAverage(getWindSpeedAverage());
480 windModel.setStandardDeviation(getWindSpeedDeviation());
481 conditions.setWindModel(windModel);
483 conditions.setAtmosphericModel(getAtmosphericModel());
485 BasicGravityModel gravityModel = new BasicGravityModel(getLaunchLatitude());
486 conditions.setGravityModel(gravityModel);
488 conditions.setAerodynamicCalculator(new BarrowmanCalculator());
489 conditions.setMassCalculator(new BasicMassCalculator());
491 conditions.setTimeStep(getTimeStep());
492 conditions.setMaximumAngleStep(getMaximumStepAngle());
494 conditions.setCalculateExtras(getCalculateExtras());