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.AtmosphericModel;
10 import net.sf.openrocket.aerodynamics.ExtendedISAModel;
11 import net.sf.openrocket.rocketcomponent.Rocket;
12 import net.sf.openrocket.util.ChangeSource;
13 import net.sf.openrocket.util.MathUtil;
16 public class SimulationConditions implements ChangeSource, Cloneable {
18 public static final double MAX_LAUNCH_ROD_ANGLE = Math.PI/3;
21 * The ISA standard atmosphere.
23 private static final AtmosphericModel ISA_ATMOSPHERIC_MODEL = new ExtendedISAModel();
26 private final Rocket rocket;
27 private String motorID = null;
31 * NOTE: When adding/modifying parameters, they must also be added to the
32 * equals and copyFrom methods!!
35 // TODO: HIGH: Fetch default values from Prefs!
37 private double launchRodLength = 1;
39 /** Launch rod angle > 0, radians from vertical */
40 private double launchRodAngle = 0;
42 /** Launch rod direction, 0 = upwind, PI = downwind. */
43 private double launchRodDirection = 0;
46 private double windAverage = 2.0;
47 private double windTurbulence = 0.1;
49 private double launchAltitude = 0;
50 private double launchLatitude = 45;
52 private boolean useISA = true;
53 private double launchTemperature = ExtendedISAModel.STANDARD_TEMPERATURE;
54 private double launchPressure = ExtendedISAModel.STANDARD_PRESSURE;
55 private AtmosphericModel atmosphericModel = null;
58 private double timeStep = RK4Simulator.RECOMMENDED_TIME_STEP;
59 private double maximumAngle = RK4Simulator.RECOMMENDED_ANGLE_STEP;
61 private boolean calculateExtras = true;
64 private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
68 public SimulationConditions(Rocket rocket) {
74 public Rocket getRocket() {
79 public String getMotorConfigurationID() {
84 * Set the motor configuration ID. This must be a valid motor configuration ID of
85 * the rocket, otherwise the configuration is set to <code>null</code>.
87 * @param id the configuration to set.
89 public void setMotorConfigurationID(String id) {
92 if (!rocket.isMotorConfigurationID(id))
101 public double getLaunchRodLength() {
102 return launchRodLength;
105 public void setLaunchRodLength(double launchRodLength) {
106 if (MathUtil.equals(this.launchRodLength, launchRodLength))
108 this.launchRodLength = launchRodLength;
113 public double getLaunchRodAngle() {
114 return launchRodAngle;
117 public void setLaunchRodAngle(double launchRodAngle) {
118 launchRodAngle = MathUtil.clamp(launchRodAngle, 0, MAX_LAUNCH_ROD_ANGLE);
119 if (MathUtil.equals(this.launchRodAngle, launchRodAngle))
121 this.launchRodAngle = launchRodAngle;
126 public double getLaunchRodDirection() {
127 return launchRodDirection;
130 public void setLaunchRodDirection(double launchRodDirection) {
131 launchRodDirection = MathUtil.reduce180(launchRodDirection);
132 if (MathUtil.equals(this.launchRodDirection, launchRodDirection))
134 this.launchRodDirection = launchRodDirection;
140 public double getWindSpeedAverage() {
144 public void setWindSpeedAverage(double windAverage) {
145 if (MathUtil.equals(this.windAverage, windAverage))
147 this.windAverage = MathUtil.max(windAverage, 0);
152 public double getWindSpeedDeviation() {
153 return windAverage * windTurbulence;
156 public void setWindSpeedDeviation(double windDeviation) {
157 if (windAverage < 0.1) {
160 setWindTurbulenceIntensity(windDeviation / windAverage);
165 * Return the wind turbulence intensity (standard deviation / average).
167 * @return the turbulence intensity
169 public double getWindTurbulenceIntensity() {
170 return windTurbulence;
174 * Set the wind standard deviation to match the given turbulence intensity.
176 * @param intensity the turbulence intensity
178 public void setWindTurbulenceIntensity(double intensity) {
179 // Does not check equality so that setWindSpeedDeviation can be sure of event firing
180 this.windTurbulence = intensity;
188 public double getLaunchAltitude() {
189 return launchAltitude;
192 public void setLaunchAltitude(double altitude) {
193 if (MathUtil.equals(this.launchAltitude, altitude))
195 this.launchAltitude = altitude;
200 public double getLaunchLatitude() {
201 return launchLatitude;
204 public void setLaunchLatitude(double launchLatitude) {
205 launchLatitude = MathUtil.clamp(launchLatitude, -90, 90);
206 if (MathUtil.equals(this.launchLatitude, launchLatitude))
208 this.launchLatitude = launchLatitude;
216 public boolean isISAAtmosphere() {
220 public void setISAAtmosphere(boolean isa) {
228 public double getLaunchTemperature() {
229 return launchTemperature;
234 public void setLaunchTemperature(double launchTemperature) {
235 if (MathUtil.equals(this.launchTemperature, launchTemperature))
237 this.launchTemperature = launchTemperature;
238 this.atmosphericModel = null;
244 public double getLaunchPressure() {
245 return launchPressure;
250 public void setLaunchPressure(double launchPressure) {
251 if (MathUtil.equals(this.launchPressure, launchPressure))
253 this.launchPressure = launchPressure;
254 this.atmosphericModel = null;
260 * Returns an atmospheric model corresponding to the launch conditions. The
261 * atmospheric models may be shared between different calls.
263 * @return an AtmosphericModel object.
265 public AtmosphericModel getAtmosphericModel() {
267 return ISA_ATMOSPHERIC_MODEL;
269 if (atmosphericModel == null) {
270 atmosphericModel = new ExtendedISAModel(launchAltitude,
271 launchTemperature, launchPressure);
273 return atmosphericModel;
277 public double getTimeStep() {
281 public void setTimeStep(double timeStep) {
282 if (MathUtil.equals(this.timeStep, timeStep))
284 this.timeStep = timeStep;
288 public double getMaximumStepAngle() {
292 public void setMaximumStepAngle(double maximumAngle) {
293 maximumAngle = MathUtil.clamp(maximumAngle, 1*Math.PI/180, 20*Math.PI/180);
294 if (MathUtil.equals(this.maximumAngle, maximumAngle))
296 this.maximumAngle = maximumAngle;
302 public boolean getCalculateExtras() {
303 return calculateExtras;
308 public void setCalculateExtras(boolean calculateExtras) {
309 if (this.calculateExtras == calculateExtras)
311 this.calculateExtras = calculateExtras;
318 public SimulationConditions clone() {
320 SimulationConditions copy = (SimulationConditions)super.clone();
321 copy.listeners = new ArrayList<ChangeListener>();
323 } catch (CloneNotSupportedException e) {
324 throw new RuntimeException(e);
329 public void copyFrom(SimulationConditions src) {
331 if (this.rocket == src.rocket) {
333 this.motorID = src.motorID;
337 if (src.rocket.hasMotors(src.motorID)) {
338 // Try to find a matching motor ID
339 String motorDesc = src.rocket.getMotorConfigurationDescription(src.motorID);
340 String matchID = null;
342 for (String id: this.rocket.getMotorConfigurationIDs()) {
343 if (motorDesc.equals(this.rocket.getMotorConfigurationDescription(id))) {
349 this.motorID = matchID;
355 this.launchAltitude = src.launchAltitude;
356 this.launchLatitude = src.launchLatitude;
357 this.launchPressure = src.launchPressure;
358 this.launchRodAngle = src.launchRodAngle;
359 this.launchRodDirection = src.launchRodDirection;
360 this.launchRodLength = src.launchRodLength;
361 this.launchTemperature = src.launchTemperature;
362 this.maximumAngle = src.maximumAngle;
363 this.timeStep = src.timeStep;
364 this.windAverage = src.windAverage;
365 this.windTurbulence = src.windTurbulence;
366 this.calculateExtras = src.calculateExtras;
374 * Compares whether the two simulation conditions are equal. The two are considered
375 * equal if the rocket, motor id and all variables are equal.
378 public boolean equals(Object other) {
379 if (!(other instanceof SimulationConditions))
381 SimulationConditions o = (SimulationConditions)other;
382 return ((this.rocket == o.rocket) &&
383 this.motorID == o.motorID &&
384 MathUtil.equals(this.launchAltitude, o.launchAltitude) &&
385 MathUtil.equals(this.launchLatitude, o.launchLatitude) &&
386 MathUtil.equals(this.launchPressure, o.launchPressure) &&
387 MathUtil.equals(this.launchRodAngle, o.launchRodAngle) &&
388 MathUtil.equals(this.launchRodDirection, o.launchRodDirection) &&
389 MathUtil.equals(this.launchRodLength, o.launchRodLength) &&
390 MathUtil.equals(this.launchTemperature, o.launchTemperature) &&
391 MathUtil.equals(this.maximumAngle, o.maximumAngle) &&
392 MathUtil.equals(this.timeStep, o.timeStep) &&
393 MathUtil.equals(this.windAverage, o.windAverage) &&
394 MathUtil.equals(this.windTurbulence, o.windTurbulence) &&
395 this.calculateExtras == o.calculateExtras);
399 * Hashcode method compatible with {@link #equals(Object)}.
402 public int hashCode() {
404 return rocket.hashCode();
405 return rocket.hashCode() + motorID.hashCode();
409 public void addChangeListener(ChangeListener listener) {
410 listeners.add(listener);
414 public void removeChangeListener(ChangeListener listener) {
415 listeners.remove(listener);
418 private final ChangeEvent event = new ChangeEvent(this);
419 private void fireChangeEvent() {
420 ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
422 for (int i=array.length-1; i >=0; i--) {
423 array[i].stateChanged(event);