1 package net.sf.openrocket.rocketcomponent;
3 import java.util.ArrayList;
4 import java.util.Collection;
7 import net.sf.openrocket.util.Coordinate;
8 import net.sf.openrocket.util.MathUtil;
9 import net.sf.openrocket.util.Transformation;
12 public abstract class FinSet extends ExternalComponent {
15 * Maximum allowed cant of fins.
17 public static final double MAX_CANT = (15.0 * Math.PI / 180);
20 public enum CrossSection {
21 SQUARE("Square", 1.00),
22 ROUNDED("Rounded", 0.99),
23 AIRFOIL("Airfoil", 0.85);
25 private final String name;
26 private final double volume;
27 CrossSection(String name, double volume) {
32 public double getRelativeVolume() {
36 public String toString() {
44 protected int fins = 3;
47 * Rotation about the x-axis by 2*PI/fins.
49 protected Transformation finRotation = Transformation.rotate_x(2*Math.PI/fins);
52 * Rotation angle of the first fin. Zero corresponds to the positive y-axis.
54 protected double rotation = 0;
57 * Rotation about the x-axis by angle this.rotation.
59 protected Transformation baseRotation = Transformation.rotate_x(rotation);
65 protected double cantAngle = 0;
68 private Transformation cantRotation = null;
72 * Thickness of the fins.
74 protected double thickness = 0.003;
78 * The cross-section shape of the fins.
80 protected CrossSection crossSection = CrossSection.SQUARE;
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;
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.
95 super(RocketComponent.Position.BOTTOM);
101 * Return the number of fins in the set.
102 * @return The number of fins.
104 public int getFinCount() {
109 * Sets the number of fins in the set.
110 * @param n The number of fins, greater of equal to one.
112 public void setFinCount(int n) {
120 finRotation = Transformation.rotate_x(2*Math.PI/fins);
121 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
124 public Transformation getFinRotationTransformation() {
129 * Gets the base rotation amount of the first fin.
130 * @return The base rotation amount.
132 public double getBaseRotation() {
137 * Sets the base rotation amount of the first fin.
138 * @param r The base rotation amount.
140 public void setBaseRotation(double r) {
141 r = MathUtil.reduce180(r);
142 if (MathUtil.equals(r, rotation))
145 baseRotation = Transformation.rotate_x(rotation);
146 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
149 public Transformation getBaseRotationTransformation() {
155 public double getCantAngle() {
159 public void setCantAngle(double cant) {
160 cant = MathUtil.clamp(cant, -MAX_CANT, MAX_CANT);
161 if (MathUtil.equals(cant, cantAngle))
163 this.cantAngle = cant;
164 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
168 public Transformation getCantRotation() {
169 if (cantRotation == null) {
170 if (MathUtil.equals(cantAngle,0)) {
171 cantRotation = Transformation.IDENTITY;
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);
184 public double getThickness() {
188 public void setThickness(double r) {
191 thickness = Math.max(r,0);
192 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
196 public CrossSection getCrossSection() {
200 public void setCrossSection(CrossSection cs) {
201 if (crossSection == cs)
204 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
212 public void setRelativePosition(RocketComponent.Position position) {
213 super.setRelativePosition(position);
214 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
219 public void setPositionValue(double value) {
220 super.setPositionValue(value);
221 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
229 /////////// Calculation methods ///////////
232 * Return the area of one side of one fin.
234 * @return the area of one side of one fin.
236 public double getFinArea() {
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.
247 * @return the unweighted CG coordinate of a single fin.
249 public Coordinate getFinCG() {
253 return new Coordinate(finCGx,finCGy,0);
259 public double getComponentVolume() {
260 return fins * getFinArea() * thickness * crossSection.getRelativeVolume();
265 public Coordinate getComponentCG() {
269 double mass = getComponentMass(); // safe
272 return baseRotation.transform(
273 new Coordinate(finCGx,finCGy + getBodyRadius(), 0, mass));
275 return new Coordinate(finCGx, 0, 0, mass);
280 private void calculateAreaCG() {
281 Coordinate[] points = this.getFinPoints();
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;
292 double da = (y0+y1)*(x1-x0) / 2;
294 if (Math.abs(y0-y1) < 0.00001) {
295 finCGx += (x0+x1)/2 * da;
298 finCGx += (x0*(2*y0 + y1) + x1*(y0 + 2*y1)) / (3*(y0 + y1)) * da;
299 finCGy += (y1 + y0*y0/(y0 + y1))/3 * da;
310 finCGx = (points[0].x + points[points.length-1].x)/2;
317 * Return an approximation of the longitudal unitary inertia of the fin set.
318 * The process is the following:
320 * 1. Approximate the fin with a rectangular fin
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
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.
330 public double getLongitudalUnitInertia() {
331 double area = getFinArea();
332 if (MathUtil.equals(area, 0))
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();
341 if (MathUtil.equals(w*h,0)) {
349 double inertia = (h2 + 2*w2)/24;
354 double radius = getBodyRadius();
356 return fins * (inertia + MathUtil.pow2(Math.sqrt(h2) + radius));
361 * Return an approximation of the rotational unitary inertia of the fin set.
362 * The process is the following:
364 * 1. Approximate the fin with a rectangular fin and calculate the inertia of the
365 * rectangular approximate
367 * 2. If there are multiple fins, shift the inertia center to the fin set center
368 * and multiply with the number of fins.
371 public double getRotationalUnitInertia() {
372 double area = getFinArea();
373 if (MathUtil.equals(area, 0))
376 // Approximate fin with a rectangular fin
377 double w = getLength();
378 double h = getSpan();
380 if (MathUtil.equals(w*h,0)) {
383 h = Math.sqrt(h*area/w);
389 double radius = getBodyRadius();
391 return fins * (h*h/12 + MathUtil.pow2(h/2 + radius));
396 * Adds the fin set's bounds to the collection.
399 public Collection<Coordinate> getComponentBounds() {
400 List<Coordinate> bounds = new ArrayList<Coordinate>();
401 double r = getBodyRadius();
403 for (Coordinate point: getFinPoints()) {
404 addFinBound(bounds, point.x, point.y + r);
412 * Adds the 2d-coordinate bound (x,y) to the collection for both z-components and for
415 private void addFinBound(Collection<Coordinate> set, double x, double y) {
419 c = new Coordinate(x,y,thickness/2);
420 c = baseRotation.transform(c);
422 for (i=1; i<fins; i++) {
423 c = finRotation.transform(c);
427 c = new Coordinate(x,y,-thickness/2);
428 c = baseRotation.transform(c);
430 for (i=1; i<fins; i++) {
431 c = finRotation.transform(c);
439 public void componentChanged(ComponentChangeEvent e) {
440 if (e.isAerodynamicChange()) {
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
452 * @return radius of the underlying BodyComponent or 0 if none exists.
454 public double getBodyRadius() {
457 s = this.getParent();
459 if (s instanceof SymmetricComponent) {
460 double x = this.toRelative(new Coordinate(0,0,0), s)[0].x;
461 return ((SymmetricComponent)s).getRadius(x);
469 * Allows nothing to be attached to a FinSet.
471 * @return <code>false</code>
474 public boolean isCompatible(Class<? extends RocketComponent> type) {
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.
487 * @return List of XY-coordinates.
489 public abstract Coordinate[] getFinPoints();
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.
495 public abstract double getSpan();
499 protected void copyFrom(RocketComponent c) {
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;