Numerous bug fixes and updates
[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.Collection;
5 import java.util.List;
6
7 import net.sf.openrocket.util.Coordinate;
8 import net.sf.openrocket.util.MathUtil;
9 import net.sf.openrocket.util.Transformation;
10
11
12 public abstract class FinSet extends ExternalComponent {
13         
14         /**
15          * Maximum allowed cant of fins.
16          */
17         public static final double MAX_CANT = (15.0 * Math.PI / 180);
18         
19         
20         public enum CrossSection {
21                 SQUARE("Square",   1.00),
22                 ROUNDED("Rounded", 0.99),
23                 AIRFOIL("Airfoil", 0.85);
24                 
25                 private final String name;
26                 private final double volume;
27                 CrossSection(String name, double volume) {
28                         this.name = name;
29                         this.volume = volume;
30                 }
31                 
32                 public double getRelativeVolume() {
33                         return volume;
34                 }
35                 @Override
36                 public String toString() {
37                         return name;
38                 }
39         }
40         
41         /**
42          * Number of fins.
43          */
44         protected int fins = 3;
45         
46         /**
47          * Rotation about the x-axis by 2*PI/fins.
48          */
49         protected Transformation finRotation = Transformation.rotate_x(2*Math.PI/fins);
50         
51         /**
52          * Rotation angle of the first fin.  Zero corresponds to the positive y-axis.
53          */
54         protected double rotation = 0;
55         
56         /**
57          * Rotation about the x-axis by angle this.rotation.
58          */
59         protected Transformation baseRotation = Transformation.rotate_x(rotation);
60         
61         
62         /**
63          * Cant angle of fins.
64          */
65         protected double cantAngle = 0;
66         
67         /* Cached value: */
68         private Transformation cantRotation = null;
69         
70
71         /**
72          * Thickness of the fins.
73          */
74         protected double thickness = 0.003;
75         
76         
77         /**
78          * The cross-section shape of the fins.
79          */
80         protected CrossSection crossSection = CrossSection.SQUARE;
81         
82         
83         // Cached fin area & CG.  Validity of both must be checked using finArea!
84         private double finArea = -1;
85         private double finCGx = -1;
86         private double finCGy = -1;
87         
88         
89         /**
90          * New FinSet with given number of fins and given base rotation angle.
91          * Sets the component relative position to POSITION_RELATIVE_BOTTOM,
92          * i.e. fins are positioned at the bottom of the parent component.
93          */
94         public FinSet() {
95                 super(RocketComponent.Position.BOTTOM);
96         }
97
98         
99         
100         /**
101          * Return the number of fins in the set.
102          * @return The number of fins.
103          */
104         public int getFinCount() {
105                 return fins;
106         }
107
108         /**
109          * Sets the number of fins in the set.
110          * @param n The number of fins, greater of equal to one.
111          */
112         public void setFinCount(int n) {
113                 if (fins == n)
114                         return;
115                 if (n < 1)
116                         n = 1;
117                 if (n > 8)
118                         n = 8;
119                 fins = n;
120                 finRotation = Transformation.rotate_x(2*Math.PI/fins);
121                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
122         }
123         
124         public Transformation getFinRotationTransformation() {
125                 return finRotation;
126         }
127
128         /**
129          * Gets the base rotation amount of the first fin.
130          * @return The base rotation amount.
131          */
132         public double getBaseRotation() {
133                 return rotation;
134         }
135         
136         /**
137          * Sets the base rotation amount of the first fin.
138          * @param r The base rotation amount.
139          */
140         public void setBaseRotation(double r) {
141                 r = MathUtil.reduce180(r);
142                 if (MathUtil.equals(r, rotation))
143                         return;
144                 rotation = r;
145                 baseRotation = Transformation.rotate_x(rotation);
146                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
147         }
148
149         public Transformation getBaseRotationTransformation() {
150                 return baseRotation;
151         }
152         
153         
154         
155         public double getCantAngle() {
156                 return cantAngle;
157         }
158         
159         public void setCantAngle(double cant) {
160                 cant = MathUtil.clamp(cant, -MAX_CANT, MAX_CANT);
161                 if (MathUtil.equals(cant, cantAngle))
162                         return;
163                 this.cantAngle = cant;
164                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
165         }
166         
167         
168         public Transformation getCantRotation() {
169                 if (cantRotation == null) {
170                         if (MathUtil.equals(cantAngle,0)) {
171                                 cantRotation = Transformation.IDENTITY;
172                         } else {
173                                 Transformation t = new Transformation(-length/2,0,0);
174                                 t = Transformation.rotate_y(cantAngle).applyTransformation(t);
175                                 t = new Transformation(length/2,0,0).applyTransformation(t);
176                                 cantRotation = t;
177                         }
178                 }
179                 return cantRotation;
180         }
181         
182         
183
184         public double getThickness() {
185                 return thickness;
186         }
187         
188         public void setThickness(double r) {
189                 if (thickness == r)
190                         return;
191                 thickness = Math.max(r,0);
192                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
193         }
194         
195         
196         public CrossSection getCrossSection() {
197                 return crossSection;
198         }
199         
200         public void setCrossSection(CrossSection cs) {
201                 if (crossSection == cs)
202                         return;
203                 crossSection = cs;
204                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
205         }
206         
207         
208         
209         
210
211         @Override
212         public void setRelativePosition(RocketComponent.Position position) {
213                 super.setRelativePosition(position);
214                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
215         }
216
217         
218         @Override
219         public void setPositionValue(double value) {
220                 super.setPositionValue(value);
221                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
222         }
223
224         
225
226         
227         
228         
229         ///////////  Calculation methods  ///////////
230         
231         /**
232          * Return the area of one side of one fin.
233          * 
234          * @return   the area of one side of one fin.
235          */
236         public double getFinArea() {
237                 if (finArea < 0)
238                         calculateAreaCG();
239                 
240                 return finArea;
241         }
242         
243         /**
244          * Return the unweighted CG of a single fin.  The X-coordinate is relative to
245          * the root chord trailing edge and the Y-coordinate to the fin root chord.
246          * 
247          * @return  the unweighted CG coordinate of a single fin. 
248          */
249         public Coordinate getFinCG() {
250                 if (finArea < 0)
251                         calculateAreaCG();
252                 
253                 return new Coordinate(finCGx,finCGy,0);
254         }
255         
256         
257
258         @Override
259         public double getComponentVolume() {
260                 return fins * getFinArea() * thickness * crossSection.getRelativeVolume();
261         }
262         
263
264         @Override
265         public Coordinate getComponentCG() {
266                 if (finArea < 0)
267                         calculateAreaCG();
268                 
269                 double mass = getComponentMass();  // safe
270                 
271                 if (fins == 1) {
272                         return baseRotation.transform(
273                                         new Coordinate(finCGx,finCGy + getBodyRadius(), 0, mass));
274                 } else {
275                         return new Coordinate(finCGx, 0, 0, mass);
276                 }
277         }
278
279         
280         private void calculateAreaCG() {
281                 Coordinate[] points = this.getFinPoints();
282                 finArea = 0;
283                 finCGx = 0;
284                 finCGy = 0;
285                 
286                 for (int i=0; i < points.length-1; i++) {
287                         final double x0 = points[i].x;
288                         final double x1 = points[i+1].x;
289                         final double y0 = points[i].y;
290                         final double y1 = points[i+1].y;
291                         
292                         double da = (y0+y1)*(x1-x0) / 2;
293                         finArea += da;
294                         if (Math.abs(y0-y1) < 0.00001) {
295                                 finCGx += (x0+x1)/2 * da;
296                                 finCGy += y0/2 * da;
297                         } else {
298                                 finCGx += (x0*(2*y0 + y1) + x1*(y0 + 2*y1)) / (3*(y0 + y1)) * da;
299                                 finCGy += (y1 + y0*y0/(y0 + y1))/3 * da;
300                         }
301                 }
302                 
303                 if (finArea < 0)
304                         finArea = 0;
305                 
306                 if (finArea > 0) {
307                         finCGx /= finArea;
308                         finCGy /= finArea;
309                 } else {
310                         finCGx = (points[0].x + points[points.length-1].x)/2;
311                         finCGy = 0;
312                 }
313         }
314         
315         
316         /*
317          * Return an approximation of the longitudal unitary inertia of the fin set.
318          * The process is the following:
319          * 
320          * 1. Approximate the fin with a rectangular fin
321          * 
322          * 2. The inertia of one fin is taken as the average of the moments of inertia
323          *    through its center perpendicular to the plane, and the inertia through
324          *    its center parallel to the plane
325          *    
326          * 3. If there are multiple fins, the inertia is shifted to the center of the fin
327          *    set and multiplied by the number of fins.
328          */
329         @Override
330         public double getLongitudalUnitInertia() {
331                 double area = getFinArea();
332                 if (MathUtil.equals(area, 0))
333                         return 0;
334                 
335                 // Approximate fin with a rectangular fin
336                 // w2 and h2 are squares of the fin width and height
337                 double w = getLength();
338                 double h = getSpan();
339                 double w2,h2;
340                 
341                 if (MathUtil.equals(w*h,0)) {
342                         w2 = area;
343                         h2 = area;
344                 } else {
345                         w2 = w*area/h;
346                         h2 = h*area/w;
347                 }
348                 
349                 double inertia = (h2 + 2*w2)/24;
350                 
351                 if (fins == 1)
352                         return inertia;
353                 
354                 double radius = getBodyRadius();
355
356                 return fins * (inertia + MathUtil.pow2(Math.sqrt(h2) + radius));
357         }
358         
359         
360         /*
361          * Return an approximation of the rotational unitary inertia of the fin set.
362          * The process is the following:
363          * 
364          * 1. Approximate the fin with a rectangular fin and calculate the inertia of the
365          *    rectangular approximate
366          *    
367          * 2. If there are multiple fins, shift the inertia center to the fin set center
368          *    and multiply with the number of fins.
369          */
370         @Override
371         public double getRotationalUnitInertia() {
372                 double area = getFinArea();
373                 if (MathUtil.equals(area, 0))
374                         return 0;
375                 
376                 // Approximate fin with a rectangular fin
377                 double w = getLength();
378                 double h = getSpan();
379                 
380                 if (MathUtil.equals(w*h,0)) {
381                         h = Math.sqrt(area);
382                 } else {
383                         h = Math.sqrt(h*area/w);
384                 }
385                 
386                 if (fins == 1)
387                         return h*h / 12;
388                 
389                 double radius = getBodyRadius();
390                 
391                 return fins * (h*h/12 + MathUtil.pow2(h/2 + radius));
392         }
393         
394         
395         /**
396          * Adds the fin set's bounds to the collection.
397          */
398         @Override
399         public Collection<Coordinate> getComponentBounds() {
400                 List<Coordinate> bounds = new ArrayList<Coordinate>();
401                 double r = getBodyRadius();
402                 
403                 for (Coordinate point: getFinPoints()) {
404                         addFinBound(bounds, point.x, point.y + r);
405                 }
406                 
407                 return bounds;
408         }
409
410         
411         /**
412          * Adds the 2d-coordinate bound (x,y) to the collection for both z-components and for
413          * all fin rotations.
414          */
415         private void addFinBound(Collection<Coordinate> set, double x, double y) {
416                 Coordinate c;
417                 int i;
418                 
419                 c = new Coordinate(x,y,thickness/2);
420                 c = baseRotation.transform(c);
421                 set.add(c);
422                 for (i=1; i<fins; i++) {
423                         c = finRotation.transform(c);
424                         set.add(c);
425                 }
426                 
427                 c = new Coordinate(x,y,-thickness/2);
428                 c = baseRotation.transform(c);
429                 set.add(c);
430                 for (i=1; i<fins; i++) {
431                         c = finRotation.transform(c);
432                         set.add(c);
433                 }
434         }
435
436         
437         
438         @Override
439         public void componentChanged(ComponentChangeEvent e) {
440                 if (e.isAerodynamicChange()) {
441                         finArea = -1;
442                         cantRotation = null;
443                 }
444         }
445         
446         
447         /**
448          * Return the radius of the BodyComponent the fin set is situated on.  Currently
449          * only supports SymmetricComponents and returns the radius at the starting point of the
450          * root chord.
451          *  
452          * @return  radius of the underlying BodyComponent or 0 if none exists.
453          */
454         public double getBodyRadius() {
455                 RocketComponent s;
456                 
457                 s = this.getParent();
458                 while (s!=null) {
459                         if (s instanceof SymmetricComponent) {
460                                 double x = this.toRelative(new Coordinate(0,0,0), s)[0].x;
461                                 return ((SymmetricComponent)s).getRadius(x);
462                         }
463                         s = s.getParent();
464                 }
465                 return 0;
466         }
467         
468         /**
469          * Allows nothing to be attached to a FinSet.
470          * 
471          * @return <code>false</code>
472          */
473         @Override
474         public boolean isCompatible(Class<? extends RocketComponent> type) {
475                 return false;
476         }
477         
478         
479         
480         
481         /**
482          * Return a list of coordinates defining the geometry of a single fin.  
483          * The coordinates are the XY-coordinates of points defining the shape of a single fin,
484          * where the origin is the leading root edge.  Therefore, the first point must be (0,0,0).
485          * All Z-coordinates must be zero, and the last coordinate must have Y=0.
486          * 
487          * @return  List of XY-coordinates.
488          */
489         public abstract Coordinate[] getFinPoints();
490         
491         /**
492          * Get the span of a single fin.  That is, the length from the root to the tip of the fin.
493          * @return  Span of a single fin.
494          */
495         public abstract double getSpan();
496         
497         
498         @Override
499         protected void copyFrom(RocketComponent c) {
500                 super.copyFrom(c);
501                 
502                 FinSet src = (FinSet)c;
503                 this.fins = src.fins;
504                 this.finRotation = src.finRotation;
505                 this.rotation = src.rotation;
506                 this.baseRotation = src.baseRotation;
507                 this.cantAngle = src.cantAngle;
508                 this.cantRotation = src.cantRotation;
509                 this.thickness = src.thickness;
510                 this.crossSection = src.crossSection;
511         }
512 }