]> git.gag.com Git - debian/openrocket/blob - core/src/net/sf/openrocket/rocketcomponent/Transition.java
When a component preset for NOSE_CONE or TRANSITION has filled set to true, then...
[debian/openrocket] / core / src / net / sf / openrocket / rocketcomponent / Transition.java
1 package net.sf.openrocket.rocketcomponent;
2
3 import static java.lang.Math.sin;
4 import static net.sf.openrocket.util.MathUtil.*;
5
6 import java.util.Collection;
7
8 import net.sf.openrocket.l10n.Translator;
9 import net.sf.openrocket.preset.ComponentPreset;
10 import net.sf.openrocket.preset.ComponentPreset.Type;
11 import net.sf.openrocket.startup.Application;
12 import net.sf.openrocket.util.Coordinate;
13 import net.sf.openrocket.util.MathUtil;
14
15
16 public class Transition extends SymmetricComponent {
17         private static final Translator trans = Application.getTranslator();
18         private static final double CLIP_PRECISION = 0.0001;
19         
20
21         private Shape type;
22         private double shapeParameter;
23         private boolean clipped; // Not to be read - use isClipped(), which may be overriden
24         
25         private double radius1, radius2;
26         private boolean autoRadius1, autoRadius2; // Whether the start radius is automatic
27                         
28
29         private double foreShoulderRadius;
30         private double foreShoulderThickness;
31         private double foreShoulderLength;
32         private boolean foreShoulderCapped;
33         private double aftShoulderRadius;
34         private double aftShoulderThickness;
35         private double aftShoulderLength;
36         private boolean aftShoulderCapped;
37         
38
39         // Used to cache the clip length
40         private double clipLength = -1;
41         
42         public Transition() {
43                 super();
44                 
45                 this.radius1 = DEFAULT_RADIUS;
46                 this.radius2 = DEFAULT_RADIUS;
47                 this.length = DEFAULT_RADIUS * 3;
48                 this.autoRadius1 = true;
49                 this.autoRadius2 = true;
50                 
51                 this.type = Shape.CONICAL;
52                 this.shapeParameter = 0;
53                 this.clipped = true;
54         }
55         
56         ////////  Length  ////////
57         @Override
58         public void setLength( double length ) {
59                 if ( this.length == length ) {
60                         return;
61                 }
62                 // Need to clearPreset when length changes.
63                 clearPreset();
64                 super.setLength( length );
65         }
66
67
68         ////////  Fore radius  ////////
69         
70
71         @Override
72         public double getForeRadius() {
73                 if (isForeRadiusAutomatic()) {
74                         // Get the automatic radius from the front
75                         double r = -1;
76                         SymmetricComponent c = this.getPreviousSymmetricComponent();
77                         if (c != null) {
78                                 r = c.getFrontAutoRadius();
79                         }
80                         if (r < 0)
81                                 r = DEFAULT_RADIUS;
82                         return r;
83                 }
84                 return radius1;
85         }
86         
87         public void setForeRadius(double radius) {
88                 if ((this.radius1 == radius) && (autoRadius1 == false))
89                         return;
90                 
91                 this.autoRadius1 = false;
92                 this.radius1 = Math.max(radius, 0);
93                 
94                 if (this.thickness > this.radius1 && this.thickness > this.radius2)
95                         this.thickness = Math.max(this.radius1, this.radius2);
96                 
97                 clearPreset();
98                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
99         }
100         
101         @Override
102         public boolean isForeRadiusAutomatic() {
103                 return autoRadius1;
104         }
105         
106         public void setForeRadiusAutomatic(boolean auto) {
107                 if (autoRadius1 == auto)
108                         return;
109                 
110                 autoRadius1 = auto;
111
112                 clearPreset();
113                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
114         }
115         
116         
117         ////////  Aft radius  /////////
118         
119         @Override
120         public double getAftRadius() {
121                 if (isAftRadiusAutomatic()) {
122                         // Return the auto radius from the rear
123                         double r = -1;
124                         SymmetricComponent c = this.getNextSymmetricComponent();
125                         if (c != null) {
126                                 r = c.getRearAutoRadius();
127                         }
128                         if (r < 0)
129                                 r = DEFAULT_RADIUS;
130                         return r;
131                 }
132                 return radius2;
133         }
134         
135         
136
137         public void setAftRadius(double radius) {
138                 if ((this.radius2 == radius) && (autoRadius2 == false))
139                         return;
140                 
141                 this.autoRadius2 = false;
142                 this.radius2 = Math.max(radius, 0);
143                 
144                 if (this.thickness > this.radius1 && this.thickness > this.radius2)
145                         this.thickness = Math.max(this.radius1, this.radius2);
146
147                 clearPreset();
148                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
149         }
150         
151         @Override
152         public boolean isAftRadiusAutomatic() {
153                 return autoRadius2;
154         }
155         
156         public void setAftRadiusAutomatic(boolean auto) {
157                 if (autoRadius2 == auto)
158                         return;
159                 
160                 autoRadius2 = auto;
161
162                 clearPreset();
163                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
164         }
165         
166         
167
168         //// Radius automatics
169         
170         @Override
171         protected double getFrontAutoRadius() {
172                 if (isAftRadiusAutomatic())
173                         return -1;
174                 return getAftRadius();
175         }
176         
177         
178         @Override
179         protected double getRearAutoRadius() {
180                 if (isForeRadiusAutomatic())
181                         return -1;
182                 return getForeRadius();
183         }
184         
185         
186
187
188         ////////  Type & shape  /////////
189         
190         public Shape getType() {
191                 return type;
192         }
193         
194         public void setType(Shape type) {
195                 if (type == null) {
196                         throw new IllegalArgumentException("setType called with null argument");
197                 }
198                 if (this.type == type)
199                         return;
200                 this.type = type;
201                 this.clipped = type.isClippable();
202                 this.shapeParameter = type.defaultParameter();
203                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
204         }
205         
206         public double getShapeParameter() {
207                 return shapeParameter;
208         }
209         
210         public void setShapeParameter(double n) {
211                 if (shapeParameter == n)
212                         return;
213                 this.shapeParameter = MathUtil.clamp(n, type.minParameter(), type.maxParameter());
214                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
215         }
216         
217         public boolean isClipped() {
218                 if (!type.isClippable())
219                         return false;
220                 return clipped;
221         }
222         
223         public void setClipped(boolean c) {
224                 if (clipped == c)
225                         return;
226                 clipped = c;
227                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
228         }
229         
230         public boolean isClippedEnabled() {
231                 return type.isClippable();
232         }
233         
234         public double getShapeParameterMin() {
235                 return type.minParameter();
236         }
237         
238         public double getShapeParameterMax() {
239                 return type.maxParameter();
240         }
241         
242         
243         ////////  Shoulders  ////////
244         
245         public double getForeShoulderRadius() {
246                 return foreShoulderRadius;
247         }
248         
249         public void setForeShoulderRadius(double foreShoulderRadius) {
250                 if (MathUtil.equals(this.foreShoulderRadius, foreShoulderRadius))
251                         return;
252                 this.foreShoulderRadius = foreShoulderRadius;
253                 clearPreset();
254                 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
255         }
256         
257         public double getForeShoulderThickness() {
258                 return foreShoulderThickness;
259         }
260         
261         public void setForeShoulderThickness(double foreShoulderThickness) {
262                 if (MathUtil.equals(this.foreShoulderThickness, foreShoulderThickness))
263                         return;
264                 this.foreShoulderThickness = foreShoulderThickness;
265                 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
266         }
267         
268         public double getForeShoulderLength() {
269                 return foreShoulderLength;
270         }
271         
272         public void setForeShoulderLength(double foreShoulderLength) {
273                 if (MathUtil.equals(this.foreShoulderLength, foreShoulderLength))
274                         return;
275                 this.foreShoulderLength = foreShoulderLength;
276                 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
277         }
278         
279         public boolean isForeShoulderCapped() {
280                 return foreShoulderCapped;
281         }
282         
283         public void setForeShoulderCapped(boolean capped) {
284                 if (this.foreShoulderCapped == capped)
285                         return;
286                 this.foreShoulderCapped = capped;
287                 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
288         }
289         
290         
291
292
293         public double getAftShoulderRadius() {
294                 return aftShoulderRadius;
295         }
296         
297         public void setAftShoulderRadius(double aftShoulderRadius) {
298                 if (MathUtil.equals(this.aftShoulderRadius, aftShoulderRadius))
299                         return;
300                 this.aftShoulderRadius = aftShoulderRadius;
301                 clearPreset();
302                 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
303         }
304         
305         public double getAftShoulderThickness() {
306                 return aftShoulderThickness;
307         }
308         
309         public void setAftShoulderThickness(double aftShoulderThickness) {
310                 if (MathUtil.equals(this.aftShoulderThickness, aftShoulderThickness))
311                         return;
312                 this.aftShoulderThickness = aftShoulderThickness;
313                 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
314         }
315         
316         public double getAftShoulderLength() {
317                 return aftShoulderLength;
318         }
319         
320         public void setAftShoulderLength(double aftShoulderLength) {
321                 if (MathUtil.equals(this.aftShoulderLength, aftShoulderLength))
322                         return;
323                 this.aftShoulderLength = aftShoulderLength;
324                 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
325         }
326         
327         public boolean isAftShoulderCapped() {
328                 return aftShoulderCapped;
329         }
330         
331         public void setAftShoulderCapped(boolean capped) {
332                 if (this.aftShoulderCapped == capped)
333                         return;
334                 this.aftShoulderCapped = capped;
335                 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
336         }
337         
338         
339
340
341         ///////////   Shape implementations   ////////////
342         
343
344
345         /**
346          * Return the radius at point x of the transition.
347          */
348         @Override
349         public double getRadius(double x) {
350                 if (x < 0 || x > length)
351                         return 0;
352                 
353                 double r1 = getForeRadius();
354                 double r2 = getAftRadius();
355                 
356                 if (r1 == r2)
357                         return r1;
358                 
359                 if (r1 > r2) {
360                         x = length - x;
361                         double tmp = r1;
362                         r1 = r2;
363                         r2 = tmp;
364                 }
365                 
366                 if (isClipped()) {
367                         // Check clip calculation
368                         if (clipLength < 0)
369                                 calculateClip(r1, r2);
370                         return type.getRadius(clipLength + x, r2, clipLength + length, shapeParameter);
371                 } else {
372                         // Not clipped
373                         return r1 + type.getRadius(x, r2 - r1, length, shapeParameter);
374                 }
375         }
376         
377         /**
378          * Numerically solve clipLength from the equation
379          *     r1 == type.getRadius(clipLength,r2,clipLength+length)
380          * using a binary search.  It assumes getOuterRadius() to be monotonically increasing.
381          */
382         private void calculateClip(double r1, double r2) {
383                 double min = 0, max = length;
384                 
385                 if (r1 >= r2) {
386                         double tmp = r1;
387                         r1 = r2;
388                         r2 = tmp;
389                 }
390                 
391                 if (r1 == 0) {
392                         clipLength = 0;
393                         return;
394                 }
395                 
396                 if (length <= 0) {
397                         clipLength = 0;
398                         return;
399                 }
400                 
401                 // Required:
402                 //    getR(min,min+length,r2) - r1 < 0
403                 //    getR(max,max+length,r2) - r1 > 0
404                 
405                 int n = 0;
406                 while (type.getRadius(max, r2, max + length, shapeParameter) - r1 < 0) {
407                         min = max;
408                         max *= 2;
409                         n++;
410                         if (n > 10)
411                                 break;
412                 }
413                 
414                 while (true) {
415                         clipLength = (min + max) / 2;
416                         if ((max - min) < CLIP_PRECISION)
417                                 return;
418                         double val = type.getRadius(clipLength, r2, clipLength + length, shapeParameter);
419                         if (val - r1 > 0) {
420                                 max = clipLength;
421                         } else {
422                                 min = clipLength;
423                         }
424                 }
425         }
426         
427         
428         @Override
429         public double getInnerRadius(double x) {
430                 return Math.max(getRadius(x) - thickness, 0);
431         }
432         
433         
434
435         @Override
436         public Collection<Coordinate> getComponentBounds() {
437                 Collection<Coordinate> bounds = super.getComponentBounds();
438                 if (foreShoulderLength > 0.001)
439                         addBound(bounds, -foreShoulderLength, foreShoulderRadius);
440                 if (aftShoulderLength > 0.001)
441                         addBound(bounds, getLength() + aftShoulderLength, aftShoulderRadius);
442                 return bounds;
443         }
444
445         @Override
446         public double getComponentVolume() {
447                 double volume =  super.getComponentVolume();
448                 if (getForeShoulderLength() > 0.001) {
449                         final double or = getForeShoulderRadius();
450                         final double ir = Math.max(getForeShoulderRadius() - getForeShoulderThickness(), 0);
451                         volume += ringVolume( or, ir, getForeShoulderLength() );
452                 }
453                 if (isForeShoulderCapped()) {
454                         final double ir = Math.max(getForeShoulderRadius() - getForeShoulderThickness(), 0);
455                         volume += ringVolume(ir, 0, getForeShoulderThickness() );
456                 }
457                 
458                 if (getAftShoulderLength() > 0.001) {
459                         final double or = getAftShoulderRadius();
460                         final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0);
461                         volume += ringVolume(or, ir, getAftShoulderLength() );
462                 }
463                 if (isAftShoulderCapped()) {
464                         final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0);
465                         volume += ringVolume(ir, 0, getAftShoulderThickness() );
466                 }
467                 
468                 return volume;
469         }
470
471         @Override
472         public Coordinate getComponentCG() {
473                 Coordinate cg = super.getComponentCG();
474                 if (getForeShoulderLength() > 0.001) {
475                         final double ir = Math.max(getForeShoulderRadius() - getForeShoulderThickness(), 0);
476                         cg = cg.average(ringCG(getForeShoulderRadius(), ir, -getForeShoulderLength(), 0,
477                                         getMaterial().getDensity()));
478                 }
479                 if (isForeShoulderCapped()) {
480                         final double ir = Math.max(getForeShoulderRadius() - getForeShoulderThickness(), 0);
481                         cg = cg.average(ringCG(ir, 0, -getForeShoulderLength(),
482                                         getForeShoulderThickness() - getForeShoulderLength(),
483                                         getMaterial().getDensity()));
484                 }
485                 
486                 if (getAftShoulderLength() > 0.001) {
487                         final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0);
488                         cg = cg.average(ringCG(getAftShoulderRadius(), ir, getLength(),
489                                         getLength() + getAftShoulderLength(), getMaterial().getDensity()));
490                 }
491                 if (isAftShoulderCapped()) {
492                         final double ir = Math.max(getAftShoulderRadius() - getAftShoulderThickness(), 0);
493                         cg = cg.average(ringCG(ir, 0,
494                                         getLength() + getAftShoulderLength() - getAftShoulderThickness(),
495                                         getLength() + getAftShoulderLength(), getMaterial().getDensity()));
496                 }
497                 return cg;
498         }
499         
500         
501         /*
502          * The moments of inertia are not explicitly corrected for the shoulders.
503          * However, since the mass is corrected, the inertia is automatically corrected
504          * to very nearly the correct value.
505          */
506
507
508
509         /**
510          * Returns the name of the component ("Transition").
511          */
512         @Override
513         public String getComponentName() {
514                 //// Transition
515                 return trans.get("Transition.Transition");
516         }
517         
518         @Override
519         protected void componentChanged(ComponentChangeEvent e) {
520                 super.componentChanged(e);
521                 clipLength = -1;
522         }
523         
524         /**
525          * Check whether the given type can be added to this component.  Transitions allow any
526          * InternalComponents to be added.
527          *
528          * @param ctype  The RocketComponent class type to add.
529          * @return      Whether such a component can be added.
530          */
531         @Override
532         public boolean isCompatible(Class<? extends RocketComponent> ctype) {
533                 if (InternalComponent.class.isAssignableFrom(ctype))
534                         return true;
535                 return false;
536         }
537         
538         @Override
539         public Type getPresetType() {
540                 return ComponentPreset.Type.TRANSITION;
541         }
542
543
544         @Override
545         protected void loadFromPreset(ComponentPreset preset) {
546
547                 boolean presetFilled = false;
548                 if ( preset.has(ComponentPreset.FILLED ) ) {
549                         presetFilled = preset.get( ComponentPreset.FILLED);
550                 }
551                 
552                 if ( preset.has(ComponentPreset.SHAPE) ) {
553                         Shape s = preset.get(ComponentPreset.SHAPE);
554                         this.setType(s);
555                 }
556                 if ( preset.has(ComponentPreset.AFT_OUTER_DIAMETER) )  {
557                         double outerDiameter = preset.get(ComponentPreset.AFT_OUTER_DIAMETER);
558                         this.setAftRadiusAutomatic(false);
559                         this.setAftRadius(outerDiameter/2.0);
560                 }
561                 if ( preset.has(ComponentPreset.AFT_SHOULDER_LENGTH) ) {
562                         double d = preset.get(ComponentPreset.AFT_SHOULDER_LENGTH);
563                         this.setAftShoulderLength(d);
564                 }
565                 if ( preset.has(ComponentPreset.AFT_SHOULDER_DIAMETER) ) {
566                         double d = preset.get(ComponentPreset.AFT_SHOULDER_DIAMETER);
567                         this.setAftShoulderRadius(d/2.0);
568                         if ( presetFilled ) {
569                                 this.setAftShoulderThickness(d/2.0);
570                         }
571                 }
572                 if ( preset.has(ComponentPreset.FORE_OUTER_DIAMETER) )  {
573                         double outerDiameter = preset.get(ComponentPreset.FORE_OUTER_DIAMETER);
574                         this.setForeRadiusAutomatic(false);
575                         this.setForeRadius(outerDiameter/2.0);
576                 }
577                 if ( preset.has(ComponentPreset.FORE_SHOULDER_LENGTH) ) {
578                         double d = preset.get(ComponentPreset.FORE_SHOULDER_LENGTH);
579                         this.setForeShoulderLength(d);
580                 }
581                 if ( preset.has(ComponentPreset.FORE_SHOULDER_DIAMETER) ) {
582                         double d = preset.get(ComponentPreset.FORE_SHOULDER_DIAMETER);
583                         this.setForeShoulderRadius(d/2.0);
584                         if ( presetFilled ) {
585                                 this.setForeShoulderThickness(d/2.0);
586                         }
587                 }
588
589                 super.loadFromPreset(preset);
590
591                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
592
593         }
594
595         /**
596          * An enumeration listing the possible shapes of transitions.
597          *
598          * @author Sampo Niskanen <sampo.niskanen@iki.fi>
599          */
600         public static enum Shape {
601                 
602                 /**
603                  * Conical shape.
604                  */
605                 //// Conical
606                 CONICAL(trans.get("Shape.Conical"),
607                                 //// A conical nose cone has a profile of a triangle.
608                                 trans.get("Shape.Conical.desc1"),
609                                 //// A conical transition has straight sides.
610                                 trans.get("Shape.Conical.desc2")) {
611                         @Override
612                         public double getRadius(double x, double radius, double length, double param) {
613                                 assert x >= 0;
614                                 assert x <= length;
615                                 assert radius >= 0;
616                                 return radius * x / length;
617                         }
618                 },
619                 
620                 /**
621                  * Ogive shape.  The shape parameter is the portion of an extended tangent ogive
622                  * that will be used.  That is, for param==1 a tangent ogive will be produced, and
623                  * for smaller values the shape straightens out into a cone at param==0.
624                  */
625                 //// Ogive
626                 OGIVE(trans.get("Shape.Ogive"),
627                                 //// An ogive nose cone has a profile that is a segment of a circle.  The shape parameter value 1 produces a <b>tangent ogive</b>, which has a smooth transition to the body tube, values less than 1 produce <b>secant ogives</b>.
628                                 trans.get("Shape.Ogive.desc1"),
629                                 //// An ogive transition has a profile that is a segment of a circle.   The shape parameter value 1 produces a <b>tangent ogive</b>, which has a smooth transition to the body tube at the aft end, values less than 1 produce <b>secant ogives</b>.
630                                 trans.get("Shape.Ogive.desc2")) {
631                         @Override
632                         public boolean usesParameter() {
633                                 return true; // Range 0...1 is default
634                         }
635                         
636                         @Override
637                         public double defaultParameter() {
638                                 return 1.0; // Tangent ogive by default
639                         }
640                         
641                         @Override
642                         public double getRadius(double x, double radius, double length, double param) {
643                                 assert x >= 0;
644                                 assert x <= length;
645                                 assert radius >= 0;
646                                 assert param >= 0;
647                                 assert param <= 1;
648                                 
649                                 // Impossible to calculate ogive for length < radius, scale instead
650                                 // TODO: LOW: secant ogive could be calculated lower
651                                 if (length < radius) {
652                                         x = x * radius / length;
653                                         length = radius;
654                                 }
655                                 
656                                 if (param < 0.001)
657                                         return CONICAL.getRadius(x, radius, length, param);
658                                 
659                                 // Radius of circle is:
660                                 double R = MathUtil.safeSqrt((pow2(length) + pow2(radius)) *
661                                                 (pow2((2 - param) * length) + pow2(param * radius)) / (4 * pow2(param * radius)));
662                                 double L = length / param;
663                                 //                              double R = (radius + length*length/(radius*param*param))/2;
664                                 double y0 = MathUtil.safeSqrt(R * R - L * L);
665                                 return MathUtil.safeSqrt(R * R - (L - x) * (L - x)) - y0;
666                         }
667                 },
668                 
669                 /**
670                  * Ellipsoidal shape.
671                  */
672                 //// Ellipsoid
673                 ELLIPSOID(trans.get("Shape.Ellipsoid"),
674                                 //// An ellipsoidal nose cone has a profile of a half-ellipse with major axes of lengths 2&times;<i>Length</i> and <i>Diameter</i>.
675                                 trans.get("Shape.Ellipsoid.desc1"),
676                                 //// An ellipsoidal transition has a profile of a half-ellipse with major axes of lengths 2&times;<i>Length</i> and <i>Diameter</i>.  If the transition is not clipped, then the profile is extended at the center by the corresponding radius.         
677                                 trans.get("Shape.Ellipsoid.desc2"), true) {
678                         @Override
679                         public double getRadius(double x, double radius, double length, double param) {
680                                 assert x >= 0;
681                                 assert x <= length;
682                                 assert radius >= 0;
683                                 x = x * radius / length;
684                                 return MathUtil.safeSqrt(2 * radius * x - x * x); // radius/length * sphere
685                         }
686                 },
687                 
688                 //// Power series
689                 POWER(trans.get("Shape.Powerseries"),
690                                 trans.get("Shape.Powerseries.desc1"),
691                                 trans.get("Shape.Powerseries.desc2"), true) {
692                         @Override
693                         public boolean usesParameter() { // Range 0...1
694                                 return true;
695                         }
696                         
697                         @Override
698                         public double defaultParameter() {
699                                 return 0.5;
700                         }
701                         
702                         @Override
703                         public double getRadius(double x, double radius, double length, double param) {
704                                 assert x >= 0;
705                                 assert x <= length;
706                                 assert radius >= 0;
707                                 assert param >= 0;
708                                 assert param <= 1;
709                                 if (param <= 0.00001) {
710                                         if (x <= 0.00001)
711                                                 return 0;
712                                         else
713                                                 return radius;
714                                 }
715                                 return radius * Math.pow(x / length, param);
716                         }
717                         
718                 },
719                 
720                 //// Parabolic series
721                 PARABOLIC(trans.get("Shape.Parabolicseries"),
722                                 ////A parabolic series nose cone has a profile of a parabola.  The shape parameter defines the segment of the parabola to utilize.  The shape parameter 1.0 produces a <b>full parabola</b> which is tangent to the body tube, 0.75 produces a <b>3/4 parabola</b>, 0.5 procudes a <b>1/2 parabola</b> and 0 produces a <b>conical</b> nose cone.
723                                 trans.get("Shape.Parabolicseries.desc1"),
724                                 ////A parabolic series transition has a profile of a parabola.  The shape parameter defines the segment of the parabola to utilize.  The shape parameter 1.0 produces a <b>full parabola</b> which is tangent to the body tube at the aft end, 0.75 produces a <b>3/4 parabola</b>, 0.5 procudes a <b>1/2 parabola</b> and 0 produces a <b>conical</b> transition.
725                                 trans.get("Shape.Parabolicseries.desc2")) {
726                         
727                         // In principle a parabolic transition is clippable, but the difference is
728                         // negligible.
729                         
730                         @Override
731                         public boolean usesParameter() { // Range 0...1
732                                 return true;
733                         }
734                         
735                         @Override
736                         public double defaultParameter() {
737                                 return 1.0;
738                         }
739                         
740                         @Override
741                         public double getRadius(double x, double radius, double length, double param) {
742                                 assert x >= 0;
743                                 assert x <= length;
744                                 assert radius >= 0;
745                                 assert param >= 0;
746                                 assert param <= 1;
747                                 
748                                 return radius * ((2 * x / length - param * pow2(x / length)) / (2 - param));
749                         }
750                 },
751                 
752                 //// Haack series
753                 HAACK(trans.get("Shape.Haackseries"),
754                                 //// The Haack series nose cones are designed to minimize drag.  The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> nose cone, which minimizes drag for fixed length and diameter, while a value of 0.333 produces an <b>LV-Haack</b> nose cone, which minimizes drag for fixed length and volume.
755                                 trans.get("Shape.Haackseries.desc1"),
756                                 //// The Haack series <i>nose cones</i> are designed to minimize drag.  These transition shapes are their equivalents, but do not necessarily produce optimal drag for transitions.  The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> shape, while a value of 0.333 produces an <b>LV-Haack</b> shape.            
757                                 trans.get("Shape.Haackseries.desc2"), true) {
758                         
759                         @Override
760                         public boolean usesParameter() {
761                                 return true;
762                         }
763                         
764                         @Override
765                         public double maxParameter() {
766                                 return 1.0 / 3.0; // Range 0...1/3
767                         }
768                         
769                         @Override
770                         public double getRadius(double x, double radius, double length, double param) {
771                                 assert x >= 0;
772                                 assert x <= length;
773                                 assert radius >= 0;
774                                 assert param >= 0;
775                                 assert param <= 2;
776                                 
777                                 double theta = Math.acos(1 - 2 * x / length);
778                                 if (MathUtil.equals(param, 0)) {
779                                         return radius * MathUtil.safeSqrt((theta - sin(2 * theta) / 2) / Math.PI);
780                                 }
781                                 return radius * MathUtil.safeSqrt((theta - sin(2 * theta) / 2 + param * pow3(sin(theta))) / Math.PI);
782                         }
783                 },
784                 
785                 //              POLYNOMIAL("Smooth polynomial",
786                 //                              "A polynomial is fitted such that the nose cone profile is horizontal "+
787                 //                              "at the aft end of the transition.  The angle at the tip is defined by "+
788                 //                              "the shape parameter.",
789                 //                              "A polynomial is fitted such that the transition profile is horizontal "+
790                 //                              "at the aft end of the transition.  The angle at the fore end is defined "+
791                 //                              "by the shape parameter.") {
792                 //                      @Override
793                 //                      public boolean usesParameter() {
794                 //                              return true;
795                 //                      }
796                 //                      @Override
797                 //                      public double maxParameter() {
798                 //                              return 3.0;   //  Range 0...3
799                 //                      }
800                 //                      @Override
801                 //                      public double defaultParameter() {
802                 //                              return 0.0;
803                 //                      }
804                 //                      public double getRadius(double x, double radius, double length, double param) {
805                 //                              assert x >= 0;
806                 //                              assert x <= length;
807                 //                              assert radius >= 0;
808                 //                              assert param >= 0;
809                 //                              assert param <= 3;
810                 //                              // p(x) = (k-2)x^3 + (3-2k)x^2 + k*x
811                 //                              x = x/length;
812                 //                              return radius*((((param-2)*x + (3-2*param))*x + param)*x);
813                 //                      }
814                 //              }
815                 ;
816                 
817                 // Privete fields of the shapes
818                 private final String name;
819                 private final String transitionDesc;
820                 private final String noseconeDesc;
821                 private final boolean canClip;
822                 
823                 // Non-clippable constructor
824                 Shape(String name, String noseconeDesc, String transitionDesc) {
825                         this(name, noseconeDesc, transitionDesc, false);
826                 }
827                 
828                 // Clippable constructor
829                 Shape(String name, String noseconeDesc, String transitionDesc, boolean canClip) {
830                         this.name = name;
831                         this.canClip = canClip;
832                         this.noseconeDesc = noseconeDesc;
833                         this.transitionDesc = transitionDesc;
834                 }
835                 
836                 
837                 /**
838                  * Return the name of the transition shape name.
839                  */
840                 public String getName() {
841                         return name;
842                 }
843                 
844                 /**
845                  * Get a description of the Transition shape.
846                  */
847                 public String getTransitionDescription() {
848                         return transitionDesc;
849                 }
850                 
851                 /**
852                  * Get a description of the NoseCone shape.
853                  */
854                 public String getNoseConeDescription() {
855                         return noseconeDesc;
856                 }
857                 
858                 /**
859                  * Check whether the shape differs in clipped mode.  The clipping should be
860                  * enabled by default if possible.
861                  */
862                 public boolean isClippable() {
863                         return canClip;
864                 }
865                 
866                 /**
867                  * Return whether the shape uses the shape parameter.  (Default false.)
868                  */
869                 public boolean usesParameter() {
870                         return false;
871                 }
872                 
873                 /**
874                  * Return the minimum value of the shape parameter.  (Default 0.)
875                  */
876                 public double minParameter() {
877                         return 0.0;
878                 }
879                 
880                 /**
881                  * Return the maximum value of the shape parameter.  (Default 1.)
882                  */
883                 public double maxParameter() {
884                         return 1.0;
885                 }
886                 
887                 /**
888                  * Return the default value of the shape parameter.  (Default 0.)
889                  */
890                 public double defaultParameter() {
891                         return 0.0;
892                 }
893                 
894                 /**
895                  * Calculate the basic radius of a transition with the given radius, length and
896                  * shape parameter at the point x from the tip of the component.  It is assumed
897                  * that the fore radius if zero and the aft radius is <code>radius >= 0</code>.
898                  * Boattails are achieved by reversing the component.
899                  *
900                  * @param x      Position from the tip of the component.
901                  * @param radius Aft end radius >= 0.
902                  * @param length Length of the transition >= 0.
903                  * @param param  Valid shape parameter.
904                  * @return       The basic radius at the given position.
905                  */
906                 public abstract double getRadius(double x, double radius, double length, double param);
907                 
908                 
909                 /**
910                  * Returns the name of the shape (same as getName()).
911                  */
912                 @Override
913                 public String toString() {
914                         return name;
915                 }
916         }
917 }