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.BugException;
13 import net.sf.openrocket.util.ChangeSource;
14 import net.sf.openrocket.util.MathUtil;
17 public class SimulationConditions implements ChangeSource, Cloneable {
19 public static final double MAX_LAUNCH_ROD_ANGLE = Math.PI/3;
22 * The ISA standard atmosphere.
24 private static final AtmosphericModel ISA_ATMOSPHERIC_MODEL = new ExtendedISAModel();
27 private final Rocket rocket;
28 private String motorID = null;
32 * NOTE: When adding/modifying parameters, they must also be added to the
33 * equals and copyFrom methods!!
36 // TODO: HIGH: Fetch default values from Prefs!
38 private double launchRodLength = 1;
40 /** Launch rod angle > 0, radians from vertical */
41 private double launchRodAngle = 0;
43 /** Launch rod direction, 0 = upwind, PI = downwind. */
44 private double launchRodDirection = 0;
47 private double windAverage = 2.0;
48 private double windTurbulence = 0.1;
50 private double launchAltitude = 0;
51 private double launchLatitude = 45;
53 private boolean useISA = true;
54 private double launchTemperature = ExtendedISAModel.STANDARD_TEMPERATURE;
55 private double launchPressure = ExtendedISAModel.STANDARD_PRESSURE;
56 private AtmosphericModel atmosphericModel = null;
59 private double timeStep = RK4Simulator.RECOMMENDED_TIME_STEP;
60 private double maximumAngle = RK4Simulator.RECOMMENDED_ANGLE_STEP;
62 private boolean calculateExtras = true;
65 private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
69 public SimulationConditions(Rocket rocket) {
75 public Rocket getRocket() {
80 public String getMotorConfigurationID() {
85 * Set the motor configuration ID. This must be a valid motor configuration ID of
86 * the rocket, otherwise the configuration is set to <code>null</code>.
88 * @param id the configuration to set.
90 public void setMotorConfigurationID(String id) {
93 if (!rocket.isMotorConfigurationID(id))
102 public double getLaunchRodLength() {
103 return launchRodLength;
106 public void setLaunchRodLength(double launchRodLength) {
107 if (MathUtil.equals(this.launchRodLength, launchRodLength))
109 this.launchRodLength = launchRodLength;
114 public double getLaunchRodAngle() {
115 return launchRodAngle;
118 public void setLaunchRodAngle(double launchRodAngle) {
119 launchRodAngle = MathUtil.clamp(launchRodAngle, 0, MAX_LAUNCH_ROD_ANGLE);
120 if (MathUtil.equals(this.launchRodAngle, launchRodAngle))
122 this.launchRodAngle = launchRodAngle;
127 public double getLaunchRodDirection() {
128 return launchRodDirection;
131 public void setLaunchRodDirection(double launchRodDirection) {
132 launchRodDirection = MathUtil.reduce180(launchRodDirection);
133 if (MathUtil.equals(this.launchRodDirection, launchRodDirection))
135 this.launchRodDirection = launchRodDirection;
141 public double getWindSpeedAverage() {
145 public void setWindSpeedAverage(double windAverage) {
146 if (MathUtil.equals(this.windAverage, windAverage))
148 this.windAverage = MathUtil.max(windAverage, 0);
153 public double getWindSpeedDeviation() {
154 return windAverage * windTurbulence;
157 public void setWindSpeedDeviation(double windDeviation) {
158 if (windAverage < 0.1) {
161 setWindTurbulenceIntensity(windDeviation / windAverage);
166 * Return the wind turbulence intensity (standard deviation / average).
168 * @return the turbulence intensity
170 public double getWindTurbulenceIntensity() {
171 return windTurbulence;
175 * Set the wind standard deviation to match the given turbulence intensity.
177 * @param intensity the turbulence intensity
179 public void setWindTurbulenceIntensity(double intensity) {
180 // Does not check equality so that setWindSpeedDeviation can be sure of event firing
181 this.windTurbulence = intensity;
189 public double getLaunchAltitude() {
190 return launchAltitude;
193 public void setLaunchAltitude(double altitude) {
194 if (MathUtil.equals(this.launchAltitude, altitude))
196 this.launchAltitude = altitude;
201 public double getLaunchLatitude() {
202 return launchLatitude;
205 public void setLaunchLatitude(double launchLatitude) {
206 launchLatitude = MathUtil.clamp(launchLatitude, -90, 90);
207 if (MathUtil.equals(this.launchLatitude, launchLatitude))
209 this.launchLatitude = launchLatitude;
217 public boolean isISAAtmosphere() {
221 public void setISAAtmosphere(boolean isa) {
229 public double getLaunchTemperature() {
230 return launchTemperature;
235 public void setLaunchTemperature(double launchTemperature) {
236 if (MathUtil.equals(this.launchTemperature, launchTemperature))
238 this.launchTemperature = launchTemperature;
239 this.atmosphericModel = null;
245 public double getLaunchPressure() {
246 return launchPressure;
251 public void setLaunchPressure(double launchPressure) {
252 if (MathUtil.equals(this.launchPressure, launchPressure))
254 this.launchPressure = launchPressure;
255 this.atmosphericModel = null;
261 * Returns an atmospheric model corresponding to the launch conditions. The
262 * atmospheric models may be shared between different calls.
264 * @return an AtmosphericModel object.
266 public AtmosphericModel getAtmosphericModel() {
268 return ISA_ATMOSPHERIC_MODEL;
270 if (atmosphericModel == null) {
271 atmosphericModel = new ExtendedISAModel(launchAltitude,
272 launchTemperature, launchPressure);
274 return atmosphericModel;
278 public double getTimeStep() {
282 public void setTimeStep(double timeStep) {
283 if (MathUtil.equals(this.timeStep, timeStep))
285 this.timeStep = timeStep;
289 public double getMaximumStepAngle() {
293 public void setMaximumStepAngle(double maximumAngle) {
294 maximumAngle = MathUtil.clamp(maximumAngle, 1*Math.PI/180, 20*Math.PI/180);
295 if (MathUtil.equals(this.maximumAngle, maximumAngle))
297 this.maximumAngle = maximumAngle;
303 public boolean getCalculateExtras() {
304 return calculateExtras;
309 public void setCalculateExtras(boolean calculateExtras) {
310 if (this.calculateExtras == calculateExtras)
312 this.calculateExtras = calculateExtras;
319 public SimulationConditions clone() {
321 SimulationConditions copy = (SimulationConditions)super.clone();
322 copy.listeners = new ArrayList<ChangeListener>();
324 } catch (CloneNotSupportedException e) {
325 throw new BugException(e);
330 public void copyFrom(SimulationConditions src) {
332 if (this.rocket == src.rocket) {
334 this.motorID = src.motorID;
338 if (src.rocket.hasMotors(src.motorID)) {
339 // Try to find a matching motor ID
340 String motorDesc = src.rocket.getMotorConfigurationDescription(src.motorID);
341 String matchID = null;
343 for (String id: this.rocket.getMotorConfigurationIDs()) {
344 if (motorDesc.equals(this.rocket.getMotorConfigurationDescription(id))) {
350 this.motorID = matchID;
356 this.launchAltitude = src.launchAltitude;
357 this.launchLatitude = src.launchLatitude;
358 this.launchPressure = src.launchPressure;
359 this.launchRodAngle = src.launchRodAngle;
360 this.launchRodDirection = src.launchRodDirection;
361 this.launchRodLength = src.launchRodLength;
362 this.launchTemperature = src.launchTemperature;
363 this.maximumAngle = src.maximumAngle;
364 this.timeStep = src.timeStep;
365 this.windAverage = src.windAverage;
366 this.windTurbulence = src.windTurbulence;
367 this.calculateExtras = src.calculateExtras;
375 * Compares whether the two simulation conditions are equal. The two are considered
376 * equal if the rocket, motor id and all variables are equal.
379 public boolean equals(Object other) {
380 if (!(other instanceof SimulationConditions))
382 SimulationConditions o = (SimulationConditions)other;
383 return ((this.rocket == o.rocket) &&
384 this.motorID == o.motorID &&
385 MathUtil.equals(this.launchAltitude, o.launchAltitude) &&
386 MathUtil.equals(this.launchLatitude, o.launchLatitude) &&
387 MathUtil.equals(this.launchPressure, o.launchPressure) &&
388 MathUtil.equals(this.launchRodAngle, o.launchRodAngle) &&
389 MathUtil.equals(this.launchRodDirection, o.launchRodDirection) &&
390 MathUtil.equals(this.launchRodLength, o.launchRodLength) &&
391 MathUtil.equals(this.launchTemperature, o.launchTemperature) &&
392 MathUtil.equals(this.maximumAngle, o.maximumAngle) &&
393 MathUtil.equals(this.timeStep, o.timeStep) &&
394 MathUtil.equals(this.windAverage, o.windAverage) &&
395 MathUtil.equals(this.windTurbulence, o.windTurbulence) &&
396 this.calculateExtras == o.calculateExtras);
400 * Hashcode method compatible with {@link #equals(Object)}.
403 public int hashCode() {
405 return rocket.hashCode();
406 return rocket.hashCode() + motorID.hashCode();
410 public void addChangeListener(ChangeListener listener) {
411 listeners.add(listener);
415 public void removeChangeListener(ChangeListener listener) {
416 listeners.remove(listener);
419 private final ChangeEvent event = new ChangeEvent(this);
420 private void fireChangeEvent() {
421 ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
423 for (int i=array.length-1; i >=0; i--) {
424 array[i].stateChanged(event);