[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:
parent
cac0a52990
commit
4c7c3be9f7
@ -1,22 +1,24 @@
|
|||||||
package net.sf.openrocket.masscalc;
|
package net.sf.openrocket.masscalc;
|
||||||
|
|
||||||
import static net.sf.openrocket.util.MathUtil.pow2;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
|
||||||
import net.sf.openrocket.motor.Motor;
|
import net.sf.openrocket.motor.Motor;
|
||||||
import net.sf.openrocket.motor.MotorInstance;
|
import net.sf.openrocket.motor.MotorInstance;
|
||||||
import net.sf.openrocket.motor.MotorInstanceConfiguration;
|
import net.sf.openrocket.motor.MotorInstanceConfiguration;
|
||||||
import net.sf.openrocket.motor.MotorInstanceId;
|
import net.sf.openrocket.motor.MotorInstanceId;
|
||||||
|
import net.sf.openrocket.motor.ThrustCurveMotor;
|
||||||
import net.sf.openrocket.rocketcomponent.AxialStage;
|
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.FlightConfiguration;
|
||||||
|
import net.sf.openrocket.rocketcomponent.Instanceable;
|
||||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
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.Coordinate;
|
||||||
import net.sf.openrocket.util.MathUtil;
|
import net.sf.openrocket.util.MathUtil;
|
||||||
import net.sf.openrocket.util.Monitorable;
|
import net.sf.openrocket.util.Monitorable;
|
||||||
@ -46,22 +48,29 @@ public class MassCalculator implements Monitorable {
|
|||||||
public abstract Coordinate getCG(Motor motor);
|
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 double MIN_MASS = 0.001 * MathUtil.EPSILON;
|
||||||
private static final Logger log = LoggerFactory.getLogger(MassCalculator.class);
|
|
||||||
|
|
||||||
private int rocketMassModID = -1;
|
private int rocketMassModID = -1;
|
||||||
private int rocketTreeModID = -1;
|
private int rocketTreeModID = -1;
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Cached data. All CG data is in absolute coordinates. All moments of inertia
|
* Cached data. All CG data is in absolute coordinates. All moments of inertia
|
||||||
* are relative to their respective CG.
|
* are relative to their respective CG.
|
||||||
*/
|
*/
|
||||||
private Coordinate[] cgCache = null;
|
private HashMap< Integer, MassData> cache = new HashMap<Integer, MassData >();
|
||||||
private double longitudinalInertiaCache[] = null;
|
// private MassData dryData = null;
|
||||||
private double rotationalInertiaCache[] = 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 ///////////////////
|
////////////////// Mass property calculations ///////////////////
|
||||||
|
|
||||||
@ -75,25 +84,43 @@ public class MassCalculator implements Monitorable {
|
|||||||
* @return the CG of the configuration
|
* @return the CG of the configuration
|
||||||
*/
|
*/
|
||||||
public Coordinate getCG(FlightConfiguration configuration, MassCalcType type) {
|
public Coordinate getCG(FlightConfiguration configuration, MassCalcType type) {
|
||||||
checkCache(configuration);
|
return getCM( configuration, type);
|
||||||
calculateStageCache(configuration);
|
}
|
||||||
|
|
||||||
Coordinate dryCG = null;
|
public Coordinate getCM(FlightConfiguration config, MassCalcType type) {
|
||||||
|
checkCache(config);
|
||||||
|
calculateStageCache(config);
|
||||||
|
|
||||||
// Stage contribution
|
// Stage contribution
|
||||||
for (AxialStage stage : configuration.getActiveStages()) {
|
Coordinate dryCM = Coordinate.ZERO;
|
||||||
int stageNumber = stage.getStageNumber();
|
for (AxialStage stage : config.getActiveStages()) {
|
||||||
dryCG = cgCache[stageNumber].average(dryCG);
|
Integer stageNumber = stage.getStageNumber();
|
||||||
|
MassData stageData = cache.get( stageNumber);
|
||||||
|
if( null == stageData ){
|
||||||
|
throw new BugException("method: calculateStageCache(...) is faulty-- returned null data for an active stage: "+stage.getName()+"("+stage.getStageNumber()+")");
|
||||||
|
}
|
||||||
|
dryCM = stageData.cm.average(dryCM);
|
||||||
|
if( debug){
|
||||||
|
System.err.println(" stageData <<@"+stageNumber+"mass: "+dryCM.weight+" @"+dryCM.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dryCG == null)
|
Coordinate totalCM=null;
|
||||||
dryCG = Coordinate.NUL;
|
if( MassCalcType.NO_MOTORS == type ){
|
||||||
|
totalCM = dryCM;
|
||||||
|
}else{
|
||||||
|
MassData motorData = getMotorMassData(config, type);
|
||||||
|
Coordinate motorCM = motorData.getCM();
|
||||||
|
totalCM = dryCM.average(motorCM);
|
||||||
|
}
|
||||||
|
|
||||||
MotorInstanceConfiguration motorConfig = new MotorInstanceConfiguration(configuration);
|
if(debug){
|
||||||
Coordinate motorCG = getMotorCG(configuration, motorConfig, MassCalcType.LAUNCH_MASS);
|
Coordinate cm = totalCM;
|
||||||
|
System.err.println(String.format("==>> Combined Mass: %5.3gg @( %g, %g, %g)",
|
||||||
|
cm.weight, cm.x, cm.y, cm.z ));
|
||||||
|
}
|
||||||
|
|
||||||
Coordinate totalCG = dryCG.average(motorCG);
|
return totalCM;
|
||||||
return totalCG;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,128 +130,119 @@ public class MassCalculator implements Monitorable {
|
|||||||
* @param motors the motor configuration
|
* @param motors the motor configuration
|
||||||
* @return the CG of the configuration
|
* @return the CG of the configuration
|
||||||
*/
|
*/
|
||||||
public Coordinate getCG(FlightConfiguration configuration, MotorInstanceConfiguration motors) {
|
private MassData getMotorMassData(FlightConfiguration config, MassCalcType type) {
|
||||||
checkCache(configuration);
|
if( MassCalcType.NO_MOTORS == type ){
|
||||||
calculateStageCache(configuration);
|
return MassData.ZERO_DATA;
|
||||||
|
}
|
||||||
Coordinate dryCG = getCG(configuration, MassCalcType.NO_MOTORS);
|
|
||||||
Coordinate motorCG = getMotorCG(configuration, motors, MassCalcType.LAUNCH_MASS);
|
|
||||||
|
|
||||||
Coordinate totalCG = dryCG.average(motorCG);
|
|
||||||
return totalCG;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Coordinate getMotorCG(FlightConfiguration config, MotorInstanceConfiguration motors, MassCalcType type) {
|
|
||||||
Coordinate motorCG = Coordinate.ZERO;
|
|
||||||
|
|
||||||
// Add motor CGs
|
// 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()");
|
|
||||||
}
|
|
||||||
if( null == inst.getMount()){
|
|
||||||
throw new NullPointerException(" detected null mount");
|
|
||||||
}
|
|
||||||
if( null == inst.getMotor()){
|
|
||||||
throw new NullPointerException(" detected null motor");
|
|
||||||
}
|
|
||||||
// END DEVEL
|
|
||||||
|
|
||||||
Coordinate position = inst.getPosition();
|
MassData motorData = MassData.ZERO_DATA;
|
||||||
Coordinate curCG = type.getCG(inst.getMotor()).add(position);
|
|
||||||
motorCG = motorCG.average(curCG);
|
|
||||||
|
|
||||||
}
|
// vvvv DEVEL vvvv
|
||||||
|
if( debug){
|
||||||
|
System.err.println("====== ====== getMotorCM: (type: "+type.name()+") ====== ====== ====== ====== ====== ======");
|
||||||
|
System.err.println(" [Number] [Name] [mass]");
|
||||||
}
|
}
|
||||||
return motorCG;
|
// ^^^^ DEVEL ^^^^
|
||||||
|
|
||||||
|
int motorCount = 0;
|
||||||
|
for (MotorInstance inst : config.getActiveMotors() ) {
|
||||||
|
//ThrustCurveMotor motor = (ThrustCurveMotor) inst.getMotor();
|
||||||
|
|
||||||
|
Coordinate position = inst.getPosition();
|
||||||
|
Coordinate curMotorCM = type.getCG(inst.getMotor()).add(position);
|
||||||
|
double Ir = inst.getRotationalInertia();
|
||||||
|
double It = inst.getLongitudinalInertia();
|
||||||
|
|
||||||
|
MassData instData = new MassData( curMotorCM, Ir, It);
|
||||||
|
motorData = motorData.add( instData );
|
||||||
|
|
||||||
|
// BEGIN DEVEL
|
||||||
|
if( debug){
|
||||||
|
System.err.println(String.format(" motor %2d: %s %s", //%5.3gg @( %g, %g, %g)",
|
||||||
|
motorCount, inst.getMotor().getDesignation(), instData.toDebug()));
|
||||||
|
System.err.println(String.format(" >> %s",
|
||||||
|
motorData.toDebug()));
|
||||||
|
}
|
||||||
|
motorCount++;
|
||||||
|
// END DEVEL
|
||||||
|
}
|
||||||
|
|
||||||
|
return motorData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the longitudinal inertia of the rocket with the specified motor instance
|
* Return the longitudinal inertia of the rocket with the specified motor instance
|
||||||
* configuration.
|
* configuration.
|
||||||
*
|
*
|
||||||
* @param configuration the current motor instance configuration
|
* @param config the current motor instance configuration
|
||||||
* @param motors the motor configuration
|
* @param type the type of analysis to pull
|
||||||
* @return the longitudinal inertia of the rocket
|
* @return the longitudinal inertia of the rocket
|
||||||
*/
|
*/
|
||||||
public double getLongitudinalInertia(FlightConfiguration configuration, MotorInstanceConfiguration motors) {
|
public double getLongitudinalInertia(FlightConfiguration config, MassCalcType type) {
|
||||||
checkCache(configuration);
|
checkCache(config);
|
||||||
calculateStageCache(configuration);
|
calculateStageCache(config);
|
||||||
|
|
||||||
final Coordinate totalCG = getCG(configuration, motors);
|
MassData structureData = MassData.ZERO_DATA;
|
||||||
double totalInertia = 0;
|
|
||||||
|
|
||||||
// Stages
|
// Stages
|
||||||
for (AxialStage stage : configuration.getActiveStages()) {
|
for (AxialStage stage : config.getActiveStages()) {
|
||||||
int stageNumber = stage.getStageNumber();
|
int stageNumber = stage.getStageNumber();
|
||||||
Coordinate stageCG = cgCache[stageNumber];
|
|
||||||
|
|
||||||
totalInertia += (longitudinalInertiaCache[stageNumber] +
|
MassData stageData = cache.get(stageNumber);
|
||||||
stageCG.weight * MathUtil.pow2(stageCG.x - totalCG.x));
|
structureData = structureData.add(stageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
MassData motorData = MassData.ZERO_DATA;
|
||||||
|
if( MassCalcType.NO_MOTORS != type ){
|
||||||
|
motorData = getMotorMassData(config, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Motors
|
MassData totalData = structureData.add( motorData);
|
||||||
if (motors != null) {
|
if(debug){
|
||||||
for (MotorInstance motor : configuration.getActiveMotors()) {
|
System.err.println(String.format("==>> Combined MassData: %s", totalData.toDebug()));
|
||||||
int stage = ((RocketComponent) motor.getMount()).getStageNumber();
|
|
||||||
if (configuration.isStageActive(stage)) {
|
|
||||||
Coordinate position = motor.getPosition();
|
|
||||||
Coordinate cg = motor.getCG().add(position);
|
|
||||||
|
|
||||||
double inertia = motor.getLongitudinalInertia();
|
|
||||||
totalInertia += inertia + cg.weight * MathUtil.pow2(cg.x - totalCG.x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return totalInertia;
|
return totalData.getLongitudinalInertia();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the rotational inertia of the provided configuration with specified motors.
|
* Compute the rotational inertia of the provided configuration with specified motors.
|
||||||
*
|
*
|
||||||
* @param configuration the current motor instance configuration
|
* @param config the current motor instance configuration
|
||||||
* @param motors the motor configuration
|
* @param type the type of analysis to get
|
||||||
* @return the rotational inertia of the configuration
|
* @return the rotational inertia of the configuration
|
||||||
*/
|
*/
|
||||||
public double getRotationalInertia(FlightConfiguration configuration, MotorInstanceConfiguration motors) {
|
public double getRotationalInertia(FlightConfiguration config, MassCalcType type) {
|
||||||
checkCache(configuration);
|
checkCache(config);
|
||||||
calculateStageCache(configuration);
|
calculateStageCache(config);
|
||||||
|
|
||||||
final Coordinate totalCG = getCG(configuration, motors);
|
MassData structureData = MassData.ZERO_DATA;
|
||||||
double totalInertia = 0;
|
|
||||||
|
|
||||||
// Stages
|
// Stages
|
||||||
for (AxialStage stage : configuration.getActiveStages()) {
|
for (AxialStage stage : config.getActiveStages()) {
|
||||||
int stageNumber = stage.getStageNumber();
|
int stageNumber = stage.getStageNumber();
|
||||||
Coordinate stageCG = cgCache[stageNumber];
|
|
||||||
|
|
||||||
totalInertia += (rotationalInertiaCache[stageNumber] +
|
MassData stageData = cache.get(stageNumber);
|
||||||
stageCG.weight * (MathUtil.pow2(stageCG.y - totalCG.y) +
|
structureData = structureData.add(stageData);
|
||||||
MathUtil.pow2(stageCG.z - totalCG.z)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MassData motorData = MassData.ZERO_DATA;
|
||||||
// Motors
|
if( MassCalcType.NO_MOTORS != type ){
|
||||||
if (motors != null) {
|
motorData = getMotorMassData(config, type);
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
* @param configuration the current motor instance configuration
|
||||||
* @return the total mass of all motors
|
* @return the total mass of all motors
|
||||||
*/
|
*/
|
||||||
public double getPropellantMass(FlightConfiguration configuration, MotorInstanceConfiguration motors) {
|
public double getPropellantMass(FlightConfiguration configuration, MassCalcType calcType ){
|
||||||
double mass = 0;
|
double mass = 0;
|
||||||
|
|
||||||
|
//throw new BugException("getPropellantMass is not yet implemented.... ");
|
||||||
// add up the masses of all motors in the rocket
|
// add up the masses of all motors in the rocket
|
||||||
if (motors != null) {
|
if ( MassCalcType.NO_MOTORS != calcType ){
|
||||||
for (MotorInstance motor : configuration.getActiveMotors()) {
|
for (MotorInstance curInstance : configuration.getActiveMotors()) {
|
||||||
mass = mass + motor.getCG().weight - motor.getMotor().getEmptyCG().weight;
|
mass = mass + curInstance.getCG().weight - curInstance.getMotor().getEmptyCG().weight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mass;
|
return mass;
|
||||||
@ -281,24 +300,22 @@ public class MassCalculator implements Monitorable {
|
|||||||
//////// Cache computations ////////
|
//////// Cache computations ////////
|
||||||
|
|
||||||
private void calculateStageCache(FlightConfiguration config) {
|
private void calculateStageCache(FlightConfiguration config) {
|
||||||
if (cgCache == null) {
|
int stageCount = config.getActiveStageCount();
|
||||||
ArrayList<AxialStage> stageList = new ArrayList<AxialStage>();
|
if(debug){
|
||||||
stageList.addAll(config.getRocket().getStageList());
|
System.err.println(">> Calculating CG cache for config: "+config.toShort()+" with "+stageCount+" stages");
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
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
|
* The inertia is returned relative to the CG, and the CG is in the coordinates
|
||||||
* of the specified component, not global coordinates.
|
* of the specified component, not global coordinates.
|
||||||
*/
|
*/
|
||||||
private MassData calculateAssemblyMassData(RocketComponent parent) {
|
private MassData calculateAssemblyMassData(RocketComponent component) {
|
||||||
Coordinate parentCG = Coordinate.ZERO;
|
return calculateAssemblyMassData(component, "....");
|
||||||
double longitudinalInertia = 0.0;
|
}
|
||||||
double rotationalInertia = 0.0;
|
|
||||||
|
|
||||||
// Calculate data for this component
|
private MassData calculateAssemblyMassData(RocketComponent component, String indent) {
|
||||||
parentCG = parent.getComponentCG();
|
|
||||||
if (parentCG.weight < MIN_MASS)
|
|
||||||
parentCG = parentCG.setWeight(MIN_MASS);
|
|
||||||
|
|
||||||
|
Coordinate parentCM = component.getComponentCG();
|
||||||
|
double parentIx = component.getRotationalUnitInertia() * parentCM.weight;
|
||||||
|
double parentIt = component.getLongitudinalUnitInertia() * parentCM.weight;
|
||||||
|
MassData parentData = new MassData( parentCM, parentIx, parentIt);
|
||||||
|
|
||||||
// Override only this component's data
|
if(( debug) &&( 0 < component.getChildCount()) && (MIN_MASS < parentCM.weight)){
|
||||||
if (!parent.getOverrideSubcomponents()) {
|
//System.err.println(String.format("%-32s: %s ",indent+">>["+ component.getName()+"]", parentData.toCMDebug() ));
|
||||||
if (parent.isMassOverridden())
|
System.err.println(String.format("%-32s: %s ",indent+">>["+ component.getName()+"]", parentData.toDebug() ));
|
||||||
parentCG = parentCG.setWeight(MathUtil.max(parent.getOverrideMass(), MIN_MASS));
|
|
||||||
if (parent.isCGOverridden())
|
|
||||||
parentCG = parentCG.setXYZ(parent.getOverrideCG());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
longitudinalInertia = parent.getLongitudinalUnitInertia() * parentCG.weight;
|
if (!component.getOverrideSubcomponents()) {
|
||||||
rotationalInertia = parent.getRotationalUnitInertia() * parentCG.weight;
|
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
|
// Combine data for subcomponents
|
||||||
for (RocketComponent sibling : parent.getChildren()) {
|
for (RocketComponent child : component.getChildren()) {
|
||||||
Coordinate combinedCG;
|
if( child instanceof BoosterSet ){
|
||||||
double dx2, dr2;
|
// this stage will be tallied separately... skip.
|
||||||
|
continue;
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// Override total data
|
||||||
if (parent.getOverrideSubcomponents()) {
|
// if (component.getOverrideSubcomponents()) {
|
||||||
if (parent.isMassOverridden()) {
|
// if (component.isMassOverridden()) {
|
||||||
double oldMass = parentCG.weight;
|
// double oldMass = parentCM.weight;
|
||||||
double newMass = MathUtil.max(parent.getOverrideMass(), MIN_MASS);
|
// double newMass = MathUtil.max(component.getOverrideMass(), MIN_MASS);
|
||||||
longitudinalInertia = longitudinalInertia * newMass / oldMass;
|
// longitudinalInertia = longitudinalInertia * newMass / oldMass;
|
||||||
rotationalInertia = rotationalInertia * newMass / oldMass;
|
// rotationalInertia = rotationalInertia * newMass / oldMass;
|
||||||
parentCG = parentCG.setWeight(newMass);
|
// parentCM = parentCM.setWeight(newMass);
|
||||||
}
|
// }
|
||||||
if (parent.isCGOverridden()) {
|
// if (component.isCGOverridden()) {
|
||||||
double oldx = parentCG.x;
|
// double oldx = parentCM.x;
|
||||||
double newx = parent.getOverrideCGX();
|
// double newx = component.getOverrideCGX();
|
||||||
longitudinalInertia += parentCG.weight * pow2(oldx - newx);
|
// longitudinalInertia += parentCM.weight * MathUtil.pow2(oldx - newx);
|
||||||
parentCG = parentCG.setX(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.
|
* its execution.
|
||||||
*/
|
*/
|
||||||
protected void voidMassCache() {
|
protected void voidMassCache() {
|
||||||
this.cgCache = null;
|
this.cache.clear();
|
||||||
this.longitudinalInertiaCache = null;
|
|
||||||
this.rotationalInertiaCache = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
294
core/src/net/sf/openrocket/masscalc/MassData.java
Normal file
294
core/src/net/sf/openrocket/masscalc/MassData.java
Normal 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() + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -76,6 +76,10 @@ public class MotorInstance implements FlightConfigurableParameter<MotorInstance>
|
|||||||
throw new UnsupportedOperationException("Retrieve a mount from an immutable no-motors instance");
|
throw new UnsupportedOperationException("Retrieve a mount from an immutable no-motors instance");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Coordinate getOffset(){
|
||||||
|
return this.position;
|
||||||
|
}
|
||||||
|
|
||||||
public Coordinate getPosition() {
|
public Coordinate getPosition() {
|
||||||
return this.position;
|
return this.position;
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,10 @@ public final class MotorInstanceId {
|
|||||||
return componentId;
|
return componentId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getInstanceNumber() {
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
|
@ -2,6 +2,7 @@ package net.sf.openrocket.motor;
|
|||||||
|
|
||||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||||
import net.sf.openrocket.rocketcomponent.MotorMount;
|
import net.sf.openrocket.rocketcomponent.MotorMount;
|
||||||
|
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||||
import net.sf.openrocket.util.Coordinate;
|
import net.sf.openrocket.util.Coordinate;
|
||||||
import net.sf.openrocket.util.Inertia;
|
import net.sf.openrocket.util.Inertia;
|
||||||
import net.sf.openrocket.util.MathUtil;
|
import net.sf.openrocket.util.MathUtil;
|
||||||
@ -64,6 +65,17 @@ public class ThrustCurveMotorInstance extends MotorInstance {
|
|||||||
return stepCG;
|
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
|
@Override
|
||||||
public double getLongitudinalInertia() {
|
public double getLongitudinalInertia() {
|
||||||
return unitLongitudinalInertia * stepCG.weight;
|
return unitLongitudinalInertia * stepCG.weight;
|
||||||
@ -94,6 +106,7 @@ public class ThrustCurveMotorInstance extends MotorInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.motor = (ThrustCurveMotor)motor;
|
this.motor = (ThrustCurveMotor)motor;
|
||||||
|
|
||||||
fireChangeEvent();
|
fireChangeEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +128,7 @@ public class ThrustCurveMotorInstance extends MotorInstance {
|
|||||||
@Override
|
@Override
|
||||||
public void setMount(final MotorMount _mount) {
|
public void setMount(final MotorMount _mount) {
|
||||||
this.mount = _mount;
|
this.mount = _mount;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -222,5 +236,10 @@ public class ThrustCurveMotorInstance extends MotorInstance {
|
|||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(){
|
||||||
|
return this.id.toString();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -383,6 +383,7 @@ public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isActingMount=true;
|
||||||
this.motors.set(fcid,newMotorInstance);
|
this.motors.set(fcid,newMotorInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,6 +125,11 @@ public class BoosterSet extends AxialStage implements FlightConfigurableComponen
|
|||||||
return this.radialPosition_m;
|
return this.radialPosition_m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Coordinate[] getInstanceOffsets(){
|
||||||
|
return this.shiftCoordinates(new Coordinate[]{Coordinate.ZERO});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Coordinate[] getLocations() {
|
public Coordinate[] getLocations() {
|
||||||
if (null == this.parent) {
|
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("%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()));
|
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();
|
Coordinate[] absCoords = this.getLocations();
|
||||||
for (int instanceNumber = 0; instanceNumber < this.count; instanceNumber++) {
|
for (int instanceNumber = 0; instanceNumber < this.count; instanceNumber++) {
|
||||||
Coordinate instanceRelativePosition = relCoords[instanceNumber];
|
Coordinate instanceRelativePosition = relCoords[instanceNumber];
|
||||||
Coordinate instanceAbsolutePosition = absCoords[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));
|
instanceRelativePosition, instanceAbsolutePosition));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
@Override
|
||||||
public int getInstanceCount(){
|
public int getInstanceCount(){
|
||||||
return this.instanceCount;
|
return this.instanceCount;
|
||||||
|
@ -12,6 +12,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import net.sf.openrocket.motor.MotorInstance;
|
import net.sf.openrocket.motor.MotorInstance;
|
||||||
|
import net.sf.openrocket.motor.MotorInstanceId;
|
||||||
import net.sf.openrocket.util.ArrayList;
|
import net.sf.openrocket.util.ArrayList;
|
||||||
import net.sf.openrocket.util.ChangeSource;
|
import net.sf.openrocket.util.ChangeSource;
|
||||||
import net.sf.openrocket.util.Coordinate;
|
import net.sf.openrocket.util.Coordinate;
|
||||||
@ -180,36 +181,46 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
|
|||||||
|
|
||||||
|
|
||||||
public List<MotorInstance> getActiveMotors() {
|
public List<MotorInstance> getActiveMotors() {
|
||||||
|
|
||||||
ArrayList<MotorInstance> toReturn = new ArrayList<MotorInstance>();
|
ArrayList<MotorInstance> toReturn = new ArrayList<MotorInstance>();
|
||||||
for ( RocketComponent comp : this.getActiveComponents() ){
|
for ( RocketComponent comp : this.getActiveComponents() ){
|
||||||
|
|
||||||
// see planning notes...
|
|
||||||
if ( comp instanceof MotorMount ){
|
if ( comp instanceof MotorMount ){
|
||||||
MotorMount mount = (MotorMount)comp;
|
MotorMount mount = (MotorMount)comp;
|
||||||
MotorInstance inst = mount.getMotorInstance(this.fcid);
|
MotorInstance inst = mount.getMotorInstance(this.fcid);
|
||||||
|
|
||||||
// NYI: if clustered...
|
// this merely accounts for instancing of this component:
|
||||||
// if( mount instanceof Clusterable ){
|
// int instancCount = comp.getInstanceCount();
|
||||||
// if( 1 < comp.getInstanceCount() ){
|
|
||||||
// if comp is clustered, it will be clustered from the innerTube, no?
|
|
||||||
//List<MotorInstance> instanceList = mount.getMotorInstance(this.fcid);
|
|
||||||
|
|
||||||
// // vvvv DEVEL vvvv
|
// we account for instances this way because it counts *all* the instancing between here
|
||||||
//
|
// and the rocket root.
|
||||||
// if(( mount.isMotorMount()) && ( MotorInstance.EMPTY_INSTANCE == inst)){
|
Coordinate[] instanceLocations= comp.getLocations();
|
||||||
// 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 ^^^^
|
|
||||||
|
|
||||||
// motors go inactive after burnout, so include this filter too
|
// motors go inactive after burnout, so include this filter too
|
||||||
if (inst.isActive()){
|
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
|
// DEBUG / DEVEL
|
||||||
public String toDebug() {
|
public String toDebug() {
|
||||||
|
@ -32,7 +32,7 @@ public class InnerTube extends ThicknessRingComponent implements Clusterable, Ra
|
|||||||
private double clusterRotation = 0.0;
|
private double clusterRotation = 0.0;
|
||||||
|
|
||||||
private double overhang = 0;
|
private double overhang = 0;
|
||||||
private boolean isActing;
|
private boolean isActingMount;
|
||||||
private MotorConfigurationSet motors;
|
private MotorConfigurationSet motors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -215,6 +215,11 @@ public class InnerTube extends ThicknessRingComponent implements Clusterable, Ra
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Coordinate[] getInstanceOffsets(){
|
||||||
|
return this.shiftCoordinates(new Coordinate[]{Coordinate.ZERO});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Coordinate[] getLocations(){
|
public Coordinate[] getLocations(){
|
||||||
if (null == this.parent) {
|
if (null == this.parent) {
|
||||||
@ -277,6 +282,7 @@ public class InnerTube extends ThicknessRingComponent implements Clusterable, Ra
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isActingMount = true;
|
||||||
this.motors.set(fcid,newMotorInstance);
|
this.motors.set(fcid,newMotorInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,15 +299,15 @@ public class InnerTube extends ThicknessRingComponent implements Clusterable, Ra
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMotorMount(boolean _active){
|
public void setMotorMount(boolean _active){
|
||||||
if (this.isActing == _active)
|
if (this.isActingMount == _active)
|
||||||
return;
|
return;
|
||||||
this.isActing = _active;
|
this.isActingMount = _active;
|
||||||
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
|
fireComponentChangeEvent(ComponentChangeEvent.MOTOR_CHANGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isMotorMount(){
|
public boolean isMotorMount(){
|
||||||
return this.isActing;
|
return this.isActingMount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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("%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()));
|
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();
|
Coordinate[] absCoords = this.getLocations();
|
||||||
FlightConfigurationID curId = this.getRocket().getDefaultConfiguration().getFlightConfigurationID();
|
FlightConfigurationID curId = this.getRocket().getDefaultConfiguration().getFlightConfigurationID();
|
||||||
int count = this.getInstanceCount();
|
int count = this.getInstanceCount();
|
||||||
MotorInstance curInstance = this.motors.get(curId);
|
MotorInstance curInstance = this.motors.get(curId);
|
||||||
if( curInstance.isEmpty() ){
|
//if( curInstance.isEmpty() ){
|
||||||
|
{
|
||||||
// print just the tube locations
|
// 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++) {
|
for (int instanceNumber = 0; instanceNumber < count; instanceNumber++) {
|
||||||
Coordinate instanceRelativePosition = relCoords[instanceNumber];
|
Coordinate tubeRelativePosition = relCoords[instanceNumber];
|
||||||
Coordinate instanceAbsolutePosition = absCoords[instanceNumber];
|
Coordinate tubeAbsolutePosition = absCoords[instanceNumber];
|
||||||
buffer.append(String.format("%s [%2d / %2d] %28s %28s\n", prefix, instanceNumber, count,
|
buffer.append(String.format("%s [%2d/%2d]; %28s; %28s;\n", prefix, instanceNumber, count,
|
||||||
instanceRelativePosition, instanceAbsolutePosition));
|
tubeRelativePosition, tubeAbsolutePosition));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( curInstance.isEmpty() ){
|
||||||
|
buffer.append(prefix+" [X] This Instance doesn't have any motors... showing mount tubes only\n");
|
||||||
}else{
|
}else{
|
||||||
// curInstance has a motor ...
|
// curInstance has a motor ...
|
||||||
Motor curMotor = curInstance.getMotor();
|
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++) {
|
for (int instanceNumber = 0; instanceNumber < count; instanceNumber++) {
|
||||||
Coordinate instanceRelativePosition = relCoords[instanceNumber];
|
Coordinate motorRelativePosition = new Coordinate(motorOffset, 0, 0);
|
||||||
Coordinate instanceAbsolutePosition = absCoords[instanceNumber];
|
Coordinate tubeAbs = absCoords[instanceNumber];
|
||||||
buffer.append(String.format("%s [%2d / %2d] %28s %28s\n", prefix, instanceNumber, count,
|
Coordinate motorAbsolutePosition = new Coordinate(tubeAbs.x+motorOffset,tubeAbs.y,tubeAbs.z);
|
||||||
instanceRelativePosition, instanceAbsolutePosition));
|
buffer.append(String.format("%s [%2d/%2d]; %28s; %28s;\n", prefix, instanceNumber, count,
|
||||||
|
motorRelativePosition, motorAbsolutePosition));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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.
|
* 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();
|
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.
|
* How many instances of this component are represented. This should generally be editable.
|
||||||
* @param newCount number of instances to set
|
* @param newCount number of instances to set
|
||||||
|
@ -119,7 +119,17 @@ public abstract class LaunchButton extends ExternalComponent implements LineInst
|
|||||||
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
|
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
|
@Override
|
||||||
protected void loadFromPreset(ComponentPreset preset) {
|
protected void loadFromPreset(ComponentPreset preset) {
|
||||||
|
@ -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
|
@Override
|
||||||
protected Coordinate[] shiftCoordinates(Coordinate[] array) {
|
protected Coordinate[] shiftCoordinates(Coordinate[] array) {
|
||||||
array = super.shiftCoordinates(array);
|
array = super.shiftCoordinates(array);
|
||||||
|
@ -76,6 +76,11 @@ public class PodSet extends ComponentAssembly implements RingInstanceable {
|
|||||||
return BodyComponent.class.isAssignableFrom(type);
|
return BodyComponent.class.isAssignableFrom(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Coordinate[] getInstanceOffsets(){
|
||||||
|
return shiftCoordinates(new Coordinate[]{Coordinate.ZERO});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Coordinate[] getLocations() {
|
public Coordinate[] getLocations() {
|
||||||
if (null == this.parent) {
|
if (null == this.parent) {
|
||||||
|
@ -1083,9 +1083,13 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
|||||||
return this.position;
|
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
|
* @return
|
||||||
*/
|
*/
|
||||||
private Coordinate getAbsoluteVector() {
|
private Coordinate getAbsoluteVector() {
|
||||||
@ -2081,7 +2085,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void toDebugTreeNode(final StringBuilder buffer, final String prefix) {
|
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]));
|
this.getOffset(), this.getLocations()[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ public abstract class RocketUtils {
|
|||||||
return length;
|
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) {
|
public static Coordinate getCG(Rocket rocket, MassCalcType calcType) {
|
||||||
MassCalculator massCalculator = new MassCalculator();
|
MassCalculator massCalculator = new MassCalculator();
|
||||||
Coordinate cg = massCalculator.getCG(rocket.getDefaultConfiguration(), calcType);
|
Coordinate cg = massCalculator.getCG(rocket.getDefaultConfiguration(), calcType);
|
||||||
|
@ -3,6 +3,8 @@ package net.sf.openrocket.simulation;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import net.sf.openrocket.masscalc.MassCalculator;
|
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.models.atmosphere.AtmosphericConditions;
|
||||||
import net.sf.openrocket.motor.MotorInstance;
|
import net.sf.openrocket.motor.MotorInstance;
|
||||||
import net.sf.openrocket.motor.MotorInstanceConfiguration;
|
import net.sf.openrocket.motor.MotorInstanceConfiguration;
|
||||||
@ -124,10 +126,11 @@ public abstract class AbstractSimulationStepper implements SimulationStepper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MassCalculator calc = status.getSimulationConditions().getMassCalculator();
|
MassCalculator calc = status.getSimulationConditions().getMassCalculator();
|
||||||
cg = calc.getCG(status.getConfiguration(), status.getMotorConfiguration());
|
// not sure if this is actually Launch mass or not...
|
||||||
longitudinalInertia = calc.getLongitudinalInertia(status.getConfiguration(), status.getMotorConfiguration());
|
cg = calc.getCG(status.getConfiguration(), MassCalcType.LAUNCH_MASS);
|
||||||
rotationalInertia = calc.getRotationalInertia(status.getConfiguration(), status.getMotorConfiguration());
|
longitudinalInertia = calc.getLongitudinalInertia(status.getConfiguration(), MassCalcType.LAUNCH_MASS);
|
||||||
propellantMass = calc.getPropellantMass(status.getConfiguration(), status.getMotorConfiguration());
|
rotationalInertia = calc.getRotationalInertia(status.getConfiguration(), MassCalcType.LAUNCH_MASS);
|
||||||
|
propellantMass = calc.getPropellantMass(status.getConfiguration(), MassCalcType.LAUNCH_MASS);
|
||||||
mass = new MassData(cg, longitudinalInertia, rotationalInertia, propellantMass);
|
mass = new MassData(cg, longitudinalInertia, rotationalInertia, propellantMass);
|
||||||
|
|
||||||
// Call post-listener
|
// Call post-listener
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.sf.openrocket.simulation;
|
package net.sf.openrocket.simulation;
|
||||||
|
|
||||||
|
import net.sf.openrocket.masscalc.MassData;
|
||||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||||
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
|
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
|
||||||
import net.sf.openrocket.simulation.exception.SimulationException;
|
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.sf.openrocket.simulation;
|
package net.sf.openrocket.simulation;
|
||||||
|
|
||||||
|
import net.sf.openrocket.masscalc.MassData;
|
||||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||||
import net.sf.openrocket.simulation.exception.SimulationException;
|
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||||
import net.sf.openrocket.util.Coordinate;
|
import net.sf.openrocket.util.Coordinate;
|
||||||
|
@ -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 + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -10,6 +10,7 @@ import net.sf.openrocket.aerodynamics.AerodynamicForces;
|
|||||||
import net.sf.openrocket.aerodynamics.FlightConditions;
|
import net.sf.openrocket.aerodynamics.FlightConditions;
|
||||||
import net.sf.openrocket.aerodynamics.WarningSet;
|
import net.sf.openrocket.aerodynamics.WarningSet;
|
||||||
import net.sf.openrocket.l10n.Translator;
|
import net.sf.openrocket.l10n.Translator;
|
||||||
|
import net.sf.openrocket.masscalc.MassData;
|
||||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||||
import net.sf.openrocket.simulation.exception.SimulationCalculationException;
|
import net.sf.openrocket.simulation.exception.SimulationCalculationException;
|
||||||
import net.sf.openrocket.simulation.exception.SimulationException;
|
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||||
|
@ -8,6 +8,7 @@ import javax.script.ScriptException;
|
|||||||
|
|
||||||
import net.sf.openrocket.aerodynamics.AerodynamicForces;
|
import net.sf.openrocket.aerodynamics.AerodynamicForces;
|
||||||
import net.sf.openrocket.aerodynamics.FlightConditions;
|
import net.sf.openrocket.aerodynamics.FlightConditions;
|
||||||
|
import net.sf.openrocket.masscalc.MassData;
|
||||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||||
import net.sf.openrocket.motor.MotorInstanceId;
|
import net.sf.openrocket.motor.MotorInstanceId;
|
||||||
import net.sf.openrocket.motor.MotorInstance;
|
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.rocketcomponent.RecoveryDevice;
|
||||||
import net.sf.openrocket.simulation.AccelerationData;
|
import net.sf.openrocket.simulation.AccelerationData;
|
||||||
import net.sf.openrocket.simulation.FlightEvent;
|
import net.sf.openrocket.simulation.FlightEvent;
|
||||||
import net.sf.openrocket.simulation.MassData;
|
|
||||||
import net.sf.openrocket.simulation.SimulationStatus;
|
import net.sf.openrocket.simulation.SimulationStatus;
|
||||||
import net.sf.openrocket.simulation.exception.SimulationException;
|
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||||
import net.sf.openrocket.simulation.exception.SimulationListenerException;
|
import net.sf.openrocket.simulation.exception.SimulationListenerException;
|
||||||
|
@ -2,6 +2,7 @@ package net.sf.openrocket.simulation.listeners;
|
|||||||
|
|
||||||
import net.sf.openrocket.aerodynamics.AerodynamicForces;
|
import net.sf.openrocket.aerodynamics.AerodynamicForces;
|
||||||
import net.sf.openrocket.aerodynamics.FlightConditions;
|
import net.sf.openrocket.aerodynamics.FlightConditions;
|
||||||
|
import net.sf.openrocket.masscalc.MassData;
|
||||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||||
import net.sf.openrocket.motor.MotorInstanceId;
|
import net.sf.openrocket.motor.MotorInstanceId;
|
||||||
import net.sf.openrocket.motor.MotorInstance;
|
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.rocketcomponent.RecoveryDevice;
|
||||||
import net.sf.openrocket.simulation.AccelerationData;
|
import net.sf.openrocket.simulation.AccelerationData;
|
||||||
import net.sf.openrocket.simulation.FlightEvent;
|
import net.sf.openrocket.simulation.FlightEvent;
|
||||||
import net.sf.openrocket.simulation.MassData;
|
|
||||||
import net.sf.openrocket.simulation.SimulationStatus;
|
import net.sf.openrocket.simulation.SimulationStatus;
|
||||||
import net.sf.openrocket.simulation.exception.SimulationException;
|
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||||
import net.sf.openrocket.util.BugException;
|
import net.sf.openrocket.util.BugException;
|
||||||
|
@ -2,9 +2,9 @@ package net.sf.openrocket.simulation.listeners;
|
|||||||
|
|
||||||
import net.sf.openrocket.aerodynamics.AerodynamicForces;
|
import net.sf.openrocket.aerodynamics.AerodynamicForces;
|
||||||
import net.sf.openrocket.aerodynamics.FlightConditions;
|
import net.sf.openrocket.aerodynamics.FlightConditions;
|
||||||
|
import net.sf.openrocket.masscalc.MassData;
|
||||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||||
import net.sf.openrocket.simulation.AccelerationData;
|
import net.sf.openrocket.simulation.AccelerationData;
|
||||||
import net.sf.openrocket.simulation.MassData;
|
|
||||||
import net.sf.openrocket.simulation.SimulationStatus;
|
import net.sf.openrocket.simulation.SimulationStatus;
|
||||||
import net.sf.openrocket.simulation.exception.SimulationException;
|
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||||
import net.sf.openrocket.util.Coordinate;
|
import net.sf.openrocket.util.Coordinate;
|
||||||
|
@ -7,6 +7,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
import net.sf.openrocket.aerodynamics.AerodynamicForces;
|
import net.sf.openrocket.aerodynamics.AerodynamicForces;
|
||||||
import net.sf.openrocket.aerodynamics.FlightConditions;
|
import net.sf.openrocket.aerodynamics.FlightConditions;
|
||||||
import net.sf.openrocket.aerodynamics.Warning;
|
import net.sf.openrocket.aerodynamics.Warning;
|
||||||
|
import net.sf.openrocket.masscalc.MassData;
|
||||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||||
import net.sf.openrocket.motor.MotorInstanceId;
|
import net.sf.openrocket.motor.MotorInstanceId;
|
||||||
import net.sf.openrocket.motor.MotorInstance;
|
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.rocketcomponent.RecoveryDevice;
|
||||||
import net.sf.openrocket.simulation.AccelerationData;
|
import net.sf.openrocket.simulation.AccelerationData;
|
||||||
import net.sf.openrocket.simulation.FlightEvent;
|
import net.sf.openrocket.simulation.FlightEvent;
|
||||||
import net.sf.openrocket.simulation.MassData;
|
|
||||||
import net.sf.openrocket.simulation.SimulationStatus;
|
import net.sf.openrocket.simulation.SimulationStatus;
|
||||||
import net.sf.openrocket.simulation.exception.SimulationException;
|
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||||
import net.sf.openrocket.util.Coordinate;
|
import net.sf.openrocket.util.Coordinate;
|
||||||
|
@ -12,15 +12,19 @@ import net.sf.openrocket.material.Material.Type;
|
|||||||
import net.sf.openrocket.motor.Manufacturer;
|
import net.sf.openrocket.motor.Manufacturer;
|
||||||
import net.sf.openrocket.motor.Motor;
|
import net.sf.openrocket.motor.Motor;
|
||||||
import net.sf.openrocket.motor.MotorInstance;
|
import net.sf.openrocket.motor.MotorInstance;
|
||||||
|
import net.sf.openrocket.motor.MotorInstanceId;
|
||||||
import net.sf.openrocket.motor.ThrustCurveMotor;
|
import net.sf.openrocket.motor.ThrustCurveMotor;
|
||||||
|
import net.sf.openrocket.motor.ThrustCurveMotorInstance;
|
||||||
import net.sf.openrocket.preset.ComponentPreset;
|
import net.sf.openrocket.preset.ComponentPreset;
|
||||||
import net.sf.openrocket.preset.ComponentPresetFactory;
|
import net.sf.openrocket.preset.ComponentPresetFactory;
|
||||||
import net.sf.openrocket.preset.InvalidComponentPresetException;
|
import net.sf.openrocket.preset.InvalidComponentPresetException;
|
||||||
import net.sf.openrocket.preset.TypedPropertyMap;
|
import net.sf.openrocket.preset.TypedPropertyMap;
|
||||||
import net.sf.openrocket.rocketcomponent.AxialStage;
|
import net.sf.openrocket.rocketcomponent.AxialStage;
|
||||||
import net.sf.openrocket.rocketcomponent.BodyTube;
|
import net.sf.openrocket.rocketcomponent.BodyTube;
|
||||||
|
import net.sf.openrocket.rocketcomponent.BoosterSet;
|
||||||
import net.sf.openrocket.rocketcomponent.Bulkhead;
|
import net.sf.openrocket.rocketcomponent.Bulkhead;
|
||||||
import net.sf.openrocket.rocketcomponent.CenteringRing;
|
import net.sf.openrocket.rocketcomponent.CenteringRing;
|
||||||
|
import net.sf.openrocket.rocketcomponent.ClusterConfiguration;
|
||||||
import net.sf.openrocket.rocketcomponent.DeploymentConfiguration;
|
import net.sf.openrocket.rocketcomponent.DeploymentConfiguration;
|
||||||
import net.sf.openrocket.rocketcomponent.DeploymentConfiguration.DeployEvent;
|
import net.sf.openrocket.rocketcomponent.DeploymentConfiguration.DeployEvent;
|
||||||
import net.sf.openrocket.rocketcomponent.ExternalComponent;
|
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.ReferenceType;
|
||||||
import net.sf.openrocket.rocketcomponent.Rocket;
|
import net.sf.openrocket.rocketcomponent.Rocket;
|
||||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||||
|
import net.sf.openrocket.rocketcomponent.ShockCord;
|
||||||
import net.sf.openrocket.rocketcomponent.RocketComponent.Position;
|
import net.sf.openrocket.rocketcomponent.RocketComponent.Position;
|
||||||
import net.sf.openrocket.rocketcomponent.StageSeparationConfiguration;
|
import net.sf.openrocket.rocketcomponent.StageSeparationConfiguration;
|
||||||
import net.sf.openrocket.rocketcomponent.Transition;
|
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
|
* 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;
|
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
|
* Create a new file version 1.00 rocket
|
||||||
|
@ -2,176 +2,463 @@ package net.sf.openrocket.masscalc;
|
|||||||
|
|
||||||
//import junit.framework.TestCase;
|
//import junit.framework.TestCase;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import net.sf.openrocket.rocketcomponent.AxialStage;
|
import net.sf.openrocket.masscalc.MassCalculator.MassCalcType;
|
||||||
import net.sf.openrocket.rocketcomponent.BodyTube;
|
import net.sf.openrocket.motor.MotorInstance;
|
||||||
import net.sf.openrocket.rocketcomponent.BoosterSet;
|
import net.sf.openrocket.rocketcomponent.BoosterSet;
|
||||||
import net.sf.openrocket.rocketcomponent.ClusterConfiguration;
|
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
|
||||||
import net.sf.openrocket.rocketcomponent.FinSet;
|
import net.sf.openrocket.rocketcomponent.FlightConfigurationID;
|
||||||
import net.sf.openrocket.rocketcomponent.InnerTube;
|
import net.sf.openrocket.rocketcomponent.InnerTube;
|
||||||
import net.sf.openrocket.rocketcomponent.NoseCone;
|
|
||||||
import net.sf.openrocket.rocketcomponent.Rocket;
|
import net.sf.openrocket.rocketcomponent.Rocket;
|
||||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
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.Coordinate;
|
||||||
|
import net.sf.openrocket.util.MathUtil;
|
||||||
|
import net.sf.openrocket.util.TestRockets;
|
||||||
import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
|
import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
|
||||||
|
|
||||||
public class MassCalculatorTest extends BaseTestCase {
|
public class MassCalculatorTest extends BaseTestCase {
|
||||||
|
|
||||||
// tolerance for compared double test results
|
// 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.);
|
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
|
@Test
|
||||||
public void testTestRocketMasses() {
|
public void testRocketNoMotors() {
|
||||||
RocketComponent rocket = createTestRocket();
|
Rocket rkt = TestRockets.makeNoMotorRocket();
|
||||||
String treeDump = rocket.toDebugTree();
|
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;
|
double expMass;
|
||||||
|
RocketComponent cc;
|
||||||
double compMass;
|
double compMass;
|
||||||
double calcMass;
|
|
||||||
|
|
||||||
expMass = 0.093417755;
|
// ====== Payload Stage ======
|
||||||
compMass = rocket.getChild(0).getChild(0).getComponentMass();
|
// ====== ====== ====== ======
|
||||||
assertEquals(" NoseCone mass calculated incorrectly: ", expMass, compMass, EPSILON);
|
{
|
||||||
|
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;
|
expMass = 0.02904490372;
|
||||||
compMass = rocket.getChild(0).getChild(1).getComponentMass();
|
cc= rkt.getChild(0).getChild(1);
|
||||||
assertEquals(" Sustainer Body mass calculated incorrectly: ", expMass, compMass, EPSILON);
|
compMass = cc.getComponentMass();
|
||||||
|
assertEquals("P/L Body mass calculated incorrectly: ", expMass, compMass, EPSILON);
|
||||||
|
|
||||||
expMass = 0.255072190;
|
expMass = 0.007289284477103441;
|
||||||
compMass = rocket.getChild(1).getChild(0).getComponentMass();
|
cc= rkt.getChild(0).getChild(2);
|
||||||
assertEquals(" Core Body mass calculated incorrectly: ", expMass, compMass, EPSILON);
|
compMass = cc.getComponentMass();
|
||||||
|
assertEquals("P/L Transition mass calculated incorrectly: ", expMass, compMass, EPSILON);
|
||||||
|
|
||||||
expMass = 0.9792000;
|
expMass = 0.029224351500753608;
|
||||||
compMass = rocket.getChild(1).getChild(0).getChild(0).getComponentMass();
|
cc= rkt.getChild(0).getChild(3);
|
||||||
assertEquals(" Core Fins mass calculated incorrectly: ", expMass, compMass, EPSILON);
|
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.00072;
|
||||||
expMass = 0.0055329;
|
cc= rkt.getChild(0).getChild(3).getChild(1);
|
||||||
compMass = motorCluster.getComponentMass();
|
compMass = cc.getComponentMass();
|
||||||
assertEquals(" Core Motor Mount Tubes: mass calculated incorrectly: ", expMass, compMass, EPSILON);
|
assertEquals(cc.getName()+" mass calculated incorrectly: ", expMass, compMass, EPSILON);
|
||||||
compMass = motorCluster.getMass();
|
}
|
||||||
assertEquals(" Core Motor Mount Tubes: mass calculated incorrectly: ", expMass, compMass, EPSILON);
|
|
||||||
|
|
||||||
expMass = 490.061;
|
expMass = 0.01948290100050243;
|
||||||
// MassCalculator mc = new BasicMassCalculator();
|
cc= rkt.getChild(0).getChild(4);
|
||||||
// calcMass = mc.getMass();
|
compMass = cc.getComponentMass();
|
||||||
calcMass = rocket.getMass();
|
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
|
@Test
|
||||||
public void testTestRocketCG() {
|
public void testTestComponentMOIs() {
|
||||||
RocketComponent rocket = createTestRocket();
|
Rocket rkt = TestRockets.makeFalcon9Heavy();
|
||||||
String treeDump = rocket.toDebugTree();
|
rkt.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName());
|
||||||
double expRelCGx;
|
|
||||||
double expAbsCGx;
|
|
||||||
double actualRelCGx;
|
|
||||||
double actualAbsCGx;
|
|
||||||
|
|
||||||
expRelCGx = 0.134068822;
|
double expInertia;
|
||||||
actualRelCGx = rocket.getChild(0).getChild(0).getComponentCG().x;
|
RocketComponent cc;
|
||||||
assertEquals(" NoseCone CG calculated incorrectly: ", expRelCGx, actualRelCGx, EPSILON);
|
double compInertia;
|
||||||
|
|
||||||
expRelCGx = 0.15;
|
// ====== Payload Stage ======
|
||||||
actualRelCGx = rocket.getChild(0).getChild(1).getComponentCG().x;
|
// ====== ====== ====== ======
|
||||||
assertEquals(" Sustainer Body cg calculated incorrectly: ", expRelCGx, actualRelCGx, EPSILON);
|
{
|
||||||
|
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);
|
cc= rkt.getChild(0).getChild(1);
|
||||||
expRelCGx = 0.3; // relative to parent
|
expInertia = 7.70416e-5;
|
||||||
actualRelCGx = coreBody.getComponentCG().x;
|
compInertia = cc.getRotationalInertia();
|
||||||
assertEquals(" Core Body (relative) cg calculated incorrectly: ", expRelCGx, actualRelCGx, EPSILON);
|
assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
|
||||||
//expAbsCGx = 0.8;
|
expInertia = 8.06940e-5;
|
||||||
//actualAbsCGx = coreBody.getCG().x;
|
compInertia = cc.getLongitudinalInertia();
|
||||||
//assertEquals(" Core Body (absolute) cg calculated incorrectly: ", expAbsCGx, actualAbsCGx, EPSILON);
|
assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
|
||||||
|
|
||||||
FinSet coreFins = (FinSet) rocket.getChild(1).getChild(0).getChild(0);
|
cc= rkt.getChild(0).getChild(2);
|
||||||
expRelCGx = 0.244444444; // relative to parent
|
expInertia = 1.43691e-5;
|
||||||
actualRelCGx = coreFins.getComponentCG().x;
|
compInertia = cc.getRotationalInertia();
|
||||||
assertEquals(" Core Fins (relative) cg calculated incorrectly: ", expRelCGx, actualRelCGx, EPSILON);
|
assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
|
||||||
// expAbsCGx = 0.9444444444;
|
expInertia = 7.30265e-6;
|
||||||
// actualAbsCGx = coreBody.getCG().x;
|
compInertia = cc.getLongitudinalInertia();
|
||||||
// assertEquals(" Core Fins (absolute) cg calculated incorrectly: ", expAbsCGx, actualAbsCGx, EPSILON);
|
assertEquals(cc.getName()+" Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
|
||||||
|
|
||||||
expRelCGx = 10.061;
|
cc= rkt.getChild(0).getChild(3);
|
||||||
// MassCalculator mc = new BasicMassCalculator();
|
expInertia = 4.22073e-5;
|
||||||
actualRelCGx = rocket.getCG().x;
|
compInertia = cc.getRotationalInertia();
|
||||||
// mc.getCG(Configuration configuration, MotorInstanceConfiguration motors) {
|
assertEquals(cc.getName()+" Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
|
||||||
// calcMass = mc.getMass();
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
190
core/test/net/sf/openrocket/masscalc/MassDataTest.java
Normal file
190
core/test/net/sf/openrocket/masscalc/MassDataTest.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user