a2b39854bbfed73b0ef8a93944b15a2aed59575d
[debian/openrocket] / src / net / sf / openrocket / simulation / SimulationOptions.java
1 package net.sf.openrocket.simulation;
2
3 import java.util.ArrayList;
4 import java.util.List;
5 import java.util.Random;
6
7 import javax.swing.event.ChangeEvent;
8 import javax.swing.event.ChangeListener;
9
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;
20 import net.sf.openrocket.util.Utils;
21
22 /**
23  * A class holding simulation options in basic parameter form and which functions
24  * as a ChangeSource.  A SimulationConditions instance is generated from this class
25  * using {@link #toSimulationConditions()}.
26  * 
27  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
28  */
29 public class SimulationOptions implements ChangeSource, Cloneable {
30         
31         public static final double MAX_LAUNCH_ROD_ANGLE = Math.PI / 3;
32         
33         /**
34          * The ISA standard atmosphere.
35          */
36         private static final AtmosphericModel ISA_ATMOSPHERIC_MODEL = new ExtendedISAModel();
37         
38
39         private final Rocket rocket;
40         private String motorID = null;
41         
42
43         /*
44          * NOTE:  When adding/modifying parameters, they must also be added to the
45          * equals and copyFrom methods!!
46          */
47
48         // TODO: HIGH: Fetch default values from Prefs!
49         
50         private double launchRodLength = 1;
51         
52         /** Launch rod angle > 0, radians from vertical */
53         private double launchRodAngle = 0;
54         
55         /** Launch rod direction, 0 = upwind, PI = downwind. */
56         private double launchRodDirection = 0;
57         
58
59         private double windAverage = 2.0;
60         private double windTurbulence = 0.1;
61         
62         private double launchAltitude = 0;
63         private double launchLatitude = 45;
64         
65         private boolean useISA = true;
66         private double launchTemperature = ExtendedISAModel.STANDARD_TEMPERATURE;
67         private double launchPressure = ExtendedISAModel.STANDARD_PRESSURE;
68         
69
70         private double timeStep = RK4SimulationStepper.RECOMMENDED_TIME_STEP;
71         private double maximumAngle = RK4SimulationStepper.RECOMMENDED_ANGLE_STEP;
72         
73         private int randomSeed = new Random().nextInt();
74         
75         private boolean calculateExtras = true;
76         
77
78         private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
79         
80         
81
82         public SimulationOptions(Rocket rocket) {
83                 this.rocket = rocket;
84         }
85         
86         
87
88         public Rocket getRocket() {
89                 return rocket;
90         }
91         
92         
93         public String getMotorConfigurationID() {
94                 return motorID;
95         }
96         
97         /**
98          * Set the motor configuration ID.  This must be a valid motor configuration ID of
99          * the rocket, otherwise the configuration is set to <code>null</code>.
100          * 
101          * @param id    the configuration to set.
102          */
103         public void setMotorConfigurationID(String id) {
104                 if (id != null)
105                         id = id.intern();
106                 if (!rocket.isMotorConfigurationID(id))
107                         id = null;
108                 if (id == motorID)
109                         return;
110                 motorID = id;
111                 fireChangeEvent();
112         }
113         
114         
115         public double getLaunchRodLength() {
116                 return launchRodLength;
117         }
118         
119         public void setLaunchRodLength(double launchRodLength) {
120                 if (MathUtil.equals(this.launchRodLength, launchRodLength))
121                         return;
122                 this.launchRodLength = launchRodLength;
123                 fireChangeEvent();
124         }
125         
126         
127         public double getLaunchRodAngle() {
128                 return launchRodAngle;
129         }
130         
131         public void setLaunchRodAngle(double launchRodAngle) {
132                 launchRodAngle = MathUtil.clamp(launchRodAngle, 0, MAX_LAUNCH_ROD_ANGLE);
133                 if (MathUtil.equals(this.launchRodAngle, launchRodAngle))
134                         return;
135                 this.launchRodAngle = launchRodAngle;
136                 fireChangeEvent();
137         }
138         
139         
140         public double getLaunchRodDirection() {
141                 return launchRodDirection;
142         }
143         
144         public void setLaunchRodDirection(double launchRodDirection) {
145                 launchRodDirection = MathUtil.reduce180(launchRodDirection);
146                 if (MathUtil.equals(this.launchRodDirection, launchRodDirection))
147                         return;
148                 this.launchRodDirection = launchRodDirection;
149                 fireChangeEvent();
150         }
151         
152         
153
154         public double getWindSpeedAverage() {
155                 return windAverage;
156         }
157         
158         public void setWindSpeedAverage(double windAverage) {
159                 if (MathUtil.equals(this.windAverage, windAverage))
160                         return;
161                 this.windAverage = MathUtil.max(windAverage, 0);
162                 fireChangeEvent();
163         }
164         
165         
166         public double getWindSpeedDeviation() {
167                 return windAverage * windTurbulence;
168         }
169         
170         public void setWindSpeedDeviation(double windDeviation) {
171                 if (windAverage < 0.1) {
172                         windAverage = 0.1;
173                 }
174                 setWindTurbulenceIntensity(windDeviation / windAverage);
175         }
176         
177         
178         /**
179          * Return the wind turbulence intensity (standard deviation / average).
180          * 
181          * @return  the turbulence intensity
182          */
183         public double getWindTurbulenceIntensity() {
184                 return windTurbulence;
185         }
186         
187         /**
188          * Set the wind standard deviation to match the given turbulence intensity.
189          * 
190          * @param intensity   the turbulence intensity
191          */
192         public void setWindTurbulenceIntensity(double intensity) {
193                 // Does not check equality so that setWindSpeedDeviation can be sure of event firing
194                 this.windTurbulence = intensity;
195                 fireChangeEvent();
196         }
197         
198         
199
200
201
202         public double getLaunchAltitude() {
203                 return launchAltitude;
204         }
205         
206         public void setLaunchAltitude(double altitude) {
207                 if (MathUtil.equals(this.launchAltitude, altitude))
208                         return;
209                 this.launchAltitude = altitude;
210                 fireChangeEvent();
211         }
212         
213         
214         public double getLaunchLatitude() {
215                 return launchLatitude;
216         }
217         
218         public void setLaunchLatitude(double launchLatitude) {
219                 launchLatitude = MathUtil.clamp(launchLatitude, -90, 90);
220                 if (MathUtil.equals(this.launchLatitude, launchLatitude))
221                         return;
222                 this.launchLatitude = launchLatitude;
223                 fireChangeEvent();
224         }
225         
226         
227
228
229
230         public boolean isISAAtmosphere() {
231                 return useISA;
232         }
233         
234         public void setISAAtmosphere(boolean isa) {
235                 if (isa == useISA)
236                         return;
237                 useISA = isa;
238                 fireChangeEvent();
239         }
240         
241         
242         public double getLaunchTemperature() {
243                 return launchTemperature;
244         }
245         
246         
247
248         public void setLaunchTemperature(double launchTemperature) {
249                 if (MathUtil.equals(this.launchTemperature, launchTemperature))
250                         return;
251                 this.launchTemperature = launchTemperature;
252                 fireChangeEvent();
253         }
254         
255         
256
257         public double getLaunchPressure() {
258                 return launchPressure;
259         }
260         
261         
262
263         public void setLaunchPressure(double launchPressure) {
264                 if (MathUtil.equals(this.launchPressure, launchPressure))
265                         return;
266                 this.launchPressure = launchPressure;
267                 fireChangeEvent();
268         }
269         
270         
271         /**
272          * Returns an atmospheric model corresponding to the launch conditions.  The
273          * atmospheric models may be shared between different calls.
274          * 
275          * @return      an AtmosphericModel object.
276          */
277         private AtmosphericModel getAtmosphericModel() {
278                 if (useISA) {
279                         return ISA_ATMOSPHERIC_MODEL;
280                 }
281                 return new ExtendedISAModel(launchAltitude, launchTemperature, launchPressure);
282         }
283         
284         
285         public double getTimeStep() {
286                 return timeStep;
287         }
288         
289         public void setTimeStep(double timeStep) {
290                 if (MathUtil.equals(this.timeStep, timeStep))
291                         return;
292                 this.timeStep = timeStep;
293                 fireChangeEvent();
294         }
295         
296         public double getMaximumStepAngle() {
297                 return maximumAngle;
298         }
299         
300         public void setMaximumStepAngle(double maximumAngle) {
301                 maximumAngle = MathUtil.clamp(maximumAngle, 1 * Math.PI / 180, 20 * Math.PI / 180);
302                 if (MathUtil.equals(this.maximumAngle, maximumAngle))
303                         return;
304                 this.maximumAngle = maximumAngle;
305                 fireChangeEvent();
306         }
307         
308         
309
310         public boolean getCalculateExtras() {
311                 return calculateExtras;
312         }
313         
314         
315
316         public void setCalculateExtras(boolean calculateExtras) {
317                 if (this.calculateExtras == calculateExtras)
318                         return;
319                 this.calculateExtras = calculateExtras;
320                 fireChangeEvent();
321         }
322         
323         
324
325         public int getRandomSeed() {
326                 return randomSeed;
327         }
328         
329         public void setRandomSeed(int randomSeed) {
330                 if (this.randomSeed == randomSeed) {
331                         return;
332                 }
333                 this.randomSeed = randomSeed;
334                 /*
335                  * This does not fire an event since we don't want to invalidate simulation results
336                  * due to changing the seed value.  This needs to be revisited if the user is ever
337                  * allowed to select the seed value.
338                  */
339                 //              fireChangeEvent();
340         }
341         
342         /**
343          * Randomize the random seed value.
344          */
345         public void randomizeSeed() {
346                 this.randomSeed = new Random().nextInt();
347                 //              fireChangeEvent();
348         }
349         
350         
351
352         @Override
353         public SimulationOptions clone() {
354                 try {
355                         SimulationOptions copy = (SimulationOptions) super.clone();
356                         copy.listeners = new ArrayList<ChangeListener>();
357                         return copy;
358                 } catch (CloneNotSupportedException e) {
359                         throw new BugException(e);
360                 }
361         }
362         
363         
364         public void copyFrom(SimulationOptions src) {
365                 
366                 if (this.rocket == src.rocket) {
367                         
368                         this.motorID = src.motorID;
369                         
370                 } else {
371                         
372                         if (src.rocket.hasMotors(src.motorID)) {
373                                 // Try to find a matching motor ID
374                                 String motorDesc = src.rocket.getMotorConfigurationDescription(src.motorID);
375                                 String matchID = null;
376                                 
377                                 for (String id : this.rocket.getMotorConfigurationIDs()) {
378                                         if (motorDesc.equals(this.rocket.getMotorConfigurationDescription(id))) {
379                                                 matchID = id;
380                                                 break;
381                                         }
382                                 }
383                                 
384                                 this.motorID = matchID;
385                         } else {
386                                 this.motorID = null;
387                         }
388                 }
389                 
390                 this.launchAltitude = src.launchAltitude;
391                 this.launchLatitude = src.launchLatitude;
392                 this.launchPressure = src.launchPressure;
393                 this.launchRodAngle = src.launchRodAngle;
394                 this.launchRodDirection = src.launchRodDirection;
395                 this.launchRodLength = src.launchRodLength;
396                 this.launchTemperature = src.launchTemperature;
397                 this.maximumAngle = src.maximumAngle;
398                 this.timeStep = src.timeStep;
399                 this.windAverage = src.windAverage;
400                 this.windTurbulence = src.windTurbulence;
401                 this.calculateExtras = src.calculateExtras;
402                 this.randomSeed = src.randomSeed;
403                 
404                 fireChangeEvent();
405         }
406         
407         
408
409         /**
410          * Compares whether the two simulation conditions are equal.  The two are considered
411          * equal if the rocket, motor id and all variables are equal.
412          */
413         @Override
414         public boolean equals(Object other) {
415                 if (!(other instanceof SimulationOptions))
416                         return false;
417                 SimulationOptions o = (SimulationOptions) other;
418                 return ((this.rocket == o.rocket) &&
419                                 Utils.equals(this.motorID, o.motorID) &&
420                                 MathUtil.equals(this.launchAltitude, o.launchAltitude) &&
421                                 MathUtil.equals(this.launchLatitude, o.launchLatitude) &&
422                                 MathUtil.equals(this.launchPressure, o.launchPressure) &&
423                                 MathUtil.equals(this.launchRodAngle, o.launchRodAngle) &&
424                                 MathUtil.equals(this.launchRodDirection, o.launchRodDirection) &&
425                                 MathUtil.equals(this.launchRodLength, o.launchRodLength) &&
426                                 MathUtil.equals(this.launchTemperature, o.launchTemperature) &&
427                                 MathUtil.equals(this.maximumAngle, o.maximumAngle) &&
428                                 MathUtil.equals(this.timeStep, o.timeStep) &&
429                                 MathUtil.equals(this.windAverage, o.windAverage) &&
430                                 MathUtil.equals(this.windTurbulence, o.windTurbulence) &&
431                                 this.calculateExtras == o.calculateExtras && this.randomSeed == o.randomSeed);
432         }
433         
434         /**
435          * Hashcode method compatible with {@link #equals(Object)}.
436          */
437         @Override
438         public int hashCode() {
439                 if (motorID == null)
440                         return rocket.hashCode();
441                 return rocket.hashCode() + motorID.hashCode();
442         }
443         
444         @Override
445         public void addChangeListener(ChangeListener listener) {
446                 listeners.add(listener);
447         }
448         
449         @Override
450         public void removeChangeListener(ChangeListener listener) {
451                 listeners.remove(listener);
452         }
453         
454         private final ChangeEvent event = new ChangeEvent(this);
455         
456         private void fireChangeEvent() {
457                 ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
458                 
459                 for (int i = array.length - 1; i >= 0; i--) {
460                         array[i].stateChanged(event);
461                 }
462         }
463         
464         
465         // TODO: HIGH: Clean up
466         public SimulationConditions toSimulationConditions() {
467                 SimulationConditions conditions = new SimulationConditions();
468                 
469                 conditions.setRocket((Rocket) getRocket().copy());
470                 conditions.setMotorConfigurationID(getMotorConfigurationID());
471                 conditions.setLaunchRodLength(getLaunchRodLength());
472                 conditions.setLaunchRodAngle(getLaunchRodAngle());
473                 conditions.setLaunchRodDirection(getLaunchRodDirection());
474                 conditions.setLaunchAltitude(getLaunchAltitude());
475                 conditions.setLaunchLatitude(getLaunchLatitude());
476                 conditions.setRandomSeed(randomSeed);
477                 
478                 PinkNoiseWindModel windModel = new PinkNoiseWindModel(randomSeed);
479                 windModel.setAverage(getWindSpeedAverage());
480                 windModel.setStandardDeviation(getWindSpeedDeviation());
481                 conditions.setWindModel(windModel);
482                 
483                 conditions.setAtmosphericModel(getAtmosphericModel());
484                 
485                 BasicGravityModel gravityModel = new BasicGravityModel(getLaunchLatitude());
486                 conditions.setGravityModel(gravityModel);
487                 
488                 conditions.setAerodynamicCalculator(new BarrowmanCalculator());
489                 conditions.setMassCalculator(new BasicMassCalculator());
490                 
491                 conditions.setTimeStep(getTimeStep());
492                 conditions.setMaximumAngleStep(getMaximumStepAngle());
493                 
494                 conditions.setCalculateExtras(getCalculateExtras());
495                 
496                 return conditions;
497         }
498         
499 }