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;
21 // TODO: HIGH: Move somewhere else and clean up
23 public class GUISimulationConditions implements ChangeSource, Cloneable {
25 public static final double MAX_LAUNCH_ROD_ANGLE = Math.PI / 3;
28 * The ISA standard atmosphere.
30 private static final AtmosphericModel ISA_ATMOSPHERIC_MODEL = new ExtendedISAModel();
33 private final Rocket rocket;
34 private String motorID = null;
38 * NOTE: When adding/modifying parameters, they must also be added to the
39 * equals and copyFrom methods!!
42 // TODO: HIGH: Fetch default values from Prefs!
44 private double launchRodLength = 1;
46 /** Launch rod angle > 0, radians from vertical */
47 private double launchRodAngle = 0;
49 /** Launch rod direction, 0 = upwind, PI = downwind. */
50 private double launchRodDirection = 0;
53 private double windAverage = 2.0;
54 private double windTurbulence = 0.1;
56 private double launchAltitude = 0;
57 private double launchLatitude = 45;
59 private boolean useISA = true;
60 private double launchTemperature = ExtendedISAModel.STANDARD_TEMPERATURE;
61 private double launchPressure = ExtendedISAModel.STANDARD_PRESSURE;
62 private AtmosphericModel atmosphericModel = null;
65 private double timeStep = RK4SimulationStepper.RECOMMENDED_TIME_STEP;
66 private double maximumAngle = RK4SimulationStepper.RECOMMENDED_ANGLE_STEP;
68 private boolean calculateExtras = true;
71 private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
75 public GUISimulationConditions(Rocket rocket) {
81 public Rocket getRocket() {
86 public String getMotorConfigurationID() {
91 * Set the motor configuration ID. This must be a valid motor configuration ID of
92 * the rocket, otherwise the configuration is set to <code>null</code>.
94 * @param id the configuration to set.
96 public void setMotorConfigurationID(String id) {
99 if (!rocket.isMotorConfigurationID(id))
108 public double getLaunchRodLength() {
109 return launchRodLength;
112 public void setLaunchRodLength(double launchRodLength) {
113 if (MathUtil.equals(this.launchRodLength, launchRodLength))
115 this.launchRodLength = launchRodLength;
120 public double getLaunchRodAngle() {
121 return launchRodAngle;
124 public void setLaunchRodAngle(double launchRodAngle) {
125 launchRodAngle = MathUtil.clamp(launchRodAngle, 0, MAX_LAUNCH_ROD_ANGLE);
126 if (MathUtil.equals(this.launchRodAngle, launchRodAngle))
128 this.launchRodAngle = launchRodAngle;
133 public double getLaunchRodDirection() {
134 return launchRodDirection;
137 public void setLaunchRodDirection(double launchRodDirection) {
138 launchRodDirection = MathUtil.reduce180(launchRodDirection);
139 if (MathUtil.equals(this.launchRodDirection, launchRodDirection))
141 this.launchRodDirection = launchRodDirection;
147 public double getWindSpeedAverage() {
151 public void setWindSpeedAverage(double windAverage) {
152 if (MathUtil.equals(this.windAverage, windAverage))
154 this.windAverage = MathUtil.max(windAverage, 0);
159 public double getWindSpeedDeviation() {
160 return windAverage * windTurbulence;
163 public void setWindSpeedDeviation(double windDeviation) {
164 if (windAverage < 0.1) {
167 setWindTurbulenceIntensity(windDeviation / windAverage);
172 * Return the wind turbulence intensity (standard deviation / average).
174 * @return the turbulence intensity
176 public double getWindTurbulenceIntensity() {
177 return windTurbulence;
181 * Set the wind standard deviation to match the given turbulence intensity.
183 * @param intensity the turbulence intensity
185 public void setWindTurbulenceIntensity(double intensity) {
186 // Does not check equality so that setWindSpeedDeviation can be sure of event firing
187 this.windTurbulence = intensity;
195 public double getLaunchAltitude() {
196 return launchAltitude;
199 public void setLaunchAltitude(double altitude) {
200 if (MathUtil.equals(this.launchAltitude, altitude))
202 this.launchAltitude = altitude;
207 public double getLaunchLatitude() {
208 return launchLatitude;
211 public void setLaunchLatitude(double launchLatitude) {
212 launchLatitude = MathUtil.clamp(launchLatitude, -90, 90);
213 if (MathUtil.equals(this.launchLatitude, launchLatitude))
215 this.launchLatitude = launchLatitude;
223 public boolean isISAAtmosphere() {
227 public void setISAAtmosphere(boolean isa) {
235 public double getLaunchTemperature() {
236 return launchTemperature;
241 public void setLaunchTemperature(double launchTemperature) {
242 if (MathUtil.equals(this.launchTemperature, launchTemperature))
244 this.launchTemperature = launchTemperature;
245 this.atmosphericModel = null;
251 public double getLaunchPressure() {
252 return launchPressure;
257 public void setLaunchPressure(double launchPressure) {
258 if (MathUtil.equals(this.launchPressure, launchPressure))
260 this.launchPressure = launchPressure;
261 this.atmosphericModel = null;
267 * Returns an atmospheric model corresponding to the launch conditions. The
268 * atmospheric models may be shared between different calls.
270 * @return an AtmosphericModel object.
272 public AtmosphericModel getAtmosphericModel() {
274 return ISA_ATMOSPHERIC_MODEL;
276 if (atmosphericModel == null) {
277 atmosphericModel = new ExtendedISAModel(launchAltitude,
278 launchTemperature, launchPressure);
280 return atmosphericModel;
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;
325 public GUISimulationConditions clone() {
327 GUISimulationConditions copy = (GUISimulationConditions) super.clone();
328 copy.listeners = new ArrayList<ChangeListener>();
330 } catch (CloneNotSupportedException e) {
331 throw new BugException(e);
336 public void copyFrom(GUISimulationConditions src) {
338 if (this.rocket == src.rocket) {
340 this.motorID = src.motorID;
344 if (src.rocket.hasMotors(src.motorID)) {
345 // Try to find a matching motor ID
346 String motorDesc = src.rocket.getMotorConfigurationDescription(src.motorID);
347 String matchID = null;
349 for (String id : this.rocket.getMotorConfigurationIDs()) {
350 if (motorDesc.equals(this.rocket.getMotorConfigurationDescription(id))) {
356 this.motorID = matchID;
362 this.launchAltitude = src.launchAltitude;
363 this.launchLatitude = src.launchLatitude;
364 this.launchPressure = src.launchPressure;
365 this.launchRodAngle = src.launchRodAngle;
366 this.launchRodDirection = src.launchRodDirection;
367 this.launchRodLength = src.launchRodLength;
368 this.launchTemperature = src.launchTemperature;
369 this.maximumAngle = src.maximumAngle;
370 this.timeStep = src.timeStep;
371 this.windAverage = src.windAverage;
372 this.windTurbulence = src.windTurbulence;
373 this.calculateExtras = src.calculateExtras;
381 * Compares whether the two simulation conditions are equal. The two are considered
382 * equal if the rocket, motor id and all variables are equal.
385 public boolean equals(Object other) {
386 if (!(other instanceof GUISimulationConditions))
388 GUISimulationConditions o = (GUISimulationConditions) other;
389 return ((this.rocket == o.rocket) &&
390 this.motorID == o.motorID &&
391 MathUtil.equals(this.launchAltitude, o.launchAltitude) &&
392 MathUtil.equals(this.launchLatitude, o.launchLatitude) &&
393 MathUtil.equals(this.launchPressure, o.launchPressure) &&
394 MathUtil.equals(this.launchRodAngle, o.launchRodAngle) &&
395 MathUtil.equals(this.launchRodDirection, o.launchRodDirection) &&
396 MathUtil.equals(this.launchRodLength, o.launchRodLength) &&
397 MathUtil.equals(this.launchTemperature, o.launchTemperature) &&
398 MathUtil.equals(this.maximumAngle, o.maximumAngle) &&
399 MathUtil.equals(this.timeStep, o.timeStep) &&
400 MathUtil.equals(this.windAverage, o.windAverage) &&
401 MathUtil.equals(this.windTurbulence, o.windTurbulence) && this.calculateExtras == o.calculateExtras);
405 * Hashcode method compatible with {@link #equals(Object)}.
408 public int hashCode() {
410 return rocket.hashCode();
411 return rocket.hashCode() + motorID.hashCode();
415 public void addChangeListener(ChangeListener listener) {
416 listeners.add(listener);
420 public void removeChangeListener(ChangeListener listener) {
421 listeners.remove(listener);
424 private final ChangeEvent event = new ChangeEvent(this);
426 private void fireChangeEvent() {
427 ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
429 for (int i = array.length - 1; i >= 0; i--) {
430 array[i].stateChanged(event);
435 // TODO: HIGH: Clean up
437 public SimulationConditions toSimulationConditions() {
438 SimulationConditions conditions = new SimulationConditions();
440 conditions.setRocket((Rocket) getRocket().copy());
441 conditions.setMotorConfigurationID(getMotorConfigurationID());
442 conditions.setLaunchRodLength(getLaunchRodLength());
443 conditions.setLaunchRodAngle(getLaunchRodAngle());
444 conditions.setLaunchRodDirection(getLaunchRodDirection());
445 conditions.setLaunchAltitude(getLaunchAltitude());
446 conditions.setLaunchLatitude(getLaunchLatitude());
448 PinkNoiseWindModel windModel = new PinkNoiseWindModel();
449 // TODO: HIGH: Randomness source for simulation
450 windModel.setSeed(new Random().nextInt());
451 windModel.setAverage(getWindSpeedAverage());
452 windModel.setStandardDeviation(getWindSpeedDeviation());
453 conditions.setWindModel(windModel);
455 conditions.setAtmosphericModel(getAtmosphericModel());
457 BasicGravityModel gravityModel = new BasicGravityModel(getLaunchLatitude());
458 conditions.setGravityModel(gravityModel);
460 conditions.setAerodynamicCalculator(new BarrowmanCalculator());
461 conditions.setMassCalculator(new BasicMassCalculator());
463 conditions.setTimeStep(getTimeStep());
464 conditions.setMaximumAngleStep(getMaximumStepAngle());
466 conditions.setCalculateExtras(getCalculateExtras());