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