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