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