1 package net.sf.openrocket.rocketcomponent;
3 import java.util.ArrayList;
4 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.BugException;
11 import net.sf.openrocket.util.Coordinate;
12 import net.sf.openrocket.util.MathUtil;
16 * This class defines an inner tube that can be used as a motor mount. The component
17 * may also be clustered.
19 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
21 public class InnerTube extends ThicknessRingComponent
22 implements Clusterable, RadialParent, MotorMount {
23 private static final Translator trans = Application.getTranslator();
25 private ClusterConfiguration cluster = ClusterConfiguration.SINGLE;
26 private double clusterScale = 1.0;
27 private double clusterRotation = 0.0;
30 private boolean motorMount = false;
31 private HashMap<String, Double> ejectionDelays = new HashMap<String, Double>();
32 private HashMap<String, Motor> motors = new HashMap<String, Motor>();
33 private IgnitionEvent ignitionEvent = IgnitionEvent.AUTOMATIC;
34 private double ignitionDelay = 0;
35 private double overhang = 0;
43 this.setOuterRadius(0.019 / 2);
44 this.setInnerRadius(0.018 / 2);
45 this.setLength(0.070);
50 public double getInnerRadius(double x) {
51 return getInnerRadius();
56 public double getOuterRadius(double x) {
57 return getOuterRadius();
62 public String getComponentName() {
64 return trans.get("InnerTube.InnerTube");
68 public boolean allowsChildren() {
73 * Allow all InternalComponents to be added to this component.
76 public boolean isCompatible(Class<? extends RocketComponent> type) {
77 return InternalComponent.class.isAssignableFrom(type);
82 ///////////// Cluster methods //////////////
85 * Get the current cluster configuration.
86 * @return The current cluster configuration.
89 public ClusterConfiguration getClusterConfiguration() {
94 * Set the current cluster configuration.
95 * @param cluster The cluster configuration.
98 public void setClusterConfiguration(ClusterConfiguration cluster) {
99 this.cluster = cluster;
100 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
104 * Return the number of tubes in the cluster.
105 * @return Number of tubes in the current cluster.
108 public int getClusterCount() {
109 return cluster.getClusterCount();
113 * Get the cluster scaling. A value of 1.0 indicates that the tubes are packed
114 * touching each other, larger values separate the tubes and smaller values
115 * pack inside each other.
117 public double getClusterScale() {
122 * Set the cluster scaling.
123 * @see #getClusterScale()
125 public void setClusterScale(double scale) {
126 scale = Math.max(scale, 0);
127 if (MathUtil.equals(clusterScale, scale))
129 clusterScale = scale;
130 fireComponentChangeEvent(new ComponentChangeEvent(this, ComponentChangeEvent.MASS_CHANGE));
136 * @return the clusterRotation
138 public double getClusterRotation() {
139 return clusterRotation;
144 * @param rotation the clusterRotation to set
146 public void setClusterRotation(double rotation) {
147 rotation = MathUtil.reduce180(rotation);
148 if (clusterRotation == rotation)
150 this.clusterRotation = rotation;
151 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
156 * Return the distance between the closest two cluster inner tube center points.
157 * This is equivalent to the cluster scale multiplied by the tube diameter.
160 public double getClusterSeparation() {
161 return 2 * getOuterRadius() * clusterScale;
165 public List<Coordinate> getClusterPoints() {
166 List<Coordinate> list = new ArrayList<Coordinate>(getClusterCount());
167 List<Double> points = cluster.getPoints(clusterRotation - getRadialDirection());
168 double separation = getClusterSeparation();
169 for (int i = 0; i < points.size() / 2; i++) {
170 list.add(new Coordinate(0, points.get(2 * i) * separation, points.get(2 * i + 1) * separation));
177 public Coordinate[] shiftCoordinates(Coordinate[] array) {
178 array = super.shiftCoordinates(array);
180 int count = getClusterCount();
184 List<Coordinate> points = getClusterPoints();
185 if (points.size() != count) {
186 throw new BugException("Inconsistent cluster configuration, cluster count=" + count +
187 " point count=" + points.size());
189 Coordinate[] newArray = new Coordinate[array.length * count];
190 for (int i = 0; i < array.length; i++) {
191 for (int j = 0; j < count; j++) {
192 newArray[i * count + j] = array[i].add(points.get(j));
202 //////////////// Motor mount /////////////////
205 public boolean isMotorMount() {
210 public void setMotorMount(boolean mount) {
211 if (motorMount == mount)
214 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
218 public Motor getMotor(String id) {
222 // Check whether the id is valid for the current rocket
223 RocketComponent root = this.getRoot();
224 if (!(root instanceof Rocket))
226 if (!((Rocket) root).isMotorConfigurationID(id))
229 return motors.get(id);
233 public void setMotor(String id, Motor motor) {
236 throw new IllegalArgumentException("Cannot set non-null motor for id null");
239 Motor current = motors.get(id);
240 if ((motor == null && current == null) ||
241 (motor != null && motor.equals(current)))
243 motors.put(id, motor);
244 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
248 public double getMotorDelay(String id) {
249 Double delay = ejectionDelays.get(id);
251 return Motor.PLUGGED;
256 public void setMotorDelay(String id, double delay) {
257 ejectionDelays.put(id, delay);
258 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
263 public int getMotorCount() {
264 return getClusterCount();
268 public double getMotorMountDiameter() {
269 return getInnerRadius() * 2;
273 public IgnitionEvent getIgnitionEvent() {
274 return ignitionEvent;
278 public void setIgnitionEvent(IgnitionEvent event) {
279 if (ignitionEvent == event)
281 ignitionEvent = event;
282 fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
287 public double getIgnitionDelay() {
288 return ignitionDelay;
292 public void setIgnitionDelay(double delay) {
293 if (MathUtil.equals(delay, ignitionDelay))
295 ignitionDelay = delay;
296 fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
301 public double getMotorOverhang() {
306 public void setMotorOverhang(double overhang) {
307 if (MathUtil.equals(this.overhang, overhang))
309 this.overhang = overhang;
310 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
315 public Coordinate getMotorPosition(String id) {
316 Motor motor = motors.get(id);
318 throw new IllegalArgumentException("No motor with id " + id + " defined.");
321 return new Coordinate(this.getLength() - motor.getLength() + this.getMotorOverhang());
326 * Copy the motor and ejection delay HashMaps.
328 * @see rocketcomponent.RocketComponent#copy()
330 @SuppressWarnings("unchecked")
332 protected RocketComponent copyWithOriginalID() {
333 RocketComponent c = super.copyWithOriginalID();
334 ((InnerTube) c).motors = (HashMap<String, Motor>) motors.clone();
335 ((InnerTube) c).ejectionDelays = (HashMap<String, Double>) ejectionDelays.clone();