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;
22 * A class holding simulation options in basic parameter form and which functions
23 * as a ChangeSource. A SimulationConditions instance is generated from this class
24 * using {@link #toSimulationConditions()}.
26 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
28 public class SimulationOptions implements ChangeSource, Cloneable {
30 public static final double MAX_LAUNCH_ROD_ANGLE = Math.PI / 3;
33 * The ISA standard atmosphere.
35 private static final AtmosphericModel ISA_ATMOSPHERIC_MODEL = new ExtendedISAModel();
38 private final Rocket rocket;
39 private String motorID = null;
43 * NOTE: When adding/modifying parameters, they must also be added to the
44 * equals and copyFrom methods!!
47 // TODO: HIGH: Fetch default values from Prefs!
49 private double launchRodLength = 1;
51 /** Launch rod angle > 0, radians from vertical */
52 private double launchRodAngle = 0;
54 /** Launch rod direction, 0 = upwind, PI = downwind. */
55 private double launchRodDirection = 0;
58 private double windAverage = 2.0;
59 private double windTurbulence = 0.1;
61 private double launchAltitude = 0;
62 private double launchLatitude = 45;
64 private boolean useISA = true;
65 private double launchTemperature = ExtendedISAModel.STANDARD_TEMPERATURE;
66 private double launchPressure = ExtendedISAModel.STANDARD_PRESSURE;
69 private double timeStep = RK4SimulationStepper.RECOMMENDED_TIME_STEP;
70 private double maximumAngle = RK4SimulationStepper.RECOMMENDED_ANGLE_STEP;
72 private int randomSeed = new Random().nextInt();
74 private boolean calculateExtras = true;
77 private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
81 public SimulationOptions(Rocket rocket) {
87 public Rocket getRocket() {
92 public String getMotorConfigurationID() {
97 * Set the motor configuration ID. This must be a valid motor configuration ID of
98 * the rocket, otherwise the configuration is set to <code>null</code>.
100 * @param id the configuration to set.
102 public void setMotorConfigurationID(String id) {
105 if (!rocket.isMotorConfigurationID(id))
114 public double getLaunchRodLength() {
115 return launchRodLength;
118 public void setLaunchRodLength(double launchRodLength) {
119 if (MathUtil.equals(this.launchRodLength, launchRodLength))
121 this.launchRodLength = launchRodLength;
126 public double getLaunchRodAngle() {
127 return launchRodAngle;
130 public void setLaunchRodAngle(double launchRodAngle) {
131 launchRodAngle = MathUtil.clamp(launchRodAngle, 0, MAX_LAUNCH_ROD_ANGLE);
132 if (MathUtil.equals(this.launchRodAngle, launchRodAngle))
134 this.launchRodAngle = launchRodAngle;
139 public double getLaunchRodDirection() {
140 return launchRodDirection;
143 public void setLaunchRodDirection(double launchRodDirection) {
144 launchRodDirection = MathUtil.reduce180(launchRodDirection);
145 if (MathUtil.equals(this.launchRodDirection, launchRodDirection))
147 this.launchRodDirection = launchRodDirection;
153 public double getWindSpeedAverage() {
157 public void setWindSpeedAverage(double windAverage) {
158 if (MathUtil.equals(this.windAverage, windAverage))
160 this.windAverage = MathUtil.max(windAverage, 0);
165 public double getWindSpeedDeviation() {
166 return windAverage * windTurbulence;
169 public void setWindSpeedDeviation(double windDeviation) {
170 if (windAverage < 0.1) {
173 setWindTurbulenceIntensity(windDeviation / windAverage);
178 * Return the wind turbulence intensity (standard deviation / average).
180 * @return the turbulence intensity
182 public double getWindTurbulenceIntensity() {
183 return windTurbulence;
187 * Set the wind standard deviation to match the given turbulence intensity.
189 * @param intensity the turbulence intensity
191 public void setWindTurbulenceIntensity(double intensity) {
192 // Does not check equality so that setWindSpeedDeviation can be sure of event firing
193 this.windTurbulence = intensity;
201 public double getLaunchAltitude() {
202 return launchAltitude;
205 public void setLaunchAltitude(double altitude) {
206 if (MathUtil.equals(this.launchAltitude, altitude))
208 this.launchAltitude = altitude;
213 public double getLaunchLatitude() {
214 return launchLatitude;
217 public void setLaunchLatitude(double launchLatitude) {
218 launchLatitude = MathUtil.clamp(launchLatitude, -90, 90);
219 if (MathUtil.equals(this.launchLatitude, launchLatitude))
221 this.launchLatitude = launchLatitude;
229 public boolean isISAAtmosphere() {
233 public void setISAAtmosphere(boolean isa) {
241 public double getLaunchTemperature() {
242 return launchTemperature;
247 public void setLaunchTemperature(double launchTemperature) {
248 if (MathUtil.equals(this.launchTemperature, launchTemperature))
250 this.launchTemperature = launchTemperature;
256 public double getLaunchPressure() {
257 return launchPressure;
262 public void setLaunchPressure(double launchPressure) {
263 if (MathUtil.equals(this.launchPressure, launchPressure))
265 this.launchPressure = launchPressure;
271 * Returns an atmospheric model corresponding to the launch conditions. The
272 * atmospheric models may be shared between different calls.
274 * @return an AtmosphericModel object.
276 private AtmosphericModel getAtmosphericModel() {
278 return ISA_ATMOSPHERIC_MODEL;
280 return new ExtendedISAModel(launchAltitude, launchTemperature, launchPressure);
284 public double getTimeStep() {
288 public void setTimeStep(double timeStep) {
289 if (MathUtil.equals(this.timeStep, timeStep))
291 this.timeStep = timeStep;
295 public double getMaximumStepAngle() {
299 public void setMaximumStepAngle(double maximumAngle) {
300 maximumAngle = MathUtil.clamp(maximumAngle, 1 * Math.PI / 180, 20 * Math.PI / 180);
301 if (MathUtil.equals(this.maximumAngle, maximumAngle))
303 this.maximumAngle = maximumAngle;
309 public boolean getCalculateExtras() {
310 return calculateExtras;
315 public void setCalculateExtras(boolean calculateExtras) {
316 if (this.calculateExtras == calculateExtras)
318 this.calculateExtras = calculateExtras;
324 public int getRandomSeed() {
328 public void setRandomSeed(int randomSeed) {
329 if (this.randomSeed == randomSeed) {
332 this.randomSeed = randomSeed;
334 * This does not fire an event since we don't want to invalidate simulation results
335 * due to changing the seed value. This needs to be revisited if the user is ever
336 * allowed to select the seed value.
338 // fireChangeEvent();
342 * Randomize the random seed value.
344 public void randomizeSeed() {
345 this.randomSeed = new Random().nextInt();
346 // fireChangeEvent();
352 public SimulationOptions clone() {
354 SimulationOptions copy = (SimulationOptions) super.clone();
355 copy.listeners = new ArrayList<ChangeListener>();
357 } catch (CloneNotSupportedException e) {
358 throw new BugException(e);
363 public void copyFrom(SimulationOptions src) {
365 if (this.rocket == src.rocket) {
367 this.motorID = src.motorID;
371 if (src.rocket.hasMotors(src.motorID)) {
372 // Try to find a matching motor ID
373 String motorDesc = src.rocket.getMotorConfigurationDescription(src.motorID);
374 String matchID = null;
376 for (String id : this.rocket.getMotorConfigurationIDs()) {
377 if (motorDesc.equals(this.rocket.getMotorConfigurationDescription(id))) {
383 this.motorID = matchID;
389 this.launchAltitude = src.launchAltitude;
390 this.launchLatitude = src.launchLatitude;
391 this.launchPressure = src.launchPressure;
392 this.launchRodAngle = src.launchRodAngle;
393 this.launchRodDirection = src.launchRodDirection;
394 this.launchRodLength = src.launchRodLength;
395 this.launchTemperature = src.launchTemperature;
396 this.maximumAngle = src.maximumAngle;
397 this.timeStep = src.timeStep;
398 this.windAverage = src.windAverage;
399 this.windTurbulence = src.windTurbulence;
400 this.calculateExtras = src.calculateExtras;
401 this.randomSeed = src.randomSeed;
409 * Compares whether the two simulation conditions are equal. The two are considered
410 * equal if the rocket, motor id and all variables are equal.
413 public boolean equals(Object other) {
414 if (!(other instanceof SimulationOptions))
416 SimulationOptions o = (SimulationOptions) other;
417 return ((this.rocket == o.rocket) &&
418 this.motorID.equals(o.motorID) &&
419 MathUtil.equals(this.launchAltitude, o.launchAltitude) &&
420 MathUtil.equals(this.launchLatitude, o.launchLatitude) &&
421 MathUtil.equals(this.launchPressure, o.launchPressure) &&
422 MathUtil.equals(this.launchRodAngle, o.launchRodAngle) &&
423 MathUtil.equals(this.launchRodDirection, o.launchRodDirection) &&
424 MathUtil.equals(this.launchRodLength, o.launchRodLength) &&
425 MathUtil.equals(this.launchTemperature, o.launchTemperature) &&
426 MathUtil.equals(this.maximumAngle, o.maximumAngle) &&
427 MathUtil.equals(this.timeStep, o.timeStep) &&
428 MathUtil.equals(this.windAverage, o.windAverage) &&
429 MathUtil.equals(this.windTurbulence, o.windTurbulence) &&
430 this.calculateExtras == o.calculateExtras && this.randomSeed == o.randomSeed);
434 * Hashcode method compatible with {@link #equals(Object)}.
437 public int hashCode() {
439 return rocket.hashCode();
440 return rocket.hashCode() + motorID.hashCode();
444 public void addChangeListener(ChangeListener listener) {
445 listeners.add(listener);
449 public void removeChangeListener(ChangeListener listener) {
450 listeners.remove(listener);
453 private final ChangeEvent event = new ChangeEvent(this);
455 private void fireChangeEvent() {
456 ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
458 for (int i = array.length - 1; i >= 0; i--) {
459 array[i].stateChanged(event);
464 // TODO: HIGH: Clean up
465 public SimulationConditions toSimulationConditions() {
466 SimulationConditions conditions = new SimulationConditions();
468 conditions.setRocket((Rocket) getRocket().copy());
469 conditions.setMotorConfigurationID(getMotorConfigurationID());
470 conditions.setLaunchRodLength(getLaunchRodLength());
471 conditions.setLaunchRodAngle(getLaunchRodAngle());
472 conditions.setLaunchRodDirection(getLaunchRodDirection());
473 conditions.setLaunchAltitude(getLaunchAltitude());
474 conditions.setLaunchLatitude(getLaunchLatitude());
475 conditions.setRandomSeed(randomSeed);
477 PinkNoiseWindModel windModel = new PinkNoiseWindModel(randomSeed);
478 windModel.setAverage(getWindSpeedAverage());
479 windModel.setStandardDeviation(getWindSpeedDeviation());
480 conditions.setWindModel(windModel);
482 conditions.setAtmosphericModel(getAtmosphericModel());
484 BasicGravityModel gravityModel = new BasicGravityModel(getLaunchLatitude());
485 conditions.setGravityModel(gravityModel);
487 conditions.setAerodynamicCalculator(new BarrowmanCalculator());
488 conditions.setMassCalculator(new BasicMassCalculator());
490 conditions.setTimeStep(getTimeStep());
491 conditions.setMaximumAngleStep(getMaximumStepAngle());
493 conditions.setCalculateExtras(getCalculateExtras());