1 package net.sf.openrocket.masscalc;
3 import static net.sf.openrocket.util.MathUtil.pow2;
5 import java.util.HashMap;
6 import java.util.Iterator;
9 import net.sf.openrocket.motor.Motor;
10 import net.sf.openrocket.motor.MotorId;
11 import net.sf.openrocket.motor.MotorInstance;
12 import net.sf.openrocket.motor.MotorInstanceConfiguration;
13 import net.sf.openrocket.rocketcomponent.Configuration;
14 import net.sf.openrocket.rocketcomponent.MotorMount;
15 import net.sf.openrocket.rocketcomponent.RocketComponent;
16 import net.sf.openrocket.util.Coordinate;
17 import net.sf.openrocket.util.MathUtil;
19 public class BasicMassCalculator extends AbstractMassCalculator {
21 private static final double MIN_MASS = 0.001 * MathUtil.EPSILON;
25 * Cached data. All CG data is in absolute coordinates. All moments of inertia
26 * are relative to their respective CG.
28 private Coordinate[] cgCache = null;
29 private double longitudinalInertiaCache[] = null;
30 private double rotationalInertiaCache[] = null;
34 ////////////////// Mass property calculations ///////////////////
38 * Return the CG of the rocket with the specified motor status (no motors,
41 public Coordinate getCG(Configuration configuration, MassCalcType type) {
42 checkCache(configuration);
43 calculateStageCache(configuration);
45 Coordinate totalCG = null;
48 for (int stage : configuration.getActiveStages()) {
49 totalCG = cgCache[stage].average(totalCG);
53 totalCG = Coordinate.NUL;
56 String motorId = configuration.getMotorConfigurationID();
57 if (type != MassCalcType.NO_MOTORS && motorId != null) {
58 Iterator<MotorMount> iterator = configuration.motorIterator();
59 while (iterator.hasNext()) {
60 MotorMount mount = iterator.next();
61 RocketComponent comp = (RocketComponent) mount;
62 Motor motor = mount.getMotor(motorId);
66 Coordinate motorCG = type.getCG(motor).add(mount.getMotorPosition(motorId));
67 Coordinate[] cgs = comp.toAbsolute(motorCG);
68 for (Coordinate cg : cgs) {
69 totalCG = totalCG.average(cg);
81 * Return the CG of the rocket with the provided motor configuration.
83 public Coordinate getCG(Configuration configuration, MotorInstanceConfiguration motors) {
84 checkCache(configuration);
85 calculateStageCache(configuration);
87 Coordinate totalCG = getCG(configuration, MassCalcType.NO_MOTORS);
91 for (MotorId id : motors.getMotorIDs()) {
92 int stage = ((RocketComponent) motors.getMotorMount(id)).getStageNumber();
93 if (configuration.isStageActive(stage)) {
95 MotorInstance motor = motors.getMotorInstance(id);
96 Coordinate position = motors.getMotorPosition(id);
97 Coordinate cg = motor.getCG().add(position);
98 totalCG = totalCG.average(cg);
108 * Return the longitudinal inertia of the rocket with the specified motor instance
111 * @param configuration the current motor instance configuration
112 * @return the longitudinal inertia of the rocket
114 public double getLongitudinalInertia(Configuration configuration, MotorInstanceConfiguration motors) {
115 checkCache(configuration);
116 calculateStageCache(configuration);
118 final Coordinate totalCG = getCG(configuration, motors);
119 double totalInertia = 0;
122 for (int stage : configuration.getActiveStages()) {
123 Coordinate stageCG = cgCache[stage];
125 totalInertia += (longitudinalInertiaCache[stage] +
126 stageCG.weight * MathUtil.pow2(stageCG.x - totalCG.x));
131 if (motors != null) {
132 for (MotorId id : motors.getMotorIDs()) {
133 int stage = ((RocketComponent) motors.getMotorMount(id)).getStageNumber();
134 if (configuration.isStageActive(stage)) {
135 MotorInstance motor = motors.getMotorInstance(id);
136 Coordinate position = motors.getMotorPosition(id);
137 Coordinate cg = motor.getCG().add(position);
139 double inertia = motor.getLongitudinalInertia();
140 totalInertia += inertia + cg.weight * MathUtil.pow2(cg.x - totalCG.x);
151 * Return the rotational inertia of the rocket with the specified motor instance
154 * @param configuration the current motor instance configuration
155 * @return the rotational inertia of the rocket
157 public double getRotationalInertia(Configuration configuration, MotorInstanceConfiguration motors) {
158 checkCache(configuration);
159 calculateStageCache(configuration);
161 final Coordinate totalCG = getCG(configuration, motors);
162 double totalInertia = 0;
165 for (int stage : configuration.getActiveStages()) {
166 Coordinate stageCG = cgCache[stage];
168 totalInertia += (rotationalInertiaCache[stage] +
169 stageCG.weight * (MathUtil.pow2(stageCG.y - totalCG.y) +
170 MathUtil.pow2(stageCG.z - totalCG.z)));
175 if (motors != null) {
176 for (MotorId id : motors.getMotorIDs()) {
177 int stage = ((RocketComponent) motors.getMotorMount(id)).getStageNumber();
178 if (configuration.isStageActive(stage)) {
179 MotorInstance motor = motors.getMotorInstance(id);
180 Coordinate position = motors.getMotorPosition(id);
181 Coordinate cg = motor.getCG().add(position);
183 double inertia = motor.getRotationalInertia();
184 totalInertia += inertia + cg.weight * (MathUtil.pow2(cg.y - totalCG.y) +
185 MathUtil.pow2(cg.z - totalCG.z));
196 public Map<RocketComponent, Coordinate> getCGAnalysis(Configuration configuration, MassCalcType type) {
197 checkCache(configuration);
198 calculateStageCache(configuration);
200 Map<RocketComponent, Coordinate> map = new HashMap<RocketComponent, Coordinate>();
202 for (RocketComponent c : configuration) {
203 Coordinate[] cgs = c.toAbsolute(c.getCG());
204 Coordinate totalCG = Coordinate.NUL;
205 for (Coordinate cg : cgs) {
206 totalCG = totalCG.average(cg);
211 map.put(configuration.getRocket(), getCG(configuration, type));
216 //////// Cache computations ////////
218 private void calculateStageCache(Configuration config) {
219 if (cgCache == null) {
221 int stages = config.getRocket().getStageCount();
223 cgCache = new Coordinate[stages];
224 longitudinalInertiaCache = new double[stages];
225 rotationalInertiaCache = new double[stages];
227 for (int i = 0; i < stages; i++) {
228 RocketComponent stage = config.getRocket().getChild(i);
229 MassData data = calculateAssemblyMassData(stage);
230 cgCache[i] = stage.toAbsolute(data.cg)[0];
231 longitudinalInertiaCache[i] = data.longitudinalInertia;
232 rotationalInertiaCache[i] = data.rotationalInetria;
241 * Returns the mass and inertia data for this component and all subcomponents.
242 * The inertia is returned relative to the CG, and the CG is in the coordinates
243 * of the specified component, not global coordinates.
245 private MassData calculateAssemblyMassData(RocketComponent parent) {
246 MassData parentData = new MassData();
248 // Calculate data for this component
249 parentData.cg = parent.getComponentCG();
250 if (parentData.cg.weight < MIN_MASS)
251 parentData.cg = parentData.cg.setWeight(MIN_MASS);
254 // Override only this component's data
255 if (!parent.getOverrideSubcomponents()) {
256 if (parent.isMassOverridden())
257 parentData.cg = parentData.cg.setWeight(MathUtil.max(parent.getOverrideMass(), MIN_MASS));
258 if (parent.isCGOverridden())
259 parentData.cg = parentData.cg.setXYZ(parent.getOverrideCG());
262 parentData.longitudinalInertia = parent.getLongitudinalUnitInertia() * parentData.cg.weight;
263 parentData.rotationalInetria = parent.getRotationalUnitInertia() * parentData.cg.weight;
266 // Combine data for subcomponents
267 for (RocketComponent sibling : parent.getChildren()) {
268 Coordinate combinedCG;
271 // Compute data of sibling
272 MassData siblingData = calculateAssemblyMassData(sibling);
273 Coordinate[] siblingCGs = sibling.toRelative(siblingData.cg, parent);
275 for (Coordinate siblingCG : siblingCGs) {
277 // Compute CG of this + sibling
278 combinedCG = parentData.cg.average(siblingCG);
280 // Add effect of this CG change to parent inertia
281 dx2 = pow2(parentData.cg.x - combinedCG.x);
282 parentData.longitudinalInertia += parentData.cg.weight * dx2;
284 dr2 = pow2(parentData.cg.y - combinedCG.y) + pow2(parentData.cg.z - combinedCG.z);
285 parentData.rotationalInetria += parentData.cg.weight * dr2;
288 // Add inertia of sibling
289 parentData.longitudinalInertia += siblingData.longitudinalInertia;
290 parentData.rotationalInetria += siblingData.rotationalInetria;
292 // Add effect of sibling CG change
293 dx2 = pow2(siblingData.cg.x - combinedCG.x);
294 parentData.longitudinalInertia += siblingData.cg.weight * dx2;
296 dr2 = pow2(siblingData.cg.y - combinedCG.y) + pow2(siblingData.cg.z - combinedCG.z);
297 parentData.rotationalInetria += siblingData.cg.weight * dr2;
300 parentData.cg = combinedCG;
304 // Override total data
305 if (parent.getOverrideSubcomponents()) {
306 if (parent.isMassOverridden()) {
307 double oldMass = parentData.cg.weight;
308 double newMass = MathUtil.max(parent.getOverrideMass(), MIN_MASS);
309 parentData.longitudinalInertia = parentData.longitudinalInertia * newMass / oldMass;
310 parentData.rotationalInetria = parentData.rotationalInetria * newMass / oldMass;
311 parentData.cg = parentData.cg.setWeight(newMass);
313 if (parent.isCGOverridden()) {
314 double oldx = parentData.cg.x;
315 double newx = parent.getOverrideCGX();
316 parentData.longitudinalInertia += parentData.cg.weight * pow2(oldx - newx);
317 parentData.cg = parentData.cg.setX(newx);
325 private static class MassData {
326 public Coordinate cg = Coordinate.NUL;
327 public double longitudinalInertia = 0;
328 public double rotationalInetria = 0;
333 protected void voidMassCache() {
334 super.voidMassCache();
336 this.longitudinalInertiaCache = null;
337 this.rotationalInertiaCache = null;
344 public int getModID() {