1 package net.sf.openrocket.rocketcomponent;
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.HashMap;
7 import net.sf.openrocket.l10n.Translator;
8 import net.sf.openrocket.motor.Motor;
9 import net.sf.openrocket.preset.ComponentPreset;
10 import net.sf.openrocket.startup.Application;
11 import net.sf.openrocket.util.Coordinate;
12 import net.sf.openrocket.util.MathUtil;
16 * Rocket body tube component. Has only two parameters, a radius and length.
18 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
21 public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial {
22 private static final Translator trans = Application.getTranslator();
24 private double outerRadius = 0;
25 private boolean autoRadius = false; // Radius chosen automatically based on parent component
27 // When changing the inner radius, thickness is modified
29 private boolean motorMount = false;
30 private HashMap<String, Double> ejectionDelays = new HashMap<String, Double>();
31 private HashMap<String, Motor> motors = new HashMap<String, Motor>();
32 private IgnitionEvent ignitionEvent = IgnitionEvent.AUTOMATIC;
33 private double ignitionDelay = 0;
34 private double overhang = 0;
40 this.length = 8 * DEFAULT_RADIUS;
41 this.outerRadius = DEFAULT_RADIUS;
42 this.autoRadius = true;
45 public BodyTube(double length, double radius) {
47 this.outerRadius = Math.max(radius, 0);
48 this.length = Math.max(length, 0);
52 public BodyTube(double length, double radius, boolean filled) {
57 public BodyTube(double length, double radius, double thickness) {
60 this.thickness = thickness;
64 /************ Get/set component parameter methods ************/
67 public ComponentPreset.Type getPresetType() {
68 return ComponentPreset.Type.BODY_TUBE;
72 * Return the outer radius of the body tube.
74 * @return the outside radius of the tube
77 public double getOuterRadius() {
79 // Return auto radius from front or rear
81 SymmetricComponent c = this.getPreviousSymmetricComponent();
83 r = c.getFrontAutoRadius();
86 c = this.getNextSymmetricComponent();
88 r = c.getRearAutoRadius();
100 * Set the outer radius of the body tube. If the radius is less than the wall thickness,
101 * the wall thickness is decreased accordingly of the value of the radius.
102 * This method sets the automatic radius off.
104 * @param radius the outside radius in standard units
107 public void setOuterRadius(double radius) {
108 if ((this.outerRadius == radius) && (autoRadius == false))
111 this.autoRadius = false;
112 this.outerRadius = Math.max(radius, 0);
114 if (this.thickness > this.outerRadius)
115 this.thickness = this.outerRadius;
116 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
122 * Returns whether the radius is selected automatically or not.
123 * Returns false also in case automatic radius selection is not possible.
125 public boolean isOuterRadiusAutomatic() {
130 * Sets whether the radius is selected automatically or not.
132 public void setOuterRadiusAutomatic(boolean auto) {
133 if (autoRadius == auto)
137 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
143 protected void loadFromPreset(ComponentPreset preset) {
144 this.autoRadius = false;
145 if ( preset.has(ComponentPreset.OUTER_DIAMETER) ) {
146 double outerDiameter = preset.get(ComponentPreset.OUTER_DIAMETER);
147 this.outerRadius = outerDiameter/2.0;
148 if ( preset.has(ComponentPreset.INNER_DIAMETER) ) {
149 double innerDiameter = preset.get(ComponentPreset.INNER_DIAMETER);
150 this.thickness = (outerDiameter-innerDiameter) / 2.0;
154 super.loadFromPreset(preset);
156 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
160 public double getAftRadius() {
161 return getOuterRadius();
165 public double getForeRadius() {
166 return getOuterRadius();
170 public boolean isAftRadiusAutomatic() {
171 return isOuterRadiusAutomatic();
175 public boolean isForeRadiusAutomatic() {
176 return isOuterRadiusAutomatic();
182 protected double getFrontAutoRadius() {
183 if (isOuterRadiusAutomatic()) {
184 // Search for previous SymmetricComponent
185 SymmetricComponent c = this.getPreviousSymmetricComponent();
187 return c.getFrontAutoRadius();
192 return getOuterRadius();
196 protected double getRearAutoRadius() {
197 if (isOuterRadiusAutomatic()) {
198 // Search for next SymmetricComponent
199 SymmetricComponent c = this.getNextSymmetricComponent();
201 return c.getRearAutoRadius();
206 return getOuterRadius();
214 public double getInnerRadius() {
217 return Math.max(getOuterRadius() - thickness, 0);
221 public void setInnerRadius(double r) {
222 setThickness(getOuterRadius() - r);
229 * Return the component name.
232 public String getComponentName() {
234 return trans.get("BodyTube.BodyTube");
238 /************ Component calculations ***********/
240 // From SymmetricComponent
242 * Returns the outer radius at the position x. This returns the same value as getOuterRadius().
245 public double getRadius(double x) {
246 return getOuterRadius();
250 * Returns the inner radius at the position x. If the tube is filled, returns always zero.
253 public double getInnerRadius(double x) {
257 return Math.max(getOuterRadius() - thickness, 0);
262 * Returns the body tube's center of gravity.
265 public Coordinate getComponentCG() {
266 return new Coordinate(length / 2, 0, 0, getComponentMass());
270 * Returns the body tube's volume.
273 public double getComponentVolume() {
274 double r = getOuterRadius();
276 return getFilledVolume(r, length);
278 return getFilledVolume(r, length) - getFilledVolume(getInnerRadius(0), length);
283 public double getLongitudinalUnitInertia() {
284 // 1/12 * (3 * (r1^2 + r2^2) + h^2)
285 return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getOuterRadius()) + MathUtil.pow2(getLength())) / 12;
289 public double getRotationalUnitInertia() {
290 // 1/2 * (r1^2 + r2^2)
291 return (MathUtil.pow2(getInnerRadius()) + MathUtil.pow2(getOuterRadius())) / 2;
298 * Helper function for cylinder volume.
300 private static double getFilledVolume(double r, double l) {
301 return Math.PI * r * r * l;
306 * Adds bounding coordinates to the given set. The body tube will fit within the
307 * convex hull of the points.
309 * Currently the points are simply a rectangular box around the body tube.
312 public Collection<Coordinate> getComponentBounds() {
313 Collection<Coordinate> bounds = new ArrayList<Coordinate>(8);
314 double r = getOuterRadius();
315 addBound(bounds, 0, r);
316 addBound(bounds, length, r);
323 * Check whether the given type can be added to this component. BodyTubes allow any
324 * InternalComponents or ExternalComponents, excluding BodyComponents, to be added.
326 * @param type The RocketComponent class type to add.
327 * @return Whether such a component can be added.
330 public boolean isCompatible(Class<? extends RocketComponent> type) {
331 if (InternalComponent.class.isAssignableFrom(type))
333 if (ExternalComponent.class.isAssignableFrom(type) &&
334 !BodyComponent.class.isAssignableFrom(type))
339 //////////////// Motor mount /////////////////
342 public boolean isMotorMount() {
347 public void setMotorMount(boolean mount) {
348 if (motorMount == mount)
351 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
355 public Motor getMotor(String id) {
359 // Check whether the id is valid for the current rocket
360 RocketComponent root = this.getRoot();
361 if (!(root instanceof Rocket))
363 if (!((Rocket) root).isMotorConfigurationID(id))
366 return motors.get(id);
370 public void setMotor(String id, Motor motor) {
373 throw new IllegalArgumentException("Cannot set non-null motor for id null");
376 Motor current = motors.get(id);
377 if ((motor == null && current == null) ||
378 (motor != null && motor.equals(current)))
380 motors.put(id, motor);
381 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
385 public double getMotorDelay(String id) {
386 Double delay = ejectionDelays.get(id);
388 return Motor.PLUGGED;
393 public void setMotorDelay(String id, double delay) {
394 ejectionDelays.put(id, delay);
395 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
399 public int getMotorCount() {
404 public double getMotorMountDiameter() {
405 return getInnerRadius() * 2;
409 public IgnitionEvent getIgnitionEvent() {
410 return ignitionEvent;
414 public void setIgnitionEvent(IgnitionEvent event) {
415 if (ignitionEvent == event)
417 ignitionEvent = event;
418 fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
423 public double getIgnitionDelay() {
424 return ignitionDelay;
428 public void setIgnitionDelay(double delay) {
429 if (MathUtil.equals(delay, ignitionDelay))
431 ignitionDelay = delay;
432 fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
437 public double getMotorOverhang() {
442 public void setMotorOverhang(double overhang) {
443 if (MathUtil.equals(this.overhang, overhang))
445 this.overhang = overhang;
446 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
451 public Coordinate getMotorPosition(String id) {
452 Motor motor = motors.get(id);
454 throw new IllegalArgumentException("No motor with id " + id + " defined.");
457 return new Coordinate(this.getLength() - motor.getLength() + this.getMotorOverhang());
465 * Copy the motor and ejection delay HashMaps.
467 * @see rocketcomponent.RocketComponent#copy()
469 @SuppressWarnings("unchecked")
471 protected RocketComponent copyWithOriginalID() {
472 RocketComponent c = super.copyWithOriginalID();
473 ((BodyTube) c).motors = (HashMap<String, Motor>) motors.clone();
474 ((BodyTube) c).ejectionDelays = (HashMap<String, Double>) ejectionDelays.clone();