--- /dev/null
+/*
+ * RecoveryDeviceHandler.java
+ */
+package net.sf.openrocket.file.rocksim;
+
+import net.sf.openrocket.aerodynamics.WarningSet;
+import net.sf.openrocket.material.Material;
+import net.sf.openrocket.rocketcomponent.RecoveryDevice;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import org.xml.sax.SAXException;
+
+import java.util.HashMap;
+
+/**
+ * A handler specific to streamers and parachutes. This is done because Rocksim allows any type of material to be
+ * used as a recovery device, which causes oddities with respect to densities. Density computation is overridden
+ * here to try to correctly compute a material's density in OpenRocket units.
+ *
+ * @param <C> either a Streamer or Parachute
+ */
+public abstract class RecoveryDeviceHandler<C extends RecoveryDevice> extends PositionDependentHandler<C> {
+
+ /**
+ * The thickness. Not used by every component, and some component handlers may parse it for their own purposes.
+ */
+ private double thickness = 0d;
+ /**
+ * The Rocksim calculated mass. Used only when not overridden and when Rocksim says density == 0 (Rocksim bug).
+ */
+ private Double calcMass = 0d;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings)
+ throws SAXException {
+ super.closeElement(element, attributes, content, warnings);
+
+ try {
+ if ("Thickness".equals(element)) {
+ thickness = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
+ }
+ if ("CalcMass".equals(element)) {
+ calcMass = Math.max(0d, Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_MASS);
+ }
+ }
+ catch (NumberFormatException nfe) {
+ warnings.add("Could not convert " + element + " value of " + content + ". It is expected to be a number.");
+ }
+ }
+
+
+ /**
+ * Compute the density. Rocksim does strange things with densities. For some streamer material it's in cubic,
+ * rather than square, units. In those cases it needs to be converted to an appropriate SURFACE material density.
+ *
+ * @param type the rocksim density
+ * @param rawDensity the density as specified in the Rocksim design file
+ * @return a value in OpenRocket SURFACE density units
+ */
+ protected double computeDensity(RocksimDensityType type, double rawDensity) {
+
+ double result;
+
+ if (rawDensity > 0d) {
+ //ROCKSIM_SURFACE is a square area density; compute normally
+ //ROCKSIM_LINE is a single length dimension (kg/m) but Rocksim ignores thickness for this type and treats
+ //it like a SURFACE.
+ if (RocksimDensityType.ROCKSIM_SURFACE.equals(type) || RocksimDensityType.ROCKSIM_LINE.equals(type)) {
+ result = rawDensity / RocksimDensityType.ROCKSIM_SURFACE.asOpenRocket();
+ }
+ //ROCKSIM_BULK is a cubic area density; multiple by thickness to make per square area; the result, when
+ //multiplied by the area will then equal Rocksim's computed mass.
+ else {
+ result = (rawDensity / type.asOpenRocket()) * thickness;
+ }
+ }
+ else {
+ result = calcMass / getComponent().getArea();
+ //A Rocksim bug on streamers/parachutes results in a 0 density at times. When that is detected, try
+ //to compute an approximate density from Rocksim's computed mass.
+ if (RocksimDensityType.ROCKSIM_BULK.equals(type)) {
+ //ROCKSIM_BULK is a cubic area density; multiple by thickness to make per square area
+ result *= thickness;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Set the relative position onto the component. This cannot be done directly because setRelativePosition is not
+ * public in all components.
+ *
+ * @param position the OpenRocket position
+ */
+ @Override
+ public void setRelativePosition(RocketComponent.Position position) {
+ getComponent().setRelativePosition(position);
+ }
+
+ /**
+ * Get the required type of material for this component. This is the OpenRocket type, which does NOT always
+ * correspond to Rocksim. Some streamer material is defined as BULK in the Rocksim file. In those cases
+ * it is adjusted in this handler.
+ *
+ * @return SURFACE
+ */
+ @Override
+ public Material.Type getMaterialType() {
+ return Material.Type.SURFACE;
+ }
+
+}