1 package net.sf.openrocket.simulation;
3 import java.util.ArrayList;
6 import javax.swing.event.ChangeEvent;
7 import javax.swing.event.ChangeListener;
9 import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
10 import net.sf.openrocket.masscalc.BasicMassCalculator;
11 import net.sf.openrocket.models.atmosphere.AtmosphericModel;
12 import net.sf.openrocket.models.atmosphere.ExtendedISAModel;
13 import net.sf.openrocket.models.gravity.BasicGravityModel;
14 import net.sf.openrocket.models.wind.PinkNoiseWindModel;
15 import net.sf.openrocket.rocketcomponent.Rocket;
16 import net.sf.openrocket.util.BugException;
17 import net.sf.openrocket.util.ChangeSource;
18 import net.sf.openrocket.util.MathUtil;
20 // TODO: HIGH: Move somewhere else and clean up
22 public class GUISimulationConditions implements ChangeSource, Cloneable {
24 public static final double MAX_LAUNCH_ROD_ANGLE = Math.PI / 3;
27 * The ISA standard atmosphere.
29 private static final AtmosphericModel ISA_ATMOSPHERIC_MODEL = new ExtendedISAModel();
32 private final Rocket rocket;
33 private String motorID = null;
37 * NOTE: When adding/modifying parameters, they must also be added to the
38 * equals and copyFrom methods!!
41 // TODO: HIGH: Fetch default values from Prefs!
43 private double launchRodLength = 1;
45 /** Launch rod angle > 0, radians from vertical */
46 private double launchRodAngle = 0;
48 /** Launch rod direction, 0 = upwind, PI = downwind. */
49 private double launchRodDirection = 0;
52 private double windAverage = 2.0;
53 private double windTurbulence = 0.1;
55 private double launchAltitude = 0;
56 private double launchLatitude = 45;
58 private boolean useISA = true;
59 private double launchTemperature = ExtendedISAModel.STANDARD_TEMPERATURE;
60 private double launchPressure = ExtendedISAModel.STANDARD_PRESSURE;
61 private AtmosphericModel atmosphericModel = null;
64 private double timeStep = RK4SimulationStepper.RECOMMENDED_TIME_STEP;
65 private double maximumAngle = RK4SimulationStepper.RECOMMENDED_ANGLE_STEP;
67 private boolean calculateExtras = true;
70 private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
74 public GUISimulationConditions(Rocket rocket) {
80 public Rocket getRocket() {
85 public String getMotorConfigurationID() {
90 * Set the motor configuration ID. This must be a valid motor configuration ID of
91 * the rocket, otherwise the configuration is set to <code>null</code>.
93 * @param id the configuration to set.
95 public void setMotorConfigurationID(String id) {
98 if (!rocket.isMotorConfigurationID(id))
107 public double getLaunchRodLength() {
108 return launchRodLength;
111 public void setLaunchRodLength(double launchRodLength) {
112 if (MathUtil.equals(this.launchRodLength, launchRodLength))
114 this.launchRodLength = launchRodLength;
119 public double getLaunchRodAngle() {
120 return launchRodAngle;
123 public void setLaunchRodAngle(double launchRodAngle) {
124 launchRodAngle = MathUtil.clamp(launchRodAngle, 0, MAX_LAUNCH_ROD_ANGLE);
125 if (MathUtil.equals(this.launchRodAngle, launchRodAngle))
127 this.launchRodAngle = launchRodAngle;
132 public double getLaunchRodDirection() {
133 return launchRodDirection;
136 public void setLaunchRodDirection(double launchRodDirection) {
137 launchRodDirection = MathUtil.reduce180(launchRodDirection);
138 if (MathUtil.equals(this.launchRodDirection, launchRodDirection))
140 this.launchRodDirection = launchRodDirection;
146 public double getWindSpeedAverage() {
150 public void setWindSpeedAverage(double windAverage) {
151 if (MathUtil.equals(this.windAverage, windAverage))
153 this.windAverage = MathUtil.max(windAverage, 0);
158 public double getWindSpeedDeviation() {
159 return windAverage * windTurbulence;
162 public void setWindSpeedDeviation(double windDeviation) {
163 if (windAverage < 0.1) {
166 setWindTurbulenceIntensity(windDeviation / windAverage);
171 * Return the wind turbulence intensity (standard deviation / average).
173 * @return the turbulence intensity
175 public double getWindTurbulenceIntensity() {
176 return windTurbulence;
180 * Set the wind standard deviation to match the given turbulence intensity.
182 * @param intensity the turbulence intensity
184 public void setWindTurbulenceIntensity(double intensity) {
185 // Does not check equality so that setWindSpeedDeviation can be sure of event firing
186 this.windTurbulence = intensity;
194 public double getLaunchAltitude() {
195 return launchAltitude;
198 public void setLaunchAltitude(double altitude) {
199 if (MathUtil.equals(this.launchAltitude, altitude))
201 this.launchAltitude = altitude;
206 public double getLaunchLatitude() {
207 return launchLatitude;
210 public void setLaunchLatitude(double launchLatitude) {
211 launchLatitude = MathUtil.clamp(launchLatitude, -90, 90);
212 if (MathUtil.equals(this.launchLatitude, launchLatitude))
214 this.launchLatitude = launchLatitude;
222 public boolean isISAAtmosphere() {
226 public void setISAAtmosphere(boolean isa) {
234 public double getLaunchTemperature() {
235 return launchTemperature;
240 public void setLaunchTemperature(double launchTemperature) {
241 if (MathUtil.equals(this.launchTemperature, launchTemperature))
243 this.launchTemperature = launchTemperature;
244 this.atmosphericModel = null;
250 public double getLaunchPressure() {
251 return launchPressure;
256 public void setLaunchPressure(double launchPressure) {
257 if (MathUtil.equals(this.launchPressure, launchPressure))
259 this.launchPressure = launchPressure;
260 this.atmosphericModel = null;
266 * Returns an atmospheric model corresponding to the launch conditions. The
267 * atmospheric models may be shared between different calls.
269 * @return an AtmosphericModel object.
271 public AtmosphericModel getAtmosphericModel() {
273 return ISA_ATMOSPHERIC_MODEL;
275 if (atmosphericModel == null) {
276 atmosphericModel = new ExtendedISAModel(launchAltitude,
277 launchTemperature, launchPressure);
279 return atmosphericModel;
283 public double getTimeStep() {
287 public void setTimeStep(double timeStep) {
288 if (MathUtil.equals(this.timeStep, timeStep))
290 this.timeStep = timeStep;
294 public double getMaximumStepAngle() {
298 public void setMaximumStepAngle(double maximumAngle) {
299 maximumAngle = MathUtil.clamp(maximumAngle, 1 * Math.PI / 180, 20 * Math.PI / 180);
300 if (MathUtil.equals(this.maximumAngle, maximumAngle))
302 this.maximumAngle = maximumAngle;
308 public boolean getCalculateExtras() {
309 return calculateExtras;
314 public void setCalculateExtras(boolean calculateExtras) {
315 if (this.calculateExtras == calculateExtras)
317 this.calculateExtras = calculateExtras;
324 public GUISimulationConditions clone() {
326 GUISimulationConditions copy = (GUISimulationConditions) super.clone();
327 copy.listeners = new ArrayList<ChangeListener>();
329 } catch (CloneNotSupportedException e) {
330 throw new BugException(e);
335 public void copyFrom(GUISimulationConditions src) {
337 if (this.rocket == src.rocket) {
339 this.motorID = src.motorID;
343 if (src.rocket.hasMotors(src.motorID)) {
344 // Try to find a matching motor ID
345 String motorDesc = src.rocket.getMotorConfigurationDescription(src.motorID);
346 String matchID = null;
348 for (String id : this.rocket.getMotorConfigurationIDs()) {
349 if (motorDesc.equals(this.rocket.getMotorConfigurationDescription(id))) {
355 this.motorID = matchID;
361 this.launchAltitude = src.launchAltitude;
362 this.launchLatitude = src.launchLatitude;
363 this.launchPressure = src.launchPressure;
364 this.launchRodAngle = src.launchRodAngle;
365 this.launchRodDirection = src.launchRodDirection;
366 this.launchRodLength = src.launchRodLength;
367 this.launchTemperature = src.launchTemperature;
368 this.maximumAngle = src.maximumAngle;
369 this.timeStep = src.timeStep;
370 this.windAverage = src.windAverage;
371 this.windTurbulence = src.windTurbulence;
372 this.calculateExtras = src.calculateExtras;
380 * Compares whether the two simulation conditions are equal. The two are considered
381 * equal if the rocket, motor id and all variables are equal.
384 public boolean equals(Object other) {
385 if (!(other instanceof GUISimulationConditions))
387 GUISimulationConditions o = (GUISimulationConditions) other;
388 return ((this.rocket == o.rocket) &&
389 this.motorID == o.motorID &&
390 MathUtil.equals(this.launchAltitude, o.launchAltitude) &&
391 MathUtil.equals(this.launchLatitude, o.launchLatitude) &&
392 MathUtil.equals(this.launchPressure, o.launchPressure) &&
393 MathUtil.equals(this.launchRodAngle, o.launchRodAngle) &&
394 MathUtil.equals(this.launchRodDirection, o.launchRodDirection) &&
395 MathUtil.equals(this.launchRodLength, o.launchRodLength) &&
396 MathUtil.equals(this.launchTemperature, o.launchTemperature) &&
397 MathUtil.equals(this.maximumAngle, o.maximumAngle) &&
398 MathUtil.equals(this.timeStep, o.timeStep) &&
399 MathUtil.equals(this.windAverage, o.windAverage) &&
400 MathUtil.equals(this.windTurbulence, o.windTurbulence) && this.calculateExtras == o.calculateExtras);
404 * Hashcode method compatible with {@link #equals(Object)}.
407 public int hashCode() {
409 return rocket.hashCode();
410 return rocket.hashCode() + motorID.hashCode();
414 public void addChangeListener(ChangeListener listener) {
415 listeners.add(listener);
419 public void removeChangeListener(ChangeListener listener) {
420 listeners.remove(listener);
423 private final ChangeEvent event = new ChangeEvent(this);
425 private void fireChangeEvent() {
426 ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
428 for (int i = array.length - 1; i >= 0; i--) {
429 array[i].stateChanged(event);
434 // TODO: HIGH: Clean up
436 public SimulationConditions toSimulationConditions() {
437 SimulationConditions conditions = new SimulationConditions();
439 conditions.setRocket((Rocket) getRocket().copy());
440 conditions.setMotorConfigurationID(getMotorConfigurationID());
441 conditions.setLaunchRodLength(getLaunchRodLength());
442 conditions.setLaunchRodAngle(getLaunchRodAngle());
443 conditions.setLaunchRodDirection(getLaunchRodDirection());
444 conditions.setLaunchAltitude(getLaunchAltitude());
445 conditions.setLaunchLatitude(getLaunchLatitude());
447 PinkNoiseWindModel windModel = new PinkNoiseWindModel();
448 windModel.setAverage(getWindSpeedAverage());
449 windModel.setStandardDeviation(getWindSpeedDeviation());
450 conditions.setWindModel(windModel);
452 conditions.setAtmosphericModel(getAtmosphericModel());
454 BasicGravityModel gravityModel = new BasicGravityModel(getLaunchLatitude());
455 conditions.setGravityModel(gravityModel);
457 conditions.setAerodynamicCalculator(new BarrowmanCalculator());
458 conditions.setMassCalculator(new BasicMassCalculator());
460 conditions.setTimeStep(getTimeStep());
461 conditions.setMaximumAngleStep(getMaximumStepAngle());
463 conditions.setCalculateExtras(getCalculateExtras());