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