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