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.models.atmosphere.AtmosphericConditions;
10 import net.sf.openrocket.rocketcomponent.Configuration;
11 import net.sf.openrocket.util.BugException;
12 import net.sf.openrocket.util.ChangeSource;
13 import net.sf.openrocket.util.Coordinate;
14 import net.sf.openrocket.util.MathUtil;
15 import net.sf.openrocket.util.Monitorable;
16 import net.sf.openrocket.util.UniqueID;
19 * A class defining the momentary flight conditions of a rocket, including
20 * the angle of attack, lateral wind angle, atmospheric conditions etc.
22 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
24 public class FlightConditions implements Cloneable, ChangeSource, Monitorable {
26 private List<ChangeListener> listenerList = new ArrayList<ChangeListener>();
27 private ChangeEvent event = new ChangeEvent(this);
30 /** Reference length used in calculations. */
31 private double refLength = 1.0;
33 /** Reference area used in calculations. */
34 private double refArea = Math.PI * 0.25;
37 /** Angle of attack. */
38 private double aoa = 0;
40 /** Sine of the angle of attack. */
41 private double sinAOA = 0;
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
48 private double sincAOA = 1.0;
50 /** Lateral wind direction. */
51 private double theta = 0;
53 /** Current Mach speed. */
54 private double mach = 0.3;
57 * Sqrt(1 - M^2) for M<1
58 * Sqrt(M^2 - 1) for M>1
60 private double beta = Math.sqrt(1 - mach * mach);
63 /** Current roll rate. */
64 private double rollRate = 0;
66 private double pitchRate = 0;
67 private double yawRate = 0;
69 private Coordinate pitchCenter = Coordinate.NUL;
72 private AtmosphericConditions atmosphericConditions = new AtmosphericConditions();
76 private int modIDadd = 0;
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
85 * @param config the configuration of which the reference length is taken.
87 public FlightConditions(Configuration config) {
89 setRefLength(config.getReferenceLength());
90 this.modID = UniqueID.next();
95 * Set the reference length from the given configuration.
96 * @param config the configuration from which to get the reference length.
98 public void setReference(Configuration config) {
99 setRefLength(config.getReferenceLength());
104 * Set the reference length and area.
106 public void setRefLength(double length) {
109 refArea = Math.PI * MathUtil.pow2(length / 2);
114 * Return the reference length.
116 public double getRefLength() {
121 * Set the reference area and length.
123 public void setRefArea(double area) {
125 refLength = Math.sqrt(area / Math.PI) * 2;
130 * Return the reference area.
132 public double getRefArea() {
138 * Sets the angle of attack. It calculates values also for the methods
139 * {@link #getSinAOA()} and {@link #getSincAOA()}.
141 * @param aoa the angle of attack.
143 public void setAOA(double aoa) {
144 aoa = MathUtil.clamp(aoa, 0, Math.PI);
145 if (MathUtil.equals(this.aoa, aoa))
153 this.sinAOA = Math.sin(aoa);
154 this.sincAOA = sinAOA / aoa;
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
166 * @param aoa the angle of attack in radians.
167 * @param sinAOA the sine of the angle of attack.
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))
175 assert (Math.abs(Math.sin(aoa) - sinAOA) < 0.0001) : "Illegal sine: aoa=" + aoa + " sinAOA=" + sinAOA;
178 this.sinAOA = sinAOA;
182 this.sincAOA = sinAOA / aoa;
189 * Return the angle of attack.
191 public double getAOA() {
196 * Return the sine of the angle of attack.
198 public double getSinAOA() {
203 * Return the sinc of the angle of attack (sin(AOA) / AOA). This method returns
204 * one if the angle of attack is zero.
206 public double getSincAOA() {
212 * Set the direction of the lateral airflow.
214 public void setTheta(double theta) {
215 if (MathUtil.equals(this.theta, theta))
222 * Return the direction of the lateral airflow.
224 public double getTheta() {
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.
233 public void setMach(double mach) {
234 mach = Math.max(mach, 0);
235 if (MathUtil.equals(this.mach, mach))
240 this.beta = Math.sqrt(1 - mach * mach);
242 this.beta = Math.sqrt(mach * mach - 1);
247 * Return the current Mach speed.
249 public double getMach() {
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
258 * @return the velocity of the rocket.
260 public double getVelocity() {
261 return mach * atmosphericConditions.getMachSpeed();
265 * Sets the Mach speed according to the given velocity and the current speed of sound.
267 * @param velocity the current velocity.
269 public void setVelocity(double velocity) {
270 setMach(velocity / atmosphericConditions.getMachSpeed());
275 * Return sqrt(abs(1 - Mach^2)). This is calculated in the setting call and is
278 public double getBeta() {
284 * Return the current roll rate.
286 public double getRollRate() {
292 * Set the current roll rate.
294 public void setRollRate(double rate) {
295 if (MathUtil.equals(this.rollRate, rate))
298 this.rollRate = rate;
303 public double getPitchRate() {
308 public void setPitchRate(double pitchRate) {
309 if (MathUtil.equals(this.pitchRate, pitchRate))
311 this.pitchRate = pitchRate;
316 public double getYawRate() {
321 public void setYawRate(double yawRate) {
322 if (MathUtil.equals(this.yawRate, yawRate))
324 this.yawRate = yawRate;
332 * @return the pitchCenter
334 public Coordinate getPitchCenter() {
340 * @param pitchCenter the pitchCenter to set
342 public void setPitchCenter(Coordinate pitchCenter) {
343 if (this.pitchCenter.equals(pitchCenter))
345 this.pitchCenter = pitchCenter;
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.
356 * @return the current atmospheric conditions.
358 public AtmosphericConditions getAtmosphericConditions() {
359 return atmosphericConditions;
363 * Set the current atmospheric conditions. This method will fire a change event
364 * if a change occurs.
366 public void setAtmosphericConditions(AtmosphericConditions cond) {
367 if (atmosphericConditions.equals(cond))
369 modIDadd += atmosphericConditions.getModID();
370 atmosphericConditions = cond;
376 * Retrieve the modification count of this object. Each time it is modified
377 * the modification count is increased by one.
379 * @return the number of times this object has been modified since instantiation.
381 public int getModID() {
382 return modID + modIDadd + this.atmosphericConditions.getModID();
387 public String toString() {
388 return String.format("FlightConditions[" +
390 "theta=%.2f\u00b0," +
396 "pitchCenter=" + pitchCenter.toString() + "," +
397 "atmosphericConditions=" + atmosphericConditions.toString() +
399 aoa * 180 / Math.PI, theta * 180 / Math.PI, mach, rollRate, pitchRate, yawRate, refLength);
404 * Return a copy of the flight conditions. The copy has no listeners. The
405 * atmospheric conditions is also cloned.
408 public FlightConditions clone() {
410 FlightConditions cond = (FlightConditions) super.clone();
411 cond.listenerList = new ArrayList<ChangeListener>();
412 cond.event = new ChangeEvent(cond);
413 cond.atmosphericConditions = atmosphericConditions.clone();
415 } catch (CloneNotSupportedException e) {
416 throw new BugException("clone not supported!", e);
422 public boolean equals(Object obj) {
425 if (!(obj instanceof FlightConditions))
428 FlightConditions other = (FlightConditions) obj;
430 return (MathUtil.equals(this.refLength, other.refLength) &&
431 MathUtil.equals(this.aoa, other.aoa) &&
432 MathUtil.equals(this.theta, other.theta) &&
433 MathUtil.equals(this.mach, other.mach) &&
434 MathUtil.equals(this.rollRate, other.rollRate) &&
435 MathUtil.equals(this.pitchRate, other.pitchRate) &&
436 MathUtil.equals(this.yawRate, other.yawRate) &&
437 this.pitchCenter.equals(other.pitchCenter) && this.atmosphericConditions.equals(other.atmosphericConditions));
441 public int hashCode() {
442 return (int) (1000 * (refLength + aoa + theta + mach + rollRate + pitchRate + yawRate));
447 public void addChangeListener(ChangeListener listener) {
448 listenerList.add(0, listener);
452 public void removeChangeListener(ChangeListener listener) {
453 listenerList.remove(listener);
456 protected void fireChangeEvent() {
457 modID = UniqueID.next();
458 ChangeListener[] listeners = listenerList.toArray(new ChangeListener[0]);
459 for (ChangeListener l : listeners) {
460 l.stateChanged(event);