package net.sf.openrocket.rocketcomponent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.preset.ComponentPreset;
import net.sf.openrocket.preset.ComponentPreset.Type;
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 class TubeFinSet extends ExternalComponent {
private static final Translator trans = Application.getTranslator();
private final static double DEFAULT_RADIUS = 0.025;
private boolean autoRadius = true; // Radius chosen automatically based on parent component
private double outerRadius = DEFAULT_RADIUS;
protected double thickness = 0.002;
protected int fins = 6;
/**
* Rotation angle of the first fin. Zero corresponds to the positive y-axis.
*/
protected double rotation = 0;
/**
* Rotation about the x-axis by angle this.rotation.
*/
protected Transformation baseRotation = Transformation.rotate_x(rotation);
/**
* Rotation about the x-axis by 2*PI/fins.
*/
protected Transformation finRotation = Transformation.rotate_x(2 * Math.PI / fins);
/**
* New FinSet with given number of fins and given base rotation angle.
* Sets the component relative position to POSITION_RELATIVE_BOTTOM,
* i.e. fins are positioned at the bottom of the parent component.
*/
public TubeFinSet() {
super(RocketComponent.Position.BOTTOM);
length = 0.10;
}
public void setLength(double length) {
if (MathUtil.equals(this.length, length))
return;
this.length = length;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
public boolean isOuterRadiusAutomatic() {
return autoRadius;
}
/**
* Return the outer radius of the body tube.
*
* @return the outside radius of the tube
*/
public double getOuterRadius() {
if (autoRadius) {
// Return auto radius from front or rear
double r = -1;
RocketComponent c = this.getParent();
if (c != null) {
if (c instanceof SymmetricComponent) {
r = ((SymmetricComponent) c).getAftRadius();
}
}
if (r < 0) {
r = DEFAULT_RADIUS;
} else {
// for 5,6, and 8 fins, adjust the diameter to provide touching fins.
switch (fins) {
case 5:
r *= 1.43; // sin(36) / (1- sin(36), 36 = 360/5/2
break;
case 7:
r *= 0.77; // sin(25.7) / (1- sin(25.7)
break;
case 8:
r *= 0.62; // sin(22.5) / (1- sin(22.5)
break;
}
}
return r;
}
return outerRadius;
}
/**
* Set the outer radius of the body tube. If the radius is less than the wall thickness,
* the wall thickness is decreased accordingly of the value of the radius.
* This method sets the automatic radius off.
*
* @param radius the outside radius in standard units
*/
public void setOuterRadius(double radius) {
if ((this.outerRadius == radius) && (autoRadius == false))
return;
this.autoRadius = false;
this.outerRadius = Math.max(radius, 0);
if (this.thickness > this.outerRadius)
this.thickness = this.outerRadius;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
clearPreset();
}
/**
* Sets whether the radius is selected automatically or not.
*/
public void setOuterRadiusAutomatic(boolean auto) {
if (autoRadius == auto)
return;
autoRadius = auto;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
clearPreset();
}
public double getInnerRadius() {
return Math.max(getOuterRadius() - thickness, 0);
}
public void setInnerRadius(double r) {
setThickness(getOuterRadius() - r);
}
/**
* Return the component wall thickness.
*/
public double getThickness() {
return Math.min(thickness, getOuterRadius());
}
/**
* Set the component wall thickness. Values greater than the maximum radius are not
* allowed, and will result in setting the thickness to the maximum radius.
*/
public void setThickness(double thickness) {
if ((this.thickness == thickness))
return;
this.thickness = MathUtil.clamp(thickness, 0, getOuterRadius());
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
clearPreset();
}
/**
* Return the number of fins in the set.
* @return The number of fins.
*/
public int getFinCount() {
return fins;
}
/**
* Sets the number of fins in the set.
* @param n The number of fins, greater of equal to one.
*/
public void setFinCount(int n) {
if (fins == n)
return;
if (n < 1)
n = 1;
if (n > 8)
n = 8;
fins = n;
finRotation = Transformation.rotate_x(2 * Math.PI / fins);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
/**
* Gets the base rotation amount of the first fin.
* @return The base rotation amount.
*/
public double getBaseRotation() {
return rotation;
}
public double getFinRotation() {
return 2 * Math.PI / fins;
}
/**
* Sets the base rotation amount of the first fin.
* @param r The base rotation amount.
*/
public void setBaseRotation(double r) {
r = MathUtil.reduce180(r);
if (MathUtil.equals(r, rotation))
return;
rotation = r;
baseRotation = Transformation.rotate_x(rotation);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
public Transformation getBaseRotationTransformation() {
return baseRotation;
}
public Transformation getFinRotationTransformation() {
return finRotation;
}
@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);
}
@Override
public double getComponentVolume() {
double or = getOuterRadius();
double ir = getInnerRadius();
double volume = or * or - ir * ir;
volume *= Math.PI;
volume *= length;
volume *= fins;
return volume;
}
@Override
public String getComponentName() {
//// Tube Fin Set
return trans.get("TubeFinSet.TubeFinSet");
}
@Override
public Coordinate getComponentCG() {
double mass = getComponentMass(); // safe
double halflength = length / 2;
if (fins == 1) {
return baseRotation.transform(new Coordinate(halflength, getOuterRadius() + getBodyRadius(), 0, mass));
} else {
return baseRotation.transform(new Coordinate(halflength, 0, 0, mass));
}
}
@Override
public double getLongitudinalUnitInertia() {
// Logitudinal Unit Inertia for a single tube fin.
// 1/12 * (3 * (r1^2 + r2^2) + h^2)
final double inertia = (3 * (MathUtil.pow2(getOuterRadius()) + MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getLength())) / 12;
if (fins == 1) {
return inertia;
}
// translate each to the center of mass.
final double hypot = getOuterRadius() + getBodyRadius();
final double finrotation = 2 * Math.PI / fins;
double angularoffset = 0.0;
double totalInertia = 0.0;
for (int i = 0; i < fins; i++) {
double offset = hypot * Math.cos(angularoffset);
totalInertia += inertia + MathUtil.pow2(offset);
angularoffset += finrotation;
}
return totalInertia;
}
@Override
public double getRotationalUnitInertia() {
// The rotational inertia of a single fin about its center.
// 1/2 * (r1^2 + r2^2)
double icentermass = (MathUtil.pow2(getInnerRadius()) + MathUtil.pow2(getOuterRadius())) / 2;
if (fins == 1) {
return icentermass;
} else {
// Use parallel axis rule and multiply by number of fins.
return fins * (icentermass + MathUtil.pow2(getOuterRadius()) + getBodyRadius());
}
}
@Override
public boolean allowsChildren() {
return false;
}
@Override
public Type getPresetType() {
return ComponentPreset.Type.BODY_TUBE;
}
@Override
public boolean isCompatible(Class<? extends RocketComponent> type) {
// TODO Auto-generated method stub
return false;
}
@Override
public Collection<Coordinate> getComponentBounds() {
List<Coordinate> bounds = new ArrayList<Coordinate>();
double r = getBodyRadius();
addBound(bounds, 0, 2 * (r + outerRadius));
addBound(bounds, length, 2 * (r + outerRadius));
return bounds;
}
/**
* Return the radius of the BodyComponent the fin set is situated on. Currently
* only supports SymmetricComponents and returns the radius at the starting point of the
* root chord.
*
* @return radius of the underlying BodyComponent or 0 if none exists.
*/
public double getBodyRadius() {
RocketComponent s;
s = this.getParent();
while (s != null) {
if (s instanceof SymmetricComponent) {
double x = this.toRelative(new Coordinate(0, 0, 0), s)[0].x;
return ((SymmetricComponent) s).getRadius(x);
}
s = s.getParent();
}
return 0;
}
}