Updates for 0.9.5
[debian/openrocket] / src / net / sf / openrocket / aerodynamics / FlightConditions.java
1 package net.sf.openrocket.aerodynamics;
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.rocketcomponent.Configuration;
10 import net.sf.openrocket.util.BugException;
11 import net.sf.openrocket.util.ChangeSource;
12 import net.sf.openrocket.util.MathUtil;
13
14
15 public class FlightConditions implements Cloneable, ChangeSource {
16
17         private List<ChangeListener> listenerList = new ArrayList<ChangeListener>();
18         private ChangeEvent event = new ChangeEvent(this);
19         
20         /** Modification count */
21         private int modCount = 0;
22         
23         /** Reference length used in calculations. */
24         private double refLength = 1.0;
25         
26         /** Reference area used in calculations. */
27         private double refArea = Math.PI * 0.25;
28
29         
30         /** Angle of attack. */
31         private double aoa = 0;
32         
33         /** Sine of the angle of attack. */
34         private double sinAOA = 0;
35         
36         /** 
37          * The fraction <code>sin(aoa) / aoa</code>.  At an AOA of zero this value 
38          * must be one.  This value may be used in many cases to avoid checking for
39          * division by zero. 
40          */
41         private double sincAOA = 1.0;
42         
43         /** Lateral wind direction. */
44         private double theta = 0;
45         
46         /** Current Mach speed. */
47         private double mach = 0.3;
48         
49         /**
50          * Sqrt(1 - M^2)  for M<1
51          * Sqrt(M^2 - 1)  for M>1
52          */
53         private double beta = Math.sqrt(1 - mach*mach);
54
55         
56         /** Current roll rate. */
57         private double rollRate = 0;
58         
59         private double pitchRate = 0;
60         private double yawRate = 0;
61         
62         
63         private AtmosphericConditions atmosphericConditions = new AtmosphericConditions();
64         
65         
66         
67         /**
68          * Sole constructor.  The reference length is initialized to the reference length
69          * of the <code>Configuration</code>, and the reference area accordingly.
70          * If <code>config</code> is <code>null</code>, then the reference length is set
71          * to 1 meter.
72          * 
73          * @param config   the configuration of which the reference length is taken.
74          */
75         public FlightConditions(Configuration config) {
76                 if (config != null)
77                         setRefLength(config.getReferenceLength());
78         }
79         
80         
81         /**
82          * Set the reference length from the given configuration.
83          * @param config        the configuration from which to get the reference length.
84          */
85         public void setReference(Configuration config) {
86                 setRefLength(config.getReferenceLength());
87         }
88         
89         
90         /**
91          * Set the reference length and area.
92          */
93         public void setRefLength(double length) {
94                 refLength = length;
95
96                 refArea = Math.PI * MathUtil.pow2(length/2);
97                 fireChangeEvent();
98         }
99
100         /**
101          * Return the reference length.
102          */
103         public double getRefLength() {
104                 return refLength;
105         }
106
107         /**
108          * Set the reference area and length.
109          */
110         public void setRefArea(double area) {
111                 refArea = area;
112                 refLength = Math.sqrt(area / Math.PI)*2;
113                 fireChangeEvent();
114         }
115         
116         /**
117          * Return the reference area.
118          */
119         public double getRefArea() {
120                 return refArea;
121         }
122
123         
124         /**
125          * Sets the angle of attack.  It calculates values also for the methods 
126          * {@link #getSinAOA()} and {@link #getSincAOA()}. 
127          * 
128          * @param aoa   the angle of attack.
129          */
130         public void setAOA(double aoa) {
131                 aoa = MathUtil.clamp(aoa, 0, Math.PI);
132                 if (MathUtil.equals(this.aoa, aoa))
133                         return;
134                 
135                 this.aoa = aoa;
136                 if (aoa < 0.001) {
137                         this.sinAOA = aoa;
138                         this.sincAOA = 1.0;
139                 } else {
140                         this.sinAOA = Math.sin(aoa);
141                         this.sincAOA = sinAOA / aoa;
142                 }
143                 fireChangeEvent();
144         }
145
146         
147         /**
148          * Sets the angle of attack with the sine.  The value <code>sinAOA</code> is assumed
149          * to be the sine of <code>aoa</code> for cases in which this value is known.
150          * The AOA must still be specified, as the sine is not unique in the range
151          * of 0..180 degrees.
152          * 
153          * @param aoa           the angle of attack in radians.
154          * @param sinAOA        the sine of the angle of attack.
155          */
156         public void setAOA(double aoa, double sinAOA) {
157                 aoa = MathUtil.clamp(aoa, 0, Math.PI);
158                 sinAOA = MathUtil.clamp(sinAOA, 0, 1);
159                 if (MathUtil.equals(this.aoa, aoa))
160                         return;
161                 
162                 assert(Math.abs(Math.sin(aoa) - sinAOA) < 0.0001) : 
163                         "Illegal sine: aoa="+aoa+" sinAOA="+sinAOA;
164                 
165                 this.aoa = aoa;
166                 this.sinAOA = sinAOA;
167                 if (aoa < 0.001) {
168                         this.sincAOA = 1.0;
169                 } else {
170                         this.sincAOA = sinAOA / aoa;
171                 }
172                 fireChangeEvent();
173         }
174
175
176         /**
177          * Return the angle of attack.
178          */
179         public double getAOA() {
180                 return aoa;
181         }
182
183         /**
184          * Return the sine of the angle of attack.
185          */
186         public double getSinAOA() {
187                 return sinAOA;
188         }
189
190         /**
191          * Return the sinc of the angle of attack (sin(AOA) / AOA).  This method returns
192          * one if the angle of attack is zero.
193          */
194         public double getSincAOA() {
195                 return sincAOA;
196         }
197         
198         
199         /**
200          * Set the direction of the lateral airflow.
201          */
202         public void setTheta(double theta) {
203                 if (MathUtil.equals(this.theta, theta))
204                         return;
205                 this.theta = theta;
206                 fireChangeEvent();
207         }
208
209         /**
210          * Return the direction of the lateral airflow.
211          */
212         public double getTheta() {
213                 return theta;
214         }
215         
216
217         /**
218          * Set the current Mach speed.  This should be (but is not required to be) in 
219          * reference to the speed of sound of the atmospheric conditions.
220          */
221         public void setMach(double mach) {
222                 mach = Math.max(mach, 0);
223                 if (MathUtil.equals(this.mach, mach))
224                         return;
225                 
226                 this.mach = mach;
227                 if (mach < 1)
228                         this.beta = Math.sqrt(1 - mach*mach);
229                 else
230                         this.beta = Math.sqrt(mach*mach - 1);
231                 fireChangeEvent();
232         }
233         
234         /**
235          * Return the current Mach speed.
236          */
237         public double getMach() {
238                 return mach;
239         }
240         
241         /**
242          * Returns the current rocket velocity, calculated from the Mach number and the
243          * speed of sound.  If either of these parameters are changed, the velocity changes
244          * as well.
245          * 
246          * @return  the velocity of the rocket.
247          */
248         public double getVelocity() {
249                 return mach * atmosphericConditions.getMachSpeed();
250         }
251         
252         /**
253          * Sets the Mach speed according to the given velocity and the current speed of sound.
254          * 
255          * @param velocity      the current velocity.
256          */
257         public void setVelocity(double velocity) {
258                 setMach(velocity / atmosphericConditions.getMachSpeed());
259         }
260         
261
262         /**
263          * Return sqrt(abs(1 - Mach^2)).  This is calculated in the setting call and is
264          * therefore fast.
265          */
266         public double getBeta() {
267                 return beta;
268         }
269         
270         
271         /**
272          * Return the current roll rate.
273          */
274         public double getRollRate() {
275                 return rollRate;
276         }
277         
278         
279         /**
280          * Set the current roll rate.
281          */
282         public void setRollRate(double rate) {
283                 if (MathUtil.equals(this.rollRate, rate))
284                         return;
285                 
286                 this.rollRate = rate;
287                 fireChangeEvent();
288         }
289
290         
291         public double getPitchRate() {
292                 return pitchRate;
293         }
294
295
296         public void setPitchRate(double pitchRate) {
297                 if (MathUtil.equals(this.pitchRate, pitchRate))
298                         return;
299                 this.pitchRate = pitchRate;
300                 fireChangeEvent();
301         }
302
303
304         public double getYawRate() {
305                 return yawRate;
306         }
307
308
309         public void setYawRate(double yawRate) {
310                 if (MathUtil.equals(this.yawRate, yawRate))
311                         return;
312                 this.yawRate = yawRate;
313                 fireChangeEvent();
314         }
315
316
317         /**
318          * Return the current atmospheric conditions.  Note that this method returns a
319          * reference to the {@link AtmosphericConditions} object used by this object.
320          * Changes made to the object will modify the encapsulated object, but will NOT
321          * generate change events.
322          * 
323          * @return              the current atmospheric conditions.
324          */
325         public AtmosphericConditions getAtmosphericConditions() {
326                 return atmosphericConditions;
327         }
328
329         /**
330          * Set the current atmospheric conditions.  This method will fire a change event
331          * if a change occurs.
332          */
333         public void setAtmosphericConditions(AtmosphericConditions cond) {
334                 if (atmosphericConditions == cond)
335                         return;
336                 atmosphericConditions = cond;
337                 fireChangeEvent();
338         }
339         
340         
341         /**
342          * Retrieve the modification count of this object.  Each time it is modified
343          * the modification count is increased by one.
344          * 
345          * @return      the number of times this object has been modified since instantiation.
346          */
347         public int getModCount() {
348                 return modCount;
349         }
350         
351         
352         @Override
353         public String toString() {
354                 return String.format("FlightConditions[aoa=%.2f\u00b0,theta=%.2f\u00b0,"+
355                                 "mach=%.2f,rollRate=%.2f]", 
356                                 aoa*180/Math.PI, theta*180/Math.PI, mach, rollRate);
357         }
358         
359         
360         /**
361          * Return a copy of the flight conditions.  The copy has no listeners.  The
362          * atmospheric conditions is also cloned.
363          */
364         @Override
365         public FlightConditions clone() {
366                 try {
367                         FlightConditions cond = (FlightConditions) super.clone();
368                         cond.listenerList = new ArrayList<ChangeListener>();
369                         cond.event = new ChangeEvent(cond);
370                         cond.atmosphericConditions = atmosphericConditions.clone();
371                         return cond;
372                 } catch (CloneNotSupportedException e) {
373                         throw new BugException("BUG: clone not supported!",e);
374                 }
375         }
376
377         
378         
379         @Override
380         public void addChangeListener(ChangeListener listener) {
381                 listenerList.add(0,listener);
382         }
383
384         @Override
385         public void removeChangeListener(ChangeListener listener) {
386                 listenerList.remove(listener);
387         }
388         
389         protected void fireChangeEvent() {
390                 modCount++;
391                 ChangeListener[] listeners = listenerList.toArray(new ChangeListener[0]);
392                 for (ChangeListener l: listeners) {
393                         l.stateChanged(event);
394                 }
395         }
396 }