bug fixes and rocket optimization
[debian/openrocket] / src / net / sf / openrocket / rocketcomponent / FinSet.java
index fc18062c895e69090f405dbe76372f9ae5a77e13..e388b4a6f00579dc397b8e8b8c97a47a8e704732 100644 (file)
@@ -1,15 +1,21 @@
 package net.sf.openrocket.rocketcomponent;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.Coordinate;
 import net.sf.openrocket.util.MathUtil;
 import net.sf.openrocket.util.Transformation;
 
 
 public abstract class FinSet extends ExternalComponent {
+       private static final Translator trans = Application.getTranslator();
+       
+       // FIXME:  converting triangular fins to freeform fails
        
        /**
         * Maximum allowed cant of fins.
@@ -18,12 +24,16 @@ public abstract class FinSet extends ExternalComponent {
        
        
        public enum CrossSection {
-               SQUARE("Square",   1.00),
-               ROUNDED("Rounded", 0.99),
-               AIRFOIL("Airfoil", 0.85);
+               //// Square
+               SQUARE(trans.get("FinSet.CrossSection.SQUARE"), 1.00),
+               //// Rounded
+               ROUNDED(trans.get("FinSet.CrossSection.ROUNDED"), 0.99),
+               //// Airfoil
+               AIRFOIL(trans.get("FinSet.CrossSection.AIRFOIL"), 0.85);
                
                private final String name;
                private final double volume;
+               
                CrossSection(String name, double volume) {
                        this.name = name;
                        this.volume = volume;
@@ -32,6 +42,27 @@ public abstract class FinSet extends ExternalComponent {
                public double getRelativeVolume() {
                        return volume;
                }
+               
+               @Override
+               public String toString() {
+                       return name;
+               }
+       }
+       
+       public enum TabRelativePosition {
+               //// Root chord leading edge
+               FRONT(trans.get("FinSet.TabRelativePosition.FRONT")),
+               //// Root chord midpoint
+               CENTER(trans.get("FinSet.TabRelativePosition.CENTER")),
+               //// Root chord trailing edge
+               END(trans.get("FinSet.TabRelativePosition.END"));
+               
+               private final String name;
+               
+               TabRelativePosition(String name) {
+                       this.name = name;
+               }
+               
                @Override
                public String toString() {
                        return name;
@@ -46,7 +77,7 @@ public abstract class FinSet extends ExternalComponent {
        /**
         * Rotation about the x-axis by 2*PI/fins.
         */
-       protected Transformation finRotation = Transformation.rotate_x(2*Math.PI/fins);
+       protected Transformation finRotation = Transformation.rotate_x(2 * Math.PI / fins);
        
        /**
         * Rotation angle of the first fin.  Zero corresponds to the positive y-axis.
@@ -58,7 +89,7 @@ public abstract class FinSet extends ExternalComponent {
         */
        protected Transformation baseRotation = Transformation.rotate_x(rotation);
        
-       
+
        /**
         * Cant angle of fins.
         */
@@ -71,16 +102,26 @@ public abstract class FinSet extends ExternalComponent {
        /**
         * Thickness of the fins.
         */
-       protected double thickness = 0;
-       
+       protected double thickness = 0.003;
        
+
        /**
         * The cross-section shape of the fins.
         */
        protected CrossSection crossSection = CrossSection.SQUARE;
        
+
+       /*
+        * Fin tab properties.
+        */
+       private double tabHeight = 0;
+       private double tabLength = 0.05;
+       private double tabShift = 0;
+       private TabRelativePosition tabRelativePosition = TabRelativePosition.CENTER;
        
+
        // Cached fin area & CG.  Validity of both must be checked using finArea!
+       // Fin area does not include fin tabs, CG does.
        private double finArea = -1;
        private double finCGx = -1;
        private double finCGy = -1;
@@ -94,9 +135,9 @@ public abstract class FinSet extends ExternalComponent {
        public FinSet() {
                super(RocketComponent.Position.BOTTOM);
        }
-
        
        
+
        /**
         * Return the number of fins in the set.
         * @return The number of fins.
@@ -104,7 +145,7 @@ public abstract class FinSet extends ExternalComponent {
        public int getFinCount() {
                return fins;
        }
-
+       
        /**
         * Sets the number of fins in the set.
         * @param n The number of fins, greater of equal to one.
@@ -117,14 +158,14 @@ public abstract class FinSet extends ExternalComponent {
                if (n > 8)
                        n = 8;
                fins = n;
-               finRotation = Transformation.rotate_x(2*Math.PI/fins);
+               finRotation = Transformation.rotate_x(2 * Math.PI / fins);
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
        
        public Transformation getFinRotationTransformation() {
                return finRotation;
        }
-
+       
        /**
         * Gets the base rotation amount of the first fin.
         * @return The base rotation amount.
@@ -145,13 +186,13 @@ public abstract class FinSet extends ExternalComponent {
                baseRotation = Transformation.rotate_x(rotation);
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-
+       
        public Transformation getBaseRotationTransformation() {
                return baseRotation;
        }
        
        
-       
+
        public double getCantAngle() {
                return cantAngle;
        }
@@ -167,12 +208,12 @@ public abstract class FinSet extends ExternalComponent {
        
        public Transformation getCantRotation() {
                if (cantRotation == null) {
-                       if (MathUtil.equals(cantAngle,0)) {
+                       if (MathUtil.equals(cantAngle, 0)) {
                                cantRotation = Transformation.IDENTITY;
                        } else {
-                               Transformation t = new Transformation(-length/2,0,0);
+                               Transformation t = new Transformation(-length / 2, 0, 0);
                                t = Transformation.rotate_y(cantAngle).applyTransformation(t);
-                               t = new Transformation(length/2,0,0).applyTransformation(t);
+                               t = new Transformation(length / 2, 0, 0).applyTransformation(t);
                                cantRotation = t;
                        }
                }
@@ -188,7 +229,7 @@ public abstract class FinSet extends ExternalComponent {
        public void setThickness(double r) {
                if (thickness == r)
                        return;
-               thickness = Math.max(r,0);
+               thickness = Math.max(r, 0);
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
        
@@ -205,31 +246,138 @@ public abstract class FinSet extends ExternalComponent {
        }
        
        
-       
-       
+
+
 
        @Override
        public void setRelativePosition(RocketComponent.Position position) {
                super.setRelativePosition(position);
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
-
+       
        
        @Override
        public void setPositionValue(double value) {
                super.setPositionValue(value);
                fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
        }
+       
+       
 
+
+       public double getTabHeight() {
+               return tabHeight;
+       }
+       
+       public void setTabHeight(double height) {
+               height = MathUtil.max(height, 0);
+               if (MathUtil.equals(this.tabHeight, height))
+                       return;
+               this.tabHeight = height;
+               fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+       }
+       
        
+       public double getTabLength() {
+               return tabLength;
+       }
+       
+       public void setTabLength(double length) {
+               length = MathUtil.max(length, 0);
+               if (MathUtil.equals(this.tabLength, length))
+                       return;
+               this.tabLength = length;
+               fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+       }
+       
+       
+       public double getTabShift() {
+               return tabShift;
+       }
+       
+       public void setTabShift(double shift) {
+               this.tabShift = shift;
+               fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+       }
+       
+       
+       public TabRelativePosition getTabRelativePosition() {
+               return tabRelativePosition;
+       }
+       
+       public void setTabRelativePosition(TabRelativePosition position) {
+               if (this.tabRelativePosition == position)
+                       return;
+               
 
+               double front = getTabFrontEdge();
+               switch (position) {
+               case FRONT:
+                       this.tabShift = front;
+                       break;
+               
+               case CENTER:
+                       this.tabShift = front + tabLength / 2 - getLength() / 2;
+                       break;
+               
+               case END:
+                       this.tabShift = front + tabLength - getLength();
+                       break;
+               
+               default:
+                       throw new IllegalArgumentException("position=" + position);
+               }
+               this.tabRelativePosition = position;
+               
+               fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
+       }
        
        
+       /**
+        * Return the tab front edge position from the front of the fin.
+        */
+       public double getTabFrontEdge() {
+               switch (this.tabRelativePosition) {
+               case FRONT:
+                       return tabShift;
+                       
+               case CENTER:
+                       return getLength() / 2 - tabLength / 2 + tabShift;
+                       
+               case END:
+                       return getLength() - tabLength + tabShift;
+                       
+               default:
+                       throw new IllegalStateException("tabRelativePosition=" + tabRelativePosition);
+               }
+       }
        
+       /**
+        * Return the tab trailing edge position *from the front of the fin*.
+        */
+       public double getTabTrailingEdge() {
+               switch (this.tabRelativePosition) {
+               case FRONT:
+                       return tabLength + tabShift;
+               case CENTER:
+                       return getLength() / 2 + tabLength / 2 + tabShift;
+                       
+               case END:
+                       return getLength() + tabShift;
+                       
+               default:
+                       throw new IllegalStateException("tabRelativePosition=" + tabRelativePosition);
+               }
+       }
+       
+       
+
+
        ///////////  Calculation methods  ///////////
        
        /**
-        * Return the area of one side of one fin.
+        * Return the area of one side of one fin.  This does NOT include the area of
+        * the fin tab.
         * 
         * @return   the area of one side of one fin.
         */
@@ -240,6 +388,7 @@ public abstract class FinSet extends ExternalComponent {
                return finArea;
        }
        
+       
        /**
         * Return the unweighted CG of a single fin.  The X-coordinate is relative to
         * the root chord trailing edge and the Y-coordinate to the fin root chord.
@@ -250,32 +399,33 @@ public abstract class FinSet extends ExternalComponent {
                if (finArea < 0)
                        calculateAreaCG();
                
-               return new Coordinate(finCGx,finCGy,0);
+               return new Coordinate(finCGx, finCGy, 0);
        }
        
        
 
        @Override
        public double getComponentVolume() {
-               return fins * getFinArea() * thickness * crossSection.getRelativeVolume();
+               return fins * (getFinArea() + tabHeight * tabLength) * thickness *
+                               crossSection.getRelativeVolume();
        }
        
-
+       
        @Override
        public Coordinate getComponentCG() {
                if (finArea < 0)
                        calculateAreaCG();
                
-               double mass = getComponentMass();  // safe
+               double mass = getComponentMass(); // safe
                
                if (fins == 1) {
                        return baseRotation.transform(
-                                       new Coordinate(finCGx,finCGy + getBodyRadius(), 0, mass));
+                                       new Coordinate(finCGx, finCGy + getBodyRadius(), 0, mass));
                } else {
                        return new Coordinate(finCGx, 0, 0, mass);
                }
        }
-
+       
        
        private void calculateAreaCG() {
                Coordinate[] points = this.getFinPoints();
@@ -283,38 +433,50 @@ public abstract class FinSet extends ExternalComponent {
                finCGx = 0;
                finCGy = 0;
                
-               for (int i=0; i < points.length-1; i++) {
+               for (int i = 0; i < points.length - 1; i++) {
                        final double x0 = points[i].x;
-                       final double x1 = points[i+1].x;
+                       final double x1 = points[i + 1].x;
                        final double y0 = points[i].y;
-                       final double y1 = points[i+1].y;
+                       final double y1 = points[i + 1].y;
                        
-                       double da = (y0+y1)*(x1-x0) / 2;
+                       double da = (y0 + y1) * (x1 - x0) / 2;
                        finArea += da;
-                       if (Math.abs(y0-y1) < 0.00001) {
-                               finCGx += (x0+x1)/2 * da;
-                               finCGy += y0/2 * da;
+                       if (Math.abs(y0 - y1) < 0.00001) {
+                               finCGx += (x0 + x1) / 2 * da;
+                               finCGy += y0 / 2 * da;
                        } else {
-                               finCGx += (x0*(2*y0 + y1) + x1*(y0 + 2*y1)) / (3*(y0 + y1)) * da;
-                               finCGy += (y1 + y0*y0/(y0 + y1))/3 * da;
+                               finCGx += (x0 * (2 * y0 + y1) + x1 * (y0 + 2 * y1)) / (3 * (y0 + y1)) * da;
+                               finCGy += (y1 + y0 * y0 / (y0 + y1)) / 3 * da;
                        }
                }
                
                if (finArea < 0)
                        finArea = 0;
                
-               if (finArea > 0) {
-                       finCGx /= finArea;
-                       finCGy /= finArea;
+               // Add effect of fin tabs to CG
+               double tabArea = tabLength * tabHeight;
+               if (!MathUtil.equals(tabArea, 0)) {
+                       
+                       double x = (getTabFrontEdge() + getTabTrailingEdge()) / 2;
+                       double y = -this.tabHeight / 2;
+                       
+                       finCGx += x * tabArea;
+                       finCGy += y * tabArea;
+                       
+               }
+               
+               if ((finArea + tabArea) > 0) {
+                       finCGx /= (finArea + tabArea);
+                       finCGy /= (finArea + tabArea);
                } else {
-                       finCGx = (points[0].x + points[points.length-1].x)/2;
+                       finCGx = (points[0].x + points[points.length - 1].x) / 2;
                        finCGy = 0;
                }
        }
        
        
        /*
-        * Return an approximation of the longitudal unitary inertia of the fin set.
+        * Return an approximation of the longitudinal unitary inertia of the fin set.
         * The process is the following:
         * 
         * 1. Approximate the fin with a rectangular fin
@@ -327,7 +489,7 @@ public abstract class FinSet extends ExternalComponent {
         *    set and multiplied by the number of fins.
         */
        @Override
-       public double getLongitudalUnitInertia() {
+       public double getLongitudinalUnitInertia() {
                double area = getFinArea();
                if (MathUtil.equals(area, 0))
                        return 0;
@@ -336,23 +498,23 @@ public abstract class FinSet extends ExternalComponent {
                // w2 and h2 are squares of the fin width and height
                double w = getLength();
                double h = getSpan();
-               double w2,h2;
+               double w2, h2;
                
-               if (MathUtil.equals(w*h,0)) {
+               if (MathUtil.equals(w * h, 0)) {
                        w2 = area;
                        h2 = area;
                } else {
-                       w2 = w*area/h;
-                       h2 = h*area/w;
+                       w2 = w * area / h;
+                       h2 = h * area / w;
                }
                
-               double inertia = (h2 + 2*w2)/24;
+               double inertia = (h2 + 2 * w2) / 24;
                
                if (fins == 1)
                        return inertia;
                
                double radius = getBodyRadius();
-
+               
                return fins * (inertia + MathUtil.pow2(Math.sqrt(h2) + radius));
        }
        
@@ -377,18 +539,18 @@ public abstract class FinSet extends ExternalComponent {
                double w = getLength();
                double h = getSpan();
                
-               if (MathUtil.equals(w*h,0)) {
+               if (MathUtil.equals(w * h, 0)) {
                        h = Math.sqrt(area);
                } else {
-                       h = Math.sqrt(h*area/w);
+                       h = Math.sqrt(h * area / w);
                }
                
                if (fins == 1)
-                       return h*h / 12;
+                       return h * h / 12;
                
                double radius = getBodyRadius();
                
-               return fins * (h*h/12 + MathUtil.pow2(h/2 + radius));
+               return fins * (h * h / 12 + MathUtil.pow2(h / 2 + radius));
        }
        
        
@@ -400,13 +562,13 @@ public abstract class FinSet extends ExternalComponent {
                List<Coordinate> bounds = new ArrayList<Coordinate>();
                double r = getBodyRadius();
                
-               for (Coordinate point: getFinPoints()) {
+               for (Coordinate point : getFinPoints()) {
                        addFinBound(bounds, point.x, point.y + r);
                }
                
                return bounds;
        }
-
+       
        
        /**
         * Adds the 2d-coordinate bound (x,y) to the collection for both z-components and for
@@ -416,25 +578,25 @@ public abstract class FinSet extends ExternalComponent {
                Coordinate c;
                int i;
                
-               c = new Coordinate(x,y,thickness/2);
+               c = new Coordinate(x, y, thickness / 2);
                c = baseRotation.transform(c);
                set.add(c);
-               for (i=1; i<fins; i++) {
+               for (i = 1; i < fins; i++) {
                        c = finRotation.transform(c);
                        set.add(c);
                }
                
-               c = new Coordinate(x,y,-thickness/2);
+               c = new Coordinate(x, y, -thickness / 2);
                c = baseRotation.transform(c);
                set.add(c);
-               for (i=1; i<fins; i++) {
+               for (i = 1; i < fins; i++) {
                        c = finRotation.transform(c);
                        set.add(c);
                }
        }
-
        
        
+
        @Override
        public void componentChanged(ComponentChangeEvent e) {
                if (e.isAerodynamicChange()) {
@@ -455,16 +617,21 @@ public abstract class FinSet extends ExternalComponent {
                RocketComponent s;
                
                s = this.getParent();
-               while (s!=null) {
+               while (s != null) {
                        if (s instanceof SymmetricComponent) {
-                               double x = this.toRelative(new Coordinate(0,0,0), s)[0].x;
-                               return ((SymmetricComponent)s).getRadius(x);
+                               double x = this.toRelative(new Coordinate(0, 0, 0), s)[0].x;
+                               return ((SymmetricComponent) s).getRadius(x);
                        }
                        s = s.getParent();
                }
                return 0;
        }
        
+       @Override
+       public boolean allowsChildren() {
+               return false;
+       }
+       
        /**
         * Allows nothing to be attached to a FinSet.
         * 
@@ -476,8 +643,8 @@ public abstract class FinSet extends ExternalComponent {
        }
        
        
-       
-       
+
+
        /**
         * Return a list of coordinates defining the geometry of a single fin.  
         * The coordinates are the XY-coordinates of points defining the shape of a single fin,
@@ -488,6 +655,38 @@ public abstract class FinSet extends ExternalComponent {
         */
        public abstract Coordinate[] getFinPoints();
        
+       
+       /**
+        * Return a list of coordinates defining the geometry of a single fin, including a
+        * possible fin tab.  The coordinates are the XY-coordinates of points defining the 
+        * shape of a single fin, where the origin is the leading root edge.  This implementation
+        * calls {@link #getFinPoints()} and adds the necessary points for the fin tab.
+        * The tab coordinates will have a negative y value.
+        * 
+        * @return  List of XY-coordinates.
+        */
+       public Coordinate[] getFinPointsWithTab() {
+               Coordinate[] points = getFinPoints();
+               
+               if (MathUtil.equals(getTabHeight(), 0) ||
+                               MathUtil.equals(getTabLength(), 0))
+                       return points;
+               
+               double x1 = getTabFrontEdge();
+               double x2 = getTabTrailingEdge();
+               double y = -getTabHeight();
+               
+               int n = points.length;
+               points = Arrays.copyOf(points, points.length + 4);
+               points[n] = new Coordinate(x2, 0);
+               points[n + 1] = new Coordinate(x2, y);
+               points[n + 2] = new Coordinate(x1, y);
+               points[n + 3] = new Coordinate(x1, 0);
+               return points;
+       }
+       
+       
+
        /**
         * Get the span of a single fin.  That is, the length from the root to the tip of the fin.
         * @return  Span of a single fin.
@@ -496,10 +695,8 @@ public abstract class FinSet extends ExternalComponent {
        
        
        @Override
-       protected void copyFrom(RocketComponent c) {
-               super.copyFrom(c);
-               
-               FinSet src = (FinSet)c;
+       protected List<RocketComponent> copyFrom(RocketComponent c) {
+               FinSet src = (FinSet) c;
                this.fins = src.fins;
                this.finRotation = src.finRotation;
                this.rotation = src.rotation;
@@ -508,5 +705,11 @@ public abstract class FinSet extends ExternalComponent {
                this.cantRotation = src.cantRotation;
                this.thickness = src.thickness;
                this.crossSection = src.crossSection;
+               this.tabHeight = src.tabHeight;
+               this.tabLength = src.tabLength;
+               this.tabRelativePosition = src.tabRelativePosition;
+               this.tabShift = src.tabShift;
+               
+               return super.copyFrom(c);
        }
 }