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