Updates for 0.9.5
[debian/openrocket] / src / net / sf / openrocket / simulation / SimulationConditions.java
1 package net.sf.openrocket.simulation;
2
3 import java.util.ArrayList;
4 import java.util.List;
5
6 import javax.swing.event.ChangeEvent;
7 import javax.swing.event.ChangeListener;
8
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;
15
16
17 public class SimulationConditions implements ChangeSource, Cloneable {
18
19         public static final double MAX_LAUNCH_ROD_ANGLE = Math.PI/3;
20         
21         /**
22          * The ISA standard atmosphere.
23          */
24         private static final AtmosphericModel ISA_ATMOSPHERIC_MODEL = new ExtendedISAModel();
25         
26         
27         private final Rocket rocket;
28         private String motorID = null;
29
30         
31         /*
32          * NOTE:  When adding/modifying parameters, they must also be added to the
33          * equals and copyFrom methods!!
34          */
35
36         // TODO: HIGH: Fetch default values from Prefs!
37         
38         private double launchRodLength = 1;
39         
40         /** Launch rod angle > 0, radians from vertical */ 
41         private double launchRodAngle = 0;
42         
43         /** Launch rod direction, 0 = upwind, PI = downwind. */
44         private double launchRodDirection = 0;
45         
46
47         private double windAverage = 2.0;
48         private double windTurbulence = 0.1;
49         
50         private double launchAltitude = 0;
51         private double launchLatitude = 45;
52         
53         private boolean useISA = true;
54         private double launchTemperature = ExtendedISAModel.STANDARD_TEMPERATURE;
55         private double launchPressure = ExtendedISAModel.STANDARD_PRESSURE;
56         private AtmosphericModel atmosphericModel = null;
57         
58         
59         private double timeStep = RK4Simulator.RECOMMENDED_TIME_STEP;
60         private double maximumAngle = RK4Simulator.RECOMMENDED_ANGLE_STEP;
61         
62         private boolean calculateExtras = true;
63         
64
65         private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
66         
67         
68         
69         public SimulationConditions(Rocket rocket) {
70                 this.rocket = rocket;
71         }
72         
73         
74         
75         public Rocket getRocket() {
76                 return rocket;
77         }
78                 
79         
80         public String getMotorConfigurationID() {
81                 return motorID;
82         }
83         
84         /**
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>.
87          * 
88          * @param id    the configuration to set.
89          */
90         public void setMotorConfigurationID(String id) {
91                 if (id != null)
92                         id = id.intern();
93                 if (!rocket.isMotorConfigurationID(id))
94                         id = null;
95                 if (id == motorID)
96                         return;
97                 motorID = id;
98                 fireChangeEvent();
99         }
100         
101
102         public double getLaunchRodLength() {
103                 return launchRodLength;
104         }
105
106         public void setLaunchRodLength(double launchRodLength) {
107                 if (MathUtil.equals(this.launchRodLength, launchRodLength))
108                         return;
109                 this.launchRodLength = launchRodLength;
110                 fireChangeEvent();
111         }
112
113         
114         public double getLaunchRodAngle() {
115                 return launchRodAngle;
116         }
117
118         public void setLaunchRodAngle(double launchRodAngle) {
119                 launchRodAngle = MathUtil.clamp(launchRodAngle, 0, MAX_LAUNCH_ROD_ANGLE);
120                 if (MathUtil.equals(this.launchRodAngle, launchRodAngle))
121                         return;
122                 this.launchRodAngle = launchRodAngle;
123                 fireChangeEvent();
124         }
125
126         
127         public double getLaunchRodDirection() {
128                 return launchRodDirection;
129         }
130
131         public void setLaunchRodDirection(double launchRodDirection) {
132                 launchRodDirection = MathUtil.reduce180(launchRodDirection);
133                 if (MathUtil.equals(this.launchRodDirection, launchRodDirection))
134                         return;
135                 this.launchRodDirection = launchRodDirection;
136                 fireChangeEvent();
137         }
138
139         
140         
141         public double getWindSpeedAverage() {
142                 return windAverage;
143         }
144
145         public void setWindSpeedAverage(double windAverage) {
146                 if (MathUtil.equals(this.windAverage, windAverage))
147                         return;
148                 this.windAverage = MathUtil.max(windAverage, 0);
149                 fireChangeEvent();
150         }
151
152         
153         public double getWindSpeedDeviation() {
154                 return windAverage * windTurbulence;
155         }
156
157         public void setWindSpeedDeviation(double windDeviation) {
158                 if (windAverage < 0.1) {
159                         windAverage = 0.1;
160                 }
161                 setWindTurbulenceIntensity(windDeviation / windAverage);
162         }
163
164         
165         /**
166      * Return the wind turbulence intensity (standard deviation / average).
167      * 
168      * @return  the turbulence intensity
169      */
170     public double getWindTurbulenceIntensity() {
171         return windTurbulence;
172     }
173
174     /**
175      * Set the wind standard deviation to match the given turbulence intensity.
176      * 
177      * @param intensity   the turbulence intensity
178      */
179     public void setWindTurbulenceIntensity(double intensity) {
180         // Does not check equality so that setWindSpeedDeviation can be sure of event firing
181         this.windTurbulence = intensity;
182         fireChangeEvent();
183     }
184     
185
186         
187         
188         
189         public double getLaunchAltitude() {
190                 return launchAltitude;
191         }
192
193         public void setLaunchAltitude(double altitude) {
194                 if (MathUtil.equals(this.launchAltitude, altitude))
195                         return;
196                 this.launchAltitude = altitude;
197                 fireChangeEvent();
198         }
199         
200         
201         public double getLaunchLatitude() {
202                 return launchLatitude;
203         }
204
205         public void setLaunchLatitude(double launchLatitude) {
206                 launchLatitude = MathUtil.clamp(launchLatitude, -90, 90);
207                 if (MathUtil.equals(this.launchLatitude, launchLatitude))
208                         return;
209                 this.launchLatitude = launchLatitude;
210                 fireChangeEvent();
211         }
212
213
214         
215         
216         
217         public boolean isISAAtmosphere() {
218                 return useISA;
219         }
220         
221         public void setISAAtmosphere(boolean isa) {
222                 if (isa == useISA)
223                         return;
224                 useISA = isa;
225                 fireChangeEvent();
226         }
227         
228
229         public double getLaunchTemperature() {
230                 return launchTemperature;
231         }
232
233
234
235         public void setLaunchTemperature(double launchTemperature) {
236                 if (MathUtil.equals(this.launchTemperature, launchTemperature))
237                         return;
238                 this.launchTemperature = launchTemperature;
239                 this.atmosphericModel = null;
240                 fireChangeEvent();
241         }
242
243
244
245         public double getLaunchPressure() {
246                 return launchPressure;
247         }
248
249
250
251         public void setLaunchPressure(double launchPressure) {
252                 if (MathUtil.equals(this.launchPressure, launchPressure))
253                         return;
254                 this.launchPressure = launchPressure;
255                 this.atmosphericModel = null;
256                 fireChangeEvent();
257         }
258
259         
260         /**
261          * Returns an atmospheric model corresponding to the launch conditions.  The
262          * atmospheric models may be shared between different calls.
263          * 
264          * @return      an AtmosphericModel object.
265          */
266         public AtmosphericModel getAtmosphericModel() {
267                 if (useISA) {
268                         return ISA_ATMOSPHERIC_MODEL;
269                 }
270                 if (atmosphericModel == null) {
271                         atmosphericModel = new ExtendedISAModel(launchAltitude, 
272                                         launchTemperature, launchPressure);
273                 }
274                 return atmosphericModel;
275         }
276         
277         
278         public double getTimeStep() {
279                 return timeStep;
280         }
281
282         public void setTimeStep(double timeStep) {
283                 if (MathUtil.equals(this.timeStep, timeStep))
284                         return;
285                 this.timeStep = timeStep;
286                 fireChangeEvent();
287         }
288
289         public double getMaximumStepAngle() {
290                 return maximumAngle;
291         }
292
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))
296                         return;
297                 this.maximumAngle = maximumAngle;
298                 fireChangeEvent();
299         }
300
301
302
303         public boolean getCalculateExtras() {
304                 return calculateExtras;
305         }
306
307
308
309         public void setCalculateExtras(boolean calculateExtras) {
310                 if (this.calculateExtras == calculateExtras)
311                         return;
312                 this.calculateExtras = calculateExtras;
313                 fireChangeEvent();
314         }
315
316
317         
318         @Override
319         public SimulationConditions clone() {
320                 try {
321                         SimulationConditions copy = (SimulationConditions)super.clone();
322                         copy.listeners = new ArrayList<ChangeListener>();
323                         return copy;
324                 } catch (CloneNotSupportedException e) {
325                         throw new BugException(e);
326                 }
327         }
328         
329         
330         public void copyFrom(SimulationConditions src) {
331                 
332                 if (this.rocket == src.rocket) {
333                         
334                         this.motorID = src.motorID;
335                         
336                 } else {
337                 
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;
342                                 
343                                 for (String id: this.rocket.getMotorConfigurationIDs()) {
344                                         if (motorDesc.equals(this.rocket.getMotorConfigurationDescription(id))) {
345                                                 matchID = id;
346                                                 break;
347                                         }
348                                 }
349                                 
350                                 this.motorID = matchID;
351                         } else {
352                                 this.motorID = null;
353                         }
354                 }
355                 
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;
368                 
369                 fireChangeEvent();
370         }
371         
372         
373         
374         /**
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.
377          */
378         @Override
379         public boolean equals(Object other) {
380                 if (!(other instanceof SimulationConditions))
381                         return false;
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);
397         }
398         
399         /**
400          * Hashcode method compatible with {@link #equals(Object)}.
401          */
402         @Override
403         public int hashCode() {
404                 if (motorID == null)
405                         return rocket.hashCode();
406                 return rocket.hashCode() + motorID.hashCode();
407         }
408
409         @Override
410         public void addChangeListener(ChangeListener listener) {
411                 listeners.add(listener);
412         }
413
414         @Override
415         public void removeChangeListener(ChangeListener listener) {
416                 listeners.remove(listener);
417         }
418         
419         private final ChangeEvent event = new ChangeEvent(this);
420         private void fireChangeEvent() {
421                 ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
422                 
423                 for (int i=array.length-1; i >=0; i--) {
424                         array[i].stateChanged(event);
425                 }
426         }
427         
428 }