updates for 0.9.3
authorplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sun, 30 Aug 2009 15:46:18 +0000 (15:46 +0000)
committerplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Sun, 30 Aug 2009 15:46:18 +0000 (15:46 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@20 180e2498-e6e9-4542-8430-84ac67f01cd8

57 files changed:
.classpath
ChangeLog
TODO
build.properties
pix-src/eventicons/event-ejection-charge.xcf.gz [new file with mode: 0644]
pix-src/eventicons/event-ground-hit.xcf.gz [new file with mode: 0644]
pix-src/eventicons/event-launch.xcf.gz [new file with mode: 0644]
pix-src/eventicons/event-recovery-device-deployment.xcf.gz [new file with mode: 0644]
pix/eventicons/copyright.txt
pix/eventicons/event-ejection-charge.png [new file with mode: 0644]
pix/eventicons/event-ground-hit.png [new file with mode: 0644]
pix/eventicons/event-launch.png [new file with mode: 0644]
pix/eventicons/event-recovery-device-deployment.png [new file with mode: 0644]
pix/icon/icon-about.png [new file with mode: 0644]
src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java
src/net/sf/openrocket/database/Databases.java
src/net/sf/openrocket/file/GeneralMotorLoader.java
src/net/sf/openrocket/file/MotorLoader.java
src/net/sf/openrocket/file/OpenRocketLoader.java
src/net/sf/openrocket/file/RASPMotorLoader.java
src/net/sf/openrocket/file/RockSimMotorLoader.java
src/net/sf/openrocket/file/openrocket/RocketComponentSaver.java
src/net/sf/openrocket/gui/configdialog/MotorConfig.java
src/net/sf/openrocket/gui/dialogs/AboutDialog.java
src/net/sf/openrocket/gui/dialogs/BugReportDialog.java
src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java
src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java
src/net/sf/openrocket/gui/plot/PlotDialog.java
src/net/sf/openrocket/gui/scalefigure/RocketFigure.java
src/net/sf/openrocket/motor/Manufacturer.java [new file with mode: 0644]
src/net/sf/openrocket/motor/Motor.java [new file with mode: 0644]
src/net/sf/openrocket/motor/ThrustCurveMotor.java [new file with mode: 0644]
src/net/sf/openrocket/rocketcomponent/BodyTube.java
src/net/sf/openrocket/rocketcomponent/InnerTube.java
src/net/sf/openrocket/rocketcomponent/Motor.java [deleted file]
src/net/sf/openrocket/rocketcomponent/MotorMount.java
src/net/sf/openrocket/rocketcomponent/Rocket.java
src/net/sf/openrocket/rocketcomponent/ThrustCurveMotor.java [deleted file]
src/net/sf/openrocket/simulation/FlightSimulator.java
src/net/sf/openrocket/util/Analysis.java [deleted file]
src/net/sf/openrocket/util/Coordinate.java
src/net/sf/openrocket/util/Icons.java
src/net/sf/openrocket/util/MathUtil.java
src/net/sf/openrocket/util/Prefs.java
src/net/sf/openrocket/util/Rotation2D.java
src/net/sf/openrocket/util/Test.java
src/net/sf/openrocket/util/TextUtil.java
src/net/sf/openrocket/utils/Analysis.java [new file with mode: 0644]
src/net/sf/openrocket/utils/MotorCheck.java
src/net/sf/openrocket/utils/MotorCompare.java
src/net/sf/openrocket/utils/MotorPrinter.java
test/Test.java [new file with mode: 0644]
test/net/sf/openrocket/motor/ManufacturerTest.java [new file with mode: 0644]
test/net/sf/openrocket/util/CoordinateTest.java [new file with mode: 0644]
test/net/sf/openrocket/util/MathUtilTest.java [new file with mode: 0644]
test/net/sf/openrocket/util/Rotation2DTest.java [new file with mode: 0644]
test/net/sf/openrocket/util/TextUtilTest.java [new file with mode: 0644]

index 290b5a5e7e5ac2c909782a3ad75c7ec2a71b6ac2..fc1bd663e3cfeaf270cd477ca9dd1c58b5bd98be 100644 (file)
@@ -2,10 +2,12 @@
 <classpath>
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="src" path="extra-src"/>
+       <classpathentry kind="src" path="test"/>
        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
        <classpathentry kind="lib" path="/home/sampo/Projects/OpenRocket/lib/miglayout15-swing.jar"/>
        <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/JCommon 1.0.16"/>
        <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/JFreeChart 1.0.13"/>
        <classpathentry kind="lib" path="/home/sampo/Projects/OpenRocket/extra-lib/RXTXcomm.jar"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
        <classpathentry kind="output" path="bin"/>
 </classpath>
index 75658dd240c389f99e87c8a85faee7f1fc29fb37..44f2149e88b12951940fb5a32ba10d96e65c169e 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2009-08-29  Sampo Niskanen
+
+       * Extracted motor manufacturer into separate class
+       * Started writing unit tests
+
+2009-08-28  Sampo Niskanen
+
+       * Added icon and source info to About dialog
+       * Finalized flight event plot icons
+       
 2009-08-27  Sampo Niskanen
 
        * Allow clicking on label to toggle checkbox in two tables
diff --git a/TODO b/TODO
index 6bcc1be1f3e57fd4b914bf442299b47c89a44c76..0309f3e276f099e52a23bcf5425eca37d0a39892 100644 (file)
--- a/TODO
+++ b/TODO
@@ -4,15 +4,11 @@ Feature roadmap for OpenRocket 1.0
 
 Must-have:
 
-- Draw remaining event icons (for 0.9.3)
-- Change automatic exception reporting success code to 202 (for 0.9.3)
-- Test automatic exception reporting (for 0.9.3)
 
 - Allow editing user-defined materials
 - Go through thrust curves and correct errors
 - Add styrofoam and depron materials
 - Through-the-wall fins
-- Update "About" dialog with icon and source info
 
 
 Bugs:
@@ -56,4 +52,7 @@ Done:
 - Read more thrust curve formats
 - Showing events in plots
 - Table boolean selecting by clicking label
+- Test automatic exception reporting (for 0.9.3)
+- Draw remaining event icons (for 0.9.3)
+- Update "About" dialog with icon and source info
 
index a4010f5ad871fbaad75c40bd97e4179b71f9cb75..4bf3b9e31409b4cf9d11a98f92c72251b5f3d883 100644 (file)
@@ -1,8 +1,12 @@
 
 # The OpenRocket build version
+
 build.version=0.9.3pre
 
+
 # The source of the package.  When building a package for a specific
 # distribution (Debian, Fedora etc.), this should be changed appropriately!
+# This is displayed in the About dialog and included in bug reports.
+
 build.source=default
 
diff --git a/pix-src/eventicons/event-ejection-charge.xcf.gz b/pix-src/eventicons/event-ejection-charge.xcf.gz
new file mode 100644 (file)
index 0000000..206593e
Binary files /dev/null and b/pix-src/eventicons/event-ejection-charge.xcf.gz differ
diff --git a/pix-src/eventicons/event-ground-hit.xcf.gz b/pix-src/eventicons/event-ground-hit.xcf.gz
new file mode 100644 (file)
index 0000000..cbce15f
Binary files /dev/null and b/pix-src/eventicons/event-ground-hit.xcf.gz differ
diff --git a/pix-src/eventicons/event-launch.xcf.gz b/pix-src/eventicons/event-launch.xcf.gz
new file mode 100644 (file)
index 0000000..a356787
Binary files /dev/null and b/pix-src/eventicons/event-launch.xcf.gz differ
diff --git a/pix-src/eventicons/event-recovery-device-deployment.xcf.gz b/pix-src/eventicons/event-recovery-device-deployment.xcf.gz
new file mode 100644 (file)
index 0000000..c33abef
Binary files /dev/null and b/pix-src/eventicons/event-recovery-device-deployment.xcf.gz differ
index 9fa8d1b002316524a3a3aed224b6a701ff5ca623..50170ef5bad94ea0a219859afffefd6b8de7315a 100644 (file)
@@ -1,5 +1,6 @@
 
+event-simulation-end.png from famfamfam Silk icon set 
+(http://www.famfamfam.com/lab/icons/silk/)
 
-event-simulation-end.png from famfamfam icon set
 
-All others custom-drawn.
+All others custom-drawn by Sampo Niskanen.
diff --git a/pix/eventicons/event-ejection-charge.png b/pix/eventicons/event-ejection-charge.png
new file mode 100644 (file)
index 0000000..d58eb8e
Binary files /dev/null and b/pix/eventicons/event-ejection-charge.png differ
diff --git a/pix/eventicons/event-ground-hit.png b/pix/eventicons/event-ground-hit.png
new file mode 100644 (file)
index 0000000..583e402
Binary files /dev/null and b/pix/eventicons/event-ground-hit.png differ
diff --git a/pix/eventicons/event-launch.png b/pix/eventicons/event-launch.png
new file mode 100644 (file)
index 0000000..85d714d
Binary files /dev/null and b/pix/eventicons/event-launch.png differ
diff --git a/pix/eventicons/event-recovery-device-deployment.png b/pix/eventicons/event-recovery-device-deployment.png
new file mode 100644 (file)
index 0000000..df701c1
Binary files /dev/null and b/pix/eventicons/event-recovery-device-deployment.png differ
diff --git a/pix/icon/icon-about.png b/pix/icon/icon-about.png
new file mode 100644 (file)
index 0000000..0ce17f7
Binary files /dev/null and b/pix/icon/icon-about.png differ
index efdd14b15ad412e81ca46ffc59a82463f2d6d085..35d53a18578e4dd82148e163144f8b5584b74fcd 100644 (file)
@@ -5,8 +5,8 @@ import static net.sf.openrocket.util.MathUtil.pow2;
 import java.util.Iterator;
 import java.util.Map;
 
+import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.rocketcomponent.Configuration;
-import net.sf.openrocket.rocketcomponent.Motor;
 import net.sf.openrocket.rocketcomponent.MotorMount;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
index 3f4d8e20fe729adab0ff6c864e53994e7a6c61ff..f3fa4bcf90295114038d1b5eeec9d0037325555b 100644 (file)
@@ -8,7 +8,7 @@ import java.util.ArrayList;
 import net.sf.openrocket.file.GeneralMotorLoader;
 import net.sf.openrocket.material.Material;
 import net.sf.openrocket.material.MaterialStorage;
-import net.sf.openrocket.rocketcomponent.Motor;
+import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.util.JarUtil;
 import net.sf.openrocket.util.MathUtil;
 import net.sf.openrocket.util.Prefs;
@@ -229,7 +229,7 @@ public class Databases {
                        boolean match = true;
                        if (type != null  &&  type != m.getMotorType())
                                match = false;
-                       else if (manufacturer != null  &&  !manufacturer.equalsIgnoreCase(m.getManufacturer()))
+                       else if (manufacturer != null  &&  !m.getManufacturer().matches(manufacturer))
                                match = false;
                        else if (designation != null  &&  !designation.equalsIgnoreCase(m.getDesignation()))
                                match = false;
index cd4ec007e6151acde1f73a48258f2a582b5d6b8e..5981c2d0879350a9c456573ef1e9efbf535bd10e 100644 (file)
@@ -6,7 +6,7 @@ import java.io.Reader;
 import java.nio.charset.Charset;
 import java.util.List;
 
-import net.sf.openrocket.rocketcomponent.Motor;
+import net.sf.openrocket.motor.Motor;
 
 /**
  * A motor loader class that detects the file type based on the file name extension.
index 2988a03e8b6ac4f78c1ec1f01b7be763d04103f9..004b14f1339e02c774cd2cc06f1eaa2dea34af15 100644 (file)
@@ -8,174 +8,15 @@ import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
-import net.sf.openrocket.rocketcomponent.Motor;
+import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.util.MathUtil;
 
 
 public abstract class MotorLoader implements Loader<Motor> {
        
        
-       /** 
-        * Manufacturer codes to expand.  These are general translations that are used
-        * both in RASP and RockSim engine files.
-        */
-       private static final Map<String,String> MANUFACTURER_CODES =
-               new HashMap<String,String>();
-       static {
-               
-               /*
-                * TODO: CRITICAL: Should names have Inc. LLC. etc?
-                */
-               
-               // AeroTech has many name combinations...
-               for (String s: new String[] { "A", "AT", "AERO", "AEROT", "AEROTECH" }) {
-                       MANUFACTURER_CODES.put(s, "AeroTech");
-                       MANUFACTURER_CODES.put(s+"-RMS", "AeroTech");
-                       MANUFACTURER_CODES.put(s+"/RMS", "AeroTech");
-                       MANUFACTURER_CODES.put(s+"-RCS", "AeroTech");
-                       MANUFACTURER_CODES.put(s+"/RCS", "AeroTech");
-                       MANUFACTURER_CODES.put("RCS-" + s, "AeroTech");
-                       MANUFACTURER_CODES.put("RCS/" + s, "AeroTech");
-                       MANUFACTURER_CODES.put(s+"-APOGEE", "AeroTech");
-                       MANUFACTURER_CODES.put(s+"/APOGEE", "AeroTech");
-               }
-               MANUFACTURER_CODES.put("ISP", "AeroTech");
-               
-               MANUFACTURER_CODES.put("AHR", "Alpha Hybrid Rocketry LLC");
-               MANUFACTURER_CODES.put("ALPHA", "Alpha Hybrid Rocketry LLC");
-               MANUFACTURER_CODES.put("ALPHA HYBRID", "Alpha Hybrid Rocketry LLC");
-               MANUFACTURER_CODES.put("ALPHA HYBRIDS", "Alpha Hybrid Rocketry LLC");
-               MANUFACTURER_CODES.put("ALPHA HYBRID ROCKETRY", "Alpha Hybrid Rocketry LLC");
-               MANUFACTURER_CODES.put("ALPHA HYBRIDS ROCKETRY", "Alpha Hybrid Rocketry LLC");
-               MANUFACTURER_CODES.put("ALPHA HYBRID ROCKETRY LLC", "Alpha Hybrid Rocketry LLC");
-               MANUFACTURER_CODES.put("ALPHA HYBRID ROCKETRY, LLC", "Alpha Hybrid Rocketry LLC");
-
-               MANUFACTURER_CODES.put("AMW", "Animal Motor Works");
-               MANUFACTURER_CODES.put("AW", "Animal Motor Works");
-               MANUFACTURER_CODES.put("ANIMAL", "Animal Motor Works");
-               MANUFACTURER_CODES.put("ANIMAL MOTOR WORKS", "Animal Motor Works");
-               
-               MANUFACTURER_CODES.put("AP", "Apogee");
-               MANUFACTURER_CODES.put("APOG", "Apogee");
-               MANUFACTURER_CODES.put("APOGEE", "Apogee");
-               MANUFACTURER_CODES.put("P", "Apogee");
-               
-               MANUFACTURER_CODES.put("CES", "Cesaroni Technology Inc.");
-               MANUFACTURER_CODES.put("CESARONI", "Cesaroni Technology Inc.");
-               MANUFACTURER_CODES.put("CESARONI TECHNOLOGY", "Cesaroni Technology Inc.");
-               MANUFACTURER_CODES.put("CESARONI TECHNOLOGY INC", "Cesaroni Technology Inc.");
-               MANUFACTURER_CODES.put("CESARONI TECHNOLOGY INC.", "Cesaroni Technology Inc.");
-               MANUFACTURER_CODES.put("CESARONI TECHNOLOGY INCORPORATED", "Cesaroni Technology Inc.");
-               MANUFACTURER_CODES.put("CTI", "Cesaroni Technology Inc.");
-               MANUFACTURER_CODES.put("CS", "Cesaroni Technology Inc.");
-               MANUFACTURER_CODES.put("CSR", "Cesaroni Technology Inc.");
-               MANUFACTURER_CODES.put("PRO38", "Cesaroni Technology Inc.");
-               
-               MANUFACTURER_CODES.put("CR", "Contrail Rockets");
-               MANUFACTURER_CODES.put("CONTR", "Contrail Rockets");
-               MANUFACTURER_CODES.put("CONTRAIL", "Contrail Rockets");
-               MANUFACTURER_CODES.put("CONTRAIL ROCKET", "Contrail Rockets");
-               MANUFACTURER_CODES.put("CONTRAIL ROCKETS", "Contrail Rockets");
-               
-               MANUFACTURER_CODES.put("E", "Estes");
-               MANUFACTURER_CODES.put("ES", "Estes");
-               MANUFACTURER_CODES.put("ESTES", "Estes");
-               
-               MANUFACTURER_CODES.put("EM", "Ellis Mountain");
-               MANUFACTURER_CODES.put("ELLIS", "Ellis Mountain");
-               MANUFACTURER_CODES.put("ELLIS MOUNTAIN", "Ellis Mountain");
-               MANUFACTURER_CODES.put("ELLIS MOUNTAIN ROCKET", "Ellis Mountain");
-               MANUFACTURER_CODES.put("ELLIS MOUNTAIN ROCKETS", "Ellis Mountain");
-               
-               MANUFACTURER_CODES.put("GR", "Gorilla Rocket Motors");
-               MANUFACTURER_CODES.put("GORILLA", "Gorilla Rocket Motors");
-               MANUFACTURER_CODES.put("GORILLA ROCKET", "Gorilla Rocket Motors");
-               MANUFACTURER_CODES.put("GORILLA ROCKETS", "Gorilla Rocket Motors");
-               MANUFACTURER_CODES.put("GORILLA MOTOR", "Gorilla Rocket Motors");
-               MANUFACTURER_CODES.put("GORILLA MOTORS", "Gorilla Rocket Motors");
-               MANUFACTURER_CODES.put("GORILLA ROCKET MOTOR", "Gorilla Rocket Motors");
-               MANUFACTURER_CODES.put("GORILLA ROCKET MOTORS", "Gorilla Rocket Motors");
-               
-               MANUFACTURER_CODES.put("H", "HyperTEK");
-               MANUFACTURER_CODES.put("HT", "HyperTEK");
-               MANUFACTURER_CODES.put("HYPER", "HyperTEK");
-               MANUFACTURER_CODES.put("HYPERTEK", "HyperTEK");
-               
-               MANUFACTURER_CODES.put("K", "Kosdon by AeroTech");
-               MANUFACTURER_CODES.put("KBA", "Kosdon by AeroTech");
-               MANUFACTURER_CODES.put("K/AT", "Kosdon by AeroTech");
-               MANUFACTURER_CODES.put("K-AT", "Kosdon by AeroTech");
-               MANUFACTURER_CODES.put("KOS", "Kosdon by AeroTech");
-               MANUFACTURER_CODES.put("KOSDON", "Kosdon by AeroTech");
-               MANUFACTURER_CODES.put("KOSDON/AT", "Kosdon by AeroTech");
-               MANUFACTURER_CODES.put("KOSDON-AT", "Kosdon by AeroTech");
-               MANUFACTURER_CODES.put("KOSDON/AEROTECH", "Kosdon by AeroTech");
-               MANUFACTURER_CODES.put("KOSDON-AEROTECH", "Kosdon by AeroTech");
-               MANUFACTURER_CODES.put("KOSDON-BY-AEROTECH", "Kosdon by AeroTech");
-               MANUFACTURER_CODES.put("KOSDON BY AEROTECH", "Kosdon by AeroTech");
-               
-               MANUFACTURER_CODES.put("LOKI", "Loki Research");
-               MANUFACTURER_CODES.put("LOKI RESEARCH", "Loki Research");
-               MANUFACTURER_CODES.put("LR", "Loki Research");
-               
-               MANUFACTURER_CODES.put("PM", "Public Missiles, Ltd.");
-               MANUFACTURER_CODES.put("PML", "Public Missiles, Ltd.");
-               MANUFACTURER_CODES.put("PUBLIC MISSILES", "Public Missiles, Ltd.");
-               MANUFACTURER_CODES.put("PUBLIC MISSILES LTD", "Public Missiles, Ltd.");
-               MANUFACTURER_CODES.put("PUBLIC MISSILES, LTD", "Public Missiles, Ltd.");
-               MANUFACTURER_CODES.put("PUBLIC MISSILES LTD.", "Public Missiles, Ltd.");
-               MANUFACTURER_CODES.put("PUBLIC MISSILES, LTD.", "Public Missiles, Ltd.");
-               MANUFACTURER_CODES.put("PUBLIC MISSILES LIMITED", "Public Missiles, Ltd.");
-               MANUFACTURER_CODES.put("PUBLIC MISSILES, LIMITED", "Public Missiles, Ltd.");
-               
-               MANUFACTURER_CODES.put("PP", "Propulsion Polymers");
-               MANUFACTURER_CODES.put("PROP", "Propulsion Polymers");
-               MANUFACTURER_CODES.put("PROPULSION", "Propulsion Polymers");
-               MANUFACTURER_CODES.put("PROPULSION-POLYMERS", "Propulsion Polymers");
-               MANUFACTURER_CODES.put("PROPULSION POLYMERS", "Propulsion Polymers");
-               
-               MANUFACTURER_CODES.put("Q", "Quest");
-               MANUFACTURER_CODES.put("QU", "Quest");
-               MANUFACTURER_CODES.put("QUEST", "Quest");
-               
-               MANUFACTURER_CODES.put("RATT", "RATT Works");
-               MANUFACTURER_CODES.put("RATT WORKS", "RATT Works");
-               MANUFACTURER_CODES.put("RT", "RATT Works");
-               MANUFACTURER_CODES.put("RTW", "RATT Works");
-               
-               MANUFACTURER_CODES.put("RR", "Roadrunner Rocketry");
-               MANUFACTURER_CODES.put("ROADRUNNER", "Roadrunner Rocketry");
-               MANUFACTURER_CODES.put("ROADRUNNER ROCKETRY", "Roadrunner Rocketry");
-               
-               MANUFACTURER_CODES.put("RV", "Rocketvision");
-               MANUFACTURER_CODES.put("ROCKETVISION", "Rocketvision");
-
-               MANUFACTURER_CODES.put("SR", "Sky Ripper Systems");
-               MANUFACTURER_CODES.put("SRS", "Sky Ripper Systems");
-               MANUFACTURER_CODES.put("SKYR", "Sky Ripper Systems");
-               MANUFACTURER_CODES.put("SKYRIPPER", "Sky Ripper Systems");
-               MANUFACTURER_CODES.put("SKYRIPPER SYSTEMS", "Sky Ripper Systems");
-               MANUFACTURER_CODES.put("SKY RIPPER SYSTEMS", "Sky Ripper Systems");
-               
-               MANUFACTURER_CODES.put("WCH", "West Coast Hybrids");
-               MANUFACTURER_CODES.put("WCR", "West Coast Hybrids");
-               MANUFACTURER_CODES.put("WEST COAST HYBRIDS", "West Coast Hybrids");
-               
-               MANUFACTURER_CODES.put("SF", "WECO Feuerwerk");  // Previously Sachsen Feuerwerks
-               MANUFACTURER_CODES.put("SACHSEN FEUERWERK", "WECO Feuerwerk");
-               MANUFACTURER_CODES.put("SACHSEN FEUERWERKS", "WECO Feuerwerk");
-               MANUFACTURER_CODES.put("WECO", "WECO Feuerwerk");
-               MANUFACTURER_CODES.put("WECO FEUERWERK", "WECO Feuerwerk");
-               MANUFACTURER_CODES.put("WECO FEUERWERKS", "WECO Feuerwerk");
-       }
-
-       
-       
-       
        /**
         * Load motors from the specified <code>InputStream</code>.  The file is read using
         * the default charset returned by {@link #getDefaultCharset()}.
@@ -330,28 +171,7 @@ public abstract class MotorLoader implements Loader<Motor> {
                } while (index < primary.size()-1);
        }
        
-       
-       /**
-        * Convert a manufacturer string.  This should be done for all manufacturers
-        * for overall consistency.  This includes both RASP name expansions and some
-        * general renaming.  This should also be performed when loading designs in order
-        * to identify manufacturers correctly.
-        * 
-        * @param mfg   the original manufacturer / manufacturer code.
-        * @return              the manufacturer name.
-        */
-       public static String convertManufacturer(String mfg) {
-               // Replace underscore and trim
-               mfg = mfg.replace('_', ' ').trim();
-               
-               // Check for conversion
-               String conv = MANUFACTURER_CODES.get(mfg.toUpperCase());
-               if (conv != null)
-                       return conv;
-               else
-                       return mfg;
-       }
-       
+
        
        @SuppressWarnings("unchecked")
        protected static void finalizeThrustCurve(List<Double> time, List<Double> thrust,
index f7b270fce2feb5c4739eaa5402bb874885a995b4..1d33fd18fb63bab5f9f07ffeea0d8241ca34fb64 100644 (file)
@@ -20,6 +20,7 @@ import net.sf.openrocket.file.simplesax.ElementHandler;
 import net.sf.openrocket.file.simplesax.PlainTextHandler;
 import net.sf.openrocket.file.simplesax.SimpleSAX;
 import net.sf.openrocket.material.Material;
+import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.rocketcomponent.BodyComponent;
 import net.sf.openrocket.rocketcomponent.BodyTube;
 import net.sf.openrocket.rocketcomponent.Bulkhead;
@@ -37,7 +38,6 @@ import net.sf.openrocket.rocketcomponent.InternalComponent;
 import net.sf.openrocket.rocketcomponent.LaunchLug;
 import net.sf.openrocket.rocketcomponent.MassComponent;
 import net.sf.openrocket.rocketcomponent.MassObject;
-import net.sf.openrocket.rocketcomponent.Motor;
 import net.sf.openrocket.rocketcomponent.MotorMount;
 import net.sf.openrocket.rocketcomponent.NoseCone;
 import net.sf.openrocket.rocketcomponent.Parachute;
@@ -1030,7 +1030,7 @@ class MotorHandler extends ElementHandler {
                } else if (element.equals("manufacturer")) {
                        
                        // Manufacturer
-                       manufacturer = MotorLoader.convertManufacturer(content);
+                       manufacturer = content;
 
                } else if (element.equals("designation")) {
                        
index 4de79825687bd32c3272ec198ed5058fcb991d4d..558e7cd8e25d1027cc75314c67ee4cc472af16f6 100644 (file)
@@ -8,8 +8,9 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-import net.sf.openrocket.rocketcomponent.Motor;
-import net.sf.openrocket.rocketcomponent.ThrustCurveMotor;
+import net.sf.openrocket.motor.Manufacturer;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.ThrustCurveMotor;
 import net.sf.openrocket.util.Coordinate;
 
 public class RASPMotorLoader extends MotorLoader {
@@ -118,7 +119,7 @@ public class RASPMotorLoader extends MotorLoader {
                                
                                propW = Double.parseDouble(pieces[4]);
                                totalW = Double.parseDouble(pieces[5]);
-                               manufacturer = convertManufacturer(pieces[6]);
+                               manufacturer = pieces[6];
                                
                                if (propW > totalW) {
                                        throw new IOException("Propellant weight exceeds total weight in " +
@@ -196,7 +197,8 @@ public class RASPMotorLoader extends MotorLoader {
                
                try {
                        
-                       return new ThrustCurveMotor(manufacturer, designation, comment, Motor.Type.UNKNOWN,
+                       return new ThrustCurveMotor(Manufacturer.getManufacturer(manufacturer), 
+                                       designation, comment, Motor.Type.UNKNOWN,
                                        delays, diameter, length, timeArray, thrustArray, cgArray);
                        
                } catch (IllegalArgumentException e) {
index eef74ac96ecaebec1f3c879ad9992999342ff8e3..b39a9218e4bdfef30f16b63e76e28f3253813419 100644 (file)
@@ -12,8 +12,9 @@ import net.sf.openrocket.file.simplesax.ElementHandler;
 import net.sf.openrocket.file.simplesax.NullElementHandler;
 import net.sf.openrocket.file.simplesax.PlainTextHandler;
 import net.sf.openrocket.file.simplesax.SimpleSAX;
-import net.sf.openrocket.rocketcomponent.Motor;
-import net.sf.openrocket.rocketcomponent.ThrustCurveMotor;
+import net.sf.openrocket.motor.Manufacturer;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.ThrustCurveMotor;
 import net.sf.openrocket.util.Coordinate;
 
 import org.xml.sax.InputSource;
@@ -147,7 +148,7 @@ public class RockSimMotorLoader extends MotorLoader {
                        str = attributes.get("mfg");
                        if (str == null)
                                throw new SAXException("Manufacturer missing");
-                       manufacturer = convertManufacturer(str);
+                       manufacturer = str;
                        
                        // Designation
                        str = attributes.get("code");
@@ -339,7 +340,8 @@ public class RockSimMotorLoader extends MotorLoader {
                        }
                        
                        try {
-                               return new ThrustCurveMotor(manufacturer, designation, description, type,
+                               return new ThrustCurveMotor(Manufacturer.getManufacturer(manufacturer), 
+                                               designation, description, type,
                                                delays, diameter, length, timeArray, thrustArray, cgArray);
                        } catch (IllegalArgumentException e) {
                                throw new SAXException("Illegal motor data", e);
index 1a1a3156b2bffb8f5e5021ad3b5b503cedfa267a..a9da9b2fd47359f6078609444601368266e12727 100644 (file)
@@ -7,8 +7,8 @@ import java.util.List;
 
 import net.sf.openrocket.file.RocketSaver;
 import net.sf.openrocket.material.Material;
+import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.rocketcomponent.ComponentAssembly;
-import net.sf.openrocket.rocketcomponent.Motor;
 import net.sf.openrocket.rocketcomponent.MotorMount;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
@@ -121,7 +121,7 @@ public class RocketComponentSaver {
                        if (motor.getMotorType() != Motor.Type.UNKNOWN) {
                                elements.add("    <type>" + motor.getMotorType().name().toLowerCase() + "</type>");
                        }
-                       elements.add("    <manufacturer>" + RocketSaver.escapeXML(motor.getManufacturer()) + "</manufacturer>");
+                       elements.add("    <manufacturer>" + RocketSaver.escapeXML(motor.getManufacturer().getSimpleName()) + "</manufacturer>");
                        elements.add("    <designation>" + RocketSaver.escapeXML(motor.getDesignation()) + "</designation>");
                        elements.add("    <diameter>" + motor.getDiameter() + "</diameter>");
                        elements.add("    <length>" + motor.getLength() + "</length>");
index 7e0afef34d869ca5fb363149eac3420e88e0e321..c4210e6c2f36a1ab40f7b3ea377b48de67b4c078 100644 (file)
@@ -25,8 +25,8 @@ import net.sf.openrocket.gui.adaptors.MotorConfigurationModel;
 import net.sf.openrocket.gui.components.BasicSlider;
 import net.sf.openrocket.gui.components.UnitSelector;
 import net.sf.openrocket.gui.dialogs.MotorChooserDialog;
+import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.rocketcomponent.Configuration;
-import net.sf.openrocket.rocketcomponent.Motor;
 import net.sf.openrocket.rocketcomponent.MotorMount;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
@@ -186,7 +186,7 @@ public class MotorConfig extends JPanel {
                if (m == null)
                        motorLabel.setText("None");
                else
-                       motorLabel.setText(m.getManufacturer() + " " +
+                       motorLabel.setText(m.getManufacturer().getDisplayName() + " " +
                                        m.getDesignation(mount.getMotorDelay(id)));
        }
        
index 3cc99f5cf286f93bdc007d0da199674a7db5d4cb..3b151f70947b509745f95550310d8320922003fc 100644 (file)
@@ -6,12 +6,14 @@ import java.awt.event.ActionListener;
 import javax.swing.JButton;
 import javax.swing.JDialog;
 import javax.swing.JFrame;
+import javax.swing.JLabel;
 import javax.swing.JPanel;
 
 import net.miginfocom.swing.MigLayout;
 import net.sf.openrocket.gui.components.ResizeLabel;
 import net.sf.openrocket.gui.components.URLLabel;
 import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.Icons;
 import net.sf.openrocket.util.Prefs;
 
 public class AboutDialog extends JDialog {
@@ -26,12 +28,24 @@ public class AboutDialog extends JDialog {
                
                JPanel panel = new JPanel(new MigLayout("fill"));
                
-               panel.add(new ResizeLabel("OpenRocket", 20), "ax 50%, wrap para");
-               panel.add(new ResizeLabel("Version " + version, 3), "ax 50%, wrap 30lp");
+               panel.add(new JLabel(Icons.loadImageIcon("pix/icon/icon-about.png", "OpenRocket")), 
+                               "spany 5, top");
                
-               panel.add(new ResizeLabel("Copyright \u00A9 2007-2009 Sampo Niskanen"), "ax 50%, wrap para");
+               panel.add(new ResizeLabel("OpenRocket", 20), "ax 50%, growy, wrap para");
+               panel.add(new ResizeLabel("Version " + version, 3), "ax 50%, growy, wrap rel");
                
-               panel.add(new URLLabel(OPENROCKET_URL), "ax 50%, wrap para");
+               String source = Prefs.getBuildSource();
+               if (!Prefs.DEFAULT_BUILD_SOURCE.equalsIgnoreCase(source)) {
+                       panel.add(new ResizeLabel("Distributed by " + source, -1), 
+                                       "ax 50%, growy, wrap para");
+               } else {
+                       panel.add(new ResizeLabel(" ", -1), "ax 50%, growy, wrap para");
+               }
+               
+               panel.add(new ResizeLabel("Copyright \u00A9 2007-2009 Sampo Niskanen"), 
+                               "ax 50%, growy, wrap para");
+               
+               panel.add(new URLLabel(OPENROCKET_URL), "ax 50%, growy, wrap para");
                
 
                JButton close = new JButton("Close");
@@ -41,7 +55,7 @@ public class AboutDialog extends JDialog {
                                AboutDialog.this.dispose();
                        }
                });
-               panel.add(close, "right");
+               panel.add(close, "spanx, right");
                
                this.add(panel);
                this.setTitle("OpenRocket " + version);
index add913274ff58f6d82fa745c7595bc1c24bbd68e..b58d48a356f371360205ac76190781489c307adf 100644 (file)
@@ -143,7 +143,7 @@ public class BugReportDialog extends JDialog {
                this.pack();
                this.setLocationRelativeTo(parent);
                GUIUtil.installEscapeCloseOperation(this);
-//             GUIUtil.setDefaultButton(close);
+               GUIUtil.setDefaultButton(send);
        }
 
        
index e554c4132fbc2b8af8645e63e7040fb116e34a7f..a42d8a944fcf17f521781ffb58b501521c3894ae 100644 (file)
@@ -27,7 +27,7 @@ import javax.swing.table.TableColumnModel;
 import net.miginfocom.swing.MigLayout;
 import net.sf.openrocket.document.OpenRocketDocument;
 import net.sf.openrocket.gui.main.BasicFrame;
-import net.sf.openrocket.rocketcomponent.Motor;
+import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.rocketcomponent.MotorMount;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
index 3a5a154cac5aa45f501d99433a8305505e2f3442..95b2aad6dd25ae22c350a9ed9308b492e291a063 100644 (file)
@@ -38,7 +38,7 @@ import javax.swing.table.TableRowSorter;
 import net.miginfocom.swing.MigLayout;
 import net.sf.openrocket.database.Databases;
 import net.sf.openrocket.gui.components.ResizeLabel;
-import net.sf.openrocket.rocketcomponent.Motor;
+import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.unit.UnitGroup;
 import net.sf.openrocket.util.GUIUtil;
 import net.sf.openrocket.util.Prefs;
@@ -382,7 +382,7 @@ public class MotorChooserDialog extends JDialog {
                MANUFACTURER("Manufacturer",100) {
                        @Override
                        public String getValue(Motor m) {
-                               return m.getManufacturer();
+                               return m.getManufacturer().getDisplayName();
                        }
 //                     @Override
 //                     public String getToolTipText(Motor m) {
index 0b8c4acd299df4aedc6e9af728aeb89514906a0a..a40c841a21f01c0dcf483282e2c6696142c92acc 100644 (file)
@@ -81,16 +81,18 @@ public class PlotDialog extends JDialog {
        private static final Map<FlightEvent.Type, Image> EVENT_IMAGES =
                new HashMap<FlightEvent.Type, Image>();
        static {
-               loadImage(FlightEvent.Type.LAUNCH, "pix/spheres/red-16x16.png");
+               loadImage(FlightEvent.Type.LAUNCH, "pix/eventicons/event-launch.png");
                loadImage(FlightEvent.Type.LIFTOFF, "pix/eventicons/event-liftoff.png");
                loadImage(FlightEvent.Type.LAUNCHROD, "pix/eventicons/event-launchrod.png");
                loadImage(FlightEvent.Type.IGNITION, "pix/eventicons/event-ignition.png");
                loadImage(FlightEvent.Type.BURNOUT, "pix/eventicons/event-burnout.png");
-               loadImage(FlightEvent.Type.EJECTION_CHARGE, "pix/spheres/green-16x16.png");
-               loadImage(FlightEvent.Type.STAGE_SEPARATION, "pix/eventicons/event-stage-separation.png");
+               loadImage(FlightEvent.Type.EJECTION_CHARGE,"pix/eventicons/event-ejection-charge.png");
+               loadImage(FlightEvent.Type.STAGE_SEPARATION, 
+                               "pix/eventicons/event-stage-separation.png");
                loadImage(FlightEvent.Type.APOGEE, "pix/eventicons/event-apogee.png");
-               loadImage(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, "pix/spheres/blue-16x16.png");
-               loadImage(FlightEvent.Type.GROUND_HIT, "pix/spheres/gray-16x16.png");
+               loadImage(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, 
+                               "pix/eventicons/event-recovery-device-deployment.png");
+               loadImage(FlightEvent.Type.GROUND_HIT, "pix/eventicons/event-ground-hit.png");
                loadImage(FlightEvent.Type.SIMULATION_END, "pix/eventicons/event-simulation-end.png");
        }
 
index f6d7d9de7413591e3cee37bf9a5c3d9096955033..35158def5fa5b7604cf67aa23ef9c9b611c5c2ee 100644 (file)
@@ -20,8 +20,8 @@ import java.util.Iterator;
 import java.util.LinkedHashSet;
 
 import net.sf.openrocket.gui.figureelements.FigureElement;
+import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.rocketcomponent.Configuration;
-import net.sf.openrocket.rocketcomponent.Motor;
 import net.sf.openrocket.rocketcomponent.MotorMount;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
 import net.sf.openrocket.util.Coordinate;
diff --git a/src/net/sf/openrocket/motor/Manufacturer.java b/src/net/sf/openrocket/motor/Manufacturer.java
new file mode 100644 (file)
index 0000000..3e7365b
--- /dev/null
@@ -0,0 +1,226 @@
+package net.sf.openrocket.motor;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class Manufacturer {
+       
+       private static Set<Manufacturer> manufacturers = new HashSet<Manufacturer>();
+       static {
+
+               // AeroTech has many name combinations...
+               List<String> names = new ArrayList<String>();
+               for (String s: new String[] { "A", "AT", "AERO", "AEROT", "AEROTECH" }) {
+                       names.add(s);
+                       names.add(s+"-RMS");
+                       names.add(s+"-RCS");
+                       names.add("RCS-" + s);
+                       names.add(s+"-APOGEE");
+               }
+               names.add("ISP");
+
+               manufacturers.add(new Manufacturer("AeroTech", "AeroTech",
+                               names.toArray(new String[0])));
+               
+               manufacturers.add(new Manufacturer("Alpha Hybrid Rocketry LLC",
+                               "Alpha Hybrid Rocketry",
+                               "AHR", "ALPHA", "ALPHA HYBRID", "ALPHA HYBRIDS", "ALPHA HYBRIDS ROCKETRY"));
+
+               manufacturers.add(new Manufacturer("Animal Motor Works","Animal Motor Works",
+                               "AMW", "AW", "ANIMAL"));
+
+               manufacturers.add(new Manufacturer("Apogee","Apogee",
+                               "AP", "APOG", "P"));
+
+               manufacturers.add(new Manufacturer("Cesaroni Technology Inc.",
+                               "Cesaroni Technology",
+                               "CES", "CESARONI", "CESARONI TECHNOLOGY INCORPORATED", "CTI",
+                               "CS", "CSR", "PRO38"));
+
+               manufacturers.add(new Manufacturer("Contrail Rockets","Contrail Rockets",
+                               "CR", "CONTR", "CONTRAIL", "CONTRAIL ROCKET"));
+               
+               manufacturers.add(new Manufacturer("Estes","Estes",
+                               "E", "ES"));
+               
+               manufacturers.add(new Manufacturer("Ellis Mountain","Ellis Mountain",
+                               "EM", "ELLIS", "ELLIS MOUNTAIN ROCKET", "ELLIS MOUNTAIN ROCKETS"));
+
+               manufacturers.add(new Manufacturer("Gorilla Rocket Motors",
+                               "Gorilla Rocket Motors",
+                               "GR", "GORILLA", "GORILLA ROCKET", "GORILLA ROCKETS", "GORILLA MOTOR", 
+                               "GORILLA MOTORS", "GORILLA ROCKET MOTOR"));
+               
+               manufacturers.add(new Manufacturer("HyperTEK", "HyperTEK",
+                               "H", "HT", "HYPER"));
+               
+               manufacturers.add(new Manufacturer("Kosdon by AeroTech", "Kosdon by AeroTech",
+                               "K", "KBA", "K-AT", "KOS", "KOSDON", "KOSDON/AT", "KOSDON/AEROTECH"));
+               
+               manufacturers.add(new Manufacturer("Loki Research", "Loki Research",
+                               "LOKI", "LR"));
+               
+               manufacturers.add(new Manufacturer("Public Missiles, Ltd.", "Public Missiles",
+                               "PM", "PML", "PUBLIC MISSILES LIMITED"));
+
+               manufacturers.add(new Manufacturer("Propulsion Polymers", "Propulsion Polymers",
+                               "PP", "PROP", "PROPULSION"));
+               
+               manufacturers.add(new Manufacturer("Quest", "Quest",
+                               "Q", "QU"));
+               
+               manufacturers.add(new Manufacturer("RATT Works", "RATT Works",
+                               "RATT", "RT", "RTW"));
+               
+               manufacturers.add(new Manufacturer("Roadrunner Rocketry","Roadrunner Rocketry",
+                               "RR", "ROADRUNNER"));
+               
+               manufacturers.add(new Manufacturer("Rocketvision", "Rocketvision",
+                               "RV", "ROCKET VISION"));
+               
+               manufacturers.add(new Manufacturer("Sky Ripper Systems","Sky Ripper Systems",
+                               "SR", "SRS", "SKYR", "SKYRIPPER", "SKY RIPPER", "SKYRIPPER SYSTEMS"));
+               
+               manufacturers.add(new Manufacturer("West Coast Hybrids", "West Coast Hybrids",
+                               "WCH", "WCR", "WEST COAST", "WEST COAST HYBRID"));
+               
+               // German WECO Feuerwerk, previously Sachsen Feuerwerk
+               manufacturers.add(new Manufacturer("WECO Feuerwerk", "WECO Feuerwerk",
+                               "WECO", "WECO FEUERWERKS", "SF", "SACHSEN", "SACHSEN FEUERWERK",
+                               "SACHSEN FEUERWERKS"));
+               
+               
+               // Check that no duplicates have appeared
+               for (Manufacturer m1: manufacturers) {
+                       for (Manufacturer m2: manufacturers) {
+                               if (m1 == m2)
+                                       continue;
+                               for (String name: m1.getAllNames()) {
+                                       if (m2.matches(name)) {
+                                               throw new IllegalStateException("Manufacturer name clash between " +
+                                                               "manufacturers " + m1 + " and " + m2 + " name " + name);
+                                       }
+                               }
+                       }
+               }
+       }
+       
+       
+
+       private final String displayName;
+       private final String simpleName;
+       private final Set<String> allNames;
+       private final Set<String> searchNames;
+       
+       
+       private Manufacturer(String displayName, String simpleName, String... alternateNames) {
+               this.displayName = displayName;
+               this.simpleName = simpleName;
+               
+               Set<String> all = new HashSet<String>();
+               Set<String> search = new HashSet<String>();
+               
+               all.add(displayName);
+               all.add(simpleName);
+               search.add(generateSearchString(displayName));
+               search.add(generateSearchString(simpleName));
+               
+               for (String name: alternateNames) {
+                       all.add(name);
+                       search.add(generateSearchString(name));
+               }
+               
+               this.allNames = Collections.unmodifiableSet(all);
+               this.searchNames = Collections.unmodifiableSet(search);
+       }
+       
+       
+       /**
+        * Returns the display name of the manufacturer.  This is the value that
+        * should be presented in the UI to the user.
+        * 
+        * @return      the display name
+        */
+       public String getDisplayName() {
+               return displayName;
+       }
+
+
+       /**
+        * Returns the simple name of the manufacturer.  This should be used for example
+        * when saving the manufacturer for compatibility.
+        * 
+        * @return      the simple name.
+        */
+       public String getSimpleName() {
+               return simpleName;
+       }
+
+
+       /**
+        * Return all names of the manufacturer.  This includes all kinds of
+        * codes that correspond to the manufacturer (for example "A" for AeroTech).
+        * 
+        * @return      an unmodifiable set of the alternative names.
+        */
+       public Set<String> getAllNames() {
+               return allNames;
+       }
+
+       
+       /**
+        * Check whether the display, simple or any of the alternative names matches the
+        * specified name.  Matching is performed case insensitively and ignoring any
+        * non-letter and non-number characters.
+        * 
+        * @param name  the name to search for.
+        * @return              whether this manufacturer matches the request.
+        */
+       public boolean matches(String name) {
+               if (name == null)
+                       return false;
+               return this.searchNames.contains(generateSearchString(name));
+       }
+       
+       
+       /**
+        * Return the display name of the manufacturer.
+        */
+       @Override
+       public String toString() {
+               return displayName;
+       }
+       
+       
+       /**
+        * Returns a manufacturer for the given name.  The manufacturer is searched for
+        * within the manufacturers and if a match is found the corresponding
+        * object is returned.  If not, a new manufacturer is returned with display and
+        * simple name the name specified.  Subsequent requests for the same (or corresponding)
+        * manufacturer name will return the same object.
+        * 
+        * @param name  the manufacturer name to search for.
+        * @return              the Manufacturer object corresponding the name.
+        */
+       public static synchronized Manufacturer getManufacturer(String name) {
+               for (Manufacturer m: manufacturers) {
+                       if (m.matches(name))
+                               return m;
+               }
+               
+               Manufacturer m = new Manufacturer(name.trim(), name.trim());
+               manufacturers.add(m);
+               return m;
+       }
+       
+       
+       
+               
+       private String generateSearchString(String str) {
+               return str.toLowerCase().replaceAll("[^a-zA-Z0-9]+", " ").trim();
+       }
+       
+}
diff --git a/src/net/sf/openrocket/motor/Motor.java b/src/net/sf/openrocket/motor/Motor.java
new file mode 100644 (file)
index 0000000..0da9c3b
--- /dev/null
@@ -0,0 +1,640 @@
+package net.sf.openrocket.motor;
+
+import java.text.Collator;
+import java.util.Comparator;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
+
+
+
+/**
+ * Abstract base class for motors.  The methods that must be implemented are
+ * {@link #getTotalTime()}, {@link #getThrust(double)} and {@link #getCG(double)}.
+ * Additionally the method {@link #getMaxThrust()} may be overridden for efficiency.
+ * <p>
+ * 
+ * NOTE:  The current implementation of {@link #getAverageTime()} and 
+ * {@link #getAverageThrust()} assume that the class is immutable!
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public abstract class Motor implements Comparable<Motor> {
+       
+       /**
+        * Enum of rocket motor types.
+        * 
+        * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+        */
+       public enum Type {
+               SINGLE("Single-use", "Single-use solid propellant motor"), 
+               RELOAD("Reloadable", "Reloadable solid propellant motor"), 
+               HYBRID("Hybrid", "Hybrid rocket motor engine"), 
+               UNKNOWN("Unknown", "Unknown motor type");
+               
+               private final String name;
+               private final String description;
+               
+               Type(String name, String description) {
+                       this.name = name;
+                       this.description = description;
+               }
+
+               /**
+                * Return a short name of this motor type.
+                * @return  a short name of the motor type.
+                */
+               public String getName() {
+                       return name;
+               }
+               
+               /**
+                * Return a long description of this motor type.
+                * @return  a description of the motor type.
+                */
+               public String getDescription() {
+                       return description;
+               }
+               
+               @Override
+               public String toString() {
+                       return name;
+               }
+       }
+       
+       
+       /**
+        * Ejection charge delay value signifying a "plugged" motor with no ejection charge.
+        * The value is that of <code>Double.POSITIVE_INFINITY</code>.
+        */
+       public static final double PLUGGED = Double.POSITIVE_INFINITY;
+       
+       
+       /**
+        * Below what portion of maximum thrust is the motor chosen to be off when
+        * calculating average thrust and burn time.  NFPA 1125 defines the "official"
+        * burn time to be the time which the motor produces over 5% of its maximum thrust.
+        */
+       public static final double AVERAGE_MARGINAL = 0.05;
+       
+       /* All data is cached, so divisions can be very tight. */
+       private static final int DIVISIONS = 1000;
+
+       
+       //  Comparators:
+       private static final Collator COLLATOR = Collator.getInstance(Locale.US);
+       static {
+               COLLATOR.setStrength(Collator.PRIMARY);
+       }
+       private static DesignationComparator DESIGNATION_COMPARATOR = new DesignationComparator();
+       
+       
+       
+       
+       private final Manufacturer manufacturer;
+       private final String designation;
+       private final String description;
+       private final Type motorType;
+       
+       private final double[] delays;
+       
+       private final double diameter;
+       private final double length;
+       
+       /* Cached data */
+       private double maxThrust = -1;
+       private double avgTime = -1;
+       private double avgThrust = -1;
+       private double totalImpulse = -1;
+       
+       
+       
+       /**
+        * Sole constructor.  None of the parameters may be <code>null</code>.
+        * 
+        * @param manufacturer  the manufacturer of the motor.
+        * @param designation   the motor designation.
+        * @param description   further description, including any comments on the origin
+        *                                              of the thrust curve.
+        * @param delays                an array of the standard ejection charge delays.  A plugged
+        *                                              motor (no ejection charge) is specified by a delay of
+        *                                              {@link #PLUGGED} (<code>Double.POSITIVE_INFINITY</code>).
+        * @param diameter              maximum diameter of the motor
+        * @param length                length of the motor
+        */
+       protected Motor(Manufacturer manufacturer, String designation, String description, 
+                       Type type, double[] delays, double diameter, double length) {
+
+               if (manufacturer == null || designation == null || description == null ||
+                               type == null || delays == null) {
+                       throw new IllegalArgumentException("Parameters cannot be null.");
+               }
+               
+               this.manufacturer = manufacturer;
+               this.designation = designation;
+               this.description = description.trim();
+               this.motorType = type;
+               this.delays = delays.clone();
+               this.diameter = diameter;
+               this.length = length;
+       }
+
+
+       
+       /**
+        * Return the total burn time of the motor.  The method {@link #getThrust(double)}
+        * must return zero for time values greater than the return value.
+        * 
+        * @return  the total burn time of the motor.
+        */
+       public abstract double getTotalTime();
+
+
+       /**
+        * Return the thrust of the motor at the specified time.
+        * 
+        * @param time  time since the ignition of the motor.
+        * @return      the thrust at the specified time.
+        */
+       public abstract double getThrust(double time);
+       
+       
+       /**
+        * Return the average thrust of the motor between times t1 and t2.
+        * 
+        * @param t1    starting time since the ignition of the motor.
+        * @param t2    end time since the ignition of the motor.
+        * @return              the average thrust during the time period.
+        */
+       /* TODO: MEDIUM: Implement better method in subclass */
+       public double getThrust(double t1, double t2) {
+               double f = 0;
+               f += getThrust(t1);
+               f += getThrust(0.8*t1 + 0.2*t2);
+               f += getThrust(0.6*t1 + 0.4*t2);
+               f += getThrust(0.4*t1 + 0.6*t2);
+               f += getThrust(0.2*t1 + 0.8*t2);
+               f += getThrust(t2);
+               return f/6;
+       }
+
+       
+       /**
+        * Return the mass and CG of the motor at the specified time.
+        * 
+        * @param time  time since the ignition of the motor.
+        * @return      the mass and CG of the motor.
+        */
+       public abstract Coordinate getCG(double time);
+       
+       
+       
+       /**
+        * Return the mass of the motor at the specified time.  The original mass
+        * of the motor can be queried by <code>getMass(0)</code> and the burnt mass
+        * by <code>getMass(Double.MAX_VALUE)</code>.
+        * 
+        * @param time  time since the ignition of the motor.
+        * @return      the mass of the motor.
+        */
+       public double getMass(double time) {
+               return getCG(time).weight;
+       }
+       
+       
+       /**
+        * Return the longitudal moment of inertia of the motor at the specified time.
+        * This default method assumes that the mass of the motor is evenly distributed
+        * in a cylinder with the diameter and length of the motor.
+        * 
+        * @param time  time since the ignition of the motor.
+        * @return              the longitudal moment of inertia of the motor.
+        */
+       public double getLongitudalInertia(double time) {
+               return getMass(time) * (3.0*MathUtil.pow2(diameter/2) + MathUtil.pow2(length))/12;
+       }
+       
+       
+       
+       /**
+        * Return the rotational moment of inertia of the motor at the specified time.
+        * This default method assumes that the mass of the motor is evenly distributed
+        * in a cylinder with the diameter and length of the motor.
+        * 
+        * @param time  time since the ignition of the motor.
+        * @return              the rotational moment of inertia of the motor.
+        */
+       public double getRotationalInertia(double time) {
+               return getMass(time) * MathUtil.pow2(diameter) / 8;
+       }
+       
+       
+       
+       
+       /**
+        * Return the maximum thrust.  This implementation slices through the thrust curve
+        * searching for the maximum thrust.  Subclasses may wish to override this with a
+        * more efficient method.
+        * 
+        * @return  the maximum thrust of the motor
+        */
+       public double getMaxThrust() {
+               if (maxThrust < 0) {
+                       double time = getTotalTime();
+                       maxThrust = 0;
+                       
+                       for (int i=0; i < DIVISIONS; i++) {
+                               double t = time * i / DIVISIONS;
+                               double thrust = getThrust(t);
+                               
+                               if (thrust > maxThrust)
+                                       maxThrust = thrust;
+                       }
+               }
+               return maxThrust;
+       }
+       
+       
+       /**
+        * Return the time used in calculating the average thrust.  The time is the
+        * length of time that the motor produces over 5% ({@link #AVERAGE_MARGINAL})
+        * of its maximum thrust.
+        * 
+        * @return  the nominal burn time.
+        */
+       public double getAverageTime() {
+               // Compute average time lazily
+               if (avgTime < 0) {
+                       double max = getMaxThrust();
+                       double time = getTotalTime();
+                       
+                       avgTime = 0;
+                       for (int i=0; i <= DIVISIONS; i++) {
+                               double t = i*time/DIVISIONS;
+                               if (getThrust(t) >= max*AVERAGE_MARGINAL)
+                                       avgTime++;
+                       }
+                       avgTime *= time/(DIVISIONS+1);
+                       
+                       if (Double.isNaN(avgTime))
+                               throw new RuntimeException("Calculated avg. time is NaN for motor "+this);
+
+               }
+               return avgTime;
+       }
+       
+       
+       /**
+        * Return the calculated average thrust during the time the motor produces
+        * over 5% ({@link #AVERAGE_MARGINAL}) of its thrust.
+        * 
+        * @return  the nominal average thrust.
+        */
+       public double getAverageThrust() {
+               // Compute average thrust lazily
+               if (avgThrust < 0) {
+                       double max = getMaxThrust();
+                       double time = getTotalTime();
+                       int points = 0;
+                       
+                       avgThrust = 0;
+                       for (int i=0; i <= DIVISIONS; i++) {
+                               double t = i*time/DIVISIONS;
+                               double thrust = getThrust(t);
+                               if (thrust >= max*AVERAGE_MARGINAL) {
+                                       avgThrust += thrust;
+                                       points++;
+                               }
+                       }
+                       if (points > 0)
+                               avgThrust /= points;
+                       
+                       if (Double.isNaN(avgThrust))
+                               throw new RuntimeException("Calculated average thrust is NaN for motor "+this);
+               }
+               return avgThrust;
+       }
+       
+       
+       /**
+        * Return the total impulse of the motor.  This is calculated from the entire
+        * burn time, and therefore may differ from the value of {@link #getAverageTime()}
+        * and {@link #getAverageThrust()} multiplied together.
+        * 
+        * @return  the total impulse of the motor.
+        */
+       public double getTotalImpulse() {
+               // Compute total impulse lazily
+               if (totalImpulse < 0) {
+                       double time = getTotalTime();
+                       double f0, t0;
+                       
+                       totalImpulse = 0;
+                       t0 = 0;
+                       f0 = getThrust(0);
+                       for (int i=1; i < DIVISIONS; i++) {
+                               double t1 = time * i / DIVISIONS;
+                               double f1 = getThrust(t1); 
+                               totalImpulse += 0.5*(f0+f1)*(t1-t0);
+                               t0 = t1;
+                               f0 = f1;
+                       }
+                       
+                       if (Double.isNaN(totalImpulse))
+                               throw new RuntimeException("Calculated total impulse is NaN for motor "+this);
+               }
+               return totalImpulse;
+       }
+       
+
+       /**
+        * Return the manufacturer of the motor.
+        * 
+        * @return the manufacturer
+        */
+       public Manufacturer getManufacturer() {
+               return manufacturer;
+       }
+       
+       /**
+        * Return the designation of the motor.
+        * 
+        * @return the designation
+        */
+       public String getDesignation() {
+               return designation;
+       }
+       
+       /**
+        * Return the designation of the motor, including a delay.
+        * 
+        * @param delay  the delay of the motor.
+        * @return               designation with delay.
+        */
+       public String getDesignation(double delay) {
+               return getDesignation() + "-" + getDelayString(delay);
+       }
+
+       
+       /**
+        * Return extra description for the motor.  This may include for example 
+        * comments on the source of the thrust curve.  The returned <code>String</code>
+        * may include new-lines.
+        * 
+        * @return the description
+        */
+       public String getDescription() {
+               return description;
+       }
+       
+       
+       /**
+        * Return the motor type.
+        * 
+        * @return  the motorType
+        */
+       public Type getMotorType() {
+               return motorType;
+       }
+
+
+
+       /**
+        * Return the standard ejection charge delays for the motor.  "Plugged" motors
+        * with no ejection charge are signified by the value {@link #PLUGGED}
+        * (<code>Double.POSITIVE_INFINITY</code>).
+        * 
+        * @return  the list of standard ejection charge delays, which may be empty.
+        */
+       public double[] getStandardDelays() {
+               return delays.clone();
+       }
+
+       /**
+        * Return the maximum diameter of the motor.
+        * 
+        * @return the diameter
+        */
+       public double getDiameter() {
+               return diameter;
+       }
+
+       /**
+        * Return the length of the motor.  This should be a "characteristic" length,
+        * and the exact definition may depend on the motor type.  Typically this should
+        * be the length from the bottom of the motor to the end of the maximum diameter
+        * portion, ignoring any smaller ejection charge compartments.
+        * 
+        * @return the length
+        */
+       public double getLength() {
+               return length;
+       }
+       
+       
+       /**
+        * Compares two <code>Motor</code> objects.  The motors are considered equal
+        * if they have identical manufacturers, designations and types, near-identical
+        * dimensions, burn times and delays and near-identical thrust curves
+        * (sampled at 10 equidistant points).
+        * <p>
+        * The comment field is ignored when comparing equality.
+        */
+       @Override
+       public boolean equals(Object o) {
+               if (!(o instanceof Motor))
+                       return false;
+               
+               Motor other = (Motor) o;
+               
+               // Tests manufacturer, designation, diameter and length
+               if (this.compareTo(other) != 0)
+                       return false;
+               
+               if (Math.abs(this.getTotalTime() - other.getTotalTime()) > 0.5 ||
+                               this.motorType != other.motorType ||
+                               this.delays.length != other.delays.length) {
+                       
+                       return false;
+               }
+               
+               for (int i=0; i < delays.length; i++) {
+                       // INF - INF == NaN, which produces false when compared
+                       if (Math.abs(this.delays[i] - other.delays[i]) > 0.5) {
+                               return false;
+                       }
+               }
+               
+               double time = getTotalTime();
+               for (int i=0; i < 10; i++) {
+                       double t = time * i/10;
+                       if (Math.abs(this.getThrust(t) - other.getThrust(t)) > 1) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+       
+       /**
+        * A <code>hashCode</code> method compatible with the <code>equals</code>
+        * method.
+        */
+       @Override
+       public int hashCode() {
+               return (manufacturer.hashCode() + designation.hashCode() + 
+                               ((int)(length*1000)) + ((int)(diameter*1000)));
+       }
+       
+       
+       
+       @Override
+       public String toString() {
+               return manufacturer + " " + designation;
+       }
+       
+       
+       //////////  Static methods
+
+       
+       /**
+        * Return a String representation of a delay time.  If the delay is {@link #PLUGGED},
+        * returns "P".
+        *  
+        * @param delay         the delay time.
+        * @return                      the <code>String</code> representation.
+        */
+       public static String getDelayString(double delay) {
+               return getDelayString(delay,"P");
+       }
+       
+       /**
+        * Return a String representation of a delay time.  If the delay is {@link #PLUGGED},
+        * <code>plugged</code> is returned.
+        *   
+        * @param delay         the delay time.
+        * @param plugged       the return value if there is no ejection charge.
+        * @return                      the String representation.
+        */
+       public static String getDelayString(double delay, String plugged) {
+               if (delay == PLUGGED)
+                       return plugged;
+               delay = Math.rint(delay*10)/10;
+               if (MathUtil.equals(delay, Math.rint(delay)))
+                       return "" + ((int)delay);
+               return "" + delay;
+       }
+       
+
+       
+       
+       ////////////  Comparation
+       
+       
+
+       @Override
+       public int compareTo(Motor other) {
+               int value;
+               
+               // 1. Manufacturer
+               value = COLLATOR.compare(this.manufacturer.getDisplayName(), 
+                               other.manufacturer.getDisplayName());
+               if (value != 0)
+                       return value;
+               
+               // 2. Designation
+               value = DESIGNATION_COMPARATOR.compare(this.designation, other.designation);
+               if (value != 0)
+                       return value;
+               
+               // 3. Diameter
+               value = (int)((this.diameter - other.diameter)*1000000);
+               if (value != 0)
+                       return value;
+                               
+               // 4. Length
+               value = (int)((this.length - other.length)*1000000);
+               if (value != 0)
+                       return value;
+               
+               // 5. Total impulse
+               value = (int)((this.getTotalImpulse() - other.getTotalImpulse())*1000);
+               return value;
+       }
+       
+       
+       
+       public static Comparator<String> getDesignationComparator() {
+               return DESIGNATION_COMPARATOR;
+       }
+       
+       
+       /**
+        * Compares two motors by their designations.  The motors are ordered first
+        * by their motor class, second by their average thrust and lastly by any
+        * extra modifiers at the end of the designation.
+        * 
+        * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+        */
+       private static class DesignationComparator implements Comparator<String> {
+               private Pattern pattern = 
+                       Pattern.compile("^([0-9][0-9]+|1/([1-8]))?([a-zA-Z])([0-9]+)(.*?)$");
+               
+               @Override
+               public int compare(String o1, String o2) {
+                       int value;
+                       Matcher m1, m2;
+                       
+                       m1 = pattern.matcher(o1);
+                       m2 = pattern.matcher(o2);
+                       
+                       if (m1.find() && m2.find()) {
+
+                               String o1Class = m1.group(3);
+                               int o1Thrust = Integer.parseInt(m1.group(4));
+                               String o1Extra = m1.group(5);
+                               
+                               String o2Class = m2.group(3);
+                               int o2Thrust = Integer.parseInt(m2.group(4));
+                               String o2Extra = m2.group(5);
+                               
+                               // 1. Motor class
+                               if (o1Class.equalsIgnoreCase("A") && o2Class.equalsIgnoreCase("A")) {
+                                       //  1/2A and 1/4A comparison
+                                       String sub1 = m1.group(2);
+                                       String sub2 = m2.group(2);
+
+                                       if (sub1 != null || sub2 != null) {
+                                               if (sub1 == null)
+                                                       sub1 = "1";
+                                               if (sub2 == null)
+                                                       sub2 = "1";
+                                               value = -COLLATOR.compare(sub1,sub2);
+                                               if (value != 0)
+                                                       return value;
+                                       }
+                               }
+                               value = COLLATOR.compare(o1Class,o2Class);
+                               if (value != 0)
+                                       return value;
+                               
+                               // 2. Average thrust
+                               if (o1Thrust != o2Thrust)
+                                       return o1Thrust - o2Thrust;
+                               
+                               // 3. Extra modifier
+                               return COLLATOR.compare(o1Extra, o2Extra);
+                               
+                       } else {
+                               
+                               System.out.println("Falling back");
+                               System.out.println("o1:"+o1 + " o2:"+o2);
+                               
+                               // Not understandable designation, simply compare strings
+                               return COLLATOR.compare(o1, o2);
+                       }
+               }
+       }
+}
diff --git a/src/net/sf/openrocket/motor/ThrustCurveMotor.java b/src/net/sf/openrocket/motor/ThrustCurveMotor.java
new file mode 100644 (file)
index 0000000..e495def
--- /dev/null
@@ -0,0 +1,161 @@
+package net.sf.openrocket.motor;
+
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
+
+/**
+ * A class of motors specified by a fixed thrust curve.  This is the most
+ * accurate for solid rocket motors.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class ThrustCurveMotor extends Motor {
+       
+       public static final double MAX_THRUST = 10e6;
+
+       private final double[] time;
+       private final double[] thrust;
+       private final Coordinate[] cg;
+       
+       private final double totalTime;
+       private final double maxThrust;
+       
+
+       /**
+        * Sole constructor.  Sets all the properties of the motor.
+        * 
+        * @param manufacturer  the manufacturer of the motor.
+        * @param designation   the designation of the motor.
+        * @param description   extra description of the motor.
+        * @param diameter      diameter of the motor.
+        * @param length        length of the motor.
+        * @param time          the time points for the thrust curve.
+        * @param thrust        thrust at the time points.
+        * @param cg            cg at the time points.
+        */
+       public ThrustCurveMotor(Manufacturer manufacturer, String designation, String description, 
+                       Motor.Type type, double[] delays, double diameter, double length,
+                       double[] time, double[] thrust, Coordinate[] cg) {
+               super(manufacturer, designation, description, type, delays, diameter, length);
+
+               double max = -1;
+
+               // Check argument validity
+               if ((time.length != thrust.length) || (time.length != cg.length)) {
+                       throw new IllegalArgumentException("Array lengths do not match, " +
+                                       "time:" + time.length + " thrust:" + thrust.length +
+                                       " cg:" + cg.length);
+               }
+               if (time.length < 2) {
+                       throw new IllegalArgumentException("Too short thrust-curve, length=" + 
+                                       time.length);
+               }
+               for (int i=0; i < time.length-1; i++) {
+                       if (time[i+1] < time[i]) {
+                               throw new IllegalArgumentException("Time goes backwards, " +
+                                               "time[" + i + "]=" + time[i] + " " +
+                                               "time[" + (i+1) + "]=" + time[i+1]);
+                       }
+               }
+               if (!MathUtil.equals(time[0], 0)) {
+                       throw new IllegalArgumentException("Curve starts at time " + time[0]);
+               }
+               if (!MathUtil.equals(thrust[0], 0)) {
+                       throw new IllegalArgumentException("Curve starts at thrust " + thrust[0]);
+               }
+               if (!MathUtil.equals(thrust[thrust.length-1], 0)) {
+                       throw new IllegalArgumentException("Curve ends at thrust " + 
+                                       thrust[thrust.length-1]);
+               }
+               for (double t: thrust) {
+                       if (t < 0) {
+                               throw new IllegalArgumentException("Negative thrust.");
+                       }
+                       if (t > MAX_THRUST || Double.isNaN(t)) {
+                               throw new IllegalArgumentException("Invalid thrust " + t);
+                       }
+                       if (t > max)
+                               max = t;
+               }
+               for (Coordinate c: cg) {
+                       if (c.isNaN()) {
+                               throw new IllegalArgumentException("Invalid CG " + c);
+                       }
+                       if (c.x < 0 || c.x > length) {
+                               throw new IllegalArgumentException("Invalid CG position " + c.x);
+                       }
+                       if (c.weight < 0) {
+                               throw new IllegalArgumentException("Negative mass " + c.weight);
+                       }
+               }
+
+               this.maxThrust = max;
+               this.time = time.clone();
+               this.thrust = thrust.clone();
+               this.cg = cg.clone();
+               this.totalTime = time[time.length-1];
+       }
+
+
+       @Override
+       public double getTotalTime() {
+               return totalTime;
+       }
+
+       @Override
+       public double getMaxThrust() {
+               return maxThrust;
+       }
+       
+       @Override
+       public double getThrust(double t) {
+               if ((t < 0) || (t > totalTime))
+                       return 0;
+
+               for (int i=0; i < time.length-1; i++) {
+                       if ((t >= time[i]) && (t <= time[i+1])) {
+                               double delta = time[i+1] - time[i];
+                               if (delta < 0.0001) {
+                                       return thrust[i];
+                               }
+                               t = t - time[i];
+                               return thrust[i] * (1 - t/delta) + thrust[i+1] * (t/delta);
+                       }
+               }
+               assert false : "Should not be reached.";
+               return 0;
+       }
+
+
+       @Override
+       public Coordinate getCG(double t) {
+               if (t <= 0)
+                       return cg[0];
+               if (t >= totalTime)
+                       return cg[cg.length-1];
+               
+               for (int i=0; i < time.length-1; i++) {
+                       if ((t >= time[i]) && (t <= time[i+1])) {
+                               double delta = time[i+1] - time[i];
+                               t = t - time[i];
+                               return cg[i].multiply(1 - t/delta).add(cg[i+1].multiply(t/delta));
+                       }
+               }
+               assert false : "Should not be reached.";
+               return cg[cg.length-1];
+       }
+
+       
+       public double[] getTimePoints() {
+               return time.clone();
+       }
+       
+       public double[] getThrustPoints() {
+               return thrust.clone();
+       }
+       
+       public Coordinate[] getCGPoints() {
+               return cg.clone();
+       }
+       
+}
index d22ab67d3f9940875cf8f58b5887ddc859cd3f56..d3368f1f1365e97eed4e8032db0dc57974631589 100644 (file)
@@ -4,6 +4,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 
+import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.util.Coordinate;
 import net.sf.openrocket.util.MathUtil;
 
index 7561c21efb6645f85b3bb707bd84800ef8c23f71..901b5cb60fe067fa910bc60919cc1ee918731295 100644 (file)
@@ -4,6 +4,7 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 
+import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.util.Coordinate;
 import net.sf.openrocket.util.MathUtil;
 
diff --git a/src/net/sf/openrocket/rocketcomponent/Motor.java b/src/net/sf/openrocket/rocketcomponent/Motor.java
deleted file mode 100644 (file)
index 3eef45a..0000000
+++ /dev/null
@@ -1,639 +0,0 @@
-package net.sf.openrocket.rocketcomponent;
-
-import java.text.Collator;
-import java.util.Comparator;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.MathUtil;
-
-
-
-/**
- * Abstract base class for motors.  The methods that must be implemented are
- * {@link #getTotalTime()}, {@link #getThrust(double)} and {@link #getCG(double)}.
- * Additionally the method {@link #getMaxThrust()} may be overridden for efficiency.
- * <p>
- * 
- * NOTE:  The current implementation of {@link #getAverageTime()} and 
- * {@link #getAverageThrust()} assume that the class is immutable!
- * 
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-public abstract class Motor implements Comparable<Motor> {
-       
-       /**
-        * Enum of rocket motor types.
-        * 
-        * @author Sampo Niskanen <sampo.niskanen@iki.fi>
-        */
-       public enum Type {
-               SINGLE("Single-use", "Single-use solid propellant motor"), 
-               RELOAD("Reloadable", "Reloadable solid propellant motor"), 
-               HYBRID("Hybrid", "Hybrid rocket motor engine"), 
-               UNKNOWN("Unknown", "Unknown motor type");
-               
-               private final String name;
-               private final String description;
-               
-               Type(String name, String description) {
-                       this.name = name;
-                       this.description = description;
-               }
-
-               /**
-                * Return a short name of this motor type.
-                * @return  a short name of the motor type.
-                */
-               public String getName() {
-                       return name;
-               }
-               
-               /**
-                * Return a long description of this motor type.
-                * @return  a description of the motor type.
-                */
-               public String getDescription() {
-                       return description;
-               }
-               
-               @Override
-               public String toString() {
-                       return name;
-               }
-       }
-       
-       
-       /**
-        * Ejection charge delay value signifying a "plugged" motor with no ejection charge.
-        * The value is that of <code>Double.POSITIVE_INFINITY</code>.
-        */
-       public static final double PLUGGED = Double.POSITIVE_INFINITY;
-       
-       
-       /**
-        * Below what portion of maximum thrust is the motor chosen to be off when
-        * calculating average thrust and burn time.  NFPA 1125 defines the "official"
-        * burn time to be the time which the motor produces over 5% of its maximum thrust.
-        */
-       public static final double AVERAGE_MARGINAL = 0.05;
-       
-       /* All data is cached, so divisions can be very tight. */
-       private static final int DIVISIONS = 1000;
-
-       
-       //  Comparators:
-       private static final Collator COLLATOR = Collator.getInstance(Locale.US);
-       static {
-               COLLATOR.setStrength(Collator.PRIMARY);
-       }
-       private static DesignationComparator DESIGNATION_COMPARATOR = new DesignationComparator();
-       
-       
-       
-       
-       private final String manufacturer;
-       private final String designation;
-       private final String description;
-       private final Type motorType;
-       
-       private final double[] delays;
-       
-       private final double diameter;
-       private final double length;
-       
-       /* Cached data */
-       private double maxThrust = -1;
-       private double avgTime = -1;
-       private double avgThrust = -1;
-       private double totalImpulse = -1;
-       
-       
-       
-       /**
-        * Sole constructor.  None of the parameters may be <code>null</code>.
-        * 
-        * @param manufacturer  the manufacturer of the motor.
-        * @param designation   the motor designation.
-        * @param description   further description, including any comments on the origin
-        *                                              of the thrust curve.
-        * @param delays                an array of the standard ejection charge delays.  A plugged
-        *                                              motor (no ejection charge) is specified by a delay of
-        *                                              {@link #PLUGGED} (<code>Double.POSITIVE_INFINITY</code>).
-        * @param diameter              maximum diameter of the motor
-        * @param length                length of the motor
-        */
-       protected Motor(String manufacturer, String designation, String description, 
-                       Type type, double[] delays, double diameter, double length) {
-
-               if (manufacturer == null || designation == null || description == null ||
-                               type == null || delays == null) {
-                       throw new IllegalArgumentException("Parameters cannot be null.");
-               }
-               
-               this.manufacturer = manufacturer;
-               this.designation = designation;
-               this.description = description.trim();
-               this.motorType = type;
-               this.delays = delays.clone();
-               this.diameter = diameter;
-               this.length = length;
-       }
-
-
-       
-       /**
-        * Return the total burn time of the motor.  The method {@link #getThrust(double)}
-        * must return zero for time values greater than the return value.
-        * 
-        * @return  the total burn time of the motor.
-        */
-       public abstract double getTotalTime();
-
-
-       /**
-        * Return the thrust of the motor at the specified time.
-        * 
-        * @param time  time since the ignition of the motor.
-        * @return      the thrust at the specified time.
-        */
-       public abstract double getThrust(double time);
-       
-       
-       /**
-        * Return the average thrust of the motor between times t1 and t2.
-        * 
-        * @param t1    starting time since the ignition of the motor.
-        * @param t2    end time since the ignition of the motor.
-        * @return              the average thrust during the time period.
-        */
-       /* TODO: MEDIUM: Implement better method in subclass */
-       public double getThrust(double t1, double t2) {
-               double f = 0;
-               f += getThrust(t1);
-               f += getThrust(0.8*t1 + 0.2*t2);
-               f += getThrust(0.6*t1 + 0.4*t2);
-               f += getThrust(0.4*t1 + 0.6*t2);
-               f += getThrust(0.2*t1 + 0.8*t2);
-               f += getThrust(t2);
-               return f/6;
-       }
-
-       
-       /**
-        * Return the mass and CG of the motor at the specified time.
-        * 
-        * @param time  time since the ignition of the motor.
-        * @return      the mass and CG of the motor.
-        */
-       public abstract Coordinate getCG(double time);
-       
-       
-       
-       /**
-        * Return the mass of the motor at the specified time.  The original mass
-        * of the motor can be queried by <code>getMass(0)</code> and the burnt mass
-        * by <code>getMass(Double.MAX_VALUE)</code>.
-        * 
-        * @param time  time since the ignition of the motor.
-        * @return      the mass of the motor.
-        */
-       public double getMass(double time) {
-               return getCG(time).weight;
-       }
-       
-       
-       /**
-        * Return the longitudal moment of inertia of the motor at the specified time.
-        * This default method assumes that the mass of the motor is evenly distributed
-        * in a cylinder with the diameter and length of the motor.
-        * 
-        * @param time  time since the ignition of the motor.
-        * @return              the longitudal moment of inertia of the motor.
-        */
-       public double getLongitudalInertia(double time) {
-               return getMass(time) * (3.0*MathUtil.pow2(diameter/2) + MathUtil.pow2(length))/12;
-       }
-       
-       
-       
-       /**
-        * Return the rotational moment of inertia of the motor at the specified time.
-        * This default method assumes that the mass of the motor is evenly distributed
-        * in a cylinder with the diameter and length of the motor.
-        * 
-        * @param time  time since the ignition of the motor.
-        * @return              the rotational moment of inertia of the motor.
-        */
-       public double getRotationalInertia(double time) {
-               return getMass(time) * MathUtil.pow2(diameter) / 8;
-       }
-       
-       
-       
-       
-       /**
-        * Return the maximum thrust.  This implementation slices through the thrust curve
-        * searching for the maximum thrust.  Subclasses may wish to override this with a
-        * more efficient method.
-        * 
-        * @return  the maximum thrust of the motor
-        */
-       public double getMaxThrust() {
-               if (maxThrust < 0) {
-                       double time = getTotalTime();
-                       maxThrust = 0;
-                       
-                       for (int i=0; i < DIVISIONS; i++) {
-                               double t = time * i / DIVISIONS;
-                               double thrust = getThrust(t);
-                               
-                               if (thrust > maxThrust)
-                                       maxThrust = thrust;
-                       }
-               }
-               return maxThrust;
-       }
-       
-       
-       /**
-        * Return the time used in calculating the average thrust.  The time is the
-        * length of time that the motor produces over 5% ({@link #AVERAGE_MARGINAL})
-        * of its maximum thrust.
-        * 
-        * @return  the nominal burn time.
-        */
-       public double getAverageTime() {
-               // Compute average time lazily
-               if (avgTime < 0) {
-                       double max = getMaxThrust();
-                       double time = getTotalTime();
-                       
-                       avgTime = 0;
-                       for (int i=0; i <= DIVISIONS; i++) {
-                               double t = i*time/DIVISIONS;
-                               if (getThrust(t) >= max*AVERAGE_MARGINAL)
-                                       avgTime++;
-                       }
-                       avgTime *= time/(DIVISIONS+1);
-                       
-                       if (Double.isNaN(avgTime))
-                               throw new RuntimeException("Calculated avg. time is NaN for motor "+this);
-
-               }
-               return avgTime;
-       }
-       
-       
-       /**
-        * Return the calculated average thrust during the time the motor produces
-        * over 5% ({@link #AVERAGE_MARGINAL}) of its thrust.
-        * 
-        * @return  the nominal average thrust.
-        */
-       public double getAverageThrust() {
-               // Compute average thrust lazily
-               if (avgThrust < 0) {
-                       double max = getMaxThrust();
-                       double time = getTotalTime();
-                       int points = 0;
-                       
-                       avgThrust = 0;
-                       for (int i=0; i <= DIVISIONS; i++) {
-                               double t = i*time/DIVISIONS;
-                               double thrust = getThrust(t);
-                               if (thrust >= max*AVERAGE_MARGINAL) {
-                                       avgThrust += thrust;
-                                       points++;
-                               }
-                       }
-                       if (points > 0)
-                               avgThrust /= points;
-                       
-                       if (Double.isNaN(avgThrust))
-                               throw new RuntimeException("Calculated average thrust is NaN for motor "+this);
-               }
-               return avgThrust;
-       }
-       
-       
-       /**
-        * Return the total impulse of the motor.  This is calculated from the entire
-        * burn time, and therefore may differ from the value of {@link #getAverageTime()}
-        * and {@link #getAverageThrust()} multiplied together.
-        * 
-        * @return  the total impulse of the motor.
-        */
-       public double getTotalImpulse() {
-               // Compute total impulse lazily
-               if (totalImpulse < 0) {
-                       double time = getTotalTime();
-                       double f0, t0;
-                       
-                       totalImpulse = 0;
-                       t0 = 0;
-                       f0 = getThrust(0);
-                       for (int i=1; i < DIVISIONS; i++) {
-                               double t1 = time * i / DIVISIONS;
-                               double f1 = getThrust(t1); 
-                               totalImpulse += 0.5*(f0+f1)*(t1-t0);
-                               t0 = t1;
-                               f0 = f1;
-                       }
-                       
-                       if (Double.isNaN(totalImpulse))
-                               throw new RuntimeException("Calculated total impulse is NaN for motor "+this);
-               }
-               return totalImpulse;
-       }
-       
-
-       /**
-        * Return the manufacturer of the motor.
-        * 
-        * @return the manufacturer
-        */
-       public String getManufacturer() {
-               return manufacturer;
-       }
-       
-       /**
-        * Return the designation of the motor.
-        * 
-        * @return the designation
-        */
-       public String getDesignation() {
-               return designation;
-       }
-       
-       /**
-        * Return the designation of the motor, including a delay.
-        * 
-        * @param delay  the delay of the motor.
-        * @return               designation with delay.
-        */
-       public String getDesignation(double delay) {
-               return getDesignation() + "-" + getDelayString(delay);
-       }
-
-       
-       /**
-        * Return extra description for the motor.  This may include for example 
-        * comments on the source of the thrust curve.  The returned <code>String</code>
-        * may include new-lines.
-        * 
-        * @return the description
-        */
-       public String getDescription() {
-               return description;
-       }
-       
-       
-       /**
-        * Return the motor type.
-        * 
-        * @return  the motorType
-        */
-       public Type getMotorType() {
-               return motorType;
-       }
-
-
-
-       /**
-        * Return the standard ejection charge delays for the motor.  "Plugged" motors
-        * with no ejection charge are signified by the value {@link #PLUGGED}
-        * (<code>Double.POSITIVE_INFINITY</code>).
-        * 
-        * @return  the list of standard ejection charge delays, which may be empty.
-        */
-       public double[] getStandardDelays() {
-               return delays.clone();
-       }
-
-       /**
-        * Return the maximum diameter of the motor.
-        * 
-        * @return the diameter
-        */
-       public double getDiameter() {
-               return diameter;
-       }
-
-       /**
-        * Return the length of the motor.  This should be a "characteristic" length,
-        * and the exact definition may depend on the motor type.  Typically this should
-        * be the length from the bottom of the motor to the end of the maximum diameter
-        * portion, ignoring any smaller ejection charge compartments.
-        * 
-        * @return the length
-        */
-       public double getLength() {
-               return length;
-       }
-       
-       
-       /**
-        * Compares two <code>Motor</code> objects.  The motors are considered equal
-        * if they have identical manufacturers, designations and types, near-identical
-        * dimensions, burn times and delays and near-identical thrust curves
-        * (sampled at 10 equidistant points).
-        * <p>
-        * The comment field is ignored when comparing equality.
-        */
-       @Override
-       public boolean equals(Object o) {
-               if (!(o instanceof Motor))
-                       return false;
-               
-               Motor other = (Motor) o;
-               
-               // Tests manufacturer, designation, diameter and length
-               if (this.compareTo(other) != 0)
-                       return false;
-               
-               if (Math.abs(this.getTotalTime() - other.getTotalTime()) > 0.5 ||
-                               this.motorType != other.motorType ||
-                               this.delays.length != other.delays.length) {
-                       
-                       return false;
-               }
-               
-               for (int i=0; i < delays.length; i++) {
-                       // INF - INF == NaN, which produces false when compared
-                       if (Math.abs(this.delays[i] - other.delays[i]) > 0.5) {
-                               return false;
-                       }
-               }
-               
-               double time = getTotalTime();
-               for (int i=0; i < 10; i++) {
-                       double t = time * i/10;
-                       if (Math.abs(this.getThrust(t) - other.getThrust(t)) > 1) {
-                               return false;
-                       }
-               }
-               return true;
-       }
-       
-       /**
-        * A <code>hashCode</code> method compatible with the <code>equals</code>
-        * method.
-        */
-       @Override
-       public int hashCode() {
-               return (manufacturer.hashCode() + designation.hashCode() + 
-                               ((int)(length*1000)) + ((int)(diameter*1000)));
-       }
-       
-       
-       
-       @Override
-       public String toString() {
-               return manufacturer + " " + designation;
-       }
-       
-       
-       //////////  Static methods
-
-       
-       /**
-        * Return a String representation of a delay time.  If the delay is {@link #PLUGGED},
-        * returns "P".
-        *  
-        * @param delay         the delay time.
-        * @return                      the <code>String</code> representation.
-        */
-       public static String getDelayString(double delay) {
-               return getDelayString(delay,"P");
-       }
-       
-       /**
-        * Return a String representation of a delay time.  If the delay is {@link #PLUGGED},
-        * <code>plugged</code> is returned.
-        *   
-        * @param delay         the delay time.
-        * @param plugged       the return value if there is no ejection charge.
-        * @return                      the String representation.
-        */
-       public static String getDelayString(double delay, String plugged) {
-               if (delay == PLUGGED)
-                       return plugged;
-               delay = Math.rint(delay*10)/10;
-               if (MathUtil.equals(delay, Math.rint(delay)))
-                       return "" + ((int)delay);
-               return "" + delay;
-       }
-       
-
-       
-       
-       ////////////  Comparation
-       
-       
-
-       @Override
-       public int compareTo(Motor other) {
-               int value;
-               
-               // 1. Manufacturer
-               value = COLLATOR.compare(this.manufacturer, other.manufacturer);
-               if (value != 0)
-                       return value;
-               
-               // 2. Designation
-               value = DESIGNATION_COMPARATOR.compare(this.designation, other.designation);
-               if (value != 0)
-                       return value;
-               
-               // 3. Diameter
-               value = (int)((this.diameter - other.diameter)*1000000);
-               if (value != 0)
-                       return value;
-                               
-               // 4. Length
-               value = (int)((this.length - other.length)*1000000);
-               if (value != 0)
-                       return value;
-               
-               // 5. Total impulse
-               value = (int)((this.getTotalImpulse() - other.getTotalImpulse())*1000);
-               return value;
-       }
-       
-       
-       
-       public static Comparator<String> getDesignationComparator() {
-               return DESIGNATION_COMPARATOR;
-       }
-       
-       
-       /**
-        * Compares two motors by their designations.  The motors are ordered first
-        * by their motor class, second by their average thrust and lastly by any
-        * extra modifiers at the end of the designation.
-        * 
-        * @author Sampo Niskanen <sampo.niskanen@iki.fi>
-        */
-       private static class DesignationComparator implements Comparator<String> {
-               private Pattern pattern = 
-                       Pattern.compile("^([0-9][0-9]+|1/([1-8]))?([a-zA-Z])([0-9]+)(.*?)$");
-               
-               @Override
-               public int compare(String o1, String o2) {
-                       int value;
-                       Matcher m1, m2;
-                       
-                       m1 = pattern.matcher(o1);
-                       m2 = pattern.matcher(o2);
-                       
-                       if (m1.find() && m2.find()) {
-
-                               String o1Class = m1.group(3);
-                               int o1Thrust = Integer.parseInt(m1.group(4));
-                               String o1Extra = m1.group(5);
-                               
-                               String o2Class = m2.group(3);
-                               int o2Thrust = Integer.parseInt(m2.group(4));
-                               String o2Extra = m2.group(5);
-                               
-                               // 1. Motor class
-                               if (o1Class.equalsIgnoreCase("A") && o2Class.equalsIgnoreCase("A")) {
-                                       //  1/2A and 1/4A comparison
-                                       String sub1 = m1.group(2);
-                                       String sub2 = m2.group(2);
-
-                                       if (sub1 != null || sub2 != null) {
-                                               if (sub1 == null)
-                                                       sub1 = "1";
-                                               if (sub2 == null)
-                                                       sub2 = "1";
-                                               value = -COLLATOR.compare(sub1,sub2);
-                                               if (value != 0)
-                                                       return value;
-                                       }
-                               }
-                               value = COLLATOR.compare(o1Class,o2Class);
-                               if (value != 0)
-                                       return value;
-                               
-                               // 2. Average thrust
-                               if (o1Thrust != o2Thrust)
-                                       return o1Thrust - o2Thrust;
-                               
-                               // 3. Extra modifier
-                               return COLLATOR.compare(o1Extra, o2Extra);
-                               
-                       } else {
-                               
-                               System.out.println("Falling back");
-                               System.out.println("o1:"+o1 + " o2:"+o2);
-                               
-                               // Not understandable designation, simply compare strings
-                               return COLLATOR.compare(o1, o2);
-                       }
-               }
-       }
-}
index b6dbb5fba2328e312848f476c8a579f768eaa4b9..20f3c82e9e2a1b5305733ab719e5e20f5341987f 100644 (file)
@@ -1,5 +1,6 @@
 package net.sf.openrocket.rocketcomponent;
 
+import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.simulation.FlightEvent;
 import net.sf.openrocket.util.ChangeSource;
 
index 48904f892992a3c2e21586bfdb8079e445ca919d..3bd4dcdabd33ed5c246d36f08ee819f67363b2ec 100644 (file)
@@ -12,6 +12,7 @@ import java.util.UUID;
 import javax.swing.event.ChangeListener;
 import javax.swing.event.EventListenerList;
 
+import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.util.Coordinate;
 import net.sf.openrocket.util.MathUtil;
 
diff --git a/src/net/sf/openrocket/rocketcomponent/ThrustCurveMotor.java b/src/net/sf/openrocket/rocketcomponent/ThrustCurveMotor.java
deleted file mode 100644 (file)
index 9519aa3..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-package net.sf.openrocket.rocketcomponent;
-
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.MathUtil;
-
-/**
- * A class of motors specified by a fixed thrust curve.  This is the most
- * accurate for solid rocket motors.
- * 
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-public class ThrustCurveMotor extends Motor {
-       
-       public static final double MAX_THRUST = 10e6;
-
-       private final double[] time;
-       private final double[] thrust;
-       private final Coordinate[] cg;
-       
-       private final double totalTime;
-       private final double maxThrust;
-       
-
-       /**
-        * Sole constructor.  Sets all the properties of the motor.
-        * 
-        * @param manufacturer  the manufacturer of the motor.
-        * @param designation   the designation of the motor.
-        * @param description   extra description of the motor.
-        * @param diameter      diameter of the motor.
-        * @param length        length of the motor.
-        * @param time          the time points for the thrust curve.
-        * @param thrust        thrust at the time points.
-        * @param cg            cg at the time points.
-        */
-       public ThrustCurveMotor(String manufacturer, String designation, String description, 
-                       Motor.Type type, double[] delays, double diameter, double length,
-                       double[] time, double[] thrust, Coordinate[] cg) {
-               super(manufacturer, designation, description, type, delays, diameter, length);
-
-               double max = -1;
-
-               // Check argument validity
-               if ((time.length != thrust.length) || (time.length != cg.length)) {
-                       throw new IllegalArgumentException("Array lengths do not match, " +
-                                       "time:" + time.length + " thrust:" + thrust.length +
-                                       " cg:" + cg.length);
-               }
-               if (time.length < 2) {
-                       throw new IllegalArgumentException("Too short thrust-curve, length=" + 
-                                       time.length);
-               }
-               for (int i=0; i < time.length-1; i++) {
-                       if (time[i+1] < time[i]) {
-                               throw new IllegalArgumentException("Time goes backwards, " +
-                                               "time[" + i + "]=" + time[i] + " " +
-                                               "time[" + (i+1) + "]=" + time[i+1]);
-                       }
-               }
-               if (!MathUtil.equals(time[0], 0)) {
-                       throw new IllegalArgumentException("Curve starts at time " + time[0]);
-               }
-               if (!MathUtil.equals(thrust[0], 0)) {
-                       throw new IllegalArgumentException("Curve starts at thrust " + thrust[0]);
-               }
-               if (!MathUtil.equals(thrust[thrust.length-1], 0)) {
-                       throw new IllegalArgumentException("Curve ends at thrust " + 
-                                       thrust[thrust.length-1]);
-               }
-               for (double t: thrust) {
-                       if (t < 0) {
-                               throw new IllegalArgumentException("Negative thrust.");
-                       }
-                       if (t > MAX_THRUST || Double.isNaN(t)) {
-                               throw new IllegalArgumentException("Invalid thrust " + t);
-                       }
-                       if (t > max)
-                               max = t;
-               }
-               for (Coordinate c: cg) {
-                       if (c.isNaN()) {
-                               throw new IllegalArgumentException("Invalid CG " + c);
-                       }
-                       if (c.x < 0 || c.x > length) {
-                               throw new IllegalArgumentException("Invalid CG position " + c.x);
-                       }
-                       if (c.weight < 0) {
-                               throw new IllegalArgumentException("Negative mass " + c.weight);
-                       }
-               }
-
-               this.maxThrust = max;
-               this.time = time.clone();
-               this.thrust = thrust.clone();
-               this.cg = cg.clone();
-               this.totalTime = time[time.length-1];
-       }
-
-
-       @Override
-       public double getTotalTime() {
-               return totalTime;
-       }
-
-       @Override
-       public double getMaxThrust() {
-               return maxThrust;
-       }
-       
-       @Override
-       public double getThrust(double t) {
-               if ((t < 0) || (t > totalTime))
-                       return 0;
-
-               for (int i=0; i < time.length-1; i++) {
-                       if ((t >= time[i]) && (t <= time[i+1])) {
-                               double delta = time[i+1] - time[i];
-                               if (delta < 0.0001) {
-                                       return thrust[i];
-                               }
-                               t = t - time[i];
-                               return thrust[i] * (1 - t/delta) + thrust[i+1] * (t/delta);
-                       }
-               }
-               assert false : "Should not be reached.";
-               return 0;
-       }
-
-
-       @Override
-       public Coordinate getCG(double t) {
-               if (t <= 0)
-                       return cg[0];
-               if (t >= totalTime)
-                       return cg[cg.length-1];
-               
-               for (int i=0; i < time.length-1; i++) {
-                       if ((t >= time[i]) && (t <= time[i+1])) {
-                               double delta = time[i+1] - time[i];
-                               t = t - time[i];
-                               return cg[i].multiply(1 - t/delta).add(cg[i+1].multiply(t/delta));
-                       }
-               }
-               assert false : "Should not be reached.";
-               return cg[cg.length-1];
-       }
-
-       
-       public double[] getTimePoints() {
-               return time.clone();
-       }
-       
-       public double[] getThrustPoints() {
-               return thrust.clone();
-       }
-       
-       public Coordinate[] getCGPoints() {
-               return cg.clone();
-       }
-       
-}
index 2b67530eaebb92ae1bfef7b0d602eb3dc178f2e6..163b7ff2adc0ecc1b1348443ae646d0581049fae 100644 (file)
@@ -11,9 +11,9 @@ import net.sf.openrocket.aerodynamics.AtmosphericConditions;
 import net.sf.openrocket.aerodynamics.AtmosphericModel;
 import net.sf.openrocket.aerodynamics.Warning;
 import net.sf.openrocket.aerodynamics.WarningSet;
+import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.rocketcomponent.Clusterable;
 import net.sf.openrocket.rocketcomponent.Configuration;
-import net.sf.openrocket.rocketcomponent.Motor;
 import net.sf.openrocket.rocketcomponent.MotorMount;
 import net.sf.openrocket.rocketcomponent.RecoveryDevice;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
diff --git a/src/net/sf/openrocket/util/Analysis.java b/src/net/sf/openrocket/util/Analysis.java
deleted file mode 100644 (file)
index 505d979..0000000
+++ /dev/null
@@ -1,203 +0,0 @@
-package net.sf.openrocket.util;
-
-import static net.sf.openrocket.aerodynamics.AtmosphericConditions.GAMMA;
-import static net.sf.openrocket.aerodynamics.AtmosphericConditions.R;
-
-import java.io.File;
-import java.io.PrintStream;
-import java.util.Arrays;
-
-import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
-import net.sf.openrocket.aerodynamics.AerodynamicForces;
-import net.sf.openrocket.aerodynamics.AtmosphericConditions;
-import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
-import net.sf.openrocket.aerodynamics.ExactAtmosphericConditions;
-import net.sf.openrocket.aerodynamics.FlightConditions;
-import net.sf.openrocket.document.OpenRocketDocument;
-import net.sf.openrocket.file.GeneralRocketLoader;
-import net.sf.openrocket.file.RocketLoadException;
-import net.sf.openrocket.file.RocketLoader;
-import net.sf.openrocket.rocketcomponent.Configuration;
-
-public class Analysis {
-       
-       private static final double MACH_MIN = 0.01;
-       private static final double MACH_MAX = 5.00001;
-       private static final double MACH_STEP = 0.02;
-       
-       private static final double AOA_MACH = 0.6;
-       private static final double AOA_MIN = 0;
-       private static final double AOA_MAX = 15.00001*Math.PI/180;
-       private static final double AOA_STEP = 0.5*Math.PI/180;
-       
-       private static final double REYNOLDS = 9.8e6;
-       private static final double STAG_TEMP = 330;
-       
-       
-       private final RocketLoader loader = new GeneralRocketLoader();
-       private final AerodynamicCalculator calculator = new BarrowmanCalculator();
-       
-       private final FlightConditions conditions;
-       private final double length;
-       
-       private final Configuration config;
-       
-       private final AtmosphericConditions atmosphere;
-       
-       
-       
-       private Analysis(String filename) throws RocketLoadException {
-
-               OpenRocketDocument doc = loader.load(new File(filename));
-               config = doc.getRocket().getDefaultConfiguration();
-               
-               calculator.setConfiguration(config);
-               
-               conditions = new FlightConditions(config);
-               System.out.println("Children: " + Arrays.toString(config.getRocket().getChildren()));
-               System.out.println("Children: " + Arrays.toString(config.getRocket().getChild(0).getChildren()));
-               length = config.getLength();
-               System.out.println("Rocket length: " + (length*1000)+"mm");
-               
-               atmosphere = new ExactAtmosphericConditions();
-               
-       }
-       
-       
-       private double computeVelocityAndAtmosphere(double mach, double reynolds, double stagTemp) {
-               final double temperature;
-               final double pressure;
-               
-               
-               temperature = stagTemp / (1 + (GAMMA-1)/2 * MathUtil.pow2(mach));
-               
-               // Speed of sound
-               double c = 331.3 * Math.sqrt(1 + (temperature - 273.15)/273.15);
-               
-               // Free-stream velocity
-               double v0 = c * mach;
-               
-//             kin.visc. = (3.7291e-06 + 4.9944e-08 * temperature) / density
-               pressure = reynolds * (3.7291e-06 + 4.9944e-08 * temperature) * R * temperature / 
-                                       (v0 * length);
-               
-               atmosphere.pressure = pressure;
-               atmosphere.temperature = temperature;
-               conditions.setAtmosphericConditions(atmosphere);
-               conditions.setVelocity(v0);
-               
-               if (Math.abs(conditions.getMach() - mach) > 0.001) {
-                       System.err.println("Computed mach: "+conditions.getMach() + " requested "+mach);
-//                     System.exit(1);
-               }
-               
-               return v0;
-       }
-       
-       
-       
-       private void computeVsMach(PrintStream stream) {
-               
-               conditions.setAOA(0);
-               conditions.setTheta(45*Math.PI/180);
-               stream.println("% Mach, Caxial, CP, , CNa, Croll");
-               
-               for (double mach = MACH_MIN; mach <= MACH_MAX; mach += MACH_STEP) {
-                       
-                       computeVelocityAndAtmosphere(mach, REYNOLDS, STAG_TEMP);
-//                     conditions.setMach(mach);
-                       
-                       
-                       AerodynamicForces forces = calculator.getAerodynamicForces(0, conditions, null);
-                       
-
-                       double Re = conditions.getVelocity() * 
-                                       calculator.getConfiguration().getLength() / 
-                                       conditions.getAtmosphericConditions().getKinematicViscosity();
-                       if (Math.abs(Re - REYNOLDS) > 1) {
-                               throw new RuntimeException("Re="+Re);
-                       }
-                       stream.printf("%f, %f, %f, %f, %f\n", mach, forces.Caxial, forces.cp.x, forces.CNa, 
-                                       forces.Croll);
-               }
-               
-       }
-
-       
-       
-       private void computeVsAOA(PrintStream stream, double thetaDeg) {
-               
-               computeVelocityAndAtmosphere(AOA_MACH, REYNOLDS, STAG_TEMP);
-               conditions.setTheta(thetaDeg * Math.PI/180);
-               stream.println("% AOA, CP, CN, Cm   at theta = "+thetaDeg);
-               
-               for (double aoa = AOA_MIN; aoa <= AOA_MAX; aoa += AOA_STEP) {
-
-                       conditions.setAOA(aoa);
-                       AerodynamicForces forces = calculator.getAerodynamicForces(0, conditions, null);
-                       
-
-                       double Re = conditions.getVelocity() * 
-                                       calculator.getConfiguration().getLength() / 
-                                       conditions.getAtmosphericConditions().getKinematicViscosity();
-                       if (Math.abs(Re - REYNOLDS) > 1) {
-                               throw new RuntimeException("Re="+Re);
-                       }
-                       stream.printf("%f, %f, %f, %f\n", aoa*180/Math.PI, forces.cp.x, forces.CN, forces.Cm);
-               }
-               
-       }
-       
-       
-       
-
-       public static void main(String arg[]) throws Exception {
-               
-               if (arg.length != 2) {
-                       System.err.println("Arguments:  <rocket file> <output prefix>");
-                       System.exit(1);
-               }
-
-               Analysis a = new Analysis(arg[0]);
-               final String prefix = arg[1];
-               
-
-               String name;
-               double v0 = a.computeVelocityAndAtmosphere(0.6, 9.8e6, 322);
-               System.out.printf("Sanity test: mach = %.1f v=%.1f temp=%.1f pres=%.0f c=%.1f " +
-                               "ref.length=%.1fmm\n",
-                               a.conditions.getMach(), v0, a.atmosphere.temperature, a.atmosphere.pressure, 
-                               a.atmosphere.getMachSpeed(), a.conditions.getRefLength()*1000);
-               System.out.println();
-               
-               
-               // CA, CP, Croll vs. Mach  at AOA=0
-               name = prefix + "-CA-CP-CNa-Croll-vs-Mach.csv";
-               System.out.println("Computing CA, CP, CNa, Croll vs. Mach to file "+name);
-               a.computeVsMach(new PrintStream(name));
-
-               
-               // CN & Cm vs. AOA  at M=0.6
-               name = prefix + "-CP-CN-Cm-vs-AOA-0.csv";
-               System.out.println("Computing CP, CN, Cm vs. AOA at theta=0 to file "+name);
-               a.computeVsAOA(new PrintStream(name), 0);
-
-               // CN & Cm vs. AOA  at M=0.6
-               name = prefix + "-CP-CN-Cm-vs-AOA-22.5.csv";
-               System.out.println("Computing CP, CN, Cm vs. AOA at theta=22.5 to file "+name);
-               a.computeVsAOA(new PrintStream(name), 0);
-
-               // CN & Cm vs. AOA  at M=0.6
-               name = prefix + "-CP-CN-Cm-vs-AOA-45.csv";
-               System.out.println("Computing CP, CN, Cm vs. AOA at theta=45 to file "+name);
-               a.computeVsAOA(new PrintStream(name), 0);
-
-               
-               System.out.println("Done.");
-       }
-       
-       
-       
-       
-       
-}
index 5b88f08a694211a44bf8edcdc10d3e8c2037ff98..099074ec350343a7840d6582f51a4f25eafaba96 100644 (file)
@@ -10,10 +10,51 @@ import java.io.Serializable;
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
 public final class Coordinate implements Serializable {
+       
+       ////////  Debug section
+       /*
+        * Debugging info.  If openrocket.debug.coordinatecount is defined, a line is
+        * printed every 1000000 instantiations (or as many as defined).
+        */
+       private static final boolean COUNT_DEBUG;
+       private static final int COUNT_DIFF;
+       static {
+               String str = System.getProperty("openrocket.debug.coordinatecount", null);
+               int diff = 0;
+               if (str == null) {
+                       COUNT_DEBUG = false;
+                       COUNT_DIFF = 0;
+               } else {
+                       COUNT_DEBUG = true;
+                       try {
+                               diff = Integer.parseInt(str);
+                       } catch (NumberFormatException ignore) { }
+                       if (diff < 1000)
+                               diff = 1000000;
+                       COUNT_DIFF = diff;
+               }
+       }
+       
+       private static int count = 0;
+       {
+               // Debug count
+               if (COUNT_DEBUG) {
+                       count++;
+                       if ((count % COUNT_DIFF) == 0) {
+                               System.out.println("Coordinate instantiated " + count + " times.");
+                       }
+               }
+       }
+       
+       ////////  End debug section
+       
+       
+       
+       
        public static final Coordinate NUL = new Coordinate(0,0,0,0);
        public static final Coordinate NaN = new Coordinate(Double.NaN,Double.NaN,
                        Double.NaN,Double.NaN);
-       public static final double COMPARISON_DELTA = 0.000001;
+
        public final double x,y,z;
        public final double weight;
        
@@ -21,15 +62,6 @@ public final class Coordinate implements Serializable {
        private double length = -1;  /* Cached when calculated */
        
        
-       /* Count and report the number of times a Coordinate is constructed: */
-//     private static int count=0;
-//     {
-//             count++;
-//             if ((count % 1000) == 0) {
-//                     System.err.println("Coordinate instantiated "+count+" times");
-//             }
-//     }
-       
        
 
        public Coordinate() {
@@ -52,6 +84,7 @@ public final class Coordinate implements Serializable {
                this.y = y;
                this.z = z;
                this.weight=w;
+               
        }
 
        
@@ -261,31 +294,4 @@ public final class Coordinate implements Serializable {
        }
        
        
-       
-       public static void main(String[] arg) {
-               double a=1.2;
-               double x;
-               Coordinate c;
-               long t1, t2;
-               
-               x = 0;
-               t1 = System.nanoTime();
-               for (int i=0; i < 100000000; i++) {
-                       x = x + a;
-               }
-               t2 = System.nanoTime();
-               System.out.println("Value: "+x);
-               System.out.println("Plain addition: "+ ((t2-t1+500000)/1000000) + " ms");
-               
-               c = Coordinate.NUL;
-               t1 = System.nanoTime();
-               for (int i=0; i < 100000000; i++) {
-                       c = c.add(a,0,0);
-               }
-               t2 = System.nanoTime();
-               System.out.println("Value: "+c.x);
-               System.out.println("Coordinate addition: "+ ((t2-t1+500000)/1000000) + " ms");
-               
-       }
-       
 }
index a97487d30f71125a3aa2420e82da66530d66b5fc..fa2c25e778d6756a7c4013f81ac36646bf5060d3 100644 (file)
@@ -57,7 +57,9 @@ public class Icons {
 
        public static final Icon DELETE = loadImageIcon("pix/icons/delete.png", "Delete");
        
-       private static ImageIcon loadImageIcon(String file, String name) {
+       
+       
+       public static ImageIcon loadImageIcon(String file, String name) {
                URL url = ClassLoader.getSystemResource(file);
                if (url == null) {
                        System.err.println("Resource "+file+" not found!  Ignoring...");
index a714f81275a8b8ad8f3d520c8f60b9d988902d8f..a40742cbbab300c131eb5063d5959ebc9c4ba41f 100644 (file)
@@ -73,7 +73,7 @@ public class MathUtil {
                if (equals(toMin, toMax))
                        return toMin;
                if (equals(fromMin, fromMax)) {
-                       throw new IllegalArgumentException("from range is singular an to range is not.");
+                       throw new IllegalArgumentException("from range is singular and to range is not.");
                }
                return (value - fromMin)/(fromMax-fromMin) * (toMax - toMin) + toMin;
        }
@@ -168,6 +168,16 @@ public class MathUtil {
                return Math.abs(a-b) < EPSILON*absb;
        }
        
+       
+       /**
+        * Return the sign of the number.  This corresponds to Math.signum, but ignores
+        * the special cases of zero and NaN.  The value returned for those is arbitrary.
+        * <p>
+        * This method is about 4 times faster than Math.signum().
+        * 
+        * @param x             the checked value.
+        * @return              -1.0 if x<0; 1.0 if x>0; otherwise either -1.0 or 1.0.
+        */
        public static double sign(double x) {
                return (x<0) ? -1.0 : 1.0;
        }
@@ -180,38 +190,4 @@ public class MathUtil {
        */
        
        
-       public static void main(String[] arg) {
-               double nan = Double.NaN;
-               System.out.println("min(5,6)     = " + min(5, 6));
-               System.out.println("min(5,nan)   = " + min(5, nan));
-               System.out.println("min(nan,6)   = " + min(nan, 6));
-               System.out.println("min(nan,nan) = " + min(nan, nan));
-               System.out.println();
-               System.out.println("max(5,6)     = " + max(5, 6));
-               System.out.println("max(5,nan)   = " + max(5, nan));
-               System.out.println("max(nan,6)   = " + max(nan, 6));
-               System.out.println("max(nan,nan) = " + max(nan, nan));
-               System.out.println();
-               System.out.println("min(5,6,7)       = " + min(5, 6, 7));
-               System.out.println("min(5,6,nan)     = " + min(5, 6, nan));
-               System.out.println("min(5,nan,7)     = " + min(5, nan, 7));
-               System.out.println("min(5,nan,nan)   = " + min(5, nan, nan));
-               System.out.println("min(nan,6,7)     = " + min(nan, 6, 7));
-               System.out.println("min(nan,6,nan)   = " + min(nan, 6, nan));
-               System.out.println("min(nan,nan,7)   = " + min(nan, nan, 7));
-               System.out.println("min(nan,nan,nan) = " + min(nan, nan, nan));
-               System.out.println();
-               System.out.println("max(5,6,7)       = " + max(5, 6, 7));
-               System.out.println("max(5,6,nan)     = " + max(5, 6, nan));
-               System.out.println("max(5,nan,7)     = " + max(5, nan, 7));
-               System.out.println("max(5,nan,nan)   = " + max(5, nan, nan));
-               System.out.println("max(nan,6,7)     = " + max(nan, 6, 7));
-               System.out.println("max(nan,6,nan)   = " + max(nan, 6, nan));
-               System.out.println("max(nan,nan,7)   = " + max(nan, nan, 7));
-               System.out.println("max(nan,nan,nan) = " + max(nan, nan, nan));
-               System.out.println();
-               
-               
-       }
-       
 }
index 0ca273f6539abbaecd99d792f898050d22f618da..049b546832ee5d657abebe15be1be8be50185cb6 100644 (file)
@@ -55,6 +55,7 @@ public class Prefs {
        
        private static final String BUILD_VERSION;
        private static final String BUILD_SOURCE;
+       public static final String DEFAULT_BUILD_SOURCE = "default";
        
        static {
                try {
index f8f063e8d604348a46958c20aa57ba53110d2f3a..1672a0114d4ed1a16a70177b3aa1dda92be74093 100644 (file)
@@ -41,18 +41,4 @@ public class Rotation2D {
                return new Coordinate(cos*c.x + sin*c.y, cos*c.y - sin*c.x, c.z, c.weight);
        }
        
-
-       
-       public static void main(String arg[]) {
-               Coordinate c = new Coordinate(1,1,1,2.5);
-               Rotation2D rot = new Rotation2D(Math.PI/4);
-               
-               System.out.println("X: "+rot.rotateX(c));
-               System.out.println("Y: "+rot.rotateY(c));
-               System.out.println("Z: "+rot.rotateZ(c));
-               System.out.println("invX: "+rot.invRotateX(c));
-               System.out.println("invY: "+rot.invRotateY(c));
-               System.out.println("invZ: "+rot.invRotateZ(c));
-       }
-       
 }
index 4d150d42debc1ea6ad1d901e08d4bb2057f89b3f..c0e37c7acca15361e3ece50c4d771868dc510a89 100644 (file)
@@ -2,6 +2,7 @@ package net.sf.openrocket.util;
 
 import net.sf.openrocket.database.Databases;
 import net.sf.openrocket.material.Material;
+import net.sf.openrocket.motor.Motor;
 import net.sf.openrocket.rocketcomponent.BodyTube;
 import net.sf.openrocket.rocketcomponent.Bulkhead;
 import net.sf.openrocket.rocketcomponent.CenteringRing;
@@ -10,7 +11,6 @@ import net.sf.openrocket.rocketcomponent.IllegalFinPointException;
 import net.sf.openrocket.rocketcomponent.InnerTube;
 import net.sf.openrocket.rocketcomponent.LaunchLug;
 import net.sf.openrocket.rocketcomponent.MassComponent;
-import net.sf.openrocket.rocketcomponent.Motor;
 import net.sf.openrocket.rocketcomponent.NoseCone;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.rocketcomponent.Stage;
index fdbf99128a4cde2e20d8fe1c7cccd1b3143f8469..7d2ee85355bd6d85c4534fea0802771f3c55891c 100644 (file)
@@ -1,16 +1,13 @@
 package net.sf.openrocket.util;
 
-import java.util.Locale;
 
 public class TextUtil {
 
        /**
-        * Return a string of the double value with suitable precision.
+        * Return a string of the double value with suitable precision (5 digits).
         * The string is the shortest representation of the value including the
         * required precision.
         * 
-        * TODO: MEDIUM: Extra zeros are added unnecessarily to the end of the string.
-        * 
         * @param d             the value to present.
         * @return              a representation with suitable precision.
         */
@@ -31,46 +28,106 @@ public class TextUtil {
                }
                
                
+               final String sign = (d < 0) ? "-" : "";
                double abs = Math.abs(d);
                
-               if (abs < 0.001) {
-                       // Compact exponential notation
-                       int exp = 0;
-                       
-                       while (abs < 1.0) {
-                               abs *= 10;
-                               exp++;
-                       }
-                       
-                       String sign = (d < 0) ? "-" : "";
-                       return sign + String.format((Locale)null, "%.4fe-%d", abs, exp);
+               // Small and large values always in exponential notation
+               if (abs < 0.001 || abs >= 100000000) {
+                       return sign + exponentialFormat(abs);
                }
-               if (abs < 0.01)
-                       return String.format((Locale)null, "%.7f", d);
-               if (abs < 0.1)
-                       return String.format((Locale)null, "%.6f", d);
-               if (abs < 1)
-                       return String.format((Locale)null, "%.5f", d);
-               if (abs < 10)
-                       return String.format((Locale)null, "%.4f", d);
-               if (abs < 100)
-                       return String.format((Locale)null, "%.3f", d);
-               if (abs < 1000)
-                       return String.format((Locale)null, "%.2f", d);
-               if (abs < 10000)
-                       return String.format((Locale)null, "%.1f", d);
-               if (abs < 100000000.0)
-                       return String.format((Locale)null, "%.0f", d);
-                       
-               // Compact exponential notation
-               int exp = 0;
-               while (abs >= 10.0) {
-                       abs /= 10;
+               
+               // Check whether decimal or exponential notation is shorter
+               
+               String exp = exponentialFormat(abs);
+               String dec = decimalFormat(abs);
+               
+               if (dec.length() <= exp.length())
+                       return sign + dec;
+               else
+                       return sign + exp;
+       }
+       
+       
+       /*
+        * value must be positive and not zero!
+        */
+       private static String exponentialFormat(double value) {
+               int exp;
+               
+               exp = 0;
+               while (value < 1.0) {
+                       value *= 10;
+                       exp--;
+               }
+               while (value >= 10.0) {
+                       value /= 10;
                        exp++;
                }
                
-               String sign = (d < 0) ? "-" : "";
-               return sign + String.format((Locale)null, "%.4fe%d", abs, exp);
+               return shortDecimal(value, 4) + "e" + exp;
+       }
+       
+       
+       /*
+        * value must be positive and not zero!
+        */
+       private static String decimalFormat(double value) {
+               if (value >= 10000)
+                       return "" + (int)(value + 0.5);
+               
+               int decimals = 1;
+               double v = value;
+               while (v < 1000) {
+                       v *= 10;
+                       decimals++;
+               }
+               
+               return shortDecimal(value, decimals);
+       }
+       
+       
+       
+       
+       /*
+        * value must be positive!
+        */
+       private static String shortDecimal(double value, int decimals) {
+               
+               int whole = (int)value;
+               value -= whole;
+               
+               // Calculate limit, return when remaining value less than this
+               double limit;
+               limit = 0.5;
+               for (int i=0; i<decimals; i++)
+                       limit /= 10;
+               
+               
+               if (value < limit)
+                       return "" + whole; 
+               limit *= 10;
+
+               StringBuilder sb = new StringBuilder();
+               sb.append("" + whole);
+               sb.append('.');
+
+               
+               for (int i = 0; i<decimals; i++) {
+                       
+                       value *= 10;
+                       if (i == decimals-1)
+                               value += 0.5;
+                       whole = (int)value;
+                       value -= whole;
+                       sb.append((char)('0' + whole));
+                       
+                       if (value < limit)
+                               return sb.toString();
+                       limit *= 10;
+
+               }
+
+               return sb.toString();
        }
 
 }
diff --git a/src/net/sf/openrocket/utils/Analysis.java b/src/net/sf/openrocket/utils/Analysis.java
new file mode 100644 (file)
index 0000000..3208852
--- /dev/null
@@ -0,0 +1,204 @@
+package net.sf.openrocket.utils;
+
+import static net.sf.openrocket.aerodynamics.AtmosphericConditions.GAMMA;
+import static net.sf.openrocket.aerodynamics.AtmosphericConditions.R;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.util.Arrays;
+
+import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
+import net.sf.openrocket.aerodynamics.AerodynamicForces;
+import net.sf.openrocket.aerodynamics.AtmosphericConditions;
+import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
+import net.sf.openrocket.aerodynamics.ExactAtmosphericConditions;
+import net.sf.openrocket.aerodynamics.FlightConditions;
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.file.GeneralRocketLoader;
+import net.sf.openrocket.file.RocketLoadException;
+import net.sf.openrocket.file.RocketLoader;
+import net.sf.openrocket.rocketcomponent.Configuration;
+import net.sf.openrocket.util.MathUtil;
+
+public class Analysis {
+       
+       private static final double MACH_MIN = 0.01;
+       private static final double MACH_MAX = 5.00001;
+       private static final double MACH_STEP = 0.02;
+       
+       private static final double AOA_MACH = 0.6;
+       private static final double AOA_MIN = 0;
+       private static final double AOA_MAX = 15.00001*Math.PI/180;
+       private static final double AOA_STEP = 0.5*Math.PI/180;
+       
+       private static final double REYNOLDS = 9.8e6;
+       private static final double STAG_TEMP = 330;
+       
+       
+       private final RocketLoader loader = new GeneralRocketLoader();
+       private final AerodynamicCalculator calculator = new BarrowmanCalculator();
+       
+       private final FlightConditions conditions;
+       private final double length;
+       
+       private final Configuration config;
+       
+       private final AtmosphericConditions atmosphere;
+       
+       
+       
+       private Analysis(String filename) throws RocketLoadException {
+
+               OpenRocketDocument doc = loader.load(new File(filename));
+               config = doc.getRocket().getDefaultConfiguration();
+               
+               calculator.setConfiguration(config);
+               
+               conditions = new FlightConditions(config);
+               System.out.println("Children: " + Arrays.toString(config.getRocket().getChildren()));
+               System.out.println("Children: " + Arrays.toString(config.getRocket().getChild(0).getChildren()));
+               length = config.getLength();
+               System.out.println("Rocket length: " + (length*1000)+"mm");
+               
+               atmosphere = new ExactAtmosphericConditions();
+               
+       }
+       
+       
+       private double computeVelocityAndAtmosphere(double mach, double reynolds, double stagTemp) {
+               final double temperature;
+               final double pressure;
+               
+               
+               temperature = stagTemp / (1 + (GAMMA-1)/2 * MathUtil.pow2(mach));
+               
+               // Speed of sound
+               double c = 331.3 * Math.sqrt(1 + (temperature - 273.15)/273.15);
+               
+               // Free-stream velocity
+               double v0 = c * mach;
+               
+//             kin.visc. = (3.7291e-06 + 4.9944e-08 * temperature) / density
+               pressure = reynolds * (3.7291e-06 + 4.9944e-08 * temperature) * R * temperature / 
+                                       (v0 * length);
+               
+               atmosphere.pressure = pressure;
+               atmosphere.temperature = temperature;
+               conditions.setAtmosphericConditions(atmosphere);
+               conditions.setVelocity(v0);
+               
+               if (Math.abs(conditions.getMach() - mach) > 0.001) {
+                       System.err.println("Computed mach: "+conditions.getMach() + " requested "+mach);
+//                     System.exit(1);
+               }
+               
+               return v0;
+       }
+       
+       
+       
+       private void computeVsMach(PrintStream stream) {
+               
+               conditions.setAOA(0);
+               conditions.setTheta(45*Math.PI/180);
+               stream.println("% Mach, Caxial, CP, , CNa, Croll");
+               
+               for (double mach = MACH_MIN; mach <= MACH_MAX; mach += MACH_STEP) {
+                       
+                       computeVelocityAndAtmosphere(mach, REYNOLDS, STAG_TEMP);
+//                     conditions.setMach(mach);
+                       
+                       
+                       AerodynamicForces forces = calculator.getAerodynamicForces(0, conditions, null);
+                       
+
+                       double Re = conditions.getVelocity() * 
+                                       calculator.getConfiguration().getLength() / 
+                                       conditions.getAtmosphericConditions().getKinematicViscosity();
+                       if (Math.abs(Re - REYNOLDS) > 1) {
+                               throw new RuntimeException("Re="+Re);
+                       }
+                       stream.printf("%f, %f, %f, %f, %f\n", mach, forces.Caxial, forces.cp.x, forces.CNa, 
+                                       forces.Croll);
+               }
+               
+       }
+
+       
+       
+       private void computeVsAOA(PrintStream stream, double thetaDeg) {
+               
+               computeVelocityAndAtmosphere(AOA_MACH, REYNOLDS, STAG_TEMP);
+               conditions.setTheta(thetaDeg * Math.PI/180);
+               stream.println("% AOA, CP, CN, Cm   at theta = "+thetaDeg);
+               
+               for (double aoa = AOA_MIN; aoa <= AOA_MAX; aoa += AOA_STEP) {
+
+                       conditions.setAOA(aoa);
+                       AerodynamicForces forces = calculator.getAerodynamicForces(0, conditions, null);
+                       
+
+                       double Re = conditions.getVelocity() * 
+                                       calculator.getConfiguration().getLength() / 
+                                       conditions.getAtmosphericConditions().getKinematicViscosity();
+                       if (Math.abs(Re - REYNOLDS) > 1) {
+                               throw new RuntimeException("Re="+Re);
+                       }
+                       stream.printf("%f, %f, %f, %f\n", aoa*180/Math.PI, forces.cp.x, forces.CN, forces.Cm);
+               }
+               
+       }
+       
+       
+       
+
+       public static void main(String arg[]) throws Exception {
+               
+               if (arg.length != 2) {
+                       System.err.println("Arguments:  <rocket file> <output prefix>");
+                       System.exit(1);
+               }
+
+               Analysis a = new Analysis(arg[0]);
+               final String prefix = arg[1];
+               
+
+               String name;
+               double v0 = a.computeVelocityAndAtmosphere(0.6, 9.8e6, 322);
+               System.out.printf("Sanity test: mach = %.1f v=%.1f temp=%.1f pres=%.0f c=%.1f " +
+                               "ref.length=%.1fmm\n",
+                               a.conditions.getMach(), v0, a.atmosphere.temperature, a.atmosphere.pressure, 
+                               a.atmosphere.getMachSpeed(), a.conditions.getRefLength()*1000);
+               System.out.println();
+               
+               
+               // CA, CP, Croll vs. Mach  at AOA=0
+               name = prefix + "-CA-CP-CNa-Croll-vs-Mach.csv";
+               System.out.println("Computing CA, CP, CNa, Croll vs. Mach to file "+name);
+               a.computeVsMach(new PrintStream(name));
+
+               
+               // CN & Cm vs. AOA  at M=0.6
+               name = prefix + "-CP-CN-Cm-vs-AOA-0.csv";
+               System.out.println("Computing CP, CN, Cm vs. AOA at theta=0 to file "+name);
+               a.computeVsAOA(new PrintStream(name), 0);
+
+               // CN & Cm vs. AOA  at M=0.6
+               name = prefix + "-CP-CN-Cm-vs-AOA-22.5.csv";
+               System.out.println("Computing CP, CN, Cm vs. AOA at theta=22.5 to file "+name);
+               a.computeVsAOA(new PrintStream(name), 0);
+
+               // CN & Cm vs. AOA  at M=0.6
+               name = prefix + "-CP-CN-Cm-vs-AOA-45.csv";
+               System.out.println("Computing CP, CN, Cm vs. AOA at theta=45 to file "+name);
+               a.computeVsAOA(new PrintStream(name), 0);
+
+               
+               System.out.println("Done.");
+       }
+       
+       
+       
+       
+       
+}
index afe9e305989cf6006c5e623b0b6c4864364b4e08..cfd1944b46a21ed77335f19985af851066c2ed15 100644 (file)
@@ -7,8 +7,9 @@ import java.util.List;
 
 import net.sf.openrocket.file.GeneralMotorLoader;
 import net.sf.openrocket.file.MotorLoader;
-import net.sf.openrocket.rocketcomponent.Motor;
-import net.sf.openrocket.rocketcomponent.ThrustCurveMotor;
+import net.sf.openrocket.motor.Manufacturer;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.ThrustCurveMotor;
 
 public class MotorCheck {
 
@@ -38,7 +39,7 @@ public class MotorCheck {
                        }
                        
                        String base = file.split("_")[0];
-                       String mfg = MotorLoader.convertManufacturer(base);
+                       Manufacturer mfg = Manufacturer.getManufacturer(base);
                        
                        if (motors != null) {
                                if (motors.size() == 0) {
@@ -63,7 +64,7 @@ public class MotorCheck {
                                                        ok = false;
                                                }
                                                
-                                               if (!m.getManufacturer().equals(mfg)) {
+                                               if (m.getManufacturer() != mfg) {
                                                        System.out.println("ERROR: Inconsistent manufacturer " + 
                                                                        m.getManufacturer() + " (file name indicates " + mfg 
                                                                        + ")");
index ed6edc0fc0f833367f9f49e14bb16128a4f62f8b..28894cac01fa32b7ae2ec2562039621fd28ca156 100644 (file)
@@ -8,8 +8,9 @@ import java.util.List;
 
 import net.sf.openrocket.file.GeneralMotorLoader;
 import net.sf.openrocket.file.MotorLoader;
-import net.sf.openrocket.rocketcomponent.Motor;
-import net.sf.openrocket.rocketcomponent.ThrustCurveMotor;
+import net.sf.openrocket.motor.Manufacturer;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.ThrustCurveMotor;
 
 public class MotorCompare {
        
@@ -83,10 +84,10 @@ public class MotorCompare {
                
                // Manufacturers
                System.out.printf("Manufacture:");
-               String mfg = motors.get(0).getManufacturer();
+               Manufacturer mfg = motors.get(0).getManufacturer();
                for (Motor m: motors) {
                        System.out.printf("\t%s", m.getManufacturer());
-                       if (!m.getManufacturer().equals(mfg)) {
+                       if (m.getManufacturer() != mfg) {
                                cause.add("Manufacturer");
                                bad = true;
                        }
index c0194d29a3c801860313b71dc6fa63e28825fad3..194e15aff75ee49a7b406aca38c417613eb8a664 100644 (file)
@@ -8,8 +8,8 @@ import java.util.List;
 
 import net.sf.openrocket.file.GeneralMotorLoader;
 import net.sf.openrocket.file.MotorLoader;
-import net.sf.openrocket.rocketcomponent.Motor;
-import net.sf.openrocket.rocketcomponent.ThrustCurveMotor;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.ThrustCurveMotor;
 
 public class MotorPrinter {
 
diff --git a/test/Test.java b/test/Test.java
new file mode 100644 (file)
index 0000000..40338d7
--- /dev/null
@@ -0,0 +1,32 @@
+import net.sf.openrocket.util.Coordinate;
+
+
+public class Test {
+
+       public static int COUNT = 10000000;
+
+       public static void main(String[] args) {
+
+               for (int i=1; ; i++) {
+                       long t1 = System.currentTimeMillis();
+                       run();
+                       long t2 = System.currentTimeMillis();
+                       System.out.println("Run " + i + " took " + (t2-t1) + " ms");
+               }
+               
+       }
+       
+       
+       private static void run() {
+               Coordinate a = new Coordinate(1,1,1,1);
+               Coordinate b = new Coordinate(1,1,1,1);
+               
+               for (int i=0; i < COUNT; i++) {
+                       a = a.add(b);
+               }
+               System.out.println("value:"+a);
+               
+               return;
+       }
+
+}
diff --git a/test/net/sf/openrocket/motor/ManufacturerTest.java b/test/net/sf/openrocket/motor/ManufacturerTest.java
new file mode 100644 (file)
index 0000000..3ff66f0
--- /dev/null
@@ -0,0 +1,88 @@
+package net.sf.openrocket.motor;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class ManufacturerTest {
+
+       @Test
+       public void testExisting() {
+               
+               Manufacturer m1, m2, m3, m4, m5;
+               
+               m1 = Manufacturer.getManufacturer("aerotech");
+               m2 = Manufacturer.getManufacturer("a ");
+               m3 = Manufacturer.getManufacturer("-isp-");
+               m4 = Manufacturer.getManufacturer("at/rcs");
+               m5 = Manufacturer.getManufacturer("e");
+               
+               assertEquals(m1, m2);
+               assertEquals(m1, m3);
+               assertEquals(m1, m4);
+               assertNotSame(m1, m5);
+               
+       }
+       
+       @Test
+       public void testNew() {
+               
+               Manufacturer m1, m2, m3;
+               
+               m1 = Manufacturer.getManufacturer("Unknown");
+               m2 = Manufacturer.getManufacturer(" Unknown/ ");
+               m3 = Manufacturer.getManufacturer("Unknown/a");
+               
+               assertEquals(m1.getDisplayName(), "Unknown");
+               assertEquals(m2.getDisplayName(), "Unknown");
+               assertEquals(m1, m2);
+               
+               assertEquals(m3.getDisplayName(), "Unknown/a");
+               assertNotSame(m1, m3);
+               
+       }
+       
+       @Test
+       public void simpleNameTest() {
+               
+               Manufacturer m1, m2, m3, m4;
+               
+               m1 = Manufacturer.getManufacturer("cs");
+               m2 = Manufacturer.getManufacturer("Cesaroni Technology");
+               m3 = Manufacturer.getManufacturer("Cesaroni Technology Inc");
+               m4 = Manufacturer.getManufacturer("Cesaroni Technology Inc.");
+
+               assertEquals(m1.getDisplayName(), "Cesaroni Technology Inc.");
+               assertEquals(m1.toString(), "Cesaroni Technology Inc.");
+               assertEquals(m1.getSimpleName(), "Cesaroni Technology");
+               
+               assertEquals(m1, m2);
+               assertEquals(m1, m3);
+               assertEquals(m1, m4);
+               
+       }
+       
+       @Test
+       public void matchesTest() {
+               
+               Manufacturer m1;
+               
+               m1 = Manufacturer.getManufacturer("aerotech");
+               
+               assertTrue(m1.matches("a"));
+               assertTrue(m1.matches("a/"));
+               assertTrue(m1.matches("a/rcs"));
+               assertTrue(m1.matches("a/rms"));
+               assertTrue(m1.matches("aerotech  ...-/%¤#_!"));
+               assertTrue(m1.matches(" .isp/"));
+               
+               assertFalse(m1.matches("aero/tech"));
+               assertFalse(m1.matches("aero.tech"));
+               assertFalse(m1.matches("aero_tech"));
+               assertFalse(m1.matches("aero tech"));
+       }
+       
+}
diff --git a/test/net/sf/openrocket/util/CoordinateTest.java b/test/net/sf/openrocket/util/CoordinateTest.java
new file mode 100644 (file)
index 0000000..97c78a7
--- /dev/null
@@ -0,0 +1,64 @@
+package net.sf.openrocket.util;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class CoordinateTest {
+       
+       private static final double EPS = 0.0000000001;
+
+       @Test
+       public void coordinateTest() {
+               
+               Coordinate x = new Coordinate(1,1,1,1);
+               Coordinate y = new Coordinate(1,2,3,4);
+               
+               assertCoordinateEquals(new Coordinate(2,1,1,1), x.setX(2));
+               assertCoordinateEquals(new Coordinate(1,2,1,1), x.setY(2));
+               assertCoordinateEquals(new Coordinate(1,1,2,1), x.setZ(2));
+               assertCoordinateEquals(new Coordinate(1,1,1,2), x.setWeight(2));
+               assertCoordinateEquals(new Coordinate(2,3,4,1), x.setXYZ(y).add(1,1,1));
+               
+               assertFalse(x.isNaN());
+               assertTrue(x.setX(Double.NaN).isNaN());
+               assertTrue(Coordinate.NaN.isNaN());
+               
+               assertTrue(x.isWeighted());
+               assertFalse(x.setWeight(0).isWeighted());
+               
+               
+               assertCoordinateEquals(x, x.add(Coordinate.NUL));
+               assertCoordinateEquals(new Coordinate(2,3,4,5), x.add(y));
+               assertCoordinateEquals(new Coordinate(2,3,4,1), x.add(1,2,3));
+               assertCoordinateEquals(new Coordinate(2,3,4,5), x.add(1,2,3,4));
+
+               assertCoordinateEquals(new Coordinate(0,-1,-2,1), x.sub(y));
+               assertCoordinateEquals(new Coordinate(0,-1,-2,1), x.sub(1,2,3));
+
+               assertCoordinateEquals(new Coordinate(2,4,6,8), y.multiply(2));
+               
+               assertEquals(1+2+3, y.dot(x));
+               assertEquals(1+2+3, x.dot(y));
+               assertEquals(1+2+3, Coordinate.dot(x,y));
+               assertEquals(x.dot(x), x.length2());
+               assertEquals(y.dot(y), y.length2());
+               assertEquals(Math.sqrt(1+4+9), y.length(), EPS);
+               assertEquals(1, y.normalize().length(), EPS);
+               
+               assertCoordinateEquals(new Coordinate(1.75,1.75,1.75,4), 
+                               new Coordinate(1,1,1,1).average(new Coordinate(2,2,2,3)));
+               assertCoordinateEquals(new Coordinate(1,1,1,1), 
+                               new Coordinate(1,1,1,1).average(new Coordinate(2,2,2,0)));
+               assertCoordinateEquals(new Coordinate(1.5,1.5,1.5,0), 
+                               new Coordinate(1,1,1,0).average(new Coordinate(2,2,2,0)));
+               
+       }
+       
+       
+       private void assertCoordinateEquals(Coordinate a, Coordinate b) {
+               assertEquals(a, b);
+               assertEquals(a.weight, b.weight, EPS);
+       }
+       
+}
diff --git a/test/net/sf/openrocket/util/MathUtilTest.java b/test/net/sf/openrocket/util/MathUtilTest.java
new file mode 100644 (file)
index 0000000..8c135c8
--- /dev/null
@@ -0,0 +1,148 @@
+package net.sf.openrocket.util;
+
+import static java.lang.Double.NaN;
+import static java.lang.Math.PI;
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class MathUtilTest {
+       
+       public static final double EPS = 0.00000000001;
+
+       @Test
+       public void miscMathTest() {
+               
+               assertEquals(PI*PI, MathUtil.pow2(PI), EPS);
+               assertEquals(PI*PI*PI, MathUtil.pow3(PI), EPS);
+               assertEquals(PI*PI*PI*PI, MathUtil.pow4(PI), EPS);
+               
+               assertEquals(1.0, MathUtil.clamp(0.9999, 1.0, 2.0));
+               assertEquals(1.23, MathUtil.clamp(1.23, 1.0, 2.0));
+               assertEquals(2.0, MathUtil.clamp(2 + EPS/100, 1.0, 2.0));
+               
+               assertEquals(1.0f, MathUtil.clamp(0.9999f, 1.0f, 2.0f));
+               assertEquals(1.23f, MathUtil.clamp(1.23f, 1.0f, 2.0f));
+               assertEquals(2.0f, MathUtil.clamp(2.0001f, 1.0f, 2.0f));
+               
+               assertEquals(1, MathUtil.clamp(-3, 1, 5));
+               assertEquals(3, MathUtil.clamp(3, 1, 5));
+               assertEquals(5, MathUtil.clamp(6, 1, 5));
+               
+               assertEquals(-1.0, MathUtil.sign(Double.NEGATIVE_INFINITY));
+               assertEquals(-1.0, MathUtil.sign(-100));
+               assertEquals(-1.0, MathUtil.sign(Math.nextAfter(0.0, -1.0)));
+               assertEquals( 1.0, MathUtil.sign(Math.nextUp(0.0)));
+               assertEquals( 1.0, MathUtil.sign(100));
+               assertEquals( 1.0, MathUtil.sign(Double.POSITIVE_INFINITY));
+       }
+       
+       @Test
+       public void hypotTest() {
+               
+               for (int i=0; i<10000; i++) {
+                       double x = Math.random()*100 - 50;
+                       double y = Math.random()*i - i/2;
+                       double z = Math.hypot(x, y);
+                       assertEquals(z, MathUtil.hypot(x, y), EPS);
+               }
+               
+       }
+       
+       @Test
+       public void reduceTest() {
+               
+               for (int i=-1000; i<1000; i++) {
+                       double angle = Math.random() * 2*PI;
+                       double shift = angle + i*2*PI;
+                       assertEquals(angle, MathUtil.reduce360(shift), EPS);
+               }
+               
+               for (int i=-1000; i<1000; i++) {
+                       double angle = Math.random() * 2*PI - PI;
+                       double shift = angle + i*2*PI;
+                       assertEquals(angle, MathUtil.reduce180(shift), EPS);
+               }
+               
+       }
+       
+       @Test
+       public void minmaxTest() {
+               assertEquals(1.0, MathUtil.min(1.0, Math.nextUp(1.0)));
+               assertEquals(1.0, MathUtil.min(1.0, Double.POSITIVE_INFINITY));
+               assertEquals(1.0, MathUtil.min(NaN, 1.0));
+               assertEquals(1.0, MathUtil.min(1.0, NaN));
+               assertEquals(NaN, MathUtil.min(NaN, NaN));
+
+               assertEquals(Math.nextUp(1.0), MathUtil.max(1.0, Math.nextUp(1.0)));
+               assertEquals(Double.POSITIVE_INFINITY, MathUtil.max(1.0, Double.POSITIVE_INFINITY));
+               assertEquals(1.0, MathUtil.max(NaN, 1.0));
+               assertEquals(1.0, MathUtil.max(1.0, NaN));
+               assertEquals(NaN, MathUtil.max(NaN, NaN));
+               
+               assertEquals(1.0, MathUtil.min(1.0, 2.0, 3.0));
+               assertEquals(1.0, MathUtil.min(1.0, NaN, NaN));
+               assertEquals(1.0, MathUtil.min(NaN, 1.0, NaN));
+               assertEquals(1.0, MathUtil.min(NaN, NaN, 1.0));
+               assertEquals(1.0, MathUtil.min(2.0, NaN, 1.0));
+               assertEquals(1.0, MathUtil.min(1.0, 2.0, NaN));
+               assertEquals(1.0, MathUtil.min(NaN, 2.0, 1.0));
+               
+               assertEquals(3.0, MathUtil.max(1.0, 3.0, 2.0));
+               assertEquals(1.0, MathUtil.max(1.0, NaN, NaN));
+               assertEquals(1.0, MathUtil.max(NaN, 1.0, NaN));
+               assertEquals(1.0, MathUtil.max(NaN, NaN, 1.0));
+               assertEquals(2.0, MathUtil.max(2.0, NaN, 1.0));
+               assertEquals(2.0, MathUtil.max(1.0, 2.0, NaN));
+               assertEquals(2.0, MathUtil.max(NaN, 2.0, 1.0));
+       }
+       
+       @Test
+       public void mapTest() {
+               assertEquals(1.0, MathUtil.map(1.0, 0.0, 5.0, -1.0, 9.0), EPS);
+               assertEquals(7.0, MathUtil.map(1.0, 5.0, 0.0, -1.0, 9.0), EPS);
+               assertEquals(7.0, MathUtil.map(1.0, 0.0, 5.0, 9.0, -1.0), EPS);
+               assertEquals(6.0, MathUtil.map(6.0, 0.0, 5.0, Math.nextUp(6.0), 6.0), EPS);
+               assertEquals(6.0, MathUtil.map(6.0, 0.0, 0.0, Math.nextUp(6.0), 6.0), EPS);
+               try {
+                       MathUtil.map(6.0, 1.0, Math.nextUp(1.0), 1.0, 2.0);
+                       fail("Should not be reached.");
+               } catch (IllegalArgumentException normal) { }
+
+               assertEquals(7.0, MathUtil.map(Math.nextUp(1.0), 0.0, 5.0, 9.0, -1.0), EPS);
+       }
+       
+       
+       @Test
+       public void equalsTest() {
+               assertTrue(MathUtil.equals(1.0, 1.0 + MathUtil.EPSILON/3));
+               assertFalse(MathUtil.equals(1.0, 1.0 + MathUtil.EPSILON*2));
+               assertTrue(MathUtil.equals(-1.0, -1.0 + MathUtil.EPSILON/3));
+               assertFalse(MathUtil.equals(-1.0, -1.0 + MathUtil.EPSILON*2));
+               
+               for (double zero: new double[] { 0.0, MathUtil.EPSILON/10, -MathUtil.EPSILON/10 }) {
+
+                       assertTrue(MathUtil.equals(zero, MathUtil.EPSILON/3));
+                       assertTrue(MathUtil.equals(zero, -MathUtil.EPSILON/3));
+                       assertFalse(MathUtil.equals(zero, MathUtil.EPSILON*2));
+                       assertFalse(MathUtil.equals(zero, -MathUtil.EPSILON*2));
+
+                       assertTrue(MathUtil.equals(MathUtil.EPSILON/3, zero));
+                       assertTrue(MathUtil.equals(-MathUtil.EPSILON/3, zero));
+                       assertFalse(MathUtil.equals(MathUtil.EPSILON*2, zero));
+                       assertFalse(MathUtil.equals(-MathUtil.EPSILON*2, zero));
+
+               }
+               
+               for (double value: new double[] { PI*1e20, -PI*1e20 }) {
+                       assertTrue("value=" + value, MathUtil.equals(value, value + 1));
+                       assertTrue("value=" + value, MathUtil.equals(value, Math.nextUp(value)));
+                       assertTrue("value=" + value, MathUtil.equals(value, value * (1+MathUtil.EPSILON)));
+               }
+               
+               assertFalse(MathUtil.equals(NaN, 0.0));
+               assertFalse(MathUtil.equals(0.0, NaN));
+               assertFalse(MathUtil.equals(NaN, NaN));
+       }
+       
+}
diff --git a/test/net/sf/openrocket/util/Rotation2DTest.java b/test/net/sf/openrocket/util/Rotation2DTest.java
new file mode 100644 (file)
index 0000000..6cc6d91
--- /dev/null
@@ -0,0 +1,31 @@
+package net.sf.openrocket.util;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class Rotation2DTest {
+       
+       @Test
+       public void rotationTest() {
+               
+               double rot60 = 0.5;
+               double rot30 = Math.sqrt(3)/2;
+               
+               Coordinate x = new Coordinate(1,1,0);
+               Coordinate y = new Coordinate(0,1,1);
+               
+               Rotation2D rot = new Rotation2D(Math.PI/3);  // 60 deg
+               
+               assertEquals(new Coordinate(rot60, 1, -rot30), rot.rotateY(x));
+               assertEquals(new Coordinate(rot60, 1, rot30), rot.invRotateY(x));
+               
+               assertEquals(new Coordinate(1, rot60, rot30), rot.rotateX(x));
+               assertEquals(new Coordinate(1, rot60, -rot30), rot.invRotateX(x));
+               
+               assertEquals(new Coordinate(-rot30, rot60, 1), rot.rotateZ(y));
+               assertEquals(new Coordinate(rot30, rot60, 1), rot.invRotateZ(y));
+               
+       }
+
+}
diff --git a/test/net/sf/openrocket/util/TextUtilTest.java b/test/net/sf/openrocket/util/TextUtilTest.java
new file mode 100644 (file)
index 0000000..72eee8f
--- /dev/null
@@ -0,0 +1,210 @@
+package net.sf.openrocket.util;
+
+import static java.lang.Math.PI;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class TextUtilTest {
+
+       @Test
+       public void specialCaseTest() {
+               assertEquals("NaN",TextUtil.doubleToString(Double.NaN));
+               assertEquals("Inf",TextUtil.doubleToString(Double.POSITIVE_INFINITY));
+               assertEquals("-Inf",TextUtil.doubleToString(Double.NEGATIVE_INFINITY));
+               assertEquals("0",TextUtil.doubleToString(0.0));
+               assertEquals("0",TextUtil.doubleToString(MathUtil.EPSILON/3));
+               assertEquals("0",TextUtil.doubleToString(-MathUtil.EPSILON/3));
+       }
+       
+       @Test
+       public void longTest() {
+               
+               assertEquals("3.1416e-5", TextUtil.doubleToString(PI*1e-5));
+               assertEquals("3.1416e-4", TextUtil.doubleToString(PI*1e-4));
+               assertEquals("0.0031416", TextUtil.doubleToString(PI*1e-3));
+               assertEquals("0.031416",  TextUtil.doubleToString(PI*1e-2));
+               assertEquals("0.31416",   TextUtil.doubleToString(PI*1e-1));
+               assertEquals("3.1416",    TextUtil.doubleToString(PI));
+               assertEquals("31.416",    TextUtil.doubleToString(PI*1e1));
+               assertEquals("314.16",    TextUtil.doubleToString(PI*1e2));
+               assertEquals("3141.6",    TextUtil.doubleToString(PI*1e3));
+               assertEquals("31416",     TextUtil.doubleToString(PI*1e4));
+               assertEquals("314159",    TextUtil.doubleToString(PI*1e5));
+               assertEquals("3141593",   TextUtil.doubleToString(PI*1e6));
+               assertEquals("31415927",  TextUtil.doubleToString(PI*1e7));
+               assertEquals("3.1416e8",  TextUtil.doubleToString(PI*1e8));
+               assertEquals("3.1416e9",  TextUtil.doubleToString(PI*1e9));
+               assertEquals("3.1416e10", TextUtil.doubleToString(PI*1e10));
+
+               assertEquals("-3.1416e-5", TextUtil.doubleToString(-PI*1e-5));
+               assertEquals("-3.1416e-4", TextUtil.doubleToString(-PI*1e-4));
+               assertEquals("-0.0031416", TextUtil.doubleToString(-PI*1e-3));
+               assertEquals("-0.031416",  TextUtil.doubleToString(-PI*1e-2));
+               assertEquals("-0.31416",   TextUtil.doubleToString(-PI*1e-1));
+               assertEquals("-3.1416",    TextUtil.doubleToString(-PI));
+               assertEquals("-31.416",    TextUtil.doubleToString(-PI*1e1));
+               assertEquals("-314.16",    TextUtil.doubleToString(-PI*1e2));
+               assertEquals("-3141.6",    TextUtil.doubleToString(-PI*1e3));
+               assertEquals("-31416",     TextUtil.doubleToString(-PI*1e4));
+               assertEquals("-314159",    TextUtil.doubleToString(-PI*1e5));
+               assertEquals("-3141593",   TextUtil.doubleToString(-PI*1e6));
+               assertEquals("-31415927",  TextUtil.doubleToString(-PI*1e7));
+               assertEquals("-3.1416e8",  TextUtil.doubleToString(-PI*1e8));
+               assertEquals("-3.1416e9",  TextUtil.doubleToString(-PI*1e9));
+               assertEquals("-3.1416e10", TextUtil.doubleToString(-PI*1e10));
+
+       }
+       
+       @Test
+       public void shortTest() {
+               double p = 3.1;
+               assertEquals("3.1e-5", TextUtil.doubleToString(p*1e-5));
+               assertEquals("3.1e-4", TextUtil.doubleToString(p*1e-4));
+               assertEquals("0.0031", TextUtil.doubleToString(p*1e-3));
+               assertEquals("0.031",  TextUtil.doubleToString(p*1e-2));
+               assertEquals("0.31",   TextUtil.doubleToString(p*1e-1));
+               assertEquals("3.1",    TextUtil.doubleToString(p));
+               assertEquals("31",     TextUtil.doubleToString(p*1e1));
+               assertEquals("310",    TextUtil.doubleToString(p*1e2));
+               assertEquals("3100",   TextUtil.doubleToString(p*1e3));
+               assertEquals("31000",  TextUtil.doubleToString(p*1e4));
+               assertEquals("3.1e5",  TextUtil.doubleToString(p*1e5));
+               assertEquals("3.1e6",  TextUtil.doubleToString(p*1e6));
+               assertEquals("3.1e7",  TextUtil.doubleToString(p*1e7));
+               assertEquals("3.1e8",  TextUtil.doubleToString(p*1e8));
+               assertEquals("3.1e9",  TextUtil.doubleToString(p*1e9));
+               assertEquals("3.1e10", TextUtil.doubleToString(p*1e10));
+
+               assertEquals("-3.1e-5", TextUtil.doubleToString(-p*1e-5));
+               assertEquals("-3.1e-4", TextUtil.doubleToString(-p*1e-4));
+               assertEquals("-0.0031", TextUtil.doubleToString(-p*1e-3));
+               assertEquals("-0.031",  TextUtil.doubleToString(-p*1e-2));
+               assertEquals("-0.31",   TextUtil.doubleToString(-p*1e-1));
+               assertEquals("-3.1",    TextUtil.doubleToString(-p));
+               assertEquals("-31",     TextUtil.doubleToString(-p*1e1));
+               assertEquals("-310",    TextUtil.doubleToString(-p*1e2));
+               assertEquals("-3100",   TextUtil.doubleToString(-p*1e3));
+               assertEquals("-31000",  TextUtil.doubleToString(-p*1e4));
+               assertEquals("-3.1e5",  TextUtil.doubleToString(-p*1e5));
+               assertEquals("-3.1e6",  TextUtil.doubleToString(-p*1e6));
+               assertEquals("-3.1e7",  TextUtil.doubleToString(-p*1e7));
+               assertEquals("-3.1e8",  TextUtil.doubleToString(-p*1e8));
+               assertEquals("-3.1e9",  TextUtil.doubleToString(-p*1e9));
+               assertEquals("-3.1e10", TextUtil.doubleToString(-p*1e10));
+
+               p = 3;
+               assertEquals("3e-5", TextUtil.doubleToString(p*1e-5));
+               assertEquals("3e-4", TextUtil.doubleToString(p*1e-4));
+               assertEquals("3e-3", TextUtil.doubleToString(p*1e-3));
+               assertEquals("0.03", TextUtil.doubleToString(p*1e-2));
+               assertEquals("0.3",  TextUtil.doubleToString(p*1e-1));
+               assertEquals("3",    TextUtil.doubleToString(p));
+               assertEquals("30",   TextUtil.doubleToString(p*1e1));
+               assertEquals("300",  TextUtil.doubleToString(p*1e2));
+               assertEquals("3e3",  TextUtil.doubleToString(p*1e3));
+               assertEquals("3e4",  TextUtil.doubleToString(p*1e4));
+               assertEquals("3e5",  TextUtil.doubleToString(p*1e5));
+               assertEquals("3e6",  TextUtil.doubleToString(p*1e6));
+               assertEquals("3e7",  TextUtil.doubleToString(p*1e7));
+               assertEquals("3e8",  TextUtil.doubleToString(p*1e8));
+               assertEquals("3e9",  TextUtil.doubleToString(p*1e9));
+               assertEquals("3e10", TextUtil.doubleToString(p*1e10));
+
+               assertEquals("-3e-5", TextUtil.doubleToString(-p*1e-5));
+               assertEquals("-3e-4", TextUtil.doubleToString(-p*1e-4));
+               assertEquals("-3e-3", TextUtil.doubleToString(-p*1e-3));
+               assertEquals("-0.03", TextUtil.doubleToString(-p*1e-2));
+               assertEquals("-0.3",  TextUtil.doubleToString(-p*1e-1));
+               assertEquals("-3",    TextUtil.doubleToString(-p));
+               assertEquals("-30",   TextUtil.doubleToString(-p*1e1));
+               assertEquals("-300",  TextUtil.doubleToString(-p*1e2));
+               assertEquals("-3e3",  TextUtil.doubleToString(-p*1e3));
+               assertEquals("-3e4",  TextUtil.doubleToString(-p*1e4));
+               assertEquals("-3e5",  TextUtil.doubleToString(-p*1e5));
+               assertEquals("-3e6",  TextUtil.doubleToString(-p*1e6));
+               assertEquals("-3e7",  TextUtil.doubleToString(-p*1e7));
+               assertEquals("-3e8",  TextUtil.doubleToString(-p*1e8));
+               assertEquals("-3e9",  TextUtil.doubleToString(-p*1e9));
+               assertEquals("-3e10", TextUtil.doubleToString(-p*1e10));
+
+       }
+       
+       @Test
+       public void roundingTest() {
+               
+               /*
+                * Not testing with 1.00015 because it might be changed during number formatting
+                * calculations.  Its rounding is basically arbitrary anyway.
+                */
+               
+               assertEquals("1.0002e-5", TextUtil.doubleToString(1.0001500001e-5));
+               assertEquals("1.0001e-5", TextUtil.doubleToString(1.0001499999e-5));
+               assertEquals("1.0002e-4", TextUtil.doubleToString(1.0001500001e-4));
+               assertEquals("1.0001e-4", TextUtil.doubleToString(1.0001499999e-4));
+               assertEquals("0.0010002", TextUtil.doubleToString(1.0001500001e-3));
+               assertEquals("0.0010001", TextUtil.doubleToString(1.0001499999e-3));
+               assertEquals("0.010002",  TextUtil.doubleToString(1.0001500001e-2));
+               assertEquals("0.010001",  TextUtil.doubleToString(1.0001499999e-2));
+               assertEquals("0.10002",   TextUtil.doubleToString(1.0001500001e-1));
+               assertEquals("0.10001",   TextUtil.doubleToString(1.0001499999e-1));
+               assertEquals("1.0002",    TextUtil.doubleToString(1.0001500001));
+               assertEquals("1.0001",    TextUtil.doubleToString(1.0001499999));
+               assertEquals("10.002",    TextUtil.doubleToString(1.0001500001e1));
+               assertEquals("10.001",    TextUtil.doubleToString(1.0001499999e1));
+               assertEquals("100.02",    TextUtil.doubleToString(1.0001500001e2));
+               assertEquals("100.01",    TextUtil.doubleToString(1.0001499999e2));
+               assertEquals("1000.2",    TextUtil.doubleToString(1.0001500001e3));
+               assertEquals("1000.1",    TextUtil.doubleToString(1.0001499999e3));
+               assertEquals("10002",     TextUtil.doubleToString(1.0001500001e4));
+               assertEquals("10001",     TextUtil.doubleToString(1.0001499999e4));
+               assertEquals("100012",    TextUtil.doubleToString(1.00011500001e5));
+               assertEquals("100011",    TextUtil.doubleToString(1.00011499999e5));
+               assertEquals("1000112",   TextUtil.doubleToString(1.000111500001e6));
+               assertEquals("1000111",   TextUtil.doubleToString(1.000111499999e6));
+               assertEquals("10001112",  TextUtil.doubleToString(1.0001111500001e7));
+               assertEquals("10001111",  TextUtil.doubleToString(1.0001111499999e7));
+               assertEquals("1.0002e8",  TextUtil.doubleToString(1.0001500001e8));
+               assertEquals("1.0001e8",  TextUtil.doubleToString(1.0001499999e8));
+               assertEquals("1.0002e9",  TextUtil.doubleToString(1.0001500001e9));
+               assertEquals("1.0001e9",  TextUtil.doubleToString(1.0001499999e9));
+               assertEquals("1.0002e10", TextUtil.doubleToString(1.0001500001e10));
+               assertEquals("1.0001e10", TextUtil.doubleToString(1.0001499999e10));
+               
+
+               assertEquals("-1.0002e-5", TextUtil.doubleToString(-1.0001500001e-5));
+               assertEquals("-1.0001e-5", TextUtil.doubleToString(-1.0001499999e-5));
+               assertEquals("-1.0002e-4", TextUtil.doubleToString(-1.0001500001e-4));
+               assertEquals("-1.0001e-4", TextUtil.doubleToString(-1.0001499999e-4));
+               assertEquals("-0.0010002", TextUtil.doubleToString(-1.0001500001e-3));
+               assertEquals("-0.0010001", TextUtil.doubleToString(-1.0001499999e-3));
+               assertEquals("-0.010002",  TextUtil.doubleToString(-1.0001500001e-2));
+               assertEquals("-0.010001",  TextUtil.doubleToString(-1.0001499999e-2));
+               assertEquals("-0.10002",   TextUtil.doubleToString(-1.0001500001e-1));
+               assertEquals("-0.10001",   TextUtil.doubleToString(-1.0001499999e-1));
+               assertEquals("-1.0002",    TextUtil.doubleToString(-1.0001500001));
+               assertEquals("-1.0001",    TextUtil.doubleToString(-1.0001499999));
+               assertEquals("-10.002",    TextUtil.doubleToString(-1.0001500001e1));
+               assertEquals("-10.001",    TextUtil.doubleToString(-1.0001499999e1));
+               assertEquals("-100.02",    TextUtil.doubleToString(-1.0001500001e2));
+               assertEquals("-100.01",    TextUtil.doubleToString(-1.0001499999e2));
+               assertEquals("-1000.2",    TextUtil.doubleToString(-1.0001500001e3));
+               assertEquals("-1000.1",    TextUtil.doubleToString(-1.0001499999e3));
+               assertEquals("-10002",     TextUtil.doubleToString(-1.0001500001e4));
+               assertEquals("-10001",     TextUtil.doubleToString(-1.0001499999e4));
+               assertEquals("-100012",    TextUtil.doubleToString(-1.00011500001e5));
+               assertEquals("-100011",    TextUtil.doubleToString(-1.00011499999e5));
+               assertEquals("-1000112",   TextUtil.doubleToString(-1.000111500001e6));
+               assertEquals("-1000111",   TextUtil.doubleToString(-1.000111499999e6));
+               assertEquals("-10001112",  TextUtil.doubleToString(-1.0001111500001e7));
+               assertEquals("-10001111",  TextUtil.doubleToString(-1.0001111499999e7));
+               assertEquals("-1.0002e8",  TextUtil.doubleToString(-1.0001500001e8));
+               assertEquals("-1.0001e8",  TextUtil.doubleToString(-1.0001499999e8));
+               assertEquals("-1.0002e9",  TextUtil.doubleToString(-1.0001500001e9));
+               assertEquals("-1.0001e9",  TextUtil.doubleToString(-1.0001499999e9));
+               assertEquals("-1.0002e10", TextUtil.doubleToString(-1.0001500001e10));
+               assertEquals("-1.0001e10", TextUtil.doubleToString(-1.0001499999e10));
+               
+       }
+       
+}