DGP - added DensityType parsing for recovery devices
authorrodinia814 <rodinia814@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sun, 21 Mar 2010 03:24:12 +0000 (03:24 +0000)
committerrodinia814 <rodinia814@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sun, 21 Mar 2010 03:24:12 +0000 (03:24 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@51 180e2498-e6e9-4542-8430-84ac67f01cd8

src/net/sf/openrocket/file/rocksim/BaseHandler.java
src/net/sf/openrocket/file/rocksim/ParachuteHandler.java
src/net/sf/openrocket/file/rocksim/PositionDependentHandler.java
src/net/sf/openrocket/file/rocksim/RecoveryDeviceHandler.java [new file with mode: 0644]
src/net/sf/openrocket/file/rocksim/RocksimDensityType.java [new file with mode: 0644]
src/net/sf/openrocket/file/rocksim/StreamerHandler.java
test/net/sf/openrocket/file/rocksim/StreamerHandlerTest.java

index e9a8469e10fb0d87c1b8a0a9c9512237903aedc2..700c2f9bdbcec4b38795d45b017d9bb932c35b93 100644 (file)
@@ -15,6 +15,8 @@ import java.util.HashMap;
 
 /**
  * An abstract base class that handles common parsing.  All Rocksim component handlers are subclassed from here.
+ *
+ * @param <C>   the specific RocketComponent subtype for which the concrete handler can create
  */
 public abstract class BaseHandler<C extends RocketComponent> extends ElementHandler {
 
@@ -30,6 +32,11 @@ public abstract class BaseHandler<C extends RocketComponent> extends ElementHand
      * The density of the material in the component.
      */
     private Double density = 0d;
+    /**
+     * The internal Rocksim density type.
+     */
+    private RocksimDensityType densityType = RocksimDensityType.ROCKSIM_BULK;
+
     /**
      * The material name.
      */
@@ -57,31 +64,54 @@ public abstract class BaseHandler<C extends RocketComponent> extends ElementHand
                 mass = Math.max(0d, Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_MASS);
             }
             if ("Density".equals(element)) {
-                density = Math.max(0d, Double.parseDouble(content) / getDensityConversion());
+                density = Math.max(0d, Double.parseDouble(content) );
             }
             if ("KnownCG".equals(element)) {
                 cg = Math.max(0d, Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH);
             }
-            if ("UseKnownCG".equals(element)) {
+            if ("UseKnownCG".equals(element)) {  //Rocksim sets UseKnownCG to true to control the override of both cg and mass
                 boolean override = "1".equals(content);
                 setOverride(component, override, mass, cg);
             }
+            if ("DensityType".equals(element)) {
+                densityType = RocksimDensityType.fromCode(Integer.parseInt(content));
+            }
         }
         catch (NumberFormatException nfe) {
             warnings.add("Could not convert " + element + " value of " + content + ".  It is expected to be a number.");
         }
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public void endHandler(String element, HashMap<String, String> attributes, String content, WarningSet warnings)
             throws SAXException {
         /* Because of the order of XML elements in Rocksim, not all information is known at the time it really needs
            to be acted upon.  So we keep temporary instance variables to be used here at the end of the parsing.
          */
+        density = computeDensity(densityType, density);
         RocketComponent component = getComponent();
         updateComponentMaterial(component, materialName, getMaterialType(), density);
     }
 
+    /**
+     * 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.
+     * Some G10 fiberglass materials are in cubic units, other G10 fiberglass is in square units.  And due to a
+     * Rocksim bug, some densities are 0 when they clearly should not be.
+     *
+     * This may be overridden for specific component density computations.
+     *
+     * @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) {
+        return rawDensity / type.asOpenRocket();
+    }
+
     /**
      * If the Rocksim component does not override the mass, then create a Material based upon the density defined
      * for that component.  This *should* result in a consistent representation of Cg between Rocksim and OpenRocket.
@@ -163,23 +193,6 @@ public abstract class BaseHandler<C extends RocketComponent> extends ElementHand
         return Material.newMaterial(type, "RS: " + name, density, true);
     }
 
-    /**
-     * Get the appropriate density conversion for different types of materials.
-     *
-     * @return a conversion value that is assumed to be in Rocksim Units / OpenRocket Units
-     */
-    private double getDensityConversion() {
-        switch (getMaterialType()) {
-            case LINE:
-                return RocksimHandler.ROCKSIM_TO_OPENROCKET_LINE_DENSITY;
-            case SURFACE:
-                return RocksimHandler.ROCKSIM_TO_OPENROCKET_SURFACE_DENSITY;
-            case BULK:
-            default:
-                return RocksimHandler.ROCKSIM_TO_OPENROCKET_BULK_DENSITY;
-        }
-    }
-
     /**
      * Set the material onto an instance of RocketComponent.  This is done because only some subtypes of RocketComponent
      * have the setMaterial method.  Unfortunately the supertype cannot be used.
index f104a07e59f52678f027786ecf2fbe6de8947de9..6cadaff56cdd3fbd3b800b638c2802170f709da0 100644 (file)
@@ -17,9 +17,8 @@ import java.util.HashMap;
 
 /**
  * A SAX handler for Rocksim's Parachute XML type.
- * <p/>
  */
-class ParachuteHandler extends PositionDependentHandler<Parachute> {
+class ParachuteHandler extends RecoveryDeviceHandler<Parachute> {
     /**
      * The OpenRocket Parachute instance
      */
@@ -43,11 +42,17 @@ class ParachuteHandler extends PositionDependentHandler<Parachute> {
         c.addChild(chute);
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) {
         return PlainTextHandler.INSTANCE;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings)
             throws SAXException {
@@ -73,8 +78,7 @@ class ParachuteHandler extends PositionDependentHandler<Parachute> {
                 chute.setLineCount(Math.max(0, Integer.parseInt(content)));
             }
             if ("ShroudLineLen".equals(element)) {
-                chute.setLineLength(Math.max(0, Double.parseDouble(
-                        content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH));
+                chute.setLineLength(Math.max(0, Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH));
             }
             if ("SpillHoleDia".equals(element)) {
                 //Not supported in OpenRocket
@@ -108,25 +112,5 @@ class ParachuteHandler extends PositionDependentHandler<Parachute> {
         return chute;
     }
 
-    /**
-     * 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
-     */
-    public void setRelativePosition(RocketComponent.Position position) {
-        chute.setRelativePosition(position);
-    }
-
-    /**
-     * Get the required type of material for this component.
-     *
-     * @return BULK
-     */
-    @Override
-    public Material.Type getMaterialType() {
-        return Material.Type.SURFACE;
-    }
-
 }
 
index 9d457227c470c4113783829e6ef38bd21b16c278..c65e1aec9b79007e8014694021f97897cd0e9719 100644 (file)
@@ -10,17 +10,22 @@ import org.xml.sax.SAXException;
 import java.util.HashMap;
 
 /**
- * An abstract base class that handles position dependencies for all lower level components that 
+ * An abstract base class that handles position dependencies for all lower level components that
  * are position aware.
+ *
+ * @param <C>   the specific position dependent RocketComponent subtype for which the concrete handler can create
  */
 public abstract class PositionDependentHandler<C extends RocketComponent> extends BaseHandler<C> {
 
     /** Temporary position value. */
     private Double positionValue;
-    
+
     /** Temporary position. */
     private RocketComponent.Position position;
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings)
             throws SAXException {
@@ -33,12 +38,12 @@ public abstract class PositionDependentHandler<C extends RocketComponent> extend
                     content)).asOpenRocket();
         }
     }
-    
+
     /**
      * This method sets the position information onto the component.  Rocksim splits the location/position
      * information into two disparate data elements.  Both pieces of data are necessary to map into OpenRocket's
      * position model.
-     * 
+     *
      * @param element     the element name
      * @param attributes  the attributes
      * @param content     the content of the element
@@ -54,16 +59,16 @@ public abstract class PositionDependentHandler<C extends RocketComponent> extend
     }
 
     /**
-     * Set the relative position onto the component.  This cannot be done directly because setRelativePosition is not 
+     * 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
      */
     protected abstract void setRelativePosition(RocketComponent.Position position);
-    
+
     /**
      * Set the position of a component.
-     * 
+     *
      * @param component  the component
      * @param position   the relative position
      * @param location   the actual position value
@@ -76,5 +81,5 @@ public abstract class PositionDependentHandler<C extends RocketComponent> extend
             component.setPositionValue(location);
         }
     }
-    
+
 }
diff --git a/src/net/sf/openrocket/file/rocksim/RecoveryDeviceHandler.java b/src/net/sf/openrocket/file/rocksim/RecoveryDeviceHandler.java
new file mode 100644 (file)
index 0000000..387e29b
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * 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;
+    }
+
+}
diff --git a/src/net/sf/openrocket/file/rocksim/RocksimDensityType.java b/src/net/sf/openrocket/file/rocksim/RocksimDensityType.java
new file mode 100644 (file)
index 0000000..c672b62
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * RocksimDensityType.java
+ */
+package net.sf.openrocket.file.rocksim;
+
+/**
+ * Models the nose cone shape of a rocket.  Maps from Rocksim's notion to OpenRocket's.
+ */
+enum RocksimDensityType {
+    ROCKSIM_BULK   (0, RocksimHandler.ROCKSIM_TO_OPENROCKET_BULK_DENSITY),
+    ROCKSIM_SURFACE(1, RocksimHandler.ROCKSIM_TO_OPENROCKET_SURFACE_DENSITY),
+    ROCKSIM_LINE   (2, RocksimHandler.ROCKSIM_TO_OPENROCKET_LINE_DENSITY);
+
+    /** The Rocksim enumeration value. Sent in XML. */
+    private final int ordinal;
+
+    /** The corresponding OpenRocket shape. */
+    private final double conversion;
+
+    /**
+     * Constructor.
+     *
+     * @param idx            the Rocksim shape code
+     * @param theConversion  the numerical conversion ratio to OpenRocket
+     */
+    private RocksimDensityType(int idx, double theConversion) {
+        ordinal = idx;
+        conversion = theConversion;
+    }
+
+    /**
+     * Get the OpenRocket shape that corresponds to the Rocksim value.
+     *
+     * @return a conversion
+     */
+    public double asOpenRocket() {
+        return conversion;
+    }
+
+    /**
+     * Lookup an instance of this enum based upon the Rocksim code.
+     *
+     * @param rocksimDensityType  the Rocksim code (from XML)
+     * @return an instance of this enum
+     */
+    public static RocksimDensityType fromCode(int rocksimDensityType) {
+        RocksimDensityType[] values = values();
+        for (RocksimDensityType value : values) {
+            if (value.ordinal == rocksimDensityType) {
+                return value;
+            }
+        }
+        return ROCKSIM_BULK; //Default
+    }
+}
+
index 9db287d2731c0d6e5c975e615c809fc9df7caa08..d638f62bc44f36c55969dd5ba006ba7d150fa1ab 100644 (file)
@@ -6,7 +6,6 @@ package net.sf.openrocket.file.rocksim;
 import net.sf.openrocket.aerodynamics.WarningSet;
 import net.sf.openrocket.file.simplesax.ElementHandler;
 import net.sf.openrocket.file.simplesax.PlainTextHandler;
-import net.sf.openrocket.material.Material;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
 import net.sf.openrocket.rocketcomponent.Streamer;
 import org.xml.sax.SAXException;
@@ -16,7 +15,7 @@ import java.util.HashMap;
 /**
  * A SAX handler for Streamer components.
  */
-class StreamerHandler extends PositionDependentHandler<Streamer> {
+class StreamerHandler extends RecoveryDeviceHandler<Streamer> {
 
     /**
      * The OpenRocket Streamer.
@@ -37,11 +36,17 @@ class StreamerHandler extends PositionDependentHandler<Streamer> {
         c.addChild(streamer);
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) {
         return PlainTextHandler.INSTANCE;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings)
             throws SAXException {
@@ -66,30 +71,13 @@ class StreamerHandler extends PositionDependentHandler<Streamer> {
         }
     }
 
-    @Override
-    public Streamer getComponent() {
-        return streamer;
-    }
-
     /**
-     * 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
+     * {@inheritDoc}
      */
     @Override
-    public void setRelativePosition(RocketComponent.Position position) {
-        streamer.setRelativePosition(position);
+    public Streamer getComponent() {
+        return streamer;
     }
 
-    /**
-     * Get the required type of material for this component.
-     *
-     * @return BULK
-     */
-    @Override
-    public Material.Type getMaterialType() {
-        return Material.Type.SURFACE;
-    }
 }
 
index 964ae52add94234f8c02efc66a131ad9305712cf..3051398d1d417c19b1223ec8f496a161c222476a 100644 (file)
@@ -6,7 +6,6 @@ package net.sf.openrocket.file.rocksim;
 import junit.framework.Test;
 import junit.framework.TestSuite;
 import net.sf.openrocket.aerodynamics.WarningSet;
-import net.sf.openrocket.database.Databases;
 import net.sf.openrocket.file.simplesax.PlainTextHandler;
 import net.sf.openrocket.material.Material;
 import net.sf.openrocket.rocketcomponent.BodyTube;
@@ -77,7 +76,7 @@ public class StreamerHandlerTest extends RocksimTestBase {
      * @throws Exception thrown if something goes awry
      */
     public void testCloseElement() throws Exception {
-        
+
         BodyTube tube = new BodyTube();
         StreamerHandler handler = new StreamerHandler(tube);
         Streamer component = (Streamer) getField(handler, "streamer");
@@ -91,7 +90,7 @@ public class StreamerHandlerTest extends RocksimTestBase {
         handler.closeElement("Width", attributes, "foo", warnings);
         assertEquals(1, warnings.size());
         warnings.clear();
-        
+
         handler.closeElement("Len", attributes, "-1", warnings);
         assertEquals(0d, component.getStripLength());
         handler.closeElement("Len", attributes, "10", warnings);
@@ -104,7 +103,7 @@ public class StreamerHandlerTest extends RocksimTestBase {
 
         handler.closeElement("Name", attributes, "Test Name", warnings);
         assertEquals("Test Name", component.getName());
-        
+
         handler.closeElement("DragCoefficient", attributes, "0.94", warnings);
         assertEquals(0.94d, component.getCD());
         handler.closeElement("DragCoefficient", attributes, "-0.94", warnings);
@@ -183,13 +182,38 @@ public class StreamerHandlerTest extends RocksimTestBase {
         handler.closeElement("LocationMode", attributes, "1", warnings);
         handler.endHandler("Streamer", attributes, null, warnings);
         assertEquals(RocketComponent.Position.ABSOLUTE, component.getRelativePosition());
-        assertEquals(component.getPositionValue(), -10d/RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH);  
-        
+        assertEquals(component.getPositionValue(), -10d/RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH);
+
         handler.closeElement("Xb", attributes, "-10", warnings);
         handler.closeElement("LocationMode", attributes, "2", warnings);
         handler.endHandler("Streamer", attributes, null, warnings);
         assertEquals(RocketComponent.Position.BOTTOM, component.getRelativePosition());
-        assertEquals(component.getPositionValue(), 10d/RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH);  
+        assertEquals(component.getPositionValue(), 10d/RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH);
+
+        handler.closeElement("Thickness", attributes, "0.02", warnings);
+        assertEquals(0.01848, handler.computeDensity(RocksimDensityType.ROCKSIM_BULK, 924d));
+
+        //Test Density Type 0 (Bulk)
+        handler.closeElement("Density", attributes, "924.0", warnings);
+        handler.closeElement("DensityType", attributes, "0", warnings);
+        handler.endHandler("Streamer", attributes, null, warnings);
+        assertEquals(0.01848d, component.getMaterial().getDensity());
+
+        //Test Density Type 1 (Surface)
+        handler.closeElement("Density", attributes, "0.006685", warnings);
+        handler.closeElement("DensityType", attributes, "1", warnings);
+        handler.endHandler("Streamer", attributes, null, warnings);
+        assertTrue(Math.abs(0.06685d - component.getMaterial().getDensity()) < 0.00001);
+
+        //Test Density Type 2 (Line)
+        handler.closeElement("Density", attributes, "0.223225", warnings);
+        handler.closeElement("DensityType", attributes, "2", warnings);
+        handler.closeElement("Len", attributes, "3810.", warnings);
+        handler.closeElement("Width", attributes, "203.2", warnings);
+        handler.endHandler("Streamer", attributes, null, warnings);
+
+        assertEquals(1.728190092, component.getMass());
+
     }
-    
+
 }