1 package net.sf.openrocket.rocketcomponent;
3 import net.sf.openrocket.motor.Motor;
4 import net.sf.openrocket.util.Coordinate;
5 import net.sf.openrocket.util.MathUtil;
7 import java.util.ArrayList;
8 import java.util.Collection;
9 import java.util.HashMap;
13 * Rocket body tube component. Has only two parameters, a radius and length.
15 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
18 public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial {
20 private double radius = 0;
21 private boolean autoRadius = false; // Radius chosen automatically based on parent component
23 // When changing the inner radius, thickness is modified
25 private boolean motorMount = false;
26 private HashMap<String, Double> ejectionDelays = new HashMap<String, Double>();
27 private HashMap<String, Motor> motors = new HashMap<String, Motor>();
28 private IgnitionEvent ignitionEvent = IgnitionEvent.AUTOMATIC;
29 private double ignitionDelay = 0;
30 private double overhang = 0;
36 this.length = 8 * DEFAULT_RADIUS;
37 this.radius = DEFAULT_RADIUS;
38 this.autoRadius = true;
41 public BodyTube(double length, double radius) {
43 this.radius = Math.max(radius, 0);
44 this.length = Math.max(length, 0);
48 public BodyTube(double length, double radius, boolean filled) {
53 public BodyTube(double length, double radius, double thickness) {
56 this.thickness = thickness;
60 /************ Get/set component parameter methods ************/
63 * Return the outer radius of the body tube.
65 * @return the outside radius of the tube
68 public double getOuterRadius () {
70 // Return auto radius from front or rear
72 SymmetricComponent c = this.getPreviousSymmetricComponent();
74 r = c.getFrontAutoRadius();
77 c = this.getNextSymmetricComponent();
79 r = c.getRearAutoRadius();
91 * Set the outer radius of the body tube. If the radius is less than the wall thickness,
92 * the wall thickness is decreased accordingly of the value of the radius.
93 * This method sets the automatic radius off.
95 * @param radius the outside radius in standard units
98 public void setOuterRadius (double radius) {
99 if ((this.radius == radius) && (autoRadius == false))
102 this.autoRadius = false;
103 this.radius = Math.max(radius, 0);
105 if (this.thickness > this.radius)
106 this.thickness = this.radius;
107 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
112 * Returns whether the radius is selected automatically or not.
113 * Returns false also in case automatic radius selection is not possible.
115 public boolean isRadiusAutomatic() {
120 * Sets whether the radius is selected automatically or not.
122 public void setRadiusAutomatic(boolean auto) {
123 if (autoRadius == auto)
127 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
132 public double getAftRadius() { return getOuterRadius(); }
134 public double getForeRadius() { return getOuterRadius(); }
136 public boolean isAftRadiusAutomatic() {
137 return isRadiusAutomatic();
141 public boolean isForeRadiusAutomatic() {
142 return isRadiusAutomatic();
148 protected double getFrontAutoRadius() {
149 if (isRadiusAutomatic()) {
150 // Search for previous SymmetricComponent
151 SymmetricComponent c = this.getPreviousSymmetricComponent();
153 return c.getFrontAutoRadius();
158 return getOuterRadius();
162 protected double getRearAutoRadius() {
163 if (isRadiusAutomatic()) {
164 // Search for next SymmetricComponent
165 SymmetricComponent c = this.getNextSymmetricComponent();
167 return c.getRearAutoRadius();
172 return getOuterRadius();
183 public double getInnerRadius() {
186 return Math.max(getOuterRadius()-thickness, 0);
190 public void setInnerRadius(double r) {
191 setThickness(getOuterRadius()-r);
198 * Return the component name.
201 public String getComponentName() {
206 * Accept a visitor to this BodyTube in the component hierarchy.
208 * @param theVisitor the visitor that will be called back with a reference to this BodyTube
211 public void accept (final ComponentVisitor theVisitor) {
212 theVisitor.visit(this);
216 /************ Component calculations ***********/
218 // From SymmetricComponent
220 * Returns the outer radius at the position x. This returns the same value as getOuterRadius().
223 public double getRadius(double x) {
224 return getOuterRadius();
228 * Returns the inner radius at the position x. If the tube is filled, returns always zero.
231 public double getInnerRadius(double x) {
235 return Math.max(getOuterRadius()-thickness,0);
240 * Returns the body tube's center of gravity.
243 public Coordinate getComponentCG() {
244 return new Coordinate(length / 2, 0, 0, getComponentMass());
248 * Returns the body tube's volume.
251 public double getComponentVolume() {
252 double r = getOuterRadius();
254 return getFilledVolume(r, length);
256 return getFilledVolume(r, length) - getFilledVolume(getInnerRadius(0), length);
261 public double getLongitudinalUnitInertia() {
262 // 1/12 * (3 * (r1^2 + r2^2) + h^2)
263 return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getOuterRadius()) +
264 MathUtil.pow2(getLength())) / 12;
268 public double getRotationalUnitInertia() {
269 // 1/2 * (r1^2 + r2^2)
270 return (MathUtil.pow2(getInnerRadius()) + MathUtil.pow2(getOuterRadius()))/2;
277 * Helper function for cylinder volume.
279 private static double getFilledVolume(double r, double l) {
280 return Math.PI * r * r * l;
285 * Adds bounding coordinates to the given set. The body tube will fit within the
286 * convex hull of the points.
288 * Currently the points are simply a rectangular box around the body tube.
291 public Collection<Coordinate> getComponentBounds() {
292 Collection<Coordinate> bounds = new ArrayList<Coordinate>(8);
293 double r = getOuterRadius();
294 addBound(bounds, 0, r);
295 addBound(bounds, length, r);
302 * Check whether the given type can be added to this component. BodyTubes allow any
303 * InternalComponents or ExternalComponents, excluding BodyComponents, to be added.
305 * @param type The RocketComponent class type to add.
306 * @return Whether such a component can be added.
309 public boolean isCompatible(Class<? extends RocketComponent> type) {
310 if (InternalComponent.class.isAssignableFrom(type))
312 if (ExternalComponent.class.isAssignableFrom(type) &&
313 !BodyComponent.class.isAssignableFrom(type))
318 //////////////// Motor mount /////////////////
321 public boolean isMotorMount() {
326 public void setMotorMount(boolean mount) {
327 if (motorMount == mount)
330 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
334 public Motor getMotor(String id) {
338 // Check whether the id is valid for the current rocket
339 RocketComponent root = this.getRoot();
340 if (!(root instanceof Rocket))
342 if (!((Rocket) root).isMotorConfigurationID(id))
345 return motors.get(id);
349 public void setMotor(String id, Motor motor) {
352 throw new IllegalArgumentException("Cannot set non-null motor for id null");
355 Motor current = motors.get(id);
356 if ((motor == null && current == null) ||
357 (motor != null && motor.equals(current)))
359 motors.put(id, motor);
360 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
364 public double getMotorDelay(String id) {
365 Double delay = ejectionDelays.get(id);
367 return Motor.PLUGGED;
372 public void setMotorDelay(String id, double delay) {
373 ejectionDelays.put(id, delay);
374 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
378 public int getMotorCount() {
383 public double getMotorMountDiameter() {
384 return getInnerRadius() * 2;
388 public IgnitionEvent getIgnitionEvent() {
389 return ignitionEvent;
393 public void setIgnitionEvent(IgnitionEvent event) {
394 if (ignitionEvent == event)
396 ignitionEvent = event;
397 fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
402 public double getIgnitionDelay() {
403 return ignitionDelay;
407 public void setIgnitionDelay(double delay) {
408 if (MathUtil.equals(delay, ignitionDelay))
410 ignitionDelay = delay;
411 fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
416 public double getMotorOverhang() {
421 public void setMotorOverhang(double overhang) {
422 if (MathUtil.equals(this.overhang, overhang))
424 this.overhang = overhang;
425 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
430 public Coordinate getMotorPosition(String id) {
431 Motor motor = motors.get(id);
433 throw new IllegalArgumentException("No motor with id " + id + " defined.");
436 return new Coordinate(this.getLength() - motor.getLength() + this.getMotorOverhang());
444 * Copy the motor and ejection delay HashMaps.
446 * @see rocketcomponent.RocketComponent#copy()
448 @SuppressWarnings("unchecked")
450 protected RocketComponent copyWithOriginalID() {
451 RocketComponent c = super.copyWithOriginalID();
452 ((BodyTube) c).motors = (HashMap<String, Motor>) motors.clone();
453 ((BodyTube) c).ejectionDelays = (HashMap<String, Double>) ejectionDelays.clone();