1 package net.sf.openrocket.rocketcomponent;
3 import net.sf.openrocket.motor.Motor;
4 import net.sf.openrocket.util.BugException;
5 import net.sf.openrocket.util.Coordinate;
6 import net.sf.openrocket.util.MathUtil;
8 import java.util.ArrayList;
9 import java.util.HashMap;
10 import java.util.List;
14 * This class defines an inner tube that can be used as a motor mount. The component
15 * may also be clustered.
17 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
19 public class InnerTube extends ThicknessRingComponent
20 implements Clusterable, RadialParent, MotorMount {
22 private ClusterConfiguration cluster = ClusterConfiguration.SINGLE;
23 private double clusterScale = 1.0;
24 private double clusterRotation = 0.0;
27 private boolean motorMount = false;
28 private HashMap<String, Double> ejectionDelays = new HashMap<String, Double>();
29 private HashMap<String, Motor> motors = new HashMap<String, Motor>();
30 private IgnitionEvent ignitionEvent = IgnitionEvent.AUTOMATIC;
31 private double ignitionDelay = 0;
32 private double overhang = 0;
40 this.setOuterRadius(0.019 / 2);
41 this.setInnerRadius(0.018 / 2);
42 this.setLength(0.070);
47 public double getInnerRadius(double x) {
48 return getInnerRadius();
53 public double getOuterRadius(double x) {
54 return getOuterRadius();
59 public String getComponentName() {
64 public boolean allowsChildren() {
69 * Allow all InternalComponents to be added to this component.
72 public boolean isCompatible(Class<? extends RocketComponent> type) {
73 return InternalComponent.class.isAssignableFrom(type);
78 ///////////// Cluster methods //////////////
81 * Get the current cluster configuration.
82 * @return The current cluster configuration.
84 public ClusterConfiguration getClusterConfiguration() {
89 * Set the current cluster configuration.
90 * @param cluster The cluster configuration.
92 public void setClusterConfiguration(ClusterConfiguration cluster) {
93 this.cluster = cluster;
94 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
98 * Return the number of tubes in the cluster.
99 * @return Number of tubes in the current cluster.
102 public int getClusterCount() {
103 return cluster.getClusterCount();
107 * Get the cluster scaling. A value of 1.0 indicates that the tubes are packed
108 * touching each other, larger values separate the tubes and smaller values
109 * pack inside each other.
111 public double getClusterScale() {
116 * Set the cluster scaling.
117 * @see #getClusterScale()
119 public void setClusterScale(double scale) {
120 scale = Math.max(scale, 0);
121 if (MathUtil.equals(clusterScale, scale))
123 clusterScale = scale;
124 fireComponentChangeEvent(new ComponentChangeEvent(this, ComponentChangeEvent.MASS_CHANGE));
130 * @return the clusterRotation
132 public double getClusterRotation() {
133 return clusterRotation;
138 * @param rotation the clusterRotation to set
140 public void setClusterRotation(double rotation) {
141 rotation = MathUtil.reduce180(rotation);
142 if (clusterRotation == rotation)
144 this.clusterRotation = rotation;
145 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
150 * Return the distance between the closest two cluster inner tube center points.
151 * This is equivalent to the cluster scale multiplied by the tube diameter.
154 public double getClusterSeparation() {
155 return 2 * getOuterRadius() * clusterScale;
159 public List<Coordinate> getClusterPoints() {
160 List<Coordinate> list = new ArrayList<Coordinate>(getClusterCount());
161 List<Double> points = cluster.getPoints(clusterRotation - getRadialDirection());
162 double separation = getClusterSeparation();
163 for (int i = 0; i < points.size() / 2; i++) {
164 list.add(new Coordinate(0, points.get(2 * i) * separation, points.get(2 * i + 1) * separation));
171 public Coordinate[] shiftCoordinates(Coordinate[] array) {
172 array = super.shiftCoordinates(array);
174 int count = getClusterCount();
178 List<Coordinate> points = getClusterPoints();
179 if (points.size() != count) {
180 throw new BugException("Inconsistent cluster configuration, cluster count=" + count +
181 " point count=" + points.size());
183 Coordinate[] newArray = new Coordinate[array.length * count];
184 for (int i = 0; i < array.length; i++) {
185 for (int j = 0; j < count; j++) {
186 newArray[i * count + j] = array[i].add(points.get(j));
196 //////////////// Motor mount /////////////////
199 public boolean isMotorMount() {
204 public void setMotorMount(boolean mount) {
205 if (motorMount == mount)
208 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
212 public Motor getMotor(String id) {
216 // Check whether the id is valid for the current rocket
217 RocketComponent root = this.getRoot();
218 if (!(root instanceof Rocket))
220 if (!((Rocket) root).isMotorConfigurationID(id))
223 return motors.get(id);
227 public void setMotor(String id, Motor motor) {
230 throw new IllegalArgumentException("Cannot set non-null motor for id null");
233 Motor current = motors.get(id);
234 if ((motor == null && current == null) ||
235 (motor != null && motor.equals(current)))
237 motors.put(id, motor);
238 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
242 public double getMotorDelay(String id) {
243 Double delay = ejectionDelays.get(id);
245 return Motor.PLUGGED;
250 public void setMotorDelay(String id, double delay) {
251 ejectionDelays.put(id, delay);
252 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
257 public int getMotorCount() {
258 return getClusterCount();
262 public double getMotorMountDiameter() {
263 return getInnerRadius() * 2;
267 public IgnitionEvent getIgnitionEvent() {
268 return ignitionEvent;
272 public void setIgnitionEvent(IgnitionEvent event) {
273 if (ignitionEvent == event)
275 ignitionEvent = event;
276 fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
281 public double getIgnitionDelay() {
282 return ignitionDelay;
286 public void setIgnitionDelay(double delay) {
287 if (MathUtil.equals(delay, ignitionDelay))
289 ignitionDelay = delay;
290 fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
295 public double getMotorOverhang() {
300 public void setMotorOverhang(double overhang) {
301 if (MathUtil.equals(this.overhang, overhang))
303 this.overhang = overhang;
304 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
309 public Coordinate getMotorPosition(String id) {
310 Motor motor = motors.get(id);
312 throw new IllegalArgumentException("No motor with id " + id + " defined.");
315 return new Coordinate(this.getLength() - motor.getLength() + this.getMotorOverhang());
320 * Copy the motor and ejection delay HashMaps.
322 * @see rocketcomponent.RocketComponent#copy()
324 @SuppressWarnings("unchecked")
326 protected RocketComponent copyWithOriginalID() {
327 RocketComponent c = super.copyWithOriginalID();
328 ((InnerTube) c).motors = (HashMap<String, Motor>) motors.clone();
329 ((InnerTube) c).ejectionDelays = (HashMap<String, Double>) ejectionDelays.clone();