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() {
83 public void setMotorConfigurationID(String id) {
93 public double getLaunchRodLength() {
94 return launchRodLength;
97 public void setLaunchRodLength(double launchRodLength) {
98 if (MathUtil.equals(this.launchRodLength, launchRodLength))
100 this.launchRodLength = launchRodLength;
105 public double getLaunchRodAngle() {
106 return launchRodAngle;
109 public void setLaunchRodAngle(double launchRodAngle) {
110 launchRodAngle = MathUtil.clamp(launchRodAngle, 0, MAX_LAUNCH_ROD_ANGLE);
111 if (MathUtil.equals(this.launchRodAngle, launchRodAngle))
113 this.launchRodAngle = launchRodAngle;
118 public double getLaunchRodDirection() {
119 return launchRodDirection;
122 public void setLaunchRodDirection(double launchRodDirection) {
123 launchRodDirection = MathUtil.reduce180(launchRodDirection);
124 if (MathUtil.equals(this.launchRodDirection, launchRodDirection))
126 this.launchRodDirection = launchRodDirection;
132 public double getWindSpeedAverage() {
136 public void setWindSpeedAverage(double windAverage) {
137 if (MathUtil.equals(this.windAverage, windAverage))
139 this.windAverage = MathUtil.max(windAverage, 0);
144 public double getWindSpeedDeviation() {
145 return windAverage * windTurbulence;
148 public void setWindSpeedDeviation(double windDeviation) {
149 if (windAverage < 0.1) {
152 setWindTurbulenceIntensity(windDeviation / windAverage);
157 * Return the wind turbulence intensity (standard deviation / average).
159 * @return the turbulence intensity
161 public double getWindTurbulenceIntensity() {
162 return windTurbulence;
166 * Set the wind standard deviation to match the given turbulence intensity.
168 * @param intensity the turbulence intensity
170 public void setWindTurbulenceIntensity(double intensity) {
171 // Does not check equality so that setWindSpeedDeviation can be sure of event firing
172 this.windTurbulence = intensity;
180 public double getLaunchAltitude() {
181 return launchAltitude;
184 public void setLaunchAltitude(double altitude) {
185 if (MathUtil.equals(this.launchAltitude, altitude))
187 this.launchAltitude = altitude;
192 public double getLaunchLatitude() {
193 return launchLatitude;
196 public void setLaunchLatitude(double launchLatitude) {
197 launchLatitude = MathUtil.clamp(launchLatitude, -90, 90);
198 if (MathUtil.equals(this.launchLatitude, launchLatitude))
200 this.launchLatitude = launchLatitude;
208 public boolean isISAAtmosphere() {
212 public void setISAAtmosphere(boolean isa) {
220 public double getLaunchTemperature() {
221 return launchTemperature;
226 public void setLaunchTemperature(double launchTemperature) {
227 if (MathUtil.equals(this.launchTemperature, launchTemperature))
229 this.launchTemperature = launchTemperature;
230 this.atmosphericModel = null;
236 public double getLaunchPressure() {
237 return launchPressure;
242 public void setLaunchPressure(double launchPressure) {
243 if (MathUtil.equals(this.launchPressure, launchPressure))
245 this.launchPressure = launchPressure;
246 this.atmosphericModel = null;
252 * Returns an atmospheric model corresponding to the launch conditions. The
253 * atmospheric models may be shared between different calls.
255 * @return an AtmosphericModel object.
257 public AtmosphericModel getAtmosphericModel() {
259 return ISA_ATMOSPHERIC_MODEL;
261 if (atmosphericModel == null) {
262 atmosphericModel = new ExtendedISAModel(launchAltitude,
263 launchTemperature, launchPressure);
265 return atmosphericModel;
269 public double getTimeStep() {
273 public void setTimeStep(double timeStep) {
274 if (MathUtil.equals(this.timeStep, timeStep))
276 this.timeStep = timeStep;
280 public double getMaximumStepAngle() {
284 public void setMaximumStepAngle(double maximumAngle) {
285 maximumAngle = MathUtil.clamp(maximumAngle, 1*Math.PI/180, 20*Math.PI/180);
286 if (MathUtil.equals(this.maximumAngle, maximumAngle))
288 this.maximumAngle = maximumAngle;
294 public boolean getCalculateExtras() {
295 return calculateExtras;
300 public void setCalculateExtras(boolean calculateExtras) {
301 if (this.calculateExtras == calculateExtras)
303 this.calculateExtras = calculateExtras;
310 public SimulationConditions clone() {
312 SimulationConditions copy = (SimulationConditions)super.clone();
313 copy.listeners = new ArrayList<ChangeListener>();
315 } catch (CloneNotSupportedException e) {
316 throw new RuntimeException(e);
321 public void copyFrom(SimulationConditions src) {
322 if (this.rocket != src.rocket) {
323 throw new IllegalArgumentException("Unable to copy simulation conditions of "+
324 "a difference rocket");
326 if (this.equals(src))
329 this.motorID = src.motorID;
330 this.launchAltitude = src.launchAltitude;
331 this.launchLatitude = src.launchLatitude;
332 this.launchPressure = src.launchPressure;
333 this.launchRodAngle = src.launchRodAngle;
334 this.launchRodDirection = src.launchRodDirection;
335 this.launchRodLength = src.launchRodLength;
336 this.launchTemperature = src.launchTemperature;
337 this.maximumAngle = src.maximumAngle;
338 this.timeStep = src.timeStep;
339 this.windAverage = src.windAverage;
340 this.windTurbulence = src.windTurbulence;
341 this.calculateExtras = src.calculateExtras;
349 * Compares whether the two simulation conditions are equal. The two are considered
350 * equal if the rocket, motor id and all variables are equal.
353 public boolean equals(Object other) {
354 if (!(other instanceof SimulationConditions))
356 SimulationConditions o = (SimulationConditions)other;
357 return ((this.rocket == o.rocket) &&
358 this.motorID == o.motorID &&
359 MathUtil.equals(this.launchAltitude, o.launchAltitude) &&
360 MathUtil.equals(this.launchLatitude, o.launchLatitude) &&
361 MathUtil.equals(this.launchPressure, o.launchPressure) &&
362 MathUtil.equals(this.launchRodAngle, o.launchRodAngle) &&
363 MathUtil.equals(this.launchRodDirection, o.launchRodDirection) &&
364 MathUtil.equals(this.launchRodLength, o.launchRodLength) &&
365 MathUtil.equals(this.launchTemperature, o.launchTemperature) &&
366 MathUtil.equals(this.maximumAngle, o.maximumAngle) &&
367 MathUtil.equals(this.timeStep, o.timeStep) &&
368 MathUtil.equals(this.windAverage, o.windAverage) &&
369 MathUtil.equals(this.windTurbulence, o.windTurbulence) &&
370 this.calculateExtras == o.calculateExtras);
374 * Hashcode method compatible with {@link #equals(Object)}.
377 public int hashCode() {
379 return rocket.hashCode();
380 return rocket.hashCode() + motorID.hashCode();
384 public void addChangeListener(ChangeListener listener) {
385 listeners.add(listener);
389 public void removeChangeListener(ChangeListener listener) {
390 listeners.remove(listener);
393 private final ChangeEvent event = new ChangeEvent(this);
394 private void fireChangeEvent() {
395 ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
397 for (int i=array.length-1; i >=0; i--) {
398 array[i].stateChanged(event);