package net.sf.openrocket.rocketcomponent; import java.util.ArrayList; import java.util.List; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.preset.ComponentPreset; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; /** * This class defines an inner tube that can be used as a motor mount. The component * may also be clustered. * * @author Sampo Niskanen <sampo.niskanen@iki.fi> */ public class InnerTube extends ThicknessRingComponent implements Clusterable, RadialParent, MotorMount { private static final Translator trans = Application.getTranslator(); private ClusterConfiguration cluster = ClusterConfiguration.SINGLE; private double clusterScale = 1.0; private double clusterRotation = 0.0; private boolean motorMount = false; private double overhang = 0; private FlightConfigurationImpl<MotorConfiguration> motorConfigurations; private FlightConfigurationImpl<IgnitionConfiguration> ignitionConfigurations; /** * Main constructor. */ public InnerTube() { // A-C motor size: this.setOuterRadius(0.019 / 2); this.setInnerRadius(0.018 / 2); this.setLength(0.070); this.motorConfigurations = new MotorFlightConfigurationImpl<MotorConfiguration>(this, ComponentChangeEvent.MOTOR_CHANGE, MotorConfiguration.NO_MOTORS); this.ignitionConfigurations = new FlightConfigurationImpl<IgnitionConfiguration>(this, ComponentChangeEvent.EVENT_CHANGE, new IgnitionConfiguration()); } @Override public double getInnerRadius(double x) { return getInnerRadius(); } @Override public double getOuterRadius(double x) { return getOuterRadius(); } @Override public String getComponentName() { //// Inner Tube return trans.get("InnerTube.InnerTube"); } @Override public boolean allowsChildren() { return true; } /** * Allow all InternalComponents to be added to this component. */ @Override public boolean isCompatible(Class<? extends RocketComponent> type) { return InternalComponent.class.isAssignableFrom(type); } @Override public ComponentPreset.Type getPresetType() { return ComponentPreset.Type.BODY_TUBE; } @Override protected void loadFromPreset(ComponentPreset preset) { if (preset.has(ComponentPreset.OUTER_DIAMETER)) { double outerDiameter = preset.get(ComponentPreset.OUTER_DIAMETER); this.outerRadius = outerDiameter / 2.0; if (preset.has(ComponentPreset.INNER_DIAMETER)) { double innerDiameter = preset.get(ComponentPreset.INNER_DIAMETER); this.thickness = (outerDiameter - innerDiameter) / 2.0; } } super.loadFromPreset(preset); fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); } ///////////// Cluster methods ////////////// /** * Get the current cluster configuration. * @return The current cluster configuration. */ @Override public ClusterConfiguration getClusterConfiguration() { return cluster; } /** * Set the current cluster configuration. * @param cluster The cluster configuration. */ @Override public void setClusterConfiguration(ClusterConfiguration cluster) { this.cluster = cluster; fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); } /** * Return the number of tubes in the cluster. * @return Number of tubes in the current cluster. */ @Override public int getClusterCount() { return cluster.getClusterCount(); } /** * Get the cluster scaling. A value of 1.0 indicates that the tubes are packed * touching each other, larger values separate the tubes and smaller values * pack inside each other. */ public double getClusterScale() { return clusterScale; } /** * Set the cluster scaling. * @see #getClusterScale() */ public void setClusterScale(double scale) { scale = Math.max(scale, 0); if (MathUtil.equals(clusterScale, scale)) return; clusterScale = scale; fireComponentChangeEvent(new ComponentChangeEvent(this, ComponentChangeEvent.MASS_CHANGE)); } /** * @return the clusterRotation */ public double getClusterRotation() { return clusterRotation; } /** * @param rotation the clusterRotation to set */ public void setClusterRotation(double rotation) { rotation = MathUtil.reduce180(rotation); if (clusterRotation == rotation) return; this.clusterRotation = rotation; fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); } /** * Return the distance between the closest two cluster inner tube center points. * This is equivalent to the cluster scale multiplied by the tube diameter. */ @Override public double getClusterSeparation() { return 2 * getOuterRadius() * clusterScale; } public List<Coordinate> getClusterPoints() { List<Coordinate> list = new ArrayList<Coordinate>(getClusterCount()); List<Double> points = cluster.getPoints(clusterRotation - getRadialDirection()); double separation = getClusterSeparation(); for (int i = 0; i < points.size() / 2; i++) { list.add(new Coordinate(0, points.get(2 * i) * separation, points.get(2 * i + 1) * separation)); } return list; } @Override public Coordinate[] shiftCoordinates(Coordinate[] array) { array = super.shiftCoordinates(array); int count = getClusterCount(); if (count == 1) return array; List<Coordinate> points = getClusterPoints(); if (points.size() != count) { throw new BugException("Inconsistent cluster configuration, cluster count=" + count + " point count=" + points.size()); } Coordinate[] newArray = new Coordinate[array.length * count]; for (int i = 0; i < array.length; i++) { for (int j = 0; j < count; j++) { newArray[i * count + j] = array[i].add(points.get(j)); } } return newArray; } //////////////// Motor mount ///////////////// @Override public FlightConfiguration<MotorConfiguration> getMotorConfiguration() { return motorConfigurations; } @Override public FlightConfiguration<IgnitionConfiguration> getIgnitionConfiguration() { return ignitionConfigurations; } @Override public void cloneFlightConfiguration(String oldConfigId, String newConfigId) { motorConfigurations.cloneFlightConfiguration(oldConfigId, newConfigId); ignitionConfigurations.cloneFlightConfiguration(oldConfigId, newConfigId); } @Override public boolean isMotorMount() { return motorMount; } @Override public void setMotorMount(boolean mount) { if (motorMount == mount) return; motorMount = mount; fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE); } @Override public double getMotorMountDiameter() { return getInnerRadius() * 2; } @SuppressWarnings("deprecation") @Deprecated @Override public int getMotorCount() { return getClusterCount(); } @SuppressWarnings("deprecation") @Deprecated @Override public Motor getMotor(String id) { return this.motorConfigurations.get(id).getMotor(); } @SuppressWarnings("deprecation") @Deprecated @Override public double getMotorDelay(String id) { return this.motorConfigurations.get(id).getEjectionDelay(); } @Override public double getMotorOverhang() { return overhang; } @Override public void setMotorOverhang(double overhang) { if (MathUtil.equals(this.overhang, overhang)) return; this.overhang = overhang; fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); } @Override public Coordinate getMotorPosition(String id) { Motor motor = getMotor(id); if (motor == null) { throw new IllegalArgumentException("No motor with id " + id + " defined."); } return new Coordinate(this.getLength() - motor.getLength() + this.getMotorOverhang()); } @Override protected RocketComponent copyWithOriginalID() { InnerTube copy = (InnerTube) super.copyWithOriginalID(); copy.motorConfigurations = new FlightConfigurationImpl<MotorConfiguration>(motorConfigurations, copy, ComponentChangeEvent.MOTOR_CHANGE); copy.ignitionConfigurations = new FlightConfigurationImpl<IgnitionConfiguration>(ignitionConfigurations, copy, ComponentChangeEvent.EVENT_CHANGE); return copy; } /** * For a given coordinate that represents one tube in a cluster, create an instance of that tube. Must be called * once for each tube in the cluster. * * @param coord the coordinate of the clustered tube to create * @param splitName the name of the individual tube * @param theInnerTube the 'parent' from which this tube will be created. * * @return an instance of an inner tube that represents ONE of the clustered tubes in the cluster represented * by <code>theInnerTube</code> */ public static InnerTube makeIndividualClusterComponent(Coordinate coord, String splitName, RocketComponent theInnerTube) { InnerTube copy = (InnerTube) theInnerTube.copy(); copy.setClusterConfiguration(ClusterConfiguration.SINGLE); copy.setClusterRotation(0.0); copy.setClusterScale(1.0); copy.setRadialShift(coord.y, coord.z); copy.setName(splitName); return copy; } }