Initial commit
[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         public void setMotorConfigurationID(String id) {
84                 if (id != null)
85                         id = id.intern();
86                 if (id == motorID)
87                         return;
88                 motorID = id;
89                 fireChangeEvent();
90         }
91         
92
93         public double getLaunchRodLength() {
94                 return launchRodLength;
95         }
96
97         public void setLaunchRodLength(double launchRodLength) {
98                 if (MathUtil.equals(this.launchRodLength, launchRodLength))
99                         return;
100                 this.launchRodLength = launchRodLength;
101                 fireChangeEvent();
102         }
103
104         
105         public double getLaunchRodAngle() {
106                 return launchRodAngle;
107         }
108
109         public void setLaunchRodAngle(double launchRodAngle) {
110                 launchRodAngle = MathUtil.clamp(launchRodAngle, 0, MAX_LAUNCH_ROD_ANGLE);
111                 if (MathUtil.equals(this.launchRodAngle, launchRodAngle))
112                         return;
113                 this.launchRodAngle = launchRodAngle;
114                 fireChangeEvent();
115         }
116
117         
118         public double getLaunchRodDirection() {
119                 return launchRodDirection;
120         }
121
122         public void setLaunchRodDirection(double launchRodDirection) {
123                 launchRodDirection = MathUtil.reduce180(launchRodDirection);
124                 if (MathUtil.equals(this.launchRodDirection, launchRodDirection))
125                         return;
126                 this.launchRodDirection = launchRodDirection;
127                 fireChangeEvent();
128         }
129
130         
131         
132         public double getWindSpeedAverage() {
133                 return windAverage;
134         }
135
136         public void setWindSpeedAverage(double windAverage) {
137                 if (MathUtil.equals(this.windAverage, windAverage))
138                         return;
139                 this.windAverage = MathUtil.max(windAverage, 0);
140                 fireChangeEvent();
141         }
142
143         
144         public double getWindSpeedDeviation() {
145                 return windAverage * windTurbulence;
146         }
147
148         public void setWindSpeedDeviation(double windDeviation) {
149                 if (windAverage < 0.1) {
150                         windAverage = 0.1;
151                 }
152                 setWindTurbulenceIntensity(windDeviation / windAverage);
153         }
154
155         
156         /**
157      * Return the wind turbulence intensity (standard deviation / average).
158      * 
159      * @return  the turbulence intensity
160      */
161     public double getWindTurbulenceIntensity() {
162         return windTurbulence;
163     }
164
165     /**
166      * Set the wind standard deviation to match the given turbulence intensity.
167      * 
168      * @param intensity   the turbulence intensity
169      */
170     public void setWindTurbulenceIntensity(double intensity) {
171         // Does not check equality so that setWindSpeedDeviation can be sure of event firing
172         this.windTurbulence = intensity;
173         fireChangeEvent();
174     }
175     
176
177         
178         
179         
180         public double getLaunchAltitude() {
181                 return launchAltitude;
182         }
183
184         public void setLaunchAltitude(double altitude) {
185                 if (MathUtil.equals(this.launchAltitude, altitude))
186                         return;
187                 this.launchAltitude = altitude;
188                 fireChangeEvent();
189         }
190         
191         
192         public double getLaunchLatitude() {
193                 return launchLatitude;
194         }
195
196         public void setLaunchLatitude(double launchLatitude) {
197                 launchLatitude = MathUtil.clamp(launchLatitude, -90, 90);
198                 if (MathUtil.equals(this.launchLatitude, launchLatitude))
199                         return;
200                 this.launchLatitude = launchLatitude;
201                 fireChangeEvent();
202         }
203
204
205         
206         
207         
208         public boolean isISAAtmosphere() {
209                 return useISA;
210         }
211         
212         public void setISAAtmosphere(boolean isa) {
213                 if (isa == useISA)
214                         return;
215                 useISA = isa;
216                 fireChangeEvent();
217         }
218         
219
220         public double getLaunchTemperature() {
221                 return launchTemperature;
222         }
223
224
225
226         public void setLaunchTemperature(double launchTemperature) {
227                 if (MathUtil.equals(this.launchTemperature, launchTemperature))
228                         return;
229                 this.launchTemperature = launchTemperature;
230                 this.atmosphericModel = null;
231                 fireChangeEvent();
232         }
233
234
235
236         public double getLaunchPressure() {
237                 return launchPressure;
238         }
239
240
241
242         public void setLaunchPressure(double launchPressure) {
243                 if (MathUtil.equals(this.launchPressure, launchPressure))
244                         return;
245                 this.launchPressure = launchPressure;
246                 this.atmosphericModel = null;
247                 fireChangeEvent();
248         }
249
250         
251         /**
252          * Returns an atmospheric model corresponding to the launch conditions.  The
253          * atmospheric models may be shared between different calls.
254          * 
255          * @return      an AtmosphericModel object.
256          */
257         public AtmosphericModel getAtmosphericModel() {
258                 if (useISA) {
259                         return ISA_ATMOSPHERIC_MODEL;
260                 }
261                 if (atmosphericModel == null) {
262                         atmosphericModel = new ExtendedISAModel(launchAltitude, 
263                                         launchTemperature, launchPressure);
264                 }
265                 return atmosphericModel;
266         }
267         
268         
269         public double getTimeStep() {
270                 return timeStep;
271         }
272
273         public void setTimeStep(double timeStep) {
274                 if (MathUtil.equals(this.timeStep, timeStep))
275                         return;
276                 this.timeStep = timeStep;
277                 fireChangeEvent();
278         }
279
280         public double getMaximumStepAngle() {
281                 return maximumAngle;
282         }
283
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))
287                         return;
288                 this.maximumAngle = maximumAngle;
289                 fireChangeEvent();
290         }
291
292
293
294         public boolean getCalculateExtras() {
295                 return calculateExtras;
296         }
297
298
299
300         public void setCalculateExtras(boolean calculateExtras) {
301                 if (this.calculateExtras == calculateExtras)
302                         return;
303                 this.calculateExtras = calculateExtras;
304                 fireChangeEvent();
305         }
306
307
308         
309         @Override
310         public SimulationConditions clone() {
311                 try {
312                         SimulationConditions copy = (SimulationConditions)super.clone();
313                         copy.listeners = new ArrayList<ChangeListener>();
314                         return copy;
315                 } catch (CloneNotSupportedException e) {
316                         throw new RuntimeException(e);
317                 }
318         }
319         
320         
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");
325                 }
326                 if (this.equals(src))
327                         return;
328                 
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;
342                 
343                 fireChangeEvent();
344         }
345         
346         
347         
348         /**
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.
351          */
352         @Override
353         public boolean equals(Object other) {
354                 if (!(other instanceof SimulationConditions))
355                         return false;
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);
371         }
372         
373         /**
374          * Hashcode method compatible with {@link #equals(Object)}.
375          */
376         @Override
377         public int hashCode() {
378                 if (motorID == null)
379                         return rocket.hashCode();
380                 return rocket.hashCode() + motorID.hashCode();
381         }
382
383         @Override
384         public void addChangeListener(ChangeListener listener) {
385                 listeners.add(listener);
386         }
387
388         @Override
389         public void removeChangeListener(ChangeListener listener) {
390                 listeners.remove(listener);
391         }
392         
393         private final ChangeEvent event = new ChangeEvent(this);
394         private void fireChangeEvent() {
395                 ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
396                 
397                 for (int i=array.length-1; i >=0; i--) {
398                         array[i].stateChanged(event);
399                 }
400         }
401         
402 }