diff --git a/core/src/net/sf/openrocket/masscalc/MassCalculation.java b/core/src/net/sf/openrocket/masscalc/MassCalculation.java index 9ecbacdc8..ca59e2019 100644 --- a/core/src/net/sf/openrocket/masscalc/MassCalculation.java +++ b/core/src/net/sf/openrocket/masscalc/MassCalculation.java @@ -246,6 +246,108 @@ public class MassCalculation { * */ /* package-scope */ MassCalculation calculateAssembly(){ + + if (this.type.includesStructure) { + MassCalculation structureCalc = this.copy(this.root, this.transform); + structureCalc.calculateStructure(); + this.merge(structureCalc); + } + + if (this.type.includesMotorCasing || this.type.includesPropellant) { + MassCalculation motorCalc = this.copy(this.root, this.transform); + motorCalc.calculateMotors(); + this.merge(motorCalc); + } + + return this; + } + + MassCalculation calculateStructure() { + final RocketComponent component = this.root; + final Transformation parentTransform = this.transform; + final int instanceCount = component.getInstanceCount(); + Coordinate[] instanceLocations = component.getInstanceLocations(); + +// // vvv DEBUG +// if( this.config.isComponentActive(component) ){ +// System.err.println(String.format( "%s[%s]....", prefix, component.getName())); +// } + + // iterate over the aggregated instances for the whole tree. + MassCalculation children = this.copy(component, parentTransform ); + for( int instanceNumber = 0; instanceNumber < instanceCount; ++instanceNumber) { + Coordinate currentLocation = instanceLocations[instanceNumber]; + Transformation currentTransform = parentTransform.applyTransformation( Transformation.getTranslationTransform( currentLocation )); + + for (RocketComponent child : component.getChildren()) { + // child data, relative to rocket reference frame + MassCalculation eachChild = copy( child, currentTransform); + + eachChild.prefix = prefix + "...."; + eachChild.calculateStructure(); + + // accumulate children's data + children.merge( eachChild ); + } + } + + if( 0 < children.getMass() ) { + this.merge( children ); +// // vvv DEBUG +// System.err.println(String.format( "%s....assembly mass (incl/children): %s", prefix, this.toCMDebug())); + } + + if (this.config.isComponentActive(component) ){ + Coordinate compCM = component.getComponentCG(); + + if (!component.getOverrideSubcomponents()) { + if (component.isMassOverridden()) { +// System.err.println("mass override=" + component.getOverrideMass()); + compCM = compCM.setWeight(MathUtil.max(component.getOverrideMass(), MIN_MASS)); + } + if (component.isCGOverridden()) + compCM = compCM.setXYZ(component.getOverrideCG()); + } + + // mass data for *this component only* in the rocket-frame + compCM = parentTransform.transform( compCM.add(component.getPosition()) ); + this.addMass( compCM ); + + double compIx = component.getRotationalUnitInertia() * compCM.weight; + double compIt = component.getLongitudinalUnitInertia() * compCM.weight; + RigidBody componentInertia = new RigidBody( compCM, compIx, compIt, compIt ); + + this.addInertia( componentInertia ); +// vvv DEBUG +// if( 0 < compCM.weight ) { +// System.err.println(String.format( "%s....componentData: %s", prefix, compCM.toPreciseString() )); +// } + + if (component.getOverrideSubcomponents()) { + if (component.isMassOverridden()) { + double newMass = MathUtil.max(component.getOverrideMass(), MIN_MASS); + Coordinate newCM = this.getCM().setWeight( newMass ); + this.setCM( newCM ); + } + + if (component.isCGOverridden()) { + Coordinate newCM = this.getCM().setX( component.getOverrideCGX() ); + this.setCM( newCM ); + } + } + } + +// // vvv DEBUG +// if( this.config.isComponentActive(component) && 0 < this.getMass() ) { +// System.err.println(String.format( "%s....<< return assemblyData: %s (tree @%s)", prefix, this.toCMDebug(), component.getName() )); +// } +// // ^^^ DEBUG + + return this; + } + + MassCalculation calculateMotors() { + final RocketComponent component = this.root; final Transformation parentTransform = this.transform; @@ -257,37 +359,14 @@ public class MassCalculation { // System.err.println(String.format( "%s[%s]....", prefix, component.getName())); // } - if( this.type.includesStructure && this.config.isComponentActive(component) ){ - Coordinate compCM = component.getCG(); - double compIx = component.getRotationalUnitInertia() * compCM.weight; - double compIt = component.getLongitudinalUnitInertia() * compCM.weight; - - if (!component.getOverrideSubcomponents()) { - if (component.isMassOverridden()) - compCM = compCM.setWeight(MathUtil.max(component.getOverrideMass(), MIN_MASS)); - if (component.isCGOverridden()) - compCM = compCM.setXYZ(component.getOverrideCG()); - } - - // mass data for *this component only* in the rocket-frame - compCM = parentTransform.transform( compCM.add(component.getPosition()) ); - this.addMass( compCM ); - - RigidBody componentInertia = new RigidBody( compCM, compIx, compIt, compIt ); - this.addInertia( componentInertia ); - -// if( 0 < compCM.weight ) { // vvv DEBUG -// System.err.println(String.format( "%s....componentData: %s", prefix, compCM.toPreciseString() )); -// } - } - - if( component.isMotorMount() && ( this.type.includesMotorCasing || this.type.includesPropellant )) { + if (component.isMotorMount()) { MassCalculation propellant = this.copy(component, parentTransform); propellant.calculateMountData(); this.merge( propellant ); - + +// // vvv DEBUG // if( 0 < propellant.getMass() ) { // System.err.println(String.format( "%s........++ propellantData: %s", prefix, propellant.toCMDebug())); // } @@ -304,7 +383,7 @@ public class MassCalculation { MassCalculation eachChild = copy( child, currentTransform); eachChild.prefix = prefix + "...."; - eachChild.calculateAssembly(); + eachChild.calculateMotors(); // accumulate children's data children.merge( eachChild ); @@ -316,25 +395,11 @@ public class MassCalculation { //System.err.println(String.format( "%s....assembly mass (incl/children): %s", prefix, this.toCMDebug())); } - // Override total data - if (component.getOverrideSubcomponents()) { - if (component.isMassOverridden()) { - double newMass = MathUtil.max(component.getOverrideMass(), MIN_MASS); - Coordinate newCM = this.getCM().setWeight( newMass ); - this.setCM( newCM ); - } - if (component.isCGOverridden()) { - Coordinate newCM = this.getCM().setX( component.getOverrideCGX() ); - this.setCM( newCM ); - } - } - - // vvv DEBUG - //if( this.config.isComponentActive(component) && 0 < this.getMass() ) { - //System.err.println(String.format( "%s....<< return assemblyData: %s (tree @%s)", prefix, this.toCMDebug(), component.getName() )); - // System.err.println(String.format( "%s Ixx = %.8f Iyy = %.8f", prefix, getIxx(), getIyy() )); - //} - // ^^^ DEBUG +// // vvv DEBUG +// if( this.config.isComponentActive(component) && 0 < this.getMass() ) { +// System.err.println(String.format( "%s....<< return assemblyData: %s (tree @%s)", prefix, this.toCMDebug(), component.getName() )); +// } +// ^^^ DEBUG return this; } @@ -350,11 +415,10 @@ public class MassCalculation { double Ir=0, It=0; for( final RigidBody eachLocal : this.bodies ){ final RigidBody eachGlobal = eachLocal.rebase( this.centerOfMass ); - Ir += eachGlobal.Ixx; It += eachGlobal.Iyy; } - + return new RigidBody( centerOfMass, Ir, It, It ); } diff --git a/core/src/net/sf/openrocket/masscalc/MassCalculator.java b/core/src/net/sf/openrocket/masscalc/MassCalculator.java index 404c98f30..08215cd4d 100644 --- a/core/src/net/sf/openrocket/masscalc/MassCalculator.java +++ b/core/src/net/sf/openrocket/masscalc/MassCalculator.java @@ -34,13 +34,13 @@ public class MassCalculator implements Monitorable { /** - * Calculates mass data of the rocket's burnout mass + * Calculates mass data of the rocket's structure * - includes structure - * - includes motors - * - for Black Powder & Composite motors, this generally *excludes* propellant + * - excludes motors + * - excludes propellant * * @param config the rocket configuration to calculate for - * @return the MassData struct of the motors at burnout + * @return the MassData struct of the rocket */ public static RigidBody calculateStructure( final FlightConfiguration config) { return calculate( MassCalculation.Type.STRUCTURE, config, Motor.PSEUDO_TIME_EMPTY ); @@ -53,29 +53,42 @@ public class MassCalculator implements Monitorable { * - for Black Powder & Composite motors, this generally *excludes* propellant * * @param config the rocket configuration to calculate for - * @return the MassData struct of the motors at burnout + * @return the MassData struct of the rocket at burnout */ public static RigidBody calculateBurnout( final FlightConfiguration config) { return calculate( MassCalculation.Type.BURNOUT, config, Motor.PSEUDO_TIME_BURNOUT ); } - + + /** + * Calculates mass data of the rocket's motor(s) at launch + * - excludes structure + * - includes motors + * - includes propellant + * + * @param config the rocket configuration to calculate for + * @return the MassData struct of the motors at launch + */ public static RigidBody calculateMotor( final FlightConfiguration config) { return calculate( MassCalculation.Type.MOTOR, config, Motor.PSEUDO_TIME_LAUNCH ); } /** - * Compute the burnout mass properties all motors, given a configuration - * - * Will calculate data for: MassCalcType.BURNOUT_MASS + * Compute the rocket's launch mass properties, given a configuration + * - includes structure + * - includes motors + * - includes propellant * * @param config the rocket configuration - * @return the MassData struct of the motors at burnout + * @return the MassData struct of the rocket at launch */ public static RigidBody calculateLaunch( final FlightConfiguration config ){ return calculate( MassCalculation.Type.LAUNCH, config, Motor.PSEUDO_TIME_LAUNCH ); } - /** calculates the massdata for all propellant in the rocket given the simulation status. + /** calculates the massdata for all motors in the rocket given the simulation status. + * - excludes structure + * - includes motors + * - includes propellant * * @param status CurrentSimulation status to calculate data with * @return combined mass data for all propellant diff --git a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java index ab56fcc62..66334245f 100644 --- a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java +++ b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java @@ -32,6 +32,7 @@ public class MassCalculatorTest extends BaseTestCase { @Test public void testAlphaIIIStructure() { + System.err.println("testing AlphaIII structure"); Rocket rocket = TestRockets.makeEstesAlphaIII(); rocket.setName("AlphaIII."+Thread.currentThread().getStackTrace()[1].getMethodName()); @@ -51,6 +52,33 @@ public class MassCalculatorTest extends BaseTestCase { assertEquals("Simple Rocket CM.y is incorrect: ", expCM.y, actualRocketDryCM.y, EPSILON); assertEquals("Simple Rocket CM.z is incorrect: ", expCM.z, actualRocketDryCM.z, EPSILON); assertEquals("Simple Rocket CM is incorrect: ", expCM, actualRocketDryCM); + + + double expMOIrot = 5.441190348849436E-5; + double expMOIlong = 3.03467093714368E-4; + + double actualMOIrot = actualStructure.getRotationalInertia(); + double actualMOIlong = actualStructure.getLongitudinalInertia(); + assertEquals("Alpha III Rotational MOI calculated incorrectly: ", expMOIrot, actualMOIrot, EPSILON); + assertEquals("Alpha III Longitudinal MOI calculated incorrectly: ", expMOIlong, actualMOIlong, EPSILON); + + // if we use a mass override, setting to same mass, we should get same result + System.err.println("calculating AlphaIII with mass override"); + AxialStage sustainer = (AxialStage) rocket.getChild(0); + + sustainer.setOverrideSubcomponents(true); + sustainer.setMassOverridden(true); + sustainer.setOverrideMass(actualRocketDryMass); + + final RigidBody overrideStructure = MassCalculator.calculateStructure( config ); + final Coordinate overrideRocketDryCM = overrideStructure.cm; + + assertEquals("Simple Rocket Override CM is incorrect: ", actualRocketDryCM, overrideRocketDryCM); + + double overrideMOIrot = overrideStructure.getRotationalInertia(); + double overrideMOIlong = overrideStructure.getLongitudinalInertia(); + assertEquals("Alpha III Rotational MOI calculated incorrectly: ", actualMOIrot, overrideMOIrot, EPSILON); + assertEquals("Alpha III Longitudinal MOI calculated incorrectly: ", actualMOIlong, overrideMOIlong, EPSILON); } @Test @@ -713,10 +741,10 @@ public class MassCalculatorTest extends BaseTestCase { double expMOI_axial = 0.01261079; double boosterMOI_xx= burnout.getRotationalInertia(); assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON); - - double expMOI_tr = 16.163954943504205; + + double expMOI_tr = 16.046826943504207; double boosterMOI_tr= burnout.getLongitudinalInertia(); - assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); + assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); } @Test @@ -734,17 +762,24 @@ public class MassCalculatorTest extends BaseTestCase { NoseCone nose = (NoseCone)boosters.getChild(0); nose.setMassOverridden(true); nose.setOverrideMass( 0.71 ); - + // cm= 0.71000000g @[0.53971058,0.07700000,0.00000000] + BodyTube body = (BodyTube)boosters.getChild(1); body.setMassOverridden(true); body.setOverrideMass( 0.622 ); - + // cm= 0.62200000g @[0.96400000,0.07700000,0.00000000] + InnerTube mmt = (InnerTube)boosters.getChild(1).getChild(0); mmt.setMassOverridden(true); mmt.setOverrideMass( 0.213 ); + // cm= 0.21300000g @[1.28900000,0.07700000,0.00000000] + + // Fin mass is not overriden + // cm= 0.15995232g @[1.23793939,0.07700000,0.00000000] RigidBody boosterData = MassCalculator.calculateStructure( config ); Coordinate boosterCM = boosterData.getCM(); + // cm= 3.409905g@[0.853614,-0.000000,0.000000] double expTotalMass = 3.40990464; assertEquals(" Booster Launch Mass is incorrect: ", expTotalMass, boosterData.getMass(), EPSILON);