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.preset.ComponentPreset;
10 import net.sf.openrocket.startup.Application;
11 import net.sf.openrocket.util.BugException;
12 import net.sf.openrocket.util.Coordinate;
13 import net.sf.openrocket.util.MathUtil;
17 * This class defines an inner tube that can be used as a motor mount. The component
18 * may also be clustered.
20 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
22 public class InnerTube extends ThicknessRingComponent
23 implements Clusterable, RadialParent, MotorMount {
24 private static final Translator trans = Application.getTranslator();
26 private ClusterConfiguration cluster = ClusterConfiguration.SINGLE;
27 private double clusterScale = 1.0;
28 private double clusterRotation = 0.0;
31 private boolean motorMount = false;
32 private HashMap<String, Double> ejectionDelays = new HashMap<String, Double>();
33 private HashMap<String, Motor> motors = new HashMap<String, Motor>();
34 private IgnitionEvent ignitionEvent = IgnitionEvent.AUTOMATIC;
35 private double ignitionDelay = 0;
36 private double overhang = 0;
44 this.setOuterRadius(0.019 / 2);
45 this.setInnerRadius(0.018 / 2);
46 this.setLength(0.070);
51 public double getInnerRadius(double x) {
52 return getInnerRadius();
57 public double getOuterRadius(double x) {
58 return getOuterRadius();
63 public String getComponentName() {
65 return trans.get("InnerTube.InnerTube");
69 public boolean allowsChildren() {
74 * Allow all InternalComponents to be added to this component.
77 public boolean isCompatible(Class<? extends RocketComponent> type) {
78 return InternalComponent.class.isAssignableFrom(type);
82 public ComponentPreset.Type getPresetType() {
83 return ComponentPreset.Type.BODY_TUBE;
87 protected void loadFromPreset(ComponentPreset preset) {
88 if ( preset.has(ComponentPreset.OUTER_DIAMETER) ) {
89 double outerDiameter = preset.get(ComponentPreset.OUTER_DIAMETER);
90 this.outerRadius = outerDiameter/2.0;
91 if ( preset.has(ComponentPreset.INNER_DIAMETER) ) {
92 double innerDiameter = preset.get(ComponentPreset.INNER_DIAMETER);
93 this.thickness = (outerDiameter-innerDiameter) / 2.0;
97 super.loadFromPreset(preset);
99 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
103 ///////////// Cluster methods //////////////
106 * Get the current cluster configuration.
107 * @return The current cluster configuration.
110 public ClusterConfiguration getClusterConfiguration() {
115 * Set the current cluster configuration.
116 * @param cluster The cluster configuration.
119 public void setClusterConfiguration(ClusterConfiguration cluster) {
120 this.cluster = cluster;
121 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
125 * Return the number of tubes in the cluster.
126 * @return Number of tubes in the current cluster.
129 public int getClusterCount() {
130 return cluster.getClusterCount();
134 * Get the cluster scaling. A value of 1.0 indicates that the tubes are packed
135 * touching each other, larger values separate the tubes and smaller values
136 * pack inside each other.
138 public double getClusterScale() {
143 * Set the cluster scaling.
144 * @see #getClusterScale()
146 public void setClusterScale(double scale) {
147 scale = Math.max(scale, 0);
148 if (MathUtil.equals(clusterScale, scale))
150 clusterScale = scale;
151 fireComponentChangeEvent(new ComponentChangeEvent(this, ComponentChangeEvent.MASS_CHANGE));
157 * @return the clusterRotation
159 public double getClusterRotation() {
160 return clusterRotation;
165 * @param rotation the clusterRotation to set
167 public void setClusterRotation(double rotation) {
168 rotation = MathUtil.reduce180(rotation);
169 if (clusterRotation == rotation)
171 this.clusterRotation = rotation;
172 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
177 * Return the distance between the closest two cluster inner tube center points.
178 * This is equivalent to the cluster scale multiplied by the tube diameter.
181 public double getClusterSeparation() {
182 return 2 * getOuterRadius() * clusterScale;
186 public List<Coordinate> getClusterPoints() {
187 List<Coordinate> list = new ArrayList<Coordinate>(getClusterCount());
188 List<Double> points = cluster.getPoints(clusterRotation - getRadialDirection());
189 double separation = getClusterSeparation();
190 for (int i = 0; i < points.size() / 2; i++) {
191 list.add(new Coordinate(0, points.get(2 * i) * separation, points.get(2 * i + 1) * separation));
198 public Coordinate[] shiftCoordinates(Coordinate[] array) {
199 array = super.shiftCoordinates(array);
201 int count = getClusterCount();
205 List<Coordinate> points = getClusterPoints();
206 if (points.size() != count) {
207 throw new BugException("Inconsistent cluster configuration, cluster count=" + count +
208 " point count=" + points.size());
210 Coordinate[] newArray = new Coordinate[array.length * count];
211 for (int i = 0; i < array.length; i++) {
212 for (int j = 0; j < count; j++) {
213 newArray[i * count + j] = array[i].add(points.get(j));
223 //////////////// Motor mount /////////////////
226 public boolean isMotorMount() {
231 public void setMotorMount(boolean mount) {
232 if (motorMount == mount)
235 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
239 public Motor getMotor(String id) {
243 // Check whether the id is valid for the current rocket
244 RocketComponent root = this.getRoot();
245 if (!(root instanceof Rocket))
247 if (!((Rocket) root).isMotorConfigurationID(id))
250 return motors.get(id);
254 public void setMotor(String id, Motor motor) {
257 throw new IllegalArgumentException("Cannot set non-null motor for id null");
260 Motor current = motors.get(id);
261 if ((motor == null && current == null) ||
262 (motor != null && motor.equals(current)))
264 motors.put(id, motor);
265 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
269 public double getMotorDelay(String id) {
270 Double delay = ejectionDelays.get(id);
272 return Motor.PLUGGED;
277 public void setMotorDelay(String id, double delay) {
278 ejectionDelays.put(id, delay);
279 fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
284 public int getMotorCount() {
285 return getClusterCount();
289 public double getMotorMountDiameter() {
290 return getInnerRadius() * 2;
294 public IgnitionEvent getIgnitionEvent() {
295 return ignitionEvent;
299 public void setIgnitionEvent(IgnitionEvent event) {
300 if (ignitionEvent == event)
302 ignitionEvent = event;
303 fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
308 public double getIgnitionDelay() {
309 return ignitionDelay;
313 public void setIgnitionDelay(double delay) {
314 if (MathUtil.equals(delay, ignitionDelay))
316 ignitionDelay = delay;
317 fireComponentChangeEvent(ComponentChangeEvent.EVENT_CHANGE);
322 public double getMotorOverhang() {
327 public void setMotorOverhang(double overhang) {
328 if (MathUtil.equals(this.overhang, overhang))
330 this.overhang = overhang;
331 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
336 public Coordinate getMotorPosition(String id) {
337 Motor motor = motors.get(id);
339 throw new IllegalArgumentException("No motor with id " + id + " defined.");
342 return new Coordinate(this.getLength() - motor.getLength() + this.getMotorOverhang());
347 * Copy the motor and ejection delay HashMaps.
349 * @see rocketcomponent.RocketComponent#copy()
351 @SuppressWarnings("unchecked")
353 protected RocketComponent copyWithOriginalID() {
354 RocketComponent c = super.copyWithOriginalID();
355 ((InnerTube) c).motors = (HashMap<String, Motor>) motors.clone();
356 ((InnerTube) c).ejectionDelays = (HashMap<String, Double>) ejectionDelays.clone();