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