diff --git a/core/src/net/sf/openrocket/aerodynamics/AerodynamicForces.java b/core/src/net/sf/openrocket/aerodynamics/AerodynamicForces.java index 904098a9d..ff40734ca 100644 --- a/core/src/net/sf/openrocket/aerodynamics/AerodynamicForces.java +++ b/core/src/net/sf/openrocket/aerodynamics/AerodynamicForces.java @@ -392,7 +392,6 @@ public class AerodynamicForces implements Cloneable, Monitorable { } public AerodynamicForces merge(AerodynamicForces other) { - this.cp = cp.average(other.getCP()); this.CNa = CNa + other.getCNa(); this.CN = CN + other.getCN(); diff --git a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java index 675ffde0f..4667476c8 100644 --- a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java @@ -2,6 +2,7 @@ package net.sf.openrocket.aerodynamics; import static net.sf.openrocket.util.MathUtil.pow2; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; @@ -17,6 +18,8 @@ import net.sf.openrocket.rocketcomponent.ExternalComponent; import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish; import net.sf.openrocket.rocketcomponent.FinSet; import net.sf.openrocket.rocketcomponent.FlightConfiguration; +import net.sf.openrocket.rocketcomponent.InstanceContext; +import net.sf.openrocket.rocketcomponent.InstanceMap; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.SymmetricComponent; @@ -24,6 +27,7 @@ import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.PolyInterpolator; import net.sf.openrocket.util.Reflection; +import net.sf.openrocket.util.Transformation; /** * An aerodynamic calculator that uses the extended Barrowman method to @@ -36,7 +40,6 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { private static final String BARROWMAN_PACKAGE = "net.sf.openrocket.aerodynamics.barrowman"; private static final String BARROWMAN_SUFFIX = "Calc"; - private Map calcMap = null; private double cacheDiameter = -1; @@ -176,65 +179,43 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { warnings.add( Warning.DIAMETER_DISCONTINUITY); } - AerodynamicForces total = calculateAssemblyNonAxialForces(configuration.getRocket(), configuration, conditions, calculators, warnings, ""); - - return total; - } - - private AerodynamicForces calculateAssemblyNonAxialForces( final RocketComponent component, - FlightConfiguration configuration, FlightConditions conditions, - Map calculators, WarningSet warnings, - String indent) { - + final InstanceMap imap = configuration.getActiveInstances(); + final AerodynamicForces assemblyForces= new AerodynamicForces().zero(); - -// System.err.println(String.format("%s@@ %s <%s>", indent, component.getName(), component.getClass().getSimpleName())); - - // ==== calculate child forces ==== - for (RocketComponent child: component.getChildren()) { - AerodynamicForces childForces = calculateAssemblyNonAxialForces( child, configuration, conditions, calculators, warnings, indent+" "); - assemblyForces.merge(childForces); - } - - // calculate *this* component's forces - RocketComponentCalc calcObj = calcMap.get(component); - if(null != calcObj) { - AerodynamicForces componentForces = new AerodynamicForces().zero(); - calcObj.calculateNonaxialForces(conditions, componentForces, warnings); - - Coordinate cp_comp = componentForces.getCP(); - - Coordinate cp_weighted = cp_comp.setWeight(cp_comp.weight); - Coordinate cp_absolute = component.toAbsolute(cp_weighted)[0]; - if(1 < component.getInstanceCount()) { - cp_absolute = cp_absolute.setY(0.).setZ(0.); + + // iterate through all components + for(Map.Entry> entry: imap.entrySet() ) { + final RocketComponent comp = entry.getKey(); + RocketComponentCalc calcObj = calcMap.get(comp); + + // System.err.println("comp=" + comp); + if (null != calcObj) { + // iterate across component instances + final ArrayList contextList = entry.getValue(); + + for(InstanceContext context: contextList ) { + AerodynamicForces instanceForces = new AerodynamicForces().zero(); + + calcObj.calculateNonaxialForces(conditions, context.transform, instanceForces, warnings); + Coordinate cp_comp = instanceForces.getCP(); + + Coordinate cp_abs = context.transform.transform(cp_comp); + if ((comp instanceof FinSet) && (((FinSet)comp).getFinCount() > 2)) + cp_abs = cp_abs.setY(0.0).setZ(0.0); + + instanceForces.setCP(cp_abs); + double CN_instanced = instanceForces.getCN(); + instanceForces.setCm(CN_instanced * instanceForces.getCP().x / conditions.getRefLength()); + // System.err.println("instanceForces=" + instanceForces); + assemblyForces.merge(instanceForces); + } } - - componentForces.setCP(cp_absolute); - double CN_instanced = componentForces.getCN(); - componentForces.setCm(CN_instanced * componentForces.getCP().x / conditions.getRefLength()); - -// if( 0.0001 < Math.abs(componentForces.getCNa())){ -// final Coordinate cp = assemblyForces.getCP(); -// System.err.println(String.format("%s....Component.CNa: %g @ CP: { %f, %f, %f}", indent, componentForces.getCNa(), cp.x, cp.y, cp.z)); -// } - - assemblyForces.merge(componentForces); - } - -// if( 0.0001 < Math.abs(0 - assemblyForces.getCNa())){ -// final Coordinate cp = assemblyForces.getCP(); -// System.err.println(String.format("%s....Assembly.CNa: %g @ CP: { %f, %f, %f}", indent, assemblyForces.getCNa(), cp.x, cp.y, cp.z)); -// } - - if( component.allowsChildren() && (component.getInstanceCount() > 1)) { - return assemblyForces.multiplex(component.getInstanceCount()); - }else { - return assemblyForces; } + + // System.err.println("assemblyForces=" + assemblyForces); + return assemblyForces; } - @Override public boolean isContinuous( final Rocket rkt){ return testIsContinuous( rkt); diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java index e56f26df6..4e97a8300 100644 --- a/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java @@ -16,7 +16,7 @@ import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.LinearInterpolator; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.PolyInterpolator; - +import net.sf.openrocket.util.Transformation; public class FinSetCalc extends RocketComponentCalc { @@ -84,7 +84,7 @@ public class FinSetCalc extends RocketComponentCalc { * (normal and side forces, pitch, yaw and roll moments, CP position, CNa). */ @Override - public void calculateNonaxialForces(FlightConditions conditions, + public void calculateNonaxialForces(FlightConditions conditions, Transformation transform, AerodynamicForces forces, WarningSet warnings) { if (span < 0.001) { @@ -114,21 +114,10 @@ public class FinSetCalc extends RocketComponentCalc { // Multiple fins with fin-fin interference double cna; double theta = conditions.getTheta(); - double angle = baseRotation; + double angle = baseRotation + transform.getXrotation(); // Compute basic CNa without interference effects - if (finCount == 1 || finCount == 2) { - // Basic CNa from geometry - double mul = 0; - for (int i = 0; i < finCount; i++) { - mul += MathUtil.pow2(Math.sin(theta - angle)); - angle += 2 * Math.PI / finCount; - } - cna = cna1 * mul; - } else { - // Basic CNa assuming full efficiency - cna = cna1 * finCount / 2.0; - } + cna = cna1 * MathUtil.pow2(Math.sin(theta - angle)); // logger.debug("Component cna = {}", cna); diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/LaunchLugCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/LaunchLugCalc.java index cd7fea012..99329280d 100644 --- a/core/src/net/sf/openrocket/aerodynamics/barrowman/LaunchLugCalc.java +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/LaunchLugCalc.java @@ -6,6 +6,7 @@ import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.rocketcomponent.LaunchLug; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.MathUtil; +import net.sf.openrocket.util.Transformation; public class LaunchLugCalc extends RocketComponentCalc { @@ -24,7 +25,7 @@ public class LaunchLugCalc extends RocketComponentCalc { } @Override - public void calculateNonaxialForces(FlightConditions conditions, + public void calculateNonaxialForces(FlightConditions conditions, Transformation transform, AerodynamicForces forces, WarningSet warnings) { // Nothing to be done } diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/RocketComponentCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/RocketComponentCalc.java index 71f4ef0cf..900b507d9 100644 --- a/core/src/net/sf/openrocket/aerodynamics/barrowman/RocketComponentCalc.java +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/RocketComponentCalc.java @@ -4,6 +4,7 @@ import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.FlightConditions; import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.util.Transformation; public abstract class RocketComponentCalc { @@ -20,11 +21,12 @@ public abstract class RocketComponentCalc { * computed around the local origin. * * @param conditions the flight conditions. + * @param transform transformation from InstanceMap to get rotations rotations * @param forces the object in which to store the values. * @param warnings set in which to store possible warnings. */ - public abstract void calculateNonaxialForces(FlightConditions conditions, - AerodynamicForces forces, WarningSet warnings); + public abstract void calculateNonaxialForces(FlightConditions conditions, Transformation transform, + AerodynamicForces forces, WarningSet warnings); /** diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/SymmetricComponentCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/SymmetricComponentCalc.java index 1bfcfa2ee..6fa72428b 100644 --- a/core/src/net/sf/openrocket/aerodynamics/barrowman/SymmetricComponentCalc.java +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/SymmetricComponentCalc.java @@ -16,6 +16,7 @@ import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.LinearInterpolator; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.PolyInterpolator; +import net.sf.openrocket.util.Transformation; @@ -95,7 +96,7 @@ public class SymmetricComponentCalc extends RocketComponentCalc { * subsonic speeds. */ @Override - public void calculateNonaxialForces(FlightConditions conditions, + public void calculateNonaxialForces(FlightConditions conditions, Transformation transform, AerodynamicForces forces, WarningSet warnings) { // Pre-calculate and store the results diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/TubeFinSetCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/TubeFinSetCalc.java index d6e1d56d7..1efab0c04 100644 --- a/core/src/net/sf/openrocket/aerodynamics/barrowman/TubeFinSetCalc.java +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/TubeFinSetCalc.java @@ -17,6 +17,7 @@ import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.LinearInterpolator; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.PolyInterpolator; +import net.sf.openrocket.util.Transformation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -92,7 +93,7 @@ public class TubeFinSetCalc extends RocketComponentCalc { * pitch, yaw and roll moments, CP position, CNa). */ @Override - public void calculateNonaxialForces(FlightConditions conditions, + public void calculateNonaxialForces(FlightConditions conditions, Transformation transform, AerodynamicForces forces, WarningSet warnings) { if (span < 0.001) { @@ -124,7 +125,7 @@ public class TubeFinSetCalc extends RocketComponentCalc { // Multiple fins with fin-fin interference double cna; double theta = conditions.getTheta(); - double angle = baseRotation; + double angle = baseRotation + transform.getXrotation(); // Compute basic CNa without interference effects if (finCount == 1 || finCount == 2) { diff --git a/core/src/net/sf/openrocket/util/Coordinate.java b/core/src/net/sf/openrocket/util/Coordinate.java index 51c8b00f1..b4d93a76a 100644 --- a/core/src/net/sf/openrocket/util/Coordinate.java +++ b/core/src/net/sf/openrocket/util/Coordinate.java @@ -331,9 +331,9 @@ public final class Coordinate implements Cloneable, Serializable { @Override public String toString() { if (isWeighted()) - return String.format("(%.3f,%.3f,%.3f,w=%.3f)", x, y, z, weight); + return String.format("(%.5f,%.5f,%.5f,w=%.5f)", x, y, z, weight); else - return String.format("(%.3f,%.3f,%.3f)", x, y, z); + return String.format("(%.5f,%.5f,%.5f)", x, y, z); } // high-precision output, for use with verifying calculations diff --git a/core/src/net/sf/openrocket/util/TestRockets.java b/core/src/net/sf/openrocket/util/TestRockets.java index 0f025371e..e6b6c2fc9 100644 --- a/core/src/net/sf/openrocket/util/TestRockets.java +++ b/core/src/net/sf/openrocket/util/TestRockets.java @@ -3,6 +3,7 @@ package net.sf.openrocket.util; import java.util.Random; import net.sf.openrocket.appearance.Appearance; +import net.sf.openrocket.file.openrocket.OpenRocketSaver; import net.sf.openrocket.database.Databases; import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.OpenRocketDocumentFactory; @@ -39,6 +40,7 @@ import net.sf.openrocket.rocketcomponent.MassComponent; import net.sf.openrocket.rocketcomponent.NoseCone; import net.sf.openrocket.rocketcomponent.Parachute; import net.sf.openrocket.rocketcomponent.ParallelStage; +import net.sf.openrocket.rocketcomponent.PodSet; import net.sf.openrocket.rocketcomponent.RecoveryDevice; import net.sf.openrocket.rocketcomponent.ReferenceType; import net.sf.openrocket.rocketcomponent.Rocket; @@ -851,6 +853,14 @@ public class TestRockets { // This function is used for unit, integration tests, DO NOT CHANGE (without updating tests). + // Comments starting with cp: are center of pressure calculations + // for components of the rocket. Note that a cp: without a weight + // has a weight of 0 (see for example body tubes) -- included for + // completeness. + // Instanced components (ie components on the side boosters) have + // a cp: for each instance. + // The unit tests change the number of fins on the side boosters; + // cp: comments are shown for 1-fin, 2-fin, and 3-fin cases public static Rocket makeFalcon9Heavy() { Rocket rocket = new Rocket(); rocket.setName("Falcon9H Scale Rocket"); @@ -876,10 +886,12 @@ public class TestRockets { payloadFairingNoseCone.setAftShoulderThickness( 0.001 ); payloadFairingNoseCone.setAftShoulderCapped( false ); payloadStage.addChild(payloadFairingNoseCone); + // cp:(0.05900,0.00000,0.00000,w=2.00000) BodyTube payloadBody = new BodyTube(0.132, 0.052, 0.001); payloadBody.setName("PL Fairing Body"); payloadStage.addChild(payloadBody); + // cp:(0.18400,0.00000,0.00000) Transition payloadFairingTail = new Transition(); payloadFairingTail.setName("PL Fairing Transition"); @@ -888,10 +900,12 @@ public class TestRockets { payloadFairingTail.setForeRadiusAutomatic(true); payloadFairingTail.setAftRadiusAutomatic(true); payloadStage.addChild(payloadFairingTail); + // cp:(0.25665,0.00000,0.00000,w=-0.90366) BodyTube upperStageBody= new BodyTube(0.18, 0.0385, 0.001); upperStageBody.setName("Upper Stage Body "); payloadStage.addChild( upperStageBody); + // cp:(0.35400,0.00000,0.00000) { // Parachute @@ -916,6 +930,7 @@ public class TestRockets { BodyTube interstage= new BodyTube(0.12, 0.0385, 0.001); interstage.setName("Interstage"); payloadStage.addChild( interstage); + // cp:(0.50400,0.00000,0.00000) } // ====== Core Stage ====== @@ -930,6 +945,7 @@ public class TestRockets { coreBody.setName("Core Stage Body"); coreBody.setMotorMount(true); coreStage.addChild( coreBody); + // cp:(0.96400,0.00000,0.00000) { MotorConfiguration coreMotorConfig = new MotorConfiguration(coreBody, selFCID); Motor mtr = TestRockets.generateMotor_M1350_75mm(); @@ -961,12 +977,15 @@ public class TestRockets { boosterCone.setAftShoulderThickness( 0.001 ); boosterCone.setAftShoulderCapped( false ); boosterStage.addChild( boosterCone); + // cp:(0.52400,0.07700,0.00000,w=1.09634) + // cp:(0.52400,-0.07700,0.00000,w=1.09634) BodyTube boosterBody = new BodyTube(0.8, 0.0385, 0.001); boosterBody.setName("Booster Body"); boosterBody.setOuterRadiusAutomatic(true); boosterStage.addChild( boosterBody); - + // cp:(0.96400,0.07700,0.00000) + // cp:(0.96400,-0.07700,0.00000) { InnerTube boosterMotorTubes = new InnerTube(); boosterMotorTubes.setName("Booster Motor Tubes"); @@ -997,6 +1016,12 @@ public class TestRockets { boosterFins.setSweep(0.18); boosterFins.setAxialMethod(AxialMethod.BOTTOM); boosterFins.setAxialOffset(0.0); + // cp:(1.17873,0.10422,0.02722,w=6.07405) (1 fin case) + // cp:(1.17873,-0.10422,-0.02722,w=6.07405) (1 fin case) + // cp:(1.17873,0.10422,0.02722,w=12.14810) (2 fin case) + // cp:(1.17873,-0.10422,-0.02722,w=12.14810) (2 fin case) + // cp:(1.17873,0.00000,0.00000,w=9.11107) (3 fin case) + // cp:(1.17873,0.00000,0.00000,w=9.11107) (3 fin case) } } @@ -1009,6 +1034,128 @@ public class TestRockets { return rocket; } + + // This is a simple four-fin rocket with large endplates on the + // fins, for testing CG and CP calculations with fins on pods. + // not a complete rocket (no motor mount nor recovery system) + + // it's parameterized to make things easy to change, but be sure + // to adjust the unit test to match! + public static Rocket makeEndPlateRocket() { + + // rocket design parameters + double radius = 0.01; // note this is diameter/2! + int finCount = 4; // also determines pod count + + // nose cone + double noseThick = 0.002; + double noseLength = 0.05; + + // body tube + double bodyWallThick = 0.002; + double bodyLength = 0.254; + + // main body tube fins + int bodyFinCount = 4; + double bodyFinRootChord = 0.05; + double bodyFinTipChord = bodyFinRootChord; + double bodyFinHeight = 0.025; + double bodyFinSweep = 0.0; + AxialMethod bodyFinAxialMethod = AxialMethod.BOTTOM; + double bodyFinAxialOffset = 0.0; + double bodyFinThickness = 0.003; + + // pods for end plates + int podSetCount = bodyFinCount; + double podSetOffset = bodyFinHeight; + AxialMethod podSetAxialMethod = bodyFinAxialMethod; + double podSetAxialOffset = 0.0; + + // "phantom" tube on pods to give us somewhere to connect end + // plates + double phantomLength = bodyFinTipChord; + double phantomRadius = 0; + double phantomWallThickness = 0; + + // end plates + int endPlateCount = 2; + double endPlateRootChord = bodyFinTipChord; + double endPlateTipChord = endPlateRootChord; + double endPlateSweep = 0.0; + double endPlateThickness = bodyFinThickness; + double endPlateHeight = bodyFinHeight; + double endPlateRotation = Math.PI/2.0; + AxialMethod endPlateAxialMethod = AxialMethod.BOTTOM; + double endPlateAxialOffset = 0; + + // create bare rocket + Rocket rocket = new Rocket(); + rocket.enableEvents(); + rocket.setName("End Plate Test"); + + // rocket has one stage + AxialStage sustainer = new AxialStage(); + rocket.addChild(sustainer); + sustainer.setName("Sustainer"); + + // nose cone + NoseCone noseCone = new NoseCone(Transition.Shape.OGIVE, noseLength, radius); + sustainer.addChild(noseCone); + noseCone.setName("Nose Cone"); + // cp:(0.02303,0.00000,0.00000,w=2.00000) + + // body tube + BodyTube bodyTube = new BodyTube(bodyLength, radius, bodyWallThick); + sustainer.addChild(bodyTube); + bodyTube.setName("Body tube"); + // cp:(0.17700,0.00000,0.00000) + + // Trapezoidal fin set on body tube + TrapezoidFinSet bodyFinSet = new TrapezoidFinSet(bodyFinCount, bodyFinRootChord, bodyFinTipChord, bodyFinSweep, bodyFinHeight); + bodyTube.addChild(bodyFinSet); + bodyFinSet.setName("Body Tube FinSet"); + bodyFinSet.setAxialMethod(bodyFinAxialMethod); + bodyFinSet.setAxialOffset(bodyFinAxialOffset); + bodyFinSet.setThickness(bodyFinThickness); + // cp:(0.26650,0.00000,0.00000,w=15.24857) + + // Pod set to put an end plate on each fin + PodSet podSet = new PodSet(); + bodyTube.addChild(podSet); + podSet.setName("Pod Set"); + podSet.setInstanceCount(podSetCount); + podSet.setRadiusOffset(podSetOffset); + podSet.setAxialMethod(podSetAxialMethod); + podSet.setAxialOffset(podSetAxialOffset); + + // 0-diameter "body tube" to give us something to hook the + // endplates to. Note that this causes a "thick fins" + // warning. + BodyTube phantom = new BodyTube(phantomLength, phantomRadius, phantomWallThickness); + podSet.addChild(phantom); + phantom.setName("Phantom"); + // cp:(0.25400,0.03540,0.00000) + // cp:(0.25400,0.00000,0.03540) + // cp:(0.25400,-0.03540,0.00000) + // cp:(0.25400,-0.00000,-0.03540) + + // end plates + TrapezoidFinSet endPlate = new TrapezoidFinSet(endPlateCount, endPlateRootChord, endPlateTipChord, endPlateSweep, endPlateHeight); + phantom.addChild(endPlate); + endPlate.setName("End plates"); + endPlate.setBaseRotation(endPlateRotation); + endPlate.setAxialMethod(endPlateAxialMethod); + endPlate.setAxialOffset(endPlateAxialOffset); + endPlate.setThickness(endPlateThickness); + // cp:(0.26650,0.03540,0.00000,w=0.00000) + // cp:(0.26650,0.00000,0.03540,w=11.86000) + // cp:(0.26650,-0.03540,0.00000,w=0.00000) + // cp:(0.26650,-0.00000,-0.03540,w=11.86000) + + // Total cp:(0.25461,-0.00000,0.00000,w=40.96857) + + return rocket; + } /* * Create a new file version 1.00 rocket @@ -1406,6 +1553,7 @@ public class TestRockets { public static OpenRocketDocument makeTestRocket_v108_withBoosters() { Rocket rocket = makeFalcon9Heavy(); OpenRocketDocument document = OpenRocketDocumentFactory.createDocumentFromRocket(rocket); + return document; } diff --git a/core/src/net/sf/openrocket/util/Transformation.java b/core/src/net/sf/openrocket/util/Transformation.java index d31bb7906..b40d9041c 100644 --- a/core/src/net/sf/openrocket/util/Transformation.java +++ b/core/src/net/sf/openrocket/util/Transformation.java @@ -374,5 +374,25 @@ public class Transformation implements java.io.Serializable { public Coordinate getTranslationVector() { return this.translate; } + + /** + * Calculate X, Y, and Z rotation angles from rotation matrices + */ + public double getXrotation() { + return Math.atan2((rotation[2][1] - rotation[1][2])/2.0, + (rotation[1][1] + rotation[2][2])/2.0); + } + + public double getYrotation() { + return Math.atan2((rotation[0][2] - rotation[2][0])/2.0, + (rotation[0][0] + rotation[2][2])/2.0); + } + + public double getZrotation() { + return Math.atan2((rotation[1][0] - rotation[0][1])/2.0, + (rotation[0][0] + rotation[1][1])/2.0); + + } + } diff --git a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java index 6e09fc678..ebcc07d78 100644 --- a/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java +++ b/core/test/net/sf/openrocket/aerodynamics/BarrowmanCalculatorTest.java @@ -86,7 +86,7 @@ public class BarrowmanCalculatorTest { assertEquals(" Estes Alpha III CNa value is incorrect:", exp_cna, cp_calc.weight, EPSILON); assertEquals(" Estes Alpha III cp x value is incorrect:", exp_cpx, cp_calc.x, EPSILON); - assertEquals(" Estes Alpha III cp x value is incorrect:", 0.0, cp_calc.y, EPSILON); + assertEquals(" Estes Alpha III cp y value is incorrect:", 0.0, cp_calc.y, EPSILON); } @Test @@ -107,7 +107,8 @@ public class BarrowmanCalculatorTest { assertEquals(" Estes Alpha III cp x value is incorrect:", expCPx, calcCP.x, EPSILON); assertEquals(" Estes Alpha III CNa value is incorrect:", exp_cna, calcCP.weight, EPSILON); } - + + // Component CP calculations resulting in expected test values are in comments in TestRockets.makeFalcon9Heavy() @Test public void testCPParallelBoosters() { final Rocket rocket = TestRockets.makeFalcon9Heavy(); @@ -128,21 +129,40 @@ public class BarrowmanCalculatorTest { }{ boosterFins.setFinCount(2); final Coordinate cp_2fin = calc.getCP(config, conditions, warnings); - assertEquals(" Falcon 9 Heavy CNa value is incorrect:", 15.43711197, cp_2fin.weight, EPSILON); - assertEquals(" Falcon 9 Heavy CP x value is incorrect:", 0.99464238, cp_2fin.x, EPSILON); + assertEquals(" Falcon 9 Heavy CNa value is incorrect:", 27.585207667168696, cp_2fin.weight, EPSILON); + assertEquals(" Falcon 9 Heavy CP x value is incorrect:", 1.0757127676940474, cp_2fin.x, EPSILON); assertEquals(" Falcon 9 Heavy CP y value is incorrect:", 0.0, cp_2fin.y, EPSILON); assertEquals(" Falcon 9 Heavy CP z value is incorrect:", 0.0, cp_2fin.z, EPSILON); }{ boosterFins.setFinCount(1); final Coordinate cp_1fin = calc.getCP(config, conditions, warnings); - assertEquals(" Falcon 9 Heavy CNa value is incorrect:", 9.36306412, cp_1fin.weight, EPSILON); - assertEquals(" Falcon 9 Heavy CP x value is incorrect:", 0.87521867, cp_1fin.x, EPSILON); + assertEquals(" Falcon 9 Heavy CNa value is incorrect:", 15.43711196967902, cp_1fin.weight, EPSILON); + assertEquals(" Falcon 9 Heavy CP x value is incorrect:", 0.99464, cp_1fin.x, EPSILON); assertEquals(" Falcon 9 Heavy CP y value is incorrect:", 0f, cp_1fin.y, EPSILON); assertEquals(" Falcon 9 Heavy CP z value is incorrect:", 0f, cp_1fin.z, EPSILON); }{ // absent -- 3.28901627g @[0.31469937,0.05133333,0.00000000] } } + + // test rocket with endplates on fins. Comments tracing + // calculation of CP are in TestRockets.makeEndPlateRocket(). + @Test + public void testEndPlateCP() { + final Rocket rocket = TestRockets.makeEndPlateRocket(); + final FlightConfiguration config = new FlightConfiguration(rocket, null); + // rocket.setFlightConfiguration(config.getId(), config); + // rocket.setSelectedConfiguration(config.getId()); + final AerodynamicCalculator calc = new BarrowmanCalculator(); + final FlightConditions conditions = new FlightConditions(config); + final WarningSet warnings = new WarningSet(); + + final Coordinate cp = calc.getCP(config, conditions, warnings); + assertEquals(" Endplate rocket cp x value is incorrect:", 0.25461, cp.x, EPSILON); + assertEquals(" Endplate rocket cp y value is incorrect:", 0.0, cp.y, EPSILON); + assertEquals(" Endplate rocket cp z value is incorrect:", 0.0, cp.z, EPSILON); + assertEquals(" Endplate rocket CNa value is incorrect:", 40.96857, cp.weight, EPSILON); + } @Test public void testGetWorstCP() { diff --git a/core/test/net/sf/openrocket/aerodynamics/FinSetCalcTest.java b/core/test/net/sf/openrocket/aerodynamics/FinSetCalcTest.java index 7eb12199f..bfdefe084 100644 --- a/core/test/net/sf/openrocket/aerodynamics/FinSetCalcTest.java +++ b/core/test/net/sf/openrocket/aerodynamics/FinSetCalcTest.java @@ -16,6 +16,7 @@ import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.TrapezoidFinSet; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.TestRockets; +import net.sf.openrocket.util.Transformation; public class FinSetCalcTest { protected final double EPSILON = 0.0001; @@ -37,6 +38,27 @@ public class FinSetCalcTest { // } } + private AerodynamicForces sumFins(TrapezoidFinSet fins, Rocket rocket) + { + FlightConfiguration config = rocket.getSelectedConfiguration(); + FlightConditions conditions = new FlightConditions(config); + WarningSet warnings = new WarningSet(); + AerodynamicForces assemblyForces = new AerodynamicForces().zero(); + AerodynamicForces componentForces = new AerodynamicForces(); + + FinSetCalc calcObj = new FinSetCalc( fins ); + + // Need to sum forces for fins + for (Integer i = 0; i < fins.getFinCount(); i++) { + calcObj.calculateNonaxialForces(conditions, + Transformation.rotate_x(Math.PI * i / fins.getFinCount()), + componentForces, warnings); + assemblyForces.merge(componentForces); + } + + return assemblyForces; + } + @Test public void test3Fin() { Rocket rocket = TestRockets.makeEstesAlphaIII(); @@ -48,18 +70,10 @@ public class FinSetCalcTest { assertEquals(" Estes Alpha III fins have wrong tip chord:", 0.03, fins.getTipChord(), EPSILON); assertEquals(" Estes Alpha III fins have wrong sweep: ", 0.02, fins.getSweep(), EPSILON); assertEquals(" Estes Alpha III fins have wrong height: ", 0.05, fins.getHeight(), EPSILON); - - FlightConfiguration config = rocket.getSelectedConfiguration(); - FlightConditions conditions = new FlightConditions(config); - WarningSet warnings = new WarningSet(); - AerodynamicForces forces = new AerodynamicForces(); - FinSetCalc calcObj = new FinSetCalc( fins ); - - - // vvv TEST MEH! vvv - calcObj.calculateNonaxialForces(conditions, forces, warnings); - // ^^^ - + + // get the forces for the three fins + AerodynamicForces forces = sumFins(fins, rocket); + double exp_cna_fins = 24.146933; double exp_cpx_fins = 0.0193484; @@ -82,17 +96,9 @@ public class FinSetCalcTest { assertEquals(" Estes Alpha III fins have wrong tip chord:", 0.03, fins.getTipChord(), EPSILON); assertEquals(" Estes Alpha III fins have wrong sweep: ", 0.02, fins.getSweep(), EPSILON); assertEquals(" Estes Alpha III fins have wrong height: ", 0.05, fins.getHeight(), EPSILON); - - FlightConfiguration config = rocket.getSelectedConfiguration(); - FlightConditions conditions = new FlightConditions(config); - WarningSet warnings = new WarningSet(); - AerodynamicForces forces = new AerodynamicForces(); - FinSetCalc calcObj = new FinSetCalc( fins ); - - - // vvv TEST MEH! vvv - calcObj.calculateNonaxialForces(conditions, forces, warnings); - // ^^^ + + // get the forces for the four fins + AerodynamicForces forces = sumFins(fins, rocket); double exp_cna_fins = 32.195911; double exp_cpx_fins = 0.0193484; diff --git a/core/test/net/sf/openrocket/aerodynamics/SymmetricComponentCalcTest.java b/core/test/net/sf/openrocket/aerodynamics/SymmetricComponentCalcTest.java index 3fb44f26e..83bee1322 100644 --- a/core/test/net/sf/openrocket/aerodynamics/SymmetricComponentCalcTest.java +++ b/core/test/net/sf/openrocket/aerodynamics/SymmetricComponentCalcTest.java @@ -18,6 +18,7 @@ import net.sf.openrocket.rocketcomponent.Transition; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.TestRockets; +import net.sf.openrocket.util.Transformation; public class SymmetricComponentCalcTest { protected final double EPSILON = MathUtil.EPSILON*1000; @@ -52,13 +53,14 @@ public class SymmetricComponentCalcTest { FlightConfiguration config = rocket.getSelectedConfiguration(); FlightConditions conditions = new FlightConditions(config); + Transformation transform = Transformation.IDENTITY; WarningSet warnings = new WarningSet(); AerodynamicForces forces = new AerodynamicForces(); SymmetricComponentCalc calcObj = new SymmetricComponentCalc( nose ); conditions.setAOA(0.0); // vvv TEST MEH! vvv - calcObj.calculateNonaxialForces(conditions, forces, warnings); + calcObj.calculateNonaxialForces(conditions, transform, forces, warnings); // ^^^ double cna_nose = 2; @@ -82,13 +84,14 @@ public class SymmetricComponentCalcTest { FlightConfiguration config = rocket.getSelectedConfiguration(); FlightConditions conditions = new FlightConditions(config); + Transformation transform = Transformation.IDENTITY; WarningSet warnings = new WarningSet(); AerodynamicForces forces = new AerodynamicForces(); SymmetricComponentCalc calcObj = new SymmetricComponentCalc( nose ); conditions.setAOA(0.0); // vvv TEST vvv - calcObj.calculateNonaxialForces(conditions, forces, warnings); + calcObj.calculateNonaxialForces(conditions, transform, forces, warnings); // ^^^ ^^^ double l_nose = nose.getLength(); diff --git a/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java b/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java index 38499750c..a0c0d0697 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java @@ -23,6 +23,7 @@ import net.sf.openrocket.util.Color; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.LineStyle; import net.sf.openrocket.util.BaseTestCase.BaseTestCase; +import net.sf.openrocket.util.Transformation; public class FreeformFinSetTest extends BaseTestCase { @@ -1203,9 +1204,10 @@ public class FreeformFinSetTest extends BaseTestCase { bt.addChild(fins); FinSetCalc calc = new FinSetCalc(fins); FlightConditions conditions = new FlightConditions(null); + Transformation transform = Transformation.IDENTITY; AerodynamicForces forces = new AerodynamicForces(); WarningSet warnings = new WarningSet(); - calc.calculateNonaxialForces(conditions, forces, warnings); + calc.calculateNonaxialForces(conditions, transform, forces, warnings); //System.out.println(forces); assertEquals(0.023409, forces.getCP().x, 0.0001); }