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