[fix] Fix MassCalculator Unittests.
(Effectively a re-write of the MassCalculation code) - renamed some mass calculator accesor methods - MassData, InertiaMatrix refactored into 'RigidBody' class - refactors out cache code to separate wrapper class - calculations now use the Transform class to translate masses, CM, and MOI - new class: MassCalculation - contains relevant context for a given calculation: tree-root, type, time, config - contains most actual calculation code - calculations are tracked with a context class: MassCalculation
This commit is contained in:
parent
6289aef0ef
commit
20eff575f4
@ -96,7 +96,7 @@ public class BodyTubeDTO extends BasePartDTO implements AttachableParts {
|
||||
final InnerTube innerTube = (InnerTube) rocketComponents;
|
||||
final InnerBodyTubeDTO innerBodyTubeDTO = new InnerBodyTubeDTO(innerTube, this);
|
||||
//Only add the inner tube if it is NOT a cluster.
|
||||
if (innerTube.getClusterCount() == 1) {
|
||||
if (innerTube.getInstanceCount() == 1) {
|
||||
attachedParts.add(innerBodyTubeDTO);
|
||||
}
|
||||
} else if (rocketComponents instanceof BodyTube) {
|
||||
|
@ -62,7 +62,7 @@ public class InnerBodyTubeDTO extends BodyTubeDTO implements AttachableParts {
|
||||
//Only if the inner tube is NOT a cluster, then create the corresponding Rocksim DTO and add it
|
||||
//to the list of attached parts. If it is a cluster, then it is handled specially outside of this
|
||||
//loop.
|
||||
if (innerTube.getClusterCount() == 1) {
|
||||
if (innerTube.getInstanceCount() == 1) {
|
||||
attachedParts.add(new InnerBodyTubeDTO(innerTube, this));
|
||||
}
|
||||
} else if (rocketComponents instanceof BodyTube) {
|
||||
|
@ -17,6 +17,7 @@ import net.sf.openrocket.document.StorageOptions;
|
||||
import net.sf.openrocket.file.RocketSaver;
|
||||
import net.sf.openrocket.file.rocksim.RocksimCommonConstants;
|
||||
import net.sf.openrocket.masscalc.MassCalculator;
|
||||
import net.sf.openrocket.masscalc.RigidBody;
|
||||
import net.sf.openrocket.rocketcomponent.AxialStage;
|
||||
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
|
||||
import net.sf.openrocket.rocketcomponent.Rocket;
|
||||
@ -92,11 +93,9 @@ public class RocksimSaver extends RocketSaver {
|
||||
private RocketDesignDTO toRocketDesignDTO(Rocket rocket) {
|
||||
RocketDesignDTO result = new RocketDesignDTO();
|
||||
|
||||
MassCalculator massCalc = new MassCalculator();
|
||||
|
||||
final FlightConfiguration configuration = rocket.getEmptyConfiguration();
|
||||
final double cg = massCalc.getRocketSpentMassData(configuration).getCM().x *
|
||||
RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH;
|
||||
final RigidBody spentData = MassCalculator.calculateStructure( configuration);
|
||||
final double cg = spentData.cm.x *RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH;
|
||||
|
||||
int stageCount = rocket.getStageCount();
|
||||
if (stageCount == 3) {
|
||||
|
369
core/src/net/sf/openrocket/masscalc/MassCalculation.java
Normal file
369
core/src/net/sf/openrocket/masscalc/MassCalculation.java
Normal file
@ -0,0 +1,369 @@
|
||||
package net.sf.openrocket.masscalc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import net.sf.openrocket.motor.Motor;
|
||||
import net.sf.openrocket.motor.MotorConfiguration;
|
||||
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
|
||||
import net.sf.openrocket.rocketcomponent.MotorMount;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.util.MathUtil;
|
||||
import net.sf.openrocket.util.Transformation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author teyrana (aka Daniel Williams) <equipoise@gmail.com>
|
||||
*
|
||||
*/
|
||||
public class MassCalculation {
|
||||
|
||||
/**
|
||||
* NOTE: Multiple enums may map to the same settings. This allows a caller to use the
|
||||
* enum which best matches their use case.
|
||||
*/
|
||||
public enum Type {
|
||||
// no motor data, even if the configuration contains active engines
|
||||
STRUCTURE( true, false, false ),
|
||||
|
||||
// n.b. 'motor-mass' calculations are to be preferred over 'propellant-mass' calculations because they are computationally simpler:
|
||||
// not only are they faster, but *slightly* more accurate because of fewer calculation rounding errors
|
||||
MOTOR( false, true, true ),
|
||||
|
||||
BURNOUT( true, true, false ),
|
||||
|
||||
LAUNCH( true, true, true );
|
||||
|
||||
public final boolean includesStructure;
|
||||
public final boolean includesMotorCasing;
|
||||
public final boolean includesPropellant;
|
||||
|
||||
Type( double simulationTime ) {
|
||||
includesStructure = false;
|
||||
includesMotorCasing = true;
|
||||
includesPropellant = true;
|
||||
}
|
||||
|
||||
Type( boolean include_structure, boolean include_casing, boolean include_prop) {
|
||||
this.includesStructure = include_structure;
|
||||
this.includesMotorCasing = include_casing;
|
||||
this.includesPropellant = include_prop;
|
||||
}
|
||||
}
|
||||
|
||||
// =========== Instance Functions ========================
|
||||
|
||||
public void merge( final MassCalculation other ) {
|
||||
if( 0 < other.getMass()) {
|
||||
// Adjust Center-of-mass
|
||||
this.addMass( other.getCM() );
|
||||
this.bodies.addAll( other.bodies );
|
||||
}
|
||||
}
|
||||
|
||||
public void addInertia( final RigidBody data ) {
|
||||
this.bodies.add( data );
|
||||
}
|
||||
|
||||
public void addMass( final Coordinate pointMass ) {
|
||||
if( 0 == this.centerOfMass.weight ){
|
||||
this.centerOfMass = pointMass;
|
||||
}else {
|
||||
this.centerOfMass = this.centerOfMass.average( pointMass);
|
||||
}
|
||||
}
|
||||
|
||||
public MassCalculation copy( final RocketComponent _root, final Transformation _transform ){
|
||||
return new MassCalculation( this.type, this.config, this.simulationTime, _root, _transform );
|
||||
}
|
||||
|
||||
public Coordinate getCM() {
|
||||
return this.centerOfMass;
|
||||
}
|
||||
|
||||
public double getMass() {
|
||||
return this.centerOfMass.weight;
|
||||
}
|
||||
|
||||
public double getLongitudinalInertia() {
|
||||
return this.inertia.Iyy;
|
||||
}
|
||||
|
||||
public double getRotationalInertia() {
|
||||
return this.inertia.Ixx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
|
||||
MassCalculation other = (MassCalculation) obj;
|
||||
return ((this.centerOfMass.equals(other.centerOfMass))
|
||||
&& (this.config.equals( other.config))
|
||||
&& (this.simulationTime == other.simulationTime)
|
||||
&& (this.type == other.type) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (int) (this.centerOfMass.hashCode());
|
||||
}
|
||||
|
||||
public MassCalculation( final Type _type, final FlightConfiguration _config, final double _time, final RocketComponent _root, final Transformation _transform) {
|
||||
type = _type;
|
||||
config = _config;
|
||||
simulationTime = _time;
|
||||
root = _root;
|
||||
transform = _transform;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
public void setCM( final Coordinate newCM ) {
|
||||
this.centerOfMass = newCM;
|
||||
}
|
||||
|
||||
public void reset(){
|
||||
centerOfMass = Coordinate.ZERO;
|
||||
inertia = RigidBody.EMPTY;
|
||||
bodies.clear();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return this.bodies.size();
|
||||
}
|
||||
|
||||
public String toCMDebug(){
|
||||
return String.format("cm= %.6fg@[%.6f,%.6f,%.6f]", centerOfMass.weight, centerOfMass.x, centerOfMass.y, centerOfMass.z);
|
||||
}
|
||||
|
||||
// public String toMOIDebug(){
|
||||
// return String.format("I_cm=[ %.8f, %.8f, %.8f ]", inertia.getIxx(), inertia.getIyy(), inertia.getIzz() ));
|
||||
// }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.toCMDebug();
|
||||
}
|
||||
|
||||
|
||||
// =========== Instance Member Variables ========================
|
||||
|
||||
private static final double MIN_MASS = MathUtil.EPSILON;
|
||||
|
||||
// === package-private ===
|
||||
final FlightConfiguration config;
|
||||
final double simulationTime;
|
||||
final RocketComponent root;
|
||||
final Transformation transform;
|
||||
final Type type;
|
||||
|
||||
// center-of-mass only.
|
||||
Coordinate centerOfMass = Coordinate.ZERO;
|
||||
|
||||
// center-of-mass AND moment-of-inertia data.
|
||||
RigidBody inertia = RigidBody.EMPTY;
|
||||
|
||||
// center-of-mass AND moment-of-inertia data.
|
||||
final ArrayList<RigidBody> bodies = new ArrayList<RigidBody>();
|
||||
|
||||
String prefix = "";
|
||||
|
||||
// =========== Private Instance Functions ========================
|
||||
|
||||
private MassCalculation calculateMountData(){
|
||||
if( ! config.isComponentActive(this.root)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
final MotorMount mount = (MotorMount)root;
|
||||
MotorConfiguration motorConfig = mount.getMotorConfig( config.getId() );
|
||||
final Motor motor = motorConfig.getMotor();
|
||||
if( motorConfig.isEmpty() ){
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
final double mountXPosition = root.getOffset().x;
|
||||
|
||||
final int instanceCount = root.getInstanceCount();
|
||||
|
||||
final double motorXPosition = motorConfig.getX(); // location of motor from mount
|
||||
final Coordinate[] offsets = root.getInstanceOffsets();
|
||||
|
||||
double eachMass;
|
||||
double eachCMx; // CoM from beginning of motor
|
||||
|
||||
if ( this.type.includesMotorCasing && this.type.includesPropellant ){
|
||||
eachMass = motor.getTotalMass( simulationTime );
|
||||
eachCMx = motor.getCMx( simulationTime);
|
||||
}else if( this.type.includesMotorCasing ) {
|
||||
eachMass = motor.getTotalMass( Motor.PSEUDO_TIME_BURNOUT );
|
||||
eachCMx = motor.getCMx( Motor.PSEUDO_TIME_BURNOUT );
|
||||
} else {
|
||||
final double eachMotorMass = motor.getTotalMass( simulationTime );
|
||||
final double eachMotorCMx = motor.getCMx( simulationTime); // CoM from beginning of motor
|
||||
final double eachCasingMass = motor.getBurnoutMass();
|
||||
final double eachCasingCMx = motor.getBurnoutCGx();
|
||||
|
||||
eachMass = eachMotorMass - eachCasingMass;
|
||||
eachCMx = (eachMotorCMx*eachMotorMass - eachCasingCMx*eachCasingMass)/eachMass;
|
||||
}
|
||||
|
||||
// System.err.println(String.format("%-40s|Motor: %s.... Mass @%f = %.6f", prefix, motorConfig.toDescription(), simulationTime, eachMass ));
|
||||
|
||||
|
||||
// coordinates in rocket frame; Ir, It about CoM.
|
||||
final Coordinate clusterLocalCM = new Coordinate( mountXPosition + motorXPosition + eachCMx, 0, 0, eachMass*instanceCount);
|
||||
|
||||
double clusterBaseIr = motorConfig.getUnitRotationalInertia()*instanceCount*eachMass;
|
||||
|
||||
double clusterIt = motorConfig.getUnitLongitudinalInertia()*instanceCount*eachMass;
|
||||
|
||||
// if more than 1 moter => motors are not an the centerline => adjust via parallel-axis theorem
|
||||
double clusterIr = clusterBaseIr;
|
||||
if( 1 < instanceCount ){
|
||||
for( Coordinate coord : offsets ){
|
||||
double distance = Math.hypot( coord.y, coord.z);
|
||||
clusterIr += eachMass*Math.pow( distance, 2);
|
||||
}
|
||||
}
|
||||
|
||||
final Coordinate clusterCM = transform.transform( clusterLocalCM );
|
||||
addMass( clusterCM );
|
||||
|
||||
RigidBody clusterMOI = new RigidBody( clusterCM, clusterIr, clusterIt, clusterIt );
|
||||
addInertia( clusterMOI );
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mass and inertia data for this component and all subcomponents.
|
||||
* The inertia is returned relative to the CG, and the CG is in the coordinates
|
||||
* of the specified component, not global coordinates.
|
||||
*
|
||||
* @param calculation - i/o parameter to specifies the calculation parameters, and
|
||||
* the instance returned with the calculation's tree data.
|
||||
*
|
||||
*/
|
||||
/* package-scope */ MassCalculation calculateAssembly(){
|
||||
final RocketComponent component = this.root;
|
||||
final Transformation parentTransform = this.transform;
|
||||
|
||||
final int instanceCount = component.getInstanceCount();
|
||||
Coordinate[] instanceLocations = component.getInstanceLocations();
|
||||
|
||||
// // vvv DEBUG
|
||||
// if( this.config.isComponentActive(component) ){
|
||||
// System.err.println(String.format( "%s[%s]....", prefix, component.getName()));
|
||||
// }
|
||||
|
||||
if( this.type.includesStructure && this.config.isComponentActive(component) ){
|
||||
Coordinate compCM = component.getCG();
|
||||
double compIx = component.getRotationalUnitInertia() * compCM.weight;
|
||||
double compIt = component.getLongitudinalUnitInertia() * compCM.weight;
|
||||
|
||||
if (!component.getOverrideSubcomponents()) {
|
||||
if (component.isMassOverridden())
|
||||
compCM = compCM.setWeight(MathUtil.max(component.getOverrideMass(), MIN_MASS));
|
||||
if (component.isCGOverridden())
|
||||
compCM = compCM.setXYZ(component.getOverrideCG());
|
||||
}
|
||||
|
||||
// mass data for *this component only* in the rocket-frame
|
||||
compCM = parentTransform.transform( compCM.add(component.getOffset()) );
|
||||
this.addMass( compCM );
|
||||
|
||||
RigidBody componentInertia = new RigidBody( compCM, compIx, compIt, compIt );
|
||||
this.addInertia( componentInertia );
|
||||
|
||||
// if( 0 < compCM.weight ) { // vvv DEBUG
|
||||
// System.err.println(String.format( "%s....componentData: %s", prefix, compCM.toPreciseString() ));
|
||||
// }
|
||||
}
|
||||
|
||||
if( component.isMotorMount() && ( this.type.includesMotorCasing || this.type.includesPropellant )) {
|
||||
MassCalculation propellant = this.copy(component, parentTransform);
|
||||
|
||||
propellant.calculateMountData();
|
||||
|
||||
this.merge( propellant );
|
||||
|
||||
// if( 0 < propellant.getMass() ) {
|
||||
// System.err.println(String.format( "%s........++ propellantData: %s", prefix, propellant.toCMDebug()));
|
||||
// }
|
||||
}
|
||||
|
||||
// iterate over the aggregated instances for the whole tree.
|
||||
MassCalculation children = this.copy(component, parentTransform );
|
||||
for( int instanceNumber = 0; instanceNumber < instanceCount; ++instanceNumber) {
|
||||
Coordinate currentLocation = instanceLocations[instanceNumber];
|
||||
Transformation currentTransform = parentTransform.applyTransformation( Transformation.getTranslationTransform( currentLocation ));
|
||||
|
||||
for (RocketComponent child : component.getChildren()) {
|
||||
// child data, relative to rocket reference frame
|
||||
MassCalculation eachChild = copy( child, currentTransform);
|
||||
|
||||
eachChild.prefix = prefix + "....";
|
||||
eachChild.calculateAssembly();
|
||||
|
||||
// accumulate children's data
|
||||
children.merge( eachChild );
|
||||
}
|
||||
}
|
||||
|
||||
if( 0 < children.getMass() ) {
|
||||
this.merge( children );
|
||||
//System.err.println(String.format( "%s....assembly mass (incl/children): %s", prefix, this.toCMDebug()));
|
||||
}
|
||||
|
||||
// Override total data
|
||||
if (component.getOverrideSubcomponents()) {
|
||||
if (component.isMassOverridden()) {
|
||||
double newMass = MathUtil.max(component.getOverrideMass(), MIN_MASS);
|
||||
Coordinate newCM = this.getCM().setWeight( newMass );
|
||||
this.setCM( newCM );
|
||||
}
|
||||
if (component.isCGOverridden()) {
|
||||
Coordinate newCM = this.getCM().setX( component.getOverrideCGX() );
|
||||
this.setCM( newCM );
|
||||
}
|
||||
}
|
||||
|
||||
// vvv DEBUG
|
||||
//if( this.config.isComponentActive(component) && 0 < this.getMass() ) {
|
||||
//System.err.println(String.format( "%s....<< return assemblyData: %s (tree @%s)", prefix, this.toCMDebug(), component.getName() ));
|
||||
// System.err.println(String.format( "%s Ixx = %.8f Iyy = %.8f", prefix, getIxx(), getIyy() ));
|
||||
//}
|
||||
// ^^^ DEBUG
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* MOI Calculation needs to be a two-step process:
|
||||
* (1) calculate overall Center-of-Mass (CM) first (down inline with data-gathering)
|
||||
* (2) Move MOIs to CM via parallel axis theorem (this method)
|
||||
*
|
||||
* @param Center-of-Mass where the MOI should be calculated around.
|
||||
* @param inertias a list of component MassData instances to condense into a single MOI
|
||||
*
|
||||
* @return freshly calculated Moment-of-Inertia matrix
|
||||
*/
|
||||
/* package-scope */ RigidBody calculateMomentOfInertia() {
|
||||
double Ir=0, It=0;
|
||||
for( final RigidBody eachLocal : this.bodies ){
|
||||
final RigidBody eachGlobal = eachLocal.rebase( this.centerOfMass );
|
||||
|
||||
Ir += eachGlobal.Ixx;
|
||||
It += eachGlobal.Iyy;
|
||||
}
|
||||
|
||||
return new RigidBody( centerOfMass, Ir, It, It );
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,241 +1,111 @@
|
||||
package net.sf.openrocket.masscalc;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.sf.openrocket.masscalc.MassCalculation.Type;
|
||||
import net.sf.openrocket.motor.Motor;
|
||||
import net.sf.openrocket.motor.MotorConfiguration;
|
||||
import net.sf.openrocket.rocketcomponent.AxialStage;
|
||||
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
|
||||
import net.sf.openrocket.rocketcomponent.FlightConfigurationId;
|
||||
import net.sf.openrocket.rocketcomponent.Instanceable;
|
||||
import net.sf.openrocket.rocketcomponent.MotorMount;
|
||||
import net.sf.openrocket.rocketcomponent.ParallelStage;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.simulation.MotorClusterState;
|
||||
import net.sf.openrocket.simulation.SimulationStatus;
|
||||
import net.sf.openrocket.util.BugException;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.util.MathUtil;
|
||||
import net.sf.openrocket.util.Monitorable;
|
||||
import net.sf.openrocket.util.Transformation;
|
||||
|
||||
public class MassCalculator implements Monitorable {
|
||||
|
||||
//private static final Logger log = LoggerFactory.getLogger(MassCalculator.class);
|
||||
public static final double MIN_MASS = 0.001 * MathUtil.EPSILON;
|
||||
|
||||
private int rocketMassModID = -1;
|
||||
private int rocketTreeModID = -1;
|
||||
private FlightConfigurationId configId = FlightConfigurationId.ERROR_FCID;
|
||||
public static final double MIN_MASS = MathUtil.EPSILON;
|
||||
|
||||
/*
|
||||
* Cached data. All CG data is in absolute coordinates. All moments of inertia
|
||||
* are relative to their respective CG.
|
||||
*/
|
||||
private HashMap< Integer, MassData> stageMassCache = new HashMap<Integer, MassData >();
|
||||
private MassData rocketSpentMassCache;
|
||||
private MassData propellantMassCache;
|
||||
// private HashMap< Integer, MassData> stageMassCache = new HashMap<Integer, MassData >();
|
||||
// private MassData rocketSpentMassCache;
|
||||
// private MassData propellantMassCache;
|
||||
|
||||
private int modId=0;
|
||||
|
||||
////////////////// Constructors ///////////////////
|
||||
public MassCalculator() {
|
||||
}
|
||||
|
||||
////////////////// Mass property calculations ///////////////////
|
||||
////////////////// Public Accessors ///////////////////
|
||||
|
||||
|
||||
public MassData getRocketSpentMassData( final FlightConfiguration config ){
|
||||
revalidateCache( config);
|
||||
return this.rocketSpentMassCache;
|
||||
/**
|
||||
* Calculates mass data of the rocket's burnout mass
|
||||
* - includes structure
|
||||
* - includes motors
|
||||
* - for Black Powder & Composite motors, this generally *excludes* propellant
|
||||
*
|
||||
* @param configuration the rocket configuration to calculate for
|
||||
* @return the MassData struct of the motors at burnout
|
||||
*/
|
||||
public static RigidBody calculateStructure( final FlightConfiguration config) {
|
||||
return calculate( MassCalculation.Type.STRUCTURE, config, Motor.PSEUDO_TIME_EMPTY );
|
||||
}
|
||||
|
||||
|
||||
public MassData getRocketLaunchMassData( final FlightConfiguration config ){
|
||||
revalidateCache( config);
|
||||
return rocketSpentMassCache.add( propellantMassCache );
|
||||
/**
|
||||
* Calculates mass data of the rocket's burnout mass
|
||||
* - includes structure
|
||||
* - includes motors
|
||||
* - for Black Powder & Composite motors, this generally *excludes* propellant
|
||||
*
|
||||
* @param configuration the rocket configuration to calculate for
|
||||
* @return the MassData struct of the motors at burnout
|
||||
*/
|
||||
public static RigidBody calculateBurnout( final FlightConfiguration config) {
|
||||
return calculate( MassCalculation.Type.BURNOUT, config, Motor.PSEUDO_TIME_BURNOUT );
|
||||
}
|
||||
|
||||
public static RigidBody calculateMotor( final FlightConfiguration config) {
|
||||
return calculate( MassCalculation.Type.MOTOR, config, Motor.PSEUDO_TIME_LAUNCH );
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the burnout mass properties all motors, given a configuration
|
||||
*
|
||||
* Will calculate data for: MassCalcType.BURNOUT_MASS
|
||||
*
|
||||
* @param config the rocket configuration
|
||||
* @return the MassData struct of the motors at burnout
|
||||
*/
|
||||
public static RigidBody calculateLaunch( final FlightConfiguration config ){
|
||||
return calculate( MassCalculation.Type.LAUNCH, config, Motor.PSEUDO_TIME_LAUNCH );
|
||||
}
|
||||
|
||||
/** calculates the massdata for all propellant in the rocket given the simulation status.
|
||||
*
|
||||
* @param status CurrentSimulation status to calculate data with
|
||||
* @return combined mass data for all propellant
|
||||
*/
|
||||
public MassData getPropellantMassData( final SimulationStatus status ){
|
||||
revalidateCache( status );
|
||||
|
||||
return propellantMassCache;
|
||||
public static RigidBody calculateMotor( final SimulationStatus status ){
|
||||
return calculate( MassCalculation.Type.MOTOR, status );
|
||||
}
|
||||
|
||||
////////////////// Mass property Wrappers ///////////////////
|
||||
// all mass calculation calls should probably call through one of these two wrappers.
|
||||
|
||||
/** calculates the massdata @ launch for all propellant in the rocket
|
||||
*
|
||||
* @param status CurrentSimulation status to calculate data with
|
||||
* @return combined mass data for all propellant
|
||||
*/
|
||||
protected MassData calculatePropellantMassData( final FlightConfiguration config ){
|
||||
MassData allPropellantData = MassData.ZERO_DATA;
|
||||
// convenience wrapper -- use this to implicitly create a plain MassCalculation object with common parameters
|
||||
public static RigidBody calculate( final MassCalculation.Type _type, final SimulationStatus status ){
|
||||
final FlightConfiguration config = status.getConfiguration();
|
||||
final double time = status.getSimulationTime();
|
||||
MassCalculation calculation= new MassCalculation( _type, config, time, config.getRocket(), Transformation.IDENTITY);
|
||||
|
||||
Collection<MotorConfiguration> activeMotorList = config.getActiveMotors();
|
||||
for (MotorConfiguration mtrConfig : activeMotorList ) {
|
||||
MassData curMotorConfigData = calculateClusterPropellantData( mtrConfig, Motor.PSEUDO_TIME_LAUNCH );
|
||||
|
||||
allPropellantData = allPropellantData.add( curMotorConfigData );
|
||||
calculation.calculateAssembly();
|
||||
RigidBody result = calculation.calculateMomentOfInertia();
|
||||
return result;
|
||||
}
|
||||
|
||||
return allPropellantData;
|
||||
// convenience wrapper -- use this to implicitly create a plain MassCalculation object with common parameters
|
||||
public static RigidBody calculate( final MassCalculation.Type _type, final FlightConfiguration _config, double _time ){
|
||||
MassCalculation calculation = new MassCalculation( _type, _config, _time, _config.getRocket(), Transformation.IDENTITY);
|
||||
calculation.calculateAssembly();
|
||||
return calculation.calculateMomentOfInertia();
|
||||
}
|
||||
|
||||
/** calculates the massdata @ launch for all propellant in the rocket
|
||||
*
|
||||
* @param status CurrentSimulation status to calculate data with
|
||||
* @return combined mass data for all propellant
|
||||
*/
|
||||
protected MassData calculatePropellantMassData( final SimulationStatus status ){
|
||||
MassData allPropellantData = MassData.ZERO_DATA;
|
||||
|
||||
Collection<MotorClusterState> motorStates = status.getActiveMotors();
|
||||
for (MotorClusterState state: motorStates) {
|
||||
final double motorTime = state.getMotorTime( status.getSimulationTime() );
|
||||
|
||||
MassData clusterPropData = calculateClusterPropellantData( state.getConfig(), motorTime );
|
||||
|
||||
allPropellantData = allPropellantData.add( clusterPropData);
|
||||
}
|
||||
|
||||
return allPropellantData;
|
||||
}
|
||||
|
||||
// helper method to calculate the propellant mass data for a given motor cluster( i.e. MotorConfiguration)
|
||||
private MassData calculateClusterPropellantData( final MotorConfiguration mtrConfig, final double motorTime ){
|
||||
final Motor mtr = mtrConfig.getMotor();
|
||||
final MotorMount mnt = mtrConfig.getMount();
|
||||
final int instanceCount = mnt.getInstanceCount();
|
||||
|
||||
// location of mount, w/in entire rocket
|
||||
final Coordinate[] locations = mnt.getLocations();
|
||||
final double motorXPosition = mtrConfig.getX(); // location of motor from mount
|
||||
|
||||
final double propMassEach = mtr.getPropellantMass( motorTime );
|
||||
final double propCMxEach = mtr.getCMx( motorTime); // CoM from beginning of motor
|
||||
|
||||
// coordinates in rocket frame; Ir, It about CoM.
|
||||
final Coordinate curClusterCM = new Coordinate( locations[0].x + motorXPosition + propCMxEach, 0, 0, propMassEach*instanceCount);
|
||||
|
||||
final double unitRotationalInertiaEach = mtrConfig.getUnitRotationalInertia();
|
||||
final double unitLongitudinalInertiaEach = mtrConfig.getUnitLongitudinalInertia();
|
||||
double Ir=unitRotationalInertiaEach*instanceCount*propMassEach;
|
||||
double It=unitLongitudinalInertiaEach*instanceCount*propMassEach;
|
||||
|
||||
if( 1 < instanceCount ){
|
||||
// if not on rocket centerline, then add an offset factor, according to the parallel axis theorem:
|
||||
for( Coordinate coord : locations ){
|
||||
double distance = Math.hypot( coord.y, coord.z);
|
||||
Ir += propMassEach*Math.pow( distance, 2);
|
||||
}
|
||||
}
|
||||
|
||||
return new MassData( curClusterCM, Ir, It);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates mass data of the rocket burnout mass
|
||||
*
|
||||
* I.O.W., this mass data is invariant during thrust (see also: calculatePropellantMassData(...) )
|
||||
*
|
||||
* @param configuration a given rocket configuration
|
||||
* @return the CG of the configuration
|
||||
*/
|
||||
protected MassData calculateBurnoutMassData( final FlightConfiguration config) {
|
||||
MassData exceptMotorsMassData = calculateStageData( config);
|
||||
|
||||
MassData motorMassData = calculateMotorBurnoutMassData( config);
|
||||
|
||||
return exceptMotorsMassData.add( motorMassData );
|
||||
}
|
||||
|
||||
private MassData calculateStageData( final FlightConfiguration config ){
|
||||
MassData componentData = MassData.ZERO_DATA;
|
||||
|
||||
// Stages
|
||||
for (AxialStage stage : config.getActiveStages()) {
|
||||
int stageNumber = stage.getStageNumber();
|
||||
|
||||
MassData stageData = this.calculateAssemblyMassData( stage );
|
||||
|
||||
stageMassCache.put(stageNumber, stageData);
|
||||
|
||||
componentData = componentData.add(stageData);
|
||||
}
|
||||
|
||||
return componentData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compute the burnout mass properties all motors, given a configuration
|
||||
*
|
||||
* Will calculate data for:MassCalcType.BURNOUT_MASS
|
||||
*
|
||||
* @param configuration the rocket configuration
|
||||
* @return the MassData struct of the motors at burnout
|
||||
*/
|
||||
private MassData calculateMotorBurnoutMassData(FlightConfiguration config) {
|
||||
|
||||
MassData allMotorData = MassData.ZERO_DATA;
|
||||
|
||||
//int motorIndex = 0;
|
||||
for (MotorConfiguration mtrConfig : config.getActiveMotors() ) {
|
||||
Motor mtr = (Motor) mtrConfig.getMotor();
|
||||
MotorMount mount = mtrConfig.getMount();
|
||||
|
||||
// use 'mount.getLocations()' because:
|
||||
// 1) includes ALL clustering sources!
|
||||
// 2) location of mount, w/in entire rocket
|
||||
// 3) Note: mount.getInstanceCount() ONLY indicates instancing of the mount's cluster, not parent components (such as stages)
|
||||
Coordinate[] locations = mount.getLocations();
|
||||
int instanceCount = locations.length;
|
||||
double motorXPosition = mtrConfig.getX(); // location of motor from mount
|
||||
|
||||
final double burnoutMassEach = mtr.getBurnoutMass();
|
||||
final double burnoutCMx = mtr.getBurnoutCGx(); // CoM from beginning of motor
|
||||
|
||||
final Coordinate clusterCM = new Coordinate( locations[0].x + motorXPosition + burnoutCMx, 0, 0, burnoutMassEach*instanceCount);
|
||||
|
||||
final double unitRotationalInertia = mtrConfig.getUnitRotationalInertia();
|
||||
final double unitLongitudinalInertia = mtrConfig.getUnitLongitudinalInertia();
|
||||
|
||||
double Ir=(unitRotationalInertia*burnoutMassEach)*instanceCount;
|
||||
double It=(unitLongitudinalInertia*burnoutMassEach)*instanceCount;
|
||||
if( 1 < instanceCount ){
|
||||
for( Coordinate coord : locations ){
|
||||
double distance_squared = ((coord.y*coord.y) + (coord.z*coord.z));
|
||||
double instance_correction = burnoutMassEach*distance_squared;
|
||||
|
||||
Ir += instance_correction;
|
||||
}
|
||||
}
|
||||
|
||||
MassData configData = new MassData( clusterCM, Ir, It);
|
||||
allMotorData = allMotorData.add( configData );
|
||||
|
||||
}
|
||||
|
||||
return allMotorData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the total mass of the motors
|
||||
*
|
||||
* @param motors the motor configuration
|
||||
* @param configuration the current motor instance configuration
|
||||
* @return the total mass of all motors
|
||||
*/
|
||||
public double getPropellantMass(SimulationStatus status ){
|
||||
return (getPropellantMassData( status )).getCM().weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute an analysis of the per-component CG's of the provided configuration.
|
||||
@ -244,12 +114,22 @@ public class MassCalculator implements Monitorable {
|
||||
* The CG of the entire configuration with motors is stored in the entry with the corresponding
|
||||
* Rocket as the key.
|
||||
*
|
||||
* Deprecated:
|
||||
* This function is fundamentally broken, because it asks for a calculation which ignores instancing.
|
||||
* This function will work with simple rockets, but will be misleading or downright wrong for others.
|
||||
*
|
||||
* This is a problem with using a single-typed map:
|
||||
* [1] multiple instances of components are not allowed, and must be merged.
|
||||
* [2] propellant / motor data does not have a corresponding RocketComponent.
|
||||
* ( or mount-data collides with motor-data )
|
||||
*
|
||||
* @param configuration the rocket configuration
|
||||
* @param type the state of the motors (none, launch mass, burnout mass)
|
||||
* @return a map from each rocket component to its corresponding CG.
|
||||
*/
|
||||
@Deprecated
|
||||
public Map<RocketComponent, Coordinate> getCGAnalysis(FlightConfiguration configuration) {
|
||||
revalidateCache(configuration);
|
||||
// revalidateCache(configuration);
|
||||
|
||||
Map<RocketComponent, Coordinate> map = new HashMap<RocketComponent, Coordinate>();
|
||||
|
||||
@ -271,170 +151,14 @@ public class MassCalculator implements Monitorable {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the mass and inertia data for this component and all subcomponents.
|
||||
* The inertia is returned relative to the CG, and the CG is in the coordinates
|
||||
* of the specified component, not global coordinates.
|
||||
*/
|
||||
private MassData calculateAssemblyMassData(RocketComponent component) {
|
||||
return calculateAssemblyMassData(component, "....");
|
||||
}
|
||||
|
||||
private MassData calculateAssemblyMassData(RocketComponent component, String indent) {
|
||||
|
||||
Coordinate compCM = component.getComponentCG();
|
||||
double compIx = component.getRotationalUnitInertia() * compCM.weight;
|
||||
double compIt = component.getLongitudinalUnitInertia() * compCM.weight;
|
||||
if( 0 > compCM.weight ){
|
||||
throw new BugException(" computed a negative rotational inertia value.");
|
||||
}
|
||||
if( 0 > compIx ){
|
||||
throw new BugException(" computed a negative rotational inertia value.");
|
||||
}
|
||||
if( 0 > compIt ){
|
||||
throw new BugException(" computed a negative longitudinal inertia value.");
|
||||
}
|
||||
|
||||
if (!component.getOverrideSubcomponents()) {
|
||||
if (component.isMassOverridden())
|
||||
compCM = compCM.setWeight(MathUtil.max(component.getOverrideMass(), MIN_MASS));
|
||||
if (component.isCGOverridden())
|
||||
compCM = compCM.setXYZ(component.getOverrideCG());
|
||||
}
|
||||
|
||||
// default if not instanced (instance count == 1)
|
||||
MassData assemblyData = new MassData( compCM, compIx, compIt);
|
||||
|
||||
MassData childrenData = MassData.ZERO_DATA;
|
||||
// Combine data for subcomponents
|
||||
for (RocketComponent child : component.getChildren()) {
|
||||
if( child instanceof ParallelStage ){
|
||||
// this stage will be tallied separately... skip.
|
||||
continue;
|
||||
}
|
||||
|
||||
// child data, relative to parent's reference frame
|
||||
MassData childData = calculateAssemblyMassData(child, indent+"....");
|
||||
|
||||
childrenData = childrenData.add( childData );
|
||||
}
|
||||
////////////////// Mass property calculations ///////////////////
|
||||
|
||||
|
||||
|
||||
// if instanced, adjust children's data too.
|
||||
if( 1 == component.getInstanceCount() ){
|
||||
assemblyData = assemblyData.add( childrenData );
|
||||
}else if( 0 < component.getChildCount()){
|
||||
final double curIxx = childrenData.getIxx(); // MOI about x-axis
|
||||
final double curIyy = childrenData.getIyy(); // MOI about y axis
|
||||
final double curIzz = childrenData.getIzz(); // MOI about z axis
|
||||
|
||||
Coordinate templateCM = assemblyData.cm;
|
||||
MassData instAccumData = new MassData(); // accumulator for instance MassData
|
||||
Coordinate[] instanceLocations = ((Instanceable) component).getInstanceOffsets();
|
||||
for( Coordinate curOffset : instanceLocations ){
|
||||
Coordinate instanceCM = curOffset.add(templateCM);
|
||||
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);
|
||||
}
|
||||
|
||||
assemblyData = assemblyData.add( instAccumData );
|
||||
}
|
||||
|
||||
|
||||
// move to parent's reference point
|
||||
assemblyData = assemblyData.move( component.getOffset() );
|
||||
if( component instanceof ParallelStage ){
|
||||
// hacky correction for the fact Booster Stages aren't direct subchildren to the rocket
|
||||
assemblyData = assemblyData.move( component.getParent().getOffset() );
|
||||
}
|
||||
|
||||
// Override total data
|
||||
if (component.getOverrideSubcomponents()) {
|
||||
if (component.isMassOverridden()) {
|
||||
double oldMass = assemblyData.getMass();
|
||||
double newMass = MathUtil.max(component.getOverrideMass(), MIN_MASS);
|
||||
Coordinate newCM = assemblyData.getCM().setWeight(newMass);
|
||||
|
||||
double newIxx = assemblyData.getIxx() * newMass / oldMass;
|
||||
double newIyy = assemblyData.getIyy() * newMass / oldMass;
|
||||
double newIzz = assemblyData.getIzz() * newMass / oldMass;
|
||||
|
||||
assemblyData = new MassData( newCM, newIxx, newIyy, newIzz );
|
||||
}
|
||||
if (component.isCGOverridden()) {
|
||||
double oldx = assemblyData.getCM().x;
|
||||
double newx = component.getOverrideCGX();
|
||||
Coordinate delta = new Coordinate(newx-oldx, 0, 0);
|
||||
|
||||
assemblyData = assemblyData.move( delta );
|
||||
}
|
||||
}
|
||||
|
||||
return assemblyData;
|
||||
}
|
||||
|
||||
|
||||
public void revalidateCache( final SimulationStatus status ){
|
||||
rocketSpentMassCache = calculateBurnoutMassData( status.getConfiguration() );
|
||||
|
||||
propellantMassCache = calculatePropellantMassData( status);
|
||||
}
|
||||
|
||||
public void revalidateCache( final FlightConfiguration config ){
|
||||
checkCache( config);
|
||||
if( null == rocketSpentMassCache ){
|
||||
rocketSpentMassCache = calculateBurnoutMassData(config);
|
||||
}
|
||||
if( null == propellantMassCache ){
|
||||
propellantMassCache = calculatePropellantMassData( config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the current cache consistency. This method must be called by all
|
||||
* methods that may use any cached data before any other operations are
|
||||
* performed. If the rocket has changed since the previous call to
|
||||
* <code>checkCache()</code>, then {@link #voidMassCache()} is called.
|
||||
* <p>
|
||||
* This method performs the checking based on the rocket's modification IDs,
|
||||
* so that these method may be called from listeners of the rocket itself.
|
||||
*
|
||||
* @param configuration the configuration of the current call
|
||||
*/
|
||||
protected final boolean checkCache(FlightConfiguration configuration) {
|
||||
if (rocketMassModID != configuration.getRocket().getMassModID() ||
|
||||
rocketTreeModID != configuration.getRocket().getTreeModID() ||
|
||||
configId != configuration.getId()) {
|
||||
rocketMassModID = configuration.getRocket().getMassModID();
|
||||
rocketTreeModID = configuration.getRocket().getTreeModID();
|
||||
configId = configuration.getId();
|
||||
voidMassCache();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Void cached mass data. This method is called whenever a change occurs in
|
||||
* the rocket structure that affects the mass of the rocket and when a new
|
||||
* Rocket is used. This method must be overridden to void any cached data
|
||||
* necessary. The method must call <code>super.voidMassCache()</code> during
|
||||
* its execution.
|
||||
*/
|
||||
protected void voidMassCache() {
|
||||
this.stageMassCache.clear();
|
||||
this.rocketSpentMassCache=null;
|
||||
this.propellantMassCache=null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getModID() {
|
||||
return 0;
|
||||
return this.modId;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,269 +0,0 @@
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
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
|
||||
+ ", rotationalInertia=" + getIxx() + ", longitudinalInertia=" + getIyy() + "]";
|
||||
}
|
||||
|
||||
}
|
141
core/src/net/sf/openrocket/masscalc/RigidBody.java
Normal file
141
core/src/net/sf/openrocket/masscalc/RigidBody.java
Normal file
@ -0,0 +1,141 @@
|
||||
package net.sf.openrocket.masscalc;
|
||||
|
||||
import static net.sf.openrocket.util.MathUtil.pow2;
|
||||
|
||||
import net.sf.openrocket.util.BugException;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.util.MathUtil;
|
||||
|
||||
// implements a simplified, diagonal MOI
|
||||
public class RigidBody {
|
||||
public final Coordinate cm;
|
||||
public final double Ixx;
|
||||
public final double Iyy;
|
||||
public final double Izz;
|
||||
|
||||
public static final RigidBody EMPTY = new RigidBody( Coordinate.ZERO, 0., 0., 0.);
|
||||
|
||||
public RigidBody( Coordinate _cm, double I_axial, double I_long ){
|
||||
this( _cm, I_axial, I_long, I_long );
|
||||
}
|
||||
|
||||
public RigidBody( Coordinate _cm, 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.cm=_cm;
|
||||
this.Ixx = Ixx;
|
||||
this.Iyy = Iyy;
|
||||
this.Izz = Izz;
|
||||
}
|
||||
|
||||
public RigidBody add( RigidBody that){
|
||||
final Coordinate newCM = this.cm.average( that.cm);
|
||||
|
||||
RigidBody movedThis = this.rebase( newCM );
|
||||
RigidBody movedThat = that.rebase( newCM );
|
||||
|
||||
final double newIxx = movedThis.Ixx + movedThat.Ixx;
|
||||
final double newIyy = movedThis.Iyy + movedThat.Iyy;
|
||||
final double newIzz = movedThis.Izz + movedThat.Izz;
|
||||
|
||||
return new RigidBody( newCM, newIxx, newIyy, newIzz );
|
||||
}
|
||||
|
||||
public Coordinate getCenterOfMass() { return cm; }
|
||||
public Coordinate getCM() { return cm; }
|
||||
public double getIyy(){ return Iyy; }
|
||||
public double getIxx(){ return Ixx; }
|
||||
public double getIzz(){ return Izz; }
|
||||
public double getLongitudinalInertia() { return Iyy; }
|
||||
public double getMass() { return this.cm.weight; }
|
||||
public double getRotationalInertia() { return Ixx; }
|
||||
|
||||
|
||||
public boolean isEmpty() {
|
||||
if( RigidBody.EMPTY == this) {
|
||||
return true;
|
||||
}
|
||||
return RigidBody.EMPTY.equals( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (int) (Double.doubleToLongBits(this.Ixx) ^ Double.doubleToLongBits(this.Iyy) ^ Double.doubleToLongBits(this.Ixx) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (!(obj instanceof RigidBody))
|
||||
return false;
|
||||
|
||||
RigidBody other = (RigidBody) obj;
|
||||
return (MathUtil.equals(this.Ixx, other.Ixx) && MathUtil.equals(this.Iyy, other.Iyy) &&
|
||||
MathUtil.equals(this.Izz, other.Izz)) ;
|
||||
}
|
||||
|
||||
public RigidBody rebase( final Coordinate newLocation ){
|
||||
final Coordinate delta = this.cm.sub( newLocation ).setWeight(0.);
|
||||
double x2 = pow2(delta.x);
|
||||
double y2 = pow2(delta.y);
|
||||
double z2 = pow2(delta.z);
|
||||
|
||||
// final double radialDistanceSquared = (y2 + z2);
|
||||
// final double axialDistanceSquared = x2;
|
||||
//
|
||||
// System.err.println(String.format(" - CM_new = %.8f @[ %.8f, %.8f, %.8f ]", eachGlobal.cm.weight, eachGlobal.cm.x, eachGlobal.cm.y, eachGlobal.cm.z ));
|
||||
// System.err.println(String.format(" - each: base: { %.12f %.12f }", eachLocal.xx, eachLocal.xx ));
|
||||
// System.err.println(String.format(" - each: carrected: { %.12f %.12f }", eachGlobal.xx, eachGlobal.yy ));
|
||||
|
||||
// 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.Ixx + cm.weight*(y2 + z2);
|
||||
double newIyy = this.Iyy + cm.weight*(x2 + z2);
|
||||
double newIzz = this.Izz + cm.weight*(x2 + y2);
|
||||
|
||||
// MOI about the reference point
|
||||
return new RigidBody( newLocation, newIxx, newIyy, newIzz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toCMString()+" // "+toMOIString();
|
||||
}
|
||||
|
||||
public String toCMString() {
|
||||
return String.format("CoM: %.8fg @[%.8f,%.8f,%.8f]", cm.weight, cm.x, cm.y, cm.z);
|
||||
}
|
||||
|
||||
public String toMOIString() {
|
||||
return String.format("MOI: [ %.8f, %.8f, %.8f]", Ixx, Iyy, Izz );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public RigidBody translateInertia( final Coordinate delta ){
|
||||
final Coordinate newLocation = this.cm.add( delta );
|
||||
return rebase( newLocation );
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -62,8 +62,6 @@ public class StabilityDomain implements SimulationDomain {
|
||||
* Caching would in any case be inefficient since the rocket changes all the time.
|
||||
*/
|
||||
AerodynamicCalculator aerodynamicCalculator = new BarrowmanCalculator();
|
||||
MassCalculator massCalculator = new MassCalculator();
|
||||
|
||||
|
||||
FlightConfiguration configuration = simulation.getRocket().getSelectedConfiguration();
|
||||
FlightConditions conditions = new FlightConditions(configuration);
|
||||
@ -73,7 +71,7 @@ public class StabilityDomain implements SimulationDomain {
|
||||
|
||||
// TODO: HIGH: This re-calculates the worst theta value every time
|
||||
cp = aerodynamicCalculator.getWorstCP(configuration, conditions, null);
|
||||
cg = massCalculator.getRocketLaunchMassData(configuration).getCM();
|
||||
cg = MassCalculator.calculateLaunch( configuration).getCM();
|
||||
|
||||
if (cp.weight > 0.000001)
|
||||
cpx = cp.x;
|
||||
|
@ -55,8 +55,6 @@ public class StabilityParameter implements OptimizableParameter {
|
||||
* Caching would in any case be inefficient since the rocket changes all the time.
|
||||
*/
|
||||
AerodynamicCalculator aerodynamicCalculator = new BarrowmanCalculator();
|
||||
MassCalculator massCalculator = new MassCalculator();
|
||||
|
||||
|
||||
FlightConfiguration configuration = simulation.getRocket().getSelectedConfiguration();
|
||||
FlightConditions conditions = new FlightConditions(configuration);
|
||||
@ -66,7 +64,7 @@ public class StabilityParameter implements OptimizableParameter {
|
||||
|
||||
cp = aerodynamicCalculator.getWorstCP(configuration, conditions, null);
|
||||
// worst case CM is also
|
||||
cg = massCalculator.getRocketLaunchMassData(configuration).getCM();
|
||||
cg = MassCalculator.calculateLaunch(configuration).getCM();
|
||||
|
||||
if (cp.weight > 0.000001)
|
||||
cpx = cp.x;
|
||||
|
@ -4,10 +4,6 @@ import net.sf.openrocket.util.ChangeSource;
|
||||
|
||||
public interface Clusterable extends ChangeSource, Instanceable {
|
||||
|
||||
@Deprecated
|
||||
// redundant with Instanceable#getInstanceCount()
|
||||
public int getClusterCount();
|
||||
|
||||
public ClusterConfiguration getClusterConfiguration();
|
||||
|
||||
public void setClusterConfiguration(ClusterConfiguration cluster);
|
||||
|
@ -167,6 +167,10 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
|
||||
return stages.get(stageNumber).active;
|
||||
}
|
||||
|
||||
|
||||
// this method is deprecated because it ignores instancing of parent components (e.g. Strapons or pods )
|
||||
// if you're calling this method, you're probably not getting the numbers you expect.
|
||||
@Deprecated
|
||||
public Collection<RocketComponent> getActiveComponents() {
|
||||
Queue<RocketComponent> toProcess = new ArrayDeque<RocketComponent>(this.getActiveStages());
|
||||
ArrayList<RocketComponent> toReturn = new ArrayList<>();
|
||||
|
@ -134,15 +134,6 @@ public class InnerTube extends ThicknessRingComponent implements Clusterable, Ra
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of tubes in the cluster.
|
||||
* @return Number of tubes in the current cluster.
|
||||
*/
|
||||
@Override
|
||||
public int getClusterCount() {
|
||||
return cluster.getClusterCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInstanceCount() {
|
||||
return cluster.getClusterCount();
|
||||
@ -212,9 +203,8 @@ public class InnerTube extends ThicknessRingComponent implements Clusterable, Ra
|
||||
return 2 * getOuterRadius() * clusterScale;
|
||||
}
|
||||
|
||||
|
||||
public List<Coordinate> getClusterPoints() {
|
||||
List<Coordinate> list = new ArrayList<Coordinate>(getClusterCount());
|
||||
List<Coordinate> list = new ArrayList<Coordinate>(getInstanceCount());
|
||||
List<Double> points = cluster.getPoints(clusterRotation - getRadialDirection());
|
||||
double separation = getClusterSeparation();
|
||||
for (int i = 0; i < points.size() / 2; i++) {
|
||||
@ -226,7 +216,7 @@ public class InnerTube extends ThicknessRingComponent implements Clusterable, Ra
|
||||
@Override
|
||||
public Coordinate[] getInstanceOffsets(){
|
||||
|
||||
if ( 1 == getClusterCount())
|
||||
if ( 1 == getInstanceCount())
|
||||
return new Coordinate[] { Coordinate.ZERO };
|
||||
|
||||
List<Coordinate> points = getClusterPoints();
|
||||
|
@ -173,18 +173,18 @@ public abstract class RingComponent extends StructuralComponent implements Coaxi
|
||||
@Override
|
||||
public Coordinate getComponentCG() {
|
||||
Coordinate cg = Coordinate.ZERO;
|
||||
int instanceCount = getInstanceCount();
|
||||
double instanceMass = ringMass(getOuterRadius(), getInnerRadius(), getLength(),
|
||||
getMaterial().getDensity());
|
||||
final int instanceCount = getInstanceCount();
|
||||
final double instanceMass = ringMass(getOuterRadius(), getInnerRadius(), getLength(), getMaterial().getDensity());
|
||||
|
||||
if (1 == instanceCount ) {
|
||||
cg = new Coordinate( length/2, 0, 0, instanceMass );
|
||||
}else{
|
||||
Coordinate offsets[] = getInstanceOffsets();
|
||||
for( Coordinate c : offsets) {
|
||||
c = c.setWeight( instanceMass );
|
||||
cg = cg.average(c);
|
||||
}
|
||||
cg.add( length/2, 0, 0);
|
||||
cg = cg.add( length/2, 0, 0);
|
||||
}
|
||||
return cg;
|
||||
}
|
||||
|
@ -1220,7 +1220,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
// == improperly initialized components OR the root Rocket instance
|
||||
return getInstanceOffsets();
|
||||
} else {
|
||||
Coordinate[] parentPositions = this.parent.getLocations();
|
||||
Coordinate[] parentPositions = this.parent.getComponentLocations();
|
||||
int parentCount = parentPositions.length;
|
||||
|
||||
// override <instance>.getInstanceOffsets() in the subclass you want to fix.
|
||||
@ -1264,7 +1264,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
checkState();
|
||||
final String lockText = "toAbsolute";
|
||||
mutex.lock(lockText);
|
||||
Coordinate[] thesePositions = this.getLocations();
|
||||
Coordinate[] thesePositions = this.getComponentLocations();
|
||||
|
||||
final int instanceCount = thesePositions.length;
|
||||
|
||||
@ -2236,6 +2236,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
buffer.append("\n ====== ====== ====== ====== ====== ====== ====== ====== ====== ====== ====== ======\n");
|
||||
buffer.append(" [Name] [Length] [Rel Pos] [Abs Pos] \n");
|
||||
this.toDebugTreeHelper(buffer, "");
|
||||
buffer.append("\n ====== ====== ====== ====== ====== ====== ====== ====== ====== ====== ====== ======\n");
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@ -2251,27 +2252,13 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
|
||||
|
||||
public void toDebugTreeNode(final StringBuilder buffer, final String indent) {
|
||||
final String prefix = String.format("%s%s", indent, this.getName());
|
||||
|
||||
// 1) instanced vs non-instanced
|
||||
if( 1 == getInstanceCount() ){
|
||||
// un-instanced RocketComponents (usual case)
|
||||
buffer.append(String.format("%-40s| %5.3f; %24s; %24s; ", prefix, this.getLength(), this.getOffset(), this.getLocations()[0]));
|
||||
buffer.append(String.format("(offset: %4.1f via: %s )\n", this.getAxialOffset(), this.relativePosition.name()));
|
||||
}else if( this instanceof Instanceable ){
|
||||
// instanced components -- think motor clusters or booster stage clusters
|
||||
final String patternName = ((Instanceable)this).getPatternName();
|
||||
buffer.append(String.format("%-40s (cluster: %s )", prefix, patternName));
|
||||
buffer.append(String.format("(offset: %4.1f via: %s )\n", this.getAxialOffset(), this.relativePosition.name()));
|
||||
|
||||
for (int instanceNumber = 0; instanceNumber < this.getInstanceCount(); instanceNumber++) {
|
||||
final String instancePrefix = String.format("%s [%2d/%2d]", indent, instanceNumber+1, getInstanceCount());
|
||||
buffer.append(String.format("%-40s| %5.3f; %24s; %24s;\n", instancePrefix, getLength(), getOffset(), getLocations()[0]));
|
||||
}
|
||||
}else{
|
||||
throw new IllegalStateException("This is a developer error! If you implement an instanced class, please subclass the Clusterable interface.");
|
||||
String prefix = String.format("%s%s", indent, this.getName());
|
||||
if( 0 < this.getInstanceCount() ){
|
||||
prefix = prefix + String.format(" (x%d)", this.getInstanceCount() );
|
||||
}
|
||||
|
||||
buffer.append(String.format("%-40s| %6.4f; %24s; %24s; \n", prefix, getLength(), getOffset().toPreciseString(), getComponentLocations()[0].toPreciseString() ));
|
||||
|
||||
// 2) if this is an ACTING motor mount:
|
||||
if(( this instanceof MotorMount ) &&( ((MotorMount)this).isMotorMount())){
|
||||
// because InnerTube and BodyTube don't really share a common ancestor besides RocketComponent
|
||||
@ -2284,10 +2271,10 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
public void toDebugMountNode(final StringBuilder buffer, final String indent) {
|
||||
MotorMount mnt = (MotorMount)this;
|
||||
|
||||
Coordinate[] relCoords = this.getInstanceOffsets();
|
||||
Coordinate[] absCoords = this.getLocations();
|
||||
// Coordinate[] relCoords = this.getInstanceOffsets();
|
||||
Coordinate[] absCoords = this.getComponentLocations();
|
||||
FlightConfigurationId curId = this.getRocket().getSelectedConfiguration().getFlightConfigurationID();
|
||||
final int intanceCount = this.getInstanceCount();
|
||||
// final int instanceCount = this.getInstanceCount();
|
||||
MotorConfiguration curInstance = mnt.getMotorConfig( curId);
|
||||
if( curInstance.isEmpty() ){
|
||||
// print just the tube locations
|
||||
@ -2296,6 +2283,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
// curInstance has a motor ...
|
||||
Motor curMotor = curInstance.getMotor();
|
||||
final double motorOffset = this.getLength() - curMotor.getLength();
|
||||
final String instancePrefix = String.format("%s [ */%2d]", indent, getInstanceCount());
|
||||
|
||||
buffer.append(String.format("%-40sThrust: %f N; \n",
|
||||
indent+" Mounted: "+curMotor.getDesignation(), curMotor.getMaxThrustEstimate() ));
|
||||
@ -2303,9 +2291,13 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
Coordinate motorRelativePosition = new Coordinate(motorOffset, 0, 0);
|
||||
Coordinate tubeAbs = absCoords[0];
|
||||
Coordinate motorAbsolutePosition = new Coordinate(tubeAbs.x+motorOffset,tubeAbs.y,tubeAbs.z);
|
||||
buffer.append(String.format("%-40s| %5.3f; %24s; %24s;\n", indent, curMotor.getLength(), motorRelativePosition, motorAbsolutePosition));
|
||||
buffer.append(String.format("%-40s| %5.3f; %24s; %24s;\n", instancePrefix, curMotor.getLength(), motorRelativePosition, motorAbsolutePosition));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMotorMount() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package net.sf.openrocket.simulation;
|
||||
import java.util.Collection;
|
||||
|
||||
import net.sf.openrocket.masscalc.MassCalculator;
|
||||
import net.sf.openrocket.masscalc.MassData;
|
||||
import net.sf.openrocket.masscalc.RigidBody;
|
||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||
import net.sf.openrocket.simulation.listeners.SimulationListenerHelper;
|
||||
@ -110,53 +110,47 @@ public abstract class AbstractSimulationStepper implements SimulationStepper {
|
||||
* @return the mass data to use
|
||||
* @throws SimulationException if a listener throws SimulationException
|
||||
*/
|
||||
protected MassData calculateDryMassData(SimulationStatus status) throws SimulationException {
|
||||
MassData mass;
|
||||
protected RigidBody calculateStructureMass(SimulationStatus status) throws SimulationException {
|
||||
RigidBody structureMass;
|
||||
|
||||
// Call pre-listener
|
||||
mass = SimulationListenerHelper.firePreMassCalculation(status);
|
||||
if (mass != null) {
|
||||
return mass;
|
||||
structureMass = SimulationListenerHelper.firePreMassCalculation(status);
|
||||
if (structureMass != null) {
|
||||
return structureMass;
|
||||
}
|
||||
|
||||
MassCalculator calc = status.getSimulationConditions().getMassCalculator();
|
||||
|
||||
mass = calc.getRocketSpentMassData( status.getConfiguration() );
|
||||
structureMass = MassCalculator.calculateStructure( status.getConfiguration() );
|
||||
|
||||
// Call post-listener
|
||||
mass = SimulationListenerHelper.firePostMassCalculation(status, mass);
|
||||
structureMass = SimulationListenerHelper.firePostMassCalculation(status, structureMass);
|
||||
|
||||
checkNaN(mass.getCG());
|
||||
checkNaN(mass.getLongitudinalInertia());
|
||||
checkNaN(mass.getRotationalInertia());
|
||||
checkNaN(structureMass.getCenterOfMass());
|
||||
checkNaN(structureMass.getLongitudinalInertia());
|
||||
checkNaN(structureMass.getRotationalInertia());
|
||||
|
||||
return mass;
|
||||
return structureMass;
|
||||
}
|
||||
|
||||
protected MassData calculatePropellantMassData(SimulationStatus status) throws SimulationException {
|
||||
MassData mass;
|
||||
protected RigidBody calculateMotorMass(SimulationStatus status) throws SimulationException {
|
||||
RigidBody motorMass;
|
||||
|
||||
// Call pre-listener
|
||||
mass = SimulationListenerHelper.firePreMassCalculation(status);
|
||||
if (mass != null) {
|
||||
return mass;
|
||||
motorMass = SimulationListenerHelper.firePreMassCalculation(status);
|
||||
if (motorMass != null) {
|
||||
return motorMass;
|
||||
}
|
||||
|
||||
MassCalculator calc = status.getSimulationConditions().getMassCalculator();
|
||||
|
||||
|
||||
|
||||
mass = calc.getPropellantMassData( status );
|
||||
motorMass = MassCalculator.calculateMotor( status );
|
||||
|
||||
|
||||
// Call post-listener
|
||||
mass = SimulationListenerHelper.firePostMassCalculation(status, mass);
|
||||
motorMass = SimulationListenerHelper.firePostMassCalculation(status, motorMass);
|
||||
|
||||
checkNaN(mass.getCG());
|
||||
checkNaN(mass.getLongitudinalInertia());
|
||||
checkNaN(mass.getRotationalInertia());
|
||||
checkNaN(motorMass.getCenterOfMass());
|
||||
checkNaN(motorMass.getLongitudinalInertia());
|
||||
checkNaN(motorMass.getRotationalInertia());
|
||||
|
||||
return mass;
|
||||
return motorMass;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.sf.openrocket.simulation;
|
||||
|
||||
import net.sf.openrocket.masscalc.MassData;
|
||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
|
||||
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||
@ -39,8 +38,7 @@ public class BasicLandingStepper extends AbstractSimulationStepper {
|
||||
// Compute drag force
|
||||
double dynP = (0.5 * atmosphere.getDensity() * airSpeed.length2());
|
||||
double dragForce = totalCD * dynP * refArea;
|
||||
MassData massData = calculateDryMassData(status);
|
||||
double mass = massData.getCG().weight;
|
||||
double mass = calculateStructureMass(status).getMass();
|
||||
|
||||
|
||||
// Compute drag acceleration
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.sf.openrocket.simulation;
|
||||
|
||||
import net.sf.openrocket.masscalc.MassData;
|
||||
|
||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
@ -35,9 +35,9 @@ public class BasicTumbleStepper extends AbstractSimulationStepper {
|
||||
// Compute drag force
|
||||
double dynP = (0.5 * atmosphere.getDensity() * airSpeed.length2());
|
||||
double dragForce = tumbleDrag * dynP;
|
||||
// n.b. this is consntant, and could be calculated once at the beginning of this simulation branch...
|
||||
MassData massData = calculateDryMassData(status);
|
||||
double mass = massData.getCG().weight;
|
||||
|
||||
// n.b. this is constant, and could be calculated once at the beginning of this simulation branch...
|
||||
double mass = calculateStructureMass(status).getMass();
|
||||
|
||||
|
||||
// Compute drag acceleration
|
||||
|
@ -10,7 +10,7 @@ import net.sf.openrocket.aerodynamics.AerodynamicForces;
|
||||
import net.sf.openrocket.aerodynamics.FlightConditions;
|
||||
import net.sf.openrocket.aerodynamics.WarningSet;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.masscalc.MassData;
|
||||
import net.sf.openrocket.masscalc.RigidBody;
|
||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||
import net.sf.openrocket.simulation.exception.SimulationCalculationException;
|
||||
import net.sf.openrocket.simulation.exception.SimulationException;
|
||||
@ -327,10 +327,10 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
|
||||
calculateForces(status, store);
|
||||
|
||||
// Calculate mass data
|
||||
MassData dryMassData = calculateDryMassData(status);
|
||||
RigidBody structureMassData = calculateStructureMass(status);
|
||||
|
||||
store.propellantMassData = calculatePropellantMassData(status);
|
||||
store.rocketMassData = dryMassData.add( store.propellantMassData );
|
||||
store.motorMass = calculateMotorMass(status);
|
||||
store.rocketMass = structureMassData.add( store.motorMass );
|
||||
|
||||
// Calculate the forces from the aerodynamic coefficients
|
||||
|
||||
@ -347,9 +347,9 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
|
||||
|
||||
double forceZ = store.thrustForce - store.dragForce;
|
||||
|
||||
store.linearAcceleration = new Coordinate(-fN / store.rocketMassData.getCG().weight,
|
||||
-fSide / store.rocketMassData.getCG().weight,
|
||||
forceZ / store.rocketMassData.getCG().weight);
|
||||
store.linearAcceleration = new Coordinate(-fN / store.rocketMass.getMass(),
|
||||
-fSide / store.rocketMass.getMass(),
|
||||
forceZ / store.rocketMass.getMass());
|
||||
|
||||
store.linearAcceleration = store.thetaRotation.rotateZ(store.linearAcceleration);
|
||||
|
||||
@ -378,8 +378,8 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
|
||||
} else {
|
||||
|
||||
// Shift moments to CG
|
||||
double Cm = store.forces.getCm() - store.forces.getCN() * store.rocketMassData.getCG().x / refLength;
|
||||
double Cyaw = store.forces.getCyaw() - store.forces.getCside() * store.rocketMassData.getCG().x / refLength;
|
||||
double Cm = store.forces.getCm() - store.forces.getCN() * store.rocketMass.getCM().x / refLength;
|
||||
double Cyaw = store.forces.getCyaw() - store.forces.getCside() * store.rocketMass.getCM().x / refLength;
|
||||
|
||||
// Compute moments
|
||||
double momX = -Cyaw * dynP * refArea * refLength;
|
||||
@ -387,9 +387,9 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
|
||||
double momZ = store.forces.getCroll() * dynP * refArea * refLength;
|
||||
|
||||
// Compute acceleration in rocket coordinates
|
||||
store.angularAcceleration = new Coordinate(momX / store.rocketMassData.getLongitudinalInertia(),
|
||||
momY / store.rocketMassData.getLongitudinalInertia(),
|
||||
momZ / store.rocketMassData.getRotationalInertia());
|
||||
store.angularAcceleration = new Coordinate(momX / store.rocketMass.getLongitudinalInertia(),
|
||||
momY / store.rocketMass.getLongitudinalInertia(),
|
||||
momZ / store.rocketMass.getRotationalInertia());
|
||||
|
||||
store.rollAcceleration = store.angularAcceleration.z;
|
||||
// TODO: LOW: This should be hypot, but does it matter?
|
||||
@ -597,30 +597,30 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
|
||||
data.setValue(FlightDataType.TYPE_MACH_NUMBER, store.flightConditions.getMach());
|
||||
}
|
||||
|
||||
if (store.rocketMassData != null) {
|
||||
data.setValue(FlightDataType.TYPE_CG_LOCATION, store.rocketMassData.getCG().x);
|
||||
if (store.rocketMass != null) {
|
||||
data.setValue(FlightDataType.TYPE_CG_LOCATION, store.rocketMass.getCM().x);
|
||||
}
|
||||
if (status.isLaunchRodCleared()) {
|
||||
// Don't include CP and stability with huge launch AOA
|
||||
if (store.forces != null) {
|
||||
data.setValue(FlightDataType.TYPE_CP_LOCATION, store.forces.getCP().x);
|
||||
}
|
||||
if (store.forces != null && store.flightConditions != null && store.rocketMassData != null) {
|
||||
if (store.forces != null && store.flightConditions != null && store.rocketMass != null) {
|
||||
data.setValue(FlightDataType.TYPE_STABILITY,
|
||||
(store.forces.getCP().x - store.rocketMassData.getCG().x) / store.flightConditions.getRefLength());
|
||||
(store.forces.getCP().x - store.rocketMass.getCM().x) / store.flightConditions.getRefLength());
|
||||
}
|
||||
}
|
||||
|
||||
if( null != store.propellantMassData ){
|
||||
data.setValue(FlightDataType.TYPE_PROPELLANT_MASS, store.propellantMassData.getCG().weight);
|
||||
if( null != store.motorMass ){
|
||||
data.setValue(FlightDataType.TYPE_PROPELLANT_MASS, store.motorMass.getMass());
|
||||
//data.setValue(FlightDataType.TYPE_PROPELLANT_LONGITUDINAL_INERTIA, store.propellantMassData.getLongitudinalInertia());
|
||||
//data.setValue(FlightDataType.TYPE_PROPELLANT_ROTATIONAL_INERTIA, store.propellantMassData.getRotationalInertia());
|
||||
}
|
||||
if (store.rocketMassData != null) {
|
||||
if (store.rocketMass != null) {
|
||||
// N.B.: These refer to total mass
|
||||
data.setValue(FlightDataType.TYPE_MASS, store.rocketMassData.getCG().weight);
|
||||
data.setValue(FlightDataType.TYPE_LONGITUDINAL_INERTIA, store.rocketMassData.getLongitudinalInertia());
|
||||
data.setValue(FlightDataType.TYPE_ROTATIONAL_INERTIA, store.rocketMassData.getRotationalInertia());
|
||||
data.setValue(FlightDataType.TYPE_MASS, store.rocketMass.getMass());
|
||||
data.setValue(FlightDataType.TYPE_LONGITUDINAL_INERTIA, store.rocketMass.getLongitudinalInertia());
|
||||
data.setValue(FlightDataType.TYPE_ROTATIONAL_INERTIA, store.rocketMass.getRotationalInertia());
|
||||
}
|
||||
|
||||
data.setValue(FlightDataType.TYPE_THRUST_FORCE, store.thrustForce);
|
||||
@ -628,11 +628,11 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
|
||||
data.setValue(FlightDataType.TYPE_GRAVITY, store.gravity);
|
||||
|
||||
if (status.isLaunchRodCleared() && store.forces != null) {
|
||||
if (store.rocketMassData != null && store.flightConditions != null) {
|
||||
if (store.rocketMass != null && store.flightConditions != null) {
|
||||
data.setValue(FlightDataType.TYPE_PITCH_MOMENT_COEFF,
|
||||
store.forces.getCm() - store.forces.getCN() * store.rocketMassData.getCG().x / store.flightConditions.getRefLength());
|
||||
store.forces.getCm() - store.forces.getCN() * store.rocketMass.getCM().x / store.flightConditions.getRefLength());
|
||||
data.setValue(FlightDataType.TYPE_YAW_MOMENT_COEFF,
|
||||
store.forces.getCyaw() - store.forces.getCside() * store.rocketMassData.getCG().x / store.flightConditions.getRefLength());
|
||||
store.forces.getCyaw() - store.forces.getCside() * store.rocketMass.getCM().x / store.flightConditions.getRefLength());
|
||||
}
|
||||
data.setValue(FlightDataType.TYPE_NORMAL_FORCE_COEFF, store.forces.getCN());
|
||||
data.setValue(FlightDataType.TYPE_SIDE_FORCE_COEFF, store.forces.getCside());
|
||||
@ -715,9 +715,9 @@ public class RK4SimulationStepper extends AbstractSimulationStepper {
|
||||
|
||||
public double longitudinalAcceleration = Double.NaN;
|
||||
|
||||
public MassData rocketMassData;
|
||||
public RigidBody rocketMass;
|
||||
|
||||
public MassData propellantMassData;
|
||||
public RigidBody motorMass;
|
||||
|
||||
public Coordinate coriolisAcceleration;
|
||||
|
||||
|
@ -7,6 +7,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
|
||||
import net.sf.openrocket.aerodynamics.FlightConditions;
|
||||
import net.sf.openrocket.aerodynamics.WarningSet;
|
||||
import net.sf.openrocket.motor.MotorConfiguration;
|
||||
@ -101,7 +102,6 @@ public class SimulationStatus implements Monitorable {
|
||||
// Initialize to roll angle with least stability w.r.t. the wind
|
||||
Quaternion o;
|
||||
FlightConditions cond = new FlightConditions(this.configuration);
|
||||
this.simulationConditions.getAerodynamicCalculator().getWorstCP(this.configuration, cond, null);
|
||||
double angle = -cond.getTheta() - (Math.PI / 2.0 - this.simulationConditions.getLaunchRodDirection());
|
||||
o = Quaternion.rotation(new Coordinate(0, 0, angle));
|
||||
|
||||
|
@ -11,7 +11,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.sf.openrocket.aerodynamics.AerodynamicForces;
|
||||
import net.sf.openrocket.aerodynamics.FlightConditions;
|
||||
import net.sf.openrocket.masscalc.MassData;
|
||||
import net.sf.openrocket.masscalc.RigidBody;
|
||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||
import net.sf.openrocket.motor.MotorConfigurationId;
|
||||
import net.sf.openrocket.rocketcomponent.MotorMount;
|
||||
@ -144,8 +144,8 @@ public class ScriptingSimulationListener implements SimulationListener, Simulati
|
||||
}
|
||||
|
||||
@Override
|
||||
public MassData preMassCalculation(SimulationStatus status) throws SimulationException {
|
||||
return invoke(MassData.class, null, "preMassCalculation", status);
|
||||
public RigidBody preMassCalculation(SimulationStatus status) throws SimulationException {
|
||||
return invoke(RigidBody.class, null, "preMassCalculation", status);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -184,8 +184,8 @@ public class ScriptingSimulationListener implements SimulationListener, Simulati
|
||||
}
|
||||
|
||||
@Override
|
||||
public MassData postMassCalculation(SimulationStatus status, MassData massData) throws SimulationException {
|
||||
return invoke(MassData.class, null, "postMassCalculation", status, massData);
|
||||
public RigidBody postMassCalculation(SimulationStatus status, RigidBody RigidBody) throws SimulationException {
|
||||
return invoke(RigidBody.class, null, "postMassCalculation", status, RigidBody);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2,7 +2,7 @@ package net.sf.openrocket.simulation.listeners;
|
||||
|
||||
import net.sf.openrocket.aerodynamics.AerodynamicForces;
|
||||
import net.sf.openrocket.aerodynamics.FlightConditions;
|
||||
import net.sf.openrocket.masscalc.MassData;
|
||||
import net.sf.openrocket.masscalc.RigidBody;
|
||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||
import net.sf.openrocket.motor.MotorConfigurationId;
|
||||
import net.sf.openrocket.rocketcomponent.MotorMount;
|
||||
@ -111,7 +111,7 @@ public class AbstractSimulationListener implements SimulationListener, Simulatio
|
||||
}
|
||||
|
||||
@Override
|
||||
public MassData preMassCalculation(SimulationStatus status) throws SimulationException {
|
||||
public RigidBody preMassCalculation(SimulationStatus status) throws SimulationException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -151,7 +151,7 @@ public class AbstractSimulationListener implements SimulationListener, Simulatio
|
||||
}
|
||||
|
||||
@Override
|
||||
public MassData postMassCalculation(SimulationStatus status, MassData massData) throws SimulationException {
|
||||
public RigidBody postMassCalculation(SimulationStatus status, RigidBody RigidBody) throws SimulationException {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ package net.sf.openrocket.simulation.listeners;
|
||||
|
||||
import net.sf.openrocket.aerodynamics.AerodynamicForces;
|
||||
import net.sf.openrocket.aerodynamics.FlightConditions;
|
||||
import net.sf.openrocket.masscalc.MassData;
|
||||
import net.sf.openrocket.masscalc.RigidBody;
|
||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||
import net.sf.openrocket.simulation.AccelerationData;
|
||||
import net.sf.openrocket.simulation.SimulationStatus;
|
||||
@ -55,9 +55,9 @@ public interface SimulationComputationListener extends SimulationListener {
|
||||
public AerodynamicForces postAerodynamicCalculation(SimulationStatus status, AerodynamicForces forces)
|
||||
throws SimulationException;
|
||||
|
||||
public MassData preMassCalculation(SimulationStatus status) throws SimulationException;
|
||||
public RigidBody preMassCalculation(SimulationStatus status) throws SimulationException;
|
||||
|
||||
public MassData postMassCalculation(SimulationStatus status, MassData massData) throws SimulationException;
|
||||
public RigidBody postMassCalculation(SimulationStatus status, RigidBody massData) throws SimulationException;
|
||||
|
||||
|
||||
public double preSimpleThrustCalculation(SimulationStatus status) throws SimulationException;
|
||||
|
@ -7,7 +7,7 @@ import org.slf4j.LoggerFactory;
|
||||
import net.sf.openrocket.aerodynamics.AerodynamicForces;
|
||||
import net.sf.openrocket.aerodynamics.FlightConditions;
|
||||
import net.sf.openrocket.aerodynamics.Warning;
|
||||
import net.sf.openrocket.masscalc.MassData;
|
||||
import net.sf.openrocket.masscalc.RigidBody;
|
||||
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
|
||||
import net.sf.openrocket.motor.MotorConfigurationId;
|
||||
import net.sf.openrocket.rocketcomponent.MotorMount;
|
||||
@ -502,9 +502,9 @@ public class SimulationListenerHelper {
|
||||
*
|
||||
* @return <code>null</code> normally, or overriding mass data.
|
||||
*/
|
||||
public static MassData firePreMassCalculation(SimulationStatus status)
|
||||
public static RigidBody firePreMassCalculation(SimulationStatus status)
|
||||
throws SimulationException {
|
||||
MassData mass;
|
||||
RigidBody mass;
|
||||
int modID = status.getModID();
|
||||
|
||||
for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
|
||||
@ -528,8 +528,8 @@ public class SimulationListenerHelper {
|
||||
*
|
||||
* @return the resultant mass data
|
||||
*/
|
||||
public static MassData firePostMassCalculation(SimulationStatus status, MassData mass) throws SimulationException {
|
||||
MassData m;
|
||||
public static RigidBody firePostMassCalculation(SimulationStatus status, RigidBody mass) throws SimulationException {
|
||||
RigidBody m;
|
||||
int modID = status.getModID();
|
||||
|
||||
for (SimulationListener l : status.getSimulationConditions().getSimulationListenerList()) {
|
||||
|
@ -336,6 +336,11 @@ public final class Coordinate implements Cloneable, Serializable {
|
||||
return String.format("(%.3f,%.3f,%.3f)", x, y, z);
|
||||
}
|
||||
|
||||
// high-precision output, for use with verifying calculations
|
||||
public String toPreciseString() {
|
||||
return String.format("cm= %.8fg @[%.8f,%.8f,%.8f]", weight, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Coordinate clone() {
|
||||
return new Coordinate(this.x, this.y, this.z, this.weight);
|
||||
|
@ -212,36 +212,6 @@ public class TestRockets {
|
||||
.build();
|
||||
}
|
||||
|
||||
//
|
||||
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);
|
||||
|
||||
rocket.enableEvents();
|
||||
return rocket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new test rocket based on the value 'key'. The rocket utilizes most of the
|
||||
* properties and features available. The same key always returns the same rocket,
|
||||
@ -877,7 +847,11 @@ public class TestRockets {
|
||||
return rocket;
|
||||
}
|
||||
|
||||
public final static String FALCON_9_FCID_1="test_config #1: [ M1350, G77]";
|
||||
public final static String FALCON_9H_FCID_1="test_config #1: [ M1350, G77]";
|
||||
public final static int FALCON_9H_PAYLOAD_STAGE_NUMBER=0;
|
||||
public final static int FALCON_9H_CORE_STAGE_NUMBER=1;
|
||||
public final static int FALCON_9H_BOOSTER_STAGE_NUMBER=2;
|
||||
|
||||
|
||||
|
||||
// This function is used for unit, integration tests, DO NOT CHANGE (without updating tests).
|
||||
@ -885,7 +859,7 @@ public class TestRockets {
|
||||
Rocket rocket = new Rocket();
|
||||
rocket.setName("Falcon9H Scale Rocket");
|
||||
|
||||
FlightConfigurationId selFCID = rocket.createFlightConfiguration( new FlightConfigurationId( FALCON_9_FCID_1 ));
|
||||
FlightConfigurationId selFCID = rocket.createFlightConfiguration( new FlightConfigurationId( FALCON_9H_FCID_1 ));
|
||||
rocket.setSelectedConfiguration(selFCID);
|
||||
|
||||
// ====== Payload Stage ======
|
||||
|
@ -122,7 +122,6 @@ public class Transformation implements java.io.Serializable {
|
||||
return new Coordinate(x,y,z,orig.weight);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Transform an array of coordinates. The transformed coordinates are stored
|
||||
* in the same array, and the array is returned.
|
||||
@ -255,6 +254,13 @@ public class Transformation implements java.io.Serializable {
|
||||
}
|
||||
|
||||
|
||||
public boolean isIdentity() {
|
||||
if( this == Transformation.IDENTITY ) {
|
||||
return true;
|
||||
}
|
||||
return this.equals( Transformation.IDENTITY );
|
||||
}
|
||||
|
||||
|
||||
public void print(String... str) {
|
||||
for (String s: str) {
|
||||
@ -365,4 +371,8 @@ public class Transformation implements java.io.Serializable {
|
||||
return DoubleBuffer.wrap(data);
|
||||
}
|
||||
|
||||
public Coordinate getTranslationVector() {
|
||||
return this.translate;
|
||||
}
|
||||
|
||||
}
|
||||
|
74
core/test/net/sf/openrocket/masscalc/MassCacheTest.java
Normal file
74
core/test/net/sf/openrocket/masscalc/MassCacheTest.java
Normal file
@ -0,0 +1,74 @@
|
||||
package net.sf.openrocket.masscalc;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import net.sf.openrocket.rocketcomponent.Rocket;
|
||||
import net.sf.openrocket.util.TestRockets;
|
||||
import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
|
||||
|
||||
public class MassCacheTest extends BaseTestCase {
|
||||
|
||||
|
||||
@Test
|
||||
public void testCMCache() {
|
||||
Rocket rocket = TestRockets.makeFalcon9Heavy();
|
||||
rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName());
|
||||
|
||||
// ant throws in error if it can't find a test case in the *Test.java file.
|
||||
// .... soooo we have this waste of space. -DMW
|
||||
assertTrue( true );
|
||||
}
|
||||
//
|
||||
// FlightConfiguration config = rocket.getEmptyConfiguration();
|
||||
// MassCalculator mc = new MassCalculator();
|
||||
//
|
||||
// {
|
||||
// // validate payload stage
|
||||
// AxialStage payloadStage = (AxialStage) rocket.getChild(0);
|
||||
// int plNum = payloadStage.getStageNumber();
|
||||
// config.setOnlyStage( plNum );
|
||||
//
|
||||
// MassData calcMass = mc.calculateBurnoutMassData( config );
|
||||
//
|
||||
// double expMass = 0.116287;
|
||||
// double expCMx = 0.278070785749;
|
||||
// assertEquals("Upper Stage Mass is incorrect: ", expMass, calcMass.getCM().weight, EPSILON);
|
||||
// assertEquals("Upper Stage CM.x is incorrect: ", expCMx, calcMass.getCM().x, EPSILON);
|
||||
// assertEquals("Upper Stage CM.y is incorrect: ", 0.0f, calcMass.getCM().y, EPSILON);
|
||||
// assertEquals("Upper Stage CM.z is incorrect: ", 0.0f, calcMass.getCM().z, EPSILON);
|
||||
//
|
||||
// MassData rocketLaunchMass = mc.getRocketLaunchMassData( config);
|
||||
// assertEquals("Upper Stage Mass (cache) is incorrect: ", expMass, rocketLaunchMass.getCM().weight, EPSILON);
|
||||
// assertEquals("Upper Stage CM.x (cache) is incorrect: ", expCMx, rocketLaunchMass.getCM().x, EPSILON);
|
||||
//
|
||||
// MassData rocketSpentMass = mc.getRocketSpentMassData( config);
|
||||
// assertEquals("Upper Stage Mass (cache) is incorrect: ", expMass, rocketSpentMass.getCM().weight, EPSILON);
|
||||
// assertEquals("Upper Stage CM.x (cache) is incorrect: ", expCMx, rocketSpentMass.getCM().x, EPSILON);
|
||||
// }{
|
||||
// ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(1);
|
||||
// int boostNum = boosters.getStageNumber();
|
||||
// config.setOnlyStage( boostNum );
|
||||
//
|
||||
// MassData boosterMass = mc.calculateBurnoutMassData( config);
|
||||
//
|
||||
// double expMass = BOOSTER_SET_NO_MOTORS_MASS;
|
||||
// double expCMx = BOOSTER_SET_NO_MOTORS_CMX;
|
||||
// assertEquals("Heavy Booster Mass is incorrect: ", expMass, boosterMass.getCM().weight, EPSILON);
|
||||
// assertEquals("Heavy Booster CM.x is incorrect: ", expCMx, boosterMass.getCM().x, EPSILON);
|
||||
// assertEquals("Heavy Booster CM.y is incorrect: ", 0.0f, boosterMass.getCM().y, EPSILON);
|
||||
// assertEquals("Heavy Booster CM.z is incorrect: ", 0.0f, boosterMass.getCM().z, EPSILON);
|
||||
//
|
||||
// MassData rocketLaunchMass = mc.getRocketLaunchMassData( config);
|
||||
// assertEquals(" Booster Stage Mass (cache) is incorrect: ", expMass, rocketLaunchMass.getCM().weight, EPSILON);
|
||||
// assertEquals(" Booster Stage CM.x (cache) is incorrect: ", expCMx, rocketLaunchMass.getCM().x, EPSILON);
|
||||
//
|
||||
// MassData rocketSpentMass = mc.getRocketSpentMassData( config);
|
||||
// assertEquals(" Booster Stage Mass (cache) is incorrect: ", expMass, rocketSpentMass.getCM().weight, EPSILON);
|
||||
// assertEquals(" Booster Stage CM.x (cache) is incorrect: ", expCMx, rocketSpentMass.getCM().x, EPSILON);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,8 @@ import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.util.MathUtil;
|
||||
import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
|
||||
|
||||
public class MassDataTest extends BaseTestCase {
|
||||
|
||||
public class RigidBodyTest extends BaseTestCase {
|
||||
|
||||
// tolerance for compared double test results
|
||||
protected final double EPSILON = MathUtil.EPSILON;
|
||||
@ -22,18 +23,19 @@ public class MassDataTest extends BaseTestCase {
|
||||
Coordinate r1 = new Coordinate(0,-40, 0, m1);
|
||||
double I1ax=28.7;
|
||||
double I1t = I1ax/2;
|
||||
MassData body1 = new MassData(r1, I1ax, I1t);
|
||||
RigidBody body1 = new RigidBody(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);
|
||||
RigidBody body2 = new RigidBody(r2, I2ax, I2t);
|
||||
|
||||
// point 3 is defined as the CM of bodies 1 and 2 combined.
|
||||
MassData asbly3 = body1.add(body2);
|
||||
RigidBody 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.
|
||||
@ -68,16 +70,16 @@ public class MassDataTest extends BaseTestCase {
|
||||
Coordinate r1 = new Coordinate(0,-40, -10, m1);
|
||||
double I1xx=28.7;
|
||||
double I1t = I1xx/2;
|
||||
MassData body1 = new MassData(r1, I1xx, I1t);
|
||||
RigidBody body1 = new RigidBody(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);
|
||||
RigidBody body2 = new RigidBody(r2, I2xx, I2t);
|
||||
|
||||
// point 3 is defined as the CM of bodies 1 and 2 combined.
|
||||
MassData asbly3 = body1.add(body2);
|
||||
RigidBody asbly3 = body1.add(body2);
|
||||
|
||||
Coordinate cm3_expected = r1.average(r2);
|
||||
assertEquals(" Center of Mass calculated incorrectly: ", cm3_expected, asbly3.getCM() );
|
||||
@ -113,30 +115,30 @@ public class MassDataTest extends BaseTestCase {
|
||||
|
||||
|
||||
@Test
|
||||
public void testMassDataCompoundCalculations() {
|
||||
public void testRigidBodyCompoundCalculations() {
|
||||
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);
|
||||
RigidBody body1 = new RigidBody(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);
|
||||
RigidBody body2 = new RigidBody(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);
|
||||
RigidBody body5 = new RigidBody(r5, I5ax, I5t);
|
||||
|
||||
// point 3 is defined as the CM of bodies 1 and 2 combined.
|
||||
MassData asbly3 = body1.add(body2);
|
||||
RigidBody asbly3 = body1.add(body2);
|
||||
|
||||
// point 4 is defined as the CM of bodies 1, 2 and 5 combined.
|
||||
MassData asbly4_indirect = asbly3.add(body5);
|
||||
RigidBody asbly4_indirect = asbly3.add(body5);
|
||||
Coordinate cm4_expected = r1.average(r2).average(r5);
|
||||
|
||||
assertEquals(" Center of Mass calculated incorrectly: ", cm4_expected, new Coordinate( 0, 7.233644859813085, 0, m1+m2+m5 ) );
|
||||
@ -154,14 +156,11 @@ public class MassDataTest extends BaseTestCase {
|
||||
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);
|
||||
RigidBody asbly4_expected = new RigidBody( cm4_expected, I4xx, I4yy, I4zz);
|
||||
|
||||
assertEquals("x-axis MOI don't match: ", asbly4_indirect.getIxx(), asbly4_expected.getIxx(), EPSILON*10);
|
||||
|
||||
assertEquals("y-axis MOI don't match: ", asbly4_indirect.getIyy(), asbly4_expected.getIyy(), EPSILON*10);
|
||||
|
||||
assertEquals("z-axis MOI don't match: ", asbly4_indirect.getIzz(), asbly4_expected.getIzz(), EPSILON*10);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -13,7 +13,6 @@ import net.sf.openrocket.util.TestRockets;
|
||||
import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
|
||||
|
||||
public class RocketTest extends BaseTestCase {
|
||||
|
||||
final double EPSILON = MathUtil.EPSILON;
|
||||
|
||||
@Test
|
||||
@ -125,9 +124,6 @@ public class RocketTest extends BaseTestCase {
|
||||
ring.setInstanceCount(2);
|
||||
Coordinate actLocs[] = ring.getComponentLocations();
|
||||
{ // first instance
|
||||
// assertEquals(" position x fail: ", expLoc.x, actLoc.x, EPSILON);
|
||||
// assertEquals(" position y fail: ", expLoc.y, actLoc.y, EPSILON);
|
||||
// assertEquals(" position z fail: ", expLoc.z, actLoc.z, EPSILON);
|
||||
expLoc = new Coordinate(0.21, 0, 0);
|
||||
actLoc = actLocs[0];
|
||||
assertThat(ring.getName()+" not positioned correctly: ", actLoc, equalTo(expLoc));
|
||||
@ -179,4 +175,100 @@ public class RocketTest extends BaseTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testFalcon9HComponentLocations() {
|
||||
Rocket rkt = TestRockets.makeFalcon9Heavy();
|
||||
rkt.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName());
|
||||
|
||||
Coordinate offset;
|
||||
Coordinate loc;
|
||||
|
||||
// ====== Payload Stage ======
|
||||
// ====== ====== ====== ======
|
||||
{
|
||||
NoseCone nc = (NoseCone)rkt.getChild(0).getChild(0);
|
||||
offset = nc.getOffset();
|
||||
loc = nc.getComponentLocations()[0];
|
||||
assertEquals("P/L NoseCone offset is incorrect: ", 0.0, offset.x, EPSILON);
|
||||
assertEquals("P/L NoseCone location is incorrect: ", 0.0, loc.x, EPSILON);
|
||||
|
||||
BodyTube plbody = (BodyTube)rkt.getChild(0).getChild(1);
|
||||
offset = plbody.getOffset();
|
||||
loc = plbody.getComponentLocations()[0];
|
||||
assertEquals("P/L Body offset calculated incorrectly: ", 0.118, offset.x, EPSILON);
|
||||
assertEquals("P/L Body location calculated incorrectly: ", 0.118, loc.x, EPSILON);
|
||||
|
||||
|
||||
Transition tr= (Transition)rkt.getChild(0).getChild(2);
|
||||
offset = tr.getOffset();
|
||||
loc = tr.getComponentLocations()[0];
|
||||
assertEquals(tr.getName()+" offset is incorrect: ", 0.250, offset.x, EPSILON);
|
||||
assertEquals(tr.getName()+" location is incorrect: ", 0.250, loc.x, EPSILON);
|
||||
|
||||
BodyTube upperBody = (BodyTube)rkt.getChild(0).getChild(3);
|
||||
offset = upperBody.getOffset();
|
||||
loc = upperBody.getComponentLocations()[0];
|
||||
assertEquals(upperBody.getName()+" offset is incorrect: ", 0.264, offset.x, EPSILON);
|
||||
assertEquals(upperBody.getName()+" location is incorrect: ", 0.264, loc.x, EPSILON);
|
||||
{
|
||||
Parachute chute = (Parachute)rkt.getChild(0).getChild(3).getChild(0);
|
||||
offset = chute.getOffset();
|
||||
loc = chute.getComponentLocations()[0];
|
||||
assertEquals(chute.getName()+" offset is incorrect: ", 0.0775, offset.x, EPSILON);
|
||||
assertEquals(chute.getName()+" location is incorrect: ", 0.3415, loc.x, EPSILON);
|
||||
|
||||
ShockCord cord= (ShockCord)rkt.getChild(0).getChild(3).getChild(1);
|
||||
offset = cord.getOffset();
|
||||
loc = cord.getComponentLocations()[0];
|
||||
assertEquals(cord.getName()+" offset is incorrect: ", 0.155, offset.x, EPSILON);
|
||||
assertEquals(cord.getName()+" location is incorrect: ", 0.419, loc.x, EPSILON);
|
||||
}
|
||||
|
||||
BodyTube interstage = (BodyTube)rkt.getChild(0).getChild(4);
|
||||
offset = interstage.getOffset();
|
||||
loc = interstage.getComponentLocations()[0];
|
||||
assertEquals(interstage.getName()+" offset is incorrect: ", 0.444, offset.x, EPSILON);
|
||||
assertEquals(interstage.getName()+" location is incorrect: ", 0.444, loc.x, EPSILON);
|
||||
}
|
||||
|
||||
// ====== Core Stage ======
|
||||
// ====== ====== ======
|
||||
{
|
||||
BodyTube coreBody = (BodyTube)rkt.getChild(1).getChild(0);
|
||||
offset = coreBody.getOffset();
|
||||
loc = coreBody.getComponentLocations()[0];
|
||||
assertEquals(coreBody.getName()+" offset is incorrect: ", 0.0, offset.x, EPSILON);
|
||||
assertEquals(coreBody.getName()+" location is incorrect: ", 0.564, loc.x, EPSILON);
|
||||
|
||||
FinSet coreFins = (FinSet)rkt.getChild(1).getChild(0).getChild(0);
|
||||
offset = coreFins.getOffset();
|
||||
loc = coreFins.getComponentLocations()[0];
|
||||
assertEquals(coreFins.getName()+" offset is incorrect: ", 0.480, offset.x, EPSILON);
|
||||
assertEquals(coreFins.getName()+" location is incorrect: ", 1.044, loc.x, EPSILON);
|
||||
}
|
||||
|
||||
// ====== Booster Set Stage ======
|
||||
// ====== ====== ======
|
||||
ParallelStage boosters = (ParallelStage) rkt.getChild(1).getChild(1);
|
||||
{
|
||||
// think of the casts as an assert that ( child instanceof NoseCone) == true
|
||||
NoseCone nose = (NoseCone) boosters.getChild(0);
|
||||
offset = nose.getOffset();
|
||||
loc = nose.getComponentLocations()[0];
|
||||
assertEquals(nose.getName()+" offset is incorrect: ", 0.0, offset.x, EPSILON);
|
||||
assertEquals(nose.getName()+" location is incorrect: ", 0.484, loc.x, EPSILON);
|
||||
|
||||
BodyTube boosterBody= (BodyTube) boosters.getChild(1);
|
||||
offset = boosterBody.getOffset();
|
||||
loc = boosterBody.getComponentLocations()[0];
|
||||
assertEquals(boosterBody.getName()+" offset is incorrect: ", 0.08, offset.x, EPSILON);
|
||||
assertEquals(boosterBody.getName()+" location is incorrect: ", 0.564, loc.x, EPSILON);
|
||||
|
||||
InnerTube mmt = (InnerTube)boosters.getChild(1).getChild(0);
|
||||
offset = mmt.getOffset();
|
||||
loc = mmt.getComponentLocations()[0];
|
||||
assertEquals(mmt.getName()+" offset is incorrect: ", 0.65, offset.x, EPSILON);
|
||||
assertEquals(mmt.getName()+" location is incorrect: ", 1.214, loc.x, EPSILON);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -340,7 +340,7 @@ public class InnerTubeConfig extends RocketComponentConfig {
|
||||
}
|
||||
|
||||
InnerTube tube = (InnerTube) component;
|
||||
if (tube.getClusterCount() <= 1)
|
||||
if (tube.getInstanceCount() <= 1)
|
||||
return;
|
||||
|
||||
document.addUndoPosition("Split cluster");
|
||||
|
@ -74,7 +74,7 @@ public class PrintableCenteringRing extends AbstractPrintable<CenteringRing> {
|
||||
List<Coordinate> points = new ArrayList<Coordinate>();
|
||||
//Transform the radial positions of the tubes.
|
||||
for (InnerTube it : theMotorMounts) {
|
||||
if (it.getClusterCount() > 1) {
|
||||
if (it.getInstanceCount() > 1) {
|
||||
List<Coordinate> c = it.getClusterPoints();
|
||||
for (Coordinate coordinate : c) {
|
||||
points.add(coordinate.setX(it.getOuterRadius()));
|
||||
|
@ -52,7 +52,7 @@ import net.sf.openrocket.gui.simulation.SimulationWorker;
|
||||
import net.sf.openrocket.gui.util.SwingPreferences;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.masscalc.MassCalculator;
|
||||
import net.sf.openrocket.masscalc.MassData;
|
||||
import net.sf.openrocket.masscalc.RigidBody;
|
||||
import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
|
||||
import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
|
||||
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
|
||||
@ -125,7 +125,6 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
|
||||
/* Calculation of CP and CG */
|
||||
private AerodynamicCalculator aerodynamicCalculator;
|
||||
private MassCalculator massCalculator;
|
||||
|
||||
private final OpenRocketDocument document;
|
||||
|
||||
@ -180,7 +179,6 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
|
||||
// TODO: FUTURE: calculator selection
|
||||
aerodynamicCalculator = new BarrowmanCalculator();
|
||||
massCalculator = new MassCalculator();
|
||||
|
||||
// Create figure and custom scroll pane
|
||||
figure = new RocketFigure(rkt);
|
||||
@ -595,7 +593,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
}
|
||||
extraText.setTheta(cpTheta);
|
||||
|
||||
cg = massCalculator.getRocketLaunchMassData( curConfig).getCG();
|
||||
cg = MassCalculator.calculateLaunch( curConfig).getCM();
|
||||
|
||||
if (cp.weight > MassCalculator.MIN_MASS){
|
||||
cpx = cp.x;
|
||||
@ -634,7 +632,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
}
|
||||
}
|
||||
|
||||
MassData emptyInfo = massCalculator.getRocketSpentMassData( curConfig.getRocket().getEmptyConfiguration());
|
||||
RigidBody emptyInfo = MassCalculator.calculateStructure( curConfig );
|
||||
|
||||
extraText.setCG(cgx);
|
||||
extraText.setCP(cpx);
|
||||
|
@ -43,11 +43,13 @@ import net.sf.openrocket.gui.main.UndoRedoAction;
|
||||
import net.sf.openrocket.l10n.DebugTranslator;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.masscalc.MassCalculator;
|
||||
import net.sf.openrocket.masscalc.RigidBody;
|
||||
import net.sf.openrocket.motor.ThrustCurveMotor;
|
||||
import net.sf.openrocket.plugin.PluginModule;
|
||||
import net.sf.openrocket.rocketcomponent.EngineBlock;
|
||||
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
|
||||
import net.sf.openrocket.rocketcomponent.FlightConfigurationId;
|
||||
import net.sf.openrocket.rocketcomponent.InnerTube;
|
||||
import net.sf.openrocket.rocketcomponent.MassComponent;
|
||||
import net.sf.openrocket.rocketcomponent.NoseCone;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
@ -69,7 +71,6 @@ public class IntegrationTest {
|
||||
private Action undoAction, redoAction;
|
||||
|
||||
private AerodynamicCalculator aeroCalc = new BarrowmanCalculator();
|
||||
private MassCalculator massCalc = new MassCalculator();
|
||||
private FlightConfiguration config;
|
||||
private FlightConditions conditions;
|
||||
private String massComponentID = null;
|
||||
@ -118,7 +119,13 @@ public class IntegrationTest {
|
||||
// Test undo state
|
||||
checkUndoState(null, null);
|
||||
|
||||
InnerTube mmt = (InnerTube)config.getRocket().getChild(0).getChild(1).getChild(2);
|
||||
System.err.println(String.format("IntegrationTest::testSimpleRocket(...)...."));
|
||||
System.err.println(String.format(" Config: %s", config.toDebug() ));
|
||||
System.err.println(String.format(" motor config: %s", mmt.getMotorConfig( config.getId() ).toDescription() ));
|
||||
|
||||
// Compute cg+cp + altitude
|
||||
// double cgx, double mass, double cpx, double cna)
|
||||
checkCgCp(0.248, 0.0645, 0.320, 12.0);
|
||||
checkAlt(48.2);
|
||||
|
||||
@ -326,13 +333,12 @@ public class IntegrationTest {
|
||||
}
|
||||
|
||||
private void checkCgCp(double cgx, double mass, double cpx, double cna) {
|
||||
Coordinate cg, cp;
|
||||
|
||||
cg = massCalc.getRocketLaunchMassData(config).getCG();
|
||||
final RigidBody launchData = MassCalculator.calculateLaunch(config);
|
||||
final Coordinate cg = launchData.getCenterOfMass();
|
||||
assertEquals(cgx, cg.x, 0.001);
|
||||
assertEquals(mass, cg.weight, 0.0005);
|
||||
|
||||
cp = aeroCalc.getWorstCP(config, conditions, null);
|
||||
final Coordinate cp = aeroCalc.getWorstCP(config, conditions, null);
|
||||
assertEquals(cpx, cp.x, 0.001);
|
||||
assertEquals(cna, cp.weight, 0.1);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user