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