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.BugException;
11 import net.sf.openrocket.util.ChangeSource;
12 import net.sf.openrocket.util.MathUtil;
15 public class FlightConditions implements Cloneable, ChangeSource {
17 private List<ChangeListener> listenerList = new ArrayList<ChangeListener>();
18 private ChangeEvent event = new ChangeEvent(this);
20 /** Modification count */
21 private int modCount = 0;
23 /** Reference length used in calculations. */
24 private double refLength = 1.0;
26 /** Reference area used in calculations. */
27 private double refArea = Math.PI * 0.25;
30 /** Angle of attack. */
31 private double aoa = 0;
33 /** Sine of the angle of attack. */
34 private double sinAOA = 0;
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
41 private double sincAOA = 1.0;
43 /** Lateral wind direction. */
44 private double theta = 0;
46 /** Current Mach speed. */
47 private double mach = 0.3;
50 * Sqrt(1 - M^2) for M<1
51 * Sqrt(M^2 - 1) for M>1
53 private double beta = Math.sqrt(1 - mach*mach);
56 /** Current roll rate. */
57 private double rollRate = 0;
59 private double pitchRate = 0;
60 private double yawRate = 0;
63 private AtmosphericConditions atmosphericConditions = new AtmosphericConditions();
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
73 * @param config the configuration of which the reference length is taken.
75 public FlightConditions(Configuration config) {
77 setRefLength(config.getReferenceLength());
82 * Set the reference length from the given configuration.
83 * @param config the configuration from which to get the reference length.
85 public void setReference(Configuration config) {
86 setRefLength(config.getReferenceLength());
91 * Set the reference length and area.
93 public void setRefLength(double length) {
96 refArea = Math.PI * MathUtil.pow2(length/2);
101 * Return the reference length.
103 public double getRefLength() {
108 * Set the reference area and length.
110 public void setRefArea(double area) {
112 refLength = Math.sqrt(area / Math.PI)*2;
117 * Return the reference area.
119 public double getRefArea() {
125 * Sets the angle of attack. It calculates values also for the methods
126 * {@link #getSinAOA()} and {@link #getSincAOA()}.
128 * @param aoa the angle of attack.
130 public void setAOA(double aoa) {
131 aoa = MathUtil.clamp(aoa, 0, Math.PI);
132 if (MathUtil.equals(this.aoa, aoa))
140 this.sinAOA = Math.sin(aoa);
141 this.sincAOA = sinAOA / aoa;
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
153 * @param aoa the angle of attack in radians.
154 * @param sinAOA the sine of the angle of attack.
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))
162 assert(Math.abs(Math.sin(aoa) - sinAOA) < 0.0001) :
163 "Illegal sine: aoa="+aoa+" sinAOA="+sinAOA;
166 this.sinAOA = sinAOA;
170 this.sincAOA = sinAOA / aoa;
177 * Return the angle of attack.
179 public double getAOA() {
184 * Return the sine of the angle of attack.
186 public double getSinAOA() {
191 * Return the sinc of the angle of attack (sin(AOA) / AOA). This method returns
192 * one if the angle of attack is zero.
194 public double getSincAOA() {
200 * Set the direction of the lateral airflow.
202 public void setTheta(double theta) {
203 if (MathUtil.equals(this.theta, theta))
210 * Return the direction of the lateral airflow.
212 public double getTheta() {
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.
221 public void setMach(double mach) {
222 mach = Math.max(mach, 0);
223 if (MathUtil.equals(this.mach, mach))
228 this.beta = Math.sqrt(1 - mach*mach);
230 this.beta = Math.sqrt(mach*mach - 1);
235 * Return the current Mach speed.
237 public double getMach() {
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
246 * @return the velocity of the rocket.
248 public double getVelocity() {
249 return mach * atmosphericConditions.getMachSpeed();
253 * Sets the Mach speed according to the given velocity and the current speed of sound.
255 * @param velocity the current velocity.
257 public void setVelocity(double velocity) {
258 setMach(velocity / atmosphericConditions.getMachSpeed());
263 * Return sqrt(abs(1 - Mach^2)). This is calculated in the setting call and is
266 public double getBeta() {
272 * Return the current roll rate.
274 public double getRollRate() {
280 * Set the current roll rate.
282 public void setRollRate(double rate) {
283 if (MathUtil.equals(this.rollRate, rate))
286 this.rollRate = rate;
291 public double getPitchRate() {
296 public void setPitchRate(double pitchRate) {
297 if (MathUtil.equals(this.pitchRate, pitchRate))
299 this.pitchRate = pitchRate;
304 public double getYawRate() {
309 public void setYawRate(double yawRate) {
310 if (MathUtil.equals(this.yawRate, yawRate))
312 this.yawRate = yawRate;
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.
323 * @return the current atmospheric conditions.
325 public AtmosphericConditions getAtmosphericConditions() {
326 return atmosphericConditions;
330 * Set the current atmospheric conditions. This method will fire a change event
331 * if a change occurs.
333 public void setAtmosphericConditions(AtmosphericConditions cond) {
334 if (atmosphericConditions == cond)
336 atmosphericConditions = cond;
342 * Retrieve the modification count of this object. Each time it is modified
343 * the modification count is increased by one.
345 * @return the number of times this object has been modified since instantiation.
347 public int getModCount() {
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);
361 * Return a copy of the flight conditions. The copy has no listeners. The
362 * atmospheric conditions is also cloned.
365 public FlightConditions clone() {
367 FlightConditions cond = (FlightConditions) super.clone();
368 cond.listenerList = new ArrayList<ChangeListener>();
369 cond.event = new ChangeEvent(cond);
370 cond.atmosphericConditions = atmosphericConditions.clone();
372 } catch (CloneNotSupportedException e) {
373 throw new BugException("BUG: clone not supported!",e);
380 public void addChangeListener(ChangeListener listener) {
381 listenerList.add(0,listener);
385 public void removeChangeListener(ChangeListener listener) {
386 listenerList.remove(listener);
389 protected void fireChangeEvent() {
391 ChangeListener[] listeners = listenerList.toArray(new ChangeListener[0]);
392 for (ChangeListener l: listeners) {
393 l.stateChanged(event);