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