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