updates for 0.9.3
[debian/openrocket] / src / net / sf / openrocket / aerodynamics / AerodynamicCalculator.java
1 package net.sf.openrocket.aerodynamics;
2
3 import static net.sf.openrocket.util.MathUtil.pow2;
4
5 import java.util.Iterator;
6 import java.util.Map;
7
8 import net.sf.openrocket.motor.Motor;
9 import net.sf.openrocket.rocketcomponent.Configuration;
10 import net.sf.openrocket.rocketcomponent.MotorMount;
11 import net.sf.openrocket.rocketcomponent.Rocket;
12 import net.sf.openrocket.rocketcomponent.RocketComponent;
13 import net.sf.openrocket.util.Coordinate;
14 import net.sf.openrocket.util.MathUtil;
15
16
17 /**
18  * A class that is the base of all aerodynamical calculations.
19  * <p>
20  * A {@link Configuration} object must be assigned to this class before any 
21  * operations are allowed.  This can be done using the constructor or using 
22  * the {@link #setConfiguration(Configuration)} method.  The default is a
23  * <code>null</code> configuration, in which case the calculation
24  * methods throw {@link NullPointerException}.
25  * 
26  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
27  */
28
29 public abstract class AerodynamicCalculator {
30         
31         private static final double MIN_MASS = 0.001 * MathUtil.EPSILON;
32
33         /** Number of divisions used when calculating worst CP. */
34         public static final int DIVISIONS = 360;
35         
36         /**
37          * A <code>WarningSet</code> that can be used if <code>null</code> is passed
38          * to a calculation method.
39          */
40         protected WarningSet ignoreWarningSet = new WarningSet();
41         
42         /**
43          * The <code>Rocket</code> currently being calculated.
44          */
45         protected Rocket rocket = null;
46         
47         protected Configuration configuration = null;
48         
49         
50         /*
51          * Cached data.  All CG data is in absolute coordinates.  All moments of inertia
52          * are relative to their respective CG.
53          */
54         private Coordinate[] cgCache = null;
55         private Coordinate[] origCG = null;  // CG of non-overridden stage
56         private double longitudalInertiaCache[] = null;
57         private double rotationalInertiaCache[] = null;
58         
59         
60         // TODO: LOW: Do not void unnecessary data (mass/aero separately)
61         private int rocketModID = -1;
62 //      private int aeroModID = -1;
63 //      private int massModID = -1;
64
65         /**
66          * No-options constructor.  The rocket is left as <code>null</code>.
67          */
68         public AerodynamicCalculator() {
69                 
70         }
71         
72         
73         
74         /**
75          * A constructor that sets the Configuration to be used.
76          * 
77          * @param config  the configuration to use
78          */
79         public AerodynamicCalculator(Configuration config) {
80                 setConfiguration(config);
81         }
82
83         
84         
85         public Configuration getConfiguration() {
86                 return configuration;
87         }
88         
89         public void setConfiguration(Configuration config) {
90                 this.configuration = config;
91                 this.rocket = config.getRocket();
92         }
93         
94         
95         public abstract AerodynamicCalculator newInstance();
96         
97         
98         //////////////////  Mass property calculations  ///////////////////
99         
100         
101         /**
102          * Get the CG and mass of the current configuration with motors at the specified
103          * time.  The motor ignition times are taken from the configuration.
104          */
105         public Coordinate getCG(double time) {
106                 Coordinate totalCG;
107                 
108                 totalCG = getEmptyCG();
109                 
110                 Iterator<MotorMount> iterator = configuration.motorIterator();
111                 while (iterator.hasNext()) {
112                         MotorMount mount = iterator.next();
113                         double ignition = configuration.getIgnitionTime(mount);
114                         Motor motor = mount.getMotor(configuration.getMotorConfigurationID());
115                         RocketComponent component = (RocketComponent) mount;
116                 
117                         double position = (component.getLength() - motor.getLength() 
118                                         + mount.getMotorOverhang());
119                         
120                         for (Coordinate c: component.toAbsolute(motor.getCG(time-ignition).
121                                         add(position,0,0))) {
122                                 totalCG = totalCG.average(c);
123                         }
124                 }
125                 
126                 return totalCG;
127         }
128         
129         
130         /**
131          * Get the CG and mass of the current configuration without motors.
132          * 
133          * @return                      the CG of the configuration
134          */
135         public Coordinate getEmptyCG() {
136                 checkCache();
137                 
138                 if (cgCache == null) {
139                         calculateStageCache();
140                 }
141                 
142                 Coordinate totalCG = null;
143                 for (int stage: configuration.getActiveStages()) {
144                         totalCG = cgCache[stage].average(totalCG);
145                 }
146                 
147                 if (totalCG == null)
148                         totalCG = Coordinate.NUL;
149                 
150                 return totalCG;
151         }
152         
153         
154         /**
155          * Return the longitudal inertia of the current configuration with motors at 
156          * the specified time.  The motor ignition times are taken from the configuration.
157          * 
158          * @param time          the time.
159          * @return                      the longitudal moment of inertia of the configuration.
160          */
161         public double getLongitudalInertia(double time) {
162                 checkCache();
163                 
164                 if (cgCache == null) {
165                         calculateStageCache();
166                 }
167                 
168                 final Coordinate totalCG = getCG(time);
169                 double totalInertia = 0;
170                 
171                 // Stages
172                 for (int stage: configuration.getActiveStages()) {
173                         Coordinate stageCG = cgCache[stage];
174                         
175                         totalInertia += (longitudalInertiaCache[stage] + 
176                                         stageCG.weight * MathUtil.pow2(stageCG.x - totalCG.x));
177                 }
178                 
179                 
180                 // Motors
181                 Iterator<MotorMount> iterator = configuration.motorIterator();
182                 while (iterator.hasNext()) {
183                         MotorMount mount = iterator.next();
184                         double ignition = configuration.getIgnitionTime(mount);
185                         Motor motor = mount.getMotor(configuration.getMotorConfigurationID());
186                         RocketComponent component = (RocketComponent) mount;
187                 
188                         double position = (component.getLength() - motor.getLength() 
189                                         + mount.getMotorOverhang());
190                         
191                         double inertia = motor.getLongitudalInertia(time - ignition);
192                         for (Coordinate c: component.toAbsolute(motor.getCG(time-ignition).
193                                         add(position,0,0))) {
194                                 totalInertia += inertia + c.weight * MathUtil.pow2(c.x - totalCG.x);
195                         }
196                 }
197                 
198                 return totalInertia;
199         }
200         
201         
202         /**
203          * Return the rotational inertia of the configuration with motors at the specified time.
204          * The motor ignition times are taken from the configuration.
205          * 
206          * @param time          the time.
207          * @return                      the rotational moment of inertia of the configuration.
208          */
209         public double getRotationalInertia(double time) {
210                 checkCache();
211                 
212                 if (cgCache == null) {
213                         calculateStageCache();
214                 }
215                 
216                 final Coordinate totalCG = getCG(time);
217                 double totalInertia = 0;
218                 
219                 // Stages
220                 for (int stage: configuration.getActiveStages()) {
221                         Coordinate stageCG = cgCache[stage];
222                         
223                         totalInertia += rotationalInertiaCache[stage] + stageCG.weight * (
224                                         MathUtil.pow2(stageCG.y-totalCG.y) + MathUtil.pow2(stageCG.z-totalCG.z)
225                         );
226                 }
227                 
228                 
229                 // Motors
230                 Iterator<MotorMount> iterator = configuration.motorIterator();
231                 while (iterator.hasNext()) {
232                         MotorMount mount = iterator.next();
233                         double ignition = configuration.getIgnitionTime(mount);
234                         Motor motor = mount.getMotor(configuration.getMotorConfigurationID());
235                         RocketComponent component = (RocketComponent) mount;
236                 
237                         double position = (component.getLength() - motor.getLength() 
238                                         + mount.getMotorOverhang());
239                         
240                         double inertia = motor.getRotationalInertia(time - ignition);
241                         for (Coordinate c: component.toAbsolute(motor.getCG(time-ignition).
242                                         add(position,0,0))) {
243                                 totalInertia += inertia + c.weight * (
244                                                 MathUtil.pow2(c.y - totalCG.y) + MathUtil.pow2(c.z - totalCG.z)
245                                 );
246                         }
247                 }
248                 
249                 return totalInertia;
250         }
251         
252         
253         
254         private void calculateStageCache() {
255                 int stages = rocket.getStageCount();
256                 
257                 cgCache = new Coordinate[stages];
258                 longitudalInertiaCache = new double[stages];
259                 rotationalInertiaCache = new double[stages];
260                 
261                 for (int i=0; i < stages; i++) {
262                         RocketComponent stage = rocket.getChild(i);
263                         MassData data = calculateAssemblyMassData(stage);
264                         cgCache[i] = stage.toAbsolute(data.cg)[0];
265                         longitudalInertiaCache[i] = data.longitudalInertia;
266                         rotationalInertiaCache[i] = data.rotationalInetria;
267                 }
268         }
269         
270         
271         
272 //      /**
273 //       * Updates the stage CGs.
274 //       */
275 //      private void calculateStageCGs() {
276 //              int stages = rocket.getStageCount();
277 //              
278 //              cgCache = new Coordinate[stages];
279 //              origCG = new Coordinate[stages];
280 //              
281 //              for (int i=0; i < stages; i++) {
282 //                      Stage stage = (Stage) rocket.getChild(i);
283 //                      Coordinate stageCG = null;
284 //                      
285 //                      Iterator<RocketComponent> iterator = stage.deepIterator();
286 //                      while (iterator.hasNext()) {
287 //                              RocketComponent component = iterator.next();
288 //
289 //                              for (Coordinate c: component.toAbsolute(component.getCG())) {
290 //                                      stageCG = c.average(stageCG);
291 //                              }
292 //                      }
293 //
294 //                      if (stageCG == null)
295 //                              stageCG = Coordinate.NUL;
296 //                      
297 //                      origCG[i] = stageCG;
298 //                      
299 //                      if (stage.isMassOverridden()) {
300 //                              stageCG = stageCG.setWeight(stage.getOverrideMass());
301 //                      }
302 //                      if (stage.isCGOverridden()) {
303 //                              stageCG = stageCG.setXYZ(stage.getOverrideCG());
304 //                      }
305 //                      
306 ////                    System.out.println("Stage "+i+" CG:"+stageCG);
307 //                      
308 //                      cgCache[i] = stageCG;
309 //              }
310 //      }
311 //      
312 //      
313 //      private Coordinate calculateCG(RocketComponent component) {
314 //              Coordinate componentCG = Coordinate.NUL;
315 //              
316 //              // Compute CG of this component
317 //              Coordinate cg = component.getCG();
318 //              if (cg.weight < MIN_MASS)
319 //                      cg = cg.setWeight(MIN_MASS);
320 //
321 //              for (Coordinate c: component.toAbsolute(cg)) {
322 //                      componentCG = componentCG.average(c);
323 //              }
324 //              
325 //              // Compute CG with subcomponents
326 //              for (RocketComponent sibling: component.getChildren()) {
327 //                      componentCG = componentCG.average(calculateCG(sibling));
328 //              }
329 //              
330 //              // Override mass/CG if subcomponents are also overridden
331 //              if (component.getOverrideSubcomponents()) {
332 //                      if (component.isMassOverridden()) {
333 //                              componentCG = componentCG.setWeight(
334 //                                              MathUtil.max(component.getOverrideMass(), MIN_MASS));
335 //                      }
336 //                      if (component.isCGOverridden()) {
337 //                              componentCG = componentCG.setXYZ(component.getOverrideCG());
338 //                      }
339 //              }
340 //              
341 //              return componentCG;
342 //      }
343 //      
344 //      
345 //      
346 //      private void calculateStageInertias() {
347 //              int stages = rocket.getStageCount();
348 //              
349 //              if (cgCache == null)
350 //                      calculateStageCGs();
351 //              
352 //              longitudalInertiaCache = new double[stages];
353 //              rotationalInertiaCache = new double[stages];
354 //
355 //              for (int i=0; i < stages; i++) {
356 //                      Coordinate stageCG = cgCache[i];
357 //                      double stageLongitudalInertia = 0;
358 //                      double stageRotationalInertia = 0;
359 //                      
360 //                      Iterator<RocketComponent> iterator = rocket.getChild(i).deepIterator();
361 //                      while (iterator.hasNext()) {
362 //                              RocketComponent component = iterator.next();
363 //                              double li = component.getLongitudalInertia();
364 //                              double ri = component.getRotationalInertia();
365 //                              double mass = component.getMass();
366 //                              
367 //                              for (Coordinate c: component.toAbsolute(component.getCG())) {
368 //                                      stageLongitudalInertia += li + mass * MathUtil.pow2(c.x - stageCG.x);
369 //                                      stageRotationalInertia += ri + mass * (MathUtil.pow2(c.y - stageCG.y) +
370 //                                                      MathUtil.pow2(c.z - stageCG.z));
371 //                              }
372 //                      }
373 //                      
374 //                      // Check for mass override of complete stage
375 //                      if ((origCG[i].weight != cgCache[i].weight) && origCG[i].weight > 0.0000001) {
376 //                              stageLongitudalInertia = (stageLongitudalInertia * cgCache[i].weight / 
377 //                                              origCG[i].weight);
378 //                              stageRotationalInertia = (stageRotationalInertia * cgCache[i].weight / 
379 //                                              origCG[i].weight);
380 //                      }
381 //                      
382 //                      longitudalInertiaCache[i] = stageLongitudalInertia;
383 //                      rotationalInertiaCache[i] = stageRotationalInertia;
384 //              }
385 //      }
386 //      
387         
388         
389         /**
390          * Returns the mass and inertia data for this component and all subcomponents.
391          * The inertia is returned relative to the CG, and the CG is in the coordinates
392          * of the specified component, not global coordinates.
393          */
394         private MassData calculateAssemblyMassData(RocketComponent parent) {
395                 MassData parentData = new MassData();
396                 
397                 // Calculate data for this component
398                 parentData.cg = parent.getComponentCG();
399                 if (parentData.cg.weight < MIN_MASS)
400                         parentData.cg = parentData.cg.setWeight(MIN_MASS);
401                 
402                 
403                 // Override only this component's data
404                 if (!parent.getOverrideSubcomponents()) {
405                         if (parent.isMassOverridden())
406                                 parentData.cg = parentData.cg.setWeight(MathUtil.max(parent.getOverrideMass(),MIN_MASS));
407                         if (parent.isCGOverridden())
408                                 parentData.cg = parentData.cg.setXYZ(parent.getOverrideCG());
409                 }
410                 
411                 parentData.longitudalInertia = parent.getLongitudalUnitInertia() * parentData.cg.weight;
412                 parentData.rotationalInetria = parent.getRotationalUnitInertia() * parentData.cg.weight;
413                 
414                 
415                 // Combine data for subcomponents
416                 for (RocketComponent sibling: parent.getChildren()) {
417                         Coordinate combinedCG;
418                         double dx2, dr2;
419                         
420                         // Compute data of sibling
421                         MassData siblingData = calculateAssemblyMassData(sibling);
422                         Coordinate[] siblingCGs = sibling.toRelative(siblingData.cg, parent);
423                         
424                         for (Coordinate siblingCG: siblingCGs) {
425                         
426                                 // Compute CG of this + sibling
427                                 combinedCG = parentData.cg.average(siblingCG);
428                                 
429                                 // Add effect of this CG change to parent inertia
430                                 dx2 = pow2(parentData.cg.x - combinedCG.x);
431                                 parentData.longitudalInertia += parentData.cg.weight * dx2;
432                                 
433                                 dr2 = pow2(parentData.cg.y - combinedCG.y) + pow2(parentData.cg.z - combinedCG.z);
434                                 parentData.rotationalInetria += parentData.cg.weight * dr2;
435                                 
436                                 
437                                 // Add inertia of sibling
438                                 parentData.longitudalInertia += siblingData.longitudalInertia;
439                                 parentData.rotationalInetria += siblingData.rotationalInetria;
440                                 
441                                 // Add effect of sibling CG change
442                                 dx2 = pow2(siblingData.cg.x - combinedCG.x);
443                                 parentData.longitudalInertia += siblingData.cg.weight * dx2;
444                                 
445                                 dr2 = pow2(siblingData.cg.y - combinedCG.y) + pow2(siblingData.cg.z - combinedCG.z);
446                                 parentData.rotationalInetria += siblingData.cg.weight * dr2;
447                                 
448                                 // Set combined CG
449                                 parentData.cg = combinedCG;
450                         }
451                 }
452
453                 // Override total data
454                 if (parent.getOverrideSubcomponents()) {
455                         if (parent.isMassOverridden()) {
456                                 double oldMass = parentData.cg.weight;
457                                 double newMass = MathUtil.max(parent.getOverrideMass(), MIN_MASS);
458                                 parentData.longitudalInertia = parentData.longitudalInertia * newMass / oldMass;
459                                 parentData.rotationalInetria = parentData.rotationalInetria * newMass / oldMass;
460                                 parentData.cg = parentData.cg.setWeight(newMass);
461                         }
462                         if (parent.isCGOverridden()) {
463                                 double oldx = parentData.cg.x;
464                                 double newx = parent.getOverrideCGX();
465                                 parentData.longitudalInertia += parentData.cg.weight * pow2(oldx - newx);
466                                 parentData.cg = parentData.cg.setX(newx);
467                         }
468                 }
469                 
470                 return parentData;
471         }
472         
473         
474         private static class MassData {
475                 public Coordinate cg = Coordinate.NUL;
476                 public double longitudalInertia = 0;
477                 public double rotationalInetria = 0;
478         }
479         
480         
481         
482         
483         
484         ////////////////  Aerodynamic calculators  ////////////////
485         
486         public abstract Coordinate getCP(FlightConditions conditions, WarningSet warnings);
487         
488         /*
489         public abstract List<AerodynamicForces> getCPAnalysis(FlightConditions conditions, 
490                         WarningSet warnings);
491         */
492         
493         public abstract Map<RocketComponent, AerodynamicForces> 
494                 getForceAnalysis(FlightConditions conditions, WarningSet warnings);
495         
496         public abstract AerodynamicForces getAerodynamicForces(double time,
497                         FlightConditions conditions, WarningSet warnings);
498         
499         
500         /* Calculate only axial forces (and do not warn about insane AOA etc) */
501         public abstract AerodynamicForces getAxialForces(double time,
502                         FlightConditions conditions, WarningSet warnings);
503
504         
505         
506         public Coordinate getWorstCP() {
507                 return getWorstCP(new FlightConditions(configuration), ignoreWarningSet);
508         }
509         
510         /*
511          * The worst theta angle is stored in conditions.
512          */
513         public Coordinate getWorstCP(FlightConditions conditions, WarningSet warnings) {
514                 FlightConditions cond = conditions.clone();
515                 Coordinate worst = new Coordinate(Double.MAX_VALUE);
516                 Coordinate cp;
517                 double theta = 0;
518                 
519                 for (int i=0; i < DIVISIONS; i++) {
520                         cond.setTheta(2*Math.PI*i/DIVISIONS);
521                         cp = getCP(cond, warnings);
522                         if (cp.x < worst.x) {
523                                 worst = cp;
524                                 theta = cond.getTheta();
525                         }
526                 }
527                 
528                 conditions.setTheta(theta);
529                 
530                 return worst;
531         }
532         
533         
534         
535         
536         
537         /**
538          * Check the current cache consistency.  This method must be called by all
539          * methods that may use any cached data before any other operations are
540          * performed.  If the rocket has changed since the previous call to
541          * <code>checkCache()</code>, then either {@link #voidAerodynamicCache()} or
542          * {@link #voidMassCache()} (or both) are called.
543          * <p>
544          * This method performs the checking based on the rocket's modification IDs,
545          * so that these method may be called from listeners of the rocket itself.
546          */
547         protected final void checkCache() {
548                 if (rocketModID != rocket.getModID()) {
549                         rocketModID = rocket.getModID();
550                         voidMassCache();
551                         voidAerodynamicCache();
552                 }
553         }
554         
555         
556         /**
557          * Void cached mass data.  This method is called whenever a change occurs in 
558          * the rocket structure that affects the mass of the rocket and when a new 
559          * Rocket is set.  This method must be overridden to void any cached data 
560          * necessary.  The method must call <code>super.voidMassCache()</code> during its 
561          * execution.
562          */
563         protected void voidMassCache() {
564                 cgCache = null;
565                 longitudalInertiaCache = null;
566                 rotationalInertiaCache = null;
567         }
568         
569         /**
570          * Void cached aerodynamic data.  This method is called whenever a change occurs in 
571          * the rocket structure that affects the aerodynamics of the rocket and when a new 
572          * Rocket is set.  This method must be overridden to void any cached data 
573          * necessary.  The method must call <code>super.voidAerodynamicCache()</code> during 
574          * its execution.
575          */
576         protected void voidAerodynamicCache() {
577                 // No-op
578         }
579         
580         
581 }