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