<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="extra-src"/>
+ <classpathentry kind="src" path="test"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="/home/sampo/Projects/OpenRocket/lib/miglayout15-swing.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/JCommon 1.0.16"/>
<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/JFreeChart 1.0.13"/>
<classpathentry kind="lib" path="/home/sampo/Projects/OpenRocket/extra-lib/RXTXcomm.jar"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+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
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:
- 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
# 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
+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.
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;
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;
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;
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.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
-import net.sf.openrocket.rocketcomponent.Motor;
+import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.util.MathUtil;
public abstract class MotorLoader implements Loader<Motor> {
- /**
- * Manufacturer codes to expand. These are general translations that are used
- * both in RASP and RockSim engine files.
- */
- private static final Map<String,String> MANUFACTURER_CODES =
- new HashMap<String,String>();
- static {
-
- /*
- * TODO: CRITICAL: Should names have Inc. LLC. etc?
- */
-
- // AeroTech has many name combinations...
- for (String s: new String[] { "A", "AT", "AERO", "AEROT", "AEROTECH" }) {
- MANUFACTURER_CODES.put(s, "AeroTech");
- MANUFACTURER_CODES.put(s+"-RMS", "AeroTech");
- MANUFACTURER_CODES.put(s+"/RMS", "AeroTech");
- MANUFACTURER_CODES.put(s+"-RCS", "AeroTech");
- MANUFACTURER_CODES.put(s+"/RCS", "AeroTech");
- MANUFACTURER_CODES.put("RCS-" + s, "AeroTech");
- MANUFACTURER_CODES.put("RCS/" + s, "AeroTech");
- MANUFACTURER_CODES.put(s+"-APOGEE", "AeroTech");
- MANUFACTURER_CODES.put(s+"/APOGEE", "AeroTech");
- }
- MANUFACTURER_CODES.put("ISP", "AeroTech");
-
- MANUFACTURER_CODES.put("AHR", "Alpha Hybrid Rocketry LLC");
- MANUFACTURER_CODES.put("ALPHA", "Alpha Hybrid Rocketry LLC");
- MANUFACTURER_CODES.put("ALPHA HYBRID", "Alpha Hybrid Rocketry LLC");
- MANUFACTURER_CODES.put("ALPHA HYBRIDS", "Alpha Hybrid Rocketry LLC");
- MANUFACTURER_CODES.put("ALPHA HYBRID ROCKETRY", "Alpha Hybrid Rocketry LLC");
- MANUFACTURER_CODES.put("ALPHA HYBRIDS ROCKETRY", "Alpha Hybrid Rocketry LLC");
- MANUFACTURER_CODES.put("ALPHA HYBRID ROCKETRY LLC", "Alpha Hybrid Rocketry LLC");
- MANUFACTURER_CODES.put("ALPHA HYBRID ROCKETRY, LLC", "Alpha Hybrid Rocketry LLC");
-
- MANUFACTURER_CODES.put("AMW", "Animal Motor Works");
- MANUFACTURER_CODES.put("AW", "Animal Motor Works");
- MANUFACTURER_CODES.put("ANIMAL", "Animal Motor Works");
- MANUFACTURER_CODES.put("ANIMAL MOTOR WORKS", "Animal Motor Works");
-
- MANUFACTURER_CODES.put("AP", "Apogee");
- MANUFACTURER_CODES.put("APOG", "Apogee");
- MANUFACTURER_CODES.put("APOGEE", "Apogee");
- MANUFACTURER_CODES.put("P", "Apogee");
-
- MANUFACTURER_CODES.put("CES", "Cesaroni Technology Inc.");
- MANUFACTURER_CODES.put("CESARONI", "Cesaroni Technology Inc.");
- MANUFACTURER_CODES.put("CESARONI TECHNOLOGY", "Cesaroni Technology Inc.");
- MANUFACTURER_CODES.put("CESARONI TECHNOLOGY INC", "Cesaroni Technology Inc.");
- MANUFACTURER_CODES.put("CESARONI TECHNOLOGY INC.", "Cesaroni Technology Inc.");
- MANUFACTURER_CODES.put("CESARONI TECHNOLOGY INCORPORATED", "Cesaroni Technology Inc.");
- MANUFACTURER_CODES.put("CTI", "Cesaroni Technology Inc.");
- MANUFACTURER_CODES.put("CS", "Cesaroni Technology Inc.");
- MANUFACTURER_CODES.put("CSR", "Cesaroni Technology Inc.");
- MANUFACTURER_CODES.put("PRO38", "Cesaroni Technology Inc.");
-
- MANUFACTURER_CODES.put("CR", "Contrail Rockets");
- MANUFACTURER_CODES.put("CONTR", "Contrail Rockets");
- MANUFACTURER_CODES.put("CONTRAIL", "Contrail Rockets");
- MANUFACTURER_CODES.put("CONTRAIL ROCKET", "Contrail Rockets");
- MANUFACTURER_CODES.put("CONTRAIL ROCKETS", "Contrail Rockets");
-
- MANUFACTURER_CODES.put("E", "Estes");
- MANUFACTURER_CODES.put("ES", "Estes");
- MANUFACTURER_CODES.put("ESTES", "Estes");
-
- MANUFACTURER_CODES.put("EM", "Ellis Mountain");
- MANUFACTURER_CODES.put("ELLIS", "Ellis Mountain");
- MANUFACTURER_CODES.put("ELLIS MOUNTAIN", "Ellis Mountain");
- MANUFACTURER_CODES.put("ELLIS MOUNTAIN ROCKET", "Ellis Mountain");
- MANUFACTURER_CODES.put("ELLIS MOUNTAIN ROCKETS", "Ellis Mountain");
-
- MANUFACTURER_CODES.put("GR", "Gorilla Rocket Motors");
- MANUFACTURER_CODES.put("GORILLA", "Gorilla Rocket Motors");
- MANUFACTURER_CODES.put("GORILLA ROCKET", "Gorilla Rocket Motors");
- MANUFACTURER_CODES.put("GORILLA ROCKETS", "Gorilla Rocket Motors");
- MANUFACTURER_CODES.put("GORILLA MOTOR", "Gorilla Rocket Motors");
- MANUFACTURER_CODES.put("GORILLA MOTORS", "Gorilla Rocket Motors");
- MANUFACTURER_CODES.put("GORILLA ROCKET MOTOR", "Gorilla Rocket Motors");
- MANUFACTURER_CODES.put("GORILLA ROCKET MOTORS", "Gorilla Rocket Motors");
-
- MANUFACTURER_CODES.put("H", "HyperTEK");
- MANUFACTURER_CODES.put("HT", "HyperTEK");
- MANUFACTURER_CODES.put("HYPER", "HyperTEK");
- MANUFACTURER_CODES.put("HYPERTEK", "HyperTEK");
-
- MANUFACTURER_CODES.put("K", "Kosdon by AeroTech");
- MANUFACTURER_CODES.put("KBA", "Kosdon by AeroTech");
- MANUFACTURER_CODES.put("K/AT", "Kosdon by AeroTech");
- MANUFACTURER_CODES.put("K-AT", "Kosdon by AeroTech");
- MANUFACTURER_CODES.put("KOS", "Kosdon by AeroTech");
- MANUFACTURER_CODES.put("KOSDON", "Kosdon by AeroTech");
- MANUFACTURER_CODES.put("KOSDON/AT", "Kosdon by AeroTech");
- MANUFACTURER_CODES.put("KOSDON-AT", "Kosdon by AeroTech");
- MANUFACTURER_CODES.put("KOSDON/AEROTECH", "Kosdon by AeroTech");
- MANUFACTURER_CODES.put("KOSDON-AEROTECH", "Kosdon by AeroTech");
- MANUFACTURER_CODES.put("KOSDON-BY-AEROTECH", "Kosdon by AeroTech");
- MANUFACTURER_CODES.put("KOSDON BY AEROTECH", "Kosdon by AeroTech");
-
- MANUFACTURER_CODES.put("LOKI", "Loki Research");
- MANUFACTURER_CODES.put("LOKI RESEARCH", "Loki Research");
- MANUFACTURER_CODES.put("LR", "Loki Research");
-
- MANUFACTURER_CODES.put("PM", "Public Missiles, Ltd.");
- MANUFACTURER_CODES.put("PML", "Public Missiles, Ltd.");
- MANUFACTURER_CODES.put("PUBLIC MISSILES", "Public Missiles, Ltd.");
- MANUFACTURER_CODES.put("PUBLIC MISSILES LTD", "Public Missiles, Ltd.");
- MANUFACTURER_CODES.put("PUBLIC MISSILES, LTD", "Public Missiles, Ltd.");
- MANUFACTURER_CODES.put("PUBLIC MISSILES LTD.", "Public Missiles, Ltd.");
- MANUFACTURER_CODES.put("PUBLIC MISSILES, LTD.", "Public Missiles, Ltd.");
- MANUFACTURER_CODES.put("PUBLIC MISSILES LIMITED", "Public Missiles, Ltd.");
- MANUFACTURER_CODES.put("PUBLIC MISSILES, LIMITED", "Public Missiles, Ltd.");
-
- MANUFACTURER_CODES.put("PP", "Propulsion Polymers");
- MANUFACTURER_CODES.put("PROP", "Propulsion Polymers");
- MANUFACTURER_CODES.put("PROPULSION", "Propulsion Polymers");
- MANUFACTURER_CODES.put("PROPULSION-POLYMERS", "Propulsion Polymers");
- MANUFACTURER_CODES.put("PROPULSION POLYMERS", "Propulsion Polymers");
-
- MANUFACTURER_CODES.put("Q", "Quest");
- MANUFACTURER_CODES.put("QU", "Quest");
- MANUFACTURER_CODES.put("QUEST", "Quest");
-
- MANUFACTURER_CODES.put("RATT", "RATT Works");
- MANUFACTURER_CODES.put("RATT WORKS", "RATT Works");
- MANUFACTURER_CODES.put("RT", "RATT Works");
- MANUFACTURER_CODES.put("RTW", "RATT Works");
-
- MANUFACTURER_CODES.put("RR", "Roadrunner Rocketry");
- MANUFACTURER_CODES.put("ROADRUNNER", "Roadrunner Rocketry");
- MANUFACTURER_CODES.put("ROADRUNNER ROCKETRY", "Roadrunner Rocketry");
-
- MANUFACTURER_CODES.put("RV", "Rocketvision");
- MANUFACTURER_CODES.put("ROCKETVISION", "Rocketvision");
-
- MANUFACTURER_CODES.put("SR", "Sky Ripper Systems");
- MANUFACTURER_CODES.put("SRS", "Sky Ripper Systems");
- MANUFACTURER_CODES.put("SKYR", "Sky Ripper Systems");
- MANUFACTURER_CODES.put("SKYRIPPER", "Sky Ripper Systems");
- MANUFACTURER_CODES.put("SKYRIPPER SYSTEMS", "Sky Ripper Systems");
- MANUFACTURER_CODES.put("SKY RIPPER SYSTEMS", "Sky Ripper Systems");
-
- MANUFACTURER_CODES.put("WCH", "West Coast Hybrids");
- MANUFACTURER_CODES.put("WCR", "West Coast Hybrids");
- MANUFACTURER_CODES.put("WEST COAST HYBRIDS", "West Coast Hybrids");
-
- MANUFACTURER_CODES.put("SF", "WECO Feuerwerk"); // Previously Sachsen Feuerwerks
- MANUFACTURER_CODES.put("SACHSEN FEUERWERK", "WECO Feuerwerk");
- MANUFACTURER_CODES.put("SACHSEN FEUERWERKS", "WECO Feuerwerk");
- MANUFACTURER_CODES.put("WECO", "WECO Feuerwerk");
- MANUFACTURER_CODES.put("WECO FEUERWERK", "WECO Feuerwerk");
- MANUFACTURER_CODES.put("WECO FEUERWERKS", "WECO Feuerwerk");
- }
-
-
-
-
/**
* Load motors from the specified <code>InputStream</code>. The file is read using
* the default charset returned by {@link #getDefaultCharset()}.
} while (index < primary.size()-1);
}
-
- /**
- * Convert a manufacturer string. This should be done for all manufacturers
- * for overall consistency. This includes both RASP name expansions and some
- * general renaming. This should also be performed when loading designs in order
- * to identify manufacturers correctly.
- *
- * @param mfg the original manufacturer / manufacturer code.
- * @return the manufacturer name.
- */
- public static String convertManufacturer(String mfg) {
- // Replace underscore and trim
- mfg = mfg.replace('_', ' ').trim();
-
- // Check for conversion
- String conv = MANUFACTURER_CODES.get(mfg.toUpperCase());
- if (conv != null)
- return conv;
- else
- return mfg;
- }
-
+
@SuppressWarnings("unchecked")
protected static void finalizeThrustCurve(List<Double> time, List<Double> thrust,
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;
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;
} else if (element.equals("manufacturer")) {
// Manufacturer
- manufacturer = MotorLoader.convertManufacturer(content);
+ manufacturer = content;
} else if (element.equals("designation")) {
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 {
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 " +
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) {
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;
str = attributes.get("mfg");
if (str == null)
throw new SAXException("Manufacturer missing");
- manufacturer = convertManufacturer(str);
+ manufacturer = str;
// Designation
str = attributes.get("code");
}
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);
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;
if (motor.getMotorType() != Motor.Type.UNKNOWN) {
elements.add(" <type>" + motor.getMotorType().name().toLowerCase() + "</type>");
}
- elements.add(" <manufacturer>" + RocketSaver.escapeXML(motor.getManufacturer()) + "</manufacturer>");
+ elements.add(" <manufacturer>" + RocketSaver.escapeXML(motor.getManufacturer().getSimpleName()) + "</manufacturer>");
elements.add(" <designation>" + RocketSaver.escapeXML(motor.getDesignation()) + "</designation>");
elements.add(" <diameter>" + motor.getDiameter() + "</diameter>");
elements.add(" <length>" + motor.getLength() + "</length>");
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;
if (m == null)
motorLabel.setText("None");
else
- motorLabel.setText(m.getManufacturer() + " " +
+ motorLabel.setText(m.getManufacturer().getDisplayName() + " " +
m.getDesignation(mount.getMotorDelay(id)));
}
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 {
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");
AboutDialog.this.dispose();
}
});
- panel.add(close, "right");
+ panel.add(close, "spanx, right");
this.add(panel);
this.setTitle("OpenRocket " + version);
this.pack();
this.setLocationRelativeTo(parent);
GUIUtil.installEscapeCloseOperation(this);
-// GUIUtil.setDefaultButton(close);
+ GUIUtil.setDefaultButton(send);
}
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;
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;
MANUFACTURER("Manufacturer",100) {
@Override
public String getValue(Motor m) {
- return m.getManufacturer();
+ return m.getManufacturer().getDisplayName();
}
// @Override
// public String getToolTipText(Motor m) {
private static final Map<FlightEvent.Type, Image> EVENT_IMAGES =
new HashMap<FlightEvent.Type, Image>();
static {
- loadImage(FlightEvent.Type.LAUNCH, "pix/spheres/red-16x16.png");
+ loadImage(FlightEvent.Type.LAUNCH, "pix/eventicons/event-launch.png");
loadImage(FlightEvent.Type.LIFTOFF, "pix/eventicons/event-liftoff.png");
loadImage(FlightEvent.Type.LAUNCHROD, "pix/eventicons/event-launchrod.png");
loadImage(FlightEvent.Type.IGNITION, "pix/eventicons/event-ignition.png");
loadImage(FlightEvent.Type.BURNOUT, "pix/eventicons/event-burnout.png");
- loadImage(FlightEvent.Type.EJECTION_CHARGE, "pix/spheres/green-16x16.png");
- loadImage(FlightEvent.Type.STAGE_SEPARATION, "pix/eventicons/event-stage-separation.png");
+ loadImage(FlightEvent.Type.EJECTION_CHARGE,"pix/eventicons/event-ejection-charge.png");
+ loadImage(FlightEvent.Type.STAGE_SEPARATION,
+ "pix/eventicons/event-stage-separation.png");
loadImage(FlightEvent.Type.APOGEE, "pix/eventicons/event-apogee.png");
- loadImage(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, "pix/spheres/blue-16x16.png");
- loadImage(FlightEvent.Type.GROUND_HIT, "pix/spheres/gray-16x16.png");
+ loadImage(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT,
+ "pix/eventicons/event-recovery-device-deployment.png");
+ loadImage(FlightEvent.Type.GROUND_HIT, "pix/eventicons/event-ground-hit.png");
loadImage(FlightEvent.Type.SIMULATION_END, "pix/eventicons/event-simulation-end.png");
}
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;
--- /dev/null
+package net.sf.openrocket.motor;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class Manufacturer {
+
+ private static Set<Manufacturer> manufacturers = new HashSet<Manufacturer>();
+ static {
+
+ // AeroTech has many name combinations...
+ List<String> names = new ArrayList<String>();
+ for (String s: new String[] { "A", "AT", "AERO", "AEROT", "AEROTECH" }) {
+ names.add(s);
+ names.add(s+"-RMS");
+ names.add(s+"-RCS");
+ names.add("RCS-" + s);
+ names.add(s+"-APOGEE");
+ }
+ names.add("ISP");
+
+ manufacturers.add(new Manufacturer("AeroTech", "AeroTech",
+ names.toArray(new String[0])));
+
+ manufacturers.add(new Manufacturer("Alpha Hybrid Rocketry LLC",
+ "Alpha Hybrid Rocketry",
+ "AHR", "ALPHA", "ALPHA HYBRID", "ALPHA HYBRIDS", "ALPHA HYBRIDS ROCKETRY"));
+
+ manufacturers.add(new Manufacturer("Animal Motor Works","Animal Motor Works",
+ "AMW", "AW", "ANIMAL"));
+
+ manufacturers.add(new Manufacturer("Apogee","Apogee",
+ "AP", "APOG", "P"));
+
+ manufacturers.add(new Manufacturer("Cesaroni Technology Inc.",
+ "Cesaroni Technology",
+ "CES", "CESARONI", "CESARONI TECHNOLOGY INCORPORATED", "CTI",
+ "CS", "CSR", "PRO38"));
+
+ manufacturers.add(new Manufacturer("Contrail Rockets","Contrail Rockets",
+ "CR", "CONTR", "CONTRAIL", "CONTRAIL ROCKET"));
+
+ manufacturers.add(new Manufacturer("Estes","Estes",
+ "E", "ES"));
+
+ manufacturers.add(new Manufacturer("Ellis Mountain","Ellis Mountain",
+ "EM", "ELLIS", "ELLIS MOUNTAIN ROCKET", "ELLIS MOUNTAIN ROCKETS"));
+
+ manufacturers.add(new Manufacturer("Gorilla Rocket Motors",
+ "Gorilla Rocket Motors",
+ "GR", "GORILLA", "GORILLA ROCKET", "GORILLA ROCKETS", "GORILLA MOTOR",
+ "GORILLA MOTORS", "GORILLA ROCKET MOTOR"));
+
+ manufacturers.add(new Manufacturer("HyperTEK", "HyperTEK",
+ "H", "HT", "HYPER"));
+
+ manufacturers.add(new Manufacturer("Kosdon by AeroTech", "Kosdon by AeroTech",
+ "K", "KBA", "K-AT", "KOS", "KOSDON", "KOSDON/AT", "KOSDON/AEROTECH"));
+
+ manufacturers.add(new Manufacturer("Loki Research", "Loki Research",
+ "LOKI", "LR"));
+
+ manufacturers.add(new Manufacturer("Public Missiles, Ltd.", "Public Missiles",
+ "PM", "PML", "PUBLIC MISSILES LIMITED"));
+
+ manufacturers.add(new Manufacturer("Propulsion Polymers", "Propulsion Polymers",
+ "PP", "PROP", "PROPULSION"));
+
+ manufacturers.add(new Manufacturer("Quest", "Quest",
+ "Q", "QU"));
+
+ manufacturers.add(new Manufacturer("RATT Works", "RATT Works",
+ "RATT", "RT", "RTW"));
+
+ manufacturers.add(new Manufacturer("Roadrunner Rocketry","Roadrunner Rocketry",
+ "RR", "ROADRUNNER"));
+
+ manufacturers.add(new Manufacturer("Rocketvision", "Rocketvision",
+ "RV", "ROCKET VISION"));
+
+ manufacturers.add(new Manufacturer("Sky Ripper Systems","Sky Ripper Systems",
+ "SR", "SRS", "SKYR", "SKYRIPPER", "SKY RIPPER", "SKYRIPPER SYSTEMS"));
+
+ manufacturers.add(new Manufacturer("West Coast Hybrids", "West Coast Hybrids",
+ "WCH", "WCR", "WEST COAST", "WEST COAST HYBRID"));
+
+ // German WECO Feuerwerk, previously Sachsen Feuerwerk
+ manufacturers.add(new Manufacturer("WECO Feuerwerk", "WECO Feuerwerk",
+ "WECO", "WECO FEUERWERKS", "SF", "SACHSEN", "SACHSEN FEUERWERK",
+ "SACHSEN FEUERWERKS"));
+
+
+ // Check that no duplicates have appeared
+ for (Manufacturer m1: manufacturers) {
+ for (Manufacturer m2: manufacturers) {
+ if (m1 == m2)
+ continue;
+ for (String name: m1.getAllNames()) {
+ if (m2.matches(name)) {
+ throw new IllegalStateException("Manufacturer name clash between " +
+ "manufacturers " + m1 + " and " + m2 + " name " + name);
+ }
+ }
+ }
+ }
+ }
+
+
+
+ private final String displayName;
+ private final String simpleName;
+ private final Set<String> allNames;
+ private final Set<String> searchNames;
+
+
+ private Manufacturer(String displayName, String simpleName, String... alternateNames) {
+ this.displayName = displayName;
+ this.simpleName = simpleName;
+
+ Set<String> all = new HashSet<String>();
+ Set<String> search = new HashSet<String>();
+
+ all.add(displayName);
+ all.add(simpleName);
+ search.add(generateSearchString(displayName));
+ search.add(generateSearchString(simpleName));
+
+ for (String name: alternateNames) {
+ all.add(name);
+ search.add(generateSearchString(name));
+ }
+
+ this.allNames = Collections.unmodifiableSet(all);
+ this.searchNames = Collections.unmodifiableSet(search);
+ }
+
+
+ /**
+ * Returns the display name of the manufacturer. This is the value that
+ * should be presented in the UI to the user.
+ *
+ * @return the display name
+ */
+ public String getDisplayName() {
+ return displayName;
+ }
+
+
+ /**
+ * Returns the simple name of the manufacturer. This should be used for example
+ * when saving the manufacturer for compatibility.
+ *
+ * @return the simple name.
+ */
+ public String getSimpleName() {
+ return simpleName;
+ }
+
+
+ /**
+ * Return all names of the manufacturer. This includes all kinds of
+ * codes that correspond to the manufacturer (for example "A" for AeroTech).
+ *
+ * @return an unmodifiable set of the alternative names.
+ */
+ public Set<String> getAllNames() {
+ return allNames;
+ }
+
+
+ /**
+ * Check whether the display, simple or any of the alternative names matches the
+ * specified name. Matching is performed case insensitively and ignoring any
+ * non-letter and non-number characters.
+ *
+ * @param name the name to search for.
+ * @return whether this manufacturer matches the request.
+ */
+ public boolean matches(String name) {
+ if (name == null)
+ return false;
+ return this.searchNames.contains(generateSearchString(name));
+ }
+
+
+ /**
+ * Return the display name of the manufacturer.
+ */
+ @Override
+ public String toString() {
+ return displayName;
+ }
+
+
+ /**
+ * Returns a manufacturer for the given name. The manufacturer is searched for
+ * within the manufacturers and if a match is found the corresponding
+ * object is returned. If not, a new manufacturer is returned with display and
+ * simple name the name specified. Subsequent requests for the same (or corresponding)
+ * manufacturer name will return the same object.
+ *
+ * @param name the manufacturer name to search for.
+ * @return the Manufacturer object corresponding the name.
+ */
+ public static synchronized Manufacturer getManufacturer(String name) {
+ for (Manufacturer m: manufacturers) {
+ if (m.matches(name))
+ return m;
+ }
+
+ Manufacturer m = new Manufacturer(name.trim(), name.trim());
+ manufacturers.add(m);
+ return m;
+ }
+
+
+
+
+ private String generateSearchString(String str) {
+ return str.toLowerCase().replaceAll("[^a-zA-Z0-9]+", " ").trim();
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.motor;
+
+import java.text.Collator;
+import java.util.Comparator;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
+
+
+
+/**
+ * Abstract base class for motors. The methods that must be implemented are
+ * {@link #getTotalTime()}, {@link #getThrust(double)} and {@link #getCG(double)}.
+ * Additionally the method {@link #getMaxThrust()} may be overridden for efficiency.
+ * <p>
+ *
+ * NOTE: The current implementation of {@link #getAverageTime()} and
+ * {@link #getAverageThrust()} assume that the class is immutable!
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public abstract class Motor implements Comparable<Motor> {
+
+ /**
+ * Enum of rocket motor types.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+ public enum Type {
+ SINGLE("Single-use", "Single-use solid propellant motor"),
+ RELOAD("Reloadable", "Reloadable solid propellant motor"),
+ HYBRID("Hybrid", "Hybrid rocket motor engine"),
+ UNKNOWN("Unknown", "Unknown motor type");
+
+ private final String name;
+ private final String description;
+
+ Type(String name, String description) {
+ this.name = name;
+ this.description = description;
+ }
+
+ /**
+ * Return a short name of this motor type.
+ * @return a short name of the motor type.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Return a long description of this motor type.
+ * @return a description of the motor type.
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+ }
+
+
+ /**
+ * Ejection charge delay value signifying a "plugged" motor with no ejection charge.
+ * The value is that of <code>Double.POSITIVE_INFINITY</code>.
+ */
+ public static final double PLUGGED = Double.POSITIVE_INFINITY;
+
+
+ /**
+ * Below what portion of maximum thrust is the motor chosen to be off when
+ * calculating average thrust and burn time. NFPA 1125 defines the "official"
+ * burn time to be the time which the motor produces over 5% of its maximum thrust.
+ */
+ public static final double AVERAGE_MARGINAL = 0.05;
+
+ /* All data is cached, so divisions can be very tight. */
+ private static final int DIVISIONS = 1000;
+
+
+ // Comparators:
+ private static final Collator COLLATOR = Collator.getInstance(Locale.US);
+ static {
+ COLLATOR.setStrength(Collator.PRIMARY);
+ }
+ private static DesignationComparator DESIGNATION_COMPARATOR = new DesignationComparator();
+
+
+
+
+ private final Manufacturer manufacturer;
+ private final String designation;
+ private final String description;
+ private final Type motorType;
+
+ private final double[] delays;
+
+ private final double diameter;
+ private final double length;
+
+ /* Cached data */
+ private double maxThrust = -1;
+ private double avgTime = -1;
+ private double avgThrust = -1;
+ private double totalImpulse = -1;
+
+
+
+ /**
+ * Sole constructor. None of the parameters may be <code>null</code>.
+ *
+ * @param manufacturer the manufacturer of the motor.
+ * @param designation the motor designation.
+ * @param description further description, including any comments on the origin
+ * of the thrust curve.
+ * @param delays an array of the standard ejection charge delays. A plugged
+ * motor (no ejection charge) is specified by a delay of
+ * {@link #PLUGGED} (<code>Double.POSITIVE_INFINITY</code>).
+ * @param diameter maximum diameter of the motor
+ * @param length length of the motor
+ */
+ protected Motor(Manufacturer manufacturer, String designation, String description,
+ Type type, double[] delays, double diameter, double length) {
+
+ if (manufacturer == null || designation == null || description == null ||
+ type == null || delays == null) {
+ throw new IllegalArgumentException("Parameters cannot be null.");
+ }
+
+ this.manufacturer = manufacturer;
+ this.designation = designation;
+ this.description = description.trim();
+ this.motorType = type;
+ this.delays = delays.clone();
+ this.diameter = diameter;
+ this.length = length;
+ }
+
+
+
+ /**
+ * Return the total burn time of the motor. The method {@link #getThrust(double)}
+ * must return zero for time values greater than the return value.
+ *
+ * @return the total burn time of the motor.
+ */
+ public abstract double getTotalTime();
+
+
+ /**
+ * Return the thrust of the motor at the specified time.
+ *
+ * @param time time since the ignition of the motor.
+ * @return the thrust at the specified time.
+ */
+ public abstract double getThrust(double time);
+
+
+ /**
+ * Return the average thrust of the motor between times t1 and t2.
+ *
+ * @param t1 starting time since the ignition of the motor.
+ * @param t2 end time since the ignition of the motor.
+ * @return the average thrust during the time period.
+ */
+ /* TODO: MEDIUM: Implement better method in subclass */
+ public double getThrust(double t1, double t2) {
+ double f = 0;
+ f += getThrust(t1);
+ f += getThrust(0.8*t1 + 0.2*t2);
+ f += getThrust(0.6*t1 + 0.4*t2);
+ f += getThrust(0.4*t1 + 0.6*t2);
+ f += getThrust(0.2*t1 + 0.8*t2);
+ f += getThrust(t2);
+ return f/6;
+ }
+
+
+ /**
+ * Return the mass and CG of the motor at the specified time.
+ *
+ * @param time time since the ignition of the motor.
+ * @return the mass and CG of the motor.
+ */
+ public abstract Coordinate getCG(double time);
+
+
+
+ /**
+ * Return the mass of the motor at the specified time. The original mass
+ * of the motor can be queried by <code>getMass(0)</code> and the burnt mass
+ * by <code>getMass(Double.MAX_VALUE)</code>.
+ *
+ * @param time time since the ignition of the motor.
+ * @return the mass of the motor.
+ */
+ public double getMass(double time) {
+ return getCG(time).weight;
+ }
+
+
+ /**
+ * Return the longitudal moment of inertia of the motor at the specified time.
+ * This default method assumes that the mass of the motor is evenly distributed
+ * in a cylinder with the diameter and length of the motor.
+ *
+ * @param time time since the ignition of the motor.
+ * @return the longitudal moment of inertia of the motor.
+ */
+ public double getLongitudalInertia(double time) {
+ return getMass(time) * (3.0*MathUtil.pow2(diameter/2) + MathUtil.pow2(length))/12;
+ }
+
+
+
+ /**
+ * Return the rotational moment of inertia of the motor at the specified time.
+ * This default method assumes that the mass of the motor is evenly distributed
+ * in a cylinder with the diameter and length of the motor.
+ *
+ * @param time time since the ignition of the motor.
+ * @return the rotational moment of inertia of the motor.
+ */
+ public double getRotationalInertia(double time) {
+ return getMass(time) * MathUtil.pow2(diameter) / 8;
+ }
+
+
+
+
+ /**
+ * Return the maximum thrust. This implementation slices through the thrust curve
+ * searching for the maximum thrust. Subclasses may wish to override this with a
+ * more efficient method.
+ *
+ * @return the maximum thrust of the motor
+ */
+ public double getMaxThrust() {
+ if (maxThrust < 0) {
+ double time = getTotalTime();
+ maxThrust = 0;
+
+ for (int i=0; i < DIVISIONS; i++) {
+ double t = time * i / DIVISIONS;
+ double thrust = getThrust(t);
+
+ if (thrust > maxThrust)
+ maxThrust = thrust;
+ }
+ }
+ return maxThrust;
+ }
+
+
+ /**
+ * Return the time used in calculating the average thrust. The time is the
+ * length of time that the motor produces over 5% ({@link #AVERAGE_MARGINAL})
+ * of its maximum thrust.
+ *
+ * @return the nominal burn time.
+ */
+ public double getAverageTime() {
+ // Compute average time lazily
+ if (avgTime < 0) {
+ double max = getMaxThrust();
+ double time = getTotalTime();
+
+ avgTime = 0;
+ for (int i=0; i <= DIVISIONS; i++) {
+ double t = i*time/DIVISIONS;
+ if (getThrust(t) >= max*AVERAGE_MARGINAL)
+ avgTime++;
+ }
+ avgTime *= time/(DIVISIONS+1);
+
+ if (Double.isNaN(avgTime))
+ throw new RuntimeException("Calculated avg. time is NaN for motor "+this);
+
+ }
+ return avgTime;
+ }
+
+
+ /**
+ * Return the calculated average thrust during the time the motor produces
+ * over 5% ({@link #AVERAGE_MARGINAL}) of its thrust.
+ *
+ * @return the nominal average thrust.
+ */
+ public double getAverageThrust() {
+ // Compute average thrust lazily
+ if (avgThrust < 0) {
+ double max = getMaxThrust();
+ double time = getTotalTime();
+ int points = 0;
+
+ avgThrust = 0;
+ for (int i=0; i <= DIVISIONS; i++) {
+ double t = i*time/DIVISIONS;
+ double thrust = getThrust(t);
+ if (thrust >= max*AVERAGE_MARGINAL) {
+ avgThrust += thrust;
+ points++;
+ }
+ }
+ if (points > 0)
+ avgThrust /= points;
+
+ if (Double.isNaN(avgThrust))
+ throw new RuntimeException("Calculated average thrust is NaN for motor "+this);
+ }
+ return avgThrust;
+ }
+
+
+ /**
+ * Return the total impulse of the motor. This is calculated from the entire
+ * burn time, and therefore may differ from the value of {@link #getAverageTime()}
+ * and {@link #getAverageThrust()} multiplied together.
+ *
+ * @return the total impulse of the motor.
+ */
+ public double getTotalImpulse() {
+ // Compute total impulse lazily
+ if (totalImpulse < 0) {
+ double time = getTotalTime();
+ double f0, t0;
+
+ totalImpulse = 0;
+ t0 = 0;
+ f0 = getThrust(0);
+ for (int i=1; i < DIVISIONS; i++) {
+ double t1 = time * i / DIVISIONS;
+ double f1 = getThrust(t1);
+ totalImpulse += 0.5*(f0+f1)*(t1-t0);
+ t0 = t1;
+ f0 = f1;
+ }
+
+ if (Double.isNaN(totalImpulse))
+ throw new RuntimeException("Calculated total impulse is NaN for motor "+this);
+ }
+ return totalImpulse;
+ }
+
+
+ /**
+ * Return the manufacturer of the motor.
+ *
+ * @return the manufacturer
+ */
+ public Manufacturer getManufacturer() {
+ return manufacturer;
+ }
+
+ /**
+ * Return the designation of the motor.
+ *
+ * @return the designation
+ */
+ public String getDesignation() {
+ return designation;
+ }
+
+ /**
+ * Return the designation of the motor, including a delay.
+ *
+ * @param delay the delay of the motor.
+ * @return designation with delay.
+ */
+ public String getDesignation(double delay) {
+ return getDesignation() + "-" + getDelayString(delay);
+ }
+
+
+ /**
+ * Return extra description for the motor. This may include for example
+ * comments on the source of the thrust curve. The returned <code>String</code>
+ * may include new-lines.
+ *
+ * @return the description
+ */
+ public String getDescription() {
+ return description;
+ }
+
+
+ /**
+ * Return the motor type.
+ *
+ * @return the motorType
+ */
+ public Type getMotorType() {
+ return motorType;
+ }
+
+
+
+ /**
+ * Return the standard ejection charge delays for the motor. "Plugged" motors
+ * with no ejection charge are signified by the value {@link #PLUGGED}
+ * (<code>Double.POSITIVE_INFINITY</code>).
+ *
+ * @return the list of standard ejection charge delays, which may be empty.
+ */
+ public double[] getStandardDelays() {
+ return delays.clone();
+ }
+
+ /**
+ * Return the maximum diameter of the motor.
+ *
+ * @return the diameter
+ */
+ public double getDiameter() {
+ return diameter;
+ }
+
+ /**
+ * Return the length of the motor. This should be a "characteristic" length,
+ * and the exact definition may depend on the motor type. Typically this should
+ * be the length from the bottom of the motor to the end of the maximum diameter
+ * portion, ignoring any smaller ejection charge compartments.
+ *
+ * @return the length
+ */
+ public double getLength() {
+ return length;
+ }
+
+
+ /**
+ * Compares two <code>Motor</code> objects. The motors are considered equal
+ * if they have identical manufacturers, designations and types, near-identical
+ * dimensions, burn times and delays and near-identical thrust curves
+ * (sampled at 10 equidistant points).
+ * <p>
+ * The comment field is ignored when comparing equality.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Motor))
+ return false;
+
+ Motor other = (Motor) o;
+
+ // Tests manufacturer, designation, diameter and length
+ if (this.compareTo(other) != 0)
+ return false;
+
+ if (Math.abs(this.getTotalTime() - other.getTotalTime()) > 0.5 ||
+ this.motorType != other.motorType ||
+ this.delays.length != other.delays.length) {
+
+ return false;
+ }
+
+ for (int i=0; i < delays.length; i++) {
+ // INF - INF == NaN, which produces false when compared
+ if (Math.abs(this.delays[i] - other.delays[i]) > 0.5) {
+ return false;
+ }
+ }
+
+ double time = getTotalTime();
+ for (int i=0; i < 10; i++) {
+ double t = time * i/10;
+ if (Math.abs(this.getThrust(t) - other.getThrust(t)) > 1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * A <code>hashCode</code> method compatible with the <code>equals</code>
+ * method.
+ */
+ @Override
+ public int hashCode() {
+ return (manufacturer.hashCode() + designation.hashCode() +
+ ((int)(length*1000)) + ((int)(diameter*1000)));
+ }
+
+
+
+ @Override
+ public String toString() {
+ return manufacturer + " " + designation;
+ }
+
+
+ ////////// Static methods
+
+
+ /**
+ * Return a String representation of a delay time. If the delay is {@link #PLUGGED},
+ * returns "P".
+ *
+ * @param delay the delay time.
+ * @return the <code>String</code> representation.
+ */
+ public static String getDelayString(double delay) {
+ return getDelayString(delay,"P");
+ }
+
+ /**
+ * Return a String representation of a delay time. If the delay is {@link #PLUGGED},
+ * <code>plugged</code> is returned.
+ *
+ * @param delay the delay time.
+ * @param plugged the return value if there is no ejection charge.
+ * @return the String representation.
+ */
+ public static String getDelayString(double delay, String plugged) {
+ if (delay == PLUGGED)
+ return plugged;
+ delay = Math.rint(delay*10)/10;
+ if (MathUtil.equals(delay, Math.rint(delay)))
+ return "" + ((int)delay);
+ return "" + delay;
+ }
+
+
+
+
+ //////////// Comparation
+
+
+
+ @Override
+ public int compareTo(Motor other) {
+ int value;
+
+ // 1. Manufacturer
+ value = COLLATOR.compare(this.manufacturer.getDisplayName(),
+ other.manufacturer.getDisplayName());
+ if (value != 0)
+ return value;
+
+ // 2. Designation
+ value = DESIGNATION_COMPARATOR.compare(this.designation, other.designation);
+ if (value != 0)
+ return value;
+
+ // 3. Diameter
+ value = (int)((this.diameter - other.diameter)*1000000);
+ if (value != 0)
+ return value;
+
+ // 4. Length
+ value = (int)((this.length - other.length)*1000000);
+ if (value != 0)
+ return value;
+
+ // 5. Total impulse
+ value = (int)((this.getTotalImpulse() - other.getTotalImpulse())*1000);
+ return value;
+ }
+
+
+
+ public static Comparator<String> getDesignationComparator() {
+ return DESIGNATION_COMPARATOR;
+ }
+
+
+ /**
+ * Compares two motors by their designations. The motors are ordered first
+ * by their motor class, second by their average thrust and lastly by any
+ * extra modifiers at the end of the designation.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+ private static class DesignationComparator implements Comparator<String> {
+ private Pattern pattern =
+ Pattern.compile("^([0-9][0-9]+|1/([1-8]))?([a-zA-Z])([0-9]+)(.*?)$");
+
+ @Override
+ public int compare(String o1, String o2) {
+ int value;
+ Matcher m1, m2;
+
+ m1 = pattern.matcher(o1);
+ m2 = pattern.matcher(o2);
+
+ if (m1.find() && m2.find()) {
+
+ String o1Class = m1.group(3);
+ int o1Thrust = Integer.parseInt(m1.group(4));
+ String o1Extra = m1.group(5);
+
+ String o2Class = m2.group(3);
+ int o2Thrust = Integer.parseInt(m2.group(4));
+ String o2Extra = m2.group(5);
+
+ // 1. Motor class
+ if (o1Class.equalsIgnoreCase("A") && o2Class.equalsIgnoreCase("A")) {
+ // 1/2A and 1/4A comparison
+ String sub1 = m1.group(2);
+ String sub2 = m2.group(2);
+
+ if (sub1 != null || sub2 != null) {
+ if (sub1 == null)
+ sub1 = "1";
+ if (sub2 == null)
+ sub2 = "1";
+ value = -COLLATOR.compare(sub1,sub2);
+ if (value != 0)
+ return value;
+ }
+ }
+ value = COLLATOR.compare(o1Class,o2Class);
+ if (value != 0)
+ return value;
+
+ // 2. Average thrust
+ if (o1Thrust != o2Thrust)
+ return o1Thrust - o2Thrust;
+
+ // 3. Extra modifier
+ return COLLATOR.compare(o1Extra, o2Extra);
+
+ } else {
+
+ System.out.println("Falling back");
+ System.out.println("o1:"+o1 + " o2:"+o2);
+
+ // Not understandable designation, simply compare strings
+ return COLLATOR.compare(o1, o2);
+ }
+ }
+ }
+}
--- /dev/null
+package net.sf.openrocket.motor;
+
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
+
+/**
+ * A class of motors specified by a fixed thrust curve. This is the most
+ * accurate for solid rocket motors.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class ThrustCurveMotor extends Motor {
+
+ public static final double MAX_THRUST = 10e6;
+
+ private final double[] time;
+ private final double[] thrust;
+ private final Coordinate[] cg;
+
+ private final double totalTime;
+ private final double maxThrust;
+
+
+ /**
+ * Sole constructor. Sets all the properties of the motor.
+ *
+ * @param manufacturer the manufacturer of the motor.
+ * @param designation the designation of the motor.
+ * @param description extra description of the motor.
+ * @param diameter diameter of the motor.
+ * @param length length of the motor.
+ * @param time the time points for the thrust curve.
+ * @param thrust thrust at the time points.
+ * @param cg cg at the time points.
+ */
+ public ThrustCurveMotor(Manufacturer manufacturer, String designation, String description,
+ Motor.Type type, double[] delays, double diameter, double length,
+ double[] time, double[] thrust, Coordinate[] cg) {
+ super(manufacturer, designation, description, type, delays, diameter, length);
+
+ double max = -1;
+
+ // Check argument validity
+ if ((time.length != thrust.length) || (time.length != cg.length)) {
+ throw new IllegalArgumentException("Array lengths do not match, " +
+ "time:" + time.length + " thrust:" + thrust.length +
+ " cg:" + cg.length);
+ }
+ if (time.length < 2) {
+ throw new IllegalArgumentException("Too short thrust-curve, length=" +
+ time.length);
+ }
+ for (int i=0; i < time.length-1; i++) {
+ if (time[i+1] < time[i]) {
+ throw new IllegalArgumentException("Time goes backwards, " +
+ "time[" + i + "]=" + time[i] + " " +
+ "time[" + (i+1) + "]=" + time[i+1]);
+ }
+ }
+ if (!MathUtil.equals(time[0], 0)) {
+ throw new IllegalArgumentException("Curve starts at time " + time[0]);
+ }
+ if (!MathUtil.equals(thrust[0], 0)) {
+ throw new IllegalArgumentException("Curve starts at thrust " + thrust[0]);
+ }
+ if (!MathUtil.equals(thrust[thrust.length-1], 0)) {
+ throw new IllegalArgumentException("Curve ends at thrust " +
+ thrust[thrust.length-1]);
+ }
+ for (double t: thrust) {
+ if (t < 0) {
+ throw new IllegalArgumentException("Negative thrust.");
+ }
+ if (t > MAX_THRUST || Double.isNaN(t)) {
+ throw new IllegalArgumentException("Invalid thrust " + t);
+ }
+ if (t > max)
+ max = t;
+ }
+ for (Coordinate c: cg) {
+ if (c.isNaN()) {
+ throw new IllegalArgumentException("Invalid CG " + c);
+ }
+ if (c.x < 0 || c.x > length) {
+ throw new IllegalArgumentException("Invalid CG position " + c.x);
+ }
+ if (c.weight < 0) {
+ throw new IllegalArgumentException("Negative mass " + c.weight);
+ }
+ }
+
+ this.maxThrust = max;
+ this.time = time.clone();
+ this.thrust = thrust.clone();
+ this.cg = cg.clone();
+ this.totalTime = time[time.length-1];
+ }
+
+
+ @Override
+ public double getTotalTime() {
+ return totalTime;
+ }
+
+ @Override
+ public double getMaxThrust() {
+ return maxThrust;
+ }
+
+ @Override
+ public double getThrust(double t) {
+ if ((t < 0) || (t > totalTime))
+ return 0;
+
+ for (int i=0; i < time.length-1; i++) {
+ if ((t >= time[i]) && (t <= time[i+1])) {
+ double delta = time[i+1] - time[i];
+ if (delta < 0.0001) {
+ return thrust[i];
+ }
+ t = t - time[i];
+ return thrust[i] * (1 - t/delta) + thrust[i+1] * (t/delta);
+ }
+ }
+ assert false : "Should not be reached.";
+ return 0;
+ }
+
+
+ @Override
+ public Coordinate getCG(double t) {
+ if (t <= 0)
+ return cg[0];
+ if (t >= totalTime)
+ return cg[cg.length-1];
+
+ for (int i=0; i < time.length-1; i++) {
+ if ((t >= time[i]) && (t <= time[i+1])) {
+ double delta = time[i+1] - time[i];
+ t = t - time[i];
+ return cg[i].multiply(1 - t/delta).add(cg[i+1].multiply(t/delta));
+ }
+ }
+ assert false : "Should not be reached.";
+ return cg[cg.length-1];
+ }
+
+
+ public double[] getTimePoints() {
+ return time.clone();
+ }
+
+ public double[] getThrustPoints() {
+ return thrust.clone();
+ }
+
+ public Coordinate[] getCGPoints() {
+ return cg.clone();
+ }
+
+}
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;
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;
+++ /dev/null
-package net.sf.openrocket.rocketcomponent;
-
-import java.text.Collator;
-import java.util.Comparator;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.MathUtil;
-
-
-
-/**
- * Abstract base class for motors. The methods that must be implemented are
- * {@link #getTotalTime()}, {@link #getThrust(double)} and {@link #getCG(double)}.
- * Additionally the method {@link #getMaxThrust()} may be overridden for efficiency.
- * <p>
- *
- * NOTE: The current implementation of {@link #getAverageTime()} and
- * {@link #getAverageThrust()} assume that the class is immutable!
- *
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-public abstract class Motor implements Comparable<Motor> {
-
- /**
- * Enum of rocket motor types.
- *
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
- public enum Type {
- SINGLE("Single-use", "Single-use solid propellant motor"),
- RELOAD("Reloadable", "Reloadable solid propellant motor"),
- HYBRID("Hybrid", "Hybrid rocket motor engine"),
- UNKNOWN("Unknown", "Unknown motor type");
-
- private final String name;
- private final String description;
-
- Type(String name, String description) {
- this.name = name;
- this.description = description;
- }
-
- /**
- * Return a short name of this motor type.
- * @return a short name of the motor type.
- */
- public String getName() {
- return name;
- }
-
- /**
- * Return a long description of this motor type.
- * @return a description of the motor type.
- */
- public String getDescription() {
- return description;
- }
-
- @Override
- public String toString() {
- return name;
- }
- }
-
-
- /**
- * Ejection charge delay value signifying a "plugged" motor with no ejection charge.
- * The value is that of <code>Double.POSITIVE_INFINITY</code>.
- */
- public static final double PLUGGED = Double.POSITIVE_INFINITY;
-
-
- /**
- * Below what portion of maximum thrust is the motor chosen to be off when
- * calculating average thrust and burn time. NFPA 1125 defines the "official"
- * burn time to be the time which the motor produces over 5% of its maximum thrust.
- */
- public static final double AVERAGE_MARGINAL = 0.05;
-
- /* All data is cached, so divisions can be very tight. */
- private static final int DIVISIONS = 1000;
-
-
- // Comparators:
- private static final Collator COLLATOR = Collator.getInstance(Locale.US);
- static {
- COLLATOR.setStrength(Collator.PRIMARY);
- }
- private static DesignationComparator DESIGNATION_COMPARATOR = new DesignationComparator();
-
-
-
-
- private final String manufacturer;
- private final String designation;
- private final String description;
- private final Type motorType;
-
- private final double[] delays;
-
- private final double diameter;
- private final double length;
-
- /* Cached data */
- private double maxThrust = -1;
- private double avgTime = -1;
- private double avgThrust = -1;
- private double totalImpulse = -1;
-
-
-
- /**
- * Sole constructor. None of the parameters may be <code>null</code>.
- *
- * @param manufacturer the manufacturer of the motor.
- * @param designation the motor designation.
- * @param description further description, including any comments on the origin
- * of the thrust curve.
- * @param delays an array of the standard ejection charge delays. A plugged
- * motor (no ejection charge) is specified by a delay of
- * {@link #PLUGGED} (<code>Double.POSITIVE_INFINITY</code>).
- * @param diameter maximum diameter of the motor
- * @param length length of the motor
- */
- protected Motor(String manufacturer, String designation, String description,
- Type type, double[] delays, double diameter, double length) {
-
- if (manufacturer == null || designation == null || description == null ||
- type == null || delays == null) {
- throw new IllegalArgumentException("Parameters cannot be null.");
- }
-
- this.manufacturer = manufacturer;
- this.designation = designation;
- this.description = description.trim();
- this.motorType = type;
- this.delays = delays.clone();
- this.diameter = diameter;
- this.length = length;
- }
-
-
-
- /**
- * Return the total burn time of the motor. The method {@link #getThrust(double)}
- * must return zero for time values greater than the return value.
- *
- * @return the total burn time of the motor.
- */
- public abstract double getTotalTime();
-
-
- /**
- * Return the thrust of the motor at the specified time.
- *
- * @param time time since the ignition of the motor.
- * @return the thrust at the specified time.
- */
- public abstract double getThrust(double time);
-
-
- /**
- * Return the average thrust of the motor between times t1 and t2.
- *
- * @param t1 starting time since the ignition of the motor.
- * @param t2 end time since the ignition of the motor.
- * @return the average thrust during the time period.
- */
- /* TODO: MEDIUM: Implement better method in subclass */
- public double getThrust(double t1, double t2) {
- double f = 0;
- f += getThrust(t1);
- f += getThrust(0.8*t1 + 0.2*t2);
- f += getThrust(0.6*t1 + 0.4*t2);
- f += getThrust(0.4*t1 + 0.6*t2);
- f += getThrust(0.2*t1 + 0.8*t2);
- f += getThrust(t2);
- return f/6;
- }
-
-
- /**
- * Return the mass and CG of the motor at the specified time.
- *
- * @param time time since the ignition of the motor.
- * @return the mass and CG of the motor.
- */
- public abstract Coordinate getCG(double time);
-
-
-
- /**
- * Return the mass of the motor at the specified time. The original mass
- * of the motor can be queried by <code>getMass(0)</code> and the burnt mass
- * by <code>getMass(Double.MAX_VALUE)</code>.
- *
- * @param time time since the ignition of the motor.
- * @return the mass of the motor.
- */
- public double getMass(double time) {
- return getCG(time).weight;
- }
-
-
- /**
- * Return the longitudal moment of inertia of the motor at the specified time.
- * This default method assumes that the mass of the motor is evenly distributed
- * in a cylinder with the diameter and length of the motor.
- *
- * @param time time since the ignition of the motor.
- * @return the longitudal moment of inertia of the motor.
- */
- public double getLongitudalInertia(double time) {
- return getMass(time) * (3.0*MathUtil.pow2(diameter/2) + MathUtil.pow2(length))/12;
- }
-
-
-
- /**
- * Return the rotational moment of inertia of the motor at the specified time.
- * This default method assumes that the mass of the motor is evenly distributed
- * in a cylinder with the diameter and length of the motor.
- *
- * @param time time since the ignition of the motor.
- * @return the rotational moment of inertia of the motor.
- */
- public double getRotationalInertia(double time) {
- return getMass(time) * MathUtil.pow2(diameter) / 8;
- }
-
-
-
-
- /**
- * Return the maximum thrust. This implementation slices through the thrust curve
- * searching for the maximum thrust. Subclasses may wish to override this with a
- * more efficient method.
- *
- * @return the maximum thrust of the motor
- */
- public double getMaxThrust() {
- if (maxThrust < 0) {
- double time = getTotalTime();
- maxThrust = 0;
-
- for (int i=0; i < DIVISIONS; i++) {
- double t = time * i / DIVISIONS;
- double thrust = getThrust(t);
-
- if (thrust > maxThrust)
- maxThrust = thrust;
- }
- }
- return maxThrust;
- }
-
-
- /**
- * Return the time used in calculating the average thrust. The time is the
- * length of time that the motor produces over 5% ({@link #AVERAGE_MARGINAL})
- * of its maximum thrust.
- *
- * @return the nominal burn time.
- */
- public double getAverageTime() {
- // Compute average time lazily
- if (avgTime < 0) {
- double max = getMaxThrust();
- double time = getTotalTime();
-
- avgTime = 0;
- for (int i=0; i <= DIVISIONS; i++) {
- double t = i*time/DIVISIONS;
- if (getThrust(t) >= max*AVERAGE_MARGINAL)
- avgTime++;
- }
- avgTime *= time/(DIVISIONS+1);
-
- if (Double.isNaN(avgTime))
- throw new RuntimeException("Calculated avg. time is NaN for motor "+this);
-
- }
- return avgTime;
- }
-
-
- /**
- * Return the calculated average thrust during the time the motor produces
- * over 5% ({@link #AVERAGE_MARGINAL}) of its thrust.
- *
- * @return the nominal average thrust.
- */
- public double getAverageThrust() {
- // Compute average thrust lazily
- if (avgThrust < 0) {
- double max = getMaxThrust();
- double time = getTotalTime();
- int points = 0;
-
- avgThrust = 0;
- for (int i=0; i <= DIVISIONS; i++) {
- double t = i*time/DIVISIONS;
- double thrust = getThrust(t);
- if (thrust >= max*AVERAGE_MARGINAL) {
- avgThrust += thrust;
- points++;
- }
- }
- if (points > 0)
- avgThrust /= points;
-
- if (Double.isNaN(avgThrust))
- throw new RuntimeException("Calculated average thrust is NaN for motor "+this);
- }
- return avgThrust;
- }
-
-
- /**
- * Return the total impulse of the motor. This is calculated from the entire
- * burn time, and therefore may differ from the value of {@link #getAverageTime()}
- * and {@link #getAverageThrust()} multiplied together.
- *
- * @return the total impulse of the motor.
- */
- public double getTotalImpulse() {
- // Compute total impulse lazily
- if (totalImpulse < 0) {
- double time = getTotalTime();
- double f0, t0;
-
- totalImpulse = 0;
- t0 = 0;
- f0 = getThrust(0);
- for (int i=1; i < DIVISIONS; i++) {
- double t1 = time * i / DIVISIONS;
- double f1 = getThrust(t1);
- totalImpulse += 0.5*(f0+f1)*(t1-t0);
- t0 = t1;
- f0 = f1;
- }
-
- if (Double.isNaN(totalImpulse))
- throw new RuntimeException("Calculated total impulse is NaN for motor "+this);
- }
- return totalImpulse;
- }
-
-
- /**
- * Return the manufacturer of the motor.
- *
- * @return the manufacturer
- */
- public String getManufacturer() {
- return manufacturer;
- }
-
- /**
- * Return the designation of the motor.
- *
- * @return the designation
- */
- public String getDesignation() {
- return designation;
- }
-
- /**
- * Return the designation of the motor, including a delay.
- *
- * @param delay the delay of the motor.
- * @return designation with delay.
- */
- public String getDesignation(double delay) {
- return getDesignation() + "-" + getDelayString(delay);
- }
-
-
- /**
- * Return extra description for the motor. This may include for example
- * comments on the source of the thrust curve. The returned <code>String</code>
- * may include new-lines.
- *
- * @return the description
- */
- public String getDescription() {
- return description;
- }
-
-
- /**
- * Return the motor type.
- *
- * @return the motorType
- */
- public Type getMotorType() {
- return motorType;
- }
-
-
-
- /**
- * Return the standard ejection charge delays for the motor. "Plugged" motors
- * with no ejection charge are signified by the value {@link #PLUGGED}
- * (<code>Double.POSITIVE_INFINITY</code>).
- *
- * @return the list of standard ejection charge delays, which may be empty.
- */
- public double[] getStandardDelays() {
- return delays.clone();
- }
-
- /**
- * Return the maximum diameter of the motor.
- *
- * @return the diameter
- */
- public double getDiameter() {
- return diameter;
- }
-
- /**
- * Return the length of the motor. This should be a "characteristic" length,
- * and the exact definition may depend on the motor type. Typically this should
- * be the length from the bottom of the motor to the end of the maximum diameter
- * portion, ignoring any smaller ejection charge compartments.
- *
- * @return the length
- */
- public double getLength() {
- return length;
- }
-
-
- /**
- * Compares two <code>Motor</code> objects. The motors are considered equal
- * if they have identical manufacturers, designations and types, near-identical
- * dimensions, burn times and delays and near-identical thrust curves
- * (sampled at 10 equidistant points).
- * <p>
- * The comment field is ignored when comparing equality.
- */
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof Motor))
- return false;
-
- Motor other = (Motor) o;
-
- // Tests manufacturer, designation, diameter and length
- if (this.compareTo(other) != 0)
- return false;
-
- if (Math.abs(this.getTotalTime() - other.getTotalTime()) > 0.5 ||
- this.motorType != other.motorType ||
- this.delays.length != other.delays.length) {
-
- return false;
- }
-
- for (int i=0; i < delays.length; i++) {
- // INF - INF == NaN, which produces false when compared
- if (Math.abs(this.delays[i] - other.delays[i]) > 0.5) {
- return false;
- }
- }
-
- double time = getTotalTime();
- for (int i=0; i < 10; i++) {
- double t = time * i/10;
- if (Math.abs(this.getThrust(t) - other.getThrust(t)) > 1) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * A <code>hashCode</code> method compatible with the <code>equals</code>
- * method.
- */
- @Override
- public int hashCode() {
- return (manufacturer.hashCode() + designation.hashCode() +
- ((int)(length*1000)) + ((int)(diameter*1000)));
- }
-
-
-
- @Override
- public String toString() {
- return manufacturer + " " + designation;
- }
-
-
- ////////// Static methods
-
-
- /**
- * Return a String representation of a delay time. If the delay is {@link #PLUGGED},
- * returns "P".
- *
- * @param delay the delay time.
- * @return the <code>String</code> representation.
- */
- public static String getDelayString(double delay) {
- return getDelayString(delay,"P");
- }
-
- /**
- * Return a String representation of a delay time. If the delay is {@link #PLUGGED},
- * <code>plugged</code> is returned.
- *
- * @param delay the delay time.
- * @param plugged the return value if there is no ejection charge.
- * @return the String representation.
- */
- public static String getDelayString(double delay, String plugged) {
- if (delay == PLUGGED)
- return plugged;
- delay = Math.rint(delay*10)/10;
- if (MathUtil.equals(delay, Math.rint(delay)))
- return "" + ((int)delay);
- return "" + delay;
- }
-
-
-
-
- //////////// Comparation
-
-
-
- @Override
- public int compareTo(Motor other) {
- int value;
-
- // 1. Manufacturer
- value = COLLATOR.compare(this.manufacturer, other.manufacturer);
- if (value != 0)
- return value;
-
- // 2. Designation
- value = DESIGNATION_COMPARATOR.compare(this.designation, other.designation);
- if (value != 0)
- return value;
-
- // 3. Diameter
- value = (int)((this.diameter - other.diameter)*1000000);
- if (value != 0)
- return value;
-
- // 4. Length
- value = (int)((this.length - other.length)*1000000);
- if (value != 0)
- return value;
-
- // 5. Total impulse
- value = (int)((this.getTotalImpulse() - other.getTotalImpulse())*1000);
- return value;
- }
-
-
-
- public static Comparator<String> getDesignationComparator() {
- return DESIGNATION_COMPARATOR;
- }
-
-
- /**
- * Compares two motors by their designations. The motors are ordered first
- * by their motor class, second by their average thrust and lastly by any
- * extra modifiers at the end of the designation.
- *
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
- private static class DesignationComparator implements Comparator<String> {
- private Pattern pattern =
- Pattern.compile("^([0-9][0-9]+|1/([1-8]))?([a-zA-Z])([0-9]+)(.*?)$");
-
- @Override
- public int compare(String o1, String o2) {
- int value;
- Matcher m1, m2;
-
- m1 = pattern.matcher(o1);
- m2 = pattern.matcher(o2);
-
- if (m1.find() && m2.find()) {
-
- String o1Class = m1.group(3);
- int o1Thrust = Integer.parseInt(m1.group(4));
- String o1Extra = m1.group(5);
-
- String o2Class = m2.group(3);
- int o2Thrust = Integer.parseInt(m2.group(4));
- String o2Extra = m2.group(5);
-
- // 1. Motor class
- if (o1Class.equalsIgnoreCase("A") && o2Class.equalsIgnoreCase("A")) {
- // 1/2A and 1/4A comparison
- String sub1 = m1.group(2);
- String sub2 = m2.group(2);
-
- if (sub1 != null || sub2 != null) {
- if (sub1 == null)
- sub1 = "1";
- if (sub2 == null)
- sub2 = "1";
- value = -COLLATOR.compare(sub1,sub2);
- if (value != 0)
- return value;
- }
- }
- value = COLLATOR.compare(o1Class,o2Class);
- if (value != 0)
- return value;
-
- // 2. Average thrust
- if (o1Thrust != o2Thrust)
- return o1Thrust - o2Thrust;
-
- // 3. Extra modifier
- return COLLATOR.compare(o1Extra, o2Extra);
-
- } else {
-
- System.out.println("Falling back");
- System.out.println("o1:"+o1 + " o2:"+o2);
-
- // Not understandable designation, simply compare strings
- return COLLATOR.compare(o1, o2);
- }
- }
- }
-}
package net.sf.openrocket.rocketcomponent;
+import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.util.ChangeSource;
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;
+++ /dev/null
-package net.sf.openrocket.rocketcomponent;
-
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.MathUtil;
-
-/**
- * A class of motors specified by a fixed thrust curve. This is the most
- * accurate for solid rocket motors.
- *
- * @author Sampo Niskanen <sampo.niskanen@iki.fi>
- */
-public class ThrustCurveMotor extends Motor {
-
- public static final double MAX_THRUST = 10e6;
-
- private final double[] time;
- private final double[] thrust;
- private final Coordinate[] cg;
-
- private final double totalTime;
- private final double maxThrust;
-
-
- /**
- * Sole constructor. Sets all the properties of the motor.
- *
- * @param manufacturer the manufacturer of the motor.
- * @param designation the designation of the motor.
- * @param description extra description of the motor.
- * @param diameter diameter of the motor.
- * @param length length of the motor.
- * @param time the time points for the thrust curve.
- * @param thrust thrust at the time points.
- * @param cg cg at the time points.
- */
- public ThrustCurveMotor(String manufacturer, String designation, String description,
- Motor.Type type, double[] delays, double diameter, double length,
- double[] time, double[] thrust, Coordinate[] cg) {
- super(manufacturer, designation, description, type, delays, diameter, length);
-
- double max = -1;
-
- // Check argument validity
- if ((time.length != thrust.length) || (time.length != cg.length)) {
- throw new IllegalArgumentException("Array lengths do not match, " +
- "time:" + time.length + " thrust:" + thrust.length +
- " cg:" + cg.length);
- }
- if (time.length < 2) {
- throw new IllegalArgumentException("Too short thrust-curve, length=" +
- time.length);
- }
- for (int i=0; i < time.length-1; i++) {
- if (time[i+1] < time[i]) {
- throw new IllegalArgumentException("Time goes backwards, " +
- "time[" + i + "]=" + time[i] + " " +
- "time[" + (i+1) + "]=" + time[i+1]);
- }
- }
- if (!MathUtil.equals(time[0], 0)) {
- throw new IllegalArgumentException("Curve starts at time " + time[0]);
- }
- if (!MathUtil.equals(thrust[0], 0)) {
- throw new IllegalArgumentException("Curve starts at thrust " + thrust[0]);
- }
- if (!MathUtil.equals(thrust[thrust.length-1], 0)) {
- throw new IllegalArgumentException("Curve ends at thrust " +
- thrust[thrust.length-1]);
- }
- for (double t: thrust) {
- if (t < 0) {
- throw new IllegalArgumentException("Negative thrust.");
- }
- if (t > MAX_THRUST || Double.isNaN(t)) {
- throw new IllegalArgumentException("Invalid thrust " + t);
- }
- if (t > max)
- max = t;
- }
- for (Coordinate c: cg) {
- if (c.isNaN()) {
- throw new IllegalArgumentException("Invalid CG " + c);
- }
- if (c.x < 0 || c.x > length) {
- throw new IllegalArgumentException("Invalid CG position " + c.x);
- }
- if (c.weight < 0) {
- throw new IllegalArgumentException("Negative mass " + c.weight);
- }
- }
-
- this.maxThrust = max;
- this.time = time.clone();
- this.thrust = thrust.clone();
- this.cg = cg.clone();
- this.totalTime = time[time.length-1];
- }
-
-
- @Override
- public double getTotalTime() {
- return totalTime;
- }
-
- @Override
- public double getMaxThrust() {
- return maxThrust;
- }
-
- @Override
- public double getThrust(double t) {
- if ((t < 0) || (t > totalTime))
- return 0;
-
- for (int i=0; i < time.length-1; i++) {
- if ((t >= time[i]) && (t <= time[i+1])) {
- double delta = time[i+1] - time[i];
- if (delta < 0.0001) {
- return thrust[i];
- }
- t = t - time[i];
- return thrust[i] * (1 - t/delta) + thrust[i+1] * (t/delta);
- }
- }
- assert false : "Should not be reached.";
- return 0;
- }
-
-
- @Override
- public Coordinate getCG(double t) {
- if (t <= 0)
- return cg[0];
- if (t >= totalTime)
- return cg[cg.length-1];
-
- for (int i=0; i < time.length-1; i++) {
- if ((t >= time[i]) && (t <= time[i+1])) {
- double delta = time[i+1] - time[i];
- t = t - time[i];
- return cg[i].multiply(1 - t/delta).add(cg[i+1].multiply(t/delta));
- }
- }
- assert false : "Should not be reached.";
- return cg[cg.length-1];
- }
-
-
- public double[] getTimePoints() {
- return time.clone();
- }
-
- public double[] getThrustPoints() {
- return thrust.clone();
- }
-
- public Coordinate[] getCGPoints() {
- return cg.clone();
- }
-
-}
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;
+++ /dev/null
-package net.sf.openrocket.util;
-
-import static net.sf.openrocket.aerodynamics.AtmosphericConditions.GAMMA;
-import static net.sf.openrocket.aerodynamics.AtmosphericConditions.R;
-
-import java.io.File;
-import java.io.PrintStream;
-import java.util.Arrays;
-
-import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
-import net.sf.openrocket.aerodynamics.AerodynamicForces;
-import net.sf.openrocket.aerodynamics.AtmosphericConditions;
-import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
-import net.sf.openrocket.aerodynamics.ExactAtmosphericConditions;
-import net.sf.openrocket.aerodynamics.FlightConditions;
-import net.sf.openrocket.document.OpenRocketDocument;
-import net.sf.openrocket.file.GeneralRocketLoader;
-import net.sf.openrocket.file.RocketLoadException;
-import net.sf.openrocket.file.RocketLoader;
-import net.sf.openrocket.rocketcomponent.Configuration;
-
-public class Analysis {
-
- private static final double MACH_MIN = 0.01;
- private static final double MACH_MAX = 5.00001;
- private static final double MACH_STEP = 0.02;
-
- private static final double AOA_MACH = 0.6;
- private static final double AOA_MIN = 0;
- private static final double AOA_MAX = 15.00001*Math.PI/180;
- private static final double AOA_STEP = 0.5*Math.PI/180;
-
- private static final double REYNOLDS = 9.8e6;
- private static final double STAG_TEMP = 330;
-
-
- private final RocketLoader loader = new GeneralRocketLoader();
- private final AerodynamicCalculator calculator = new BarrowmanCalculator();
-
- private final FlightConditions conditions;
- private final double length;
-
- private final Configuration config;
-
- private final AtmosphericConditions atmosphere;
-
-
-
- private Analysis(String filename) throws RocketLoadException {
-
- OpenRocketDocument doc = loader.load(new File(filename));
- config = doc.getRocket().getDefaultConfiguration();
-
- calculator.setConfiguration(config);
-
- conditions = new FlightConditions(config);
- System.out.println("Children: " + Arrays.toString(config.getRocket().getChildren()));
- System.out.println("Children: " + Arrays.toString(config.getRocket().getChild(0).getChildren()));
- length = config.getLength();
- System.out.println("Rocket length: " + (length*1000)+"mm");
-
- atmosphere = new ExactAtmosphericConditions();
-
- }
-
-
- private double computeVelocityAndAtmosphere(double mach, double reynolds, double stagTemp) {
- final double temperature;
- final double pressure;
-
-
- temperature = stagTemp / (1 + (GAMMA-1)/2 * MathUtil.pow2(mach));
-
- // Speed of sound
- double c = 331.3 * Math.sqrt(1 + (temperature - 273.15)/273.15);
-
- // Free-stream velocity
- double v0 = c * mach;
-
-// kin.visc. = (3.7291e-06 + 4.9944e-08 * temperature) / density
- pressure = reynolds * (3.7291e-06 + 4.9944e-08 * temperature) * R * temperature /
- (v0 * length);
-
- atmosphere.pressure = pressure;
- atmosphere.temperature = temperature;
- conditions.setAtmosphericConditions(atmosphere);
- conditions.setVelocity(v0);
-
- if (Math.abs(conditions.getMach() - mach) > 0.001) {
- System.err.println("Computed mach: "+conditions.getMach() + " requested "+mach);
-// System.exit(1);
- }
-
- return v0;
- }
-
-
-
- private void computeVsMach(PrintStream stream) {
-
- conditions.setAOA(0);
- conditions.setTheta(45*Math.PI/180);
- stream.println("% Mach, Caxial, CP, , CNa, Croll");
-
- for (double mach = MACH_MIN; mach <= MACH_MAX; mach += MACH_STEP) {
-
- computeVelocityAndAtmosphere(mach, REYNOLDS, STAG_TEMP);
-// conditions.setMach(mach);
-
-
- AerodynamicForces forces = calculator.getAerodynamicForces(0, conditions, null);
-
-
- double Re = conditions.getVelocity() *
- calculator.getConfiguration().getLength() /
- conditions.getAtmosphericConditions().getKinematicViscosity();
- if (Math.abs(Re - REYNOLDS) > 1) {
- throw new RuntimeException("Re="+Re);
- }
- stream.printf("%f, %f, %f, %f, %f\n", mach, forces.Caxial, forces.cp.x, forces.CNa,
- forces.Croll);
- }
-
- }
-
-
-
- private void computeVsAOA(PrintStream stream, double thetaDeg) {
-
- computeVelocityAndAtmosphere(AOA_MACH, REYNOLDS, STAG_TEMP);
- conditions.setTheta(thetaDeg * Math.PI/180);
- stream.println("% AOA, CP, CN, Cm at theta = "+thetaDeg);
-
- for (double aoa = AOA_MIN; aoa <= AOA_MAX; aoa += AOA_STEP) {
-
- conditions.setAOA(aoa);
- AerodynamicForces forces = calculator.getAerodynamicForces(0, conditions, null);
-
-
- double Re = conditions.getVelocity() *
- calculator.getConfiguration().getLength() /
- conditions.getAtmosphericConditions().getKinematicViscosity();
- if (Math.abs(Re - REYNOLDS) > 1) {
- throw new RuntimeException("Re="+Re);
- }
- stream.printf("%f, %f, %f, %f\n", aoa*180/Math.PI, forces.cp.x, forces.CN, forces.Cm);
- }
-
- }
-
-
-
-
- public static void main(String arg[]) throws Exception {
-
- if (arg.length != 2) {
- System.err.println("Arguments: <rocket file> <output prefix>");
- System.exit(1);
- }
-
- Analysis a = new Analysis(arg[0]);
- final String prefix = arg[1];
-
-
- String name;
- double v0 = a.computeVelocityAndAtmosphere(0.6, 9.8e6, 322);
- System.out.printf("Sanity test: mach = %.1f v=%.1f temp=%.1f pres=%.0f c=%.1f " +
- "ref.length=%.1fmm\n",
- a.conditions.getMach(), v0, a.atmosphere.temperature, a.atmosphere.pressure,
- a.atmosphere.getMachSpeed(), a.conditions.getRefLength()*1000);
- System.out.println();
-
-
- // CA, CP, Croll vs. Mach at AOA=0
- name = prefix + "-CA-CP-CNa-Croll-vs-Mach.csv";
- System.out.println("Computing CA, CP, CNa, Croll vs. Mach to file "+name);
- a.computeVsMach(new PrintStream(name));
-
-
- // CN & Cm vs. AOA at M=0.6
- name = prefix + "-CP-CN-Cm-vs-AOA-0.csv";
- System.out.println("Computing CP, CN, Cm vs. AOA at theta=0 to file "+name);
- a.computeVsAOA(new PrintStream(name), 0);
-
- // CN & Cm vs. AOA at M=0.6
- name = prefix + "-CP-CN-Cm-vs-AOA-22.5.csv";
- System.out.println("Computing CP, CN, Cm vs. AOA at theta=22.5 to file "+name);
- a.computeVsAOA(new PrintStream(name), 0);
-
- // CN & Cm vs. AOA at M=0.6
- name = prefix + "-CP-CN-Cm-vs-AOA-45.csv";
- System.out.println("Computing CP, CN, Cm vs. AOA at theta=45 to file "+name);
- a.computeVsAOA(new PrintStream(name), 0);
-
-
- System.out.println("Done.");
- }
-
-
-
-
-
-}
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public final class Coordinate implements Serializable {
+
+ //////// Debug section
+ /*
+ * Debugging info. If openrocket.debug.coordinatecount is defined, a line is
+ * printed every 1000000 instantiations (or as many as defined).
+ */
+ private static final boolean COUNT_DEBUG;
+ private static final int COUNT_DIFF;
+ static {
+ String str = System.getProperty("openrocket.debug.coordinatecount", null);
+ int diff = 0;
+ if (str == null) {
+ COUNT_DEBUG = false;
+ COUNT_DIFF = 0;
+ } else {
+ COUNT_DEBUG = true;
+ try {
+ diff = Integer.parseInt(str);
+ } catch (NumberFormatException ignore) { }
+ if (diff < 1000)
+ diff = 1000000;
+ COUNT_DIFF = diff;
+ }
+ }
+
+ private static int count = 0;
+ {
+ // Debug count
+ if (COUNT_DEBUG) {
+ count++;
+ if ((count % COUNT_DIFF) == 0) {
+ System.out.println("Coordinate instantiated " + count + " times.");
+ }
+ }
+ }
+
+ //////// End debug section
+
+
+
+
public static final Coordinate NUL = new Coordinate(0,0,0,0);
public static final Coordinate NaN = new Coordinate(Double.NaN,Double.NaN,
Double.NaN,Double.NaN);
- public static final double COMPARISON_DELTA = 0.000001;
+
public final double x,y,z;
public final double weight;
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() {
this.y = y;
this.z = z;
this.weight=w;
+
}
}
-
- 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");
-
- }
-
}
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...");
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;
}
return Math.abs(a-b) < EPSILON*absb;
}
+
+ /**
+ * Return the sign of the number. This corresponds to Math.signum, but ignores
+ * the special cases of zero and NaN. The value returned for those is arbitrary.
+ * <p>
+ * This method is about 4 times faster than Math.signum().
+ *
+ * @param x the checked value.
+ * @return -1.0 if x<0; 1.0 if x>0; otherwise either -1.0 or 1.0.
+ */
public static double sign(double x) {
return (x<0) ? -1.0 : 1.0;
}
*/
- 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();
-
-
- }
-
}
private static final String BUILD_VERSION;
private static final String BUILD_SOURCE;
+ public static final String DEFAULT_BUILD_SOURCE = "default";
static {
try {
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));
- }
-
}
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;
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;
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.
*/
}
+ final String sign = (d < 0) ? "-" : "";
double abs = Math.abs(d);
- if (abs < 0.001) {
- // Compact exponential notation
- int exp = 0;
-
- while (abs < 1.0) {
- abs *= 10;
- exp++;
- }
-
- String sign = (d < 0) ? "-" : "";
- return sign + String.format((Locale)null, "%.4fe-%d", abs, exp);
+ // Small and large values always in exponential notation
+ if (abs < 0.001 || abs >= 100000000) {
+ return sign + exponentialFormat(abs);
}
- if (abs < 0.01)
- return String.format((Locale)null, "%.7f", d);
- if (abs < 0.1)
- return String.format((Locale)null, "%.6f", d);
- if (abs < 1)
- return String.format((Locale)null, "%.5f", d);
- if (abs < 10)
- return String.format((Locale)null, "%.4f", d);
- if (abs < 100)
- return String.format((Locale)null, "%.3f", d);
- if (abs < 1000)
- return String.format((Locale)null, "%.2f", d);
- if (abs < 10000)
- return String.format((Locale)null, "%.1f", d);
- if (abs < 100000000.0)
- return String.format((Locale)null, "%.0f", d);
-
- // Compact exponential notation
- int exp = 0;
- while (abs >= 10.0) {
- abs /= 10;
+
+ // Check whether decimal or exponential notation is shorter
+
+ String exp = exponentialFormat(abs);
+ String dec = decimalFormat(abs);
+
+ if (dec.length() <= exp.length())
+ return sign + dec;
+ else
+ return sign + exp;
+ }
+
+
+ /*
+ * value must be positive and not zero!
+ */
+ private static String exponentialFormat(double value) {
+ int exp;
+
+ exp = 0;
+ while (value < 1.0) {
+ value *= 10;
+ exp--;
+ }
+ while (value >= 10.0) {
+ value /= 10;
exp++;
}
- String sign = (d < 0) ? "-" : "";
- return sign + String.format((Locale)null, "%.4fe%d", abs, exp);
+ return shortDecimal(value, 4) + "e" + exp;
+ }
+
+
+ /*
+ * value must be positive and not zero!
+ */
+ private static String decimalFormat(double value) {
+ if (value >= 10000)
+ return "" + (int)(value + 0.5);
+
+ int decimals = 1;
+ double v = value;
+ while (v < 1000) {
+ v *= 10;
+ decimals++;
+ }
+
+ return shortDecimal(value, decimals);
+ }
+
+
+
+
+ /*
+ * value must be positive!
+ */
+ private static String shortDecimal(double value, int decimals) {
+
+ int whole = (int)value;
+ value -= whole;
+
+ // Calculate limit, return when remaining value less than this
+ double limit;
+ limit = 0.5;
+ for (int i=0; i<decimals; i++)
+ limit /= 10;
+
+
+ if (value < limit)
+ return "" + whole;
+ limit *= 10;
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("" + whole);
+ sb.append('.');
+
+
+ for (int i = 0; i<decimals; i++) {
+
+ value *= 10;
+ if (i == decimals-1)
+ value += 0.5;
+ whole = (int)value;
+ value -= whole;
+ sb.append((char)('0' + whole));
+
+ if (value < limit)
+ return sb.toString();
+ limit *= 10;
+
+ }
+
+ return sb.toString();
}
}
--- /dev/null
+package net.sf.openrocket.utils;
+
+import static net.sf.openrocket.aerodynamics.AtmosphericConditions.GAMMA;
+import static net.sf.openrocket.aerodynamics.AtmosphericConditions.R;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.util.Arrays;
+
+import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
+import net.sf.openrocket.aerodynamics.AerodynamicForces;
+import net.sf.openrocket.aerodynamics.AtmosphericConditions;
+import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
+import net.sf.openrocket.aerodynamics.ExactAtmosphericConditions;
+import net.sf.openrocket.aerodynamics.FlightConditions;
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.file.GeneralRocketLoader;
+import net.sf.openrocket.file.RocketLoadException;
+import net.sf.openrocket.file.RocketLoader;
+import net.sf.openrocket.rocketcomponent.Configuration;
+import net.sf.openrocket.util.MathUtil;
+
+public class Analysis {
+
+ private static final double MACH_MIN = 0.01;
+ private static final double MACH_MAX = 5.00001;
+ private static final double MACH_STEP = 0.02;
+
+ private static final double AOA_MACH = 0.6;
+ private static final double AOA_MIN = 0;
+ private static final double AOA_MAX = 15.00001*Math.PI/180;
+ private static final double AOA_STEP = 0.5*Math.PI/180;
+
+ private static final double REYNOLDS = 9.8e6;
+ private static final double STAG_TEMP = 330;
+
+
+ private final RocketLoader loader = new GeneralRocketLoader();
+ private final AerodynamicCalculator calculator = new BarrowmanCalculator();
+
+ private final FlightConditions conditions;
+ private final double length;
+
+ private final Configuration config;
+
+ private final AtmosphericConditions atmosphere;
+
+
+
+ private Analysis(String filename) throws RocketLoadException {
+
+ OpenRocketDocument doc = loader.load(new File(filename));
+ config = doc.getRocket().getDefaultConfiguration();
+
+ calculator.setConfiguration(config);
+
+ conditions = new FlightConditions(config);
+ System.out.println("Children: " + Arrays.toString(config.getRocket().getChildren()));
+ System.out.println("Children: " + Arrays.toString(config.getRocket().getChild(0).getChildren()));
+ length = config.getLength();
+ System.out.println("Rocket length: " + (length*1000)+"mm");
+
+ atmosphere = new ExactAtmosphericConditions();
+
+ }
+
+
+ private double computeVelocityAndAtmosphere(double mach, double reynolds, double stagTemp) {
+ final double temperature;
+ final double pressure;
+
+
+ temperature = stagTemp / (1 + (GAMMA-1)/2 * MathUtil.pow2(mach));
+
+ // Speed of sound
+ double c = 331.3 * Math.sqrt(1 + (temperature - 273.15)/273.15);
+
+ // Free-stream velocity
+ double v0 = c * mach;
+
+// kin.visc. = (3.7291e-06 + 4.9944e-08 * temperature) / density
+ pressure = reynolds * (3.7291e-06 + 4.9944e-08 * temperature) * R * temperature /
+ (v0 * length);
+
+ atmosphere.pressure = pressure;
+ atmosphere.temperature = temperature;
+ conditions.setAtmosphericConditions(atmosphere);
+ conditions.setVelocity(v0);
+
+ if (Math.abs(conditions.getMach() - mach) > 0.001) {
+ System.err.println("Computed mach: "+conditions.getMach() + " requested "+mach);
+// System.exit(1);
+ }
+
+ return v0;
+ }
+
+
+
+ private void computeVsMach(PrintStream stream) {
+
+ conditions.setAOA(0);
+ conditions.setTheta(45*Math.PI/180);
+ stream.println("% Mach, Caxial, CP, , CNa, Croll");
+
+ for (double mach = MACH_MIN; mach <= MACH_MAX; mach += MACH_STEP) {
+
+ computeVelocityAndAtmosphere(mach, REYNOLDS, STAG_TEMP);
+// conditions.setMach(mach);
+
+
+ AerodynamicForces forces = calculator.getAerodynamicForces(0, conditions, null);
+
+
+ double Re = conditions.getVelocity() *
+ calculator.getConfiguration().getLength() /
+ conditions.getAtmosphericConditions().getKinematicViscosity();
+ if (Math.abs(Re - REYNOLDS) > 1) {
+ throw new RuntimeException("Re="+Re);
+ }
+ stream.printf("%f, %f, %f, %f, %f\n", mach, forces.Caxial, forces.cp.x, forces.CNa,
+ forces.Croll);
+ }
+
+ }
+
+
+
+ private void computeVsAOA(PrintStream stream, double thetaDeg) {
+
+ computeVelocityAndAtmosphere(AOA_MACH, REYNOLDS, STAG_TEMP);
+ conditions.setTheta(thetaDeg * Math.PI/180);
+ stream.println("% AOA, CP, CN, Cm at theta = "+thetaDeg);
+
+ for (double aoa = AOA_MIN; aoa <= AOA_MAX; aoa += AOA_STEP) {
+
+ conditions.setAOA(aoa);
+ AerodynamicForces forces = calculator.getAerodynamicForces(0, conditions, null);
+
+
+ double Re = conditions.getVelocity() *
+ calculator.getConfiguration().getLength() /
+ conditions.getAtmosphericConditions().getKinematicViscosity();
+ if (Math.abs(Re - REYNOLDS) > 1) {
+ throw new RuntimeException("Re="+Re);
+ }
+ stream.printf("%f, %f, %f, %f\n", aoa*180/Math.PI, forces.cp.x, forces.CN, forces.Cm);
+ }
+
+ }
+
+
+
+
+ public static void main(String arg[]) throws Exception {
+
+ if (arg.length != 2) {
+ System.err.println("Arguments: <rocket file> <output prefix>");
+ System.exit(1);
+ }
+
+ Analysis a = new Analysis(arg[0]);
+ final String prefix = arg[1];
+
+
+ String name;
+ double v0 = a.computeVelocityAndAtmosphere(0.6, 9.8e6, 322);
+ System.out.printf("Sanity test: mach = %.1f v=%.1f temp=%.1f pres=%.0f c=%.1f " +
+ "ref.length=%.1fmm\n",
+ a.conditions.getMach(), v0, a.atmosphere.temperature, a.atmosphere.pressure,
+ a.atmosphere.getMachSpeed(), a.conditions.getRefLength()*1000);
+ System.out.println();
+
+
+ // CA, CP, Croll vs. Mach at AOA=0
+ name = prefix + "-CA-CP-CNa-Croll-vs-Mach.csv";
+ System.out.println("Computing CA, CP, CNa, Croll vs. Mach to file "+name);
+ a.computeVsMach(new PrintStream(name));
+
+
+ // CN & Cm vs. AOA at M=0.6
+ name = prefix + "-CP-CN-Cm-vs-AOA-0.csv";
+ System.out.println("Computing CP, CN, Cm vs. AOA at theta=0 to file "+name);
+ a.computeVsAOA(new PrintStream(name), 0);
+
+ // CN & Cm vs. AOA at M=0.6
+ name = prefix + "-CP-CN-Cm-vs-AOA-22.5.csv";
+ System.out.println("Computing CP, CN, Cm vs. AOA at theta=22.5 to file "+name);
+ a.computeVsAOA(new PrintStream(name), 0);
+
+ // CN & Cm vs. AOA at M=0.6
+ name = prefix + "-CP-CN-Cm-vs-AOA-45.csv";
+ System.out.println("Computing CP, CN, Cm vs. AOA at theta=45 to file "+name);
+ a.computeVsAOA(new PrintStream(name), 0);
+
+
+ System.out.println("Done.");
+ }
+
+
+
+
+
+}
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 {
}
String base = file.split("_")[0];
- String mfg = MotorLoader.convertManufacturer(base);
+ Manufacturer mfg = Manufacturer.getManufacturer(base);
if (motors != null) {
if (motors.size() == 0) {
ok = false;
}
- if (!m.getManufacturer().equals(mfg)) {
+ if (m.getManufacturer() != mfg) {
System.out.println("ERROR: Inconsistent manufacturer " +
m.getManufacturer() + " (file name indicates " + mfg
+ ")");
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 {
// 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;
}
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 {
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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"));
+ }
+
+}
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+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));
+ }
+
+}
--- /dev/null
+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));
+
+ }
+
+}
--- /dev/null
+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));
+
+ }
+
+}