From 04c0914d0a69e7ce7d615ba5bd791d8d854fd51a Mon Sep 17 00:00:00 2001 From: Daniel_M_Williams Date: Fri, 1 Jul 2016 16:26:02 -0400 Subject: [PATCH] [bugfix] Overhauled MassCalculator Methods. - Rocket total mass is split into 'dryMass' and 'propellantMass' -- each mass type has a corresponding calculation method in MassCalculator - Calculating Inertias and Center-of-Mass FOR MOTORS assume: - time-invariant x-coordinate - time-decreasing density - mass correctly tracks propellant usage, as measured by thrustCurves - elimated MassCalcType enum: was not actually solving a problem. - simply use motorTime: 0 for launch Double.MAX_VALUE for burnout - NO_MOTORS is represented by a configuration without attached motors -- try: "rocket.setSelectedConfiguration( rocket.getEmptyConfiguration())" - 'dry mass' vs 'total mass': 90% of the time, a caller wanted 'total mass' - total_mass = dry_mass + propellant_mass - mass @ simulation time wasn't represented by the enum, had existing overloads anyway - get vs calculate methods: -- gets revalidate the cache, then retreive specific information -- calculate simple calculate the desired information, and ignore the cache. --- I'm particularly confident about cache reliability: particularly as it doesn't account for changing time during simulation. - reduced / simplified debugging messages --- .../file/rocksim/export/RocksimSaver.java | 4 +- .../openrocket/masscalc/MassCalculator.java | 594 +++++++++--------- .../net/sf/openrocket/masscalc/MassData.java | 15 +- core/src/net/sf/openrocket/motor/Motor.java | 15 +- .../sf/openrocket/motor/ThrustCurveMotor.java | 41 +- .../motor/ThrustCurveMotorPlaceholder.java | 24 +- .../domains/StabilityDomain.java | 3 +- .../parameters/StabilityParameter.java | 4 +- .../rocketcomponent/AxialStage.java | 22 +- .../openrocket/rocketcomponent/BodyTube.java | 5 + .../rocketcomponent/FlightConfiguration.java | 4 +- .../openrocket/rocketcomponent/InnerTube.java | 37 -- .../rocketcomponent/MotorMount.java | 12 + .../rocketcomponent/ParallelStage.java | 16 - .../sf/openrocket/rocketcomponent/Rocket.java | 17 +- .../rocketcomponent/RocketComponent.java | 80 ++- .../rocketcomponent/RocketUtils.java | 8 - .../simulation/AbstractSimulationStepper.java | 41 +- .../simulation/BasicLandingStepper.java | 2 +- .../simulation/BasicTumbleStepper.java | 3 +- .../simulation/MotorClusterState.java | 8 + .../simulation/RK4SimulationStepper.java | 54 +- .../simulation/SimulationStatus.java | 3 +- .../listeners/SimulationListenerHelper.java | 2 +- .../net/sf/openrocket/util/TestRockets.java | 42 +- .../masscalc/MassCalculatorTest.java | 413 ++++++++---- .../motor/ThrustCurveMotorTest.java | 4 +- .../FlightConfigurationTest.java | 16 +- .../gui/dialogs/ComponentAnalysisDialog.java | 5 +- .../sf/openrocket/gui/print/DesignReport.java | 9 +- .../gui/scalefigure/RocketPanel.java | 7 +- .../net/sf/openrocket/IntegrationTest.java | 38 +- 32 files changed, 904 insertions(+), 644 deletions(-) diff --git a/core/src/net/sf/openrocket/file/rocksim/export/RocksimSaver.java b/core/src/net/sf/openrocket/file/rocksim/export/RocksimSaver.java index 0dfc58792..099cc2448 100644 --- a/core/src/net/sf/openrocket/file/rocksim/export/RocksimSaver.java +++ b/core/src/net/sf/openrocket/file/rocksim/export/RocksimSaver.java @@ -94,8 +94,8 @@ public class RocksimSaver extends RocketSaver { MassCalculator massCalc = new MassCalculator(); - final FlightConfiguration configuration = rocket.getSelectedConfiguration(); - final double cg = massCalc.getCG(configuration, MassCalculator.MassCalcType.NO_MOTORS).x * + final FlightConfiguration configuration = rocket.getEmptyConfiguration(); + final double cg = massCalc.getRocketSpentMassData(configuration).getCM().x * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH; int stageCount = rocket.getStageCount(); diff --git a/core/src/net/sf/openrocket/masscalc/MassCalculator.java b/core/src/net/sf/openrocket/masscalc/MassCalculator.java index ea589a460..f39bab384 100644 --- a/core/src/net/sf/openrocket/masscalc/MassCalculator.java +++ b/core/src/net/sf/openrocket/masscalc/MassCalculator.java @@ -4,9 +4,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.MotorConfiguration; import net.sf.openrocket.rocketcomponent.AxialStage; @@ -17,74 +14,27 @@ import net.sf.openrocket.rocketcomponent.ParallelStage; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.simulation.MotorClusterState; import net.sf.openrocket.simulation.SimulationStatus; -import net.sf.openrocket.util.BugException; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.Monitorable; public class MassCalculator implements Monitorable { + +// public static enum MassCalcType { +// NO_MOTORS( Double.NaN), +// LAUNCH_MASS(0.), +// BURNOUT_MASS(Double.MAX_VALUE); +// +// public final double motorTime; +// +// MassCalcType( final double _motorTime ){ +// this.motorTime = _motorTime; } +// +// }; - public static enum MassCalcType { - NO_MOTORS { - @Override - public double getCGx(Motor motor) { - return 0; - } - @Override - public double getMass(Motor motor) { - return 0; - } - }, - LAUNCH_MASS { - @Override - public double getCGx(Motor motor) { - return motor.getLaunchCGx(); - } - @Override - public double getMass(Motor motor) { - return motor.getLaunchMass(); - } - }, - BURNOUT_MASS { - @Override - public double getCGx(Motor motor) { - return motor.getBurnoutCGx(); - } - @Override - public double getMass(Motor motor) { - return motor.getBurnoutMass(); - } - }; - - public abstract double getMass(Motor motor); - public abstract double getCGx(Motor motor); - - /** - * Compute the cg contribution of the motor relative to the rocket's coordinates - * - * @param motorConfig - * @return - */ - public Coordinate getCG(MotorConfiguration motorConfig) { - Motor mtr = motorConfig.getMotor(); - double mass = getMass(mtr); - Coordinate cg = motorConfig.getPosition().add( getCGx(mtr), 0, 0, mass); - - RocketComponent motorMount = (RocketComponent) motorConfig.getMount(); - Coordinate totalCM = new Coordinate(); - for (Coordinate cord : motorMount.toAbsolute(cg) ) { - totalCM = totalCM.average(cord); - } - - return totalCM; - } - - public Coordinate getCM( Motor motor ){ - return new Coordinate( getCGx(motor), 0, 0, getMass(motor)); - } - } + //private static final Logger log = LoggerFactory.getLogger(MassCalculator.class); - private static final Logger log = LoggerFactory.getLogger(MassCalculator.class); + public boolean debug=false; public static final double MIN_MASS = 0.001 * MathUtil.EPSILON; @@ -96,14 +46,10 @@ public class MassCalculator implements Monitorable { * Cached data. All CG data is in absolute coordinates. All moments of inertia * are relative to their respective CG. */ - private HashMap< Integer, MassData> cache = new HashMap(); - // private MassData dryData = null; - // private MassData launchData = null; - // private Vector< MassData> motorData = new Vector(); + private HashMap< Integer, MassData> stageMassCache = new HashMap(); + private MassData rocketSpentMassCache; + private MassData propellantMassCache; - // this turns on copious amounts of debug. Recommend leaving this false - // until reaching code that causes troublesome conditions. - public boolean debug = false; ////////////////// Constructors /////////////////// public MassCalculator() { @@ -111,60 +57,189 @@ public class MassCalculator implements Monitorable { ////////////////// Mass property calculations /////////////////// - - /** - * Return the CG of the rocket with the specified motor status (no motors, - * ignition, burnout). - * - * @param configuration the rocket configuration - * @param type the state of the motors (none, launch mass, burnout mass) - * @return the CG of the configuration - */ - public Coordinate getCG(FlightConfiguration configuration, MassCalcType type) { - return getCM( configuration, type); + + public MassData getRocketSpentMassData( final FlightConfiguration config ){ + revalidateCache( config); + return this.rocketSpentMassCache; } - public Coordinate getCM(FlightConfiguration config, MassCalcType type) { - checkCache(config); - calculateStageCache(config); + + public MassData getRocketLaunchMassData( final FlightConfiguration config ){ + revalidateCache( config); + return rocketSpentMassCache.add( propellantMassCache ); + } + + + /** calculates the massdata for all propellant in the rocket given the simulation status. + * + * @param status CurrentSimulation status to calculate data with + * @return combined mass data for all propellant + */ + public MassData getPropellantMassData( final SimulationStatus status ){ + revalidateCache( status ); - // Stage contribution - 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); + return propellantMassCache; + } + + + /** calculates the massdata @ launch for all propellant in the rocket + * + * @param status CurrentSimulation status to calculate data with + * @return combined mass data for all propellant + */ + protected MassData calculatePropellantMassData( final FlightConfiguration config ){ + MassData allPropellantData = MassData.ZERO_DATA; + + if(debug){// vvvv DEVEL vvvv + System.err.println("====== ====== calculatePropellantMassData( config: "+config.toDebug()+" ) ====== ====== ====== ====== ====== ======"); + //String massFormat = " [%2s]: %-16s %6s x %6s = %6s += %6s @ (%s, %s, %s )"; + //System.err.println(String.format(massFormat, " #", "","Mass","Count","Config","Sum", "x","y","z")); + String inertiaFormat = " [%2s](%2s): %-16s %6s %6s"; + System.err.println(String.format(inertiaFormat, " #","ct", "","I_ax","I_tr")); + }// ^^^^ DEVEL ^^^^ + + + Collection activeMotorList = config.getActiveMotors(); + for (MotorConfiguration mtrConfig : activeMotorList ) { + MassData curMotorConfigData = calculateClusterPropellantData( mtrConfig, Motor.PSEUDO_TIME_LAUNCH ); + allPropellantData = allPropellantData.add( curMotorConfigData ); } + return allPropellantData; + } + + /** calculates the massdata @ launch for all propellant in the rocket + * + * @param status CurrentSimulation status to calculate data with + * @return combined mass data for all propellant + */ + protected MassData calculatePropellantMassData( final SimulationStatus status ){ + MassData allPropellantData = MassData.ZERO_DATA; + + if(debug){// vvvv DEVEL vvvv + System.err.println("====== ====== calculatePropellantMassData( status.config: "+status.getConfiguration().toDebug()+" ) ====== ====== ====== ====== ====== ======"); + //String massFormat = " [%2s]: %-16s %6s x %6s = %6s += %6s @ (%s, %s, %s )"; + //System.err.println(String.format(massFormat, " #", "","Mass","Count","Config","Sum", "x","y","z")); + String inertiaFormat = " [%2s](%2s): %-16s %6s %6s"; + System.err.println(String.format(inertiaFormat, " #","ct", "","I_ax","I_tr")); + }// ^^^^ DEVEL ^^^^ - Coordinate totalCM=null; - if( MassCalcType.NO_MOTORS == type ){ - totalCM = dryCM; - }else{ - MassData motorData = getMotorMassData(config, type); - Coordinate motorCM = motorData.getCM(); - totalCM = dryCM.average(motorCM); + + Collection motorStates = status.getActiveMotors(); + for (MotorClusterState state: motorStates) { + final double motorTime = state.getMotorTime( status.getSimulationTime() ); + + MassData clusterPropData = calculateClusterPropellantData( state.getConfig(), motorTime ); + + allPropellantData = allPropellantData.add( clusterPropData); } + + return allPropellantData; + } + + // helper method to calculate the propellant mass data for a given motor cluster( i.e. MotorConfiguration) + private MassData calculateClusterPropellantData( final MotorConfiguration mtrConfig, final double motorTime ){ + final Motor mtr = mtrConfig.getMotor(); + final MotorMount mnt = mtrConfig.getMount(); + final int instanceCount = mnt.getInstanceCount(); - return totalCM; + // location of mount, w/in entire rocket + final Coordinate[] locations = mnt.getLocations(); + final double motorXPosition = mtrConfig.getX(); // location of motor from mount + + final double propMassEach = mtr.getPropellantMass( motorTime ); + final double propCMxEach = mtr.getCMx( motorTime); // CoM from beginning of motor + + // coordinates in rocket frame; Ir, It about CoM. + final Coordinate curClusterCM = new Coordinate( locations[0].x + motorXPosition + propCMxEach, 0, 0, propMassEach*instanceCount); + + final double unitRotationalInertiaEach = mtrConfig.getUnitRotationalInertia(); + final double unitLongitudinalInertiaEach = mtrConfig.getUnitLongitudinalInertia(); + double Ir=unitRotationalInertiaEach*instanceCount*propMassEach; + double It=unitLongitudinalInertiaEach*instanceCount*propMassEach; + + if(debug){ + System.err.println(String.format(" Motor: %-16s ( %2dx): m: %6.4f l: %6.4f od: %6.4f I_xx_u: %6.4g I_yy_u: %6.4g", + mtr.getDesignation(), instanceCount, propMassEach, mtr.getLength(), mtr.getDiameter(), unitRotationalInertiaEach, unitLongitudinalInertiaEach)); + }// ^^^^ DEVEL ^^^^ + + if( 1 < instanceCount ){ + // if not on rocket centerline, then add an offset factor, according to the parallel axis theorem: + for( Coordinate coord : locations ){ + double distance = Math.hypot( coord.y, coord.z); + Ir += propMassEach*Math.pow( distance, 2); + } + } + if(debug){ + System.err.println(String.format(" :cluster: m: %6.4f Ixx: %6.4g Iyy: %6.4g", curClusterCM.weight, Ir, It)); + } + + return new MassData( curClusterCM, Ir, It); } /** - * Compute the CM of all motors, given a configuration and type + * Calculates mass data of the rocket burnout mass * - * @param configuration the rocket configuration - * @param motors the motor configuration + * I.O.W., this mass data is invariant during thrust (see also: calculatePropellantMassData(...) ) + * + * @param configuration a given rocket configuration * @return the CG of the configuration */ - public MassData getMotorMassData(FlightConfiguration config, MassCalcType type) { - if( MassCalcType.NO_MOTORS == type ){ - return MassData.ZERO_DATA; - } + protected MassData calculateBurnoutMassData( final FlightConfiguration config) { + if(debug){// vvvv DEVEL vvvv + //String massFormat = " [%2s]: %-16s %6s x %6s = %6s += %6s @ (%s, %s, %s )"; + String inertiaFormat = " [%2s](%2s): %-16s %6s %6s"; + System.err.println("====== ====== getMotorMassData( config:"+config.toDebug()+" ) ====== ====== ====== ====== ====== ======"); + //System.err.println(String.format(massFormat, " #", "","Mass","Count","Config","Sum", "x","y","z")); + System.err.println(String.format(inertiaFormat, " #","ct", "","I_ax","I_tr")); + }// ^^^^ DEVEL ^^^^ + MassData exceptMotorsMassData = calculateStageData( config); + + if(debug){// vvvv DEVEL vvvv + System.err.println(" exc motors stage data: "+exceptMotorsMassData ); + System.err.println(" ====== ====== ^^^^ stage data ^^^^ ====== ======\n"); + System.err.println(" ====== ====== vvvv motor spent mass data vvvv ====== ======\n"); + }// ^^^^ DEVEL ^^^^ + + MassData motorMassData = calculateMotorBurnoutMassData( config); + + if(debug){ // vvvv DEVEL vvvv + System.err.println(" exc motors stage data: "+motorMassData); + System.err.println(" ====== ====== ^^^^ motor spent mass data ^^^^ ====== ======\n\n"); + } // ^^^^ DEVEL ^^^^ + + return exceptMotorsMassData.add( motorMassData ); + } + + private MassData calculateStageData( final FlightConfiguration config ){ + MassData componentData = MassData.ZERO_DATA; + + // Stages + for (AxialStage stage : config.getActiveStages()) { + int stageNumber = stage.getStageNumber(); + + MassData stageData = this.calculateAssemblyMassData( stage ); + + stageMassCache.put(stageNumber, stageData); + + componentData = componentData.add(stageData); + } + + return componentData; + } + + + /** + * Compute the burnout mass properties all motors, given a configuration + * + * Will calculate data for:MassCalcType.BURNOUT_MASS + * + * @param configuration the rocket configuration + * @return the MassData struct of the motors at burnout + */ + private MassData calculateMotorBurnoutMassData(FlightConfiguration config) { // // vvvv DEVEL vvvv // //String massFormat = " [%2s]: %-16s %6s x %6s = %6s += %6s @ (%s, %s, %s )"; // String inertiaFormat = " [%2s](%2s): %-16s %6s %6s"; @@ -176,163 +251,64 @@ public class MassCalculator implements Monitorable { // // ^^^^ DEVEL ^^^^ MassData allMotorData = MassData.ZERO_DATA; + //int motorIndex = 0; for (MotorConfiguration mtrConfig : config.getActiveMotors() ) { Motor mtr = (Motor) mtrConfig.getMotor(); - MotorMount mount = mtrConfig.getMount(); - RocketComponent mountComp = (RocketComponent)mount; - Coordinate[] locations = mountComp.getLocations(); // location of mount, w/in entire rocket + + // use 'mount.getLocations()' because: + // 1) includes ALL clustering sources! + // 2) location of mount, w/in entire rocket + // 3) Note: mount.getInstanceCount() ONLY indicates instancing of the mount's cluster, not parent components (such as stages) + Coordinate[] locations = mount.getLocations(); int instanceCount = locations.length; double motorXPosition = mtrConfig.getX(); // location of motor from mount + final double burnoutMassEach = mtr.getBurnoutMass(); + final double burnoutCMx = mtr.getBurnoutCGx(); // CoM from beginning of motor - Coordinate localCM = type.getCM( mtr ); // CoM from beginning of motor - localCM = localCM.setWeight( localCM.weight * instanceCount); - // a *bit* hacky :P - Coordinate curMotorCM = localCM.setX( localCM.x + locations[0].x + motorXPosition ); + final Coordinate clusterCM = new Coordinate( locations[0].x + motorXPosition + burnoutCMx, 0, 0, burnoutMassEach*instanceCount); - // alternate version: - // double Ir = inst.getRotationalInertia(); - // double It = inst.getLongitudinalInertia(); - // + - // + Coordinate curMotorCM = type.getCG(inst); + final double unitRotationalInertia = mtrConfig.getUnitRotationalInertia(); + final double unitLongitudinalInertia = mtrConfig.getUnitLongitudinalInertia(); + + if(debug){// vv DEBUG + System.err.println(String.format(" Processing f/mount: %s [%8s] (ct: %d)(w/spent mass = %g)", mtrConfig.getMount(), mtr.getDesignation(), instanceCount, mtr.getBurnoutMass())); + double eachIxx = unitRotationalInertia*burnoutMassEach; + double eachIyy = unitLongitudinalInertia*burnoutMassEach; + System.err.println(String.format("(MOI: [%8g, %8g])", eachIxx, eachIyy)); + } // ^^ DEBUG - double motorMass = curMotorCM.weight; - double Ir_single = mtrConfig.getUnitRotationalInertia()*motorMass; - double It_single = mtrConfig.getUnitLongitudinalInertia()*motorMass; - double Ir=0; - double It=0; - if( 1 == instanceCount ){ - Ir=Ir_single; - It=It_single; - }else{ - It = It_single * instanceCount; + double Ir=(unitRotationalInertia*burnoutMassEach)*instanceCount; + double It=(unitLongitudinalInertia*burnoutMassEach)*instanceCount; + if( 1 < instanceCount ){ + if(debug){// vv DEBUG + System.err.println(String.format(" Instanced. %d motors in a %s", instanceCount, mount.getClusterConfiguration().getXMLName())); + System.err.println(String.format(" I_long: %6g * %6g * %d = %6g ", unitLongitudinalInertia, burnoutMassEach, instanceCount, It)); + System.err.println(String.format(" I_rot_base: %6g * %6g * %d = %6g ", unitRotationalInertia, burnoutMassEach, instanceCount, Ir)); + } // ^^ DEBUG - Ir = Ir_single*instanceCount; - // these need more complex instancing code... for( Coordinate coord : locations ){ - double distance = Math.hypot( coord.y, coord.z); - Ir += motorMass*Math.pow( distance, 2); + double distance_squared = ((coord.y*coord.y) + (coord.z*coord.z)); + double instance_correction = burnoutMassEach*distance_squared; + + Ir += instance_correction; } + if(debug){// vv DEBUG + System.err.println(String.format(" I_rot: %6g ", Ir)); + } // ^^ DEBUG } - MassData configData = new MassData( curMotorCM, Ir, It); + MassData configData = new MassData( clusterCM, Ir, It); allMotorData = allMotorData.add( configData ); - // BEGIN DEVEL - //if( debug){ - // // Inertia - // System.err.println(String.format( inertiaFormat, motorIndex, instanceCount, mtr.getDesignation(), Ir, It)); - // // mass only - //double singleMass = type.getCG( mtr ).weight; - //System.err.println(String.format( massFormat, motorIndex, mtr.getDesignation(), - // singleMass, instanceCount, curMotorCM.weight, allMotorData.getMass(),curMotorCM.x, curMotorCM.y, curMotorCM.z )); - //} - //motorIndex++; - // END DEVEL } return allMotorData; } - - /** - * Return the longitudinal inertia of the rocket with the specified motor instance - * 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 config, MassCalcType type) { - checkCache(config); - calculateStageCache(config); - - MassData structureData = MassData.ZERO_DATA; - - // Stages - for (AxialStage stage : config.getActiveStages()) { - int stageNumber = stage.getStageNumber(); - - MassData stageData = cache.get(stageNumber); - structureData = structureData.add(stageData); - } - - MassData motorData = MassData.ZERO_DATA; - if( MassCalcType.NO_MOTORS != type ){ - motorData = getMotorMassData(config, type); - } - - - MassData totalData = structureData.add( motorData); - if(debug){ - System.err.println(String.format(" >> Structural MassData: %s", structureData.toDebug())); - System.err.println(String.format(" >> Motor MassData: %s", motorData.toDebug())); - 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 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 config, MassCalcType type) { - checkCache(config); - calculateStageCache(config); - - MassData structureData = MassData.ZERO_DATA; - - // Stages - for (AxialStage stage : config.getActiveStages()) { - int stageNumber = stage.getStageNumber(); - - MassData stageData = cache.get(stageNumber); - structureData = structureData.add(stageData); - } - - MassData motorData = MassData.ZERO_DATA; - if( MassCalcType.NO_MOTORS != type ){ - motorData = getMotorMassData(config, type); - } - - MassData totalData = structureData.add( motorData); - if(debug){ - System.err.println(String.format(" >> Structural MassData: %s", structureData.toDebug())); - System.err.println(String.format(" >> Motor MassData: %s", motorData.toDebug())); - System.err.println(String.format("==>> Combined MassData: %s", totalData.toDebug())); - } - - return totalData.getRotationalInertia(); - } - - - /** - * Return the total mass of the motors - * - * @param motors the motor configuration - * @param configuration the current motor instance configuration - * @return the total mass of all 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 ( MassCalcType.NO_MOTORS != calcType ){ - for (MotorConfiguration curConfig : configuration.getActiveMotors()) { - int instanceCount = curConfig.getMount().getInstanceCount(); - mass = mass + curConfig.getPropellantMass()*instanceCount; - } - } - return mass; - } - + /** * Return the total mass of the motors * @@ -341,14 +317,7 @@ public class MassCalculator implements Monitorable { * @return the total mass of all motors */ public double getPropellantMass(SimulationStatus status ){ - double mass = 0; - Collection activeMotorList = status.getMotors(); - for (MotorClusterState curConfig : activeMotorList ) { - int instanceCount = curConfig.getMount().getInstanceCount(); - double motorTime = curConfig.getMotorTime(status.getSimulationTime()); - mass += (curConfig.getMotor().getTotalMass(motorTime) - curConfig.getMotor().getBurnoutMass())*instanceCount; - } - return mass; + return (getPropellantMassData( status )).getCM().weight; } /** @@ -362,43 +331,28 @@ public class MassCalculator implements Monitorable { * @param type the state of the motors (none, launch mass, burnout mass) * @return a map from each rocket component to its corresponding CG. */ - public Map getCGAnalysis(FlightConfiguration configuration, MassCalcType type) { - checkCache(configuration); - calculateStageCache(configuration); + public Map getCGAnalysis(FlightConfiguration configuration) { + revalidateCache(configuration); Map map = new HashMap(); + Coordinate rocketCG = Coordinate.ZERO; for (RocketComponent comp : configuration.getActiveComponents()) { Coordinate[] cgs = comp.toAbsolute(comp.getCG()); - Coordinate totalCG = Coordinate.NUL; + Coordinate stageCG = Coordinate.NUL; for (Coordinate cg : cgs) { - totalCG = totalCG.average(cg); + stageCG = stageCG.average(cg); } - map.put(comp, totalCG); + map.put(comp, stageCG); + + rocketCG.average( stageCG); } - map.put(configuration.getRocket(), getCG(configuration, type)); + map.put(configuration.getRocket(), rocketCG ); return map; } - //////// Cache computations //////// - - private void calculateStageCache(FlightConfiguration config) { - int stageCount = config.getActiveStageCount(); - if(debug){ - System.err.println(">> Calculating massData cache for config: "+config.toDebug()+" with "+stageCount+" stages"); - } - if( 0 < stageCount ){ - for( AxialStage curStage : config.getActiveStages()){ - int index = curStage.getStageNumber(); - MassData stageData = calculateAssemblyMassData( curStage); - cache.put(index, stageData); - } - } - - } - /** * Returns the mass and inertia data for this component and all subcomponents. @@ -423,9 +377,9 @@ public class MassCalculator implements Monitorable { } // default if not instanced (instance count == 1) - MassData resultantData = new MassData( compCM, compIx, compIt); + MassData assemblyData = new MassData( compCM, compIx, compIt); - if( debug && (MIN_MASS < compCM.weight)){ + if( debug && ( MIN_MASS < compCM.weight)){ System.err.println(String.format("%-32s: %s ",indent+"ea["+ component.getName()+"]", compCM )); if( component.isMassOverridden() && component.isMassOverridden() && component.getOverrideSubcomponents()){ System.err.println(indent+" ?["+ component.isMassOverridden()+"]["+ @@ -447,20 +401,20 @@ public class MassCalculator implements Monitorable { childrenData = childrenData.add( childData ); } - resultantData = resultantData.add( childrenData); + assemblyData = assemblyData.add( childrenData); // if instanced, adjust children's data too. if ( 1 < component.getInstanceCount() ){ - if( debug ){ + if(debug){// vv DEBUG System.err.println(String.format("%s Found instanceable with %d children: %s (t= %s)", indent, component.getInstanceCount(), component.getName(), component.getClass().getSimpleName() )); - } + }// ^^ DEBUG 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 templateCM = resultantData.cm; + Coordinate templateCM = assemblyData.cm; MassData instAccumData = new MassData(); // accumulator for instance MassData Coordinate[] instanceLocations = ((Instanceable) component).getInstanceOffsets(); for( Coordinate curOffset : instanceLocations ){ @@ -472,55 +426,77 @@ public class MassCalculator implements Monitorable { instAccumData = instAccumData.add( instanceData); } - resultantData = instAccumData; + assemblyData = instAccumData; if( debug && (MIN_MASS < compCM.weight)){ - System.err.println(String.format("%-32s: %s ", indent+"x"+component.getInstanceCount()+"["+component.getName()+"][asbly]", resultantData.toDebug())); + System.err.println(String.format("%-32s: %s ", indent+"x"+component.getInstanceCount()+"["+component.getName()+"][asbly]", assemblyData.toDebug())); } } // move to parent's reference point - resultantData = resultantData.move( component.getOffset() ); + assemblyData = assemblyData.move( component.getOffset() ); if( component instanceof ParallelStage ){ // hacky correction for the fact Booster Stages aren't direct subchildren to the rocket - resultantData = resultantData.move( component.getParent().getOffset() ); + assemblyData = assemblyData.move( component.getParent().getOffset() ); } // Override total data if (component.getOverrideSubcomponents()) { - if( debug){ - System.err.println(String.format("%-32s: %s ", indent+"vv["+component.getName()+"][asbly]", resultantData.toDebug())); - } + if(debug){// vv DEBUG + System.err.println(String.format("%-32s: %s ", indent+"vv["+component.getName()+"][asbly]", assemblyData.toDebug())); + }// ^^ DEBUG + if (component.isMassOverridden()) { - double oldMass = resultantData.getMass(); + double oldMass = assemblyData.getMass(); double newMass = MathUtil.max(component.getOverrideMass(), MIN_MASS); - Coordinate newCM = resultantData.getCM().setWeight(newMass); + Coordinate newCM = assemblyData.getCM().setWeight(newMass); - double newIxx = resultantData.getIxx() * newMass / oldMass; - double newIyy = resultantData.getIyy() * newMass / oldMass; - double newIzz = resultantData.getIzz() * newMass / oldMass; + double newIxx = assemblyData.getIxx() * newMass / oldMass; + double newIyy = assemblyData.getIyy() * newMass / oldMass; + double newIzz = assemblyData.getIzz() * newMass / oldMass; - resultantData = new MassData( newCM, newIxx, newIyy, newIzz ); + assemblyData = new MassData( newCM, newIxx, newIyy, newIzz ); } if (component.isCGOverridden()) { - double oldx = resultantData.getCM().x; + double oldx = assemblyData.getCM().x; double newx = component.getOverrideCGX(); Coordinate delta = new Coordinate(newx-oldx, 0, 0); - if(debug){ + if(debug){// vv DEBUG System.err.println(String.format("%-32s: x: %g => %g (%g)", indent+" 88", oldx, newx, delta.x)); - } - resultantData = resultantData.move( delta ); + }// ^^ DEBUG + + assemblyData = assemblyData.move( delta ); } } - if( debug){ - System.err.println(String.format("%-32s: %s ", indent+"<<["+component.getName()+"][asbly]", resultantData.toDebug())); + if(debug){// vv DEBUG + System.err.println(String.format("%-32s: %s ", indent+"<<["+component.getName()+"][asbly]", assemblyData.toDebug())); + }// ^^ DEBUG + + return assemblyData; + } + + + /// nooooot quite done, yet. + public void revalidateCache( final SimulationStatus status ){ + //if( ... check what? the config may not have changed, but if the time has, we want to recalculate the cache! + rocketSpentMassCache = calculateBurnoutMassData( status.getConfiguration() ); + + propellantMassCache = calculatePropellantMassData( status); + + //} + } + + public void revalidateCache( final FlightConfiguration config ){ + checkCache( config); + if( null == rocketSpentMassCache ){ + rocketSpentMassCache = calculateBurnoutMassData(config); + } + if( null == propellantMassCache ){ + propellantMassCache = calculatePropellantMassData( config); } - - - return resultantData; } /** @@ -534,13 +510,16 @@ public class MassCalculator implements Monitorable { * * @param configuration the configuration of the current call */ - protected final void checkCache(FlightConfiguration configuration) { + protected final boolean checkCache(FlightConfiguration configuration) { + //System.err.println("?? Checking the cache ... "); if (rocketMassModID != configuration.getRocket().getMassModID() || rocketTreeModID != configuration.getRocket().getTreeModID()) { rocketMassModID = configuration.getRocket().getMassModID(); rocketTreeModID = configuration.getRocket().getTreeModID(); voidMassCache(); + return false; } + return true; } /** @@ -551,11 +530,12 @@ public class MassCalculator implements Monitorable { * its execution. */ protected void voidMassCache() { - this.cache.clear(); + this.stageMassCache.clear(); + this.rocketSpentMassCache=null; + this.propellantMassCache=null; } - @Override public int getModID() { return 0; diff --git a/core/src/net/sf/openrocket/masscalc/MassData.java b/core/src/net/sf/openrocket/masscalc/MassData.java index 86c8d0834..bbb52481f 100644 --- a/core/src/net/sf/openrocket/masscalc/MassData.java +++ b/core/src/net/sf/openrocket/masscalc/MassData.java @@ -1,6 +1,7 @@ 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; @@ -227,16 +228,6 @@ public class MassData { return I_cm.zz; } - // this is a tacked-on hack. - double m_p = Double.NaN; - public void setPropellantMass( final double _mp){ - this.m_p = _mp; - } - - public double getPropellantMass(){ - return this.m_p; - } - /** * 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 @@ -293,8 +284,8 @@ public class MassData { @Override public String toString() { - return "MassData [cg=" + cm + ", longitudinalInertia=" + getIyy() - + ", rotationalInertia=" + getIxx() + "]"; + return "MassData [cg=" + cm + + ", rotationalInertia=" + getIxx() + ", longitudinalInertia=" + getIyy() + "]"; } } diff --git a/core/src/net/sf/openrocket/motor/Motor.java b/core/src/net/sf/openrocket/motor/Motor.java index 9fbd4fbc8..a3c468818 100644 --- a/core/src/net/sf/openrocket/motor/Motor.java +++ b/core/src/net/sf/openrocket/motor/Motor.java @@ -45,7 +45,11 @@ public interface Motor extends Cloneable { } } + public static final double PSEUDO_TIME_EMPTY = Double.NaN; + public static final double PSEUDO_TIME_LAUNCH = 0.0; + public static final double PSEUDO_TIME_BURNOUT = Double.MAX_VALUE; + /** * Ejection charge delay value signifying a "plugged" motor with no ejection charge. * The value is that of Double.POSITIVE_INFINITY. @@ -118,7 +122,7 @@ public interface Motor extends Cloneable { public Motor clone(); public double getAverageThrust( final double startTime, final double endTime ); - + public double getLaunchCGx(); public double getBurnoutCGx(); @@ -169,11 +173,18 @@ public interface Motor extends Cloneable { */ public double getTotalMass( final double motorTime); + public double getPropellantMass( final Double motorTime); + /** Return the mass at a given time * * @param motorTime time (in seconds) since motor ignition * @return */ - public double getCGx( final double motorTime); + public double getCMx( final double motorTime); + public double getUnitIxx(); + + public double getUnitIyy(); + + public double getUnitIzz(); } diff --git a/core/src/net/sf/openrocket/motor/ThrustCurveMotor.java b/core/src/net/sf/openrocket/motor/ThrustCurveMotor.java index 4f5e512d7..f68b3efac 100644 --- a/core/src/net/sf/openrocket/motor/ThrustCurveMotor.java +++ b/core/src/net/sf/openrocket/motor/ThrustCurveMotor.java @@ -43,8 +43,8 @@ public class ThrustCurveMotor implements Motor, Comparable, Se private final double length; private final double[] time; private final double[] thrust; -// private final double[] cgx; // cannot add without rebuilding the motor database ... automatically on every user's install. -// private final double[] mass; // cannot add without rebuilding the motor database ... on every user's install. +// private final double[] cgx; // cannot add without rebuilding the motor database ... *automatically on every user's install* +// private final double[] mass; // cannot add without rebuilding the motor database ... private final Coordinate[] cg; private double maxThrust; private double burnTimeEstimate; @@ -317,7 +317,7 @@ public class ThrustCurveMotor implements Motor, Comparable, Se } @Override - public double getCGx( final double motorTime ){ + public double getCMx( final double motorTime ){ double pseudoIndex = getPseudoIndex( motorTime ); return this.interpolateCenterOfMassAtIndex( pseudoIndex).x; } @@ -378,6 +378,21 @@ public class ThrustCurveMotor implements Motor, Comparable, Se public double getUnitRotationalInertia() { return this.unitRotationalInertia; } + + @Override + public double getUnitIxx() { + return this.unitRotationalInertia; + } + + @Override + public double getUnitIyy() { + return this.unitLongitudinalInertia; + } + + @Override + public double getUnitIzz(){ + return this.unitLongitudinalInertia; + } @Override public String getDesignation() { @@ -428,21 +443,19 @@ public class ThrustCurveMotor implements Motor, Comparable, Se @Override public double getBurnoutMass() { return cg[cg.length-1].weight; //mass[mass.length - 1]; - } + } @Override public double getBurnTime() { return time[time.length-1]; } - // FIXME - there seems to be some numeric problems in here... - // simple linear interpolation... not sample density for anything more complex private static double interpolateAtIndex( final double[] values, final double pseudoIndex ){ final double SNAP_TOLERANCE = 0.0001; final int lowerIndex = (int)pseudoIndex; final int upperIndex= lowerIndex+1; -// + final double lowerFrac = pseudoIndex - ((double) lowerIndex); final double upperFrac = 1-lowerFrac; @@ -482,6 +495,17 @@ public class ThrustCurveMotor implements Motor, Comparable, Se return interpolateCenterOfMassAtIndex( pseudoIndex).weight; } + public double getPropellantMass(){ + return (getLaunchMass() - getBurnoutMass()); + } + + @Override + public double getPropellantMass( final Double motorTime){ + final double pseudoIndex = getPseudoIndex( motorTime); + final double totalMass = interpolateCenterOfMassAtIndex( pseudoIndex).weight; + return totalMass - this.getBurnoutMass(); + } + protected Coordinate interpolateCenterOfMassAtIndex( final double pseudoIndex ){ final double SNAP_TOLERANCE = 0.0001; @@ -692,6 +716,5 @@ public class ThrustCurveMotor implements Motor, Comparable, Se return value; } - - + } diff --git a/core/src/net/sf/openrocket/motor/ThrustCurveMotorPlaceholder.java b/core/src/net/sf/openrocket/motor/ThrustCurveMotorPlaceholder.java index 5c5e72143..fd173d5d6 100644 --- a/core/src/net/sf/openrocket/motor/ThrustCurveMotorPlaceholder.java +++ b/core/src/net/sf/openrocket/motor/ThrustCurveMotorPlaceholder.java @@ -192,7 +192,6 @@ public class ThrustCurveMotorPlaceholder implements Motor { return emptyMass; } - @Override public double getThrust(double pseudoIndex) { return 0; @@ -209,13 +208,32 @@ public class ThrustCurveMotorPlaceholder implements Motor { } @Override - public double getCGx(double pseudoIndex) { + public double getPropellantMass( final Double motorTime){ + return 0.; + } + + @Override + public double getCMx(double pseudoIndex) { return 0; } - + @Override public double getBurnTime() { return 0; } + + @Override + public double getUnitIxx() { + return 0.; + } + + @Override + public double getUnitIyy() { + return 0.; + } + @Override + public double getUnitIzz(){ + return 0.; + } } diff --git a/core/src/net/sf/openrocket/optimization/rocketoptimization/domains/StabilityDomain.java b/core/src/net/sf/openrocket/optimization/rocketoptimization/domains/StabilityDomain.java index cdcd548fd..338db8fb1 100644 --- a/core/src/net/sf/openrocket/optimization/rocketoptimization/domains/StabilityDomain.java +++ b/core/src/net/sf/openrocket/optimization/rocketoptimization/domains/StabilityDomain.java @@ -5,7 +5,6 @@ import net.sf.openrocket.aerodynamics.BarrowmanCalculator; import net.sf.openrocket.aerodynamics.FlightConditions; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.masscalc.MassCalculator; -import net.sf.openrocket.masscalc.MassCalculator.MassCalcType; import net.sf.openrocket.optimization.rocketoptimization.SimulationDomain; import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.RocketComponent; @@ -74,7 +73,7 @@ public class StabilityDomain implements SimulationDomain { // TODO: HIGH: This re-calculates the worst theta value every time cp = aerodynamicCalculator.getWorstCP(configuration, conditions, null); - cg = massCalculator.getCG(configuration, MassCalcType.LAUNCH_MASS); + cg = massCalculator.getRocketLaunchMassData(configuration).getCM(); if (cp.weight > 0.000001) cpx = cp.x; diff --git a/core/src/net/sf/openrocket/optimization/rocketoptimization/parameters/StabilityParameter.java b/core/src/net/sf/openrocket/optimization/rocketoptimization/parameters/StabilityParameter.java index c6e3595b9..cce846453 100644 --- a/core/src/net/sf/openrocket/optimization/rocketoptimization/parameters/StabilityParameter.java +++ b/core/src/net/sf/openrocket/optimization/rocketoptimization/parameters/StabilityParameter.java @@ -9,7 +9,6 @@ import net.sf.openrocket.aerodynamics.FlightConditions; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.masscalc.MassCalculator; -import net.sf.openrocket.masscalc.MassCalculator.MassCalcType; import net.sf.openrocket.optimization.general.OptimizationException; import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter; import net.sf.openrocket.rocketcomponent.FlightConfiguration; @@ -66,7 +65,8 @@ public class StabilityParameter implements OptimizableParameter { conditions.setRollRate(0); cp = aerodynamicCalculator.getWorstCP(configuration, conditions, null); - cg = massCalculator.getCG(configuration, MassCalcType.LAUNCH_MASS); + // worst case CM is also + cg = massCalculator.getRocketLaunchMassData(configuration).getCM(); if (cp.weight > 0.000001) cpx = cp.x; diff --git a/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java b/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java index 84d8131a4..7fcd2e101 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java +++ b/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java @@ -164,6 +164,26 @@ public class AxialStage extends ComponentAssembly implements FlightConfigurableC return null; } - + @Override + public void toDebugTreeNode(final StringBuilder buffer, final String indent) { + + Coordinate[] relCoords = this.getInstanceOffsets(); + Coordinate[] absCoords = this.getLocations(); + if( 1 == getInstanceCount()){ + buffer.append(String.format("%-40s| %5.3f; %24s; %24s;", indent+this.getName()+" (# "+this.getStageNumber()+")", + this.getLength(), this.getOffset(), this.getLocations()[0])); + buffer.append(String.format("len: %6.4f )(offset: %4.1f via: %s )\n", this.getLength(), this.getAxialOffset(), this.relativePosition.name())); + }else{ + buffer.append(String.format("%-40s|(len: %6.4f )(offset: %4.1f via: %s)\n", (indent+this.getName()+"(# "+this.getStageNumber()+")"), this.getLength(), this.getAxialOffset(), this.relativePosition.name())); + for (int instanceNumber = 0; instanceNumber < this.getInstanceCount(); instanceNumber++) { + Coordinate instanceRelativePosition = relCoords[instanceNumber]; + Coordinate instanceAbsolutePosition = absCoords[instanceNumber]; + final String prefix = String.format("%s [%2d/%2d]", indent, instanceNumber+1, getInstanceCount()); + buffer.append(String.format("%-40s| %5.3f; %24s; %24s;\n", prefix, this.getLength(), instanceRelativePosition, instanceAbsolutePosition)); + } + } + + } + } diff --git a/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java b/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java index 734a96a4b..aba1030b2 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java +++ b/core/src/net/sf/openrocket/rocketcomponent/BodyTube.java @@ -472,4 +472,9 @@ public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial copy.motors = new MotorConfigurationSet( this.motors, copy ); return copy; } + + @Override + public ClusterConfiguration getClusterConfiguration() { + return ClusterConfiguration.SINGLE; + } } diff --git a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java index 8cae380ae..bb637dcc5 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java @@ -546,7 +546,9 @@ public class FlightConfiguration implements FlightConfigurableParameter iterator = this.children.iterator(); while (iterator.hasNext()) { - iterator.next().dumpTreeHelper(buffer, prefix + "...."); + iterator.next().toDebugTreeHelper(buffer, indent + "...."); } } + + + // this method is in need of some refactoring... + // eventually, combine the stage-instance debug code into here... + public void toDebugTreeNode(final StringBuilder buffer, final String indent) { + final String prefix = String.format("%s%s", indent, this.getName()); + + // 1) instanced vs non-instanced + if( 1 == getInstanceCount() ){ + // un-instanced RocketComponents (usual case) + buffer.append(String.format("%-40s| %5.3f; %24s; %24s; ", prefix, this.getLength(), this.getOffset(), this.getLocations()[0])); + buffer.append(String.format("(offset: %4.1f via: %s )\n", this.getAxialOffset(), this.relativePosition.name())); + }else if( this instanceof Clusterable ){ + // instanced components -- think motor clusters or booster stage clusters + final String patternName = ((Clusterable)this).getPatternName(); + buffer.append(String.format("%-40s (cluster: %s )", prefix, patternName)); + buffer.append(String.format("(offset: %4.1f via: %s )\n", this.getAxialOffset(), this.relativePosition.name())); + + for (int instanceNumber = 0; instanceNumber < this.getInstanceCount(); instanceNumber++) { + final String instancePrefix = String.format("%s [%2d/%2d]", indent, instanceNumber+1, getInstanceCount()); + buffer.append(String.format("%-40s| %5.3f; %24s; %24s;\n", instancePrefix, getLength(), getOffset(), getLocations()[0])); + } + }else{ + throw new IllegalStateException("This is a developer error! If you implement an instanced class, please subclass the Clusterable interface."); + } + + // 2) if this is an ACTING motor mount: + if(( this instanceof MotorMount ) &&( ((MotorMount)this).isMotorMount())){ + // because InnerTube and BodyTube don't really share a common ancestor besides RocketComponent + // ... and it's easier to have all this code in one place... + toDebugMountNode( buffer, indent); + } + } + + + public void toDebugMountNode(final StringBuilder buffer, final String indent) { + MotorMount mnt = (MotorMount)this; + + Coordinate[] relCoords = this.getInstanceOffsets(); + Coordinate[] absCoords = this.getLocations(); + FlightConfigurationId curId = this.getRocket().getSelectedConfiguration().getFlightConfigurationID(); + final int intanceCount = this.getInstanceCount(); + MotorConfiguration curInstance = mnt.getMotorConfig( curId); + if( curInstance.isEmpty() ){ + // print just the tube locations + buffer.append(indent+" [X] This Instance doesn't have any motors for the active configuration.\n"); + }else{ + // curInstance has a motor ... + Motor curMotor = curInstance.getMotor(); + final double motorOffset = this.getLength() - curMotor.getLength(); + + buffer.append(String.format("%-40sThrust: %f N; \n", + indent+" Mounted: "+curMotor.getDesignation(), curMotor.getMaxThrustEstimate() )); + + Coordinate motorRelativePosition = new Coordinate(motorOffset, 0, 0); + Coordinate tubeAbs = absCoords[0]; + Coordinate motorAbsolutePosition = new Coordinate(tubeAbs.x+motorOffset,tubeAbs.y,tubeAbs.z); + buffer.append(String.format("%-40s| %5.3f; %24s; %24s;\n", indent, curMotor.getLength(), motorRelativePosition, motorAbsolutePosition)); + + } + } + } diff --git a/core/src/net/sf/openrocket/rocketcomponent/RocketUtils.java b/core/src/net/sf/openrocket/rocketcomponent/RocketUtils.java index e97b8dcad..f875c2b76 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RocketUtils.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RocketUtils.java @@ -2,8 +2,6 @@ package net.sf.openrocket.rocketcomponent; import java.util.Collection; -import net.sf.openrocket.masscalc.MassCalculator; -import net.sf.openrocket.masscalc.MassCalculator.MassCalcType; import net.sf.openrocket.util.Coordinate; public abstract class RocketUtils { @@ -24,11 +22,5 @@ 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.getSelectedConfiguration(), calcType); - return cg; - } } diff --git a/core/src/net/sf/openrocket/simulation/AbstractSimulationStepper.java b/core/src/net/sf/openrocket/simulation/AbstractSimulationStepper.java index 2e5b82641..e3180ffef 100644 --- a/core/src/net/sf/openrocket/simulation/AbstractSimulationStepper.java +++ b/core/src/net/sf/openrocket/simulation/AbstractSimulationStepper.java @@ -3,7 +3,6 @@ package net.sf.openrocket.simulation; import java.util.Collection; import net.sf.openrocket.masscalc.MassCalculator; -import net.sf.openrocket.masscalc.MassCalculator.MassCalcType; import net.sf.openrocket.masscalc.MassData; import net.sf.openrocket.models.atmosphere.AtmosphericConditions; import net.sf.openrocket.simulation.exception.SimulationException; @@ -111,10 +110,8 @@ public abstract class AbstractSimulationStepper implements SimulationStepper { * @return the mass data to use * @throws SimulationException if a listener throws SimulationException */ - protected MassData calculateMassData(SimulationStatus status) throws SimulationException { + protected MassData calculateDryMassData(SimulationStatus status) throws SimulationException { MassData mass; - Coordinate cg; - double longitudinalInertia, rotationalInertia, propellantMass; // Call pre-listener mass = SimulationListenerHelper.firePreMassCalculation(status); @@ -123,13 +120,34 @@ public abstract class AbstractSimulationStepper implements SimulationStepper { } MassCalculator calc = status.getSimulationConditions().getMassCalculator(); - // 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); - mass = new MassData(cg, rotationalInertia, longitudinalInertia); - propellantMass = calc.getPropellantMass(status); - mass.setPropellantMass( propellantMass ); + + mass = calc.getRocketSpentMassData( status.getConfiguration() ); + + // Call post-listener + mass = SimulationListenerHelper.firePostMassCalculation(status, mass); + + checkNaN(mass.getCG()); + checkNaN(mass.getLongitudinalInertia()); + checkNaN(mass.getRotationalInertia()); + + return mass; + } + + protected MassData calculatePropellantMassData(SimulationStatus status) throws SimulationException { + MassData mass; + + // Call pre-listener + mass = SimulationListenerHelper.firePreMassCalculation(status); + if (mass != null) { + return mass; + } + + MassCalculator calc = status.getSimulationConditions().getMassCalculator(); + + + + mass = calc.getPropellantMassData( status ); + // Call post-listener mass = SimulationListenerHelper.firePostMassCalculation(status, mass); @@ -137,7 +155,6 @@ public abstract class AbstractSimulationStepper implements SimulationStepper { checkNaN(mass.getCG()); checkNaN(mass.getLongitudinalInertia()); checkNaN(mass.getRotationalInertia()); - checkNaN(mass.getPropellantMass()); return mass; } diff --git a/core/src/net/sf/openrocket/simulation/BasicLandingStepper.java b/core/src/net/sf/openrocket/simulation/BasicLandingStepper.java index 6723de204..65636b432 100644 --- a/core/src/net/sf/openrocket/simulation/BasicLandingStepper.java +++ b/core/src/net/sf/openrocket/simulation/BasicLandingStepper.java @@ -39,7 +39,7 @@ public class BasicLandingStepper extends AbstractSimulationStepper { // Compute drag force double dynP = (0.5 * atmosphere.getDensity() * airSpeed.length2()); double dragForce = totalCD * dynP * refArea; - MassData massData = calculateMassData(status); + MassData massData = calculateDryMassData(status); double mass = massData.getCG().weight; diff --git a/core/src/net/sf/openrocket/simulation/BasicTumbleStepper.java b/core/src/net/sf/openrocket/simulation/BasicTumbleStepper.java index bc142e051..3b732fee0 100644 --- a/core/src/net/sf/openrocket/simulation/BasicTumbleStepper.java +++ b/core/src/net/sf/openrocket/simulation/BasicTumbleStepper.java @@ -35,7 +35,8 @@ public class BasicTumbleStepper extends AbstractSimulationStepper { // Compute drag force double dynP = (0.5 * atmosphere.getDensity() * airSpeed.length2()); double dragForce = tumbleDrag * dynP; - MassData massData = calculateMassData(status); + // n.b. this is consntant, and could be calculated once at the beginning of this simulation branch... + MassData massData = calculateDryMassData(status); double mass = massData.getCG().weight; diff --git a/core/src/net/sf/openrocket/simulation/MotorClusterState.java b/core/src/net/sf/openrocket/simulation/MotorClusterState.java index 776700965..7a7defbe4 100644 --- a/core/src/net/sf/openrocket/simulation/MotorClusterState.java +++ b/core/src/net/sf/openrocket/simulation/MotorClusterState.java @@ -82,6 +82,10 @@ public class MotorClusterState { burnOut( _cutoffTime ); } + public MotorConfiguration getConfig(){ + return config; + } + public double getEjectionDelay() { return config.getEjectionDelay(); } @@ -93,6 +97,10 @@ public class MotorClusterState { public double getPropellantMass(){ return (motor.getLaunchMass() - motor.getBurnoutMass()); } + + public double getPropellantMass( final double motorTime ){ + return (motor.getPropellantMass( motorTime) - motor.getBurnoutMass()); + } public MotorMount getMount(){ return config.getMount(); diff --git a/core/src/net/sf/openrocket/simulation/RK4SimulationStepper.java b/core/src/net/sf/openrocket/simulation/RK4SimulationStepper.java index 364996de1..e658469f7 100644 --- a/core/src/net/sf/openrocket/simulation/RK4SimulationStepper.java +++ b/core/src/net/sf/openrocket/simulation/RK4SimulationStepper.java @@ -327,8 +327,10 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { calculateForces(status, store); // Calculate mass data - store.massData = calculateMassData(status); + MassData dryMassData = calculateDryMassData(status); + store.propellantMassData = calculatePropellantMassData(status); + store.rocketMassData = dryMassData.add( store.propellantMassData ); // Calculate the forces from the aerodynamic coefficients @@ -345,9 +347,9 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { double forceZ = store.thrustForce - store.dragForce; - store.linearAcceleration = new Coordinate(-fN / store.massData.getCG().weight, - -fSide / store.massData.getCG().weight, - forceZ / store.massData.getCG().weight); + store.linearAcceleration = new Coordinate(-fN / store.rocketMassData.getCG().weight, + -fSide / store.rocketMassData.getCG().weight, + forceZ / store.rocketMassData.getCG().weight); store.linearAcceleration = store.thetaRotation.rotateZ(store.linearAcceleration); @@ -376,8 +378,8 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { } else { // Shift moments to CG - double Cm = store.forces.getCm() - store.forces.getCN() * store.massData.getCG().x / refLength; - double Cyaw = store.forces.getCyaw() - store.forces.getCside() * store.massData.getCG().x / refLength; + double Cm = store.forces.getCm() - store.forces.getCN() * store.rocketMassData.getCG().x / refLength; + double Cyaw = store.forces.getCyaw() - store.forces.getCside() * store.rocketMassData.getCG().x / refLength; // Compute moments double momX = -Cyaw * dynP * refArea * refLength; @@ -385,9 +387,9 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { double momZ = store.forces.getCroll() * dynP * refArea * refLength; // Compute acceleration in rocket coordinates - store.angularAcceleration = new Coordinate(momX / store.massData.getLongitudinalInertia(), - momY / store.massData.getLongitudinalInertia(), - momZ / store.massData.getRotationalInertia()); + store.angularAcceleration = new Coordinate(momX / store.rocketMassData.getLongitudinalInertia(), + momY / store.rocketMassData.getLongitudinalInertia(), + momZ / store.rocketMassData.getRotationalInertia()); store.rollAcceleration = store.angularAcceleration.z; // TODO: LOW: This should be hypot, but does it matter? @@ -595,24 +597,30 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { data.setValue(FlightDataType.TYPE_MACH_NUMBER, store.flightConditions.getMach()); } - if (store.massData != null) { - data.setValue(FlightDataType.TYPE_CG_LOCATION, store.massData.getCG().x); + if (store.rocketMassData != null) { + data.setValue(FlightDataType.TYPE_CG_LOCATION, store.rocketMassData.getCG().x); } if (status.isLaunchRodCleared()) { // Don't include CP and stability with huge launch AOA if (store.forces != null) { data.setValue(FlightDataType.TYPE_CP_LOCATION, store.forces.getCP().x); } - if (store.forces != null && store.flightConditions != null && store.massData != null) { + if (store.forces != null && store.flightConditions != null && store.rocketMassData != null) { data.setValue(FlightDataType.TYPE_STABILITY, - (store.forces.getCP().x - store.massData.getCG().x) / store.flightConditions.getRefLength()); + (store.forces.getCP().x - store.rocketMassData.getCG().x) / store.flightConditions.getRefLength()); } } - if (store.massData != null) { - data.setValue(FlightDataType.TYPE_MASS, store.massData.getCG().weight); - data.setValue(FlightDataType.TYPE_PROPELLANT_MASS, store.massData.getPropellantMass()); - data.setValue(FlightDataType.TYPE_LONGITUDINAL_INERTIA, store.massData.getLongitudinalInertia()); - data.setValue(FlightDataType.TYPE_ROTATIONAL_INERTIA, store.massData.getRotationalInertia()); + + if( null != store.propellantMassData ){ + data.setValue(FlightDataType.TYPE_PROPELLANT_MASS, store.propellantMassData.getCG().weight); + //data.setValue(FlightDataType.TYPE_PROPELLANT_LONGITUDINAL_INERTIA, store.propellantMassData.getLongitudinalInertia()); + //data.setValue(FlightDataType.TYPE_PROPELLANT_ROTATIONAL_INERTIA, store.propellantMassData.getRotationalInertia()); + } + if (store.rocketMassData != null) { + // N.B.: These refer to total mass + data.setValue(FlightDataType.TYPE_MASS, store.rocketMassData.getCG().weight); + data.setValue(FlightDataType.TYPE_LONGITUDINAL_INERTIA, store.rocketMassData.getLongitudinalInertia()); + data.setValue(FlightDataType.TYPE_ROTATIONAL_INERTIA, store.rocketMassData.getRotationalInertia()); } data.setValue(FlightDataType.TYPE_THRUST_FORCE, store.thrustForce); @@ -620,11 +628,11 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { data.setValue(FlightDataType.TYPE_GRAVITY, store.gravity); if (status.isLaunchRodCleared() && store.forces != null) { - if (store.massData != null && store.flightConditions != null) { + if (store.rocketMassData != null && store.flightConditions != null) { data.setValue(FlightDataType.TYPE_PITCH_MOMENT_COEFF, - store.forces.getCm() - store.forces.getCN() * store.massData.getCG().x / store.flightConditions.getRefLength()); + store.forces.getCm() - store.forces.getCN() * store.rocketMassData.getCG().x / store.flightConditions.getRefLength()); data.setValue(FlightDataType.TYPE_YAW_MOMENT_COEFF, - store.forces.getCyaw() - store.forces.getCside() * store.massData.getCG().x / store.flightConditions.getRefLength()); + store.forces.getCyaw() - store.forces.getCside() * store.rocketMassData.getCG().x / store.flightConditions.getRefLength()); } data.setValue(FlightDataType.TYPE_NORMAL_FORCE_COEFF, store.forces.getCN()); data.setValue(FlightDataType.TYPE_SIDE_FORCE_COEFF, store.forces.getCside()); @@ -707,7 +715,9 @@ public class RK4SimulationStepper extends AbstractSimulationStepper { public double longitudinalAcceleration = Double.NaN; - public MassData massData; + public MassData rocketMassData; + + public MassData propellantMassData; public Coordinate coriolisAcceleration; diff --git a/core/src/net/sf/openrocket/simulation/SimulationStatus.java b/core/src/net/sf/openrocket/simulation/SimulationStatus.java index bc0f49edc..929880426 100644 --- a/core/src/net/sf/openrocket/simulation/SimulationStatus.java +++ b/core/src/net/sf/openrocket/simulation/SimulationStatus.java @@ -87,8 +87,7 @@ public class SimulationStatus implements Monitorable { private int modID = 0; private int modIDadd = 0; - public SimulationStatus(FlightConfiguration configuration, - SimulationConditions simulationConditions) { + public SimulationStatus(FlightConfiguration configuration, SimulationConditions simulationConditions) { this.simulationConditions = simulationConditions; this.configuration = configuration; diff --git a/core/src/net/sf/openrocket/simulation/listeners/SimulationListenerHelper.java b/core/src/net/sf/openrocket/simulation/listeners/SimulationListenerHelper.java index bfcc45e5e..6691ae2dc 100644 --- a/core/src/net/sf/openrocket/simulation/listeners/SimulationListenerHelper.java +++ b/core/src/net/sf/openrocket/simulation/listeners/SimulationListenerHelper.java @@ -526,7 +526,7 @@ public class SimulationListenerHelper { /** * Fire postMassCalculation event. * - * @return the aerodynamic forces to use. + * @return the resultant mass data */ public static MassData firePostMassCalculation(SimulationStatus status, MassData mass) throws SimulationException { MassData m; diff --git a/core/src/net/sf/openrocket/util/TestRockets.java b/core/src/net/sf/openrocket/util/TestRockets.java index 9c5ed9b77..6ca8ecc7b 100644 --- a/core/src/net/sf/openrocket/util/TestRockets.java +++ b/core/src/net/sf/openrocket/util/TestRockets.java @@ -188,7 +188,6 @@ public class TestRockets { NoseCone nosecone; BodyTube bodytube; - rocket = new Rocket(); stage = new AxialStage(); stage.setName("Stage1"); @@ -371,23 +370,25 @@ public class TestRockets { return values[rnd.nextInt(values.length)]; } + public final static String ESTES_ALPHA_III_FCID_1="test_config #1: A8-0"; + public final static String ESTES_ALPHA_III_FCID_2="test_config #2: B4-3"; + public final static String ESTES_ALPHA_III_FCID_3="test_config #3: C6-3"; + public final static String ESTES_ALPHA_III_FCID_4="test_config #4: C6-5"; + public final static String ESTES_ALPHA_III_FCID_5="test_config #5: C6-7"; + // This is a Estes Alpha III // http://www.rocketreviews.com/alpha-iii---estes-221256.html // It is picked as a standard, simple, validation rocket. // This function is used for unit, integration tests, DO NOT CHANGE (without updating tests). public static final Rocket makeEstesAlphaIII(){ Rocket rocket = new Rocket(); - FlightConfigurationId fcid[] = new FlightConfigurationId[5]; - fcid[0] = new FlightConfigurationId(); - rocket.createFlightConfiguration(fcid[0]); - fcid[1] = new FlightConfigurationId(); - rocket.createFlightConfiguration(fcid[1]); - fcid[2] = new FlightConfigurationId(); - rocket.createFlightConfiguration(fcid[2]); - fcid[3] = new FlightConfigurationId(); - rocket.createFlightConfiguration(fcid[3]); - fcid[4] = new FlightConfigurationId(); - rocket.createFlightConfiguration(fcid[4]); + FlightConfigurationId fcid[] = new FlightConfigurationId[5]; + fcid[0] = rocket.createFlightConfiguration( new FlightConfigurationId( ESTES_ALPHA_III_FCID_1 )); + fcid[1] = rocket.createFlightConfiguration( new FlightConfigurationId( ESTES_ALPHA_III_FCID_2 )); + fcid[2] = rocket.createFlightConfiguration( new FlightConfigurationId( ESTES_ALPHA_III_FCID_3 )); + fcid[3] = rocket.createFlightConfiguration( new FlightConfigurationId( ESTES_ALPHA_III_FCID_4 )); + fcid[4] = rocket.createFlightConfiguration( new FlightConfigurationId( ESTES_ALPHA_III_FCID_5 )); + rocket.setName("Estes Alpha III / Code Verification Rocket"); AxialStage stage = new AxialStage(); @@ -515,8 +516,9 @@ public class TestRockets { bodytube.setMaterial(material); finset.setMaterial(material); - rocket.setSelectedConfiguration( fcid[0] ); - rocket.getSelectedConfiguration().setAllStages(); + + // preserve default default configuration of rocket -- to test what the default is set to upon initialization. + rocket.enableEvents(); return rocket; } @@ -1018,13 +1020,15 @@ public class TestRockets { return rocket; } + public final static String FALCON_9_FCID_1="test_config #1: [ M1350, G77]"; + + // This function is used for unit, integration tests, DO NOT CHANGE (without updating tests). public static Rocket makeFalcon9Heavy() { Rocket rocket = new Rocket(); rocket.setName("Falcon9H Scale Rocket"); - FlightConfiguration selConfig = rocket.createFlightConfiguration(null); - FlightConfigurationId selFCID = selConfig.getFlightConfigurationID(); + FlightConfigurationId selFCID = rocket.createFlightConfiguration( new FlightConfigurationId( FALCON_9_FCID_1 )); rocket.setSelectedConfiguration(selFCID); // ====== Payload Stage ====== @@ -1104,7 +1108,7 @@ public class TestRockets { Motor mtr = TestRockets.generateMotor_M1350_75mm(); motorConfig.setMotor( mtr); coreBody.setMotorMount( true); - FlightConfigurationId motorConfigId = selConfig.getFlightConfigurationID(); + FlightConfigurationId motorConfigId = selFCID; coreBody.setMotorConfig( motorConfig, motorConfigId); } @@ -1162,7 +1166,7 @@ public class TestRockets { boosterMotorTubes.setClusterScale(1.0); boosterBody.addChild( boosterMotorTubes); - FlightConfigurationId motorConfigId = selConfig.getFlightConfigurationID(); + FlightConfigurationId motorConfigId = selFCID; MotorConfiguration motorConfig = new MotorConfiguration( boosterMotorTubes, selFCID); Motor mtr = TestRockets.generateMotor_G77_29mm(); motorConfig.setMotor(mtr); @@ -1174,7 +1178,7 @@ public class TestRockets { rocket.enableEvents(); rocket.setSelectedConfiguration( selFCID); - selConfig.setAllStages(); + rocket.getFlightConfiguration( selFCID).setAllStages(); return rocket; } diff --git a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java index c737c6923..c160fb78f 100644 --- a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java +++ b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java @@ -1,16 +1,18 @@ package net.sf.openrocket.masscalc; -//import junit.framework.TestCase; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import org.junit.Test; -import net.sf.openrocket.masscalc.MassCalculator.MassCalcType; import net.sf.openrocket.motor.Motor; +import net.sf.openrocket.rocketcomponent.AxialStage; import net.sf.openrocket.rocketcomponent.BodyTube; import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.FlightConfigurationId; import net.sf.openrocket.rocketcomponent.InnerTube; +import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.NoseCone; import net.sf.openrocket.rocketcomponent.ParallelStage; import net.sf.openrocket.rocketcomponent.Rocket; @@ -22,19 +24,27 @@ import net.sf.openrocket.util.BaseTestCase.BaseTestCase; public class MassCalculatorTest extends BaseTestCase { // tolerance for compared double test results - protected final double EPSILON = 0.000001; + private static final double EPSILON = 0.000001; + + private static final double G77_MASS_LAUNCH = 0.123; + private static final double G77_MASS_SPENT = 0.064; + + + private static final double M1350_MASS_LAUNCH = 4.808; + private static final double M1350_MASS_SPENT = 1.970; + + + private static final double BOOSTER_SET_NO_MOTORS_MASS = 0.4555128227852; + private static final double BOOSTER_SET_NO_MOTORS_CMX = 1.246297525; + private static final double BOOSTER_SET_SPENT_MASS = BOOSTER_SET_NO_MOTORS_MASS + G77_MASS_SPENT*8; - private final double BOOSTER_NOSE_MASS_EA = 0.0222459863653; - private final double BOOSTER_BODY_MASS_EA = 0.129886006; - private final double BOOSTER_MMT_MASS_EA = 0.01890610458; - private final double BOOSTER_TOTAL_DRY_MASS_EACH = 0.4555128227852; - private final double BOOSTER_TOTAL_DRY_CMX = 1.246297525; - private final double G77_MASS_LAUNCH = 0.123; @Test public void testRocketNoMotors() { Rocket rkt = TestRockets.makeNoMotorRocket(); + FlightConfiguration config = rkt.getEmptyConfiguration(); + config.setAllStages(); rkt.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); // String treeDump = rkt.toDebugTree(); @@ -42,23 +52,21 @@ public class MassCalculatorTest extends BaseTestCase { // Validate Boosters MassCalculator mc = new MassCalculator(); - //mc.debug = true; - Coordinate rocketCM = mc.getCM( rkt.getSelectedConfiguration(), MassCalcType.NO_MOTORS); + // any config will do, beceause the rocket literally has no defined motors. + Coordinate rocketCM = mc.getRocketSpentMassData( config).getCM( ); 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); + assertEquals("Simple Motor Rocket mass incorrect: ", expMass, calcMass, EPSILON); + assertEquals("Simple Rocket CM.x is incorrect: ", expCM.x, rocketCM.x, EPSILON); + assertEquals("Simple Rocket CM.y is incorrect: ", expCM.y, rocketCM.y, EPSILON); + assertEquals("Simple Rocket CM.z is incorrect: ", expCM.z, rocketCM.z, EPSILON); + assertEquals("Simple Rocket CM is incorrect: ", expCM, rocketCM); - rocketCM = mc.getCM( rkt.getSelectedConfiguration(), MassCalcType.LAUNCH_MASS); - assertEquals(" Delta Heavy Booster CM is incorrect: ", expCM, rocketCM); - rocketCM = mc.getCM( rkt.getSelectedConfiguration(), MassCalcType.BURNOUT_MASS); - assertEquals(" Delta Heavy Booster CM is incorrect: ", expCM, rocketCM); + rocketCM = mc.getRocketLaunchMassData(config).getCM( ); + assertEquals("Simple Rocket w/no Motors: CM is incorrect: ", expCM, rocketCM); } @Test @@ -129,18 +137,18 @@ public class MassCalculatorTest extends BaseTestCase { // ====== ====== ====== ParallelStage boosters = (ParallelStage) rkt.getChild(1).getChild(1); { - expMass = BOOSTER_NOSE_MASS_EA; + expMass = 0.0222459863653; // think of the casts as an assert that ( child instanceof NoseCone) == true NoseCone nose = (NoseCone) boosters.getChild(0); compMass = nose.getComponentMass(); assertEquals( nose.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON); - expMass = BOOSTER_BODY_MASS_EA; + expMass = 0.129886006; BodyTube body = (BodyTube) boosters.getChild(1); compMass = body.getComponentMass(); assertEquals( body.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON); - expMass = BOOSTER_MMT_MASS_EA; + expMass = 0.01890610458; InnerTube mmt = (InnerTube)boosters.getChild(1).getChild(0); compMass = mmt.getComponentMass(); assertEquals( mmt.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON); @@ -149,8 +157,12 @@ public class MassCalculatorTest extends BaseTestCase { @Test public void testComponentMOIs() { - Rocket rkt = TestRockets.makeFalcon9Heavy(); - rkt.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); + Rocket rocket = TestRockets.makeFalcon9Heavy(); + rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); + + FlightConfiguration emptyConfig = rocket.getEmptyConfiguration(); + rocket.setSelectedConfiguration( emptyConfig.getFlightConfigurationID() ); + double expInertia; RocketComponent cc; @@ -160,14 +172,14 @@ public class MassCalculatorTest extends BaseTestCase { // ====== ====== ====== ====== { expInertia = 3.1698055283e-5; - cc= rkt.getChild(0).getChild(0); + cc= rocket.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); + cc= rocket.getChild(0).getChild(1); expInertia = 7.70416e-5; compInertia = cc.getRotationalInertia(); assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); @@ -175,7 +187,7 @@ public class MassCalculatorTest extends BaseTestCase { compInertia = cc.getLongitudinalInertia(); assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); - cc= rkt.getChild(0).getChild(2); + cc= rocket.getChild(0).getChild(2); expInertia = 1.43691e-5; compInertia = cc.getRotationalInertia(); assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); @@ -183,7 +195,7 @@ public class MassCalculatorTest extends BaseTestCase { compInertia = cc.getLongitudinalInertia(); assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); - cc= rkt.getChild(0).getChild(3); + cc= rocket.getChild(0).getChild(3); expInertia = 4.22073e-5; compInertia = cc.getRotationalInertia(); assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); @@ -192,7 +204,7 @@ public class MassCalculatorTest extends BaseTestCase { assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); { - cc= rkt.getChild(0).getChild(3).getChild(0); + cc= rocket.getChild(0).getChild(3).getChild(0); expInertia = 6.23121e-7; compInertia = cc.getRotationalInertia(); assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); @@ -200,7 +212,7 @@ public class MassCalculatorTest extends BaseTestCase { compInertia = cc.getLongitudinalInertia(); assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); - cc= rkt.getChild(0).getChild(3).getChild(1); + cc= rocket.getChild(0).getChild(3).getChild(1); expInertia = 5.625e-8; compInertia = cc.getRotationalInertia(); assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); @@ -209,7 +221,7 @@ public class MassCalculatorTest extends BaseTestCase { assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); } - cc= rkt.getChild(0).getChild(4); + cc= rocket.getChild(0).getChild(4); expInertia = 2.81382e-5; compInertia = cc.getRotationalInertia(); assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); @@ -221,7 +233,7 @@ public class MassCalculatorTest extends BaseTestCase { // ====== Core Stage ====== // ====== ====== ====== { - cc= rkt.getChild(1).getChild(0); + cc= rocket.getChild(1).getChild(0); expInertia = 0.000187588; compInertia = cc.getRotationalInertia(); assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); @@ -229,7 +241,7 @@ public class MassCalculatorTest extends BaseTestCase { compInertia = cc.getLongitudinalInertia(); assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); - cc= rkt.getChild(1).getChild(0).getChild(0); + cc= rocket.getChild(1).getChild(0).getChild(0); expInertia = 0.00734753; compInertia = cc.getRotationalInertia(); assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); @@ -241,7 +253,7 @@ public class MassCalculatorTest extends BaseTestCase { // ====== Booster Set Stage ====== // ====== ====== ====== - ParallelStage boosters = (ParallelStage) rkt.getChild(1).getChild(1); + ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(1); { cc= boosters.getChild(0); expInertia = 1.82665797857e-5; @@ -268,6 +280,127 @@ public class MassCalculatorTest extends BaseTestCase { assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON); } } + + + @Test + public void testPropellantMasses() { + Rocket rocket = TestRockets.makeFalcon9Heavy(); + rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); + + FlightConfiguration config = rocket.getFlightConfiguration( new FlightConfigurationId( TestRockets.FALCON_9_FCID_1) ); + config.setAllStages(); + + MassCalculator calc = new MassCalculator(); + { // test core stage motors + AxialStage core = (AxialStage) rocket.getChild(1); + final int coreNum = core.getStageNumber(); + config.setOnlyStage( coreNum); + + MassData corePropInertia = calc.calculatePropellantMassData(config); + final Coordinate actCM= corePropInertia.getCM(); + final double actCorePropMass = corePropInertia.getMass(); + final MotorMount mnt = (MotorMount)core.getChild(0); + final Motor coreMotor = mnt.getMotorConfig( config.getFlightConfigurationID()).getMotor(); + + final double expCorePropMassEach = M1350_MASS_LAUNCH - M1350_MASS_SPENT; + final double coreMotorCount = 1.; + final double expCorePropMass = expCorePropMassEach * coreMotorCount; + + final Coordinate expCM = new Coordinate( 1.053, 0, 0, expCorePropMass); + + assertEquals(core.getName()+" => "+coreMotor.getDesignation()+" propellant mass is incorrect: ", expCorePropMass, actCorePropMass, EPSILON); + assertEquals(core.getName()+" => "+coreMotor.getDesignation()+" propellant CoM x is incorrect: ", expCM.x, actCM.x, EPSILON); + assertEquals(core.getName()+" => "+coreMotor.getDesignation()+" propellant CoM y is incorrect: ", expCM.y, actCM.y, EPSILON); + assertEquals(core.getName()+" => "+coreMotor.getDesignation()+" propellant CoM z is incorrect: ", expCM.z, actCM.z, EPSILON); + + + } + + { // test booster stage motors + ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(1); + final int boostNum = boosters.getStageNumber(); + config.setOnlyStage( boostNum); + + MassData boosterPropInertia = calc.calculatePropellantMassData(config); + final Coordinate actCM= boosterPropInertia.getCM(); + final double actBoosterPropMass = boosterPropInertia.getMass(); + final MotorMount mnt = (MotorMount)boosters.getChild(1).getChild(0); + final Motor boosterMotor = mnt.getMotorConfig( config.getFlightConfigurationID()).getMotor(); + + final double expBoosterPropMassEach = G77_MASS_LAUNCH - G77_MASS_SPENT; + final double boosterSetMotorCount = 8.; /// it's a double merely to prevent type-casting issues + final double expBoosterPropMass = expBoosterPropMassEach * boosterSetMotorCount; + + final Coordinate expCM = new Coordinate( 1.31434, 0, 0, expBoosterPropMass); + + assertEquals( boosters.getName()+" => "+boosterMotor.getDesignation()+" propellant mass is incorrect: ", expBoosterPropMass, actBoosterPropMass, EPSILON); + assertEquals( boosters.getName()+" => "+boosterMotor.getDesignation()+" propellant CoM x is incorrect: ", expCM.x, actCM.x, EPSILON); + assertEquals( boosters.getName()+" => "+boosterMotor.getDesignation()+" propellant CoM y is incorrect: ", expCM.y, actCM.y, EPSILON); + assertEquals( boosters.getName()+" => "+boosterMotor.getDesignation()+" propellant CoM z is incorrect: ", expCM.z, actCM.z, EPSILON); + } + + + } + + @Test + public void testPropellantMOIs() { + Rocket rocket = TestRockets.makeFalcon9Heavy(); + rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); + + FlightConfiguration config = rocket.getFlightConfiguration( new FlightConfigurationId( TestRockets.FALCON_9_FCID_1) ); + + { // test core stage motors + AxialStage core = (AxialStage) rocket.getChild(1); + final int coreNum = core.getStageNumber(); + config.setOnlyStage( coreNum); + + MassCalculator calc = new MassCalculator(); + MassData corePropInertia = calc.calculatePropellantMassData(config); + final double actCorePropMass = corePropInertia.getMass(); + final MotorMount mnt = (MotorMount)core.getChild(0); + final Motor coreMotor = mnt.getMotorConfig( config.getFlightConfigurationID()).getMotor(); + + // validated against a specific motor/radius/length + final double expIxxEach = 0.00199546875; + final double expIyyEach = 0.092495800375; + + final double actIxxEach = coreMotor.getUnitIxx()*actCorePropMass; + final double actIyyEach = coreMotor.getUnitIyy()*actCorePropMass; + final double coreMotorCount = mnt.getInstanceCount(); + final double actCorePropIxx = actIxxEach*coreMotorCount; + final double actCorePropIyy = actIyyEach*coreMotorCount; + + assertEquals(core.getName()+" propellant axial MOI is incorrect: ", expIxxEach, actCorePropIxx, EPSILON); + assertEquals(core.getName()+" propellant longitudinal MOI is incorrect: ", expIyyEach, actCorePropIyy, EPSILON); + } + + { // test booster stage motors + ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(1); + final int boostNum = boosters.getStageNumber(); + config.setOnlyStage( boostNum); + + MassCalculator calc = new MassCalculator(); + MassData boosterPropInertia = calc.calculatePropellantMassData(config); + final double actBoosterPropMass = boosterPropInertia.getMass(); + final MotorMount mnt = (MotorMount)boosters.getChild(1).getChild(0); + final Motor boosterMotor = mnt.getMotorConfig( config.getFlightConfigurationID()).getMotor(); + + final double expBrIxxEach = 3.96952E-4; + final double expBrIyyEach = 0.005036790; + + final double actIxxEach = boosterMotor.getUnitIxx()*actBoosterPropMass; + final double actIyyEach = boosterMotor.getUnitIyy()*actBoosterPropMass; + final int boosterMotorCount = mnt.getInstanceCount(); + assertThat( boosters.getName()+" booster motor count is not as expected! ", boosterMotorCount, equalTo(8)); + final double actBoosterPropIxx = actIxxEach*boosterMotorCount; + final double actBoosterPropIyy = actIyyEach*boosterMotorCount; + + assertEquals(boosters.getName()+" propellant axial MOI is incorrect: ", expBrIxxEach, actBoosterPropIxx, EPSILON); + assertEquals(boosters.getName()+" propellant longitudinal MOI is incorrect: ", expBrIyyEach, actBoosterPropIyy, EPSILON); + } + + } + @Test public void testBoosterStructureCM() { Rocket rocket = TestRockets.makeFalcon9Heavy(); @@ -276,22 +409,24 @@ public class MassCalculatorTest extends BaseTestCase { ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(1); int boostNum = boosters.getStageNumber(); - rocket.getSelectedConfiguration().setOnlyStage( boostNum); + FlightConfiguration config = rocket.getEmptyConfiguration(); + config.setOnlyStage( boostNum); // Validate Boosters MassCalculator mc = new MassCalculator(); - Coordinate boosterSetCM = mc.getCM( rocket.getSelectedConfiguration(), MassCalcType.NO_MOTORS); - - double expMass = BOOSTER_TOTAL_DRY_MASS_EACH; - double expCMx = BOOSTER_TOTAL_DRY_CMX; - double calcMass = boosterSetCM.weight; + MassData md = mc.calculateBurnoutMassData( config); + Coordinate actCM = md.getCM(); + + double expMass = BOOSTER_SET_NO_MOTORS_MASS; + double expCMx = BOOSTER_SET_NO_MOTORS_CMX; + double calcMass = actCM.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); + assertEquals(" Delta Heavy Booster CM.x is incorrect: ", expCM.x, md.getCM().x, EPSILON); + assertEquals(" Delta Heavy Booster CM.y is incorrect: ", expCM.y, md.getCM().y, EPSILON); + assertEquals(" Delta Heavy Booster CM.z is incorrect: ", expCM.z, md.getCM().z, EPSILON); + assertEquals(" Delta Heavy Booster CM is incorrect: ", expCM, md.getCM() ); } @@ -300,7 +435,8 @@ public class MassCalculatorTest extends BaseTestCase { Rocket rocket = TestRockets.makeEstesAlphaIII(); InnerTube mmt = (InnerTube) rocket.getChild(0).getChild(1).getChild(2); - FlightConfigurationId fcid = rocket.getSelectedConfiguration().getId(); + FlightConfiguration config = rocket.getFlightConfigurationByIndex(0, false); + FlightConfigurationId fcid = config.getFlightConfigurationID(); Motor activeMotor = mmt.getMotorConfig( fcid ).getMotor(); String desig = activeMotor.getDesignation(); @@ -311,153 +447,149 @@ public class MassCalculatorTest extends BaseTestCase { // Validate Booster Launch Mass MassCalculator mc = new MassCalculator(); - double actPropMass = mc.getPropellantMass( rocket.getSelectedConfiguration(), MassCalcType.LAUNCH_MASS); - + MassData propMassData = mc.calculatePropellantMassData( config); + double actPropMass = propMassData.getCM().weight; + double expPropMass = expLaunchMass - expSpentMass; assertEquals(" Motor Mass "+desig+" is incorrect: ", expPropMass, actPropMass, EPSILON); } - @Test - public void testBoosterMotorMass() { + public void testBoosterPropellantInertia() { Rocket rocket = TestRockets.makeFalcon9Heavy(); ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(1); int boostNum = boosters.getStageNumber(); - FlightConfiguration currentConfig = rocket.getSelectedConfiguration(); - currentConfig.setOnlyStage( boostNum); - -// String treeDump = rocket.toDebugTree(); -// System.err.println( treeDump); + FlightConfiguration config = rocket.getFlightConfiguration( new FlightConfigurationId( TestRockets.FALCON_9_FCID_1)); + config.setOnlyStage( boostNum); + InnerTube mmt = (InnerTube) boosters.getChild(1).getChild(0); { - InnerTube mmt = (InnerTube) boosters.getChild(1).getChild(0); double expX = (.564 + 0.8 - 0.150 ); double actX = mmt.getLocations()[0].x; assertEquals(" Booster motor mount tubes located incorrectly: ", expX, actX, EPSILON); } + { + // Validate Booster Propellant Mass + Motor mtr = mmt.getMotorConfig( config.getId()).getMotor(); + double propMassEach = mtr.getLaunchMass()-mtr.getBurnoutMass(); + MassCalculator mc = new MassCalculator(); + MassData propMotorData = mc.calculatePropellantMassData( config ); + Coordinate propCM = propMotorData.getCM(); + Coordinate expPropCM = new Coordinate(1.31434, 0, 0, propMassEach*2*4); + assertEquals(" Booster Prop Mass is incorrect: ", expPropCM.weight, propCM.weight, EPSILON); + assertEquals(" Booster Prop CM.x is incorrect: ", expPropCM.x, propCM.x, EPSILON); + assertEquals(" Booster Prop CM.y is incorrect: ", expPropCM.y, propCM.y, EPSILON); + assertEquals(" Booster Prop CM.z is incorrect: ", expPropCM.z, propCM.z, EPSILON); + assertEquals(" Booster Prop CM is incorrect: ", expPropCM, propCM); + } + } + + @Test + public void testBoosterSpentCM(){ + Rocket rocket = TestRockets.makeFalcon9Heavy(); + ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(1); + int boostNum = boosters.getStageNumber(); + FlightConfiguration config = rocket.getFlightConfiguration( new FlightConfigurationId( TestRockets.FALCON_9_FCID_1)); + config.setOnlyStage( boostNum); + { // Validate Booster Launch Mass MassCalculator mc = new MassCalculator(); - MassData launchMotorData = mc.getMotorMassData( rocket.getSelectedConfiguration(), MassCalcType.LAUNCH_MASS); - Coordinate launchCM = launchMotorData.getCM(); - // 1.214 = beginning of engine mmt - // 1.364-.062 = middle of engine: 1.302 - Coordinate expLaunchCM = new Coordinate(1.31434, 0, 0, 0.123*2*4); + MassData launchData = mc.calculateBurnoutMassData( config ); + Coordinate launchCM = launchData.getCM(); + double expLaunchCMx = 1.2823050552779347; + double expLaunchMass = BOOSTER_SET_SPENT_MASS; + Coordinate expLaunchCM = new Coordinate( expLaunchCMx, 0, 0, expLaunchMass); assertEquals(" Booster Launch Mass is incorrect: ", expLaunchCM.weight, launchCM.weight, EPSILON); assertEquals(" Booster Launch CM.x is incorrect: ", expLaunchCM.x, launchCM.x, EPSILON); assertEquals(" Booster Launch CM.y is incorrect: ", expLaunchCM.y, launchCM.y, EPSILON); assertEquals(" Booster Launch CM.z is incorrect: ", expLaunchCM.z, launchCM.z, EPSILON); assertEquals(" Booster Launch CM is incorrect: ", expLaunchCM, launchCM); - - - MassData spentMotorData = mc.getMotorMassData( rocket.getSelectedConfiguration(), MassCalcType.BURNOUT_MASS); - Coordinate spentCM = spentMotorData.getCM(); - Coordinate expSpentCM = new Coordinate(1.31434, 0, 0, 0.064*2*4); - assertEquals(" Booster Spent Mass is incorrect: ", expSpentCM.weight, spentCM.weight, EPSILON); - assertEquals(" Booster Launch CM.x is incorrect: ", expSpentCM.x, spentCM.x, EPSILON); - assertEquals(" Booster Launch CM.y is incorrect: ", expSpentCM.y, spentCM.y, EPSILON); - assertEquals(" Booster Launch CM.z is incorrect: ", expSpentCM.z, spentCM.z, EPSILON); - assertEquals(" Booster Launch CM is incorrect: ", expSpentCM, spentCM); } - } @Test - public void testBoosterTotalCM() { + public void testBoosterLaunchCM() { Rocket rocket = TestRockets.makeFalcon9Heavy(); - ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(1); int boostNum = boosters.getStageNumber(); - rocket.getSelectedConfiguration().setOnlyStage( boostNum); + FlightConfiguration config = rocket.getFlightConfiguration( new FlightConfigurationId( TestRockets.FALCON_9_FCID_1)); + config.setOnlyStage( boostNum); { // Validate Booster Launch Mass MassCalculator mc = new MassCalculator(); - Coordinate boosterSetCM = mc.getCM( rocket.getSelectedConfiguration(), MassCalcType.LAUNCH_MASS); - double calcTotalMass = boosterSetCM.weight; + Coordinate boosterSetLaunchCM = mc.getRocketLaunchMassData( rocket.getSelectedConfiguration()).getCM(); + double calcTotalMass = boosterSetLaunchCM.weight; - double expTotalMass = BOOSTER_TOTAL_DRY_MASS_EACH + 8*G77_MASS_LAUNCH; + double expTotalMass = BOOSTER_SET_NO_MOTORS_MASS + 2*4*G77_MASS_LAUNCH; assertEquals(" Booster Launch Mass is incorrect: ", expTotalMass, calcTotalMass, EPSILON); double expX = 1.292808951; 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); - } - { - // Validate Booster Burnout Mass - MassCalculator mc = new MassCalculator(); - //mc.debug = true; - Coordinate boosterSetCM = mc.getCM( rocket.getSelectedConfiguration(), MassCalcType.BURNOUT_MASS); - double calcTotalMass = boosterSetCM.weight; - - double expTotalMass = BOOSTER_TOTAL_DRY_MASS_EACH + 8*0.064; - assertEquals(" Booster Burnout Mass is incorrect: ", expTotalMass, calcTotalMass, EPSILON); - - double expX = 1.282305055; - Coordinate expCM = new Coordinate(expX,0,0, expTotalMass); - assertEquals(" Booster Burnout CM.x is incorrect: ", expCM.x, boosterSetCM.x, EPSILON); - assertEquals(" Booster Burnout CM.y is incorrect: ", expCM.y, boosterSetCM.y, EPSILON); - assertEquals(" Booster Burnout CM.z is incorrect: ", expCM.z, boosterSetCM.z, EPSILON); - assertEquals(" Booster Burnout CM is incorrect: ", expCM, boosterSetCM); + assertEquals(" Booster Launch CM.x is incorrect: ", expCM.x, boosterSetLaunchCM.x, EPSILON); + assertEquals(" Booster Launch CM.y is incorrect: ", expCM.y, boosterSetLaunchCM.y, EPSILON); + assertEquals(" Booster Launch CM.z is incorrect: ", expCM.z, boosterSetLaunchCM.z, EPSILON); + assertEquals(" Booster Launch CM is incorrect: ", expCM, boosterSetLaunchCM); } } @Test - public void testBoosterStructureMOI() { + public void testBoosterSpentMOIs() { Rocket rocket = TestRockets.makeFalcon9Heavy(); rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); - FlightConfiguration defaultConfig = rocket.getSelectedConfiguration(); - + FlightConfiguration config = rocket.getFlightConfiguration( new FlightConfigurationId( TestRockets.FALCON_9_FCID_1)); ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(1); int boostNum = boosters.getStageNumber(); - - rocket.getSelectedConfiguration().setOnlyStage( boostNum); + config.setOnlyStage( boostNum); // Validate Boosters MassCalculator mc = new MassCalculator(); - double expMOI_axial = .00304203; - 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.129566277; - double boosterMOI_tr= mc.getLongitudinalInertia( defaultConfig, MassCalcType.NO_MOTORS); + MassData spent = mc.calculateBurnoutMassData( config); + + double expMOIRotational = .0062065449; + double boosterMOIRotational = spent.getRotationalInertia(); + assertEquals(" Booster x-axis MOI is incorrect: ", expMOIRotational, boosterMOIRotational, EPSILON); + + double expMOI_tr = 0.13136525; + double boosterMOI_tr= spent.getLongitudinalInertia(); assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); } @Test - public void testBoosterTotalMOI() { + public void testBoosterLaunchMOIs() { Rocket rocket = TestRockets.makeFalcon9Heavy(); - FlightConfiguration defaultConfig = rocket.getSelectedConfiguration(); rocket.setName("TestRocket:F9H:Total_MOI"); - + FlightConfiguration config = rocket.getFlightConfiguration( new FlightConfigurationId( TestRockets.FALCON_9_FCID_1)); + ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(1); int boostNum = boosters.getStageNumber(); - - rocket.getSelectedConfiguration().setOnlyStage( boostNum); + config.setOnlyStage( boostNum); // Validate Boosters MassCalculator mc = new MassCalculator(); - final double expMOI_axial = 0.0516919744; - final double boosterMOI_xx= mc.getRotationalInertia( defaultConfig, MassCalcType.LAUNCH_MASS); + - final double expMOI_tr = 0.141508294; - final double boosterMOI_tr= mc.getLongitudinalInertia( defaultConfig, MassCalcType.LAUNCH_MASS); + final MassData launch= mc.getRocketLaunchMassData( config); + final double expIxx = 0.00912327349; + final double actIxx= launch.getRotationalInertia(); + final double expIyy = 0.132320; + final double actIyy= launch.getLongitudinalInertia(); - assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON); - assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); + assertEquals(" Booster x-axis MOI is incorrect: ", expIxx, actIxx, EPSILON); + assertEquals(" Booster transverse MOI is incorrect: ", expIyy, actIyy, EPSILON); } @Test public void testStageMassOverride() { Rocket rocket = TestRockets.makeFalcon9Heavy(); - FlightConfiguration config = rocket.getSelectedConfiguration(); rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); - + FlightConfiguration config = rocket.getEmptyConfiguration(); + rocket.setSelectedConfiguration( config.getId() ); + ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(1); int boostNum = boosters.getStageNumber(); config.setOnlyStage( boostNum); @@ -471,7 +603,9 @@ public class MassCalculatorTest extends BaseTestCase { { // Validate Mass MassCalculator mc = new MassCalculator(); - Coordinate boosterSetCM = mc.getCM( rocket.getSelectedConfiguration(), MassCalcType.NO_MOTORS); + + MassData burnout = mc.calculateBurnoutMassData( config); + Coordinate boosterSetCM = burnout.getCM(); double calcTotalMass = boosterSetCM.weight; double expTotalMass = overrideMass; @@ -486,11 +620,11 @@ public class MassCalculatorTest extends BaseTestCase { // Validate MOI double expMOI_axial = .00333912717; - double boosterMOI_xx= mc.getRotationalInertia( config, MassCalcType.NO_MOTORS); + double boosterMOI_xx= burnout.getRotationalInertia(); assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON); double expMOI_tr = 0.142220231; - double boosterMOI_tr= mc.getLongitudinalInertia( config, MassCalcType.NO_MOTORS); + double boosterMOI_tr= burnout.getLongitudinalInertia(); assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); } @@ -499,9 +633,11 @@ public class MassCalculatorTest extends BaseTestCase { @Test public void testComponentMassOverride() { Rocket rocket = TestRockets.makeFalcon9Heavy(); - FlightConfiguration config = rocket.getSelectedConfiguration(); rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); - + + FlightConfiguration config = rocket.getEmptyConfiguration(); + rocket.setSelectedConfiguration( config.getId() ); + ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(1); int boostNum = boosters.getStageNumber(); config.setOnlyStage( boostNum); @@ -521,7 +657,8 @@ public class MassCalculatorTest extends BaseTestCase { { // Validate Mass MassCalculator mc = new MassCalculator(); - Coordinate boosterSetCM = mc.getCM( rocket.getSelectedConfiguration(), MassCalcType.NO_MOTORS); + MassData burnout = mc.calculateBurnoutMassData( config); + Coordinate boosterSetCM = burnout.getCM(); double calcTotalMass = boosterSetCM.weight; double expTotalMass = 4.368; @@ -536,11 +673,11 @@ public class MassCalculatorTest extends BaseTestCase { // Validate MOI double expMOI_axial = 0.0257485; - double boosterMOI_xx= mc.getRotationalInertia( config, MassCalcType.NO_MOTORS); + double boosterMOI_xx= burnout.getRotationalInertia(); assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON); double expMOI_tr = 1.633216231; - double boosterMOI_tr= mc.getLongitudinalInertia( config, MassCalcType.NO_MOTORS); + double boosterMOI_tr= burnout.getLongitudinalInertia(); assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); } @@ -549,9 +686,10 @@ public class MassCalculatorTest extends BaseTestCase { @Test public void testCMOverride() { Rocket rocket = TestRockets.makeFalcon9Heavy(); - FlightConfiguration config = rocket.getSelectedConfiguration(); rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); - + FlightConfiguration config = rocket.getEmptyConfiguration(); + rocket.setSelectedConfiguration( config.getId() ); + ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(1); int boostNum = boosters.getStageNumber(); config.setOnlyStage( boostNum); @@ -571,10 +709,11 @@ public class MassCalculatorTest extends BaseTestCase { { // Validate Mass MassCalculator mc = new MassCalculator(); - //mc.debug = true; - Coordinate boosterSetCM = mc.getCM( rocket.getSelectedConfiguration(), MassCalcType.NO_MOTORS); - double expMass = BOOSTER_TOTAL_DRY_MASS_EACH; + MassData burnout = mc.calculateBurnoutMassData( config); + Coordinate boosterSetCM = burnout.getCM(); + + double expMass = BOOSTER_SET_NO_MOTORS_MASS; double calcTotalMass = boosterSetCM.weight; assertEquals(" Booster Launch Mass is incorrect: ", expMass, calcTotalMass, EPSILON); @@ -587,11 +726,11 @@ public class MassCalculatorTest extends BaseTestCase { // Validate MOI double expMOI_axial = 0.00304203; - double boosterMOI_xx= mc.getRotationalInertia( config, MassCalcType.NO_MOTORS); + double boosterMOI_xx= burnout.getRotationalInertia(); assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON); double expMOI_tr = 0.1893499746; - double boosterMOI_tr= mc.getLongitudinalInertia( config, MassCalcType.NO_MOTORS); + double boosterMOI_tr= burnout.getLongitudinalInertia(); assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); } diff --git a/core/test/net/sf/openrocket/motor/ThrustCurveMotorTest.java b/core/test/net/sf/openrocket/motor/ThrustCurveMotorTest.java index f4cf11ac4..f6ccbe773 100644 --- a/core/test/net/sf/openrocket/motor/ThrustCurveMotorTest.java +++ b/core/test/net/sf/openrocket/motor/ThrustCurveMotorTest.java @@ -84,12 +84,12 @@ public class ThrustCurveMotorTest { public void testVerifyMotorA8_3CG(){ final ThrustCurveMotor mtr = motorEstesA8_3; - final double actCGx0p041 = mtr.getCGx(0.041); + final double actCGx0p041 = mtr.getCMx(0.041); assertEquals( 0.0352, actCGx0p041, 0.001 ); final double actMass0p041 = mtr.getTotalMass( 0.041 ); assertEquals( 0.016335, actMass0p041, 0.001 ); - final double actCGx0p206 = mtr.getCGx( 0.206 ); + final double actCGx0p206 = mtr.getCMx( 0.206 ); assertEquals( 0.0362, actCGx0p206, 0.001 ); final double actMass0p206 = mtr.getTotalMass( 0.206 ); assertEquals( 0.015285, actMass0p206, 0.001 ); diff --git a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java index 47bcfb3e1..b7007a7cf 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FlightConfigurationTest.java @@ -142,6 +142,20 @@ public class FlightConfigurationTest extends BaseTestCase { config.setAllStages(); } + + /** + * Single stage rocket specific configuration tests + */ + @Test + public void testDefaultConfigurationIsEmpty() { + Rocket r1 = TestRockets.makeEstesAlphaIII(); + + // don't change the configuration: + FlightConfiguration defaultConfig = r1.getSelectedConfiguration(); + + assertThat( "Empty configuration has motors! it should be empty!", r1.getEmptyConfiguration().getActiveMotors().size(), equalTo(0)); + assertThat( "Default configuration is not the empty configuration. It should be!", defaultConfig.getActiveMotors().size(), equalTo(0)); + } @Test public void testCreateConfigurationNullId() { @@ -155,7 +169,7 @@ public class FlightConfigurationTest extends BaseTestCase { assertThat("number of loaded configuration counts doesn't actually match.", actualConfigCount, equalTo(expectedConfigCount)); // create with - rkt.createFlightConfiguration(null); + rkt.createFlightConfiguration( (FlightConfigurationId)null); expectedConfigCount = 6; actualConfigCount = rkt.getConfigurationCount(); assertThat("createFlightConfiguration with null: doesn't actually work.", actualConfigCount, equalTo(expectedConfigCount)); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java index 4dd50d50d..71d7eccba 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java @@ -55,7 +55,6 @@ import net.sf.openrocket.gui.scalefigure.RocketPanel; import net.sf.openrocket.gui.util.GUIUtil; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.masscalc.MassCalculator; -import net.sf.openrocket.masscalc.MassCalculator.MassCalcType; import net.sf.openrocket.motor.MotorConfiguration; import net.sf.openrocket.rocketcomponent.AxialStage; import net.sf.openrocket.rocketcomponent.FinSet; @@ -540,7 +539,7 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe Map aeroData = aerodynamicCalculator.getForceAnalysis(configuration, conditions, set); Map massData = - massCalculator.getCGAnalysis(configuration, MassCalcType.LAUNCH_MASS); + massCalculator.getCGAnalysis(configuration); cgData.clear(); @@ -579,7 +578,7 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe cgData.add(data); data[0] = motorConfig.getMotor().getDesignation(); - data[1] = MassCalcType.LAUNCH_MASS.getCG(motorConfig); + data[1] = motorConfig.getMotor().getLaunchMass(); } forces = aeroData.get(rkt); diff --git a/swing/src/net/sf/openrocket/gui/print/DesignReport.java b/swing/src/net/sf/openrocket/gui/print/DesignReport.java index 3428c0de5..0a56cce7d 100644 --- a/swing/src/net/sf/openrocket/gui/print/DesignReport.java +++ b/swing/src/net/sf/openrocket/gui/print/DesignReport.java @@ -28,7 +28,6 @@ import net.sf.openrocket.gui.figureelements.FigureElement; import net.sf.openrocket.gui.figureelements.RocketInfo; import net.sf.openrocket.gui.scalefigure.RocketPanel; import net.sf.openrocket.masscalc.MassCalculator; -import net.sf.openrocket.masscalc.MassCalculator.MassCalcType; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.rocketcomponent.AxialStage; import net.sf.openrocket.rocketcomponent.FlightConfiguration; @@ -329,7 +328,11 @@ public class DesignReport { MassCalculator massCalc = new MassCalculator(); - FlightConfiguration config = rocket.createFlightConfiguration(motorId); + if( !motorId.hasError() ){ + throw new IllegalStateException("Attempted to add motor data with an invalid fcid"); + } + rocket.createFlightConfiguration(motorId); + FlightConfiguration config = rocket.getFlightConfiguration( motorId); int totalMotorCount = 0; double totalPropMass = 0; @@ -346,7 +349,7 @@ public class DesignReport { config.clearAllStages(); config.setOnlyStage(stage); stage++; - stageMass = massCalc.getCG(config, MassCalcType.LAUNCH_MASS).weight; + stageMass = massCalc.getCGAnalysis( config).get(stage).weight; // Calculate total thrust-to-weight from only lowest stage motors totalTTW = 0; topBorder = true; diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java index 275c88523..d21e4bdfa 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java @@ -38,9 +38,9 @@ import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.Simulation; import net.sf.openrocket.gui.adaptors.DoubleModel; import net.sf.openrocket.gui.components.BasicSlider; +import net.sf.openrocket.gui.components.ConfigurationModel; import net.sf.openrocket.gui.components.StageSelector; import net.sf.openrocket.gui.components.UnitSelector; -import net.sf.openrocket.gui.components.ConfigurationModel; import net.sf.openrocket.gui.configdialog.ComponentConfigDialog; import net.sf.openrocket.gui.figure3d.RocketFigure3d; import net.sf.openrocket.gui.figureelements.CGCaret; @@ -52,7 +52,6 @@ import net.sf.openrocket.gui.simulation.SimulationWorker; import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.masscalc.MassCalculator; -import net.sf.openrocket.masscalc.MassCalculator.MassCalcType; import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; import net.sf.openrocket.rocketcomponent.ComponentChangeListener; import net.sf.openrocket.rocketcomponent.FlightConfiguration; @@ -599,7 +598,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change } extraText.setTheta(cpTheta); - cg = massCalculator.getCG(curConfig, MassCalcType.LAUNCH_MASS); + cg = massCalculator.getRocketLaunchMassData( curConfig).getCG(); if (cp.weight > MassCalculator.MIN_MASS){ @@ -644,7 +643,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change extraText.setLength(length); extraText.setDiameter(diameter); extraText.setMass(cg.weight); - extraText.setMassWithoutMotors( massCalculator.getCG( getSelectedConfiguration(), MassCalcType.NO_MOTORS ).weight ); + extraText.setMassWithoutMotors( massCalculator.getRocketSpentMassData( curConfig.getRocket().getEmptyConfiguration() ).getMass() ); extraText.setWarnings(warnings); if (figure.getType() == RocketPanel.VIEW_TYPE.SideView && length > 0) { diff --git a/swing/test/net/sf/openrocket/IntegrationTest.java b/swing/test/net/sf/openrocket/IntegrationTest.java index 1783228e8..f14fdec3e 100644 --- a/swing/test/net/sf/openrocket/IntegrationTest.java +++ b/swing/test/net/sf/openrocket/IntegrationTest.java @@ -13,6 +13,20 @@ import java.io.InputStream; import javax.swing.Action; +import org.jmock.Mockery; +import org.jmock.integration.junit4.JMock; +import org.jmock.integration.junit4.JUnit4Mockery; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.Provider; +import com.google.inject.util.Modules; + import net.sf.openrocket.aerodynamics.AerodynamicCalculator; import net.sf.openrocket.aerodynamics.BarrowmanCalculator; import net.sf.openrocket.aerodynamics.FlightConditions; @@ -29,31 +43,21 @@ import net.sf.openrocket.gui.main.UndoRedoAction; import net.sf.openrocket.l10n.DebugTranslator; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.masscalc.MassCalculator; -import net.sf.openrocket.masscalc.MassCalculator.MassCalcType; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.ThrustCurveMotor; import net.sf.openrocket.plugin.PluginModule; -import net.sf.openrocket.rocketcomponent.*; +import net.sf.openrocket.rocketcomponent.EngineBlock; +import net.sf.openrocket.rocketcomponent.FlightConfiguration; +import net.sf.openrocket.rocketcomponent.FlightConfigurationId; +import net.sf.openrocket.rocketcomponent.MassComponent; +import net.sf.openrocket.rocketcomponent.NoseCone; +import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.simulation.FlightDataType; import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.utils.CoreServicesModule; -import org.jmock.Mockery; -import org.jmock.integration.junit4.JMock; -import org.jmock.integration.junit4.JUnit4Mockery; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; - -import com.google.inject.AbstractModule; -import com.google.inject.Guice; -import com.google.inject.Injector; -import com.google.inject.Module; -import com.google.inject.Provider; -import com.google.inject.util.Modules; - /** * This class contains various integration tests that simulate user actions that * might be performed. @@ -325,7 +329,7 @@ public class IntegrationTest { private void checkCgCp(double cgx, double mass, double cpx, double cna) { Coordinate cg, cp; - cg = massCalc.getCG(config, MassCalcType.LAUNCH_MASS); + cg = massCalc.getRocketLaunchMassData(config).getCG(); assertEquals(cgx, cg.x, 0.001); assertEquals(mass, cg.weight, 0.0005);