From 4c7c3be9f78a8a7765d4d16bb6eb4f274668995c Mon Sep 17 00:00:00 2001 From: Daniel_M_Williams Date: Sat, 14 Nov 2015 11:19:08 -0500 Subject: [PATCH] [Bugfix] Verify / Fix Mass Calculation Code - MassCalculator class: Debug toggle will print debug-level output to stderr fixed / reimplemented: getCG(...) -> getCM( FlightConfiguration, MassCalcType); -> getRotationalInertia( FlightConfiguration, MassCalcType); -> getLongitudinalInertia( FlightConfiguration, MassCalcType); - MassData class: can now add MassData classes Instanced componets w/children: take into account component mass... propellantMass field is vague: no indication whether it's include in the inertia or not. longitudinalInertia => rollMOI (?) rotationalInertia => yaw / pitch MOI (?) assorted other fixes - added unit test classes: ... .masscalc.MassCalculatorTest; ... .masscalc.MassDataTest; --- .../openrocket/masscalc/MassCalculator.java | 447 +++++++------- .../net/sf/openrocket/masscalc/MassData.java | 294 +++++++++ .../sf/openrocket/motor/MotorInstance.java | 4 + .../sf/openrocket/motor/MotorInstanceId.java | 4 + .../motor/ThrustCurveMotorInstance.java | 19 + .../openrocket/rocketcomponent/BodyTube.java | 1 + .../rocketcomponent/BoosterSet.java | 9 +- .../rocketcomponent/CenteringRing.java | 13 + .../rocketcomponent/FlightConfiguration.java | 56 +- .../openrocket/rocketcomponent/InnerTube.java | 47 +- .../rocketcomponent/Instanceable.java | 13 +- .../rocketcomponent/LaunchButton.java | 14 +- .../openrocket/rocketcomponent/LaunchLug.java | 12 + .../sf/openrocket/rocketcomponent/PodSet.java | 5 + .../rocketcomponent/RocketComponent.java | 8 +- .../rocketcomponent/RocketUtils.java | 1 + .../simulation/AbstractSimulationStepper.java | 11 +- .../simulation/BasicLandingStepper.java | 1 + .../simulation/BasicTumbleStepper.java | 1 + .../sf/openrocket/simulation/MassData.java | 74 --- .../simulation/RK4SimulationStepper.java | 1 + .../impl/ScriptingSimulationListener.java | 2 +- .../listeners/AbstractSimulationListener.java | 2 +- .../SimulationComputationListener.java | 2 +- .../listeners/SimulationListenerHelper.java | 2 +- .../net/sf/openrocket/util/TestRockets.java | 216 +++++++ .../masscalc/MassCalculatorTest.java | 559 +++++++++++++----- .../sf/openrocket/masscalc/MassDataTest.java | 190 ++++++ 28 files changed, 1544 insertions(+), 464 deletions(-) create mode 100644 core/src/net/sf/openrocket/masscalc/MassData.java delete mode 100644 core/src/net/sf/openrocket/simulation/MassData.java create mode 100644 core/test/net/sf/openrocket/masscalc/MassDataTest.java diff --git a/core/src/net/sf/openrocket/masscalc/MassCalculator.java b/core/src/net/sf/openrocket/masscalc/MassCalculator.java index 21c48bc7b..bdf60cd67 100644 --- a/core/src/net/sf/openrocket/masscalc/MassCalculator.java +++ b/core/src/net/sf/openrocket/masscalc/MassCalculator.java @@ -1,22 +1,24 @@ package net.sf.openrocket.masscalc; -import static net.sf.openrocket.util.MathUtil.pow2; - -import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.MotorInstance; import net.sf.openrocket.motor.MotorInstanceConfiguration; import net.sf.openrocket.motor.MotorInstanceId; +import net.sf.openrocket.motor.ThrustCurveMotor; import net.sf.openrocket.rocketcomponent.AxialStage; +import net.sf.openrocket.rocketcomponent.BoosterSet; +import net.sf.openrocket.rocketcomponent.ComponentAssembly; import net.sf.openrocket.rocketcomponent.FlightConfiguration; +import net.sf.openrocket.rocketcomponent.Instanceable; import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.simulation.MassData; +import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.Monitorable; @@ -46,25 +48,32 @@ public class MassCalculator implements Monitorable { public abstract Coordinate getCG(Motor motor); } + private static final Logger log = LoggerFactory.getLogger(MassCalculator.class); private static final double MIN_MASS = 0.001 * MathUtil.EPSILON; - private static final Logger log = LoggerFactory.getLogger(MassCalculator.class); private int rocketMassModID = -1; private int rocketTreeModID = -1; + /* * Cached data. All CG data is in absolute coordinates. All moments of inertia * are relative to their respective CG. */ - private Coordinate[] cgCache = null; - private double longitudinalInertiaCache[] = null; - private double rotationalInertiaCache[] = null; + private HashMap< Integer, MassData> cache = new HashMap(); +// private MassData dryData = null; +// private MassData launchData = null; +// private Vector< MassData> motorData = new Vector(); + // unless in active development, this should be set to false. + public boolean debug = false; + ////////////////// Constructors /////////////////// + public MassCalculator() { + } ////////////////// Mass property calculations /////////////////// - + /** * Return the CG of the rocket with the specified motor status (no motors, @@ -75,25 +84,43 @@ public class MassCalculator implements Monitorable { * @return the CG of the configuration */ public Coordinate getCG(FlightConfiguration configuration, MassCalcType type) { - checkCache(configuration); - calculateStageCache(configuration); - - Coordinate dryCG = null; + return getCM( configuration, type); + } + + public Coordinate getCM(FlightConfiguration config, MassCalcType type) { + checkCache(config); + calculateStageCache(config); // Stage contribution - for (AxialStage stage : configuration.getActiveStages()) { - int stageNumber = stage.getStageNumber(); - dryCG = cgCache[stageNumber].average(dryCG); + Coordinate dryCM = Coordinate.ZERO; + for (AxialStage stage : config.getActiveStages()) { + Integer stageNumber = stage.getStageNumber(); + MassData stageData = cache.get( stageNumber); + if( null == stageData ){ + throw new BugException("method: calculateStageCache(...) is faulty-- returned null data for an active stage: "+stage.getName()+"("+stage.getStageNumber()+")"); + } + dryCM = stageData.cm.average(dryCM); + if( debug){ + System.err.println(" stageData <<@"+stageNumber+"mass: "+dryCM.weight+" @"+dryCM.toString()); + } } - if (dryCG == null) - dryCG = Coordinate.NUL; + Coordinate totalCM=null; + if( MassCalcType.NO_MOTORS == type ){ + totalCM = dryCM; + }else{ + MassData motorData = getMotorMassData(config, type); + Coordinate motorCM = motorData.getCM(); + totalCM = dryCM.average(motorCM); + } - MotorInstanceConfiguration motorConfig = new MotorInstanceConfiguration(configuration); - Coordinate motorCG = getMotorCG(configuration, motorConfig, MassCalcType.LAUNCH_MASS); - - Coordinate totalCG = dryCG.average(motorCG); - return totalCG; + if(debug){ + Coordinate cm = totalCM; + System.err.println(String.format("==>> Combined Mass: %5.3gg @( %g, %g, %g)", + cm.weight, cm.x, cm.y, cm.z )); + } + + return totalCM; } /** @@ -103,128 +130,119 @@ public class MassCalculator implements Monitorable { * @param motors the motor configuration * @return the CG of the configuration */ - public Coordinate getCG(FlightConfiguration configuration, MotorInstanceConfiguration motors) { - checkCache(configuration); - calculateStageCache(configuration); - - Coordinate dryCG = getCG(configuration, MassCalcType.NO_MOTORS); - Coordinate motorCG = getMotorCG(configuration, motors, MassCalcType.LAUNCH_MASS); - - Coordinate totalCG = dryCG.average(motorCG); - return totalCG; - } - - public Coordinate getMotorCG(FlightConfiguration config, MotorInstanceConfiguration motors, MassCalcType type) { - Coordinate motorCG = Coordinate.ZERO; + private MassData getMotorMassData(FlightConfiguration config, MassCalcType type) { + if( MassCalcType.NO_MOTORS == type ){ + return MassData.ZERO_DATA; + } // Add motor CGs - if (motors != null) { - for (MotorInstance inst : config.getActiveMotors()) { - // DEVEL - if(MotorInstanceId.EMPTY_ID == inst.getMotorID()){ - throw new IllegalArgumentException(" detected empty motor from .getActiveMotors()"); - } - if( null == inst.getMount()){ - throw new NullPointerException(" detected null mount"); - } - if( null == inst.getMotor()){ - throw new NullPointerException(" detected null motor"); - } - // END DEVEL - - Coordinate position = inst.getPosition(); - Coordinate curCG = type.getCG(inst.getMotor()).add(position); - motorCG = motorCG.average(curCG); - - } + + MassData motorData = MassData.ZERO_DATA; + + // vvvv DEVEL vvvv + if( debug){ + System.err.println("====== ====== getMotorCM: (type: "+type.name()+") ====== ====== ====== ====== ====== ======"); + System.err.println(" [Number] [Name] [mass]"); } - return motorCG; + // ^^^^ DEVEL ^^^^ + + int motorCount = 0; + for (MotorInstance inst : config.getActiveMotors() ) { + //ThrustCurveMotor motor = (ThrustCurveMotor) inst.getMotor(); + + Coordinate position = inst.getPosition(); + Coordinate curMotorCM = type.getCG(inst.getMotor()).add(position); + double Ir = inst.getRotationalInertia(); + double It = inst.getLongitudinalInertia(); + + MassData instData = new MassData( curMotorCM, Ir, It); + motorData = motorData.add( instData ); + + // BEGIN DEVEL + if( debug){ + System.err.println(String.format(" motor %2d: %s %s", //%5.3gg @( %g, %g, %g)", + motorCount, inst.getMotor().getDesignation(), instData.toDebug())); + System.err.println(String.format(" >> %s", + motorData.toDebug())); + } + motorCount++; + // END DEVEL + } + + return motorData; } /** * Return the longitudinal inertia of the rocket with the specified motor instance * configuration. * - * @param configuration the current motor instance configuration - * @param motors the motor configuration + * @param config the current motor instance configuration + * @param type the type of analysis to pull * @return the longitudinal inertia of the rocket */ - public double getLongitudinalInertia(FlightConfiguration configuration, MotorInstanceConfiguration motors) { - checkCache(configuration); - calculateStageCache(configuration); + public double getLongitudinalInertia(FlightConfiguration config, MassCalcType type) { + checkCache(config); + calculateStageCache(config); - final Coordinate totalCG = getCG(configuration, motors); - double totalInertia = 0; + MassData structureData = MassData.ZERO_DATA; // Stages - for (AxialStage stage : configuration.getActiveStages()) { + for (AxialStage stage : config.getActiveStages()) { int stageNumber = stage.getStageNumber(); - Coordinate stageCG = cgCache[stageNumber]; - totalInertia += (longitudinalInertiaCache[stageNumber] + - stageCG.weight * MathUtil.pow2(stageCG.x - totalCG.x)); + MassData stageData = cache.get(stageNumber); + structureData = structureData.add(stageData); } - - // Motors - if (motors != null) { - for (MotorInstance motor : configuration.getActiveMotors()) { - int stage = ((RocketComponent) motor.getMount()).getStageNumber(); - if (configuration.isStageActive(stage)) { - Coordinate position = motor.getPosition(); - Coordinate cg = motor.getCG().add(position); - - double inertia = motor.getLongitudinalInertia(); - totalInertia += inertia + cg.weight * MathUtil.pow2(cg.x - totalCG.x); - } - } + MassData motorData = MassData.ZERO_DATA; + if( MassCalcType.NO_MOTORS != type ){ + motorData = getMotorMassData(config, type); } - return totalInertia; + + MassData totalData = structureData.add( motorData); + if(debug){ + System.err.println(String.format("==>> Combined MassData: %s", totalData.toDebug())); + + } + + return totalData.getLongitudinalInertia(); } /** * Compute the rotational inertia of the provided configuration with specified motors. * - * @param configuration the current motor instance configuration - * @param motors the motor configuration + * @param config the current motor instance configuration + * @param type the type of analysis to get * @return the rotational inertia of the configuration */ - public double getRotationalInertia(FlightConfiguration configuration, MotorInstanceConfiguration motors) { - checkCache(configuration); - calculateStageCache(configuration); + public double getRotationalInertia(FlightConfiguration config, MassCalcType type) { + checkCache(config); + calculateStageCache(config); - final Coordinate totalCG = getCG(configuration, motors); - double totalInertia = 0; + MassData structureData = MassData.ZERO_DATA; // Stages - for (AxialStage stage : configuration.getActiveStages()) { + for (AxialStage stage : config.getActiveStages()) { int stageNumber = stage.getStageNumber(); - Coordinate stageCG = cgCache[stageNumber]; - totalInertia += (rotationalInertiaCache[stageNumber] + - stageCG.weight * (MathUtil.pow2(stageCG.y - totalCG.y) + - MathUtil.pow2(stageCG.z - totalCG.z))); + MassData stageData = cache.get(stageNumber); + structureData = structureData.add(stageData); } - - // Motors - if (motors != null) { - for (MotorInstance motor : configuration.getActiveMotors()) { - int stage = ((RocketComponent) motor.getMount()).getStageNumber(); - if (configuration.isStageActive(stage)) { - Coordinate position = motor.getPosition(); - Coordinate cg = motor.getCG().add(position); - - double inertia = motor.getRotationalInertia(); - totalInertia += inertia + cg.weight * (MathUtil.pow2(cg.y - totalCG.y) + - MathUtil.pow2(cg.z - totalCG.z)); - } - } + MassData motorData = MassData.ZERO_DATA; + if( MassCalcType.NO_MOTORS != type ){ + motorData = getMotorMassData(config, type); } - return totalInertia; + MassData totalData = structureData.add( motorData); + if(debug){ + System.err.println(String.format("==>> Combined MassData: %s", totalData.toDebug())); + + } + + return totalData.getRotationalInertia(); } @@ -235,13 +253,14 @@ public class MassCalculator implements Monitorable { * @param configuration the current motor instance configuration * @return the total mass of all motors */ - public double getPropellantMass(FlightConfiguration configuration, MotorInstanceConfiguration motors) { + public double getPropellantMass(FlightConfiguration configuration, MassCalcType calcType ){ double mass = 0; + //throw new BugException("getPropellantMass is not yet implemented.... "); // add up the masses of all motors in the rocket - if (motors != null) { - for (MotorInstance motor : configuration.getActiveMotors()) { - mass = mass + motor.getCG().weight - motor.getMotor().getEmptyCG().weight; + if ( MassCalcType.NO_MOTORS != calcType ){ + for (MotorInstance curInstance : configuration.getActiveMotors()) { + mass = mass + curInstance.getCG().weight - curInstance.getMotor().getEmptyCG().weight; } } return mass; @@ -281,24 +300,22 @@ public class MassCalculator implements Monitorable { //////// Cache computations //////// private void calculateStageCache(FlightConfiguration config) { - if (cgCache == null) { - ArrayList stageList = new ArrayList(); - stageList.addAll(config.getRocket().getStageList()); - int stageCount = stageList.size(); - - cgCache = new Coordinate[stageCount]; - longitudinalInertiaCache = new double[stageCount]; - rotationalInertiaCache = new double[stageCount]; - - for (int i = 0; i < stageCount; i++) { - RocketComponent stage = stageList.get(i); - MassData data = calculateAssemblyMassData(stage); - cgCache[i] = stage.toAbsolute(data.getCG())[0]; - longitudinalInertiaCache[i] = data.getLongitudinalInertia(); - rotationalInertiaCache[i] = data.getRotationalInertia(); - } - + int stageCount = config.getActiveStageCount(); + if(debug){ + System.err.println(">> Calculating CG cache for config: "+config.toShort()+" with "+stageCount+" stages"); } + if( 0 < stageCount ){ + for( AxialStage curStage : config.getActiveStages()){ + int index = curStage.getStageNumber(); + MassData stageData = calculateAssemblyMassData( curStage); + if( curStage instanceof BoosterSet ){ + // hacky correction for the fact Booster Stages aren't direct subchildren to the rocket + stageData = stageData.move( curStage.getParent().getOffset() ); + } + cache.put(index, stageData); + } + } + } @@ -307,86 +324,114 @@ public class MassCalculator implements Monitorable { * The inertia is returned relative to the CG, and the CG is in the coordinates * of the specified component, not global coordinates. */ - private MassData calculateAssemblyMassData(RocketComponent parent) { - Coordinate parentCG = Coordinate.ZERO; - double longitudinalInertia = 0.0; - double rotationalInertia = 0.0; + private MassData calculateAssemblyMassData(RocketComponent component) { + return calculateAssemblyMassData(component, "...."); + } + + private MassData calculateAssemblyMassData(RocketComponent component, String indent) { - // Calculate data for this component - parentCG = parent.getComponentCG(); - if (parentCG.weight < MIN_MASS) - parentCG = parentCG.setWeight(MIN_MASS); + Coordinate parentCM = component.getComponentCG(); + double parentIx = component.getRotationalUnitInertia() * parentCM.weight; + double parentIt = component.getLongitudinalUnitInertia() * parentCM.weight; + MassData parentData = new MassData( parentCM, parentIx, parentIt); - - // Override only this component's data - if (!parent.getOverrideSubcomponents()) { - if (parent.isMassOverridden()) - parentCG = parentCG.setWeight(MathUtil.max(parent.getOverrideMass(), MIN_MASS)); - if (parent.isCGOverridden()) - parentCG = parentCG.setXYZ(parent.getOverrideCG()); + if(( debug) &&( 0 < component.getChildCount()) && (MIN_MASS < parentCM.weight)){ + //System.err.println(String.format("%-32s: %s ",indent+">>["+ component.getName()+"]", parentData.toCMDebug() )); + System.err.println(String.format("%-32s: %s ",indent+">>["+ component.getName()+"]", parentData.toDebug() )); } - longitudinalInertia = parent.getLongitudinalUnitInertia() * parentCG.weight; - rotationalInertia = parent.getRotationalUnitInertia() * parentCG.weight; - + if (!component.getOverrideSubcomponents()) { + if (component.isMassOverridden()) + parentCM = parentCM.setWeight(MathUtil.max(component.getOverrideMass(), MIN_MASS)); + if (component.isCGOverridden()) + parentCM = parentCM.setXYZ(component.getOverrideCG()); + } + MassData childrenData = MassData.ZERO_DATA; // Combine data for subcomponents - for (RocketComponent sibling : parent.getChildren()) { - Coordinate combinedCG; - double dx2, dr2; - - // Compute data of sibling - MassData siblingData = calculateAssemblyMassData(sibling); - Coordinate[] siblingCGs = sibling.toRelative(siblingData.getCG(), parent); - - for (Coordinate siblingCG : siblingCGs) { - - // Compute CG of this + sibling - combinedCG = parentCG.average(siblingCG); - - // Add effect of this CG change to parent inertia - dx2 = pow2(parentCG.x - combinedCG.x); - longitudinalInertia += parentCG.weight * dx2; - - dr2 = pow2(parentCG.y - combinedCG.y) + pow2(parentCG.z - combinedCG.z); - rotationalInertia += parentCG.weight * dr2; - - - // Add inertia of sibling - longitudinalInertia += siblingData.getLongitudinalInertia(); - rotationalInertia += siblingData.getRotationalInertia(); - - // Add effect of sibling CG change - dx2 = pow2(siblingData.getCG().x - combinedCG.x); - longitudinalInertia += siblingData.getCG().weight * dx2; - - dr2 = pow2(siblingData.getCG().y - combinedCG.y) + pow2(siblingData.getCG().z - combinedCG.z); - rotationalInertia += siblingData.getCG().weight * dr2; - - // Set combined CG - parentCG = combinedCG; + for (RocketComponent child : component.getChildren()) { + if( child instanceof BoosterSet ){ + // this stage will be tallied separately... skip. + continue; } + + // child data, relative to parent's reference frame + MassData childData = calculateAssemblyMassData(child, indent+"...."); + + childrenData = childrenData.add( childData ); } + + + MassData resultantData = parentData; // default if not instanced + // compensate for component-instancing propogating to children's data + int instanceCount = component.getInstanceCount(); + boolean hasChildren = ( 0 < component.getChildCount()); + if (( 1 < instanceCount )&&( hasChildren )){ + if(( debug )){ + System.err.println(String.format("%s Found instanceable with %d children: %s (t= %s)", + indent, component.getInstanceCount(), component.getName(), component.getClass().getSimpleName() )); + } + + final double curIxx = childrenData.getIxx(); // MOI about x-axis + final double curIyy = childrenData.getIyy(); // MOI about y axis + final double curIzz = childrenData.getIzz(); // MOI about z axis + + Coordinate eachCM = childrenData.cm; + MassData instAccumData = new MassData(); // accumulator for instance MassData + Coordinate[] instanceLocations = ((Instanceable) component).getInstanceOffsets(); + for( Coordinate curOffset : instanceLocations ){ + if( debug){ + //System.err.println(String.format("%-32s: %s", indent+" inst Accum", instAccumData.toCMDebug() )); + System.err.println(String.format("%-32s: %s", indent+" inst Accum", instAccumData.toDebug() )); + } + + Coordinate instanceCM = curOffset.add(eachCM); + + MassData instanceData = new MassData( instanceCM, curIxx, curIyy, curIzz); + + // 3) Project the template data to the new CM + // and add to the total + instAccumData = instAccumData.add( instanceData); + } + + childrenData = instAccumData; + } + + // combine the parent's and children's data + resultantData = parentData.add( childrenData); + // Override total data - if (parent.getOverrideSubcomponents()) { - if (parent.isMassOverridden()) { - double oldMass = parentCG.weight; - double newMass = MathUtil.max(parent.getOverrideMass(), MIN_MASS); - longitudinalInertia = longitudinalInertia * newMass / oldMass; - rotationalInertia = rotationalInertia * newMass / oldMass; - parentCG = parentCG.setWeight(newMass); - } - if (parent.isCGOverridden()) { - double oldx = parentCG.x; - double newx = parent.getOverrideCGX(); - longitudinalInertia += parentCG.weight * pow2(oldx - newx); - parentCG = parentCG.setX(newx); - } - } +// if (component.getOverrideSubcomponents()) { +// if (component.isMassOverridden()) { +// double oldMass = parentCM.weight; +// double newMass = MathUtil.max(component.getOverrideMass(), MIN_MASS); +// longitudinalInertia = longitudinalInertia * newMass / oldMass; +// rotationalInertia = rotationalInertia * newMass / oldMass; +// parentCM = parentCM.setWeight(newMass); +// } +// if (component.isCGOverridden()) { +// double oldx = parentCM.x; +// double newx = component.getOverrideCGX(); +// longitudinalInertia += parentCM.weight * MathUtil.pow2(oldx - newx); +// parentCM = parentCM.setX(newx); +// } +// } - MassData parentData = new MassData(parentCG, longitudinalInertia, rotationalInertia, 0); - return parentData; +// if( debug){ +// //System.err.println(String.format("%-32s: %s ", indent+"<<["+component.getName()+"][asbly]", resultantData.toCMDebug())); +// System.err.println(String.format("%-32s: %s ", indent+"@@["+component.getName()+"][asbly]", resultantData.toDebug())); +// } +// + // move to parent's reference point + resultantData = resultantData.move( component.getOffset() ); + if( debug){ + //System.err.println(String.format("%-32s: %s ", indent+"<<["+component.getName()+"][asbly]", resultantData.toCMDebug())); + System.err.println(String.format("%-32s: %s ", indent+"<<["+component.getName()+"][asbly]", resultantData.toDebug())); + } + + + return resultantData; } /** @@ -418,9 +463,7 @@ public class MassCalculator implements Monitorable { * its execution. */ protected void voidMassCache() { - this.cgCache = null; - this.longitudinalInertiaCache = null; - this.rotationalInertiaCache = null; + this.cache.clear(); } diff --git a/core/src/net/sf/openrocket/masscalc/MassData.java b/core/src/net/sf/openrocket/masscalc/MassData.java new file mode 100644 index 000000000..7befe7a2b --- /dev/null +++ b/core/src/net/sf/openrocket/masscalc/MassData.java @@ -0,0 +1,294 @@ +package net.sf.openrocket.masscalc; + +import static net.sf.openrocket.util.MathUtil.pow2; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.util.BugException; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + +/** + * An immutable value object containing the mass data of a component, assembly or entire rocket. + * + * @author Sampo Niskanen + * @author Daniel Williams + */ +public class MassData { + private static final double MIN_MASS = MathUtil.EPSILON; + + /* ASSUME: a MassData instance implicitly describes a bodies w.r.t. a reference point + * a) the cm locates the body from the reference point + * b) each MOI is about the reference point. + */ + public final Coordinate cm; + + // Moment of Inertias: + // x-axis: through the rocket's nose + // y,z axes: mutually perpendicular to each other and the x-axis. Because a rocket + // is (mostly) axisymmetric, y-axis and z-axis placement is arbitrary. + + // MOI about the Center of Mass + private final InertiaMatrix I_cm; + + // implements a simplified, diagonal MOI + private class InertiaMatrix { + public final double xx; + public final double yy; + public final double zz; + + public InertiaMatrix( double Ixx, double Iyy, double Izz){ + if(( 0 > Ixx)||( 0 > Iyy)||( 0 > Izz)){ + throw new BugException(" attempted to initialize an InertiaMatrix with a negative inertia value."); + } + this.xx = Ixx; + this.yy = Iyy; + this.zz = Izz; + } + + public InertiaMatrix add( InertiaMatrix other){ + return new InertiaMatrix( this.xx + other.xx, this.yy + other.yy, this.zz + other.zz); + } + + /** + * This function returns a copy of this MassData translated to a new location via + * a simplified model. + * + * Assuming rotations are independent, and occur perpendicular to the principal axes, + * The above can be simplified to produce a diagonal newMOI in + * the form of the parallel axis theorem: + * [ oldMOI + m*d^2, ...] + * + * For the full version of the equations, see: + * [1] https://en.wikipedia.org/wiki/Parallel_axis_theorem#Tensor_generalization + * [2] http://www.kwon3d.com/theory/moi/triten.html + * + * + * @param delta vector position from center of mass to desired reference location + * + * @return MassData the new MassData instance + */ + private InertiaMatrix translateInertia( final Coordinate delta, final double mass){ + double x2 = pow2(delta.x); + double y2 = pow2(delta.y); + double z2 = pow2(delta.z); + + // See: Parallel Axis Theorem in the function comments. + // I = I + m L^2; L = sqrt( y^2 + z^2); + // ergo: I = I + m (y^2 + z^2); + double newIxx = this.xx + mass*(y2 + z2); + double newIyy = this.yy + mass*(x2 + z2); + double newIzz = this.zz + mass*(x2 + y2); + + // MOI about the reference point + InertiaMatrix toReturn=new InertiaMatrix( newIxx, newIyy, newIzz); + return toReturn; + } + + @Override + public int hashCode() { + return (int) (Double.doubleToLongBits(this.xx) ^ Double.doubleToLongBits(this.yy) ^ Double.doubleToLongBits(this.xx) ); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof InertiaMatrix)) + return false; + + InertiaMatrix other = (InertiaMatrix) obj; + return (MathUtil.equals(this.xx, other.xx) && MathUtil.equals(this.yy, other.yy) && + MathUtil.equals(this.zz, other.zz)) ; + } + + } + + public static final MassData ZERO_DATA = new MassData(Coordinate.ZERO, 0, 0, 0); + + public MassData() { + this.cm = Coordinate.ZERO; + this.I_cm = new InertiaMatrix(0,0,0); + } + + + /** + * Return a new instance of MassData which is a sum of this and the other. + * + * @param childData second mass data to combine with this + * + * @return MassData the new MassData instance + */ + public MassData add(MassData body2){ + MassData body1 = this; + + // the combined Center of mass is defined to be 'point 3' + Coordinate combinedCM = body1.cm.average( body2.cm ); + + // transform InertiaMatrix from it's previous frame to the common frame + Coordinate delta1 = combinedCM.sub( body1.cm).setWeight(0); + InertiaMatrix I1_at_3 = body1.I_cm.translateInertia(delta1, body1.getMass()); + + // transform InertiaMatrix from it's previous frame to the common frame + Coordinate delta2 = combinedCM.sub( body2.cm).setWeight(0); + InertiaMatrix I2_at_3 = body2.I_cm.translateInertia(delta2, body2.getMass()); + + // once both are in the same frame, simply add them + InertiaMatrix combinedMOI = I1_at_3.add(I2_at_3); + MassData sumData = new MassData( combinedCM, combinedMOI); + + { // vvvv DEVEL vvvv +// System.err.println(" ?? body1: "+ body1.toDebug() ); +// System.err.println(" delta 1=>3: "+ delta1); +// System.err.println(String.format(" >> 1@3: == [ %g, %g, %g ]", +// I1_at_3.xx, I1_at_3.yy, I1_at_3.zz)); +// +// System.err.println(" ?? body2: "+ body2.toDebug() ); +// System.err.println(" delta 2=>3: "+ delta2); +// System.err.println(String.format(" >> 2@3: [ %g, %g, %g ]", +// I2_at_3.xx, I2_at_3.yy, I2_at_3.zz)); +// System.err.println(" ?? asbly3: "+sumData.toDebug()+"\n"); + +// InertiaMatrix rev1 = It1.translateInertia(delta1.multiply(-1), body1.getMass()); +// System.err.println(String.format(" !!XX orig: %s\n", childDataChild.toDebug() )); +// System.err.println(String.format("%s!!XX revr: %s\n", indent, reverse.toDebug() )); + } + + return sumData; + } + + private MassData(Coordinate newCM, InertiaMatrix newMOI){ + this.cm = newCM; + this.I_cm = newMOI; + } + + public MassData( RocketComponent component ){ + //MassData parentData = new MassData( parentCM, parentIx, parentIt); + + // Calculate data for this component + Coordinate newCM = component.getComponentCG(); + double mass = newCM.weight; + if ( mass < MIN_MASS){ + newCM = newCM .setWeight(MIN_MASS); + mass = MIN_MASS; + } + this.cm = newCM; + double Ix = component.getRotationalUnitInertia() * mass; + double It = component.getLongitudinalUnitInertia() * mass; + this.I_cm = new InertiaMatrix( Ix, It, It); + } + + public MassData(Coordinate newCM, double newIxx, double newIyy, double newIzz){ + if (newCM == null) { + throw new IllegalArgumentException("CM is null"); + } + + this.cm = newCM; + this.I_cm = new InertiaMatrix(newIxx, newIyy, newIzz); + } + + public MassData(final Coordinate cg, final double rotationalInertia, final double longitudinalInertia) { + if (cg == null) { + throw new IllegalArgumentException("cg is null"); + } + this.cm = cg; + double newIxx = rotationalInertia; + double newIyy = longitudinalInertia; + double newIzz = longitudinalInertia; + + this.I_cm = new InertiaMatrix( newIxx, newIyy, newIzz); + } + + public double getMass(){ + return cm.weight; + } + + public Coordinate getCG() { + return cm; + } + + public Coordinate getCM() { + return cm; + } + + public double getIyy(){ + return I_cm.yy; + } + public double getLongitudinalInertia() { + return I_cm.yy; + } + + public double getIxx(){ + return I_cm.xx; + } + public double getRotationalInertia() { + return I_cm.xx; + } + + public double getIzz(){ + return I_cm.zz; + } + + public double getPropellantMass(){ + return Double.NaN; + } + + /** + * Return a new instance of MassData moved by the delta vector supplied. + * This function is intended to move the REFERENCE POINT, not the CM, and will leave + * the Inertia matrix completely unchanged. + * + * ASSUME: MassData implicity describe their respective bodies w.r.t 0,0,0) + * a) the cm locates the body from the reference point + * b) each MOI is about the reference point. + * + * @param delta vector from current reference point to new/desired reference point (mass is ignored) + * + * @return MassData a new MassData instance, locating the same CM from a different reference point. + */ + public MassData move( final Coordinate delta ){ + MassData body1 = this; + + // don't change the mass, just move it. + Coordinate newCM = body1.cm.add( delta ); + + MassData newData = new MassData( newCM, this.I_cm); + + { // DEVEL-DEBUG +// System.err.println(" ?? body1: "+ body1.toDebug() ); +// System.err.println(" delta: "+ delta); +// System.err.println(" ?? asbly3: "+newData.toDebug()+"\n"); + } + return newData; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof MassData)) + return false; + + MassData other = (MassData) obj; + return (this.cm.equals(other.cm) && (this.I_cm.equals(other.I_cm))); + } + + @Override + public int hashCode() { + return (int) (cm.hashCode() ^ I_cm.hashCode() ); + } + + public String toCMDebug(){ + return String.format("cm= %6.4fg@[%g,%g,%g]", cm.weight, cm.x, cm.y, cm.z); + } + + public String toDebug(){ + return toCMDebug()+" " + + String.format("I_cm=[ %g, %g, %g ]", I_cm.xx, I_cm.yy, I_cm.zz); + } + + @Override + public String toString() { + return "MassData [cg=" + cm + ", longitudinalInertia=" + getIyy() + + ", rotationalInertia=" + getIxx() + "]"; + } + +} diff --git a/core/src/net/sf/openrocket/motor/MotorInstance.java b/core/src/net/sf/openrocket/motor/MotorInstance.java index a8ccae999..e6a8a9c36 100644 --- a/core/src/net/sf/openrocket/motor/MotorInstance.java +++ b/core/src/net/sf/openrocket/motor/MotorInstance.java @@ -76,6 +76,10 @@ public class MotorInstance implements FlightConfigurableParameter throw new UnsupportedOperationException("Retrieve a mount from an immutable no-motors instance"); } + public Coordinate getOffset(){ + return this.position; + } + public Coordinate getPosition() { return this.position; } diff --git a/core/src/net/sf/openrocket/motor/MotorInstanceId.java b/core/src/net/sf/openrocket/motor/MotorInstanceId.java index 33b7a196b..35778b81f 100644 --- a/core/src/net/sf/openrocket/motor/MotorInstanceId.java +++ b/core/src/net/sf/openrocket/motor/MotorInstanceId.java @@ -49,6 +49,10 @@ public final class MotorInstanceId { return componentId; } + public int getInstanceNumber() { + return number; + } + @Override public boolean equals(Object o) { diff --git a/core/src/net/sf/openrocket/motor/ThrustCurveMotorInstance.java b/core/src/net/sf/openrocket/motor/ThrustCurveMotorInstance.java index ee4c33d0e..dec45e925 100644 --- a/core/src/net/sf/openrocket/motor/ThrustCurveMotorInstance.java +++ b/core/src/net/sf/openrocket/motor/ThrustCurveMotorInstance.java @@ -2,6 +2,7 @@ package net.sf.openrocket.motor; import net.sf.openrocket.models.atmosphere.AtmosphericConditions; import net.sf.openrocket.rocketcomponent.MotorMount; +import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.Inertia; import net.sf.openrocket.util.MathUtil; @@ -64,6 +65,17 @@ public class ThrustCurveMotorInstance extends MotorInstance { return stepCG; } + @Override + public Coordinate getOffset( ){ + if( null == mount ){ + return Coordinate.NaN; + }else{ + RocketComponent comp = (RocketComponent) mount; + double delta_x = comp.getLength() + mount.getMotorOverhang() - this.motor.getLength(); + return new Coordinate(delta_x, 0, 0); + } + } + @Override public double getLongitudinalInertia() { return unitLongitudinalInertia * stepCG.weight; @@ -94,6 +106,7 @@ public class ThrustCurveMotorInstance extends MotorInstance { } this.motor = (ThrustCurveMotor)motor; + fireChangeEvent(); } @@ -115,6 +128,7 @@ public class ThrustCurveMotorInstance extends MotorInstance { @Override public void setMount(final MotorMount _mount) { this.mount = _mount; + } @Override @@ -222,5 +236,10 @@ public class ThrustCurveMotorInstance extends MotorInstance { return clone; } + @Override + public String toString(){ + return this.id.toString(); + } + } \ No newline at end of file diff --git a/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java b/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java index e924a5f68..e40717507 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java +++ b/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java @@ -383,6 +383,7 @@ public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial } } + this.isActingMount=true; this.motors.set(fcid,newMotorInstance); } diff --git a/core/src/net/sf/openrocket/rocketcomponent/BoosterSet.java b/core/src/net/sf/openrocket/rocketcomponent/BoosterSet.java index 6ccbd6dad..35688662a 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/BoosterSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/BoosterSet.java @@ -125,6 +125,11 @@ public class BoosterSet extends AxialStage implements FlightConfigurableComponen return this.radialPosition_m; } + @Override + public Coordinate[] getInstanceOffsets(){ + return this.shiftCoordinates(new Coordinate[]{Coordinate.ZERO}); + } + @Override public Coordinate[] getLocations() { if (null == this.parent) { @@ -211,12 +216,12 @@ public class BoosterSet extends AxialStage implements FlightConfigurableComponen buffer.append(String.format("%s %-24s (stage: %d)", prefix, this.getName(), this.getStageNumber())); buffer.append(String.format(" (len: %5.3f offset: %4.1f via: %s )\n", this.getLength(), this.getAxialOffset(), this.relativePosition.name())); - Coordinate[] relCoords = this.shiftCoordinates(new Coordinate[] { Coordinate.ZERO }); + Coordinate[] relCoords = this.shiftCoordinates(new Coordinate[] { this.getOffset() }); Coordinate[] absCoords = this.getLocations(); for (int instanceNumber = 0; instanceNumber < this.count; instanceNumber++) { Coordinate instanceRelativePosition = relCoords[instanceNumber]; Coordinate instanceAbsolutePosition = absCoords[instanceNumber]; - buffer.append(String.format("%s [instance %2d of %2d] %28s %28s\n", prefix, instanceNumber, count, + buffer.append(String.format("%s [%2d/%2d]; %28s; %28s;\n", prefix, instanceNumber+1, count, instanceRelativePosition, instanceAbsolutePosition)); } diff --git a/core/src/net/sf/openrocket/rocketcomponent/CenteringRing.java b/core/src/net/sf/openrocket/rocketcomponent/CenteringRing.java index 8f06bb778..73a999d0a 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/CenteringRing.java +++ b/core/src/net/sf/openrocket/rocketcomponent/CenteringRing.java @@ -99,6 +99,19 @@ public class CenteringRing extends RadiusRingComponent implements LineInstanceab } } + @Override + public Coordinate[] getInstanceOffsets(){ + Coordinate[] toReturn = new Coordinate[this.getInstanceCount()]; + toReturn[0] = Coordinate.ZERO; + + for ( int index=1 ; index < this.getInstanceCount(); index++){ + toReturn[index] = new Coordinate(index*this.instanceSeparation,0,0,0); + + } + + return toReturn; + } + @Override public int getInstanceCount(){ return this.instanceCount; diff --git a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java index 9dae2f02b..125c1fa1c 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java @@ -12,6 +12,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.sf.openrocket.motor.MotorInstance; +import net.sf.openrocket.motor.MotorInstanceId; import net.sf.openrocket.util.ArrayList; import net.sf.openrocket.util.ChangeSource; import net.sf.openrocket.util.Coordinate; @@ -180,36 +181,46 @@ public class FlightConfiguration implements FlightConfigurableParameter getActiveMotors() { + ArrayList toReturn = new ArrayList(); for ( RocketComponent comp : this.getActiveComponents() ){ - - // see planning notes... if ( comp instanceof MotorMount ){ MotorMount mount = (MotorMount)comp; MotorInstance inst = mount.getMotorInstance(this.fcid); + + // this merely accounts for instancing of this component: + // int instancCount = comp.getInstanceCount(); - // NYI: if clustered... - // if( mount instanceof Clusterable ){ - // if( 1 < comp.getInstanceCount() ){ - // if comp is clustered, it will be clustered from the innerTube, no? - //List instanceList = mount.getMotorInstance(this.fcid); - -// // vvvv DEVEL vvvv -// -// if(( mount.isMotorMount()) && ( MotorInstance.EMPTY_INSTANCE == inst)){ -// if( mount instanceof BodyTube){ -// MotorInstance bt_inst = ((BodyTube)mount).getMotorInstance(this.fcid); -// log.error("Detected EMPTY_INSTANCE in config: "+this.fcid.key.substring(0,8)+", mount: \""+comp.getName()+"\""); -// ((BodyTube)mount).printMotorDebug(); -// } -// continue; -// } -// // ^^^^ DEVEL ^^^^ + // we account for instances this way because it counts *all* the instancing between here + // and the rocket root. + Coordinate[] instanceLocations= comp.getLocations(); // motors go inactive after burnout, so include this filter too if (inst.isActive()){ - toReturn.add(inst); - } +// System.err.println(String.format(",,,,,,,, : %s (%s)", +// inst.getMotor().getDigest(), inst.getMotorID() )); + int instanceNumber = 0; + for ( Coordinate curMountLocation : instanceLocations ){ + MotorInstance curInstance = inst.clone(); + curInstance.setID( new MotorInstanceId( comp.getName(), instanceNumber+1) ); + + // 1) mount location + // == curMountLocation + + // 2) motor location w/in mount: parent.refpoint -> motor.refpoint + Coordinate curMotorOffset = curInstance.getOffset(); + curInstance.setPosition( curMountLocation.add(curMotorOffset) ); + toReturn.add( curInstance); + + // vvvv DEVEL vvvv +// System.err.println(String.format(",,,,,,,,[ %2d]: %s. (%s)", +// instanceNumber, curInstance.getMotor().getDigest(), curInstance)); + // ^^^^ DEVEL ^^^^ + instanceNumber ++; + } + + } + } } @@ -350,6 +361,9 @@ public class FlightConfiguration implements FlightConfigurableParameter this.getOffsets().length == this.getInstanceCount() should ALWAYS be true. + * If getInstanceCount() returns anything besides 1 this function should be overridden as well. + * + * + * @return coordinates location of each instance relative to this component's reference point. + */ + public Coordinate[] getInstanceOffsets(); + /** * How many instances of this component are represented. This should generally be editable. * @param newCount number of instances to set diff --git a/core/src/net/sf/openrocket/rocketcomponent/LaunchButton.java b/core/src/net/sf/openrocket/rocketcomponent/LaunchButton.java index ca9ed89b5..f11775232 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/LaunchButton.java +++ b/core/src/net/sf/openrocket/rocketcomponent/LaunchButton.java @@ -118,8 +118,18 @@ public abstract class LaunchButton extends ExternalComponent implements LineInst super.setPositionValue(value); fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); } - - + + @Override + public Coordinate[] getInstanceOffsets(){ + Coordinate[] toReturn = new Coordinate[this.getInstanceCount()]; + toReturn[0] = Coordinate.ZERO; + + for ( int index=1 ; index < this.getInstanceCount(); index++){ + toReturn[index] = new Coordinate(index*this.instanceSeparation,0,0,0); + } + + return toReturn; + } @Override protected void loadFromPreset(ComponentPreset preset) { diff --git a/core/src/net/sf/openrocket/rocketcomponent/LaunchLug.java b/core/src/net/sf/openrocket/rocketcomponent/LaunchLug.java index c08fc7425..ca8167d96 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/LaunchLug.java +++ b/core/src/net/sf/openrocket/rocketcomponent/LaunchLug.java @@ -141,6 +141,18 @@ public class LaunchLug extends ExternalComponent implements Coaxial, LineInstanc return ComponentPreset.Type.LAUNCH_LUG; } + + @Override + public Coordinate[] getInstanceOffsets(){ + Coordinate[] toReturn = new Coordinate[this.getInstanceCount()]; + toReturn[0] = Coordinate.ZERO; + + for ( int index=1 ; index < this.getInstanceCount(); index++){ + toReturn[index] = new Coordinate(index*this.instanceSeparation,0,0,0); + } + + return toReturn; + } @Override protected Coordinate[] shiftCoordinates(Coordinate[] array) { diff --git a/core/src/net/sf/openrocket/rocketcomponent/PodSet.java b/core/src/net/sf/openrocket/rocketcomponent/PodSet.java index 9fe969969..f81e3f36f 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/PodSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/PodSet.java @@ -76,6 +76,11 @@ public class PodSet extends ComponentAssembly implements RingInstanceable { return BodyComponent.class.isAssignableFrom(type); } + @Override + public Coordinate[] getInstanceOffsets(){ + return shiftCoordinates(new Coordinate[]{Coordinate.ZERO}); + } + @Override public Coordinate[] getLocations() { if (null == this.parent) { diff --git a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java index fb69179fe..e94dc7a7c 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java @@ -1083,9 +1083,13 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab return this.position; } +// public Coordinate[] getOffset() { +// return new Coordinate[]{this.position}; +// } + /** - * @deprecated kept around as example code. instead use + * @deprecated kept around as example code. instead use getLocations * @return */ private Coordinate getAbsoluteVector() { @@ -2081,7 +2085,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab } public void toDebugTreeNode(final StringBuilder buffer, final String prefix) { - buffer.append(String.format("%s %-24s %5.3f %24s %24s\n", prefix, this.getName(), this.getLength(), + buffer.append(String.format("%s %-24s; %5.3f; %24s; %24s;\n", prefix, this.getName(), this.getLength(), this.getOffset(), this.getLocations()[0])); } diff --git a/core/src/net/sf/openrocket/rocketcomponent/RocketUtils.java b/core/src/net/sf/openrocket/rocketcomponent/RocketUtils.java index 517aeaad4..6169976a6 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RocketUtils.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RocketUtils.java @@ -24,6 +24,7 @@ public abstract class RocketUtils { return length; } + // get rid of this method.... we can sure come up with a better way to do this.... public static Coordinate getCG(Rocket rocket, MassCalcType calcType) { MassCalculator massCalculator = new MassCalculator(); Coordinate cg = massCalculator.getCG(rocket.getDefaultConfiguration(), calcType); diff --git a/core/src/net/sf/openrocket/simulation/AbstractSimulationStepper.java b/core/src/net/sf/openrocket/simulation/AbstractSimulationStepper.java index 5c256862d..d3ae5cc3c 100644 --- a/core/src/net/sf/openrocket/simulation/AbstractSimulationStepper.java +++ b/core/src/net/sf/openrocket/simulation/AbstractSimulationStepper.java @@ -3,6 +3,8 @@ package net.sf.openrocket.simulation; import java.util.List; import net.sf.openrocket.masscalc.MassCalculator; +import net.sf.openrocket.masscalc.MassData; +import net.sf.openrocket.masscalc.MassCalculator.MassCalcType; import net.sf.openrocket.models.atmosphere.AtmosphericConditions; import net.sf.openrocket.motor.MotorInstance; import net.sf.openrocket.motor.MotorInstanceConfiguration; @@ -124,10 +126,11 @@ public abstract class AbstractSimulationStepper implements SimulationStepper { } MassCalculator calc = status.getSimulationConditions().getMassCalculator(); - cg = calc.getCG(status.getConfiguration(), status.getMotorConfiguration()); - longitudinalInertia = calc.getLongitudinalInertia(status.getConfiguration(), status.getMotorConfiguration()); - rotationalInertia = calc.getRotationalInertia(status.getConfiguration(), status.getMotorConfiguration()); - propellantMass = calc.getPropellantMass(status.getConfiguration(), status.getMotorConfiguration()); + // not sure if this is actually Launch mass or not... + cg = calc.getCG(status.getConfiguration(), MassCalcType.LAUNCH_MASS); + longitudinalInertia = calc.getLongitudinalInertia(status.getConfiguration(), MassCalcType.LAUNCH_MASS); + rotationalInertia = calc.getRotationalInertia(status.getConfiguration(), MassCalcType.LAUNCH_MASS); + propellantMass = calc.getPropellantMass(status.getConfiguration(), MassCalcType.LAUNCH_MASS); mass = new MassData(cg, longitudinalInertia, rotationalInertia, propellantMass); // Call post-listener diff --git a/core/src/net/sf/openrocket/simulation/BasicLandingStepper.java b/core/src/net/sf/openrocket/simulation/BasicLandingStepper.java index eea835200..6723de204 100644 --- a/core/src/net/sf/openrocket/simulation/BasicLandingStepper.java +++ b/core/src/net/sf/openrocket/simulation/BasicLandingStepper.java @@ -1,5 +1,6 @@ package net.sf.openrocket.simulation; +import net.sf.openrocket.masscalc.MassData; import net.sf.openrocket.models.atmosphere.AtmosphericConditions; import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.simulation.exception.SimulationException; diff --git a/core/src/net/sf/openrocket/simulation/BasicTumbleStepper.java b/core/src/net/sf/openrocket/simulation/BasicTumbleStepper.java index 15afc122d..bc142e051 100644 --- a/core/src/net/sf/openrocket/simulation/BasicTumbleStepper.java +++ b/core/src/net/sf/openrocket/simulation/BasicTumbleStepper.java @@ -1,5 +1,6 @@ package net.sf.openrocket.simulation; +import net.sf.openrocket.masscalc.MassData; import net.sf.openrocket.models.atmosphere.AtmosphericConditions; import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.util.Coordinate; diff --git a/core/src/net/sf/openrocket/simulation/MassData.java b/core/src/net/sf/openrocket/simulation/MassData.java deleted file mode 100644 index 74386364d..000000000 --- a/core/src/net/sf/openrocket/simulation/MassData.java +++ /dev/null @@ -1,74 +0,0 @@ -package net.sf.openrocket.simulation; - -import net.sf.openrocket.util.Coordinate; -import net.sf.openrocket.util.MathUtil; - -/** - * An immutable value object containing the mass data of a rocket. - * - * @author Sampo Niskanen - */ -public class MassData { - - private final Coordinate cg; - private final double longitudinalInertia; - private final double rotationalInertia; - private final double propellantMass; - - - public MassData(Coordinate cg, double longitudinalInertia, double rotationalInertia, double propellantMass) { - if (cg == null) { - throw new IllegalArgumentException("cg is null"); - } - this.cg = cg; - this.longitudinalInertia = longitudinalInertia; - this.rotationalInertia = rotationalInertia; - this.propellantMass = propellantMass; - } - - - - - public Coordinate getCG() { - return cg; - } - - public double getLongitudinalInertia() { - return longitudinalInertia; - } - - public double getRotationalInertia() { - return rotationalInertia; - } - - public double getPropellantMass() { - return propellantMass; - } - - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (!(obj instanceof MassData)) - return false; - - MassData other = (MassData) obj; - return (this.cg.equals(other.cg) && MathUtil.equals(this.longitudinalInertia, other.longitudinalInertia) && - MathUtil.equals(this.rotationalInertia, other.rotationalInertia)) && MathUtil.equals(this.propellantMass, other.propellantMass) ; - } - - - @Override - public int hashCode() { - return (int) (cg.hashCode() ^ Double.doubleToLongBits(longitudinalInertia) ^ Double.doubleToLongBits(rotationalInertia) ^ Double.doubleToLongBits(propellantMass) ); - } - - - @Override - public String toString() { - return "MassData [cg=" + cg + ", longitudinalInertia=" + longitudinalInertia - + ", rotationalInertia=" + rotationalInertia + ", propellantMass="+propellantMass + "]"; - } - -} diff --git a/core/src/net/sf/openrocket/simulation/RK4SimulationStepper.java b/core/src/net/sf/openrocket/simulation/RK4SimulationStepper.java index d431569af..9dee82d4e 100644 --- a/core/src/net/sf/openrocket/simulation/RK4SimulationStepper.java +++ b/core/src/net/sf/openrocket/simulation/RK4SimulationStepper.java @@ -10,6 +10,7 @@ import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.l10n.Translator; +import net.sf.openrocket.masscalc.MassData; import net.sf.openrocket.models.atmosphere.AtmosphericConditions; import net.sf.openrocket.simulation.exception.SimulationCalculationException; import net.sf.openrocket.simulation.exception.SimulationException; diff --git a/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingSimulationListener.java b/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingSimulationListener.java index 8b54f8224..21e7f96aa 100644 --- a/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingSimulationListener.java +++ b/core/src/net/sf/openrocket/simulation/extension/impl/ScriptingSimulationListener.java @@ -8,6 +8,7 @@ import javax.script.ScriptException; import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.masscalc.MassData; import net.sf.openrocket.models.atmosphere.AtmosphericConditions; import net.sf.openrocket.motor.MotorInstanceId; import net.sf.openrocket.motor.MotorInstance; @@ -15,7 +16,6 @@ import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.simulation.AccelerationData; import net.sf.openrocket.simulation.FlightEvent; -import net.sf.openrocket.simulation.MassData; import net.sf.openrocket.simulation.SimulationStatus; import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.simulation.exception.SimulationListenerException; diff --git a/core/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java b/core/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java index 91d8a49d1..03c55bf0a 100644 --- a/core/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java +++ b/core/src/net/sf/openrocket/simulation/listeners/AbstractSimulationListener.java @@ -2,6 +2,7 @@ package net.sf.openrocket.simulation.listeners; import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.masscalc.MassData; import net.sf.openrocket.models.atmosphere.AtmosphericConditions; import net.sf.openrocket.motor.MotorInstanceId; import net.sf.openrocket.motor.MotorInstance; @@ -9,7 +10,6 @@ import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.simulation.AccelerationData; import net.sf.openrocket.simulation.FlightEvent; -import net.sf.openrocket.simulation.MassData; import net.sf.openrocket.simulation.SimulationStatus; import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.util.BugException; diff --git a/core/src/net/sf/openrocket/simulation/listeners/SimulationComputationListener.java b/core/src/net/sf/openrocket/simulation/listeners/SimulationComputationListener.java index 88422d72c..5a3e3c95d 100644 --- a/core/src/net/sf/openrocket/simulation/listeners/SimulationComputationListener.java +++ b/core/src/net/sf/openrocket/simulation/listeners/SimulationComputationListener.java @@ -2,9 +2,9 @@ package net.sf.openrocket.simulation.listeners; import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; +import net.sf.openrocket.masscalc.MassData; import net.sf.openrocket.models.atmosphere.AtmosphericConditions; import net.sf.openrocket.simulation.AccelerationData; -import net.sf.openrocket.simulation.MassData; import net.sf.openrocket.simulation.SimulationStatus; import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.util.Coordinate; diff --git a/core/src/net/sf/openrocket/simulation/listeners/SimulationListenerHelper.java b/core/src/net/sf/openrocket/simulation/listeners/SimulationListenerHelper.java index 45c3ced9f..d6417471f 100644 --- a/core/src/net/sf/openrocket/simulation/listeners/SimulationListenerHelper.java +++ b/core/src/net/sf/openrocket/simulation/listeners/SimulationListenerHelper.java @@ -7,6 +7,7 @@ import org.slf4j.LoggerFactory; import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; import net.sf.openrocket.aerodynamics.Warning; +import net.sf.openrocket.masscalc.MassData; import net.sf.openrocket.models.atmosphere.AtmosphericConditions; import net.sf.openrocket.motor.MotorInstanceId; import net.sf.openrocket.motor.MotorInstance; @@ -14,7 +15,6 @@ import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.simulation.AccelerationData; import net.sf.openrocket.simulation.FlightEvent; -import net.sf.openrocket.simulation.MassData; import net.sf.openrocket.simulation.SimulationStatus; import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.util.Coordinate; diff --git a/core/src/net/sf/openrocket/util/TestRockets.java b/core/src/net/sf/openrocket/util/TestRockets.java index f73888273..b352e10e2 100644 --- a/core/src/net/sf/openrocket/util/TestRockets.java +++ b/core/src/net/sf/openrocket/util/TestRockets.java @@ -12,15 +12,19 @@ import net.sf.openrocket.material.Material.Type; import net.sf.openrocket.motor.Manufacturer; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.MotorInstance; +import net.sf.openrocket.motor.MotorInstanceId; import net.sf.openrocket.motor.ThrustCurveMotor; +import net.sf.openrocket.motor.ThrustCurveMotorInstance; import net.sf.openrocket.preset.ComponentPreset; import net.sf.openrocket.preset.ComponentPresetFactory; import net.sf.openrocket.preset.InvalidComponentPresetException; import net.sf.openrocket.preset.TypedPropertyMap; import net.sf.openrocket.rocketcomponent.AxialStage; import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.BoosterSet; import net.sf.openrocket.rocketcomponent.Bulkhead; import net.sf.openrocket.rocketcomponent.CenteringRing; +import net.sf.openrocket.rocketcomponent.ClusterConfiguration; import net.sf.openrocket.rocketcomponent.DeploymentConfiguration; import net.sf.openrocket.rocketcomponent.DeploymentConfiguration.DeployEvent; import net.sf.openrocket.rocketcomponent.ExternalComponent; @@ -40,6 +44,7 @@ import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.rocketcomponent.ReferenceType; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.rocketcomponent.ShockCord; import net.sf.openrocket.rocketcomponent.RocketComponent.Position; import net.sf.openrocket.rocketcomponent.StageSeparationConfiguration; import net.sf.openrocket.rocketcomponent.Transition; @@ -83,6 +88,66 @@ public class TestRockets { } + private static MotorInstance generateMotorInstance_M1350_75mm(){ + // public ThrustCurveMotor(Manufacturer manufacturer, String designation, String description, + // Motor.Type type, double[] delays, double diameter, double length, + // double[] time, double[] thrust, + // Coordinate[] cg, String digest); + ThrustCurveMotor mtr = new ThrustCurveMotor( + Manufacturer.getManufacturer("AeroTech"),"M1350", "Desc", + Motor.Type.SINGLE, new double[] {}, 0.075, 0.622, + new double[] { 0, 1, 2 }, new double[] { 0, 1357, 0 }, + new Coordinate[] { + new Coordinate(.311, 0, 0, 4.808),new Coordinate(.311, 0, 0, 3.389),new Coordinate(.311, 0, 0, 1.970)}, + "digest M1350 test"); + return mtr.getNewInstance(); + } + + private static MotorInstance generateMotorInstance_G77_29mm(){ + // public ThrustCurveMotor(Manufacturer manufacturer, String designation, String description, + // Motor.Type type, double[] delays, double diameter, double length, + // double[] time, double[] thrust, + // Coordinate[] cg, String digest); + ThrustCurveMotor mtr = new ThrustCurveMotor( + Manufacturer.getManufacturer("AeroTech"),"G77", "Desc", + Motor.Type.SINGLE, new double[] {4,7,10},0.029, 0.124, + new double[] { 0, 1, 2 }, new double[] { 0, 1, 0 }, + new Coordinate[] { + new Coordinate(.062, 0, 0, 0.123),new Coordinate(.062, 0, 0, .0935),new Coordinate(.062, 0, 0, 0.064)}, + "digest G77 test"); + return mtr.getNewInstance(); + } + + // + public static Rocket makeNoMotorRocket() { + Rocket rocket; + AxialStage stage; + NoseCone nosecone; + BodyTube bodytube; + + + rocket = new Rocket(); + stage = new AxialStage(); + stage.setName("Stage1"); + // Stage construction + rocket.addChild(stage); + + nosecone = new NoseCone(Transition.Shape.ELLIPSOID, 0.105, 0.033); + nosecone.setThickness(0.001); + bodytube = new BodyTube(0.69, 0.033, 0.001); + bodytube.setMotorMount(true); + + TrapezoidFinSet finset = new TrapezoidFinSet(3, 0.495, 0.1, 0.3, 0.185); + finset.setThickness(0.005); + bodytube.addChild(finset); + + // Component construction + stage.addChild(nosecone); + stage.addChild(bodytube); + + + return rocket; + } /** * Create a new test rocket based on the value 'key'. The rocket utilizes most of the @@ -544,6 +609,157 @@ public class TestRockets { return rocket; } + public static Rocket makeFalcon9Heavy() { + Rocket rocket = new Rocket(); + rocket.setName("Falcon9H Scale Rocket"); + FlightConfiguration config = rocket.getDefaultConfiguration(); + + // ====== Payload Stage ====== + // ====== ====== ====== ====== + AxialStage payloadStage = new AxialStage(); + payloadStage.setName("Payload Fairing"); + rocket.addChild(payloadStage); + + { + NoseCone payloadFairingNoseCone = new NoseCone(Transition.Shape.POWER, 0.118, 0.052); + payloadFairingNoseCone.setName("PL Fairing Nose"); + payloadFairingNoseCone.setThickness(0.001); + payloadFairingNoseCone.setShapeParameter(0.5); + //payloadFairingNoseCone.setLength(0.118); + //payloadFairingNoseCone.setAftRadius(0.052); + payloadFairingNoseCone.setAftShoulderRadius( 0.051 ); + payloadFairingNoseCone.setAftShoulderLength( 0.02 ); + payloadFairingNoseCone.setAftShoulderThickness( 0.001 ); + payloadFairingNoseCone.setAftShoulderCapped( false ); + payloadStage.addChild(payloadFairingNoseCone); + + BodyTube payloadBody = new BodyTube(0.132, 0.052, 0.001); + payloadBody.setName("PL Fairing Body"); + payloadStage.addChild(payloadBody); + + Transition payloadFairingTail = new Transition(); + payloadFairingTail.setName("PL Fairing Transition"); + payloadFairingTail.setLength(0.014); + payloadFairingTail.setThickness(0.002); + payloadFairingTail.setForeRadiusAutomatic(true); + payloadFairingTail.setAftRadiusAutomatic(true); + payloadStage.addChild(payloadFairingTail); + + BodyTube upperStageBody= new BodyTube(0.18, 0.0385, 0.001); + upperStageBody.setName("Upper Stage Body "); + payloadStage.addChild( upperStageBody); + + { + // Parachute + Parachute upperChute= new Parachute(); + upperChute.setName("Parachute"); + upperChute.setRelativePosition(Position.MIDDLE); + upperChute.setPositionValue(0.0); + upperChute.setDiameter(0.3); + upperChute.setLineCount(6); + upperChute.setLineLength(0.3); + upperStageBody.addChild( upperChute); + + // Cord + ShockCord cord = new ShockCord(); + cord.setName("Shock Cord"); + cord.setRelativePosition(Position.BOTTOM); + cord.setPositionValue(0.0); + cord.setCordLength(0.4); + upperStageBody.addChild( cord); + } + + BodyTube interstage= new BodyTube(0.12, 0.0385, 0.001); + interstage.setName("Interstage"); + payloadStage.addChild( interstage); + } + + // ====== Core Stage ====== + // ====== ====== ====== ====== + AxialStage coreStage = new AxialStage(); + coreStage.setName("Core Stage"); + rocket.addChild(coreStage); + + { + BodyTube coreBody = new BodyTube(0.8, 0.0385, 0.001); + // 74 mm inner dia + coreBody.setName("Core Stage Body"); + coreBody.setMotorMount(true); + coreStage.addChild( coreBody); + { + MotorInstance motorInstance = TestRockets.generateMotorInstance_M1350_75mm(); + motorInstance.setID( new MotorInstanceId( coreBody.getName(), 1) ); + coreBody.setMotorMount( true); + FlightConfigurationID motorConfigId = config.getFlightConfigurationID(); + coreBody.setMotorInstance( motorConfigId, motorInstance); + } + + TrapezoidFinSet coreFins = new TrapezoidFinSet(); + coreFins.setName("Core Fins"); + coreFins.setFinCount(4); + coreFins.setRelativePosition(Position.BOTTOM); + coreFins.setPositionValue(0.0); + coreFins.setBaseRotation( Math.PI / 4); + coreFins.setThickness(0.003); + coreFins.setCrossSection(CrossSection.ROUNDED); + coreFins.setRootChord(0.32); + coreFins.setTipChord(0.12); + coreFins.setHeight(0.12); + coreFins.setSweep(0.18); + coreBody.addChild(coreFins); + } + + // ====== Booster Stage Set ====== + // ====== ====== ====== ====== + BoosterSet boosterStage = new BoosterSet(); + boosterStage.setName("Booster Stage"); + coreStage.addChild( boosterStage); + boosterStage.setRelativePositionMethod(Position.BOTTOM); + boosterStage.setAxialOffset(0.0); + boosterStage.setInstanceCount(2); + boosterStage.setRadialOffset(0.075); + + { + NoseCone boosterCone = new NoseCone(Transition.Shape.POWER, 0.08, 0.0385); + boosterCone.setShapeParameter(0.5); + boosterCone.setName("Booster Nose"); + boosterCone.setThickness(0.002); + //payloadFairingNoseCone.setLength(0.118); + //payloadFairingNoseCone.setAftRadius(0.052); + boosterCone.setAftShoulderRadius( 0.051 ); + boosterCone.setAftShoulderLength( 0.02 ); + boosterCone.setAftShoulderThickness( 0.001 ); + boosterCone.setAftShoulderCapped( false ); + boosterStage.addChild( boosterCone); + + BodyTube boosterBody = new BodyTube(0.8, 0.0385, 0.001); + boosterBody.setName("Booster Body"); + boosterBody.setOuterRadiusAutomatic(true); + boosterStage.addChild( boosterBody); + + { + InnerTube boosterMotorTubes = new InnerTube(); + boosterMotorTubes.setName("Booster Motor Tubes"); + boosterMotorTubes.setLength(0.15); + boosterMotorTubes.setOuterRadius(0.015); // => 29mm motors + boosterMotorTubes.setThickness(0.0005); + boosterMotorTubes.setClusterConfiguration( ClusterConfiguration.CONFIGURATIONS[5]); // 4-ring + //boosterMotorTubes.setClusterConfiguration( ClusterConfiguration.CONFIGURATIONS[13]); // 9-star + boosterMotorTubes.setClusterScale(1.0); + boosterBody.addChild( boosterMotorTubes); + + FlightConfigurationID motorConfigId = config.getFlightConfigurationID(); + MotorInstance motorInstance = TestRockets.generateMotorInstance_G77_29mm(); + motorInstance.setID( new MotorInstanceId( boosterMotorTubes.getName(), 1) ); + boosterMotorTubes.setMotorInstance( motorConfigId, motorInstance); + boosterMotorTubes.setMotorOverhang(0.01234); + } + } + + config.setAllStages(true); + + return rocket; + } /* * Create a new file version 1.00 rocket diff --git a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java index fb52aba53..4c0752d8d 100644 --- a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java +++ b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java @@ -2,176 +2,463 @@ package net.sf.openrocket.masscalc; //import junit.framework.TestCase; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import org.junit.Test; -import net.sf.openrocket.rocketcomponent.AxialStage; -import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.masscalc.MassCalculator.MassCalcType; +import net.sf.openrocket.motor.MotorInstance; import net.sf.openrocket.rocketcomponent.BoosterSet; -import net.sf.openrocket.rocketcomponent.ClusterConfiguration; -import net.sf.openrocket.rocketcomponent.FinSet; +import net.sf.openrocket.rocketcomponent.FlightConfiguration; +import net.sf.openrocket.rocketcomponent.FlightConfigurationID; import net.sf.openrocket.rocketcomponent.InnerTube; -import net.sf.openrocket.rocketcomponent.NoseCone; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; -import net.sf.openrocket.rocketcomponent.Transition; -import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.TestRockets; import net.sf.openrocket.util.BaseTestCase.BaseTestCase; public class MassCalculatorTest extends BaseTestCase { // tolerance for compared double test results - protected final double EPSILON = 0.000001; + protected final double EPSILON = MathUtil.EPSILON; protected final Coordinate ZERO = new Coordinate(0., 0., 0.); - - public void test() { - // fail("Not yet implemented"); - } - - public Rocket createTestRocket() { - double tubeRadius = 0.1; - // setup - Rocket rocket = new Rocket(); - rocket.setName("Rocket"); - - AxialStage sustainer = new AxialStage(); - sustainer.setName("Sustainer stage"); - RocketComponent sustainerNose = new NoseCone(Transition.Shape.CONICAL, 0.2, tubeRadius); - sustainerNose.setName("Sustainer Nosecone"); - sustainer.addChild(sustainerNose); - RocketComponent sustainerBody = new BodyTube(0.3, tubeRadius, 0.001); - sustainerBody.setName("Sustainer Body "); - sustainer.addChild(sustainerBody); - rocket.addChild(sustainer); - - AxialStage core = new AxialStage(); - core.setName("Core stage"); - rocket.addChild(core); - BodyTube coreBody = new BodyTube(0.6, tubeRadius, 0.001); - coreBody.setName("Core Body "); - core.addChild(coreBody); - FinSet coreFins = new TrapezoidFinSet(4, 0.4, 0.2, 0.2, 0.4); - coreFins.setName("Core Fins"); - coreBody.addChild(coreFins); - - InnerTube motorCluster = new InnerTube(); - motorCluster.setName("Core Motor Cluster"); - // outdated. Just add an actual motor + motor instance - //motorCluster.setMotorMount(true); - motorCluster.setClusterConfiguration(ClusterConfiguration.CONFIGURATIONS[5]); - coreBody.addChild(motorCluster); - - return rocket; - } - - public BoosterSet createBooster() { - double tubeRadius = 0.08; - - BoosterSet booster = new BoosterSet(); - booster.setName("Booster Stage"); - RocketComponent boosterNose = new NoseCone(Transition.Shape.CONICAL, 0.2, tubeRadius); - boosterNose.setName("Booster Nosecone"); - booster.addChild(boosterNose); - RocketComponent boosterBody = new BodyTube(0.2, tubeRadius, 0.001); - boosterBody.setName("Booster Body "); - booster.addChild(boosterBody); - Transition boosterTail = new Transition(); - boosterTail.setName("Booster Tail"); - boosterTail.setForeRadius(tubeRadius); - boosterTail.setAftRadius(0.05); - boosterTail.setLength(0.1); - booster.addChild(boosterTail); - - booster.setInstanceCount(3); - booster.setRadialOffset(0.18); - - return booster; - } - + @Test - public void testTestRocketMasses() { - RocketComponent rocket = createTestRocket(); - String treeDump = rocket.toDebugTree(); + public void testRocketNoMotors() { + Rocket rkt = TestRockets.makeNoMotorRocket(); + rkt.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); + +// String treeDump = rkt.toDebugTree(); +// System.err.println( treeDump); + + // Validate Boosters + MassCalculator mc = new MassCalculator(); + //mc.debug = true; + Coordinate rocketCM = mc.getCM( rkt.getDefaultConfiguration(), MassCalcType.NO_MOTORS); + + double expMass = 0.668984592; + double expCMx = 0.558422219894; + double calcMass = rocketCM.weight; + Coordinate expCM = new Coordinate(expCMx,0,0, expMass); + assertEquals(" Simple Motor Rocket mass incorrect: ", expMass, calcMass, EPSILON); + assertEquals(" Delta Heavy Booster CM.x is incorrect: ", expCM.x, rocketCM.x, EPSILON); + assertEquals(" Delta Heavy Booster CM.y is incorrect: ", expCM.y, rocketCM.y, EPSILON); + assertEquals(" Delta Heavy Booster CM.z is incorrect: ", expCM.z, rocketCM.z, EPSILON); + assertEquals(" Delta Heavy Booster CM is incorrect: ", expCM, rocketCM); + + rocketCM = mc.getCM( rkt.getDefaultConfiguration(), MassCalcType.LAUNCH_MASS); + assertEquals(" Delta Heavy Booster CM is incorrect: ", expCM, rocketCM); + rocketCM = mc.getCM( rkt.getDefaultConfiguration(), MassCalcType.BURNOUT_MASS); + assertEquals(" Delta Heavy Booster CM is incorrect: ", expCM, rocketCM); + } + + @Test + public void testTestComponentMasses() { + Rocket rkt = TestRockets.makeFalcon9Heavy(); + rkt.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); + double expMass; + RocketComponent cc; double compMass; - double calcMass; - expMass = 0.093417755; - compMass = rocket.getChild(0).getChild(0).getComponentMass(); - assertEquals(" NoseCone mass calculated incorrectly: ", expMass, compMass, EPSILON); + // ====== Payload Stage ====== + // ====== ====== ====== ====== + { + expMass = 0.022549558353; + cc= rkt.getChild(0).getChild(0); + compMass = cc.getComponentMass(); + assertEquals("P/L NoseCone mass calculated incorrectly: ", expMass, compMass, EPSILON); + + expMass = 0.02904490372; + cc= rkt.getChild(0).getChild(1); + compMass = cc.getComponentMass(); + assertEquals("P/L Body mass calculated incorrectly: ", expMass, compMass, EPSILON); + + expMass = 0.007289284477103441; + cc= rkt.getChild(0).getChild(2); + compMass = cc.getComponentMass(); + assertEquals("P/L Transition mass calculated incorrectly: ", expMass, compMass, EPSILON); + + expMass = 0.029224351500753608; + cc= rkt.getChild(0).getChild(3); + compMass = cc.getComponentMass(); + assertEquals("P/L Upper Stage Body mass calculated incorrectly: ", expMass, compMass, EPSILON); + { + expMass = 0.0079759509252; + cc= rkt.getChild(0).getChild(3).getChild(0); + compMass = cc.getComponentMass(); + assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON); + + expMass = 0.00072; + cc= rkt.getChild(0).getChild(3).getChild(1); + compMass = cc.getComponentMass(); + assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON); + } + + expMass = 0.01948290100050243; + cc= rkt.getChild(0).getChild(4); + compMass = cc.getComponentMass(); + assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON); + } - expMass = 0.1275360953; - compMass = rocket.getChild(0).getChild(1).getComponentMass(); - assertEquals(" Sustainer Body mass calculated incorrectly: ", expMass, compMass, EPSILON); + // ====== Core Stage ====== + // ====== ====== ====== + { + expMass = 0.1298860066700161; + cc= rkt.getChild(1).getChild(0); + compMass = cc.getComponentMass(); + assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON); + + expMass = 0.21326976; + cc= rkt.getChild(1).getChild(0).getChild(0); + compMass = cc.getComponentMass(); + assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON); + } - expMass = 0.255072190; - compMass = rocket.getChild(1).getChild(0).getComponentMass(); - assertEquals(" Core Body mass calculated incorrectly: ", expMass, compMass, EPSILON); - expMass = 0.9792000; - compMass = rocket.getChild(1).getChild(0).getChild(0).getComponentMass(); - assertEquals(" Core Fins mass calculated incorrectly: ", expMass, compMass, EPSILON); - - InnerTube motorCluster = (InnerTube) rocket.getChild(1).getChild(0).getChild(1); - expMass = 0.0055329; - compMass = motorCluster.getComponentMass(); - assertEquals(" Core Motor Mount Tubes: mass calculated incorrectly: ", expMass, compMass, EPSILON); - compMass = motorCluster.getMass(); - assertEquals(" Core Motor Mount Tubes: mass calculated incorrectly: ", expMass, compMass, EPSILON); - - expMass = 490.061; - // MassCalculator mc = new BasicMassCalculator(); - // calcMass = mc.getMass(); - calcMass = rocket.getMass(); - - assertEquals(" Simple Rocket Mass is incorrect: " + treeDump, expMass, calcMass, EPSILON); + // ====== Booster Set Stage ====== + // ====== ====== ====== + BoosterSet boosters = (BoosterSet) rkt.getChild(1).getChild(1); + { + expMass = 0.01530561538; + cc= boosters.getChild(0); + compMass = cc.getComponentMass(); + assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON); + + expMass = 0.08374229377; + cc= boosters.getChild(1); + compMass = cc.getComponentMass(); + assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON); + + expMass = 0.018906104589303415; + cc= boosters.getChild(1).getChild(0); + compMass = cc.getComponentMass(); + assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON); + } } @Test - public void testTestRocketCG() { - RocketComponent rocket = createTestRocket(); - String treeDump = rocket.toDebugTree(); - double expRelCGx; - double expAbsCGx; - double actualRelCGx; - double actualAbsCGx; + public void testTestComponentMOIs() { + Rocket rkt = TestRockets.makeFalcon9Heavy(); + rkt.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); - expRelCGx = 0.134068822; - actualRelCGx = rocket.getChild(0).getChild(0).getComponentCG().x; - assertEquals(" NoseCone CG calculated incorrectly: ", expRelCGx, actualRelCGx, EPSILON); + double expInertia; + RocketComponent cc; + double compInertia; - expRelCGx = 0.15; - actualRelCGx = rocket.getChild(0).getChild(1).getComponentCG().x; - assertEquals(" Sustainer Body cg calculated incorrectly: ", expRelCGx, actualRelCGx, EPSILON); + // ====== Payload Stage ====== + // ====== ====== ====== ====== + { + expInertia = 3.1698055283e-5; + cc= rkt.getChild(0).getChild(0); + compInertia = cc.getRotationalInertia(); + assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + expInertia = 1.79275e-5; + compInertia = cc.getLongitudinalInertia(); + assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + + cc= rkt.getChild(0).getChild(1); + expInertia = 7.70416e-5; + compInertia = cc.getRotationalInertia(); + assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + expInertia = 8.06940e-5; + compInertia = cc.getLongitudinalInertia(); + assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + + cc= rkt.getChild(0).getChild(2); + expInertia = 1.43691e-5; + compInertia = cc.getRotationalInertia(); + assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + expInertia = 7.30265e-6; + compInertia = cc.getLongitudinalInertia(); + assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + + cc= rkt.getChild(0).getChild(3); + expInertia = 4.22073e-5; + compInertia = cc.getRotationalInertia(); + assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + expInertia = 0.0001; + compInertia = cc.getLongitudinalInertia(); + assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + + { + cc= rkt.getChild(0).getChild(3).getChild(0); + expInertia = 6.23121e-7; + compInertia = cc.getRotationalInertia(); + assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + expInertia = 7.26975e-7; + compInertia = cc.getLongitudinalInertia(); + assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + + cc= rkt.getChild(0).getChild(3).getChild(1); + expInertia = 5.625e-8; + compInertia = cc.getRotationalInertia(); + assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + expInertia = 6.5625e-8; + compInertia = cc.getLongitudinalInertia(); + assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + } + + cc= rkt.getChild(0).getChild(4); + expInertia = 2.81382e-5; + compInertia = cc.getRotationalInertia(); + assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + expInertia = 3.74486e-5; + compInertia = cc.getLongitudinalInertia(); + assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + } - BodyTube coreBody = (BodyTube) rocket.getChild(1).getChild(0); - expRelCGx = 0.3; // relative to parent - actualRelCGx = coreBody.getComponentCG().x; - assertEquals(" Core Body (relative) cg calculated incorrectly: ", expRelCGx, actualRelCGx, EPSILON); - //expAbsCGx = 0.8; - //actualAbsCGx = coreBody.getCG().x; - //assertEquals(" Core Body (absolute) cg calculated incorrectly: ", expAbsCGx, actualAbsCGx, EPSILON); + // ====== Core Stage ====== + // ====== ====== ====== + { + cc= rkt.getChild(1).getChild(0); + expInertia = 0.000187588; + compInertia = cc.getRotationalInertia(); + assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + expInertia = 0.00702105; + compInertia = cc.getLongitudinalInertia(); + assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + + cc= rkt.getChild(1).getChild(0).getChild(0); + expInertia = 0.00734753; + compInertia = cc.getRotationalInertia(); + assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + expInertia = 0.02160236691801411; + compInertia = cc.getLongitudinalInertia(); + assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + } - FinSet coreFins = (FinSet) rocket.getChild(1).getChild(0).getChild(0); - expRelCGx = 0.244444444; // relative to parent - actualRelCGx = coreFins.getComponentCG().x; - assertEquals(" Core Fins (relative) cg calculated incorrectly: ", expRelCGx, actualRelCGx, EPSILON); - // expAbsCGx = 0.9444444444; - // actualAbsCGx = coreBody.getCG().x; - // assertEquals(" Core Fins (absolute) cg calculated incorrectly: ", expAbsCGx, actualAbsCGx, EPSILON); - expRelCGx = 10.061; - // MassCalculator mc = new BasicMassCalculator(); - actualRelCGx = rocket.getCG().x; - // mc.getCG(Configuration configuration, MotorInstanceConfiguration motors) { - // calcMass = mc.getMass(); + // ====== Booster Set Stage ====== + // ====== ====== ====== + BoosterSet boosters = (BoosterSet) rkt.getChild(1).getChild(1); + { + cc= boosters.getChild(0); + expInertia = 5.20107e-6; + compInertia = cc.getRotationalInertia(); + assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + expInertia = 0; + compInertia = cc.getLongitudinalInertia(); + assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + + cc= boosters.getChild(1); + expInertia = 5.02872e-5; + compInertia = cc.getRotationalInertia(); + assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + expInertia = 0.00449140; + compInertia = cc.getLongitudinalInertia(); + assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + + cc= boosters.getChild(1).getChild(0); + expInertia = 4.11444e-6; + compInertia = cc.getRotationalInertia(); + assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + expInertia = 3.75062e-5; + compInertia = cc.getLongitudinalInertia(); + assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); + } + } + @Test + public void testTestBoosterStructureCM() { + Rocket rocket = TestRockets.makeFalcon9Heavy(); + rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); + + BoosterSet boosters = (BoosterSet) rocket.getChild(1).getChild(1); + int boostNum = boosters.getStageNumber(); - assertEquals(" Simple Rocket CG is incorrect: " + treeDump, expRelCGx, actualRelCGx, EPSILON); + rocket.getDefaultConfiguration().setAllStages(false); + rocket.getDefaultConfiguration().setOnlyStage( boostNum); +// String treeDump = rocket.toDebugTree(); +// System.err.println( treeDump); + + // Validate Boosters + MassCalculator mc = new MassCalculator(); + Coordinate boosterSetCM = mc.getCM( rocket.getDefaultConfiguration(), MassCalcType.NO_MOTORS); + + double expMass = 0.23590802751203407; + double expCMx = 0.9615865040919498; + double calcMass = boosterSetCM.weight; + assertEquals(" Delta Heavy Booster Mass is incorrect: ", expMass, calcMass, EPSILON); + + Coordinate expCM = new Coordinate(expCMx,0,0, expMass); + assertEquals(" Delta Heavy Booster CM.x is incorrect: ", expCM.x, boosterSetCM.x, EPSILON); + assertEquals(" Delta Heavy Booster CM.y is incorrect: ", expCM.y, boosterSetCM.y, EPSILON); + assertEquals(" Delta Heavy Booster CM.z is incorrect: ", expCM.z, boosterSetCM.z, EPSILON); + assertEquals(" Delta Heavy Booster CM is incorrect: ", expCM, boosterSetCM); } +// @Test +// public void testBoosterMotorCM() { +// Rocket rocket = TestRockets.makeFalcon9Heavy(); +// FlightConfiguration defaultConfig = rocket.getDefaultConfiguration(); +// FlightConfigurationID fcid = defaultConfig.getFlightConfigurationID(); +// rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); +// +// BoosterSet boosters = (BoosterSet) rocket.getChild(1).getChild(1); +// int boostNum = boosters.getStageNumber(); +// rocket.getDefaultConfiguration().setAllStages(false); +// rocket.getDefaultConfiguration().setOnlyStage( boostNum); +//// String treeDump = rocket.toDebugTree(); +//// System.err.println( treeDump); +// +// // Validate Boosters +// InnerTube inner = (InnerTube)boosters.getChild(1).getChild(0); +// MassCalculator mc = new MassCalculator(); +// +// double innerStartX = inner.getLocations()[0].x; +// double innerLength = inner.getLength(); +// MotorInstance moto= inner.getMotorInstance(fcid); +// double motorStart = innerStartX + innerLength + inner.getMotorOverhang() - moto.getMotor().getLength(); +// +// int instanceCount = boosters.getInstanceCount()*inner.getInstanceCount(); +// assertEquals(" Total engine count is incorrect: ", 8, instanceCount); +// +// // LAUNCH +// { +// +// Coordinate motorCM = mc.getMotorCG( rocket.getDefaultConfiguration(), MassCalcType.LAUNCH_MASS); +// +// double expMotorMass = 0.123*instanceCount; +// double actMotorMass = motorCM.weight; +// assertEquals(" Booster motor launch mass is incorrect: ", expMotorMass, actMotorMass, EPSILON); +// +// double expMotorCMX = motorStart + moto.getMotor().getLaunchCG().x; +// Coordinate expMotorCM = new Coordinate(expMotorCMX, 0, 0, expMotorMass); +// assertEquals(" Booster Motor CM.x is incorrect: ", expMotorCM.x, motorCM.x, EPSILON); +// assertEquals(" Booster Motor CM.y is incorrect: ", expMotorCM.y, motorCM.y, EPSILON); +// assertEquals(" Booster Motor CM.z is incorrect: ", expMotorCM.z, motorCM.z, EPSILON); +// assertEquals(" Booster Motor CM is incorrect: ", expMotorCM, motorCM ); +// } +// +// +// // EMPTY / BURNOUT +// { +// Coordinate motorCM = mc.getMotorCG( rocket.getDefaultConfiguration(), MassCalcType.BURNOUT_MASS); +// +// double expMotorMass = 0.064*instanceCount; +// double actMotorMass = motorCM.weight; +// assertEquals(" Booster motor burnout mass is incorrect: ", expMotorMass, actMotorMass, EPSILON); +// +// // vvvv DEVEL vvvv +//// System.err.println("\n ====== ====== "); +//// System.err.println(String.format(" final position: %g = %g innerTube start", motorStart, innerStart )); +//// System.err.println(String.format(" + %g innerTube Length ", innerLength )); +//// System.err.println(String.format(" + %g overhang", inner.getMotorOverhang() )); +//// System.err.println(String.format(" - %g motor length", moto.getMotor().getLength() )); +//// double motorOffs = innerLength + inner.getMotorOverhang() - moto.getMotor().getLength(); +//// System.err.println(String.format(" [ %g motor Offset ]", motorOffs )); +//// System.err.println(String.format(" [ %g motor CM ]", moto.getMotor().getEmptyCG().x)); +// // ^^^^ DEVEL ^^^^ +// +// double expCMX = motorStart + moto.getMotor().getEmptyCG().x; +// Coordinate expMotorCM = new Coordinate(expCMX, 0, 0, expMotorMass); +// assertEquals(" Booster Motor CM.x is incorrect: ", expMotorCM.x, motorCM.x, EPSILON); +// assertEquals(" Booster Motor CM.y is incorrect: ", expMotorCM.y, motorCM.y, EPSILON); +// assertEquals(" Booster Motor CM.z is incorrect: ", expMotorCM.z, motorCM.z, EPSILON); +// assertEquals(" Booster Motor CM is incorrect: ", expMotorCM, motorCM ); +// } +// } + @Test + public void testBoosterTotalCM() { + Rocket rocket = TestRockets.makeFalcon9Heavy(); + rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); + + BoosterSet boosters = (BoosterSet) rocket.getChild(1).getChild(1); + int boostNum = boosters.getStageNumber(); + rocket.getDefaultConfiguration().setAllStages(false); + rocket.getDefaultConfiguration().setOnlyStage( boostNum); + + //String treeDump = rocket.toDebugTree(); + //System.err.println( treeDump); + { + // Validate Booster Launch Mass + MassCalculator mc = new MassCalculator(); + Coordinate boosterSetCM = mc.getCM( rocket.getDefaultConfiguration(), MassCalcType.LAUNCH_MASS); + double calcTotalMass = boosterSetCM.weight; + + double expTotalMass = 1.219908027512034; + double expX = 1.2461238889997992; + Coordinate expCM = new Coordinate(expX,0,0, expTotalMass); + assertEquals(" Booster Launch Mass is incorrect: ", expTotalMass, calcTotalMass, EPSILON); + assertEquals(" Booster Launch CM.x is incorrect: ", expCM.x, boosterSetCM.x, EPSILON); + assertEquals(" Booster Launch CM.y is incorrect: ", expCM.y, boosterSetCM.y, EPSILON); + assertEquals(" Booster Launch CM.z is incorrect: ", expCM.z, boosterSetCM.z, EPSILON); + assertEquals(" Booster Launch CM is incorrect: ", expCM, boosterSetCM); + } + { + // Validate Booster Burnout Mass + MassCalculator mc = new MassCalculator(); + Coordinate boosterSetCM = mc.getCM( rocket.getDefaultConfiguration(), MassCalcType.BURNOUT_MASS); + double calcTotalMass = boosterSetCM.weight; + + double expTotalMass = 0.7479080275020341; + assertEquals(" Booster Launch Mass is incorrect: ", expTotalMass, calcTotalMass, EPSILON); + + double expX = 1.2030731351529202; + Coordinate expCM = new Coordinate(expX,0,0, expTotalMass); + assertEquals(" Booster Launch CM.x is incorrect: ", expCM.x, boosterSetCM.x, EPSILON); + assertEquals(" Booster Launch CM.y is incorrect: ", expCM.y, boosterSetCM.y, EPSILON); + assertEquals(" Booster Launch CM.z is incorrect: ", expCM.z, boosterSetCM.z, EPSILON); + assertEquals(" Booster Launch CM is incorrect: ", expCM, boosterSetCM); + } + } + + @Test + public void testTestBoosterStructureMOI() { + Rocket rocket = TestRockets.makeFalcon9Heavy(); + rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); + FlightConfiguration defaultConfig = rocket.getDefaultConfiguration(); + + BoosterSet boosters = (BoosterSet) rocket.getChild(1).getChild(1); + int boostNum = boosters.getStageNumber(); + + rocket.getDefaultConfiguration().setAllStages(false); + rocket.getDefaultConfiguration().setOnlyStage( boostNum); +// String treeDump = rocket.toDebugTree(); +// System.err.println( treeDump); + + // Validate Boosters + MassCalculator mc = new MassCalculator(); + //mc.debug = true; + double expMOI_axial = .00144619; + double boosterMOI_xx= mc.getRotationalInertia( defaultConfig, MassCalcType.NO_MOTORS); + assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON); + + double expMOI_tr = 0.01845152840733412; + double boosterMOI_tr= mc.getLongitudinalInertia( defaultConfig, MassCalcType.NO_MOTORS); + assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); + } + + @Test + public void testBoosterTotalMOI() { + Rocket rocket = TestRockets.makeFalcon9Heavy(); + FlightConfiguration defaultConfig = rocket.getDefaultConfiguration(); + rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); + + BoosterSet boosters = (BoosterSet) rocket.getChild(1).getChild(1); + int boostNum = boosters.getStageNumber(); + + rocket.getDefaultConfiguration().setAllStages(false); + rocket.getDefaultConfiguration().setOnlyStage( boostNum); + //String treeDump = rocket.toDebugTree(); + //System.err.println( treeDump); + + // Validate Boosters + MassCalculator mc = new MassCalculator(); + mc.debug = true; + double expMOI_axial = 0.00752743; + double boosterMOI_xx= mc.getRotationalInertia( defaultConfig, MassCalcType.LAUNCH_MASS); + + double expMOI_tr = 0.0436639; + double boosterMOI_tr= mc.getLongitudinalInertia( defaultConfig, MassCalcType.LAUNCH_MASS); + + assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON); + assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); + } } diff --git a/core/test/net/sf/openrocket/masscalc/MassDataTest.java b/core/test/net/sf/openrocket/masscalc/MassDataTest.java new file mode 100644 index 000000000..4fe3caca8 --- /dev/null +++ b/core/test/net/sf/openrocket/masscalc/MassDataTest.java @@ -0,0 +1,190 @@ +package net.sf.openrocket.masscalc; + +//import junit.framework.TestCase; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.BaseTestCase.BaseTestCase; + +public class MassDataTest extends BaseTestCase { + + // tolerance for compared double test results + protected final double EPSILON = MathUtil.EPSILON; + + protected final Coordinate ZERO = new Coordinate(0., 0., 0.); + + @Test + public void testTwoPointInline() { + double m1 = 2.5; + Coordinate r1 = new Coordinate(0,-40, 0, m1); + double I1ax=28.7; + double I1t = I1ax/2; + MassData body1 = new MassData(r1, I1ax, I1t); + + double m2 = 5.7; + Coordinate r2 = new Coordinate(0, 32, 0, m2); + double I2ax=20; + double I2t = I2ax/2; + MassData body2 = new MassData(r2, I2ax, I2t); + + // point 3 is defined as the CM of bodies 1 and 2 combined. + MassData asbly3 = body1.add(body2); + + Coordinate cm3_expected = r1.average(r2); + assertEquals(" Center of Mass calculated incorrectly: ", cm3_expected, asbly3.getCM() ); + + // these are a bit of a hack, and depend upon all the bodies being along the y=0, z=0 line. + Coordinate delta13 = asbly3.getCM().sub( r1); + Coordinate delta23 = asbly3.getCM().sub( r2); + + double y13 = delta13.y; + double dy_13_2 = MathUtil.pow2( y13); // hack + double I13ax = I1ax + m1*dy_13_2; + double I13zz = I1t + m1*dy_13_2; + + double y23 = delta23.y; + double dy_23_2 = MathUtil.pow2( y23); // hack + double I23ax = I2ax + m2*dy_23_2 ; + double I23zz = I2t + m2*dy_23_2 ; + + double expI3xx = I13ax+I23ax; + double expI3yy = I1t+I2t; + double expI3zz = I13zz+I23zz; + + assertEquals("x-axis MOI don't match: ", asbly3.getIxx(), expI3xx, EPSILON*10); + + assertEquals("y-axis MOI don't match: ", asbly3.getIyy(), expI3yy, EPSILON*10); + + assertEquals("z-axis MOI don't match: ", asbly3.getIzz(), expI3zz, EPSILON*10); + } + + + @Test + public void testTwoPointGeneral() { + boolean debug=false; + double m1 = 2.5; + Coordinate r1 = new Coordinate(0,-40, -10, m1); + double I1xx=28.7; + double I1t = I1xx/2; + MassData body1 = new MassData(r1, I1xx, I1t); + + double m2 = 5.7; + Coordinate r2 = new Coordinate(0, 32, 15, m2); + double I2xx=20; + double I2t = I2xx/2; + MassData body2 = new MassData(r2, I2xx, I2t); + + // point 3 is defined as the CM of bodies 1 and 2 combined. + MassData asbly3 = body1.add(body2); + + Coordinate cm3_expected = r1.average(r2); +// System.err.println(" @(1): "+ body1.toDebug()); +// System.err.println(" @(2): "+ body2.toDebug()); +// System.err.println(" @(3): "+ asbly3.toDebug()); + System.err.println(" Center of Mass: (3) expected: "+ cm3_expected); + assertEquals(" Center of Mass calculated incorrectly: ", cm3_expected, asbly3.getCM() ); + + + if(debug){ + System.err.println(" Body 1: "+ body1.toDebug() ); + System.err.println(" Body 2: "+ body2.toDebug() ); + System.err.println(" Body 3: "+ asbly3.toDebug() ); + } + + + // these are a bit of a hack, and depend upon all the bodies being along the y=0, z=0 line. + Coordinate delta13 = asbly3.getCM().sub( r1); + Coordinate delta23 = asbly3.getCM().sub( r2); + double x2, y2, z2; + + x2 = MathUtil.pow2( delta13.x); + y2 = MathUtil.pow2( delta13.y); + z2 = MathUtil.pow2( delta13.z); + double I13xx = I1xx + m1*(y2+z2); + double I13yy = I1t + m1*(x2+z2); + double I13zz = I1t + m1*(x2+y2); + + x2 = MathUtil.pow2( delta23.x); + y2 = MathUtil.pow2( delta23.y); + z2 = MathUtil.pow2( delta23.z); + double I23xx = I2xx + m2*(y2+z2); + double I23yy = I2t + m2*(x2+z2); + double I23zz = I2t + m2*(x2+y2); + + double expI3xx = I13xx + I23xx; + assertEquals("x-axis MOI don't match: ", asbly3.getIxx(), expI3xx, EPSILON*10); + + double expI3yy = I13yy + I23yy; + assertEquals("y-axis MOI don't match: ", asbly3.getIyy(), expI3yy, EPSILON*10); + + double expI3zz = I13zz + I23zz; + assertEquals("z-axis MOI don't match: ", asbly3.getIzz(), expI3zz, EPSILON*10); + } + + + @Test + public void testMassDataCompoundCalculations() { + double m1 = 2.5; + Coordinate r1 = new Coordinate(0,-40, 0, m1); + double I1ax=28.7; + double I1t = I1ax/2; + MassData body1 = new MassData(r1, I1ax, I1t); + + double m2 = m1; + Coordinate r2 = new Coordinate(0, -2, 0, m2); + double I2ax=28.7; + double I2t = I2ax/2; + MassData body2 = new MassData(r2, I2ax, I2t); + + double m5 = 5.7; + Coordinate r5 = new Coordinate(0, 32, 0, m5); + double I5ax=20; + double I5t = I5ax/2; + MassData body5 = new MassData(r5, I5ax, I5t); + + // point 3 is defined as the CM of bodies 1 and 2 combined. + MassData asbly3 = body1.add(body2); + + // point 4 is defined as the CM of bodies 1, 2 and 5 combined. + MassData asbly4_indirect = asbly3.add(body5); + Coordinate cm4_expected = r1.average(r2).average(r5); + + //System.err.println(" Center of Mass: (3): "+ asbly3.toCMDebug() ); + //System.err.println(" MOI: (3): "+ asbly3.toIMDebug() ); + //System.err.println(" Center of Mass: indirect:"+ asbly4_indirect.getCM() ); + //System.err.println(" Center of Mass: (4) direct: "+ cm4_expected); + assertEquals(" Center of Mass calculated incorrectly: ", cm4_expected, new Coordinate( 0, 7.233644859813085, 0, m1+m2+m5 ) ); + + // these are a bit of a hack, and depend upon all the bodies being along the y=0, z=0 line. + double y4 = cm4_expected.y; + double I14ax = I1ax + m1*MathUtil.pow2( Math.abs(body1.getCM().y - y4) ); + double I24ax = I2ax + m2*MathUtil.pow2( Math.abs(body2.getCM().y - y4) ); + double I54ax = I5ax + m5*MathUtil.pow2( Math.abs(body5.getCM().y - y4) ); + + double I14zz = I1t + m1*MathUtil.pow2( Math.abs(body1.getCM().y - y4) ); + double I24zz = I2t + m2*MathUtil.pow2( Math.abs(body2.getCM().y - y4) ); +// System.err.println(String.format(" I24yy: %8g = %6g + %3g*%g", I24zz, I2t, m2, MathUtil.pow2( Math.abs(body2.getCM().y - y4)) )); +// System.err.println(String.format(" : delta y24: %8g = ||%g - %g||", Math.abs(body2.getCM().y - y4), body2.getCM().y, y4 )); + double I54zz = I5t + m5*MathUtil.pow2( Math.abs(body5.getCM().y - y4) ); + + double I4xx = I14ax+I24ax+I54ax; + double I4yy = I1t+I2t+I5t; + double I4zz = I14zz+I24zz+I54zz; + MassData asbly4_expected = new MassData( cm4_expected, I4xx, I4yy, I4zz); + //System.err.println(String.format(" Ixx: direct: %12g", I4xx )); + assertEquals("x-axis MOI don't match: ", asbly4_indirect.getIxx(), asbly4_expected.getIxx(), EPSILON*10); + + //System.err.println(String.format(" Iyy: direct: %12g", I4yy )); + assertEquals("y-axis MOI don't match: ", asbly4_indirect.getIyy(), asbly4_expected.getIyy(), EPSILON*10); + + //System.err.println(String.format(" Izz: direct: %12g", I4zz)); + assertEquals("z-axis MOI don't match: ", asbly4_indirect.getIzz(), asbly4_expected.getIzz(), EPSILON*10); + } + + +}