[Bugfix] Verify / Fix Mass Calculation Code

- MassCalculator class:
    Debug toggle will print debug-level output to stderr
    fixed / reimplemented:
        getCG(...) -> getCM( FlightConfiguration, MassCalcType);
        -> getRotationalInertia( FlightConfiguration, MassCalcType);
        -> getLongitudinalInertia( FlightConfiguration, MassCalcType);
- MassData class:
    can now add MassData classes
    Instanced componets w/children:
       take into account component mass...
    propellantMass field is vague: no indication whether it's include in the inertia or not.
    longitudinalInertia => rollMOI (?)
    rotationalInertia => yaw / pitch MOI (?)
    assorted other fixes
- added unit test classes:
    ... .masscalc.MassCalculatorTest;
    ... .masscalc.MassDataTest;
This commit is contained in:
Daniel_M_Williams 2015-11-14 11:19:08 -05:00
parent cac0a52990
commit 4c7c3be9f7
28 changed files with 1544 additions and 464 deletions

View File

@ -1,22 +1,24 @@
package net.sf.openrocket.masscalc;
import static net.sf.openrocket.util.MathUtil.pow2;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.motor.MotorInstance;
import net.sf.openrocket.motor.MotorInstanceConfiguration;
import net.sf.openrocket.motor.MotorInstanceId;
import net.sf.openrocket.motor.ThrustCurveMotor;
import net.sf.openrocket.rocketcomponent.AxialStage;
import net.sf.openrocket.rocketcomponent.BoosterSet;
import net.sf.openrocket.rocketcomponent.ComponentAssembly;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.Instanceable;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.MassData;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Monitorable;
@ -46,22 +48,29 @@ public class MassCalculator implements Monitorable {
public abstract Coordinate getCG(Motor motor);
}
private static final Logger log = LoggerFactory.getLogger(MassCalculator.class);
private static final double MIN_MASS = 0.001 * MathUtil.EPSILON;
private static final Logger log = LoggerFactory.getLogger(MassCalculator.class);
private int rocketMassModID = -1;
private int rocketTreeModID = -1;
/*
* Cached data. All CG data is in absolute coordinates. All moments of inertia
* are relative to their respective CG.
*/
private Coordinate[] cgCache = null;
private double longitudinalInertiaCache[] = null;
private double rotationalInertiaCache[] = null;
private HashMap< Integer, MassData> cache = new HashMap<Integer, MassData >();
// private MassData dryData = null;
// private MassData launchData = null;
// private Vector< MassData> motorData = new Vector<MassData>();
// unless in active development, this should be set to false.
public boolean debug = false;
////////////////// Constructors ///////////////////
public MassCalculator() {
}
////////////////// Mass property calculations ///////////////////
@ -75,25 +84,43 @@ public class MassCalculator implements Monitorable {
* @return the CG of the configuration
*/
public Coordinate getCG(FlightConfiguration configuration, MassCalcType type) {
checkCache(configuration);
calculateStageCache(configuration);
Coordinate dryCG = null;
// Stage contribution
for (AxialStage stage : configuration.getActiveStages()) {
int stageNumber = stage.getStageNumber();
dryCG = cgCache[stageNumber].average(dryCG);
return getCM( configuration, type);
}
if (dryCG == null)
dryCG = Coordinate.NUL;
public Coordinate getCM(FlightConfiguration config, MassCalcType type) {
checkCache(config);
calculateStageCache(config);
MotorInstanceConfiguration motorConfig = new MotorInstanceConfiguration(configuration);
Coordinate motorCG = getMotorCG(configuration, motorConfig, MassCalcType.LAUNCH_MASS);
// 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);
if( debug){
System.err.println(" stageData <<@"+stageNumber+"mass: "+dryCM.weight+" @"+dryCM.toString());
}
}
Coordinate totalCG = dryCG.average(motorCG);
return totalCG;
Coordinate totalCM=null;
if( MassCalcType.NO_MOTORS == type ){
totalCM = dryCM;
}else{
MassData motorData = getMotorMassData(config, type);
Coordinate motorCM = motorData.getCM();
totalCM = dryCM.average(motorCM);
}
if(debug){
Coordinate cm = totalCM;
System.err.println(String.format("==>> Combined Mass: %5.3gg @( %g, %g, %g)",
cm.weight, cm.x, cm.y, cm.z ));
}
return totalCM;
}
/**
@ -103,128 +130,119 @@ public class MassCalculator implements Monitorable {
* @param motors the motor configuration
* @return the CG of the configuration
*/
public Coordinate getCG(FlightConfiguration configuration, MotorInstanceConfiguration motors) {
checkCache(configuration);
calculateStageCache(configuration);
Coordinate dryCG = getCG(configuration, MassCalcType.NO_MOTORS);
Coordinate motorCG = getMotorCG(configuration, motors, MassCalcType.LAUNCH_MASS);
Coordinate totalCG = dryCG.average(motorCG);
return totalCG;
private MassData getMotorMassData(FlightConfiguration config, MassCalcType type) {
if( MassCalcType.NO_MOTORS == type ){
return MassData.ZERO_DATA;
}
public Coordinate getMotorCG(FlightConfiguration config, MotorInstanceConfiguration motors, MassCalcType type) {
Coordinate motorCG = Coordinate.ZERO;
// Add motor CGs
if (motors != null) {
for (MotorInstance inst : config.getActiveMotors()) {
// DEVEL
if(MotorInstanceId.EMPTY_ID == inst.getMotorID()){
throw new IllegalArgumentException(" detected empty motor from <FlightConfiguration>.getActiveMotors()");
MassData motorData = MassData.ZERO_DATA;
// vvvv DEVEL vvvv
if( debug){
System.err.println("====== ====== getMotorCM: (type: "+type.name()+") ====== ====== ====== ====== ====== ======");
System.err.println(" [Number] [Name] [mass]");
}
if( null == inst.getMount()){
throw new NullPointerException(" detected null mount");
}
if( null == inst.getMotor()){
throw new NullPointerException(" detected null motor");
}
// END DEVEL
// ^^^^ DEVEL ^^^^
int motorCount = 0;
for (MotorInstance inst : config.getActiveMotors() ) {
//ThrustCurveMotor motor = (ThrustCurveMotor) inst.getMotor();
Coordinate position = inst.getPosition();
Coordinate curCG = type.getCG(inst.getMotor()).add(position);
motorCG = motorCG.average(curCG);
Coordinate curMotorCM = type.getCG(inst.getMotor()).add(position);
double Ir = inst.getRotationalInertia();
double It = inst.getLongitudinalInertia();
MassData instData = new MassData( curMotorCM, Ir, It);
motorData = motorData.add( instData );
// BEGIN DEVEL
if( debug){
System.err.println(String.format(" motor %2d: %s %s", //%5.3gg @( %g, %g, %g)",
motorCount, inst.getMotor().getDesignation(), instData.toDebug()));
System.err.println(String.format(" >> %s",
motorData.toDebug()));
}
motorCount++;
// END DEVEL
}
return motorCG;
return motorData;
}
/**
* Return the longitudinal inertia of the rocket with the specified motor instance
* configuration.
*
* @param configuration the current motor instance configuration
* @param motors the motor configuration
* @param config the current motor instance configuration
* @param type the type of analysis to pull
* @return the longitudinal inertia of the rocket
*/
public double getLongitudinalInertia(FlightConfiguration configuration, MotorInstanceConfiguration motors) {
checkCache(configuration);
calculateStageCache(configuration);
public double getLongitudinalInertia(FlightConfiguration config, MassCalcType type) {
checkCache(config);
calculateStageCache(config);
final Coordinate totalCG = getCG(configuration, motors);
double totalInertia = 0;
MassData structureData = MassData.ZERO_DATA;
// Stages
for (AxialStage stage : configuration.getActiveStages()) {
for (AxialStage stage : config.getActiveStages()) {
int stageNumber = stage.getStageNumber();
Coordinate stageCG = cgCache[stageNumber];
totalInertia += (longitudinalInertiaCache[stageNumber] +
stageCG.weight * MathUtil.pow2(stageCG.x - totalCG.x));
MassData stageData = cache.get(stageNumber);
structureData = structureData.add(stageData);
}
MassData motorData = MassData.ZERO_DATA;
if( MassCalcType.NO_MOTORS != type ){
motorData = getMotorMassData(config, type);
}
// Motors
if (motors != null) {
for (MotorInstance motor : configuration.getActiveMotors()) {
int stage = ((RocketComponent) motor.getMount()).getStageNumber();
if (configuration.isStageActive(stage)) {
Coordinate position = motor.getPosition();
Coordinate cg = motor.getCG().add(position);
MassData totalData = structureData.add( motorData);
if(debug){
System.err.println(String.format("==>> Combined MassData: %s", totalData.toDebug()));
double inertia = motor.getLongitudinalInertia();
totalInertia += inertia + cg.weight * MathUtil.pow2(cg.x - totalCG.x);
}
}
}
return totalInertia;
return totalData.getLongitudinalInertia();
}
/**
* Compute the rotational inertia of the provided configuration with specified motors.
*
* @param configuration the current motor instance configuration
* @param motors the motor configuration
* @param config the current motor instance configuration
* @param type the type of analysis to get
* @return the rotational inertia of the configuration
*/
public double getRotationalInertia(FlightConfiguration configuration, MotorInstanceConfiguration motors) {
checkCache(configuration);
calculateStageCache(configuration);
public double getRotationalInertia(FlightConfiguration config, MassCalcType type) {
checkCache(config);
calculateStageCache(config);
final Coordinate totalCG = getCG(configuration, motors);
double totalInertia = 0;
MassData structureData = MassData.ZERO_DATA;
// Stages
for (AxialStage stage : configuration.getActiveStages()) {
for (AxialStage stage : config.getActiveStages()) {
int stageNumber = stage.getStageNumber();
Coordinate stageCG = cgCache[stageNumber];
totalInertia += (rotationalInertiaCache[stageNumber] +
stageCG.weight * (MathUtil.pow2(stageCG.y - totalCG.y) +
MathUtil.pow2(stageCG.z - totalCG.z)));
MassData stageData = cache.get(stageNumber);
structureData = structureData.add(stageData);
}
// Motors
if (motors != null) {
for (MotorInstance motor : configuration.getActiveMotors()) {
int stage = ((RocketComponent) motor.getMount()).getStageNumber();
if (configuration.isStageActive(stage)) {
Coordinate position = motor.getPosition();
Coordinate cg = motor.getCG().add(position);
double inertia = motor.getRotationalInertia();
totalInertia += inertia + cg.weight * (MathUtil.pow2(cg.y - totalCG.y) +
MathUtil.pow2(cg.z - totalCG.z));
}
}
MassData motorData = MassData.ZERO_DATA;
if( MassCalcType.NO_MOTORS != type ){
motorData = getMotorMassData(config, type);
}
return totalInertia;
MassData totalData = structureData.add( motorData);
if(debug){
System.err.println(String.format("==>> Combined MassData: %s", totalData.toDebug()));
}
return totalData.getRotationalInertia();
}
@ -235,13 +253,14 @@ public class MassCalculator implements Monitorable {
* @param configuration the current motor instance configuration
* @return the total mass of all motors
*/
public double getPropellantMass(FlightConfiguration configuration, MotorInstanceConfiguration motors) {
public double getPropellantMass(FlightConfiguration configuration, MassCalcType calcType ){
double mass = 0;
//throw new BugException("getPropellantMass is not yet implemented.... ");
// add up the masses of all motors in the rocket
if (motors != null) {
for (MotorInstance motor : configuration.getActiveMotors()) {
mass = mass + motor.getCG().weight - motor.getMotor().getEmptyCG().weight;
if ( MassCalcType.NO_MOTORS != calcType ){
for (MotorInstance curInstance : configuration.getActiveMotors()) {
mass = mass + curInstance.getCG().weight - curInstance.getMotor().getEmptyCG().weight;
}
}
return mass;
@ -281,25 +300,23 @@ public class MassCalculator implements Monitorable {
//////// Cache computations ////////
private void calculateStageCache(FlightConfiguration config) {
if (cgCache == null) {
ArrayList<AxialStage> stageList = new ArrayList<AxialStage>();
stageList.addAll(config.getRocket().getStageList());
int stageCount = stageList.size();
cgCache = new Coordinate[stageCount];
longitudinalInertiaCache = new double[stageCount];
rotationalInertiaCache = new double[stageCount];
for (int i = 0; i < stageCount; i++) {
RocketComponent stage = stageList.get(i);
MassData data = calculateAssemblyMassData(stage);
cgCache[i] = stage.toAbsolute(data.getCG())[0];
longitudinalInertiaCache[i] = data.getLongitudinalInertia();
rotationalInertiaCache[i] = data.getRotationalInertia();
int stageCount = config.getActiveStageCount();
if(debug){
System.err.println(">> Calculating CG cache for config: "+config.toShort()+" with "+stageCount+" stages");
}
if( 0 < stageCount ){
for( AxialStage curStage : config.getActiveStages()){
int index = curStage.getStageNumber();
MassData stageData = calculateAssemblyMassData( curStage);
if( curStage instanceof BoosterSet ){
// hacky correction for the fact Booster Stages aren't direct subchildren to the rocket
stageData = stageData.move( curStage.getParent().getOffset() );
}
cache.put(index, stageData);
}
}
}
}
/**
@ -307,86 +324,114 @@ public class MassCalculator implements Monitorable {
* The inertia is returned relative to the CG, and the CG is in the coordinates
* of the specified component, not global coordinates.
*/
private MassData calculateAssemblyMassData(RocketComponent parent) {
Coordinate parentCG = Coordinate.ZERO;
double longitudinalInertia = 0.0;
double rotationalInertia = 0.0;
// Calculate data for this component
parentCG = parent.getComponentCG();
if (parentCG.weight < MIN_MASS)
parentCG = parentCG.setWeight(MIN_MASS);
// Override only this component's data
if (!parent.getOverrideSubcomponents()) {
if (parent.isMassOverridden())
parentCG = parentCG.setWeight(MathUtil.max(parent.getOverrideMass(), MIN_MASS));
if (parent.isCGOverridden())
parentCG = parentCG.setXYZ(parent.getOverrideCG());
private MassData calculateAssemblyMassData(RocketComponent component) {
return calculateAssemblyMassData(component, "....");
}
longitudinalInertia = parent.getLongitudinalUnitInertia() * parentCG.weight;
rotationalInertia = parent.getRotationalUnitInertia() * parentCG.weight;
private MassData calculateAssemblyMassData(RocketComponent component, String indent) {
Coordinate parentCM = component.getComponentCG();
double parentIx = component.getRotationalUnitInertia() * parentCM.weight;
double parentIt = component.getLongitudinalUnitInertia() * parentCM.weight;
MassData parentData = new MassData( parentCM, parentIx, parentIt);
if(( debug) &&( 0 < component.getChildCount()) && (MIN_MASS < parentCM.weight)){
//System.err.println(String.format("%-32s: %s ",indent+">>["+ component.getName()+"]", parentData.toCMDebug() ));
System.err.println(String.format("%-32s: %s ",indent+">>["+ component.getName()+"]", parentData.toDebug() ));
}
if (!component.getOverrideSubcomponents()) {
if (component.isMassOverridden())
parentCM = parentCM.setWeight(MathUtil.max(component.getOverrideMass(), MIN_MASS));
if (component.isCGOverridden())
parentCM = parentCM.setXYZ(component.getOverrideCG());
}
MassData childrenData = MassData.ZERO_DATA;
// Combine data for subcomponents
for (RocketComponent sibling : parent.getChildren()) {
Coordinate combinedCG;
double dx2, dr2;
// Compute data of sibling
MassData siblingData = calculateAssemblyMassData(sibling);
Coordinate[] siblingCGs = sibling.toRelative(siblingData.getCG(), parent);
for (Coordinate siblingCG : siblingCGs) {
// Compute CG of this + sibling
combinedCG = parentCG.average(siblingCG);
// Add effect of this CG change to parent inertia
dx2 = pow2(parentCG.x - combinedCG.x);
longitudinalInertia += parentCG.weight * dx2;
dr2 = pow2(parentCG.y - combinedCG.y) + pow2(parentCG.z - combinedCG.z);
rotationalInertia += parentCG.weight * dr2;
// Add inertia of sibling
longitudinalInertia += siblingData.getLongitudinalInertia();
rotationalInertia += siblingData.getRotationalInertia();
// Add effect of sibling CG change
dx2 = pow2(siblingData.getCG().x - combinedCG.x);
longitudinalInertia += siblingData.getCG().weight * dx2;
dr2 = pow2(siblingData.getCG().y - combinedCG.y) + pow2(siblingData.getCG().z - combinedCG.z);
rotationalInertia += siblingData.getCG().weight * dr2;
// Set combined CG
parentCG = combinedCG;
for (RocketComponent child : component.getChildren()) {
if( child instanceof BoosterSet ){
// this stage will be tallied separately... skip.
continue;
}
// child data, relative to parent's reference frame
MassData childData = calculateAssemblyMassData(child, indent+"....");
childrenData = childrenData.add( childData );
}
MassData resultantData = parentData; // default if not instanced
// compensate for component-instancing propogating to children's data
int instanceCount = component.getInstanceCount();
boolean hasChildren = ( 0 < component.getChildCount());
if (( 1 < instanceCount )&&( hasChildren )){
if(( debug )){
System.err.println(String.format("%s Found instanceable with %d children: %s (t= %s)",
indent, component.getInstanceCount(), component.getName(), component.getClass().getSimpleName() ));
}
final double curIxx = childrenData.getIxx(); // MOI about x-axis
final double curIyy = childrenData.getIyy(); // MOI about y axis
final double curIzz = childrenData.getIzz(); // MOI about z axis
Coordinate eachCM = childrenData.cm;
MassData instAccumData = new MassData(); // accumulator for instance MassData
Coordinate[] instanceLocations = ((Instanceable) component).getInstanceOffsets();
for( Coordinate curOffset : instanceLocations ){
if( debug){
//System.err.println(String.format("%-32s: %s", indent+" inst Accum", instAccumData.toCMDebug() ));
System.err.println(String.format("%-32s: %s", indent+" inst Accum", instAccumData.toDebug() ));
}
Coordinate instanceCM = curOffset.add(eachCM);
MassData instanceData = new MassData( instanceCM, curIxx, curIyy, curIzz);
// 3) Project the template data to the new CM
// and add to the total
instAccumData = instAccumData.add( instanceData);
}
childrenData = instAccumData;
}
// combine the parent's and children's data
resultantData = parentData.add( childrenData);
// Override total data
if (parent.getOverrideSubcomponents()) {
if (parent.isMassOverridden()) {
double oldMass = parentCG.weight;
double newMass = MathUtil.max(parent.getOverrideMass(), MIN_MASS);
longitudinalInertia = longitudinalInertia * newMass / oldMass;
rotationalInertia = rotationalInertia * newMass / oldMass;
parentCG = parentCG.setWeight(newMass);
}
if (parent.isCGOverridden()) {
double oldx = parentCG.x;
double newx = parent.getOverrideCGX();
longitudinalInertia += parentCG.weight * pow2(oldx - newx);
parentCG = parentCG.setX(newx);
}
// if (component.getOverrideSubcomponents()) {
// if (component.isMassOverridden()) {
// double oldMass = parentCM.weight;
// double newMass = MathUtil.max(component.getOverrideMass(), MIN_MASS);
// longitudinalInertia = longitudinalInertia * newMass / oldMass;
// rotationalInertia = rotationalInertia * newMass / oldMass;
// parentCM = parentCM.setWeight(newMass);
// }
// if (component.isCGOverridden()) {
// double oldx = parentCM.x;
// double newx = component.getOverrideCGX();
// longitudinalInertia += parentCM.weight * MathUtil.pow2(oldx - newx);
// parentCM = parentCM.setX(newx);
// }
// }
// if( debug){
// //System.err.println(String.format("%-32s: %s ", indent+"<<["+component.getName()+"][asbly]", resultantData.toCMDebug()));
// System.err.println(String.format("%-32s: %s ", indent+"@@["+component.getName()+"][asbly]", resultantData.toDebug()));
// }
//
// move to parent's reference point
resultantData = resultantData.move( component.getOffset() );
if( debug){
//System.err.println(String.format("%-32s: %s ", indent+"<<["+component.getName()+"][asbly]", resultantData.toCMDebug()));
System.err.println(String.format("%-32s: %s ", indent+"<<["+component.getName()+"][asbly]", resultantData.toDebug()));
}
MassData parentData = new MassData(parentCG, longitudinalInertia, rotationalInertia, 0);
return parentData;
return resultantData;
}
/**
@ -418,9 +463,7 @@ public class MassCalculator implements Monitorable {
* its execution.
*/
protected void voidMassCache() {
this.cgCache = null;
this.longitudinalInertiaCache = null;
this.rotationalInertiaCache = null;
this.cache.clear();
}

View File

@ -0,0 +1,294 @@
package net.sf.openrocket.masscalc;
import static net.sf.openrocket.util.MathUtil.pow2;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
/**
* An immutable value object containing the mass data of a component, assembly or entire rocket.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
* @author Daniel Williams <equipoise@gmail.com>
*/
public class MassData {
private static final double MIN_MASS = MathUtil.EPSILON;
/* ASSUME: a MassData instance implicitly describes a bodies w.r.t. a reference point
* a) the cm locates the body from the reference point
* b) each MOI is about the reference point.
*/
public final Coordinate cm;
// Moment of Inertias:
// x-axis: through the rocket's nose
// y,z axes: mutually perpendicular to each other and the x-axis. Because a rocket
// is (mostly) axisymmetric, y-axis and z-axis placement is arbitrary.
// MOI about the Center of Mass
private final InertiaMatrix I_cm;
// implements a simplified, diagonal MOI
private class InertiaMatrix {
public final double xx;
public final double yy;
public final double zz;
public InertiaMatrix( double Ixx, double Iyy, double Izz){
if(( 0 > Ixx)||( 0 > Iyy)||( 0 > Izz)){
throw new BugException(" attempted to initialize an InertiaMatrix with a negative inertia value.");
}
this.xx = Ixx;
this.yy = Iyy;
this.zz = Izz;
}
public InertiaMatrix add( InertiaMatrix other){
return new InertiaMatrix( this.xx + other.xx, this.yy + other.yy, this.zz + other.zz);
}
/**
* This function returns a <b>copy</b> of this MassData translated to a new location via
* a simplified model.
*
* Assuming rotations are independent, and occur perpendicular to the principal axes,
* The above can be simplified to produce a diagonal newMOI in
* the form of the parallel axis theorem:
* [ oldMOI + m*d^2, ...]
*
* For the full version of the equations, see:
* [1] https://en.wikipedia.org/wiki/Parallel_axis_theorem#Tensor_generalization
* [2] http://www.kwon3d.com/theory/moi/triten.html
*
*
* @param delta vector position from center of mass to desired reference location
*
* @return MassData the new MassData instance
*/
private InertiaMatrix translateInertia( final Coordinate delta, final double mass){
double x2 = pow2(delta.x);
double y2 = pow2(delta.y);
double z2 = pow2(delta.z);
// See: Parallel Axis Theorem in the function comments.
// I = I + m L^2; L = sqrt( y^2 + z^2);
// ergo: I = I + m (y^2 + z^2);
double newIxx = this.xx + mass*(y2 + z2);
double newIyy = this.yy + mass*(x2 + z2);
double newIzz = this.zz + mass*(x2 + y2);
// MOI about the reference point
InertiaMatrix toReturn=new InertiaMatrix( newIxx, newIyy, newIzz);
return toReturn;
}
@Override
public int hashCode() {
return (int) (Double.doubleToLongBits(this.xx) ^ Double.doubleToLongBits(this.yy) ^ Double.doubleToLongBits(this.xx) );
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof InertiaMatrix))
return false;
InertiaMatrix other = (InertiaMatrix) obj;
return (MathUtil.equals(this.xx, other.xx) && MathUtil.equals(this.yy, other.yy) &&
MathUtil.equals(this.zz, other.zz)) ;
}
}
public static final MassData ZERO_DATA = new MassData(Coordinate.ZERO, 0, 0, 0);
public MassData() {
this.cm = Coordinate.ZERO;
this.I_cm = new InertiaMatrix(0,0,0);
}
/**
* Return a new instance of MassData which is a sum of this and the other.
*
* @param childData second mass data to combine with this
*
* @return MassData the new MassData instance
*/
public MassData add(MassData body2){
MassData body1 = this;
// the combined Center of mass is defined to be 'point 3'
Coordinate combinedCM = body1.cm.average( body2.cm );
// transform InertiaMatrix from it's previous frame to the common frame
Coordinate delta1 = combinedCM.sub( body1.cm).setWeight(0);
InertiaMatrix I1_at_3 = body1.I_cm.translateInertia(delta1, body1.getMass());
// transform InertiaMatrix from it's previous frame to the common frame
Coordinate delta2 = combinedCM.sub( body2.cm).setWeight(0);
InertiaMatrix I2_at_3 = body2.I_cm.translateInertia(delta2, body2.getMass());
// once both are in the same frame, simply add them
InertiaMatrix combinedMOI = I1_at_3.add(I2_at_3);
MassData sumData = new MassData( combinedCM, combinedMOI);
{ // vvvv DEVEL vvvv
// System.err.println(" ?? body1: "+ body1.toDebug() );
// System.err.println(" delta 1=>3: "+ delta1);
// System.err.println(String.format(" >> 1@3: == [ %g, %g, %g ]",
// I1_at_3.xx, I1_at_3.yy, I1_at_3.zz));
//
// System.err.println(" ?? body2: "+ body2.toDebug() );
// System.err.println(" delta 2=>3: "+ delta2);
// System.err.println(String.format(" >> 2@3: [ %g, %g, %g ]",
// I2_at_3.xx, I2_at_3.yy, I2_at_3.zz));
// System.err.println(" ?? asbly3: "+sumData.toDebug()+"\n");
// InertiaMatrix rev1 = It1.translateInertia(delta1.multiply(-1), body1.getMass());
// System.err.println(String.format(" !!XX orig: %s\n", childDataChild.toDebug() ));
// System.err.println(String.format("%s!!XX revr: %s\n", indent, reverse.toDebug() ));
}
return sumData;
}
private MassData(Coordinate newCM, InertiaMatrix newMOI){
this.cm = newCM;
this.I_cm = newMOI;
}
public MassData( RocketComponent component ){
//MassData parentData = new MassData( parentCM, parentIx, parentIt);
// Calculate data for this component
Coordinate newCM = component.getComponentCG();
double mass = newCM.weight;
if ( mass < MIN_MASS){
newCM = newCM .setWeight(MIN_MASS);
mass = MIN_MASS;
}
this.cm = newCM;
double Ix = component.getRotationalUnitInertia() * mass;
double It = component.getLongitudinalUnitInertia() * mass;
this.I_cm = new InertiaMatrix( Ix, It, It);
}
public MassData(Coordinate newCM, double newIxx, double newIyy, double newIzz){
if (newCM == null) {
throw new IllegalArgumentException("CM is null");
}
this.cm = newCM;
this.I_cm = new InertiaMatrix(newIxx, newIyy, newIzz);
}
public MassData(final Coordinate cg, final double rotationalInertia, final double longitudinalInertia) {
if (cg == null) {
throw new IllegalArgumentException("cg is null");
}
this.cm = cg;
double newIxx = rotationalInertia;
double newIyy = longitudinalInertia;
double newIzz = longitudinalInertia;
this.I_cm = new InertiaMatrix( newIxx, newIyy, newIzz);
}
public double getMass(){
return cm.weight;
}
public Coordinate getCG() {
return cm;
}
public Coordinate getCM() {
return cm;
}
public double getIyy(){
return I_cm.yy;
}
public double getLongitudinalInertia() {
return I_cm.yy;
}
public double getIxx(){
return I_cm.xx;
}
public double getRotationalInertia() {
return I_cm.xx;
}
public double getIzz(){
return I_cm.zz;
}
public double getPropellantMass(){
return Double.NaN;
}
/**
* Return a new instance of MassData moved by the delta vector supplied.
* This function is intended to move the REFERENCE POINT, not the CM, and will leave
* the Inertia matrix completely unchanged.
*
* ASSUME: MassData implicity describe their respective bodies w.r.t 0,0,0)
* a) the cm locates the body from the reference point
* b) each MOI is about the reference point.
*
* @param delta vector from current reference point to new/desired reference point (mass is ignored)
*
* @return MassData a new MassData instance, locating the same CM from a different reference point.
*/
public MassData move( final Coordinate delta ){
MassData body1 = this;
// don't change the mass, just move it.
Coordinate newCM = body1.cm.add( delta );
MassData newData = new MassData( newCM, this.I_cm);
{ // DEVEL-DEBUG
// System.err.println(" ?? body1: "+ body1.toDebug() );
// System.err.println(" delta: "+ delta);
// System.err.println(" ?? asbly3: "+newData.toDebug()+"\n");
}
return newData;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof MassData))
return false;
MassData other = (MassData) obj;
return (this.cm.equals(other.cm) && (this.I_cm.equals(other.I_cm)));
}
@Override
public int hashCode() {
return (int) (cm.hashCode() ^ I_cm.hashCode() );
}
public String toCMDebug(){
return String.format("cm= %6.4fg@[%g,%g,%g]", cm.weight, cm.x, cm.y, cm.z);
}
public String toDebug(){
return toCMDebug()+" " +
String.format("I_cm=[ %g, %g, %g ]", I_cm.xx, I_cm.yy, I_cm.zz);
}
@Override
public String toString() {
return "MassData [cg=" + cm + ", longitudinalInertia=" + getIyy()
+ ", rotationalInertia=" + getIxx() + "]";
}
}

View File

@ -76,6 +76,10 @@ public class MotorInstance implements FlightConfigurableParameter<MotorInstance>
throw new UnsupportedOperationException("Retrieve a mount from an immutable no-motors instance");
}
public Coordinate getOffset(){
return this.position;
}
public Coordinate getPosition() {
return this.position;
}

View File

@ -49,6 +49,10 @@ public final class MotorInstanceId {
return componentId;
}
public int getInstanceNumber() {
return number;
}
@Override
public boolean equals(Object o) {

View File

@ -2,6 +2,7 @@ package net.sf.openrocket.motor;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.Inertia;
import net.sf.openrocket.util.MathUtil;
@ -64,6 +65,17 @@ public class ThrustCurveMotorInstance extends MotorInstance {
return stepCG;
}
@Override
public Coordinate getOffset( ){
if( null == mount ){
return Coordinate.NaN;
}else{
RocketComponent comp = (RocketComponent) mount;
double delta_x = comp.getLength() + mount.getMotorOverhang() - this.motor.getLength();
return new Coordinate(delta_x, 0, 0);
}
}
@Override
public double getLongitudinalInertia() {
return unitLongitudinalInertia * stepCG.weight;
@ -94,6 +106,7 @@ public class ThrustCurveMotorInstance extends MotorInstance {
}
this.motor = (ThrustCurveMotor)motor;
fireChangeEvent();
}
@ -115,6 +128,7 @@ public class ThrustCurveMotorInstance extends MotorInstance {
@Override
public void setMount(final MotorMount _mount) {
this.mount = _mount;
}
@Override
@ -222,5 +236,10 @@ public class ThrustCurveMotorInstance extends MotorInstance {
return clone;
}
@Override
public String toString(){
return this.id.toString();
}
}

View File

@ -383,6 +383,7 @@ public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial
}
}
this.isActingMount=true;
this.motors.set(fcid,newMotorInstance);
}

View File

@ -125,6 +125,11 @@ public class BoosterSet extends AxialStage implements FlightConfigurableComponen
return this.radialPosition_m;
}
@Override
public Coordinate[] getInstanceOffsets(){
return this.shiftCoordinates(new Coordinate[]{Coordinate.ZERO});
}
@Override
public Coordinate[] getLocations() {
if (null == this.parent) {
@ -211,12 +216,12 @@ public class BoosterSet extends AxialStage implements FlightConfigurableComponen
buffer.append(String.format("%s %-24s (stage: %d)", prefix, this.getName(), this.getStageNumber()));
buffer.append(String.format(" (len: %5.3f offset: %4.1f via: %s )\n", this.getLength(), this.getAxialOffset(), this.relativePosition.name()));
Coordinate[] relCoords = this.shiftCoordinates(new Coordinate[] { Coordinate.ZERO });
Coordinate[] relCoords = this.shiftCoordinates(new Coordinate[] { this.getOffset() });
Coordinate[] absCoords = this.getLocations();
for (int instanceNumber = 0; instanceNumber < this.count; instanceNumber++) {
Coordinate instanceRelativePosition = relCoords[instanceNumber];
Coordinate instanceAbsolutePosition = absCoords[instanceNumber];
buffer.append(String.format("%s [instance %2d of %2d] %28s %28s\n", prefix, instanceNumber, count,
buffer.append(String.format("%s [%2d/%2d]; %28s; %28s;\n", prefix, instanceNumber+1, count,
instanceRelativePosition, instanceAbsolutePosition));
}

View File

@ -99,6 +99,19 @@ public class CenteringRing extends RadiusRingComponent implements LineInstanceab
}
}
@Override
public Coordinate[] getInstanceOffsets(){
Coordinate[] toReturn = new Coordinate[this.getInstanceCount()];
toReturn[0] = Coordinate.ZERO;
for ( int index=1 ; index < this.getInstanceCount(); index++){
toReturn[index] = new Coordinate(index*this.instanceSeparation,0,0,0);
}
return toReturn;
}
@Override
public int getInstanceCount(){
return this.instanceCount;

View File

@ -12,6 +12,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.openrocket.motor.MotorInstance;
import net.sf.openrocket.motor.MotorInstanceId;
import net.sf.openrocket.util.ArrayList;
import net.sf.openrocket.util.ChangeSource;
import net.sf.openrocket.util.Coordinate;
@ -180,36 +181,46 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
public List<MotorInstance> getActiveMotors() {
ArrayList<MotorInstance> toReturn = new ArrayList<MotorInstance>();
for ( RocketComponent comp : this.getActiveComponents() ){
// see planning notes...
if ( comp instanceof MotorMount ){
MotorMount mount = (MotorMount)comp;
MotorInstance inst = mount.getMotorInstance(this.fcid);
// NYI: if clustered...
// if( mount instanceof Clusterable ){
// if( 1 < comp.getInstanceCount() ){
// if comp is clustered, it will be clustered from the innerTube, no?
//List<MotorInstance> instanceList = mount.getMotorInstance(this.fcid);
// this merely accounts for instancing of this component:
// int instancCount = comp.getInstanceCount();
// // vvvv DEVEL vvvv
//
// if(( mount.isMotorMount()) && ( MotorInstance.EMPTY_INSTANCE == inst)){
// if( mount instanceof BodyTube){
// MotorInstance bt_inst = ((BodyTube)mount).getMotorInstance(this.fcid);
// log.error("Detected EMPTY_INSTANCE in config: "+this.fcid.key.substring(0,8)+", mount: \""+comp.getName()+"\"");
// ((BodyTube)mount).printMotorDebug();
// }
// continue;
// }
// // ^^^^ DEVEL ^^^^
// we account for instances this way because it counts *all* the instancing between here
// and the rocket root.
Coordinate[] instanceLocations= comp.getLocations();
// motors go inactive after burnout, so include this filter too
if (inst.isActive()){
toReturn.add(inst);
// System.err.println(String.format(",,,,,,,, : %s (%s)",
// inst.getMotor().getDigest(), inst.getMotorID() ));
int instanceNumber = 0;
for ( Coordinate curMountLocation : instanceLocations ){
MotorInstance curInstance = inst.clone();
curInstance.setID( new MotorInstanceId( comp.getName(), instanceNumber+1) );
// 1) mount location
// == curMountLocation
// 2) motor location w/in mount: parent.refpoint -> motor.refpoint
Coordinate curMotorOffset = curInstance.getOffset();
curInstance.setPosition( curMountLocation.add(curMotorOffset) );
toReturn.add( curInstance);
// vvvv DEVEL vvvv
// System.err.println(String.format(",,,,,,,,[ %2d]: %s. (%s)",
// instanceNumber, curInstance.getMotor().getDigest(), curInstance));
// ^^^^ DEVEL ^^^^
instanceNumber ++;
}
}
}
}
@ -350,6 +361,9 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
}
}
public String toShort() {
return this.fcid.getShortKey();
}
// DEBUG / DEVEL
public String toDebug() {

View File

@ -32,7 +32,7 @@ public class InnerTube extends ThicknessRingComponent implements Clusterable, Ra
private double clusterRotation = 0.0;
private double overhang = 0;
private boolean isActing;
private boolean isActingMount;
private MotorConfigurationSet motors;
/**
@ -215,6 +215,11 @@ public class InnerTube extends ThicknessRingComponent implements Clusterable, Ra
return list;
}
@Override
public Coordinate[] getInstanceOffsets(){
return this.shiftCoordinates(new Coordinate[]{Coordinate.ZERO});
}
@Override
public Coordinate[] getLocations(){
if (null == this.parent) {
@ -277,6 +282,7 @@ public class InnerTube extends ThicknessRingComponent implements Clusterable, Ra
}
}
this.isActingMount = true;
this.motors.set(fcid,newMotorInstance);
}
@ -293,15 +299,15 @@ public class InnerTube extends ThicknessRingComponent implements Clusterable, Ra
@Override
public void setMotorMount(boolean _active){
if (this.isActing == _active)
if (this.isActingMount == _active)
return;
this.isActing = _active;
this.isActingMount = _active;
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
}
@Override
public boolean isMotorMount(){
return this.isActing;
return this.isActingMount;
}
@Override
@ -382,29 +388,38 @@ public class InnerTube extends ThicknessRingComponent implements Clusterable, Ra
buffer.append(String.format("%s %-24s (cluster: %s)", prefix, this.getName(), this.getPatternName()));
buffer.append(String.format(" (len: %5.3f offset: %4.1f via: %s )\n", this.getLength(), this.getAxialOffset(), this.relativePosition.name()));
Coordinate[] relCoords = this.shiftCoordinates(new Coordinate[] { Coordinate.ZERO });
Coordinate[] relCoords = this.shiftCoordinates(new Coordinate[] { this.getOffset() });
Coordinate[] absCoords = this.getLocations();
FlightConfigurationID curId = this.getRocket().getDefaultConfiguration().getFlightConfigurationID();
int count = this.getInstanceCount();
MotorInstance curInstance = this.motors.get(curId);
if( curInstance.isEmpty() ){
//if( curInstance.isEmpty() ){
{
// print just the tube locations
buffer.append(prefix+" [X] This Instance doesn't have any motors... showing mount tubes only\n");
for (int instanceNumber = 0; instanceNumber < count; instanceNumber++) {
Coordinate instanceRelativePosition = relCoords[instanceNumber];
Coordinate instanceAbsolutePosition = absCoords[instanceNumber];
buffer.append(String.format("%s [%2d / %2d] %28s %28s\n", prefix, instanceNumber, count,
instanceRelativePosition, instanceAbsolutePosition));
Coordinate tubeRelativePosition = relCoords[instanceNumber];
Coordinate tubeAbsolutePosition = absCoords[instanceNumber];
buffer.append(String.format("%s [%2d/%2d]; %28s; %28s;\n", prefix, instanceNumber, count,
tubeRelativePosition, tubeAbsolutePosition));
}
}
if( curInstance.isEmpty() ){
buffer.append(prefix+" [X] This Instance doesn't have any motors... showing mount tubes only\n");
}else{
// curInstance has a motor ...
Motor curMotor = curInstance.getMotor();
buffer.append(String.format("%s %-24s (in cluster: %s)\n", prefix, curMotor.getDesignation(), this.getPatternName()));
double motorOffset = this.getLength() - curMotor.getLength();
buffer.append(String.format("%s %-24s Thrust: %f N; (Length: %f); \n",
prefix, curMotor.getDesignation(), curMotor.getMaxThrustEstimate(), curMotor.getLength() ));
for (int instanceNumber = 0; instanceNumber < count; instanceNumber++) {
Coordinate instanceRelativePosition = relCoords[instanceNumber];
Coordinate instanceAbsolutePosition = absCoords[instanceNumber];
buffer.append(String.format("%s [%2d / %2d] %28s %28s\n", prefix, instanceNumber, count,
instanceRelativePosition, instanceAbsolutePosition));
Coordinate motorRelativePosition = new Coordinate(motorOffset, 0, 0);
Coordinate tubeAbs = absCoords[instanceNumber];
Coordinate motorAbsolutePosition = new Coordinate(tubeAbs.x+motorOffset,tubeAbs.y,tubeAbs.z);
buffer.append(String.format("%s [%2d/%2d]; %28s; %28s;\n", prefix, instanceNumber, count,
motorRelativePosition, motorAbsolutePosition));
}
}

View File

@ -10,10 +10,21 @@ public interface Instanceable {
*
* Note: This is function has a concrete implementation in RocketComponent.java ... it is included here only as a reminder.
*
* @return coordinates of each instance of this component -- specifically the front center of each instance
* @return coordinates of each instance of this component -- specifically the front center of each instance in global coordinates
*/
public Coordinate[] getLocations();
/**
* Returns vector coordinates of each instance of this component relative to this component's reference point (typically front center)
*
* Note: <code> this.getOffsets().length == this.getInstanceCount() </code> should ALWAYS be true.
* If getInstanceCount() returns anything besides 1 this function should be overridden as well.
*
*
* @return coordinates location of each instance relative to <b>this</b> component's reference point.
*/
public Coordinate[] getInstanceOffsets();
/**
* How many instances of this component are represented. This should generally be editable.
* @param newCount number of instances to set

View File

@ -119,7 +119,17 @@ public abstract class LaunchButton extends ExternalComponent implements LineInst
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
@Override
public Coordinate[] getInstanceOffsets(){
Coordinate[] toReturn = new Coordinate[this.getInstanceCount()];
toReturn[0] = Coordinate.ZERO;
for ( int index=1 ; index < this.getInstanceCount(); index++){
toReturn[index] = new Coordinate(index*this.instanceSeparation,0,0,0);
}
return toReturn;
}
@Override
protected void loadFromPreset(ComponentPreset preset) {

View File

@ -142,6 +142,18 @@ public class LaunchLug extends ExternalComponent implements Coaxial, LineInstanc
}
@Override
public Coordinate[] getInstanceOffsets(){
Coordinate[] toReturn = new Coordinate[this.getInstanceCount()];
toReturn[0] = Coordinate.ZERO;
for ( int index=1 ; index < this.getInstanceCount(); index++){
toReturn[index] = new Coordinate(index*this.instanceSeparation,0,0,0);
}
return toReturn;
}
@Override
protected Coordinate[] shiftCoordinates(Coordinate[] array) {
array = super.shiftCoordinates(array);

View File

@ -76,6 +76,11 @@ public class PodSet extends ComponentAssembly implements RingInstanceable {
return BodyComponent.class.isAssignableFrom(type);
}
@Override
public Coordinate[] getInstanceOffsets(){
return shiftCoordinates(new Coordinate[]{Coordinate.ZERO});
}
@Override
public Coordinate[] getLocations() {
if (null == this.parent) {

View File

@ -1083,9 +1083,13 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
return this.position;
}
// public Coordinate[] getOffset() {
// return new Coordinate[]{this.position};
// }
/**
* @deprecated kept around as example code. instead use
* @deprecated kept around as example code. instead use getLocations
* @return
*/
private Coordinate getAbsoluteVector() {
@ -2081,7 +2085,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
}
public void toDebugTreeNode(final StringBuilder buffer, final String prefix) {
buffer.append(String.format("%s %-24s %5.3f %24s %24s\n", prefix, this.getName(), this.getLength(),
buffer.append(String.format("%s %-24s; %5.3f; %24s; %24s;\n", prefix, this.getName(), this.getLength(),
this.getOffset(), this.getLocations()[0]));
}

View File

@ -24,6 +24,7 @@ public abstract class RocketUtils {
return length;
}
// get rid of this method.... we can sure come up with a better way to do this....
public static Coordinate getCG(Rocket rocket, MassCalcType calcType) {
MassCalculator massCalculator = new MassCalculator();
Coordinate cg = massCalculator.getCG(rocket.getDefaultConfiguration(), calcType);

View File

@ -3,6 +3,8 @@ package net.sf.openrocket.simulation;
import java.util.List;
import net.sf.openrocket.masscalc.MassCalculator;
import net.sf.openrocket.masscalc.MassData;
import net.sf.openrocket.masscalc.MassCalculator.MassCalcType;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.motor.MotorInstance;
import net.sf.openrocket.motor.MotorInstanceConfiguration;
@ -124,10 +126,11 @@ public abstract class AbstractSimulationStepper implements SimulationStepper {
}
MassCalculator calc = status.getSimulationConditions().getMassCalculator();
cg = calc.getCG(status.getConfiguration(), status.getMotorConfiguration());
longitudinalInertia = calc.getLongitudinalInertia(status.getConfiguration(), status.getMotorConfiguration());
rotationalInertia = calc.getRotationalInertia(status.getConfiguration(), status.getMotorConfiguration());
propellantMass = calc.getPropellantMass(status.getConfiguration(), status.getMotorConfiguration());
// not sure if this is actually Launch mass or not...
cg = calc.getCG(status.getConfiguration(), MassCalcType.LAUNCH_MASS);
longitudinalInertia = calc.getLongitudinalInertia(status.getConfiguration(), MassCalcType.LAUNCH_MASS);
rotationalInertia = calc.getRotationalInertia(status.getConfiguration(), MassCalcType.LAUNCH_MASS);
propellantMass = calc.getPropellantMass(status.getConfiguration(), MassCalcType.LAUNCH_MASS);
mass = new MassData(cg, longitudinalInertia, rotationalInertia, propellantMass);
// Call post-listener

View File

@ -1,5 +1,6 @@
package net.sf.openrocket.simulation;
import net.sf.openrocket.masscalc.MassData;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.simulation.exception.SimulationException;

View File

@ -1,5 +1,6 @@
package net.sf.openrocket.simulation;
import net.sf.openrocket.masscalc.MassData;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.util.Coordinate;

View File

@ -1,74 +0,0 @@
package net.sf.openrocket.simulation;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
/**
* An immutable value object containing the mass data of a rocket.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class MassData {
private final Coordinate cg;
private final double longitudinalInertia;
private final double rotationalInertia;
private final double propellantMass;
public MassData(Coordinate cg, double longitudinalInertia, double rotationalInertia, double propellantMass) {
if (cg == null) {
throw new IllegalArgumentException("cg is null");
}
this.cg = cg;
this.longitudinalInertia = longitudinalInertia;
this.rotationalInertia = rotationalInertia;
this.propellantMass = propellantMass;
}
public Coordinate getCG() {
return cg;
}
public double getLongitudinalInertia() {
return longitudinalInertia;
}
public double getRotationalInertia() {
return rotationalInertia;
}
public double getPropellantMass() {
return propellantMass;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof MassData))
return false;
MassData other = (MassData) obj;
return (this.cg.equals(other.cg) && MathUtil.equals(this.longitudinalInertia, other.longitudinalInertia) &&
MathUtil.equals(this.rotationalInertia, other.rotationalInertia)) && MathUtil.equals(this.propellantMass, other.propellantMass) ;
}
@Override
public int hashCode() {
return (int) (cg.hashCode() ^ Double.doubleToLongBits(longitudinalInertia) ^ Double.doubleToLongBits(rotationalInertia) ^ Double.doubleToLongBits(propellantMass) );
}
@Override
public String toString() {
return "MassData [cg=" + cg + ", longitudinalInertia=" + longitudinalInertia
+ ", rotationalInertia=" + rotationalInertia + ", propellantMass="+propellantMass + "]";
}
}

View File

@ -10,6 +10,7 @@ import net.sf.openrocket.aerodynamics.AerodynamicForces;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.masscalc.MassData;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.simulation.exception.SimulationCalculationException;
import net.sf.openrocket.simulation.exception.SimulationException;

View File

@ -8,6 +8,7 @@ import javax.script.ScriptException;
import net.sf.openrocket.aerodynamics.AerodynamicForces;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.masscalc.MassData;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.motor.MotorInstanceId;
import net.sf.openrocket.motor.MotorInstance;
@ -15,7 +16,6 @@ import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.simulation.AccelerationData;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.MassData;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.exception.SimulationListenerException;

View File

@ -2,6 +2,7 @@ package net.sf.openrocket.simulation.listeners;
import net.sf.openrocket.aerodynamics.AerodynamicForces;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.masscalc.MassData;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.motor.MotorInstanceId;
import net.sf.openrocket.motor.MotorInstance;
@ -9,7 +10,6 @@ import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.simulation.AccelerationData;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.MassData;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.util.BugException;

View File

@ -2,9 +2,9 @@ package net.sf.openrocket.simulation.listeners;
import net.sf.openrocket.aerodynamics.AerodynamicForces;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.masscalc.MassData;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.simulation.AccelerationData;
import net.sf.openrocket.simulation.MassData;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.util.Coordinate;

View File

@ -7,6 +7,7 @@ import org.slf4j.LoggerFactory;
import net.sf.openrocket.aerodynamics.AerodynamicForces;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.aerodynamics.Warning;
import net.sf.openrocket.masscalc.MassData;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.motor.MotorInstanceId;
import net.sf.openrocket.motor.MotorInstance;
@ -14,7 +15,6 @@ import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.simulation.AccelerationData;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.MassData;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.util.Coordinate;

View File

@ -12,15 +12,19 @@ import net.sf.openrocket.material.Material.Type;
import net.sf.openrocket.motor.Manufacturer;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.motor.MotorInstance;
import net.sf.openrocket.motor.MotorInstanceId;
import net.sf.openrocket.motor.ThrustCurveMotor;
import net.sf.openrocket.motor.ThrustCurveMotorInstance;
import net.sf.openrocket.preset.ComponentPreset;
import net.sf.openrocket.preset.ComponentPresetFactory;
import net.sf.openrocket.preset.InvalidComponentPresetException;
import net.sf.openrocket.preset.TypedPropertyMap;
import net.sf.openrocket.rocketcomponent.AxialStage;
import net.sf.openrocket.rocketcomponent.BodyTube;
import net.sf.openrocket.rocketcomponent.BoosterSet;
import net.sf.openrocket.rocketcomponent.Bulkhead;
import net.sf.openrocket.rocketcomponent.CenteringRing;
import net.sf.openrocket.rocketcomponent.ClusterConfiguration;
import net.sf.openrocket.rocketcomponent.DeploymentConfiguration;
import net.sf.openrocket.rocketcomponent.DeploymentConfiguration.DeployEvent;
import net.sf.openrocket.rocketcomponent.ExternalComponent;
@ -40,6 +44,7 @@ import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.rocketcomponent.ReferenceType;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.ShockCord;
import net.sf.openrocket.rocketcomponent.RocketComponent.Position;
import net.sf.openrocket.rocketcomponent.StageSeparationConfiguration;
import net.sf.openrocket.rocketcomponent.Transition;
@ -83,6 +88,66 @@ public class TestRockets {
}
private static MotorInstance generateMotorInstance_M1350_75mm(){
// public ThrustCurveMotor(Manufacturer manufacturer, String designation, String description,
// Motor.Type type, double[] delays, double diameter, double length,
// double[] time, double[] thrust,
// Coordinate[] cg, String digest);
ThrustCurveMotor mtr = new ThrustCurveMotor(
Manufacturer.getManufacturer("AeroTech"),"M1350", "Desc",
Motor.Type.SINGLE, new double[] {}, 0.075, 0.622,
new double[] { 0, 1, 2 }, new double[] { 0, 1357, 0 },
new Coordinate[] {
new Coordinate(.311, 0, 0, 4.808),new Coordinate(.311, 0, 0, 3.389),new Coordinate(.311, 0, 0, 1.970)},
"digest M1350 test");
return mtr.getNewInstance();
}
private static MotorInstance generateMotorInstance_G77_29mm(){
// public ThrustCurveMotor(Manufacturer manufacturer, String designation, String description,
// Motor.Type type, double[] delays, double diameter, double length,
// double[] time, double[] thrust,
// Coordinate[] cg, String digest);
ThrustCurveMotor mtr = new ThrustCurveMotor(
Manufacturer.getManufacturer("AeroTech"),"G77", "Desc",
Motor.Type.SINGLE, new double[] {4,7,10},0.029, 0.124,
new double[] { 0, 1, 2 }, new double[] { 0, 1, 0 },
new Coordinate[] {
new Coordinate(.062, 0, 0, 0.123),new Coordinate(.062, 0, 0, .0935),new Coordinate(.062, 0, 0, 0.064)},
"digest G77 test");
return mtr.getNewInstance();
}
//
public static Rocket makeNoMotorRocket() {
Rocket rocket;
AxialStage stage;
NoseCone nosecone;
BodyTube bodytube;
rocket = new Rocket();
stage = new AxialStage();
stage.setName("Stage1");
// Stage construction
rocket.addChild(stage);
nosecone = new NoseCone(Transition.Shape.ELLIPSOID, 0.105, 0.033);
nosecone.setThickness(0.001);
bodytube = new BodyTube(0.69, 0.033, 0.001);
bodytube.setMotorMount(true);
TrapezoidFinSet finset = new TrapezoidFinSet(3, 0.495, 0.1, 0.3, 0.185);
finset.setThickness(0.005);
bodytube.addChild(finset);
// Component construction
stage.addChild(nosecone);
stage.addChild(bodytube);
return rocket;
}
/**
* Create a new test rocket based on the value 'key'. The rocket utilizes most of the
@ -544,6 +609,157 @@ public class TestRockets {
return rocket;
}
public static Rocket makeFalcon9Heavy() {
Rocket rocket = new Rocket();
rocket.setName("Falcon9H Scale Rocket");
FlightConfiguration config = rocket.getDefaultConfiguration();
// ====== Payload Stage ======
// ====== ====== ====== ======
AxialStage payloadStage = new AxialStage();
payloadStage.setName("Payload Fairing");
rocket.addChild(payloadStage);
{
NoseCone payloadFairingNoseCone = new NoseCone(Transition.Shape.POWER, 0.118, 0.052);
payloadFairingNoseCone.setName("PL Fairing Nose");
payloadFairingNoseCone.setThickness(0.001);
payloadFairingNoseCone.setShapeParameter(0.5);
//payloadFairingNoseCone.setLength(0.118);
//payloadFairingNoseCone.setAftRadius(0.052);
payloadFairingNoseCone.setAftShoulderRadius( 0.051 );
payloadFairingNoseCone.setAftShoulderLength( 0.02 );
payloadFairingNoseCone.setAftShoulderThickness( 0.001 );
payloadFairingNoseCone.setAftShoulderCapped( false );
payloadStage.addChild(payloadFairingNoseCone);
BodyTube payloadBody = new BodyTube(0.132, 0.052, 0.001);
payloadBody.setName("PL Fairing Body");
payloadStage.addChild(payloadBody);
Transition payloadFairingTail = new Transition();
payloadFairingTail.setName("PL Fairing Transition");
payloadFairingTail.setLength(0.014);
payloadFairingTail.setThickness(0.002);
payloadFairingTail.setForeRadiusAutomatic(true);
payloadFairingTail.setAftRadiusAutomatic(true);
payloadStage.addChild(payloadFairingTail);
BodyTube upperStageBody= new BodyTube(0.18, 0.0385, 0.001);
upperStageBody.setName("Upper Stage Body ");
payloadStage.addChild( upperStageBody);
{
// Parachute
Parachute upperChute= new Parachute();
upperChute.setName("Parachute");
upperChute.setRelativePosition(Position.MIDDLE);
upperChute.setPositionValue(0.0);
upperChute.setDiameter(0.3);
upperChute.setLineCount(6);
upperChute.setLineLength(0.3);
upperStageBody.addChild( upperChute);
// Cord
ShockCord cord = new ShockCord();
cord.setName("Shock Cord");
cord.setRelativePosition(Position.BOTTOM);
cord.setPositionValue(0.0);
cord.setCordLength(0.4);
upperStageBody.addChild( cord);
}
BodyTube interstage= new BodyTube(0.12, 0.0385, 0.001);
interstage.setName("Interstage");
payloadStage.addChild( interstage);
}
// ====== Core Stage ======
// ====== ====== ====== ======
AxialStage coreStage = new AxialStage();
coreStage.setName("Core Stage");
rocket.addChild(coreStage);
{
BodyTube coreBody = new BodyTube(0.8, 0.0385, 0.001);
// 74 mm inner dia
coreBody.setName("Core Stage Body");
coreBody.setMotorMount(true);
coreStage.addChild( coreBody);
{
MotorInstance motorInstance = TestRockets.generateMotorInstance_M1350_75mm();
motorInstance.setID( new MotorInstanceId( coreBody.getName(), 1) );
coreBody.setMotorMount( true);
FlightConfigurationID motorConfigId = config.getFlightConfigurationID();
coreBody.setMotorInstance( motorConfigId, motorInstance);
}
TrapezoidFinSet coreFins = new TrapezoidFinSet();
coreFins.setName("Core Fins");
coreFins.setFinCount(4);
coreFins.setRelativePosition(Position.BOTTOM);
coreFins.setPositionValue(0.0);
coreFins.setBaseRotation( Math.PI / 4);
coreFins.setThickness(0.003);
coreFins.setCrossSection(CrossSection.ROUNDED);
coreFins.setRootChord(0.32);
coreFins.setTipChord(0.12);
coreFins.setHeight(0.12);
coreFins.setSweep(0.18);
coreBody.addChild(coreFins);
}
// ====== Booster Stage Set ======
// ====== ====== ====== ======
BoosterSet boosterStage = new BoosterSet();
boosterStage.setName("Booster Stage");
coreStage.addChild( boosterStage);
boosterStage.setRelativePositionMethod(Position.BOTTOM);
boosterStage.setAxialOffset(0.0);
boosterStage.setInstanceCount(2);
boosterStage.setRadialOffset(0.075);
{
NoseCone boosterCone = new NoseCone(Transition.Shape.POWER, 0.08, 0.0385);
boosterCone.setShapeParameter(0.5);
boosterCone.setName("Booster Nose");
boosterCone.setThickness(0.002);
//payloadFairingNoseCone.setLength(0.118);
//payloadFairingNoseCone.setAftRadius(0.052);
boosterCone.setAftShoulderRadius( 0.051 );
boosterCone.setAftShoulderLength( 0.02 );
boosterCone.setAftShoulderThickness( 0.001 );
boosterCone.setAftShoulderCapped( false );
boosterStage.addChild( boosterCone);
BodyTube boosterBody = new BodyTube(0.8, 0.0385, 0.001);
boosterBody.setName("Booster Body");
boosterBody.setOuterRadiusAutomatic(true);
boosterStage.addChild( boosterBody);
{
InnerTube boosterMotorTubes = new InnerTube();
boosterMotorTubes.setName("Booster Motor Tubes");
boosterMotorTubes.setLength(0.15);
boosterMotorTubes.setOuterRadius(0.015); // => 29mm motors
boosterMotorTubes.setThickness(0.0005);
boosterMotorTubes.setClusterConfiguration( ClusterConfiguration.CONFIGURATIONS[5]); // 4-ring
//boosterMotorTubes.setClusterConfiguration( ClusterConfiguration.CONFIGURATIONS[13]); // 9-star
boosterMotorTubes.setClusterScale(1.0);
boosterBody.addChild( boosterMotorTubes);
FlightConfigurationID motorConfigId = config.getFlightConfigurationID();
MotorInstance motorInstance = TestRockets.generateMotorInstance_G77_29mm();
motorInstance.setID( new MotorInstanceId( boosterMotorTubes.getName(), 1) );
boosterMotorTubes.setMotorInstance( motorConfigId, motorInstance);
boosterMotorTubes.setMotorOverhang(0.01234);
}
}
config.setAllStages(true);
return rocket;
}
/*
* Create a new file version 1.00 rocket

View File

@ -2,176 +2,463 @@ package net.sf.openrocket.masscalc;
//import junit.framework.TestCase;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import net.sf.openrocket.rocketcomponent.AxialStage;
import net.sf.openrocket.rocketcomponent.BodyTube;
import net.sf.openrocket.masscalc.MassCalculator.MassCalcType;
import net.sf.openrocket.motor.MotorInstance;
import net.sf.openrocket.rocketcomponent.BoosterSet;
import net.sf.openrocket.rocketcomponent.ClusterConfiguration;
import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.FlightConfigurationID;
import net.sf.openrocket.rocketcomponent.InnerTube;
import net.sf.openrocket.rocketcomponent.NoseCone;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.Transition;
import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.TestRockets;
import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
public class MassCalculatorTest extends BaseTestCase {
// tolerance for compared double test results
protected final double EPSILON = 0.000001;
protected final double EPSILON = MathUtil.EPSILON;
protected final Coordinate ZERO = new Coordinate(0., 0., 0.);
public void test() {
// fail("Not yet implemented");
}
public Rocket createTestRocket() {
double tubeRadius = 0.1;
// setup
Rocket rocket = new Rocket();
rocket.setName("Rocket");
AxialStage sustainer = new AxialStage();
sustainer.setName("Sustainer stage");
RocketComponent sustainerNose = new NoseCone(Transition.Shape.CONICAL, 0.2, tubeRadius);
sustainerNose.setName("Sustainer Nosecone");
sustainer.addChild(sustainerNose);
RocketComponent sustainerBody = new BodyTube(0.3, tubeRadius, 0.001);
sustainerBody.setName("Sustainer Body ");
sustainer.addChild(sustainerBody);
rocket.addChild(sustainer);
AxialStage core = new AxialStage();
core.setName("Core stage");
rocket.addChild(core);
BodyTube coreBody = new BodyTube(0.6, tubeRadius, 0.001);
coreBody.setName("Core Body ");
core.addChild(coreBody);
FinSet coreFins = new TrapezoidFinSet(4, 0.4, 0.2, 0.2, 0.4);
coreFins.setName("Core Fins");
coreBody.addChild(coreFins);
InnerTube motorCluster = new InnerTube();
motorCluster.setName("Core Motor Cluster");
// outdated. Just add an actual motor + motor instance
//motorCluster.setMotorMount(true);
motorCluster.setClusterConfiguration(ClusterConfiguration.CONFIGURATIONS[5]);
coreBody.addChild(motorCluster);
return rocket;
}
public BoosterSet createBooster() {
double tubeRadius = 0.08;
BoosterSet booster = new BoosterSet();
booster.setName("Booster Stage");
RocketComponent boosterNose = new NoseCone(Transition.Shape.CONICAL, 0.2, tubeRadius);
boosterNose.setName("Booster Nosecone");
booster.addChild(boosterNose);
RocketComponent boosterBody = new BodyTube(0.2, tubeRadius, 0.001);
boosterBody.setName("Booster Body ");
booster.addChild(boosterBody);
Transition boosterTail = new Transition();
boosterTail.setName("Booster Tail");
boosterTail.setForeRadius(tubeRadius);
boosterTail.setAftRadius(0.05);
boosterTail.setLength(0.1);
booster.addChild(boosterTail);
booster.setInstanceCount(3);
booster.setRadialOffset(0.18);
return booster;
}
@Test
public void testTestRocketMasses() {
RocketComponent rocket = createTestRocket();
String treeDump = rocket.toDebugTree();
public void testRocketNoMotors() {
Rocket rkt = TestRockets.makeNoMotorRocket();
rkt.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName());
// String treeDump = rkt.toDebugTree();
// System.err.println( treeDump);
// Validate Boosters
MassCalculator mc = new MassCalculator();
//mc.debug = true;
Coordinate rocketCM = mc.getCM( rkt.getDefaultConfiguration(), MassCalcType.NO_MOTORS);
double expMass = 0.668984592;
double expCMx = 0.558422219894;
double calcMass = rocketCM.weight;
Coordinate expCM = new Coordinate(expCMx,0,0, expMass);
assertEquals(" Simple Motor Rocket mass incorrect: ", expMass, calcMass, EPSILON);
assertEquals(" Delta Heavy Booster CM.x is incorrect: ", expCM.x, rocketCM.x, EPSILON);
assertEquals(" Delta Heavy Booster CM.y is incorrect: ", expCM.y, rocketCM.y, EPSILON);
assertEquals(" Delta Heavy Booster CM.z is incorrect: ", expCM.z, rocketCM.z, EPSILON);
assertEquals(" Delta Heavy Booster CM is incorrect: ", expCM, rocketCM);
rocketCM = mc.getCM( rkt.getDefaultConfiguration(), MassCalcType.LAUNCH_MASS);
assertEquals(" Delta Heavy Booster CM is incorrect: ", expCM, rocketCM);
rocketCM = mc.getCM( rkt.getDefaultConfiguration(), MassCalcType.BURNOUT_MASS);
assertEquals(" Delta Heavy Booster CM is incorrect: ", expCM, rocketCM);
}
@Test
public void testTestComponentMasses() {
Rocket rkt = TestRockets.makeFalcon9Heavy();
rkt.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName());
double expMass;
RocketComponent cc;
double compMass;
double calcMass;
expMass = 0.093417755;
compMass = rocket.getChild(0).getChild(0).getComponentMass();
assertEquals(" NoseCone mass calculated incorrectly: ", expMass, compMass, EPSILON);
// ====== Payload Stage ======
// ====== ====== ====== ======
{
expMass = 0.022549558353;
cc= rkt.getChild(0).getChild(0);
compMass = cc.getComponentMass();
assertEquals("P/L NoseCone mass calculated incorrectly: ", expMass, compMass, EPSILON);
expMass = 0.1275360953;
compMass = rocket.getChild(0).getChild(1).getComponentMass();
assertEquals(" Sustainer Body mass calculated incorrectly: ", expMass, compMass, EPSILON);
expMass = 0.02904490372;
cc= rkt.getChild(0).getChild(1);
compMass = cc.getComponentMass();
assertEquals("P/L Body mass calculated incorrectly: ", expMass, compMass, EPSILON);
expMass = 0.255072190;
compMass = rocket.getChild(1).getChild(0).getComponentMass();
assertEquals(" Core Body mass calculated incorrectly: ", expMass, compMass, EPSILON);
expMass = 0.007289284477103441;
cc= rkt.getChild(0).getChild(2);
compMass = cc.getComponentMass();
assertEquals("P/L Transition mass calculated incorrectly: ", expMass, compMass, EPSILON);
expMass = 0.9792000;
compMass = rocket.getChild(1).getChild(0).getChild(0).getComponentMass();
assertEquals(" Core Fins mass calculated incorrectly: ", expMass, compMass, EPSILON);
expMass = 0.029224351500753608;
cc= rkt.getChild(0).getChild(3);
compMass = cc.getComponentMass();
assertEquals("P/L Upper Stage Body mass calculated incorrectly: ", expMass, compMass, EPSILON);
{
expMass = 0.0079759509252;
cc= rkt.getChild(0).getChild(3).getChild(0);
compMass = cc.getComponentMass();
assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON);
InnerTube motorCluster = (InnerTube) rocket.getChild(1).getChild(0).getChild(1);
expMass = 0.0055329;
compMass = motorCluster.getComponentMass();
assertEquals(" Core Motor Mount Tubes: mass calculated incorrectly: ", expMass, compMass, EPSILON);
compMass = motorCluster.getMass();
assertEquals(" Core Motor Mount Tubes: mass calculated incorrectly: ", expMass, compMass, EPSILON);
expMass = 0.00072;
cc= rkt.getChild(0).getChild(3).getChild(1);
compMass = cc.getComponentMass();
assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON);
}
expMass = 490.061;
// MassCalculator mc = new BasicMassCalculator();
// calcMass = mc.getMass();
calcMass = rocket.getMass();
expMass = 0.01948290100050243;
cc= rkt.getChild(0).getChild(4);
compMass = cc.getComponentMass();
assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON);
}
assertEquals(" Simple Rocket Mass is incorrect: " + treeDump, expMass, calcMass, EPSILON);
// ====== Core Stage ======
// ====== ====== ======
{
expMass = 0.1298860066700161;
cc= rkt.getChild(1).getChild(0);
compMass = cc.getComponentMass();
assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON);
expMass = 0.21326976;
cc= rkt.getChild(1).getChild(0).getChild(0);
compMass = cc.getComponentMass();
assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON);
}
// ====== Booster Set Stage ======
// ====== ====== ======
BoosterSet boosters = (BoosterSet) rkt.getChild(1).getChild(1);
{
expMass = 0.01530561538;
cc= boosters.getChild(0);
compMass = cc.getComponentMass();
assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON);
expMass = 0.08374229377;
cc= boosters.getChild(1);
compMass = cc.getComponentMass();
assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON);
expMass = 0.018906104589303415;
cc= boosters.getChild(1).getChild(0);
compMass = cc.getComponentMass();
assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON);
}
}
@Test
public void testTestRocketCG() {
RocketComponent rocket = createTestRocket();
String treeDump = rocket.toDebugTree();
double expRelCGx;
double expAbsCGx;
double actualRelCGx;
double actualAbsCGx;
public void testTestComponentMOIs() {
Rocket rkt = TestRockets.makeFalcon9Heavy();
rkt.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName());
expRelCGx = 0.134068822;
actualRelCGx = rocket.getChild(0).getChild(0).getComponentCG().x;
assertEquals(" NoseCone CG calculated incorrectly: ", expRelCGx, actualRelCGx, EPSILON);
double expInertia;
RocketComponent cc;
double compInertia;
expRelCGx = 0.15;
actualRelCGx = rocket.getChild(0).getChild(1).getComponentCG().x;
assertEquals(" Sustainer Body cg calculated incorrectly: ", expRelCGx, actualRelCGx, EPSILON);
// ====== Payload Stage ======
// ====== ====== ====== ======
{
expInertia = 3.1698055283e-5;
cc= rkt.getChild(0).getChild(0);
compInertia = cc.getRotationalInertia();
assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
expInertia = 1.79275e-5;
compInertia = cc.getLongitudinalInertia();
assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
BodyTube coreBody = (BodyTube) rocket.getChild(1).getChild(0);
expRelCGx = 0.3; // relative to parent
actualRelCGx = coreBody.getComponentCG().x;
assertEquals(" Core Body (relative) cg calculated incorrectly: ", expRelCGx, actualRelCGx, EPSILON);
//expAbsCGx = 0.8;
//actualAbsCGx = coreBody.getCG().x;
//assertEquals(" Core Body (absolute) cg calculated incorrectly: ", expAbsCGx, actualAbsCGx, EPSILON);
cc= rkt.getChild(0).getChild(1);
expInertia = 7.70416e-5;
compInertia = cc.getRotationalInertia();
assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
expInertia = 8.06940e-5;
compInertia = cc.getLongitudinalInertia();
assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
FinSet coreFins = (FinSet) rocket.getChild(1).getChild(0).getChild(0);
expRelCGx = 0.244444444; // relative to parent
actualRelCGx = coreFins.getComponentCG().x;
assertEquals(" Core Fins (relative) cg calculated incorrectly: ", expRelCGx, actualRelCGx, EPSILON);
// expAbsCGx = 0.9444444444;
// actualAbsCGx = coreBody.getCG().x;
// assertEquals(" Core Fins (absolute) cg calculated incorrectly: ", expAbsCGx, actualAbsCGx, EPSILON);
cc= rkt.getChild(0).getChild(2);
expInertia = 1.43691e-5;
compInertia = cc.getRotationalInertia();
assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
expInertia = 7.30265e-6;
compInertia = cc.getLongitudinalInertia();
assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
expRelCGx = 10.061;
// MassCalculator mc = new BasicMassCalculator();
actualRelCGx = rocket.getCG().x;
// mc.getCG(Configuration configuration, MotorInstanceConfiguration motors) {
// calcMass = mc.getMass();
cc= rkt.getChild(0).getChild(3);
expInertia = 4.22073e-5;
compInertia = cc.getRotationalInertia();
assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
expInertia = 0.0001;
compInertia = cc.getLongitudinalInertia();
assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
assertEquals(" Simple Rocket CG is incorrect: " + treeDump, expRelCGx, actualRelCGx, EPSILON);
{
cc= rkt.getChild(0).getChild(3).getChild(0);
expInertia = 6.23121e-7;
compInertia = cc.getRotationalInertia();
assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
expInertia = 7.26975e-7;
compInertia = cc.getLongitudinalInertia();
assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
cc= rkt.getChild(0).getChild(3).getChild(1);
expInertia = 5.625e-8;
compInertia = cc.getRotationalInertia();
assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
expInertia = 6.5625e-8;
compInertia = cc.getLongitudinalInertia();
assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
}
cc= rkt.getChild(0).getChild(4);
expInertia = 2.81382e-5;
compInertia = cc.getRotationalInertia();
assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
expInertia = 3.74486e-5;
compInertia = cc.getLongitudinalInertia();
assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
}
// ====== Core Stage ======
// ====== ====== ======
{
cc= rkt.getChild(1).getChild(0);
expInertia = 0.000187588;
compInertia = cc.getRotationalInertia();
assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
expInertia = 0.00702105;
compInertia = cc.getLongitudinalInertia();
assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
cc= rkt.getChild(1).getChild(0).getChild(0);
expInertia = 0.00734753;
compInertia = cc.getRotationalInertia();
assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
expInertia = 0.02160236691801411;
compInertia = cc.getLongitudinalInertia();
assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
}
// ====== Booster Set Stage ======
// ====== ====== ======
BoosterSet boosters = (BoosterSet) rkt.getChild(1).getChild(1);
{
cc= boosters.getChild(0);
expInertia = 5.20107e-6;
compInertia = cc.getRotationalInertia();
assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
expInertia = 0;
compInertia = cc.getLongitudinalInertia();
assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
cc= boosters.getChild(1);
expInertia = 5.02872e-5;
compInertia = cc.getRotationalInertia();
assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
expInertia = 0.00449140;
compInertia = cc.getLongitudinalInertia();
assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
cc= boosters.getChild(1).getChild(0);
expInertia = 4.11444e-6;
compInertia = cc.getRotationalInertia();
assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
expInertia = 3.75062e-5;
compInertia = cc.getLongitudinalInertia();
assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
}
}
@Test
public void testTestBoosterStructureCM() {
Rocket rocket = TestRockets.makeFalcon9Heavy();
rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName());
BoosterSet boosters = (BoosterSet) rocket.getChild(1).getChild(1);
int boostNum = boosters.getStageNumber();
rocket.getDefaultConfiguration().setAllStages(false);
rocket.getDefaultConfiguration().setOnlyStage( boostNum);
// String treeDump = rocket.toDebugTree();
// System.err.println( treeDump);
// Validate Boosters
MassCalculator mc = new MassCalculator();
Coordinate boosterSetCM = mc.getCM( rocket.getDefaultConfiguration(), MassCalcType.NO_MOTORS);
double expMass = 0.23590802751203407;
double expCMx = 0.9615865040919498;
double calcMass = boosterSetCM.weight;
assertEquals(" Delta Heavy Booster Mass is incorrect: ", expMass, calcMass, EPSILON);
Coordinate expCM = new Coordinate(expCMx,0,0, expMass);
assertEquals(" Delta Heavy Booster CM.x is incorrect: ", expCM.x, boosterSetCM.x, EPSILON);
assertEquals(" Delta Heavy Booster CM.y is incorrect: ", expCM.y, boosterSetCM.y, EPSILON);
assertEquals(" Delta Heavy Booster CM.z is incorrect: ", expCM.z, boosterSetCM.z, EPSILON);
assertEquals(" Delta Heavy Booster CM is incorrect: ", expCM, boosterSetCM);
}
// @Test
// public void testBoosterMotorCM() {
// Rocket rocket = TestRockets.makeFalcon9Heavy();
// FlightConfiguration defaultConfig = rocket.getDefaultConfiguration();
// FlightConfigurationID fcid = defaultConfig.getFlightConfigurationID();
// rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName());
//
// BoosterSet boosters = (BoosterSet) rocket.getChild(1).getChild(1);
// int boostNum = boosters.getStageNumber();
// rocket.getDefaultConfiguration().setAllStages(false);
// rocket.getDefaultConfiguration().setOnlyStage( boostNum);
//// String treeDump = rocket.toDebugTree();
//// System.err.println( treeDump);
//
// // Validate Boosters
// InnerTube inner = (InnerTube)boosters.getChild(1).getChild(0);
// MassCalculator mc = new MassCalculator();
//
// double innerStartX = inner.getLocations()[0].x;
// double innerLength = inner.getLength();
// MotorInstance moto= inner.getMotorInstance(fcid);
// double motorStart = innerStartX + innerLength + inner.getMotorOverhang() - moto.getMotor().getLength();
//
// int instanceCount = boosters.getInstanceCount()*inner.getInstanceCount();
// assertEquals(" Total engine count is incorrect: ", 8, instanceCount);
//
// // LAUNCH
// {
//
// Coordinate motorCM = mc.getMotorCG( rocket.getDefaultConfiguration(), MassCalcType.LAUNCH_MASS);
//
// double expMotorMass = 0.123*instanceCount;
// double actMotorMass = motorCM.weight;
// assertEquals(" Booster motor launch mass is incorrect: ", expMotorMass, actMotorMass, EPSILON);
//
// double expMotorCMX = motorStart + moto.getMotor().getLaunchCG().x;
// Coordinate expMotorCM = new Coordinate(expMotorCMX, 0, 0, expMotorMass);
// assertEquals(" Booster Motor CM.x is incorrect: ", expMotorCM.x, motorCM.x, EPSILON);
// assertEquals(" Booster Motor CM.y is incorrect: ", expMotorCM.y, motorCM.y, EPSILON);
// assertEquals(" Booster Motor CM.z is incorrect: ", expMotorCM.z, motorCM.z, EPSILON);
// assertEquals(" Booster Motor CM is incorrect: ", expMotorCM, motorCM );
// }
//
//
// // EMPTY / BURNOUT
// {
// Coordinate motorCM = mc.getMotorCG( rocket.getDefaultConfiguration(), MassCalcType.BURNOUT_MASS);
//
// double expMotorMass = 0.064*instanceCount;
// double actMotorMass = motorCM.weight;
// assertEquals(" Booster motor burnout mass is incorrect: ", expMotorMass, actMotorMass, EPSILON);
//
// // vvvv DEVEL vvvv
//// System.err.println("\n ====== ====== ");
//// System.err.println(String.format(" final position: %g = %g innerTube start", motorStart, innerStart ));
//// System.err.println(String.format(" + %g innerTube Length ", innerLength ));
//// System.err.println(String.format(" + %g overhang", inner.getMotorOverhang() ));
//// System.err.println(String.format(" - %g motor length", moto.getMotor().getLength() ));
//// double motorOffs = innerLength + inner.getMotorOverhang() - moto.getMotor().getLength();
//// System.err.println(String.format(" [ %g motor Offset ]", motorOffs ));
//// System.err.println(String.format(" [ %g motor CM ]", moto.getMotor().getEmptyCG().x));
// // ^^^^ DEVEL ^^^^
//
// double expCMX = motorStart + moto.getMotor().getEmptyCG().x;
// Coordinate expMotorCM = new Coordinate(expCMX, 0, 0, expMotorMass);
// assertEquals(" Booster Motor CM.x is incorrect: ", expMotorCM.x, motorCM.x, EPSILON);
// assertEquals(" Booster Motor CM.y is incorrect: ", expMotorCM.y, motorCM.y, EPSILON);
// assertEquals(" Booster Motor CM.z is incorrect: ", expMotorCM.z, motorCM.z, EPSILON);
// assertEquals(" Booster Motor CM is incorrect: ", expMotorCM, motorCM );
// }
// }
@Test
public void testBoosterTotalCM() {
Rocket rocket = TestRockets.makeFalcon9Heavy();
rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName());
BoosterSet boosters = (BoosterSet) rocket.getChild(1).getChild(1);
int boostNum = boosters.getStageNumber();
rocket.getDefaultConfiguration().setAllStages(false);
rocket.getDefaultConfiguration().setOnlyStage( boostNum);
//String treeDump = rocket.toDebugTree();
//System.err.println( treeDump);
{
// Validate Booster Launch Mass
MassCalculator mc = new MassCalculator();
Coordinate boosterSetCM = mc.getCM( rocket.getDefaultConfiguration(), MassCalcType.LAUNCH_MASS);
double calcTotalMass = boosterSetCM.weight;
double expTotalMass = 1.219908027512034;
double expX = 1.2461238889997992;
Coordinate expCM = new Coordinate(expX,0,0, expTotalMass);
assertEquals(" Booster Launch Mass is incorrect: ", expTotalMass, calcTotalMass, EPSILON);
assertEquals(" Booster Launch CM.x is incorrect: ", expCM.x, boosterSetCM.x, EPSILON);
assertEquals(" Booster Launch CM.y is incorrect: ", expCM.y, boosterSetCM.y, EPSILON);
assertEquals(" Booster Launch CM.z is incorrect: ", expCM.z, boosterSetCM.z, EPSILON);
assertEquals(" Booster Launch CM is incorrect: ", expCM, boosterSetCM);
}
{
// Validate Booster Burnout Mass
MassCalculator mc = new MassCalculator();
Coordinate boosterSetCM = mc.getCM( rocket.getDefaultConfiguration(), MassCalcType.BURNOUT_MASS);
double calcTotalMass = boosterSetCM.weight;
double expTotalMass = 0.7479080275020341;
assertEquals(" Booster Launch Mass is incorrect: ", expTotalMass, calcTotalMass, EPSILON);
double expX = 1.2030731351529202;
Coordinate expCM = new Coordinate(expX,0,0, expTotalMass);
assertEquals(" Booster Launch CM.x is incorrect: ", expCM.x, boosterSetCM.x, EPSILON);
assertEquals(" Booster Launch CM.y is incorrect: ", expCM.y, boosterSetCM.y, EPSILON);
assertEquals(" Booster Launch CM.z is incorrect: ", expCM.z, boosterSetCM.z, EPSILON);
assertEquals(" Booster Launch CM is incorrect: ", expCM, boosterSetCM);
}
}
@Test
public void testTestBoosterStructureMOI() {
Rocket rocket = TestRockets.makeFalcon9Heavy();
rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName());
FlightConfiguration defaultConfig = rocket.getDefaultConfiguration();
BoosterSet boosters = (BoosterSet) rocket.getChild(1).getChild(1);
int boostNum = boosters.getStageNumber();
rocket.getDefaultConfiguration().setAllStages(false);
rocket.getDefaultConfiguration().setOnlyStage( boostNum);
// String treeDump = rocket.toDebugTree();
// System.err.println( treeDump);
// Validate Boosters
MassCalculator mc = new MassCalculator();
//mc.debug = true;
double expMOI_axial = .00144619;
double boosterMOI_xx= mc.getRotationalInertia( defaultConfig, MassCalcType.NO_MOTORS);
assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON);
double expMOI_tr = 0.01845152840733412;
double boosterMOI_tr= mc.getLongitudinalInertia( defaultConfig, MassCalcType.NO_MOTORS);
assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON);
}
@Test
public void testBoosterTotalMOI() {
Rocket rocket = TestRockets.makeFalcon9Heavy();
FlightConfiguration defaultConfig = rocket.getDefaultConfiguration();
rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName());
BoosterSet boosters = (BoosterSet) rocket.getChild(1).getChild(1);
int boostNum = boosters.getStageNumber();
rocket.getDefaultConfiguration().setAllStages(false);
rocket.getDefaultConfiguration().setOnlyStage( boostNum);
//String treeDump = rocket.toDebugTree();
//System.err.println( treeDump);
// Validate Boosters
MassCalculator mc = new MassCalculator();
mc.debug = true;
double expMOI_axial = 0.00752743;
double boosterMOI_xx= mc.getRotationalInertia( defaultConfig, MassCalcType.LAUNCH_MASS);
double expMOI_tr = 0.0436639;
double boosterMOI_tr= mc.getLongitudinalInertia( defaultConfig, MassCalcType.LAUNCH_MASS);
assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON);
assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON);
}
}

View File

@ -0,0 +1,190 @@
package net.sf.openrocket.masscalc;
//import junit.framework.TestCase;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
public class MassDataTest extends BaseTestCase {
// tolerance for compared double test results
protected final double EPSILON = MathUtil.EPSILON;
protected final Coordinate ZERO = new Coordinate(0., 0., 0.);
@Test
public void testTwoPointInline() {
double m1 = 2.5;
Coordinate r1 = new Coordinate(0,-40, 0, m1);
double I1ax=28.7;
double I1t = I1ax/2;
MassData body1 = new MassData(r1, I1ax, I1t);
double m2 = 5.7;
Coordinate r2 = new Coordinate(0, 32, 0, m2);
double I2ax=20;
double I2t = I2ax/2;
MassData body2 = new MassData(r2, I2ax, I2t);
// point 3 is defined as the CM of bodies 1 and 2 combined.
MassData asbly3 = body1.add(body2);
Coordinate cm3_expected = r1.average(r2);
assertEquals(" Center of Mass calculated incorrectly: ", cm3_expected, asbly3.getCM() );
// these are a bit of a hack, and depend upon all the bodies being along the y=0, z=0 line.
Coordinate delta13 = asbly3.getCM().sub( r1);
Coordinate delta23 = asbly3.getCM().sub( r2);
double y13 = delta13.y;
double dy_13_2 = MathUtil.pow2( y13); // hack
double I13ax = I1ax + m1*dy_13_2;
double I13zz = I1t + m1*dy_13_2;
double y23 = delta23.y;
double dy_23_2 = MathUtil.pow2( y23); // hack
double I23ax = I2ax + m2*dy_23_2 ;
double I23zz = I2t + m2*dy_23_2 ;
double expI3xx = I13ax+I23ax;
double expI3yy = I1t+I2t;
double expI3zz = I13zz+I23zz;
assertEquals("x-axis MOI don't match: ", asbly3.getIxx(), expI3xx, EPSILON*10);
assertEquals("y-axis MOI don't match: ", asbly3.getIyy(), expI3yy, EPSILON*10);
assertEquals("z-axis MOI don't match: ", asbly3.getIzz(), expI3zz, EPSILON*10);
}
@Test
public void testTwoPointGeneral() {
boolean debug=false;
double m1 = 2.5;
Coordinate r1 = new Coordinate(0,-40, -10, m1);
double I1xx=28.7;
double I1t = I1xx/2;
MassData body1 = new MassData(r1, I1xx, I1t);
double m2 = 5.7;
Coordinate r2 = new Coordinate(0, 32, 15, m2);
double I2xx=20;
double I2t = I2xx/2;
MassData body2 = new MassData(r2, I2xx, I2t);
// point 3 is defined as the CM of bodies 1 and 2 combined.
MassData asbly3 = body1.add(body2);
Coordinate cm3_expected = r1.average(r2);
// System.err.println(" @(1): "+ body1.toDebug());
// System.err.println(" @(2): "+ body2.toDebug());
// System.err.println(" @(3): "+ asbly3.toDebug());
System.err.println(" Center of Mass: (3) expected: "+ cm3_expected);
assertEquals(" Center of Mass calculated incorrectly: ", cm3_expected, asbly3.getCM() );
if(debug){
System.err.println(" Body 1: "+ body1.toDebug() );
System.err.println(" Body 2: "+ body2.toDebug() );
System.err.println(" Body 3: "+ asbly3.toDebug() );
}
// these are a bit of a hack, and depend upon all the bodies being along the y=0, z=0 line.
Coordinate delta13 = asbly3.getCM().sub( r1);
Coordinate delta23 = asbly3.getCM().sub( r2);
double x2, y2, z2;
x2 = MathUtil.pow2( delta13.x);
y2 = MathUtil.pow2( delta13.y);
z2 = MathUtil.pow2( delta13.z);
double I13xx = I1xx + m1*(y2+z2);
double I13yy = I1t + m1*(x2+z2);
double I13zz = I1t + m1*(x2+y2);
x2 = MathUtil.pow2( delta23.x);
y2 = MathUtil.pow2( delta23.y);
z2 = MathUtil.pow2( delta23.z);
double I23xx = I2xx + m2*(y2+z2);
double I23yy = I2t + m2*(x2+z2);
double I23zz = I2t + m2*(x2+y2);
double expI3xx = I13xx + I23xx;
assertEquals("x-axis MOI don't match: ", asbly3.getIxx(), expI3xx, EPSILON*10);
double expI3yy = I13yy + I23yy;
assertEquals("y-axis MOI don't match: ", asbly3.getIyy(), expI3yy, EPSILON*10);
double expI3zz = I13zz + I23zz;
assertEquals("z-axis MOI don't match: ", asbly3.getIzz(), expI3zz, EPSILON*10);
}
@Test
public void testMassDataCompoundCalculations() {
double m1 = 2.5;
Coordinate r1 = new Coordinate(0,-40, 0, m1);
double I1ax=28.7;
double I1t = I1ax/2;
MassData body1 = new MassData(r1, I1ax, I1t);
double m2 = m1;
Coordinate r2 = new Coordinate(0, -2, 0, m2);
double I2ax=28.7;
double I2t = I2ax/2;
MassData body2 = new MassData(r2, I2ax, I2t);
double m5 = 5.7;
Coordinate r5 = new Coordinate(0, 32, 0, m5);
double I5ax=20;
double I5t = I5ax/2;
MassData body5 = new MassData(r5, I5ax, I5t);
// point 3 is defined as the CM of bodies 1 and 2 combined.
MassData asbly3 = body1.add(body2);
// point 4 is defined as the CM of bodies 1, 2 and 5 combined.
MassData asbly4_indirect = asbly3.add(body5);
Coordinate cm4_expected = r1.average(r2).average(r5);
//System.err.println(" Center of Mass: (3): "+ asbly3.toCMDebug() );
//System.err.println(" MOI: (3): "+ asbly3.toIMDebug() );
//System.err.println(" Center of Mass: indirect:"+ asbly4_indirect.getCM() );
//System.err.println(" Center of Mass: (4) direct: "+ cm4_expected);
assertEquals(" Center of Mass calculated incorrectly: ", cm4_expected, new Coordinate( 0, 7.233644859813085, 0, m1+m2+m5 ) );
// these are a bit of a hack, and depend upon all the bodies being along the y=0, z=0 line.
double y4 = cm4_expected.y;
double I14ax = I1ax + m1*MathUtil.pow2( Math.abs(body1.getCM().y - y4) );
double I24ax = I2ax + m2*MathUtil.pow2( Math.abs(body2.getCM().y - y4) );
double I54ax = I5ax + m5*MathUtil.pow2( Math.abs(body5.getCM().y - y4) );
double I14zz = I1t + m1*MathUtil.pow2( Math.abs(body1.getCM().y - y4) );
double I24zz = I2t + m2*MathUtil.pow2( Math.abs(body2.getCM().y - y4) );
// System.err.println(String.format(" I24yy: %8g = %6g + %3g*%g", I24zz, I2t, m2, MathUtil.pow2( Math.abs(body2.getCM().y - y4)) ));
// System.err.println(String.format(" : delta y24: %8g = ||%g - %g||", Math.abs(body2.getCM().y - y4), body2.getCM().y, y4 ));
double I54zz = I5t + m5*MathUtil.pow2( Math.abs(body5.getCM().y - y4) );
double I4xx = I14ax+I24ax+I54ax;
double I4yy = I1t+I2t+I5t;
double I4zz = I14zz+I24zz+I54zz;
MassData asbly4_expected = new MassData( cm4_expected, I4xx, I4yy, I4zz);
//System.err.println(String.format(" Ixx: direct: %12g", I4xx ));
assertEquals("x-axis MOI don't match: ", asbly4_indirect.getIxx(), asbly4_expected.getIxx(), EPSILON*10);
//System.err.println(String.format(" Iyy: direct: %12g", I4yy ));
assertEquals("y-axis MOI don't match: ", asbly4_indirect.getIyy(), asbly4_expected.getIyy(), EPSILON*10);
//System.err.println(String.format(" Izz: direct: %12g", I4zz));
assertEquals("z-axis MOI don't match: ", asbly4_indirect.getIzz(), asbly4_expected.getIzz(), EPSILON*10);
}
}