geodetic computations
[debian/openrocket] / src / net / sf / openrocket / simulation / RK4SimulationStepper.java
1 package net.sf.openrocket.simulation;
2
3 import java.util.Arrays;
4 import java.util.Random;
5
6 import net.sf.openrocket.aerodynamics.AerodynamicForces;
7 import net.sf.openrocket.aerodynamics.FlightConditions;
8 import net.sf.openrocket.aerodynamics.WarningSet;
9 import net.sf.openrocket.logging.LogHelper;
10 import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
11 import net.sf.openrocket.simulation.exception.SimulationCalculationException;
12 import net.sf.openrocket.simulation.exception.SimulationException;
13 import net.sf.openrocket.simulation.listeners.SimulationListenerHelper;
14 import net.sf.openrocket.startup.Application;
15 import net.sf.openrocket.util.Coordinate;
16 import net.sf.openrocket.util.GeodeticComputationStrategy;
17 import net.sf.openrocket.util.MathUtil;
18 import net.sf.openrocket.util.Quaternion;
19 import net.sf.openrocket.util.Rotation2D;
20 import net.sf.openrocket.util.WorldCoordinate;
21
22 public class RK4SimulationStepper extends AbstractSimulationStepper {
23         
24         private static final LogHelper log = Application.getLogger();
25         
26         /** Random value with which to XOR the random seed value */
27         private static final int SEED_RANDOMIZATION = 0x23E3A01F;
28         
29
30         /**
31          * A recommended reasonably accurate time step.
32          */
33         public static final double RECOMMENDED_TIME_STEP = 0.05;
34         
35         /**
36          * A recommended maximum angle step value.
37          */
38         public static final double RECOMMENDED_ANGLE_STEP = 3 * Math.PI / 180;
39         
40         /**
41          * A random amount that is added to pitch and yaw coefficients, plus or minus.
42          */
43         public static final double PITCH_YAW_RANDOM = 0.0005;
44         
45         /**
46          * Maximum roll step allowed.  This is selected as an uneven division of the full
47          * circle so that the simulation will sample the most wind directions
48          */
49         private static final double MAX_ROLL_STEP_ANGLE = 2 * 28.32 * Math.PI / 180;
50         //      private static final double MAX_ROLL_STEP_ANGLE = 8.32 * Math.PI/180;
51         
52         private static final double MAX_ROLL_RATE_CHANGE = 2 * Math.PI / 180;
53         private static final double MAX_PITCH_CHANGE = 4 * Math.PI / 180;
54         
55         private static final double MIN_TIME_STEP = 0.001;
56         
57
58         private Random random;
59         
60         
61
62
63         @Override
64         public RK4SimulationStatus initialize(SimulationStatus original) {
65                 
66                 RK4SimulationStatus status = new RK4SimulationStatus();
67                 
68                 status.copyFrom(original);
69                 
70                 SimulationConditions sim = original.getSimulationConditions();
71                 
72                 status.setLaunchRodDirection(new Coordinate(
73                                 Math.sin(sim.getLaunchRodAngle()) * Math.cos(sim.getLaunchRodDirection()),
74                                 Math.sin(sim.getLaunchRodAngle()) * Math.sin(sim.getLaunchRodDirection()),
75                                 Math.cos(sim.getLaunchRodAngle())
76                                 ));
77                 
78                 this.random = new Random(original.getSimulationConditions().getRandomSeed() ^ SEED_RANDOMIZATION);
79                 
80                 return status;
81         }
82         
83         
84
85
86         @Override
87         public void step(SimulationStatus simulationStatus, double maxTimeStep) throws SimulationException {
88                 
89                 RK4SimulationStatus status = (RK4SimulationStatus) simulationStatus;
90                 DataStore store = new DataStore();
91                 
92                 ////////  Perform RK4 integration:  ////////
93                 
94                 RK4SimulationStatus status2;
95                 RK4Parameters k1, k2, k3, k4;
96                 
97                 /*
98                  * Start with previous time step which is used to compute the initial thrust estimate.
99                  * Don't make it longer than maxTimeStep, but at least MIN_TIME_STEP.
100                  */
101                 store.timestep = status.getPreviousTimeStep();
102                 store.timestep = MathUtil.max(MathUtil.min(store.timestep, maxTimeStep), MIN_TIME_STEP);
103                 checkNaN(store.timestep);
104                 
105                 /*
106                  * Compute the initial thrust estimate.  This is used for the first time step computation.
107                  */
108                 store.thrustForce = calculateThrust(status, store.timestep, status.getPreviousAcceleration(),
109                                 status.getPreviousAtmosphericConditions(), false);
110                 
111
112                 /*
113                  * Perform RK4 integration.  Decide the time step length after the first step.
114                  */
115
116                 //// First position, k1 = f(t, y)
117                 
118                 k1 = computeParameters(status, store);
119                 
120                 /*
121                  * Select the actual time step to use.  It is the minimum of the following:
122                  *  dt[0]:  the user-specified time step (or 1/5th of it if still on the launch rod)
123                  *  dt[1]:  the value of maxTimeStep
124                  *  dt[2]:  the maximum pitch step angle limit
125                  *  dt[3]:  the maximum roll step angle limit
126                  *  dt[4]:  the maximum roll rate change limit
127                  *  dt[5]:  the maximum pitch change limit
128                  *  dt[6]:  1/10th of the launch rod length if still on the launch rod
129                  *  dt[7]:  1.50 times the previous time step
130                  * 
131                  * The limits #5 and #6 are required since near the steady-state roll rate the roll rate
132                  * may oscillate significantly even between the sub-steps of the RK4 integration.
133                  * 
134                  * The step is still at least 1/20th of the user-selected time step.
135                  */
136                 double[] dt = new double[8];
137                 Arrays.fill(dt, Double.MAX_VALUE);
138                 
139                 dt[0] = status.getSimulationConditions().getTimeStep();
140                 dt[1] = maxTimeStep;
141                 dt[2] = status.getSimulationConditions().getMaximumAngleStep() / store.lateralPitchRate;
142                 dt[3] = Math.abs(MAX_ROLL_STEP_ANGLE / store.flightConditions.getRollRate());
143                 dt[4] = Math.abs(MAX_ROLL_RATE_CHANGE / store.rollAcceleration);
144                 dt[5] = Math.abs(MAX_PITCH_CHANGE / store.lateralPitchAcceleration);
145                 if (!status.isLaunchRodCleared()) {
146                         dt[0] /= 5.0;
147                         dt[6] = status.getSimulationConditions().getLaunchRodLength() / k1.v.length() / 10;
148                 }
149                 dt[7] = 1.5 * status.getPreviousTimeStep();
150                 
151                 store.timestep = Double.MAX_VALUE;
152                 int limitingValue = -1;
153                 for (int i = 0; i < dt.length; i++) {
154                         if (dt[i] < store.timestep) {
155                                 store.timestep = dt[i];
156                                 limitingValue = i;
157                         }
158                 }
159                 
160                 double minTimeStep = status.getSimulationConditions().getTimeStep() / 20;
161                 if (store.timestep < minTimeStep) {
162                         log.verbose("Too small time step " + store.timestep + " (limiting factor " + limitingValue + "), using " +
163                                         minTimeStep + " instead.");
164                         store.timestep = minTimeStep;
165                 } else {
166                         log.verbose("Selected time step " + store.timestep + " (limiting factor " + limitingValue + ")");
167                 }
168                 checkNaN(store.timestep);
169                 
170                 /*
171                  * Compute the correct thrust for this time step.  If the original thrust estimate differs more
172                  * than 10% from the true value then recompute the RK4 step 1.  The 10% error in step 1 is
173                  * diminished by it affecting only 1/6th of the total, so it's an acceptable error.
174                  */
175                 double thrustEstimate = store.thrustForce;
176                 store.thrustForce = calculateThrust(status, store.timestep, store.longitudinalAcceleration,
177                                 store.atmosphericConditions, true);
178                 double thrustDiff = Math.abs(store.thrustForce - thrustEstimate);
179                 // Log if difference over 1%, recompute if over 10%
180                 if (thrustDiff > 0.01 * thrustEstimate) {
181                         if (thrustDiff > 0.1 * thrustEstimate + 0.001) {
182                                 log.debug("Thrust estimate differs from correct value by " +
183                                                 (Math.rint(1000 * (thrustDiff + 0.000001) / thrustEstimate) / 10.0) + "%," +
184                                                 " estimate=" + thrustEstimate +
185                                                 " correct=" + store.thrustForce +
186                                                 " timestep=" + store.timestep +
187                                                 ", recomputing k1 parameters");
188                                 k1 = computeParameters(status, store);
189                         } else {
190                                 log.verbose("Thrust estimate differs from correct value by " +
191                                                 (Math.rint(1000 * (thrustDiff + 0.000001) / thrustEstimate) / 10.0) + "%," +
192                                                 " estimate=" + thrustEstimate +
193                                                 " correct=" + store.thrustForce +
194                                                 " timestep=" + store.timestep +
195                                                 ", error acceptable");
196                         }
197                 }
198                 
199                 // Store data
200                 // TODO: MEDIUM: Store acceleration etc of entire RK4 step, store should be cloned or something...
201                 storeData(status, store);
202                 
203
204                 //// Second position, k2 = f(t + h/2, y + k1*h/2)
205                 
206                 status2 = status.clone();
207                 status2.setSimulationTime(status.getSimulationTime() + store.timestep / 2);
208                 status2.setRocketPosition(status.getRocketPosition().add(k1.v.multiply(store.timestep / 2)));
209                 status2.setRocketVelocity(status.getRocketVelocity().add(k1.a.multiply(store.timestep / 2)));
210                 status2.setRocketOrientationQuaternion(status.getRocketOrientationQuaternion().multiplyLeft(Quaternion.rotation(k1.rv.multiply(store.timestep / 2))));
211                 status2.setRocketRotationVelocity(status.getRocketRotationVelocity().add(k1.ra.multiply(store.timestep / 2)));
212                 
213                 k2 = computeParameters(status2, store);
214                 
215
216                 //// Third position, k3 = f(t + h/2, y + k2*h/2)
217                 
218                 status2 = status.clone();
219                 status2.setSimulationTime(status.getSimulationTime() + store.timestep / 2);
220                 status2.setRocketPosition(status.getRocketPosition().add(k2.v.multiply(store.timestep / 2)));
221                 status2.setRocketVelocity(status.getRocketVelocity().add(k2.a.multiply(store.timestep / 2)));
222                 status2.setRocketOrientationQuaternion(status2.getRocketOrientationQuaternion().multiplyLeft(Quaternion.rotation(k2.rv.multiply(store.timestep / 2))));
223                 status2.setRocketRotationVelocity(status.getRocketRotationVelocity().add(k2.ra.multiply(store.timestep / 2)));
224                 
225                 k3 = computeParameters(status2, store);
226                 
227
228                 //// Fourth position, k4 = f(t + h, y + k3*h)
229                 
230                 status2 = status.clone();
231                 status2.setSimulationTime(status.getSimulationTime() + store.timestep);
232                 status2.setRocketPosition(status.getRocketPosition().add(k3.v.multiply(store.timestep)));
233                 status2.setRocketVelocity(status.getRocketVelocity().add(k3.a.multiply(store.timestep)));
234                 status2.setRocketOrientationQuaternion(status2.getRocketOrientationQuaternion().multiplyLeft(Quaternion.rotation(k3.rv.multiply(store.timestep))));
235                 status2.setRocketRotationVelocity(status.getRocketRotationVelocity().add(k3.ra.multiply(store.timestep)));
236                 
237                 k4 = computeParameters(status2, store);
238                 
239
240                 //// Sum all together,  y(n+1) = y(n) + h*(k1 + 2*k2 + 2*k3 + k4)/6
241                 
242
243
244                 Coordinate deltaV, deltaP, deltaR, deltaO;
245                 deltaV = k2.a.add(k3.a).multiply(2).add(k1.a).add(k4.a).multiply(store.timestep / 6);
246                 deltaP = k2.v.add(k3.v).multiply(2).add(k1.v).add(k4.v).multiply(store.timestep / 6);
247                 deltaR = k2.ra.add(k3.ra).multiply(2).add(k1.ra).add(k4.ra).multiply(store.timestep / 6);
248                 deltaO = k2.rv.add(k3.rv).multiply(2).add(k1.rv).add(k4.rv).multiply(store.timestep / 6);
249                 
250
251
252                 status.setRocketVelocity(status.getRocketVelocity().add(deltaV));
253                 status.setRocketPosition(status.getRocketPosition().add(deltaP));
254                 status.setRocketRotationVelocity(status.getRocketRotationVelocity().add(deltaR));
255                 status.setRocketOrientationQuaternion(status.getRocketOrientationQuaternion().multiplyLeft(Quaternion.rotation(deltaO)).normalizeIfNecessary());
256                 
257                 WorldCoordinate w = status.getSimulationConditions().getLaunchSite();
258                 w = status.getSimulationConditions().getGeodeticComputation().addCoordinate(w, status.getRocketPosition());
259                 status.setRocketWorldPosition(w);
260                 
261                 status.setSimulationTime(status.getSimulationTime() + store.timestep);
262                 
263                 status.setPreviousTimeStep(store.timestep);
264                 
265                 // Verify that values don't run out of range
266                 if (status.getRocketVelocity().length2() > 1e18 ||
267                                 status.getRocketPosition().length2() > 1e18 ||
268                                 status.getRocketRotationVelocity().length2() > 1e18) {
269                         throw new SimulationCalculationException("Simulation values exceeded limits");
270                 }
271         }
272         
273         
274
275
276
277         private RK4Parameters computeParameters(RK4SimulationStatus status, DataStore dataStore)
278                         throws SimulationException {
279                 RK4Parameters params = new RK4Parameters();
280                 
281                 //              if (dataStore == null) {
282                 //                      dataStore = new DataStore();
283                 //              }
284                 
285                 calculateAcceleration(status, dataStore);
286                 params.a = dataStore.linearAcceleration;
287                 params.ra = dataStore.angularAcceleration;
288                 params.v = status.getRocketVelocity();
289                 params.rv = status.getRocketRotationVelocity();
290                 
291                 checkNaN(params.a);
292                 checkNaN(params.ra);
293                 checkNaN(params.v);
294                 checkNaN(params.rv);
295                 
296                 return params;
297         }
298         
299         
300
301
302
303         /**
304          * Calculate the linear and angular acceleration at the given status.  The results
305          * are stored in the fields {@link #linearAcceleration} and {@link #angularAcceleration}.
306          *  
307          * @param status   the status of the rocket.
308          * @throws SimulationException 
309          */
310         private void calculateAcceleration(RK4SimulationStatus status, DataStore store) throws SimulationException {
311                 
312                 // Call pre-listeners
313                 store.accelerationData = SimulationListenerHelper.firePreAccelerationCalculation(status);
314                 if (store.accelerationData != null) {
315                         return;
316                 }
317                 
318                 // Compute the forces affecting the rocket
319                 calculateForces(status, store);
320                 
321                 // Calculate mass data
322                 store.massData = calculateMassData(status);
323                 
324
325                 // Calculate the forces from the aerodynamic coefficients
326                 
327                 double dynP = (0.5 * store.flightConditions.getAtmosphericConditions().getDensity() *
328                                         MathUtil.pow2(store.flightConditions.getVelocity()));
329                 double refArea = store.flightConditions.getRefArea();
330                 double refLength = store.flightConditions.getRefLength();
331                 
332
333                 // Linear forces in rocket coordinates
334                 store.dragForce = store.forces.getCaxial() * dynP * refArea;
335                 double fN = store.forces.getCN() * dynP * refArea;
336                 double fSide = store.forces.getCside() * dynP * refArea;
337                 
338                 double forceZ = store.thrustForce - store.dragForce;
339                 
340                 store.linearAcceleration = new Coordinate(-fN / store.massData.getCG().weight,
341                                         -fSide / store.massData.getCG().weight,
342                                         forceZ / store.massData.getCG().weight);
343                 
344                 store.linearAcceleration = store.thetaRotation.rotateZ(store.linearAcceleration);
345                 
346                 // Convert into rocket world coordinates
347                 store.linearAcceleration = status.getRocketOrientationQuaternion().rotate(store.linearAcceleration);
348                 
349                 // add effect of gravity
350                 store.gravity = modelGravity(status);
351                 store.linearAcceleration = store.linearAcceleration.sub(0, 0, store.gravity);
352                 
353                 // add effect of Coriolis acceleration
354                 store.coriolisAcceleration = status.getSimulationConditions().getGeodeticComputation()
355                                 .getCoriolisAcceleration(status.getRocketWorldPosition(), status.getRocketVelocity());
356                 store.linearAcceleration = store.linearAcceleration.add(store.coriolisAcceleration);
357                 
358                 // If still on the launch rod, project acceleration onto launch rod direction and
359                 // set angular acceleration to zero.
360                 if (!status.isLaunchRodCleared()) {
361                         
362                         store.linearAcceleration = status.getLaunchRodDirection().multiply(
363                                                 store.linearAcceleration.dot(status.getLaunchRodDirection()));
364                         store.angularAcceleration = Coordinate.NUL;
365                         store.rollAcceleration = 0;
366                         store.lateralPitchAcceleration = 0;
367                         
368                 } else {
369                         
370                         // Shift moments to CG
371                         double Cm = store.forces.getCm() - store.forces.getCN() * store.massData.getCG().x / refLength;
372                         double Cyaw = store.forces.getCyaw() - store.forces.getCside() * store.massData.getCG().x / refLength;
373                         
374                         // Compute moments
375                         double momX = -Cyaw * dynP * refArea * refLength;
376                         double momY = Cm * dynP * refArea * refLength;
377                         double momZ = store.forces.getCroll() * dynP * refArea * refLength;
378                         
379                         // Compute acceleration in rocket coordinates
380                         store.angularAcceleration = new Coordinate(momX / store.massData.getLongitudinalInertia(),
381                                                 momY / store.massData.getLongitudinalInertia(),
382                                                 momZ / store.massData.getRotationalInertia());
383                         
384                         store.rollAcceleration = store.angularAcceleration.z;
385                         // TODO: LOW: This should be hypot, but does it matter?
386                         store.lateralPitchAcceleration = MathUtil.max(Math.abs(store.angularAcceleration.x),
387                                                 Math.abs(store.angularAcceleration.y));
388                         
389                         store.angularAcceleration = store.thetaRotation.rotateZ(store.angularAcceleration);
390                         
391                         // Convert to world coordinates
392                         store.angularAcceleration = status.getRocketOrientationQuaternion().rotate(store.angularAcceleration);
393                         
394                 }
395                 
396                 // Call post-listeners
397                 store.accelerationData = SimulationListenerHelper.firePostAccelerationCalculation(status, store.accelerationData);
398         }
399         
400         
401         /**
402          * Calculate the aerodynamic forces into the data store.  This method also handles
403          * whether to include aerodynamic computation warnings or not.
404          */
405         private void calculateForces(RK4SimulationStatus status, DataStore store) throws SimulationException {
406                 
407                 // Call pre-listeners
408                 store.forces = SimulationListenerHelper.firePreAerodynamicCalculation(status);
409                 if (store.forces != null) {
410                         return;
411                 }
412                 
413                 // Compute flight conditions
414                 calculateFlightConditions(status, store);
415                 
416                 /*
417                  * Check whether to store warnings or not.  Warnings are ignored when on the 
418                  * launch rod or 0.25 seconds after departure, and when the velocity has dropped
419                  * below 20% of the max. velocity.
420                  */
421                 WarningSet warnings = status.getWarnings();
422                 status.setMaxZVelocity(MathUtil.max(status.getMaxZVelocity(), status.getRocketVelocity().z));
423                 
424                 if (!status.isLaunchRodCleared()) {
425                         warnings = null;
426                 } else {
427                         if (status.getRocketVelocity().z < 0.2 * status.getMaxZVelocity())
428                                 warnings = null;
429                         if (status.getStartWarningTime() < 0)
430                                 status.setStartWarningTime(status.getSimulationTime() + 0.25);
431                 }
432                 if (status.getSimulationTime() < status.getStartWarningTime())
433                         warnings = null;
434                 
435
436                 // Calculate aerodynamic forces
437                 store.forces = status.getSimulationConditions().getAerodynamicCalculator()
438                                 .getAerodynamicForces(status.getConfiguration(), store.flightConditions, warnings);
439                 
440
441                 // Add very small randomization to yaw & pitch moments to prevent over-perfect flight
442                 // TODO: HIGH: This should rather be performed as a listener
443                 store.forces.setCm(store.forces.getCm() + (PITCH_YAW_RANDOM * 2 * (random.nextDouble() - 0.5)));
444                 store.forces.setCyaw(store.forces.getCyaw() + (PITCH_YAW_RANDOM * 2 * (random.nextDouble() - 0.5)));
445                 
446
447                 // Call post-listeners
448                 store.forces = SimulationListenerHelper.firePostAerodynamicCalculation(status, store.forces);
449         }
450         
451         
452
453         /**
454          * Calculate and return the flight conditions for the current rocket status.
455          * Listeners can override these if necessary.
456          * <p>
457          * Additionally the fields thetaRotation and lateralPitchRate are defined in
458          * the data store, and can be used after calling this method.
459          */
460         private void calculateFlightConditions(RK4SimulationStatus status, DataStore store)
461                         throws SimulationException {
462                 
463                 // Call pre listeners, allow complete override
464                 store.flightConditions = SimulationListenerHelper.firePreFlightConditions(
465                                 status);
466                 if (store.flightConditions != null) {
467                         // Compute the store values
468                         store.thetaRotation = new Rotation2D(store.flightConditions.getTheta());
469                         store.lateralPitchRate = Math.hypot(store.flightConditions.getPitchRate(), store.flightConditions.getYawRate());
470                         return;
471                 }
472                 
473
474
475                 //// Atmospheric conditions
476                 AtmosphericConditions atmosphere = modelAtmosphericConditions(status);
477                 store.flightConditions = new FlightConditions(status.getConfiguration());
478                 store.flightConditions.setAtmosphericConditions(atmosphere);
479                 
480
481                 //// Local wind speed and direction
482                 Coordinate windSpeed = modelWindVelocity(status);
483                 Coordinate airSpeed = status.getRocketVelocity().add(windSpeed);
484                 airSpeed = status.getRocketOrientationQuaternion().invRotate(airSpeed);
485                 
486
487                 // Lateral direction:
488                 double len = MathUtil.hypot(airSpeed.x, airSpeed.y);
489                 if (len > 0.0001) {
490                         store.thetaRotation = new Rotation2D(airSpeed.y / len, airSpeed.x / len);
491                         store.flightConditions.setTheta(Math.atan2(airSpeed.y, airSpeed.x));
492                 } else {
493                         store.thetaRotation = Rotation2D.ID;
494                         store.flightConditions.setTheta(0);
495                 }
496                 
497                 double velocity = airSpeed.length();
498                 store.flightConditions.setVelocity(velocity);
499                 if (velocity > 0.01) {
500                         // aoa must be calculated from the monotonous cosine
501                         // sine can be calculated by a simple division
502                         store.flightConditions.setAOA(Math.acos(airSpeed.z / velocity), len / velocity);
503                 } else {
504                         store.flightConditions.setAOA(0);
505                 }
506                 
507
508                 // Roll, pitch and yaw rate
509                 Coordinate rot = status.getRocketOrientationQuaternion().invRotate(status.getRocketRotationVelocity());
510                 rot = store.thetaRotation.invRotateZ(rot);
511                 
512                 store.flightConditions.setRollRate(rot.z);
513                 if (len < 0.001) {
514                         store.flightConditions.setPitchRate(0);
515                         store.flightConditions.setYawRate(0);
516                         store.lateralPitchRate = 0;
517                 } else {
518                         store.flightConditions.setPitchRate(rot.y);
519                         store.flightConditions.setYawRate(rot.x);
520                         // TODO: LOW: set this as power of two?
521                         store.lateralPitchRate = MathUtil.hypot(rot.x, rot.y);
522                 }
523                 
524
525                 // Call post listeners
526                 FlightConditions c = SimulationListenerHelper.firePostFlightConditions(
527                                 status, store.flightConditions);
528                 if (c != store.flightConditions) {
529                         // Listeners changed the values, recalculate data store
530                         store.flightConditions = c;
531                         store.thetaRotation = new Rotation2D(store.flightConditions.getTheta());
532                         store.lateralPitchRate = Math.hypot(store.flightConditions.getPitchRate(), store.flightConditions.getYawRate());
533                 }
534                 
535         }
536         
537         
538
539         private void storeData(RK4SimulationStatus status, DataStore store) {
540                 
541                 FlightDataBranch data = status.getFlightData();
542                 boolean extra = status.getSimulationConditions().isCalculateExtras();
543                 
544                 data.addPoint();
545                 data.setValue(FlightDataType.TYPE_TIME, status.getSimulationTime());
546                 data.setValue(FlightDataType.TYPE_ALTITUDE, status.getRocketPosition().z);
547                 data.setValue(FlightDataType.TYPE_POSITION_X, status.getRocketPosition().x);
548                 data.setValue(FlightDataType.TYPE_POSITION_Y, status.getRocketPosition().y);
549                 
550                 if (status.getSimulationConditions().getGeodeticComputation() != GeodeticComputationStrategy.NONE) {
551                         data.setValue(FlightDataType.TYPE_LATITUDE, status.getRocketWorldPosition().getLatitudeRad());
552                         data.setValue(FlightDataType.TYPE_LONGITUDE, status.getRocketWorldPosition().getLongitudeRad());
553                         data.setValue(FlightDataType.TYPE_CORIOLIS_ACCELERATION, store.coriolisAcceleration.length());
554                 }
555                 
556                 if (extra) {
557                         data.setValue(FlightDataType.TYPE_POSITION_XY,
558                                         MathUtil.hypot(status.getRocketPosition().x, status.getRocketPosition().y));
559                         data.setValue(FlightDataType.TYPE_POSITION_DIRECTION,
560                                         Math.atan2(status.getRocketPosition().y, status.getRocketPosition().x));
561                         
562                         data.setValue(FlightDataType.TYPE_VELOCITY_XY,
563                                         MathUtil.hypot(status.getRocketVelocity().x, status.getRocketVelocity().y));
564                         
565                         if (store.linearAcceleration != null) {
566                                 data.setValue(FlightDataType.TYPE_ACCELERATION_XY,
567                                                 MathUtil.hypot(store.linearAcceleration.x, store.linearAcceleration.y));
568                                 
569                                 data.setValue(FlightDataType.TYPE_ACCELERATION_TOTAL, store.linearAcceleration.length());
570                         }
571                         
572                         if (store.flightConditions != null) {
573                                 double Re = (store.flightConditions.getVelocity() *
574                                                 status.getConfiguration().getLength() /
575                                                 store.flightConditions.getAtmosphericConditions().getKinematicViscosity());
576                                 data.setValue(FlightDataType.TYPE_REYNOLDS_NUMBER, Re);
577                         }
578                 }
579                 
580                 data.setValue(FlightDataType.TYPE_VELOCITY_Z, status.getRocketVelocity().z);
581                 if (store.linearAcceleration != null) {
582                         data.setValue(FlightDataType.TYPE_ACCELERATION_Z, store.linearAcceleration.z);
583                 }
584                 
585                 if (store.flightConditions != null) {
586                         data.setValue(FlightDataType.TYPE_VELOCITY_TOTAL, status.getRocketVelocity().length());
587                         data.setValue(FlightDataType.TYPE_MACH_NUMBER, store.flightConditions.getMach());
588                 }
589                 
590                 if (store.massData != null) {
591                         data.setValue(FlightDataType.TYPE_CG_LOCATION, store.massData.getCG().x);
592                 }
593                 if (status.isLaunchRodCleared()) {
594                         // Don't include CP and stability with huge launch AOA
595                         if (store.forces != null) {
596                                 data.setValue(FlightDataType.TYPE_CP_LOCATION, store.forces.getCP().x);
597                         }
598                         if (store.forces != null && store.flightConditions != null && store.massData != null) {
599                                 data.setValue(FlightDataType.TYPE_STABILITY,
600                                                 (store.forces.getCP().x - store.massData.getCG().x) / store.flightConditions.getRefLength());
601                         }
602                 }
603                 if (store.massData != null) {
604                         data.setValue(FlightDataType.TYPE_MASS, store.massData.getCG().weight);
605                         data.setValue(FlightDataType.TYPE_LONGITUDINAL_INERTIA, store.massData.getLongitudinalInertia());
606                         data.setValue(FlightDataType.TYPE_ROTATIONAL_INERTIA, store.massData.getRotationalInertia());
607                 }
608                 
609                 data.setValue(FlightDataType.TYPE_THRUST_FORCE, store.thrustForce);
610                 data.setValue(FlightDataType.TYPE_DRAG_FORCE, store.dragForce);
611                 
612                 if (status.isLaunchRodCleared() && store.forces != null) {
613                         if (store.massData != null && store.flightConditions != null) {
614                                 data.setValue(FlightDataType.TYPE_PITCH_MOMENT_COEFF,
615                                                 store.forces.getCm() - store.forces.getCN() * store.massData.getCG().x / store.flightConditions.getRefLength());
616                                 data.setValue(FlightDataType.TYPE_YAW_MOMENT_COEFF,
617                                                 store.forces.getCyaw() - store.forces.getCside() * store.massData.getCG().x / store.flightConditions.getRefLength());
618                         }
619                         data.setValue(FlightDataType.TYPE_NORMAL_FORCE_COEFF, store.forces.getCN());
620                         data.setValue(FlightDataType.TYPE_SIDE_FORCE_COEFF, store.forces.getCside());
621                         data.setValue(FlightDataType.TYPE_ROLL_MOMENT_COEFF, store.forces.getCroll());
622                         data.setValue(FlightDataType.TYPE_ROLL_FORCING_COEFF, store.forces.getCrollForce());
623                         data.setValue(FlightDataType.TYPE_ROLL_DAMPING_COEFF, store.forces.getCrollDamp());
624                         data.setValue(FlightDataType.TYPE_PITCH_DAMPING_MOMENT_COEFF,
625                                         store.forces.getPitchDampingMoment());
626                 }
627                 
628                 if (store.forces != null) {
629                         data.setValue(FlightDataType.TYPE_DRAG_COEFF, store.forces.getCD());
630                         data.setValue(FlightDataType.TYPE_AXIAL_DRAG_COEFF, store.forces.getCaxial());
631                         data.setValue(FlightDataType.TYPE_FRICTION_DRAG_COEFF, store.forces.getFrictionCD());
632                         data.setValue(FlightDataType.TYPE_PRESSURE_DRAG_COEFF, store.forces.getPressureCD());
633                         data.setValue(FlightDataType.TYPE_BASE_DRAG_COEFF, store.forces.getBaseCD());
634                 }
635                 
636                 if (store.flightConditions != null) {
637                         data.setValue(FlightDataType.TYPE_REFERENCE_LENGTH, store.flightConditions.getRefLength());
638                         data.setValue(FlightDataType.TYPE_REFERENCE_AREA, store.flightConditions.getRefArea());
639                         
640                         data.setValue(FlightDataType.TYPE_PITCH_RATE, store.flightConditions.getPitchRate());
641                         data.setValue(FlightDataType.TYPE_YAW_RATE, store.flightConditions.getYawRate());
642                         data.setValue(FlightDataType.TYPE_ROLL_RATE, store.flightConditions.getRollRate());
643                         
644                         data.setValue(FlightDataType.TYPE_AOA, store.flightConditions.getAOA());
645                 }
646                 
647
648                 if (extra) {
649                         Coordinate c = status.getRocketOrientationQuaternion().rotateZ();
650                         double theta = Math.atan2(c.z, MathUtil.hypot(c.x, c.y));
651                         double phi = Math.atan2(c.y, c.x);
652                         if (phi < -(Math.PI - 0.0001))
653                                 phi = Math.PI;
654                         data.setValue(FlightDataType.TYPE_ORIENTATION_THETA, theta);
655                         data.setValue(FlightDataType.TYPE_ORIENTATION_PHI, phi);
656                 }
657                 
658                 data.setValue(FlightDataType.TYPE_WIND_VELOCITY, store.windSpeed);
659                 
660                 if (store.flightConditions != null) {
661                         data.setValue(FlightDataType.TYPE_AIR_TEMPERATURE,
662                                         store.flightConditions.getAtmosphericConditions().getTemperature());
663                         data.setValue(FlightDataType.TYPE_AIR_PRESSURE,
664                                         store.flightConditions.getAtmosphericConditions().getPressure());
665                         data.setValue(FlightDataType.TYPE_SPEED_OF_SOUND,
666                                         store.flightConditions.getAtmosphericConditions().getMachSpeed());
667                 }
668                 
669
670                 data.setValue(FlightDataType.TYPE_TIME_STEP, store.timestep);
671                 data.setValue(FlightDataType.TYPE_COMPUTATION_TIME,
672                                 (System.nanoTime() - status.getSimulationStartWallTime()) / 1000000000.0);
673         }
674         
675         
676
677
678         private static class RK4Parameters {
679                 /** Linear acceleration */
680                 public Coordinate a;
681                 /** Linear velocity */
682                 public Coordinate v;
683                 /** Rotational acceleration */
684                 public Coordinate ra;
685                 /** Rotational velocity */
686                 public Coordinate rv;
687         }
688         
689         private static class DataStore {
690                 public double timestep = Double.NaN;
691                 
692                 public AccelerationData accelerationData;
693                 
694                 public AtmosphericConditions atmosphericConditions;
695                 
696                 public FlightConditions flightConditions;
697                 
698                 public double longitudinalAcceleration = Double.NaN;
699                 
700                 public MassData massData;
701                 
702                 public Coordinate coriolisAcceleration;
703                 
704                 public Coordinate linearAcceleration;
705                 public Coordinate angularAcceleration;
706                 
707                 // set by calculateFlightConditions and calculateAcceleration:
708                 public AerodynamicForces forces;
709                 public double windSpeed = Double.NaN;
710                 public double gravity = Double.NaN;
711                 public double thrustForce = Double.NaN;
712                 public double dragForce = Double.NaN;
713                 public double lateralPitchRate = Double.NaN;
714                 
715                 public double rollAcceleration = Double.NaN;
716                 public double lateralPitchAcceleration = Double.NaN;
717                 
718                 public Rotation2D thetaRotation;
719                 
720         }
721         
722 }