1 package net.sf.openrocket.aerodynamics;
3 import java.util.ArrayList;
6 import javax.swing.event.ChangeEvent;
7 import javax.swing.event.ChangeListener;
9 import net.sf.openrocket.rocketcomponent.Configuration;
10 import net.sf.openrocket.util.ChangeSource;
11 import net.sf.openrocket.util.MathUtil;
14 public class FlightConditions implements Cloneable, ChangeSource {
16 private List<ChangeListener> listenerList = new ArrayList<ChangeListener>();
17 private ChangeEvent event = new ChangeEvent(this);
19 /** Modification count */
20 private int modCount = 0;
22 /** Reference length used in calculations. */
23 private double refLength = 1.0;
25 /** Reference area used in calculations. */
26 private double refArea = Math.PI * 0.25;
29 /** Angle of attack. */
30 private double aoa = 0;
32 /** Sine of the angle of attack. */
33 private double sinAOA = 0;
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
40 private double sincAOA = 1.0;
42 /** Lateral wind direction. */
43 private double theta = 0;
45 /** Current Mach speed. */
46 private double mach = 0.3;
49 * Sqrt(1 - M^2) for M<1
50 * Sqrt(M^2 - 1) for M>1
52 private double beta = Math.sqrt(1 - mach*mach);
55 /** Current roll rate. */
56 private double rollRate = 0;
58 private double pitchRate = 0;
59 private double yawRate = 0;
62 private AtmosphericConditions atmosphericConditions = new AtmosphericConditions();
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
72 * @param config the configuration of which the reference length is taken.
74 public FlightConditions(Configuration config) {
76 setRefLength(config.getReferenceLength());
81 * Set the reference length from the given configuration.
82 * @param config the configuration from which to get the reference length.
84 public void setReference(Configuration config) {
85 setRefLength(config.getReferenceLength());
90 * Set the reference length and area.
92 public void setRefLength(double length) {
95 refArea = Math.PI * MathUtil.pow2(length/2);
100 * Return the reference length.
102 public double getRefLength() {
107 * Set the reference area and length.
109 public void setRefArea(double area) {
111 refLength = Math.sqrt(area / Math.PI)*2;
116 * Return the reference area.
118 public double getRefArea() {
124 * Sets the angle of attack. It calculates values also for the methods
125 * {@link #getSinAOA()} and {@link #getSincAOA()}.
127 * @param aoa the angle of attack.
129 public void setAOA(double aoa) {
130 aoa = MathUtil.clamp(aoa, 0, Math.PI);
131 if (MathUtil.equals(this.aoa, aoa))
139 this.sinAOA = Math.sin(aoa);
140 this.sincAOA = sinAOA / aoa;
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
152 * @param aoa the angle of attack in radians.
153 * @param sinAOA the sine of the angle of attack.
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))
161 assert(Math.abs(Math.sin(aoa) - sinAOA) < 0.0001) :
162 "Illegal sine: aoa="+aoa+" sinAOA="+sinAOA;
165 this.sinAOA = sinAOA;
169 this.sincAOA = sinAOA / aoa;
176 * Return the angle of attack.
178 public double getAOA() {
183 * Return the sine of the angle of attack.
185 public double getSinAOA() {
190 * Return the sinc of the angle of attack (sin(AOA) / AOA). This method returns
191 * one if the angle of attack is zero.
193 public double getSincAOA() {
199 * Set the direction of the lateral airflow.
201 public void setTheta(double theta) {
202 if (MathUtil.equals(this.theta, theta))
209 * Return the direction of the lateral airflow.
211 public double getTheta() {
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.
220 public void setMach(double mach) {
221 mach = Math.max(mach, 0);
222 if (MathUtil.equals(this.mach, mach))
227 this.beta = Math.sqrt(1 - mach*mach);
229 this.beta = Math.sqrt(mach*mach - 1);
234 * Return the current Mach speed.
236 public double getMach() {
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
245 * @return the velocity of the rocket.
247 public double getVelocity() {
248 return mach * atmosphericConditions.getMachSpeed();
252 * Sets the Mach speed according to the given velocity and the current speed of sound.
254 * @param velocity the current velocity.
256 public void setVelocity(double velocity) {
257 setMach(velocity / atmosphericConditions.getMachSpeed());
262 * Return sqrt(abs(1 - Mach^2)). This is calculated in the setting call and is
265 public double getBeta() {
271 * Return the current roll rate.
273 public double getRollRate() {
279 * Set the current roll rate.
281 public void setRollRate(double rate) {
282 if (MathUtil.equals(this.rollRate, rate))
285 this.rollRate = rate;
290 public double getPitchRate() {
295 public void setPitchRate(double pitchRate) {
296 if (MathUtil.equals(this.pitchRate, pitchRate))
298 this.pitchRate = pitchRate;
303 public double getYawRate() {
308 public void setYawRate(double yawRate) {
309 if (MathUtil.equals(this.yawRate, yawRate))
311 this.yawRate = yawRate;
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.
322 * @return the current atmospheric conditions.
324 public AtmosphericConditions getAtmosphericConditions() {
325 return atmosphericConditions;
329 * Set the current atmospheric conditions. This method will fire a change event
330 * if a change occurs.
332 public void setAtmosphericConditions(AtmosphericConditions cond) {
333 if (atmosphericConditions == cond)
335 atmosphericConditions = cond;
341 * Retrieve the modification count of this object. Each time it is modified
342 * the modification count is increased by one.
344 * @return the number of times this object has been modified since instantiation.
346 public int getModCount() {
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);
360 * Return a copy of the flight conditions. The copy has no listeners. The
361 * atmospheric conditions is also cloned.
364 public FlightConditions clone() {
366 FlightConditions cond = (FlightConditions) super.clone();
367 cond.listenerList = new ArrayList<ChangeListener>();
368 cond.event = new ChangeEvent(cond);
369 cond.atmosphericConditions = atmosphericConditions.clone();
371 } catch (CloneNotSupportedException e) {
372 throw new RuntimeException("BUG: clone not supported!",e);
379 public void addChangeListener(ChangeListener listener) {
380 listenerList.add(0,listener);
384 public void removeChangeListener(ChangeListener listener) {
385 listenerList.remove(listener);
388 protected void fireChangeEvent() {
390 ChangeListener[] listeners = listenerList.toArray(new ChangeListener[0]);
391 for (ChangeListener l: listeners) {
392 l.stateChanged(event);