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