From: plaa Date: Sun, 30 Aug 2009 15:46:18 +0000 (+0000) Subject: updates for 0.9.3 X-Git-Tag: upstream/1.0.0~20 X-Git-Url: https://git.gag.com/?p=debian%2Fopenrocket;a=commitdiff_plain;h=dfc10c016c5f1bb4b7714dff414562f2f32e2866 updates for 0.9.3 git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@20 180e2498-e6e9-4542-8430-84ac67f01cd8 --- diff --git a/.classpath b/.classpath index 290b5a5e..fc1bd663 100644 --- a/.classpath +++ b/.classpath @@ -2,10 +2,12 @@ + + diff --git a/ChangeLog b/ChangeLog index 75658dd2..44f2149e 100644 --- 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 6bcc1be1..0309f3e2 100644 --- 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 diff --git a/build.properties b/build.properties index a4010f5a..4bf3b9e3 100644 --- a/build.properties +++ b/build.properties @@ -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 index 00000000..206593ed 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 index 00000000..cbce15f8 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 index 00000000..a356787d 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 index 00000000..c33abef4 Binary files /dev/null and b/pix-src/eventicons/event-recovery-device-deployment.xcf.gz differ diff --git a/pix/eventicons/copyright.txt b/pix/eventicons/copyright.txt index 9fa8d1b0..50170ef5 100644 --- a/pix/eventicons/copyright.txt +++ b/pix/eventicons/copyright.txt @@ -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 index 00000000..d58eb8e1 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 index 00000000..583e402a 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 index 00000000..85d714df 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 index 00000000..df701c14 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 index 00000000..0ce17f77 Binary files /dev/null and b/pix/icon/icon-about.png differ diff --git a/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java b/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java index efdd14b1..35d53a18 100644 --- a/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java +++ b/src/net/sf/openrocket/aerodynamics/AerodynamicCalculator.java @@ -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; diff --git a/src/net/sf/openrocket/database/Databases.java b/src/net/sf/openrocket/database/Databases.java index 3f4d8e20..f3fa4bcf 100644 --- a/src/net/sf/openrocket/database/Databases.java +++ b/src/net/sf/openrocket/database/Databases.java @@ -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; diff --git a/src/net/sf/openrocket/file/GeneralMotorLoader.java b/src/net/sf/openrocket/file/GeneralMotorLoader.java index cd4ec007..5981c2d0 100644 --- a/src/net/sf/openrocket/file/GeneralMotorLoader.java +++ b/src/net/sf/openrocket/file/GeneralMotorLoader.java @@ -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. diff --git a/src/net/sf/openrocket/file/MotorLoader.java b/src/net/sf/openrocket/file/MotorLoader.java index 2988a03e..004b14f1 100644 --- a/src/net/sf/openrocket/file/MotorLoader.java +++ b/src/net/sf/openrocket/file/MotorLoader.java @@ -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 { - /** - * Manufacturer codes to expand. These are general translations that are used - * both in RASP and RockSim engine files. - */ - private static final Map MANUFACTURER_CODES = - new HashMap(); - 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 InputStream. The file is read using * the default charset returned by {@link #getDefaultCharset()}. @@ -330,28 +171,7 @@ public abstract class MotorLoader implements Loader { } 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 time, List thrust, diff --git a/src/net/sf/openrocket/file/OpenRocketLoader.java b/src/net/sf/openrocket/file/OpenRocketLoader.java index f7b270fc..1d33fd18 100644 --- a/src/net/sf/openrocket/file/OpenRocketLoader.java +++ b/src/net/sf/openrocket/file/OpenRocketLoader.java @@ -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")) { diff --git a/src/net/sf/openrocket/file/RASPMotorLoader.java b/src/net/sf/openrocket/file/RASPMotorLoader.java index 4de79825..558e7cd8 100644 --- a/src/net/sf/openrocket/file/RASPMotorLoader.java +++ b/src/net/sf/openrocket/file/RASPMotorLoader.java @@ -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) { diff --git a/src/net/sf/openrocket/file/RockSimMotorLoader.java b/src/net/sf/openrocket/file/RockSimMotorLoader.java index eef74ac9..b39a9218 100644 --- a/src/net/sf/openrocket/file/RockSimMotorLoader.java +++ b/src/net/sf/openrocket/file/RockSimMotorLoader.java @@ -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); diff --git a/src/net/sf/openrocket/file/openrocket/RocketComponentSaver.java b/src/net/sf/openrocket/file/openrocket/RocketComponentSaver.java index 1a1a3156..a9da9b2f 100644 --- a/src/net/sf/openrocket/file/openrocket/RocketComponentSaver.java +++ b/src/net/sf/openrocket/file/openrocket/RocketComponentSaver.java @@ -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(" " + motor.getMotorType().name().toLowerCase() + ""); } - elements.add(" " + RocketSaver.escapeXML(motor.getManufacturer()) + ""); + elements.add(" " + RocketSaver.escapeXML(motor.getManufacturer().getSimpleName()) + ""); elements.add(" " + RocketSaver.escapeXML(motor.getDesignation()) + ""); elements.add(" " + motor.getDiameter() + ""); elements.add(" " + motor.getLength() + ""); diff --git a/src/net/sf/openrocket/gui/configdialog/MotorConfig.java b/src/net/sf/openrocket/gui/configdialog/MotorConfig.java index 7e0afef3..c4210e6c 100644 --- a/src/net/sf/openrocket/gui/configdialog/MotorConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/MotorConfig.java @@ -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))); } diff --git a/src/net/sf/openrocket/gui/dialogs/AboutDialog.java b/src/net/sf/openrocket/gui/dialogs/AboutDialog.java index 3cc99f5c..3b151f70 100644 --- a/src/net/sf/openrocket/gui/dialogs/AboutDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/AboutDialog.java @@ -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); diff --git a/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java b/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java index add91327..b58d48a3 100644 --- a/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java @@ -143,7 +143,7 @@ public class BugReportDialog extends JDialog { this.pack(); this.setLocationRelativeTo(parent); GUIUtil.installEscapeCloseOperation(this); -// GUIUtil.setDefaultButton(close); + GUIUtil.setDefaultButton(send); } diff --git a/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java b/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java index e554c413..a42d8a94 100644 --- a/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/EditMotorConfigurationDialog.java @@ -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; diff --git a/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java b/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java index 3a5a154c..95b2aad6 100644 --- a/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java +++ b/src/net/sf/openrocket/gui/dialogs/MotorChooserDialog.java @@ -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) { diff --git a/src/net/sf/openrocket/gui/plot/PlotDialog.java b/src/net/sf/openrocket/gui/plot/PlotDialog.java index 0b8c4acd..a40c841a 100644 --- a/src/net/sf/openrocket/gui/plot/PlotDialog.java +++ b/src/net/sf/openrocket/gui/plot/PlotDialog.java @@ -81,16 +81,18 @@ public class PlotDialog extends JDialog { private static final Map EVENT_IMAGES = new HashMap(); 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"); } diff --git a/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java b/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java index f6d7d9de..35158def 100644 --- a/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java +++ b/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java @@ -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 index 00000000..3e7365be --- /dev/null +++ b/src/net/sf/openrocket/motor/Manufacturer.java @@ -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 manufacturers = new HashSet(); + static { + + // AeroTech has many name combinations... + List names = new ArrayList(); + 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 allNames; + private final Set searchNames; + + + private Manufacturer(String displayName, String simpleName, String... alternateNames) { + this.displayName = displayName; + this.simpleName = simpleName; + + Set all = new HashSet(); + Set search = new HashSet(); + + 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 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 index 00000000..0da9c3b4 --- /dev/null +++ b/src/net/sf/openrocket/motor/Motor.java @@ -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. + *

+ * + * NOTE: The current implementation of {@link #getAverageTime()} and + * {@link #getAverageThrust()} assume that the class is immutable! + * + * @author Sampo Niskanen + */ +public abstract class Motor implements Comparable { + + /** + * Enum of rocket motor types. + * + * @author Sampo Niskanen + */ + 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 Double.POSITIVE_INFINITY. + */ + 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 null. + * + * @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} (Double.POSITIVE_INFINITY). + * @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 getMass(0) and the burnt mass + * by getMass(Double.MAX_VALUE). + * + * @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 String + * 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} + * (Double.POSITIVE_INFINITY). + * + * @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 Motor 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). + *

+ * 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 hashCode method compatible with the equals + * 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 String 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}, + * plugged 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 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 + */ + private static class DesignationComparator implements Comparator { + 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 index 00000000..e495defe --- /dev/null +++ b/src/net/sf/openrocket/motor/ThrustCurveMotor.java @@ -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 + */ +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(); + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/BodyTube.java b/src/net/sf/openrocket/rocketcomponent/BodyTube.java index d22ab67d..d3368f1f 100644 --- a/src/net/sf/openrocket/rocketcomponent/BodyTube.java +++ b/src/net/sf/openrocket/rocketcomponent/BodyTube.java @@ -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; diff --git a/src/net/sf/openrocket/rocketcomponent/InnerTube.java b/src/net/sf/openrocket/rocketcomponent/InnerTube.java index 7561c21e..901b5cb6 100644 --- a/src/net/sf/openrocket/rocketcomponent/InnerTube.java +++ b/src/net/sf/openrocket/rocketcomponent/InnerTube.java @@ -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 index 3eef45a1..00000000 --- a/src/net/sf/openrocket/rocketcomponent/Motor.java +++ /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. - *

- * - * NOTE: The current implementation of {@link #getAverageTime()} and - * {@link #getAverageThrust()} assume that the class is immutable! - * - * @author Sampo Niskanen - */ -public abstract class Motor implements Comparable { - - /** - * Enum of rocket motor types. - * - * @author Sampo Niskanen - */ - 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 Double.POSITIVE_INFINITY. - */ - 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 null. - * - * @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} (Double.POSITIVE_INFINITY). - * @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 getMass(0) and the burnt mass - * by getMass(Double.MAX_VALUE). - * - * @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 String - * 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} - * (Double.POSITIVE_INFINITY). - * - * @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 Motor 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). - *

- * 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 hashCode method compatible with the equals - * 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 String 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}, - * plugged 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 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 - */ - private static class DesignationComparator implements Comparator { - 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/rocketcomponent/MotorMount.java b/src/net/sf/openrocket/rocketcomponent/MotorMount.java index b6dbb5fb..20f3c82e 100644 --- a/src/net/sf/openrocket/rocketcomponent/MotorMount.java +++ b/src/net/sf/openrocket/rocketcomponent/MotorMount.java @@ -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; diff --git a/src/net/sf/openrocket/rocketcomponent/Rocket.java b/src/net/sf/openrocket/rocketcomponent/Rocket.java index 48904f89..3bd4dcda 100644 --- a/src/net/sf/openrocket/rocketcomponent/Rocket.java +++ b/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -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 index 9519aa3e..00000000 --- a/src/net/sf/openrocket/rocketcomponent/ThrustCurveMotor.java +++ /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 - */ -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(); - } - -} diff --git a/src/net/sf/openrocket/simulation/FlightSimulator.java b/src/net/sf/openrocket/simulation/FlightSimulator.java index 2b67530e..163b7ff2 100644 --- a/src/net/sf/openrocket/simulation/FlightSimulator.java +++ b/src/net/sf/openrocket/simulation/FlightSimulator.java @@ -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 index 505d979f..00000000 --- a/src/net/sf/openrocket/util/Analysis.java +++ /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: "); - 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."); - } - - - - - -} diff --git a/src/net/sf/openrocket/util/Coordinate.java b/src/net/sf/openrocket/util/Coordinate.java index 5b88f08a..099074ec 100644 --- a/src/net/sf/openrocket/util/Coordinate.java +++ b/src/net/sf/openrocket/util/Coordinate.java @@ -10,10 +10,51 @@ import java.io.Serializable; * @author Sampo Niskanen */ 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"); - - } - } diff --git a/src/net/sf/openrocket/util/Icons.java b/src/net/sf/openrocket/util/Icons.java index a97487d3..fa2c25e7 100644 --- a/src/net/sf/openrocket/util/Icons.java +++ b/src/net/sf/openrocket/util/Icons.java @@ -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..."); diff --git a/src/net/sf/openrocket/util/MathUtil.java b/src/net/sf/openrocket/util/MathUtil.java index a714f812..a40742cb 100644 --- a/src/net/sf/openrocket/util/MathUtil.java +++ b/src/net/sf/openrocket/util/MathUtil.java @@ -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. + *

+ * 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(); - - - } - } diff --git a/src/net/sf/openrocket/util/Prefs.java b/src/net/sf/openrocket/util/Prefs.java index 0ca273f6..049b5468 100644 --- a/src/net/sf/openrocket/util/Prefs.java +++ b/src/net/sf/openrocket/util/Prefs.java @@ -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 { diff --git a/src/net/sf/openrocket/util/Rotation2D.java b/src/net/sf/openrocket/util/Rotation2D.java index f8f063e8..1672a011 100644 --- a/src/net/sf/openrocket/util/Rotation2D.java +++ b/src/net/sf/openrocket/util/Rotation2D.java @@ -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)); - } - } diff --git a/src/net/sf/openrocket/util/Test.java b/src/net/sf/openrocket/util/Test.java index 4d150d42..c0e37c7a 100644 --- a/src/net/sf/openrocket/util/Test.java +++ b/src/net/sf/openrocket/util/Test.java @@ -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; diff --git a/src/net/sf/openrocket/util/TextUtil.java b/src/net/sf/openrocket/util/TextUtil.java index fdbf9912..7d2ee853 100644 --- a/src/net/sf/openrocket/util/TextUtil.java +++ b/src/net/sf/openrocket/util/TextUtil.java @@ -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 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: "); + 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."); + } + + + + + +} diff --git a/src/net/sf/openrocket/utils/MotorCheck.java b/src/net/sf/openrocket/utils/MotorCheck.java index afe9e305..cfd1944b 100644 --- a/src/net/sf/openrocket/utils/MotorCheck.java +++ b/src/net/sf/openrocket/utils/MotorCheck.java @@ -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 + ")"); diff --git a/src/net/sf/openrocket/utils/MotorCompare.java b/src/net/sf/openrocket/utils/MotorCompare.java index ed6edc0f..28894cac 100644 --- a/src/net/sf/openrocket/utils/MotorCompare.java +++ b/src/net/sf/openrocket/utils/MotorCompare.java @@ -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; } diff --git a/src/net/sf/openrocket/utils/MotorPrinter.java b/src/net/sf/openrocket/utils/MotorPrinter.java index c0194d29..194e15af 100644 --- a/src/net/sf/openrocket/utils/MotorPrinter.java +++ b/src/net/sf/openrocket/utils/MotorPrinter.java @@ -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 index 00000000..40338d7c --- /dev/null +++ b/test/Test.java @@ -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 index 00000000..3ff66f0b --- /dev/null +++ b/test/net/sf/openrocket/motor/ManufacturerTest.java @@ -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 index 00000000..97c78a7a --- /dev/null +++ b/test/net/sf/openrocket/util/CoordinateTest.java @@ -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 index 00000000..8c135c8a --- /dev/null +++ b/test/net/sf/openrocket/util/MathUtilTest.java @@ -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 index 00000000..6cc6d91f --- /dev/null +++ b/test/net/sf/openrocket/util/Rotation2DTest.java @@ -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 index 00000000..72eee8f8 --- /dev/null +++ b/test/net/sf/openrocket/util/TextUtilTest.java @@ -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)); + + } + +}