package net.sf.openrocket.masscalc;
import static net.sf.openrocket.util.MathUtil.pow2;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.motor.MotorId;
import net.sf.openrocket.motor.MotorInstance;
import net.sf.openrocket.motor.MotorInstanceConfiguration;
import net.sf.openrocket.rocketcomponent.Configuration;
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
public class BasicMassCalculator extends AbstractMassCalculator {
private static final double MIN_MASS = 0.001 * MathUtil.EPSILON;
/*
* Cached data. All CG data is in absolute coordinates. All moments of inertia
* are relative to their respective CG.
*/
private Coordinate[] cgCache = null;
private double longitudinalInertiaCache[] = null;
private double rotationalInertiaCache[] = null;
////////////////// Mass property calculations ///////////////////
/**
* Return the CG of the rocket with the specified motor status (no motors,
* ignition, burnout).
*/
@Override
public Coordinate getCG(Configuration configuration, MassCalcType type) {
checkCache(configuration);
calculateStageCache(configuration);
Coordinate totalCG = null;
// Stage contribution
for (int stage : configuration.getActiveStages()) {
totalCG = cgCache[stage].average(totalCG);
}
if (totalCG == null)
totalCG = Coordinate.NUL;
// Add motor CGs
String motorId = configuration.getFlightConfigurationID();
if (type != MassCalcType.NO_MOTORS && motorId != null) {
Iterator<MotorMount> iterator = configuration.motorIterator();
while (iterator.hasNext()) {
MotorMount mount = iterator.next();
RocketComponent comp = (RocketComponent) mount;
Motor motor = mount.getMotor(motorId);
if (motor == null)
continue;
Coordinate motorCG = type.getCG(motor).add(mount.getMotorPosition(motorId));
Coordinate[] cgs = comp.toAbsolute(motorCG);
for (Coordinate cg : cgs) {
totalCG = totalCG.average(cg);
}
}
}
return totalCG;
}
/**
* Return the CG of the rocket with the provided motor configuration.
*/
@Override
public Coordinate getCG(Configuration configuration, MotorInstanceConfiguration motors) {
checkCache(configuration);
calculateStageCache(configuration);
Coordinate totalCG = getCG(configuration, MassCalcType.NO_MOTORS);
// Add motor CGs
if (motors != null) {
for (MotorId id : motors.getMotorIDs()) {
int stage = ((RocketComponent) motors.getMotorMount(id)).getStageNumber();
if (configuration.isStageActive(stage)) {
MotorInstance motor = motors.getMotorInstance(id);
Coordinate position = motors.getMotorPosition(id);
Coordinate cg = motor.getCG().add(position);
totalCG = totalCG.average(cg);
}
}
}
return totalCG;
}
/**
* Return the longitudinal inertia of the rocket with the specified motor instance
* configuration.
*
* @param configuration the current motor instance configuration
* @return the longitudinal inertia of the rocket
*/
@Override
public double getLongitudinalInertia(Configuration configuration, MotorInstanceConfiguration motors) {
checkCache(configuration);
calculateStageCache(configuration);
final Coordinate totalCG = getCG(configuration, motors);
double totalInertia = 0;
// Stages
for (int stage : configuration.getActiveStages()) {
Coordinate stageCG = cgCache[stage];
totalInertia += (longitudinalInertiaCache[stage] +
stageCG.weight * MathUtil.pow2(stageCG.x - totalCG.x));
}
// Motors
if (motors != null) {
for (MotorId id : motors.getMotorIDs()) {
int stage = ((RocketComponent) motors.getMotorMount(id)).getStageNumber();
if (configuration.isStageActive(stage)) {
MotorInstance motor = motors.getMotorInstance(id);
Coordinate position = motors.getMotorPosition(id);
Coordinate cg = motor.getCG().add(position);
double inertia = motor.getLongitudinalInertia();
totalInertia += inertia + cg.weight * MathUtil.pow2(cg.x - totalCG.x);
}
}
}
return totalInertia;
}
/**
* Return the rotational inertia of the rocket with the specified motor instance
* configuration.
*
* @param configuration the current motor instance configuration
* @return the rotational inertia of the rocket
*/
@Override
public double getRotationalInertia(Configuration configuration, MotorInstanceConfiguration motors) {
checkCache(configuration);
calculateStageCache(configuration);
final Coordinate totalCG = getCG(configuration, motors);
double totalInertia = 0;
// Stages
for (int stage : configuration.getActiveStages()) {
Coordinate stageCG = cgCache[stage];
totalInertia += (rotationalInertiaCache[stage] +
stageCG.weight * (MathUtil.pow2(stageCG.y - totalCG.y) +
MathUtil.pow2(stageCG.z - totalCG.z)));
}
// Motors
if (motors != null) {
for (MotorId id : motors.getMotorIDs()) {
int stage = ((RocketComponent) motors.getMotorMount(id)).getStageNumber();
if (configuration.isStageActive(stage)) {
MotorInstance motor = motors.getMotorInstance(id);
Coordinate position = motors.getMotorPosition(id);
Coordinate cg = motor.getCG().add(position);
double inertia = motor.getRotationalInertia();
totalInertia += inertia + cg.weight * (MathUtil.pow2(cg.y - totalCG.y) +
MathUtil.pow2(cg.z - totalCG.z));
}
}
}
return totalInertia;
}
/**
* Return the total mass of the motors
*
* @param configuration the current motor instance configuration
* @return the total mass of all motors
*/
@Override
public double getPropellantMass(Configuration configuration, MotorInstanceConfiguration motors){
double mass = 0;
// add up the masses of all motors in the rocket
if (motors != null) {
for (MotorId id : motors.getMotorIDs()) {
MotorInstance motor = motors.getMotorInstance(id);
mass = mass + motor.getCG().weight - motor.getParentMotor().getEmptyCG().weight;
}
}
return mass;
}
@Override
public Map<RocketComponent, Coordinate> getCGAnalysis(Configuration configuration, MassCalcType type) {
checkCache(configuration);
calculateStageCache(configuration);
Map<RocketComponent, Coordinate> map = new HashMap<RocketComponent, Coordinate>();
for (RocketComponent c : configuration) {
Coordinate[] cgs = c.toAbsolute(c.getCG());
Coordinate totalCG = Coordinate.NUL;
for (Coordinate cg : cgs) {
totalCG = totalCG.average(cg);
}
map.put(c, totalCG);
}
map.put(configuration.getRocket(), getCG(configuration, type));
return map;
}
//////// Cache computations ////////
private void calculateStageCache(Configuration config) {
if (cgCache == null) {
int stages = config.getRocket().getStageCount();
cgCache = new Coordinate[stages];
longitudinalInertiaCache = new double[stages];
rotationalInertiaCache = new double[stages];
for (int i = 0; i < stages; i++) {
RocketComponent stage = config.getRocket().getChild(i);
MassData data = calculateAssemblyMassData(stage);
cgCache[i] = stage.toAbsolute(data.cg)[0];
longitudinalInertiaCache[i] = data.longitudinalInertia;
rotationalInertiaCache[i] = data.rotationalInetria;
}
}
}
/**
* 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 parent) {
MassData parentData = new MassData();
// Calculate data for this component
parentData.cg = parent.getComponentCG();
if (parentData.cg.weight < MIN_MASS)
parentData.cg = parentData.cg.setWeight(MIN_MASS);
// Override only this component's data
if (!parent.getOverrideSubcomponents()) {
if (parent.isMassOverridden())
parentData.cg = parentData.cg.setWeight(MathUtil.max(parent.getOverrideMass(), MIN_MASS));
if (parent.isCGOverridden())
parentData.cg = parentData.cg.setXYZ(parent.getOverrideCG());
}
parentData.longitudinalInertia = parent.getLongitudinalUnitInertia() * parentData.cg.weight;
parentData.rotationalInetria = parent.getRotationalUnitInertia() * parentData.cg.weight;
// Combine data for subcomponents
for (RocketComponent sibling : parent.getChildren()) {
Coordinate combinedCG;
double dx2, dr2;
// Compute data of sibling
MassData siblingData = calculateAssemblyMassData(sibling);
Coordinate[] siblingCGs = sibling.toRelative(siblingData.cg, parent);
for (Coordinate siblingCG : siblingCGs) {
// Compute CG of this + sibling
combinedCG = parentData.cg.average(siblingCG);
// Add effect of this CG change to parent inertia
dx2 = pow2(parentData.cg.x - combinedCG.x);
parentData.longitudinalInertia += parentData.cg.weight * dx2;
dr2 = pow2(parentData.cg.y - combinedCG.y) + pow2(parentData.cg.z - combinedCG.z);
parentData.rotationalInetria += parentData.cg.weight * dr2;
// Add inertia of sibling
parentData.longitudinalInertia += siblingData.longitudinalInertia;
parentData.rotationalInetria += siblingData.rotationalInetria;
// Add effect of sibling CG change
dx2 = pow2(siblingData.cg.x - combinedCG.x);
parentData.longitudinalInertia += siblingData.cg.weight * dx2;
dr2 = pow2(siblingData.cg.y - combinedCG.y) + pow2(siblingData.cg.z - combinedCG.z);
parentData.rotationalInetria += siblingData.cg.weight * dr2;
// Set combined CG
parentData.cg = combinedCG;
}
}
// Override total data
if (parent.getOverrideSubcomponents()) {
if (parent.isMassOverridden()) {
double oldMass = parentData.cg.weight;
double newMass = MathUtil.max(parent.getOverrideMass(), MIN_MASS);
parentData.longitudinalInertia = parentData.longitudinalInertia * newMass / oldMass;
parentData.rotationalInetria = parentData.rotationalInetria * newMass / oldMass;
parentData.cg = parentData.cg.setWeight(newMass);
}
if (parent.isCGOverridden()) {
double oldx = parentData.cg.x;
double newx = parent.getOverrideCGX();
parentData.longitudinalInertia += parentData.cg.weight * pow2(oldx - newx);
parentData.cg = parentData.cg.setX(newx);
}
}
return parentData;
}
private static class MassData {
public Coordinate cg = Coordinate.NUL;
public double longitudinalInertia = 0;
public double rotationalInetria = 0;
}
@Override
protected void voidMassCache() {
super.voidMassCache();
this.cgCache = null;
this.longitudinalInertiaCache = null;
this.rotationalInertiaCache = null;
}
@Override
public int getModID() {
return 0;
}
}