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.startup.Application;
10 import net.sf.openrocket.util.Coordinate;
11 import net.sf.openrocket.util.MathUtil;
15 * Rocket body tube component. Has only two parameters, a radius and length.
17 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
20 public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial {
21 private static final Translator trans = Application.getTranslator();
23 private double outerRadius = 0;
24 private boolean autoRadius = false; // Radius chosen automatically based on parent component
26 // When changing the inner radius, thickness is modified
28 private boolean motorMount = false;
29 private HashMap<String, Double> ejectionDelays = new HashMap<String, Double>();
30 private HashMap<String, Motor> motors = new HashMap<String, Motor>();
31 private IgnitionEvent ignitionEvent = IgnitionEvent.AUTOMATIC;
32 private double ignitionDelay = 0;
33 private double overhang = 0;
39 this.length = 8 * DEFAULT_RADIUS;
40 this.outerRadius = DEFAULT_RADIUS;
41 this.autoRadius = true;
44 public BodyTube(double length, double radius) {
46 this.outerRadius = Math.max(radius, 0);
47 this.length = Math.max(length, 0);
51 public BodyTube(double length, double radius, boolean filled) {
56 public BodyTube(double length, double radius, double thickness) {
59 this.thickness = thickness;
63 /************ Get/set component parameter methods ************/
66 * Return the outer radius of the body tube.
68 * @return the outside radius of the tube
71 public double getOuterRadius() {
73 // Return auto radius from front or rear
75 SymmetricComponent c = this.getPreviousSymmetricComponent();
77 r = c.getFrontAutoRadius();
80 c = this.getNextSymmetricComponent();
82 r = c.getRearAutoRadius();
94 * Set the outer radius of the body tube. If the radius is less than the wall thickness,
95 * the wall thickness is decreased accordingly of the value of the radius.
96 * This method sets the automatic radius off.
98 * @param radius the outside radius in standard units
101 public void setOuterRadius(double radius) {
102 if ((this.outerRadius == radius) && (autoRadius == false))
105 this.autoRadius = false;
106 this.outerRadius = Math.max(radius, 0);
108 if (this.thickness > this.outerRadius)
109 this.thickness = this.outerRadius;
110 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
116 * Returns whether the radius is selected automatically or not.
117 * Returns false also in case automatic radius selection is not possible.
119 public boolean isOuterRadiusAutomatic() {
124 * Sets whether the radius is selected automatically or not.
126 public void setOuterRadiusAutomatic(boolean auto) {
127 if (autoRadius == auto)
131 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
137 protected void loadFromPreset(RocketComponent preset) {
138 super.loadFromPreset(preset);
139 BodyTube bt = (BodyTube) preset;
140 this.autoRadius = false;
141 this.outerRadius = bt.getOuterRadius();
142 this.thickness = (bt.getOuterRadius() - bt.getInnerRadius());
143 this.length = bt.getLength();
145 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
149 public double getAftRadius() {
150 return getOuterRadius();
154 public double getForeRadius() {
155 return getOuterRadius();
159 public boolean isAftRadiusAutomatic() {
160 return isOuterRadiusAutomatic();
164 public boolean isForeRadiusAutomatic() {
165 return isOuterRadiusAutomatic();
171 protected double getFrontAutoRadius() {
172 if (isOuterRadiusAutomatic()) {
173 // Search for previous SymmetricComponent
174 SymmetricComponent c = this.getPreviousSymmetricComponent();
176 return c.getFrontAutoRadius();
181 return getOuterRadius();
185 protected double getRearAutoRadius() {
186 if (isOuterRadiusAutomatic()) {
187 // Search for next SymmetricComponent
188 SymmetricComponent c = this.getNextSymmetricComponent();
190 return c.getRearAutoRadius();
195 return getOuterRadius();
203 public double getInnerRadius() {
206 return Math.max(getOuterRadius() - thickness, 0);
210 public void setInnerRadius(double r) {
211 setThickness(getOuterRadius() - r);
218 * Return the component name.
221 public String getComponentName() {
223 return trans.get("BodyTube.BodyTube");
227 /************ Component calculations ***********/
229 // From SymmetricComponent
231 * Returns the outer radius at the position x. This returns the same value as getOuterRadius().
234 public double getRadius(double x) {
235 return getOuterRadius();
239 * Returns the inner radius at the position x. If the tube is filled, returns always zero.
242 public double getInnerRadius(double x) {
246 return Math.max(getOuterRadius() - thickness, 0);
251 * Returns the body tube's center of gravity.
254 public Coordinate getComponentCG() {
255 return new Coordinate(length / 2, 0, 0, getComponentMass());
259 * Returns the body tube's volume.
262 public double getComponentVolume() {
263 double r = getOuterRadius();
265 return getFilledVolume(r, length);
267 return getFilledVolume(r, length) - getFilledVolume(getInnerRadius(0), length);
272 public double getLongitudinalUnitInertia() {
273 // 1/12 * (3 * (r1^2 + r2^2) + h^2)
274 return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getOuterRadius()) + MathUtil.pow2(getLength())) / 12;
278 public double getRotationalUnitInertia() {
279 // 1/2 * (r1^2 + r2^2)
280 return (MathUtil.pow2(getInnerRadius()) + MathUtil.pow2(getOuterRadius())) / 2;
287 * Helper function for cylinder volume.
289 private static double getFilledVolume(double r, double l) {
290 return Math.PI * r * r * l;
295 * Adds bounding coordinates to the given set. The body tube will fit within the
296 * convex hull of the points.
298 * Currently the points are simply a rectangular box around the body tube.
301 public Collection<Coordinate> getComponentBounds() {
302 Collection<Coordinate> bounds = new ArrayList<Coordinate>(8);
303 double r = getOuterRadius();
304 addBound(bounds, 0, r);
305 addBound(bounds, length, r);
312 * Check whether the given type can be added to this component. BodyTubes allow any
313 * InternalComponents or ExternalComponents, excluding BodyComponents, to be added.
315 * @param type The RocketComponent class type to add.
316 * @return Whether such a component can be added.
319 public boolean isCompatible(Class<? extends RocketComponent> type) {
320 if (InternalComponent.class.isAssignableFrom(type))
322 if (ExternalComponent.class.isAssignableFrom(type) &&
323 !BodyComponent.class.isAssignableFrom(type))
328 //////////////// Motor mount /////////////////
331 public boolean isMotorMount() {
336 public void setMotorMount(boolean mount) {
337 if (motorMount == mount)
340 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
344 public Motor getMotor(String id) {
348 // Check whether the id is valid for the current rocket
349 RocketComponent root = this.getRoot();
350 if (!(root instanceof Rocket))
352 if (!((Rocket) root).isMotorConfigurationID(id))
355 return motors.get(id);
359 public void setMotor(String id, Motor motor) {
362 throw new IllegalArgumentException("Cannot set non-null motor for id null");
365 Motor current = motors.get(id);
366 if ((motor == null && current == null) ||
367 (motor != null && motor.equals(current)))
369 motors.put(id, motor);
370 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
374 public double getMotorDelay(String id) {
375 Double delay = ejectionDelays.get(id);
377 return Motor.PLUGGED;
382 public void setMotorDelay(String id, double delay) {
383 ejectionDelays.put(id, delay);
384 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
388 public int getMotorCount() {
393 public double getMotorMountDiameter() {
394 return getInnerRadius() * 2;
398 public IgnitionEvent getIgnitionEvent() {
399 return ignitionEvent;
403 public void setIgnitionEvent(IgnitionEvent event) {
404 if (ignitionEvent == event)
406 ignitionEvent = event;
407 fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
412 public double getIgnitionDelay() {
413 return ignitionDelay;
417 public void setIgnitionDelay(double delay) {
418 if (MathUtil.equals(delay, ignitionDelay))
420 ignitionDelay = delay;
421 fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
426 public double getMotorOverhang() {
431 public void setMotorOverhang(double overhang) {
432 if (MathUtil.equals(this.overhang, overhang))
434 this.overhang = overhang;
435 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
440 public Coordinate getMotorPosition(String id) {
441 Motor motor = motors.get(id);
443 throw new IllegalArgumentException("No motor with id " + id + " defined.");
446 return new Coordinate(this.getLength() - motor.getLength() + this.getMotorOverhang());
454 * Copy the motor and ejection delay HashMaps.
456 * @see rocketcomponent.RocketComponent#copy()
458 @SuppressWarnings("unchecked")
460 protected RocketComponent copyWithOriginalID() {
461 RocketComponent c = super.copyWithOriginalID();
462 ((BodyTube) c).motors = (HashMap<String, Motor>) motors.clone();
463 ((BodyTube) c).ejectionDelays = (HashMap<String, Double>) ejectionDelays.clone();