diff --git a/core/fileformat.txt b/core/fileformat.txt index 9e04dcfff..17247f26d 100644 --- a/core/fileformat.txt +++ b/core/fileformat.txt @@ -39,3 +39,5 @@ The following file format versions exist: 1.3: Introduced with OpenRocket 1.1.9. Adds the and parameters to the simulation conditions element. +1.4: Introduced with OpenRocket 1.1.10. Adds the launchrodvelocity and + deploymentvelocity attributes to element. \ No newline at end of file diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 3a51de447..e385eb94e 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -378,6 +378,8 @@ simpanel.dlg.lbl.DeleteSim2 = This operation cannot be undone. simpanel.dlg.lbl.DeleteSim3 = Delete simulations simpanel.col.Name = Name simpanel.col.Motors = Motors +simpanel.col.Velocityoffrod = Velocity off rod +simpanel.col.Velocityatdeploy = Velocity at deployment simpanel.col.Apogee = Apogee simpanel.col.Maxvelocity = Max. velocity simpanel.col.Maxacceleration = Max. acceleration diff --git a/core/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java b/core/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java index 518f1e44c..475245ddb 100644 --- a/core/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java +++ b/core/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java @@ -1517,6 +1517,8 @@ class FlightDataHandler extends ElementHandler { double timeToApogee = Double.NaN; double flightTime = Double.NaN; double groundHitVelocity = Double.NaN; + double launchRodVelocity = Double.NaN; + double deploymentVelocity = Double.NaN; try { maxAltitude = DocumentConfig.stringToDouble(attributes.get("maxaltitude")); @@ -1547,10 +1549,18 @@ class FlightDataHandler extends ElementHandler { DocumentConfig.stringToDouble(attributes.get("groundhitvelocity")); } catch (NumberFormatException ignore) { } + try { + launchRodVelocity = DocumentConfig.stringToDouble(attributes.get("launchrodvelocity")); + } catch (NumberFormatException ignore) { + } + try { + deploymentVelocity = DocumentConfig.stringToDouble(attributes.get("deploymentvelocity")); + } catch (NumberFormatException ignore) { + } // TODO: HIGH: Store and load launchRodVelocity data = new FlightData(maxAltitude, maxVelocity, maxAcceleration, maxMach, - timeToApogee, flightTime, groundHitVelocity, Double.NaN); + timeToApogee, flightTime, groundHitVelocity, launchRodVelocity, deploymentVelocity); } data.getWarningSet().addAll(warningSet); diff --git a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java index 7dbb2fce5..c04cce9b2 100644 --- a/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java +++ b/core/src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java @@ -360,6 +360,10 @@ public class OpenRocketSaver extends RocketSaver { str += " flighttime=\"" + TextUtil.doubleToString(data.getFlightTime()) + "\""; if (!Double.isNaN(data.getGroundHitVelocity())) str += " groundhitvelocity=\"" + TextUtil.doubleToString(data.getGroundHitVelocity()) + "\""; + if (!Double.isNaN(data.getLaunchRodVelocity())) + str += " launchrodvelocity=\"" + TextUtil.doubleToString(data.getLaunchRodVelocity()) + "\""; + if (!Double.isNaN(data.getDeploymentVelocity())) + str += " deploymentvelocity=\"" + TextUtil.doubleToString(data.getDeploymentVelocity()) + "\""; str += ">"; writeln(str); indent++; diff --git a/core/src/net/sf/openrocket/gui/main/SimulationPanel.java b/core/src/net/sf/openrocket/gui/main/SimulationPanel.java index 11b707eac..719e1cf6a 100644 --- a/core/src/net/sf/openrocket/gui/main/SimulationPanel.java +++ b/core/src/net/sf/openrocket/gui/main/SimulationPanel.java @@ -311,6 +311,22 @@ public class SimulationPanel extends JPanel { } }, + //// Launch rod velocity + new Column(trans.get("simpanel.col.Velocityoffrod")) { + @Override + public Object getValueAt(int row) { + if (row < 0 || row >= document.getSimulationCount()) + return null; + + FlightData data = document.getSimulation(row).getSimulatedData(); + if (data == null) + return null; + + return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit( + data.getLaunchRodVelocity()); + } + }, + //// Apogee new Column(trans.get("simpanel.col.Apogee")) { @Override @@ -327,6 +343,22 @@ public class SimulationPanel extends JPanel { } }, + //// Velocity at deployment + new Column(trans.get("simpanel.col.Velocityatdeploy")) { + @Override + public Object getValueAt(int row) { + if (row < 0 || row >= document.getSimulationCount()) + return null; + + FlightData data = document.getSimulation(row).getSimulatedData(); + if (data == null) + return null; + + return UnitGroup.UNITS_VELOCITY.getDefaultUnit().toStringUnit( + data.getDeploymentVelocity()); + } + }, + //// Maximum velocity new Column(trans.get("simpanel.col.Maxvelocity")) { @Override diff --git a/core/src/net/sf/openrocket/gui/print/DesignReport.java b/core/src/net/sf/openrocket/gui/print/DesignReport.java index 886e225ad..e6290c3e9 100644 --- a/core/src/net/sf/openrocket/gui/print/DesignReport.java +++ b/core/src/net/sf/openrocket/gui/print/DesignReport.java @@ -117,6 +117,7 @@ public class DesignReport { private static final String TIME_TO_APOGEE = "Time to Apogee"; private static final String VELOCITY_OFF_PAD = "Velocity off Pad"; private static final String MAX_VELOCITY = "Max Velocity"; + private static final String DEPLOYMENT_VELOCITY = "Velocity at Deployment"; private static final String LANDING_VELOCITY = "Landing Velocity"; private static final String ROCKET_DESIGN = "Rocket Design"; private static final double GRAVITY_CONSTANT = 9.80665d; @@ -469,6 +470,9 @@ public class DesignReport { labelTable.addCell(ITextHelper.createCell(MAX_VELOCITY, 2, 2)); labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getMaxVelocity()), 2, 2)); + labelTable.addCell(ITextHelper.createCell(DEPLOYMENT_VELOCITY, 2,2)); + labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getDeploymentVelocity()),2,2)); + labelTable.addCell(ITextHelper.createCell(LANDING_VELOCITY, 2, 2)); labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getGroundHitVelocity()), 2, 2)); diff --git a/core/src/net/sf/openrocket/simulation/FlightData.java b/core/src/net/sf/openrocket/simulation/FlightData.java index 839c65846..c0fb90e80 100644 --- a/core/src/net/sf/openrocket/simulation/FlightData.java +++ b/core/src/net/sf/openrocket/simulation/FlightData.java @@ -49,6 +49,7 @@ public class FlightData { private double flightTime = Double.NaN; private double groundHitVelocity = Double.NaN; private double launchRodVelocity = Double.NaN; + private double deploymentVelocity = Double.NaN; /** @@ -70,11 +71,12 @@ public class FlightData { * @param timeToApogee time to apogee. * @param flightTime total flight time. * @param groundHitVelocity ground hit velocity. - * @param launchRodVelocity TODO + * @param launchRodVelocity velocity at launch rod clearance + * @param deploymentVelocity velocity at deployment */ public FlightData(double maxAltitude, double maxVelocity, double maxAcceleration, double maxMachNumber, double timeToApogee, double flightTime, - double groundHitVelocity, double launchRodVelocity) { + double groundHitVelocity, double launchRodVelocity, double deploymentVelocity) { this.maxAltitude = maxAltitude; this.maxVelocity = maxVelocity; this.maxAcceleration = maxAcceleration; @@ -83,6 +85,7 @@ public class FlightData { this.flightTime = flightTime; this.groundHitVelocity = groundHitVelocity; this.launchRodVelocity = launchRodVelocity; + this.deploymentVelocity = deploymentVelocity; } @@ -171,7 +174,11 @@ public class FlightData { return launchRodVelocity; } - + + public double getDeploymentVelocity() { + return deploymentVelocity; + } + /** * Calculate the max. altitude/velocity/acceleration, time to apogee, flight time @@ -223,15 +230,11 @@ public class FlightData { if (event.getType() == FlightEvent.Type.LAUNCHROD) { double t = event.getTime(); List velocity = branch.get(FlightDataType.TYPE_VELOCITY_TOTAL); - if (velocity == null) { - break; - } - for (int i = 0; i < velocity.size(); i++) { - if (time.get(i) >= t) { - launchRodVelocity = velocity.get(i); - break eventloop; - } - } + launchRodVelocity = MathUtil.interpolate( time, velocity, t); + } else if ( event.getType() == FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT) { + double t = event.getTime(); + List velocity = branch.get(FlightDataType.TYPE_VELOCITY_TOTAL); + deploymentVelocity = MathUtil.interpolate( time, velocity, t); } } diff --git a/core/src/net/sf/openrocket/util/MathUtil.java b/core/src/net/sf/openrocket/util/MathUtil.java index dc2e105d6..c88e85853 100644 --- a/core/src/net/sf/openrocket/util/MathUtil.java +++ b/core/src/net/sf/openrocket/util/MathUtil.java @@ -11,9 +11,9 @@ import net.sf.openrocket.startup.Application; public class MathUtil { private static final LogHelper log = Application.getLogger(); - + public static final double EPSILON = 0.00000001; // 10mm^3 in m^3 - + /** * The square of x (x^2). On Sun's JRE using this method is as fast as typing x*x. * @param x x @@ -22,7 +22,7 @@ public class MathUtil { public static double pow2(double x) { return x * x; } - + /** * The cube of x (x^3). * @param x x @@ -31,11 +31,11 @@ public class MathUtil { public static double pow3(double x) { return x * x * x; } - + public static double pow4(double x) { return (x * x) * (x * x); } - + /** * Clamps the value x to the range min - max. * @param x Original value. @@ -50,7 +50,7 @@ public class MathUtil { return max; return x; } - + public static float clamp(float x, float min, float max) { if (x < min) return min; @@ -58,7 +58,7 @@ public class MathUtil { return max; return x; } - + public static int clamp(int x, int min, int max) { if (x < min) return min; @@ -66,8 +66,8 @@ public class MathUtil { return max; return x; } - - + + /** * Maps a value from one value range to another. * @@ -90,8 +90,8 @@ public class MathUtil { } return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin; } - - + + /** * Maps a coordinate from one value range to another. * @@ -115,8 +115,8 @@ public class MathUtil { double a = (value - fromMin) / (fromMax - fromMin); return toMax.multiply(a).add(toMin.multiply(1 - a)); } - - + + /** * Compute the minimum of two values. This is performed by direct comparison. * However, if one of the values is NaN and the other is not, the non-NaN value is @@ -127,7 +127,7 @@ public class MathUtil { return x; return (x < y) ? x : y; } - + /** * Compute the maximum of two values. This is performed by direct comparison. * However, if one of the values is NaN and the other is not, the non-NaN value is @@ -138,7 +138,7 @@ public class MathUtil { return y; return (x < y) ? y : x; } - + /** * Compute the minimum of three values. This is performed by direct comparison. * However, if one of the values is NaN and the other is not, the non-NaN value is @@ -151,8 +151,8 @@ public class MathUtil { return min(y, z); } } - - + + /** * Compute the minimum of three values. This is performed by direct comparison. @@ -162,8 +162,8 @@ public class MathUtil { public static double min(double w, double x, double y, double z) { return min(min(w, x), min(y, z)); } - - + + /** * Compute the maximum of three values. This is performed by direct comparison. * However, if one of the values is NaN and the other is not, the non-NaN value is @@ -176,7 +176,7 @@ public class MathUtil { return max(y, z); } } - + /** * Calculates the hypotenuse sqrt(x^2+y^2). This method is SIGNIFICANTLY * faster than Math.hypot(x,y). @@ -184,7 +184,7 @@ public class MathUtil { public static double hypot(double x, double y) { return Math.sqrt(x * x + y * y); } - + /** * Reduce the angle x to the range 0 - 2*PI. * @param x Original angle. @@ -194,7 +194,7 @@ public class MathUtil { double d = Math.floor(x / (2 * Math.PI)); return x - d * 2 * Math.PI; } - + /** * Reduce the angle x to the range -PI - PI. * @@ -207,8 +207,8 @@ public class MathUtil { double d = Math.rint(x / (2 * Math.PI)); return x - d * 2 * Math.PI; } - - + + /** * Return the square root of a value. If the value is negative, zero is returned. * This is safer in cases where rounding errors might make a value slightly negative. @@ -225,20 +225,20 @@ public class MathUtil { } return Math.sqrt(d); } - - + + public static boolean equals(double a, double b) { double absb = Math.abs(b); - + if (absb < EPSILON / 2) { // Near zero return Math.abs(a) < EPSILON / 2; } 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. @@ -251,20 +251,20 @@ public class MathUtil { public static double sign(double x) { return (x < 0) ? -1.0 : 1.0; } - + /* Math.abs() is about 3x as fast as this: - + public static double abs(double x) { return (x<0) ? -x : x; } - */ + */ public static double average(Collection values) { if (values.isEmpty()) { return Double.NaN; } - + double avg = 0.0; int count = 0; for (Number n : values) { @@ -273,12 +273,12 @@ public class MathUtil { } return avg / count; } - + public static double stddev(Collection values) { if (values.size() < 2) { return Double.NaN; } - + double avg = average(values); double stddev = 0.0; int count = 0; @@ -289,12 +289,12 @@ public class MathUtil { stddev = Math.sqrt(stddev / (count - 1)); return stddev; } - + public static double median(Collection values) { if (values.isEmpty()) { return Double.NaN; } - + List sorted = new ArrayList(values); Collections.sort(sorted, new Comparator() { @Override @@ -302,7 +302,7 @@ public class MathUtil { return Double.compare(o1.doubleValue(), o2.doubleValue()); } }); - + int n = sorted.size(); if (n % 2 == 0) { return (sorted.get(n / 2).doubleValue() + sorted.get(n / 2 - 1).doubleValue()) / 2; @@ -310,5 +310,58 @@ public class MathUtil { return sorted.get(n / 2).doubleValue(); } } - + + /** + * Use interpolation to determine the value of the function at point t. + * Current implementation uses simple linear interpolation. The domain + * and range lists must include the same number of values, t must be within + * the domain, and the domain list must be sorted. + * + * @param domain list containing domain samples + * @param range list of corresponding range samples + * @param t domain value at which to interpolate + * @return returns Double.NaN if either list is null or empty or different size, or if t is outsize the domain. + */ + public static double interpolate( List domain, List range, double t ) { + + if ( domain == null || range == null || domain.size() != range.size() ) { + return Double.NaN; + } + + int length = domain.size(); + if ( length <= 1 || t < domain.get(0) || t > domain.get( length-1 ) ) { + return Double.NaN; + } + + // Look for the index of the right end point. + int right = 1; + while( t > domain.get(right) ) { + right ++; + } + int left = right -1; + + // Points are: + + double deltax = domain.get(right) - domain.get(left); + double deltay = range.get(right) - range.get(left); + + // For numerical stability, if deltax is small, + if ( Math.abs(deltax) < EPSILON ) { + if ( deltay < -1.0 * EPSILON ) { + // return neg infinity if deltay is negative + return Double.NEGATIVE_INFINITY; + } + else if ( deltay > EPSILON ) { + // return infinity if deltay is large + return Double.POSITIVE_INFINITY; + } else { + // otherwise return 0 + return 0.0d; + } + } + + return range.get(left) + ( t - domain.get(left) ) * deltay / deltax; + + } + } diff --git a/core/test/net/sf/openrocket/util/MathUtilTest.java b/core/test/net/sf/openrocket/util/MathUtilTest.java index 8b79aeca0..f11ba89c3 100644 --- a/core/test/net/sf/openrocket/util/MathUtilTest.java +++ b/core/test/net/sf/openrocket/util/MathUtilTest.java @@ -216,4 +216,64 @@ public class MathUtilTest { assertEquals(5.43, MathUtil.median(doubles), EPS); } + @Test + public void testInterpolate() { + double v; + List x; + List y; + + x = new ArrayList(); + y = new ArrayList(); + y.add(1.0); + + v= MathUtil.interpolate(null, y, 0.0); + assertEquals("Failed to test for domain null", Double.NaN, v, EPS); + + v = MathUtil.interpolate(x, y, 0.0); + assertEquals("Failed to test for empty domain", Double.NaN, v, EPS); + + x = new ArrayList(); + x.add(1.0); + y = new ArrayList(); + + v = MathUtil.interpolate(x, null, 0.0); + assertEquals("Failed to test for range null", Double.NaN, v, EPS); + + v = MathUtil.interpolate(x, y, 0.0); + assertEquals("Failed to test for empty range", Double.NaN, v, EPS); + + x = new ArrayList(); + x.add(1.0); + x.add(2.0); + y = new ArrayList(); + y.add(15.0); + y.add(17.0); + + v = MathUtil.interpolate(x,y,0.0); + assertEquals("Failed to test t out of domain", Double.NaN, v, EPS); + + v = MathUtil.interpolate(x,y,5.0); + assertEquals("Failed to test t out of domain", Double.NaN, v, EPS); + + v = MathUtil.interpolate(x,y,1.0); + assertEquals("Failed to calculate left endpoint", 15.0, v, EPS); + v = MathUtil.interpolate(x,y,2.0); + assertEquals("Failed to calculate right endpoint", 17.0, v, EPS); + v = MathUtil.interpolate(x,y,1.5); + assertEquals("Failed to calculate center", 16.0, v, EPS); + + x = new ArrayList(); + x.add(0.25); + x.add(0.5); + x.add(1.0); + x.add(2.0); + y = new ArrayList(); + y.add(0.0); + y.add(0.0); + y.add(15.0); + y.add(17.0); + v = MathUtil.interpolate(x,y,1.5); + assertEquals("Failed to calculate center with longer list", 16.0, v, EPS); + + } }