I18 changes
[debian/openrocket] / src / net / sf / openrocket / rocketcomponent / FinSet.java
1 package net.sf.openrocket.rocketcomponent;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.Collection;
6 import java.util.List;
7
8 import net.sf.openrocket.util.Coordinate;
9 import net.sf.openrocket.util.MathUtil;
10 import net.sf.openrocket.util.Transformation;
11
12
13 public abstract class FinSet extends ExternalComponent {
14         
15         /**
16          * Maximum allowed cant of fins.
17          */
18         public static final double MAX_CANT = (15.0 * Math.PI / 180);
19         
20         
21         public enum CrossSection {
22                 SQUARE("Square", 1.00),
23                 ROUNDED("Rounded", 0.99),
24                 AIRFOIL("Airfoil", 0.85);
25                 
26                 private final String name;
27                 private final double volume;
28                 
29                 CrossSection(String name, double volume) {
30                         this.name = name;
31                         this.volume = volume;
32                 }
33                 
34                 public double getRelativeVolume() {
35                         return volume;
36                 }
37                 
38                 @Override
39                 public String toString() {
40                         return name;
41                 }
42         }
43         
44         public enum TabRelativePosition {
45                 FRONT("Root chord leading edge"),
46                 CENTER("Root chord midpoint"),
47                 END("Root chord trailing edge");
48                 
49                 private final String name;
50                 
51                 TabRelativePosition(String name) {
52                         this.name = name;
53                 }
54                 
55                 @Override
56                 public String toString() {
57                         return name;
58                 }
59         }
60         
61         /**
62          * Number of fins.
63          */
64         protected int fins = 3;
65         
66         /**
67          * Rotation about the x-axis by 2*PI/fins.
68          */
69         protected Transformation finRotation = Transformation.rotate_x(2 * Math.PI / fins);
70         
71         /**
72          * Rotation angle of the first fin.  Zero corresponds to the positive y-axis.
73          */
74         protected double rotation = 0;
75         
76         /**
77          * Rotation about the x-axis by angle this.rotation.
78          */
79         protected Transformation baseRotation = Transformation.rotate_x(rotation);
80         
81
82         /**
83          * Cant angle of fins.
84          */
85         protected double cantAngle = 0;
86         
87         /* Cached value: */
88         private Transformation cantRotation = null;
89         
90
91         /**
92          * Thickness of the fins.
93          */
94         protected double thickness = 0.003;
95         
96
97         /**
98          * The cross-section shape of the fins.
99          */
100         protected CrossSection crossSection = CrossSection.SQUARE;
101         
102
103         /*
104          * Fin tab properties.
105          */
106         private double tabHeight = 0;
107         private double tabLength = 0.05;
108         private double tabShift = 0;
109         private TabRelativePosition tabRelativePosition = TabRelativePosition.CENTER;
110         
111
112         // Cached fin area & CG.  Validity of both must be checked using finArea!
113         // Fin area does not include fin tabs, CG does.
114         private double finArea = -1;
115         private double finCGx = -1;
116         private double finCGy = -1;
117         
118         
119         /**
120          * New FinSet with given number of fins and given base rotation angle.
121          * Sets the component relative position to POSITION_RELATIVE_BOTTOM,
122          * i.e. fins are positioned at the bottom of the parent component.
123          */
124         public FinSet() {
125                 super(RocketComponent.Position.BOTTOM);
126         }
127         
128         
129
130         /**
131          * Return the number of fins in the set.
132          * @return The number of fins.
133          */
134         public int getFinCount() {
135                 return fins;
136         }
137         
138         /**
139          * Sets the number of fins in the set.
140          * @param n The number of fins, greater of equal to one.
141          */
142         public void setFinCount(int n) {
143                 if (fins == n)
144                         return;
145                 if (n < 1)
146                         n = 1;
147                 if (n > 8)
148                         n = 8;
149                 fins = n;
150                 finRotation = Transformation.rotate_x(2 * Math.PI / fins);
151                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
152         }
153         
154         public Transformation getFinRotationTransformation() {
155                 return finRotation;
156         }
157         
158         /**
159          * Gets the base rotation amount of the first fin.
160          * @return The base rotation amount.
161          */
162         public double getBaseRotation() {
163                 return rotation;
164         }
165         
166         /**
167          * Sets the base rotation amount of the first fin.
168          * @param r The base rotation amount.
169          */
170         public void setBaseRotation(double r) {
171                 r = MathUtil.reduce180(r);
172                 if (MathUtil.equals(r, rotation))
173                         return;
174                 rotation = r;
175                 baseRotation = Transformation.rotate_x(rotation);
176                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
177         }
178         
179         public Transformation getBaseRotationTransformation() {
180                 return baseRotation;
181         }
182         
183         
184
185         public double getCantAngle() {
186                 return cantAngle;
187         }
188         
189         public void setCantAngle(double cant) {
190                 cant = MathUtil.clamp(cant, -MAX_CANT, MAX_CANT);
191                 if (MathUtil.equals(cant, cantAngle))
192                         return;
193                 this.cantAngle = cant;
194                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
195         }
196         
197         
198         public Transformation getCantRotation() {
199                 if (cantRotation == null) {
200                         if (MathUtil.equals(cantAngle, 0)) {
201                                 cantRotation = Transformation.IDENTITY;
202                         } else {
203                                 Transformation t = new Transformation(-length / 2, 0, 0);
204                                 t = Transformation.rotate_y(cantAngle).applyTransformation(t);
205                                 t = new Transformation(length / 2, 0, 0).applyTransformation(t);
206                                 cantRotation = t;
207                         }
208                 }
209                 return cantRotation;
210         }
211         
212         
213
214         public double getThickness() {
215                 return thickness;
216         }
217         
218         public void setThickness(double r) {
219                 if (thickness == r)
220                         return;
221                 thickness = Math.max(r, 0);
222                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
223         }
224         
225         
226         public CrossSection getCrossSection() {
227                 return crossSection;
228         }
229         
230         public void setCrossSection(CrossSection cs) {
231                 if (crossSection == cs)
232                         return;
233                 crossSection = cs;
234                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
235         }
236         
237         
238
239
240
241         @Override
242         public void setRelativePosition(RocketComponent.Position position) {
243                 super.setRelativePosition(position);
244                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
245         }
246         
247         
248         @Override
249         public void setPositionValue(double value) {
250                 super.setPositionValue(value);
251                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
252         }
253         
254         
255
256
257         public double getTabHeight() {
258                 return tabHeight;
259         }
260         
261         public void setTabHeight(double height) {
262                 height = MathUtil.max(height, 0);
263                 if (MathUtil.equals(this.tabHeight, height))
264                         return;
265                 this.tabHeight = height;
266                 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
267         }
268         
269         
270         public double getTabLength() {
271                 return tabLength;
272         }
273         
274         public void setTabLength(double length) {
275                 length = MathUtil.max(length, 0);
276                 if (MathUtil.equals(this.tabLength, length))
277                         return;
278                 this.tabLength = length;
279                 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
280         }
281         
282         
283         public double getTabShift() {
284                 return tabShift;
285         }
286         
287         public void setTabShift(double shift) {
288                 this.tabShift = shift;
289                 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
290         }
291         
292         
293         public TabRelativePosition getTabRelativePosition() {
294                 return tabRelativePosition;
295         }
296         
297         public void setTabRelativePosition(TabRelativePosition position) {
298                 if (this.tabRelativePosition == position)
299                         return;
300                 
301
302                 double front = getTabFrontEdge();
303                 switch (position) {
304                 case FRONT:
305                         this.tabShift = front;
306                         break;
307                 
308                 case CENTER:
309                         this.tabShift = front + tabLength / 2 - getLength() / 2;
310                         break;
311                 
312                 case END:
313                         this.tabShift = front + tabLength - getLength();
314                         break;
315                 
316                 default:
317                         throw new IllegalArgumentException("position=" + position);
318                 }
319                 this.tabRelativePosition = position;
320                 
321                 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
322         }
323         
324         
325         /**
326          * Return the tab front edge position from the front of the fin.
327          */
328         public double getTabFrontEdge() {
329                 switch (this.tabRelativePosition) {
330                 case FRONT:
331                         return tabShift;
332                         
333                 case CENTER:
334                         return getLength() / 2 - tabLength / 2 + tabShift;
335                         
336                 case END:
337                         return getLength() - tabLength + tabShift;
338                         
339                 default:
340                         throw new IllegalStateException("tabRelativePosition=" + tabRelativePosition);
341                 }
342         }
343         
344         /**
345          * Return the tab trailing edge position *from the front of the fin*.
346          */
347         public double getTabTrailingEdge() {
348                 switch (this.tabRelativePosition) {
349                 case FRONT:
350                         return tabLength + tabShift;
351                 case CENTER:
352                         return getLength() / 2 + tabLength / 2 + tabShift;
353                         
354                 case END:
355                         return getLength() + tabShift;
356                         
357                 default:
358                         throw new IllegalStateException("tabRelativePosition=" + tabRelativePosition);
359                 }
360         }
361         
362         
363
364
365         ///////////  Calculation methods  ///////////
366         
367         /**
368          * Return the area of one side of one fin.  This does NOT include the area of
369          * the fin tab.
370          * 
371          * @return   the area of one side of one fin.
372          */
373         public double getFinArea() {
374                 if (finArea < 0)
375                         calculateAreaCG();
376                 
377                 return finArea;
378         }
379         
380         
381         /**
382          * Return the unweighted CG of a single fin.  The X-coordinate is relative to
383          * the root chord trailing edge and the Y-coordinate to the fin root chord.
384          * 
385          * @return  the unweighted CG coordinate of a single fin. 
386          */
387         public Coordinate getFinCG() {
388                 if (finArea < 0)
389                         calculateAreaCG();
390                 
391                 return new Coordinate(finCGx, finCGy, 0);
392         }
393         
394         
395
396         @Override
397         public double getComponentVolume() {
398                 return fins * (getFinArea() + tabHeight * tabLength) * thickness *
399                                 crossSection.getRelativeVolume();
400         }
401         
402         
403         @Override
404         public Coordinate getComponentCG() {
405                 if (finArea < 0)
406                         calculateAreaCG();
407                 
408                 double mass = getComponentMass(); // safe
409                 
410                 if (fins == 1) {
411                         return baseRotation.transform(
412                                         new Coordinate(finCGx, finCGy + getBodyRadius(), 0, mass));
413                 } else {
414                         return new Coordinate(finCGx, 0, 0, mass);
415                 }
416         }
417         
418         
419         private void calculateAreaCG() {
420                 Coordinate[] points = this.getFinPoints();
421                 finArea = 0;
422                 finCGx = 0;
423                 finCGy = 0;
424                 
425                 for (int i = 0; i < points.length - 1; i++) {
426                         final double x0 = points[i].x;
427                         final double x1 = points[i + 1].x;
428                         final double y0 = points[i].y;
429                         final double y1 = points[i + 1].y;
430                         
431                         double da = (y0 + y1) * (x1 - x0) / 2;
432                         finArea += da;
433                         if (Math.abs(y0 - y1) < 0.00001) {
434                                 finCGx += (x0 + x1) / 2 * da;
435                                 finCGy += y0 / 2 * da;
436                         } else {
437                                 finCGx += (x0 * (2 * y0 + y1) + x1 * (y0 + 2 * y1)) / (3 * (y0 + y1)) * da;
438                                 finCGy += (y1 + y0 * y0 / (y0 + y1)) / 3 * da;
439                         }
440                 }
441                 
442                 if (finArea < 0)
443                         finArea = 0;
444                 
445                 // Add effect of fin tabs to CG
446                 double tabArea = tabLength * tabHeight;
447                 if (!MathUtil.equals(tabArea, 0)) {
448                         
449                         double x = (getTabFrontEdge() + getTabTrailingEdge()) / 2;
450                         double y = -this.tabHeight / 2;
451                         
452                         finCGx += x * tabArea;
453                         finCGy += y * tabArea;
454                         
455                 }
456                 
457                 if ((finArea + tabArea) > 0) {
458                         finCGx /= (finArea + tabArea);
459                         finCGy /= (finArea + tabArea);
460                 } else {
461                         finCGx = (points[0].x + points[points.length - 1].x) / 2;
462                         finCGy = 0;
463                 }
464         }
465         
466         
467         /*
468          * Return an approximation of the longitudinal unitary inertia of the fin set.
469          * The process is the following:
470          * 
471          * 1. Approximate the fin with a rectangular fin
472          * 
473          * 2. The inertia of one fin is taken as the average of the moments of inertia
474          *    through its center perpendicular to the plane, and the inertia through
475          *    its center parallel to the plane
476          *    
477          * 3. If there are multiple fins, the inertia is shifted to the center of the fin
478          *    set and multiplied by the number of fins.
479          */
480         @Override
481         public double getLongitudinalUnitInertia() {
482                 double area = getFinArea();
483                 if (MathUtil.equals(area, 0))
484                         return 0;
485                 
486                 // Approximate fin with a rectangular fin
487                 // w2 and h2 are squares of the fin width and height
488                 double w = getLength();
489                 double h = getSpan();
490                 double w2, h2;
491                 
492                 if (MathUtil.equals(w * h, 0)) {
493                         w2 = area;
494                         h2 = area;
495                 } else {
496                         w2 = w * area / h;
497                         h2 = h * area / w;
498                 }
499                 
500                 double inertia = (h2 + 2 * w2) / 24;
501                 
502                 if (fins == 1)
503                         return inertia;
504                 
505                 double radius = getBodyRadius();
506                 
507                 return fins * (inertia + MathUtil.pow2(Math.sqrt(h2) + radius));
508         }
509         
510         
511         /*
512          * Return an approximation of the rotational unitary inertia of the fin set.
513          * The process is the following:
514          * 
515          * 1. Approximate the fin with a rectangular fin and calculate the inertia of the
516          *    rectangular approximate
517          *    
518          * 2. If there are multiple fins, shift the inertia center to the fin set center
519          *    and multiply with the number of fins.
520          */
521         @Override
522         public double getRotationalUnitInertia() {
523                 double area = getFinArea();
524                 if (MathUtil.equals(area, 0))
525                         return 0;
526                 
527                 // Approximate fin with a rectangular fin
528                 double w = getLength();
529                 double h = getSpan();
530                 
531                 if (MathUtil.equals(w * h, 0)) {
532                         h = Math.sqrt(area);
533                 } else {
534                         h = Math.sqrt(h * area / w);
535                 }
536                 
537                 if (fins == 1)
538                         return h * h / 12;
539                 
540                 double radius = getBodyRadius();
541                 
542                 return fins * (h * h / 12 + MathUtil.pow2(h / 2 + radius));
543         }
544         
545         
546         /**
547          * Adds the fin set's bounds to the collection.
548          */
549         @Override
550         public Collection<Coordinate> getComponentBounds() {
551                 List<Coordinate> bounds = new ArrayList<Coordinate>();
552                 double r = getBodyRadius();
553                 
554                 for (Coordinate point : getFinPoints()) {
555                         addFinBound(bounds, point.x, point.y + r);
556                 }
557                 
558                 return bounds;
559         }
560         
561         
562         /**
563          * Adds the 2d-coordinate bound (x,y) to the collection for both z-components and for
564          * all fin rotations.
565          */
566         private void addFinBound(Collection<Coordinate> set, double x, double y) {
567                 Coordinate c;
568                 int i;
569                 
570                 c = new Coordinate(x, y, thickness / 2);
571                 c = baseRotation.transform(c);
572                 set.add(c);
573                 for (i = 1; i < fins; i++) {
574                         c = finRotation.transform(c);
575                         set.add(c);
576                 }
577                 
578                 c = new Coordinate(x, y, -thickness / 2);
579                 c = baseRotation.transform(c);
580                 set.add(c);
581                 for (i = 1; i < fins; i++) {
582                         c = finRotation.transform(c);
583                         set.add(c);
584                 }
585         }
586         
587         
588
589         @Override
590         public void componentChanged(ComponentChangeEvent e) {
591                 if (e.isAerodynamicChange()) {
592                         finArea = -1;
593                         cantRotation = null;
594                 }
595         }
596         
597         
598         /**
599          * Return the radius of the BodyComponent the fin set is situated on.  Currently
600          * only supports SymmetricComponents and returns the radius at the starting point of the
601          * root chord.
602          *  
603          * @return  radius of the underlying BodyComponent or 0 if none exists.
604          */
605         public double getBodyRadius() {
606                 RocketComponent s;
607                 
608                 s = this.getParent();
609                 while (s != null) {
610                         if (s instanceof SymmetricComponent) {
611                                 double x = this.toRelative(new Coordinate(0, 0, 0), s)[0].x;
612                                 return ((SymmetricComponent) s).getRadius(x);
613                         }
614                         s = s.getParent();
615                 }
616                 return 0;
617         }
618         
619         @Override
620         public boolean allowsChildren() {
621                 return false;
622         }
623         
624         /**
625          * Allows nothing to be attached to a FinSet.
626          * 
627          * @return <code>false</code>
628          */
629         @Override
630         public boolean isCompatible(Class<? extends RocketComponent> type) {
631                 return false;
632         }
633         
634         
635
636
637         /**
638          * Return a list of coordinates defining the geometry of a single fin.  
639          * The coordinates are the XY-coordinates of points defining the shape of a single fin,
640          * where the origin is the leading root edge.  Therefore, the first point must be (0,0,0).
641          * All Z-coordinates must be zero, and the last coordinate must have Y=0.
642          * 
643          * @return  List of XY-coordinates.
644          */
645         public abstract Coordinate[] getFinPoints();
646         
647         
648         /**
649          * Return a list of coordinates defining the geometry of a single fin, including a
650          * possible fin tab.  The coordinates are the XY-coordinates of points defining the 
651          * shape of a single fin, where the origin is the leading root edge.  This implementation
652          * calls {@link #getFinPoints()} and adds the necessary points for the fin tab.
653          * The tab coordinates will have a negative y value.
654          * 
655          * @return  List of XY-coordinates.
656          */
657         public Coordinate[] getFinPointsWithTab() {
658                 Coordinate[] points = getFinPoints();
659                 
660                 if (MathUtil.equals(getTabHeight(), 0) ||
661                                 MathUtil.equals(getTabLength(), 0))
662                         return points;
663                 
664                 double x1 = getTabFrontEdge();
665                 double x2 = getTabTrailingEdge();
666                 double y = -getTabHeight();
667                 
668                 int n = points.length;
669                 points = Arrays.copyOf(points, points.length + 4);
670                 points[n] = new Coordinate(x2, 0);
671                 points[n + 1] = new Coordinate(x2, y);
672                 points[n + 2] = new Coordinate(x1, y);
673                 points[n + 3] = new Coordinate(x1, 0);
674                 return points;
675         }
676         
677         
678
679         /**
680          * Get the span of a single fin.  That is, the length from the root to the tip of the fin.
681          * @return  Span of a single fin.
682          */
683         public abstract double getSpan();
684         
685         
686         @Override
687         protected List<RocketComponent> copyFrom(RocketComponent c) {
688                 FinSet src = (FinSet) c;
689                 this.fins = src.fins;
690                 this.finRotation = src.finRotation;
691                 this.rotation = src.rotation;
692                 this.baseRotation = src.baseRotation;
693                 this.cantAngle = src.cantAngle;
694                 this.cantRotation = src.cantRotation;
695                 this.thickness = src.thickness;
696                 this.crossSection = src.crossSection;
697                 this.tabHeight = src.tabHeight;
698                 this.tabLength = src.tabLength;
699                 this.tabRelativePosition = src.tabRelativePosition;
700                 this.tabShift = src.tabShift;
701                 
702                 return super.copyFrom(c);
703         }
704 }