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