1.3: Introduced with OpenRocket 1.1.9. Adds the <launchlongitude> and
<geodeticmethod> parameters to the simulation conditions element.
+1.4: Introduced with OpenRocket 1.1.10. Adds the launchrodvelocity and
+ deploymentvelocity attributes to <flightdata> element.
\ No newline at end of file
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
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"));
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);
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++;
}
},
+ //// 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
}
},
+ //// 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
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;
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));
private double flightTime = Double.NaN;
private double groundHitVelocity = Double.NaN;
private double launchRodVelocity = Double.NaN;
+ private double deploymentVelocity = Double.NaN;
/**
* @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;
this.flightTime = flightTime;
this.groundHitVelocity = groundHitVelocity;
this.launchRodVelocity = launchRodVelocity;
+ this.deploymentVelocity = deploymentVelocity;
}
return launchRodVelocity;
}
-
+
+ public double getDeploymentVelocity() {
+ return deploymentVelocity;
+ }
+
/**
* Calculate the max. altitude/velocity/acceleration, time to apogee, flight time
if (event.getType() == FlightEvent.Type.LAUNCHROD) {
double t = event.getTime();
List<Double> 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<Double> velocity = branch.get(FlightDataType.TYPE_VELOCITY_TOTAL);
+ deploymentVelocity = MathUtil.interpolate( time, velocity, t);
}
}
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
public static double pow2(double x) {
return x * x;
}
-
+
/**
* The cube of x (x^3).
* @param x x
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.
return max;
return x;
}
-
+
public static float clamp(float x, float min, float max) {
if (x < min)
return min;
return max;
return x;
}
-
+
public static int clamp(int x, int min, int max) {
if (x < min)
return min;
return max;
return x;
}
-
-
+
+
/**
* Maps a value from one value range to another.
*
}
return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin;
}
-
-
+
+
/**
* Maps a coordinate from one value range to another.
*
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
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
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
return min(y, z);
}
}
-
-
+
+
/**
* Compute the minimum of three values. This is performed by direct comparison.
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
return max(y, z);
}
}
-
+
/**
* Calculates the hypotenuse <code>sqrt(x^2+y^2)</code>. This method is SIGNIFICANTLY
* faster than <code>Math.hypot(x,y)</code>.
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.
double d = Math.floor(x / (2 * Math.PI));
return x - d * 2 * Math.PI;
}
-
+
/**
* Reduce the angle x to the range -PI - PI.
*
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.
}
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.
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<? extends Number> values) {
if (values.isEmpty()) {
return Double.NaN;
}
-
+
double avg = 0.0;
int count = 0;
for (Number n : values) {
}
return avg / count;
}
-
+
public static double stddev(Collection<? extends Number> values) {
if (values.size() < 2) {
return Double.NaN;
}
-
+
double avg = average(values);
double stddev = 0.0;
int count = 0;
stddev = Math.sqrt(stddev / (count - 1));
return stddev;
}
-
+
public static double median(Collection<? extends Number> values) {
if (values.isEmpty()) {
return Double.NaN;
}
-
+
List<Number> sorted = new ArrayList<Number>(values);
Collections.sort(sorted, new Comparator<Number>() {
@Override
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;
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<Double> domain, List<Double> 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;
+
+ }
+
}
assertEquals(5.43, MathUtil.median(doubles), EPS);
}
+ @Test
+ public void testInterpolate() {
+ double v;
+ List<Double> x;
+ List<Double> y;
+
+ x = new ArrayList<Double>();
+ y = new ArrayList<Double>();
+ 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<Double>();
+ x.add(1.0);
+ y = new ArrayList<Double>();
+
+ 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<Double>();
+ x.add(1.0);
+ x.add(2.0);
+ y = new ArrayList<Double>();
+ 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<Double>();
+ x.add(0.25);
+ x.add(0.5);
+ x.add(1.0);
+ x.add(2.0);
+ y = new ArrayList<Double>();
+ 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);
+
+ }
}