package net.sf.openrocket.rocketcomponent;
import java.util.ArrayList;
import java.util.Collection;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.preset.ComponentPreset;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
/**
* Rocket body tube component. Has only two parameters, a radius and length.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial {
private static final Translator trans = Application.getTranslator();
private double outerRadius = 0;
private boolean autoRadius = false; // Radius chosen automatically based on parent component
// When changing the inner radius, thickness is modified
private boolean motorMount = false;
private double overhang = 0;
private FlightConfigurationImpl<MotorConfiguration> motorConfigurations;
private FlightConfigurationImpl<IgnitionConfiguration> ignitionConfigurations;
public BodyTube() {
this(8 * DEFAULT_RADIUS, DEFAULT_RADIUS);
this.autoRadius = true;
this.motorConfigurations = new MotorFlightConfigurationImpl<MotorConfiguration>(this, ComponentChangeEvent.MOTOR_CHANGE, MotorConfiguration.NO_MOTORS);
this.ignitionConfigurations = new FlightConfigurationImpl<IgnitionConfiguration>(this, ComponentChangeEvent.EVENT_CHANGE, new IgnitionConfiguration());
}
public BodyTube(double length, double radius) {
super();
this.outerRadius = Math.max(radius, 0);
this.length = Math.max(length, 0);
this.motorConfigurations = new MotorFlightConfigurationImpl<MotorConfiguration>(this, ComponentChangeEvent.MOTOR_CHANGE, MotorConfiguration.NO_MOTORS);
this.ignitionConfigurations = new FlightConfigurationImpl<IgnitionConfiguration>(this, ComponentChangeEvent.EVENT_CHANGE, new IgnitionConfiguration());
}
public BodyTube(double length, double radius, boolean filled) {
this(length, radius);
this.filled = filled;
}
public BodyTube(double length, double radius, double thickness) {
this(length, radius);
this.filled = false;
this.thickness = thickness;
}
/************ Get/set component parameter methods ************/
@Override
public ComponentPreset.Type getPresetType() {
return ComponentPreset.Type.BODY_TUBE;
}
/**
* Return the outer radius of the body tube.
*
* @return the outside radius of the tube
*/
@Override
public double getOuterRadius() {
if (autoRadius) {
// Return auto radius from front or rear
double r = -1;
SymmetricComponent c = this.getPreviousSymmetricComponent();
if (c != null) {
r = c.getFrontAutoRadius();
}
if (r < 0) {
c = this.getNextSymmetricComponent();
if (c != null) {
r = c.getRearAutoRadius();
}
}
if (r < 0)
r = DEFAULT_RADIUS;
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
*/
@Override
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();
}
/**
* Returns whether the radius is selected automatically or not.
* Returns false also in case automatic radius selection is not possible.
*/
public boolean isOuterRadiusAutomatic() {
return autoRadius;
}
/**
* 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();
}
@Override
protected void loadFromPreset(ComponentPreset preset) {
this.autoRadius = false;
if (preset.has(ComponentPreset.OUTER_DIAMETER)) {
double outerDiameter = preset.get(ComponentPreset.OUTER_DIAMETER);
this.outerRadius = outerDiameter / 2.0;
if (preset.has(ComponentPreset.INNER_DIAMETER)) {
double innerDiameter = preset.get(ComponentPreset.INNER_DIAMETER);
this.thickness = (outerDiameter - innerDiameter) / 2.0;
}
}
super.loadFromPreset(preset);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
@Override
public double getAftRadius() {
return getOuterRadius();
}
@Override
public double getForeRadius() {
return getOuterRadius();
}
@Override
public boolean isAftRadiusAutomatic() {
return isOuterRadiusAutomatic();
}
@Override
public boolean isForeRadiusAutomatic() {
return isOuterRadiusAutomatic();
}
@Override
protected double getFrontAutoRadius() {
if (isOuterRadiusAutomatic()) {
// Search for previous SymmetricComponent
SymmetricComponent c = this.getPreviousSymmetricComponent();
if (c != null) {
return c.getFrontAutoRadius();
} else {
return -1;
}
}
return getOuterRadius();
}
@Override
protected double getRearAutoRadius() {
if (isOuterRadiusAutomatic()) {
// Search for next SymmetricComponent
SymmetricComponent c = this.getNextSymmetricComponent();
if (c != null) {
return c.getRearAutoRadius();
} else {
return -1;
}
}
return getOuterRadius();
}
@Override
public double getInnerRadius() {
if (filled)
return 0;
return Math.max(getOuterRadius() - thickness, 0);
}
@Override
public void setInnerRadius(double r) {
setThickness(getOuterRadius() - r);
}
/**
* Return the component name.
*/
@Override
public String getComponentName() {
//// Body tube
return trans.get("BodyTube.BodyTube");
}
/************ Component calculations ***********/
// From SymmetricComponent
/**
* Returns the outer radius at the position x. This returns the same value as getOuterRadius().
*/
@Override
public double getRadius(double x) {
return getOuterRadius();
}
/**
* Returns the inner radius at the position x. If the tube is filled, returns always zero.
*/
@Override
public double getInnerRadius(double x) {
if (filled)
return 0.0;
else
return Math.max(getOuterRadius() - thickness, 0);
}
/**
* Returns the body tube's center of gravity.
*/
@Override
public Coordinate getComponentCG() {
return new Coordinate(length / 2, 0, 0, getComponentMass());
}
/**
* Returns the body tube's volume.
*/
@Override
public double getComponentVolume() {
double r = getOuterRadius();
if (filled)
return getFilledVolume(r, length);
else
return getFilledVolume(r, length) - getFilledVolume(getInnerRadius(0), length);
}
@Override
public double getLongitudinalUnitInertia() {
// 1/12 * (3 * (r2^2 + r1^2) + h^2)
return (3 * (MathUtil.pow2(getOuterRadius()) + MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getLength())) / 12;
}
@Override
public double getRotationalUnitInertia() {
// 1/2 * (r1^2 + r2^2)
return (MathUtil.pow2(getInnerRadius()) + MathUtil.pow2(getOuterRadius())) / 2;
}
/**
* Helper function for cylinder volume.
*/
private static double getFilledVolume(double r, double l) {
return Math.PI * r * r * l;
}
/**
* Adds bounding coordinates to the given set. The body tube will fit within the
* convex hull of the points.
*
* Currently the points are simply a rectangular box around the body tube.
*/
@Override
public Collection<Coordinate> getComponentBounds() {
Collection<Coordinate> bounds = new ArrayList<Coordinate>(8);
double r = getOuterRadius();
addBound(bounds, 0, r);
addBound(bounds, length, r);
return bounds;
}
/**
* Check whether the given type can be added to this component. BodyTubes allow any
* InternalComponents or ExternalComponents, excluding BodyComponents, to be added.
*
* @param type The RocketComponent class type to add.
* @return Whether such a component can be added.
*/
@Override
public boolean isCompatible(Class<? extends RocketComponent> type) {
if (InternalComponent.class.isAssignableFrom(type))
return true;
if (ExternalComponent.class.isAssignableFrom(type) &&
!BodyComponent.class.isAssignableFrom(type))
return true;
return false;
}
//////////////// Motor mount /////////////////
@Override
public FlightConfiguration<MotorConfiguration> getMotorConfiguration() {
return motorConfigurations;
}
@Override
public FlightConfiguration<IgnitionConfiguration> getIgnitionConfiguration() {
return ignitionConfigurations;
}
@Override
public void cloneFlightConfiguration(String oldConfigId, String newConfigId) {
motorConfigurations.cloneFlightConfiguration(oldConfigId, newConfigId);
ignitionConfigurations.cloneFlightConfiguration(oldConfigId, newConfigId);
}
@Override
public boolean isMotorMount() {
return motorMount;
}
@Override
public void setMotorMount(boolean mount) {
if (motorMount == mount)
return;
motorMount = mount;
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
}
@SuppressWarnings("deprecation")
@Deprecated
@Override
public Motor getMotor(String id) {
return this.motorConfigurations.get(id).getMotor();
}
@SuppressWarnings("deprecation")
@Deprecated
@Override
public double getMotorDelay(String id) {
return this.motorConfigurations.get(id).getEjectionDelay();
}
@SuppressWarnings("deprecation")
@Deprecated
@Override
public int getMotorCount() {
return 1;
}
@Override
public double getMotorMountDiameter() {
return getInnerRadius() * 2;
}
@Override
public double getMotorOverhang() {
return overhang;
}
@Override
public void setMotorOverhang(double overhang) {
if (MathUtil.equals(this.overhang, overhang))
return;
this.overhang = overhang;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
@Override
public Coordinate getMotorPosition(String id) {
Motor motor = getMotor(id);
if (motor == null) {
throw new IllegalArgumentException("No motor with id " + id + " defined.");
}
return new Coordinate(this.getLength() - motor.getLength() + this.getMotorOverhang());
}
@Override
protected RocketComponent copyWithOriginalID() {
BodyTube copy = (BodyTube) super.copyWithOriginalID();
copy.motorConfigurations = new FlightConfigurationImpl<MotorConfiguration>(motorConfigurations, copy, ComponentChangeEvent.MOTOR_CHANGE);
copy.ignitionConfigurations = new FlightConfigurationImpl<IgnitionConfiguration>(ignitionConfigurations, copy, ComponentChangeEvent.EVENT_CHANGE);
return copy;
}
}