Update unit tests for SymmetricComponent. This is another pretty big rewrite -- the old code had a lot of references to numbers transcribed from an unnamed 2D CAD package, and also expected values that were just presented without derivation. New code has the trig functions to calculate the numbers, has helper functios for the comparisons, and puts a much tighter bound on results.
This commit is contained in:
parent
9f13635357
commit
4dd2c3a86a
@ -4,384 +4,389 @@ import static org.junit.Assert.assertEquals;
|
||||
import net.sf.openrocket.material.Material;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
|
||||
import net.sf.openrocket.util.MathUtil;
|
||||
import static net.sf.openrocket.util.MathUtil.pow2;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class SymmetricComponentVolumeTest extends BaseTestCase {
|
||||
final double EPSILON = MathUtil.EPSILON*1000;
|
||||
|
||||
@Test
|
||||
public void testVolumeSimpleConeFilled() {
|
||||
NoseCone nc = new NoseCone();
|
||||
// helper functions
|
||||
|
||||
final double epsilonPercent = 0.001;
|
||||
final double density = 2.0;
|
||||
// return Coordinate containing CG and volume of (possibly hollow if thickness < outerR) shoulder
|
||||
private Coordinate calculateShoulderCG(double x1, double length, double outerR, double thickness) {
|
||||
final double cg = x1 + length/2.0;
|
||||
|
||||
nc.setLength(1.0);
|
||||
nc.setFilled(true);
|
||||
nc.setShapeType(Transition.Shape.CONICAL);
|
||||
nc.setAftRadius(1.0);
|
||||
nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
|
||||
final double innerR = Math.max(0.0, outerR - thickness);
|
||||
final double volume = Math.PI * length * (pow2(outerR) - pow2(innerR));
|
||||
|
||||
return new Coordinate(cg, 0, 0, volume);
|
||||
}
|
||||
|
||||
// return Coordinate containing CG and volume of frustum
|
||||
// still OK if foreward radius is 0 (ie a cone)
|
||||
private Coordinate calculateFrustumCG(double length, double foreRadius, double aftRadius) {
|
||||
final double moment = Math.PI * pow2(length) * (pow2(foreRadius) + 2.0 * foreRadius * aftRadius + 3.0 * pow2(aftRadius))/12.0;
|
||||
final double volume = Math.PI * length * (pow2(foreRadius) + foreRadius * aftRadius + pow2(aftRadius)) / 3.0;
|
||||
|
||||
return new Coordinate(moment/volume, 0, 0, volume);
|
||||
}
|
||||
|
||||
// return Coordinate containing CG and volume of conical transition
|
||||
private Coordinate calculateConicalTransitionCG(double length, double foreRadius, double aftRadius, double thickness) {
|
||||
// get moment and volume of outer frustum
|
||||
final Coordinate fullCG = calculateFrustumCG(length, foreRadius, aftRadius);
|
||||
|
||||
// project thickness onto yz plane to get height
|
||||
final double angle = Math.atan((aftRadius - foreRadius)/length);
|
||||
final double height = thickness/Math.cos(angle);
|
||||
|
||||
// if aftRadius <= height the transition is filled and we don't need to mess with
|
||||
// the inner frustum
|
||||
if (aftRadius <= height) {
|
||||
return fullCG;
|
||||
}
|
||||
|
||||
double innerLen = length;
|
||||
double innerForeRad = foreRadius - height;
|
||||
final double innerAftRad = aftRadius - height;
|
||||
|
||||
// if forward radius <= height the transition is a cone; we
|
||||
// need to determine its length
|
||||
if (foreRadius < height) {
|
||||
innerLen = length * (aftRadius - height) / (aftRadius - foreRadius);
|
||||
innerForeRad = 0;
|
||||
}
|
||||
|
||||
final Coordinate innerCG = calculateFrustumCG(innerLen, innerForeRad, innerAftRad);
|
||||
|
||||
// subtract inner from outer
|
||||
final double offset = length - innerLen;
|
||||
final double moment = fullCG.x * fullCG.weight - (innerCG.x + offset) * innerCG.weight;
|
||||
final double volume = fullCG.weight - innerCG.weight;
|
||||
|
||||
return new Coordinate(moment/volume, 0, 0, volume);
|
||||
}
|
||||
|
||||
// combine three CGs (typically forward shoulder, transition, and aft shoulder)
|
||||
private Coordinate combineCG(Coordinate cg1, Coordinate cg2, Coordinate cg3) {
|
||||
final double moment1 = cg1.x * cg1.weight;
|
||||
final double moment2 = cg2.x * cg2.weight;
|
||||
final double moment3 = cg3.x * cg3.weight;
|
||||
|
||||
final double volume = cg1.weight + cg2.weight + cg3.weight;
|
||||
return new Coordinate((moment1 + moment2 + moment3) / volume, 0, 0, volume);
|
||||
}
|
||||
|
||||
// check CG, volume, mass
|
||||
private void checkCG(Coordinate expectedCG, Transition nc) {
|
||||
|
||||
Coordinate cg = nc.getCG();
|
||||
double volume = Math.PI / 3.0;
|
||||
double mass = density * volume;
|
||||
assertEquals("CG is incorrect", expectedCG.x, cg.x, EPSILON);
|
||||
assertEquals("Volume is incorrect", expectedCG.weight, nc.getComponentVolume(), EPSILON);
|
||||
|
||||
assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
|
||||
assertEquals(mass, nc.getMass(), epsilonPercent * mass);
|
||||
final double mass = expectedCG.weight * nc.getMaterial().getDensity();
|
||||
assertEquals("Mass is incorrect", mass, nc.getMass(), EPSILON);
|
||||
assertEquals("Mass (stored in cg.weight) is incorrect", mass, cg.weight, EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVolumeSimpleConeFilled() {
|
||||
|
||||
assertEquals(0.75, cg.x, epsilonPercent * 0.75);
|
||||
assertEquals(mass, cg.weight, epsilonPercent * mass);
|
||||
final double length = 1.0;
|
||||
final double aftRadius = 1.0;
|
||||
final double density = 2.0;
|
||||
|
||||
NoseCone nc = new NoseCone();
|
||||
nc.setLength(length);
|
||||
nc.setFilled(true);
|
||||
nc.setShapeType(Transition.Shape.CONICAL);
|
||||
nc.setAftRadius(aftRadius);
|
||||
nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
|
||||
|
||||
Coordinate expectedCG = calculateConicalTransitionCG(length, 0, aftRadius, aftRadius);
|
||||
|
||||
checkCG(expectedCG, nc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVolumeSimpleConeWithShoulderFilled() {
|
||||
NoseCone nc = new NoseCone();
|
||||
|
||||
final double epsilonPercent = 0.001;
|
||||
final double length = 1.0;
|
||||
final double aftRadius = 1.0;
|
||||
final double thickness = 1.0;
|
||||
final double density = 2.0;
|
||||
|
||||
nc.setLength(1.0);
|
||||
|
||||
NoseCone nc = new NoseCone();
|
||||
nc.setLength(length);
|
||||
nc.setFilled(true);
|
||||
nc.setShapeType(Transition.Shape.CONICAL);
|
||||
nc.setAftRadius(1.0);
|
||||
nc.setAftShoulderRadius(1.0);
|
||||
nc.setAftShoulderLength(1.0);
|
||||
nc.setAftShoulderThickness(1.0);
|
||||
nc.setAftRadius(aftRadius);
|
||||
nc.setAftShoulderRadius(aftRadius);
|
||||
nc.setAftShoulderLength(length);
|
||||
nc.setAftShoulderThickness(aftRadius);
|
||||
nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
|
||||
|
||||
Coordinate cg = nc.getCG();
|
||||
final Coordinate coneCG = calculateConicalTransitionCG(length, 0, aftRadius, aftRadius);
|
||||
final Coordinate shoulderCG = calculateShoulderCG(length, length, aftRadius, aftRadius);
|
||||
final Coordinate expectedCG = coneCG.average(shoulderCG);
|
||||
|
||||
double volume = Math.PI / 3.0;
|
||||
volume += Math.PI;
|
||||
double mass = density * volume;
|
||||
|
||||
assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
|
||||
assertEquals(mass, nc.getMass(), epsilonPercent * mass);
|
||||
|
||||
assertEquals(1.312, cg.x, epsilonPercent * 1.071);
|
||||
assertEquals(mass, cg.weight, epsilonPercent * mass);
|
||||
checkCG(expectedCG, nc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVolumeSimpleConeHollow() {
|
||||
NoseCone nc = new NoseCone();
|
||||
|
||||
final double epsilonPercent = 0.001;
|
||||
final double length = 1.0;
|
||||
final double aftRadius = 1.0;
|
||||
final double thickness = 0.5;
|
||||
final double density = 2.0;
|
||||
|
||||
nc.setLength(1.0);
|
||||
nc.setAftRadius(1.0);
|
||||
nc.setThickness(0.5);
|
||||
NoseCone nc = new NoseCone();
|
||||
nc.setLength(length);
|
||||
nc.setAftRadius(aftRadius);
|
||||
nc.setThickness(thickness);
|
||||
nc.setShapeType(Transition.Shape.CONICAL);
|
||||
nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
|
||||
|
||||
Coordinate cg = nc.getCG();
|
||||
Coordinate expectedCG = calculateConicalTransitionCG(length, 0.0, aftRadius, thickness);
|
||||
|
||||
double volume = Math.PI / 3.0; // outer volume
|
||||
|
||||
// manually projected Thickness of 0.5 on to radius to determine
|
||||
// the innerConeDimen. Since the outer cone is "square" (height = radius),
|
||||
// we only need to compute this one dimension in order to compute the
|
||||
// volume of the inner cone.
|
||||
double innerConeDimen = 1.0 - Math.sqrt(2.0) / 2.0;
|
||||
double innerVolume = Math.PI / 3.0 * innerConeDimen * innerConeDimen * innerConeDimen;
|
||||
volume -= innerVolume;
|
||||
|
||||
double mass = density * volume;
|
||||
|
||||
assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
|
||||
assertEquals(mass, nc.getMass(), epsilonPercent * mass);
|
||||
|
||||
assertEquals(0.7454, cg.x, epsilonPercent * 0.7454);
|
||||
assertEquals(mass, cg.weight, epsilonPercent * mass);
|
||||
checkCG(expectedCG, nc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVolumeSimpleConeWithShoulderHollow() {
|
||||
NoseCone nc = new NoseCone();
|
||||
|
||||
final double epsilonPercent = 0.001;
|
||||
final double aftRadius = 1.0;
|
||||
final double length = 1.0;
|
||||
final double thickness = 0.5;
|
||||
final double density = 2.0;
|
||||
|
||||
nc.setLength(1.0);
|
||||
|
||||
NoseCone nc = new NoseCone();
|
||||
nc.setLength(length);
|
||||
nc.setShapeType(Transition.Shape.CONICAL);
|
||||
nc.setAftRadius(1.0);
|
||||
nc.setThickness(0.5);
|
||||
nc.setAftShoulderRadius(1.0);
|
||||
nc.setAftShoulderLength(1.0);
|
||||
nc.setAftShoulderThickness(0.5);
|
||||
nc.setAftRadius(aftRadius);
|
||||
nc.setThickness(thickness);
|
||||
nc.setAftShoulderRadius(aftRadius);
|
||||
nc.setAftShoulderLength(length);
|
||||
nc.setAftShoulderThickness(thickness);
|
||||
nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
|
||||
|
||||
Coordinate cg = nc.getCG();
|
||||
final Coordinate expectedConeCG = calculateConicalTransitionCG(length, 0, aftRadius, thickness);
|
||||
final Coordinate expectedShoulderCG = calculateShoulderCG(length, length, aftRadius, thickness);
|
||||
final Coordinate expectedCG = expectedConeCG.average(expectedShoulderCG);
|
||||
|
||||
double volume = Math.PI / 3.0; // outer volume
|
||||
|
||||
// manually projected Thickness of 0.5 on to radius to determine
|
||||
// the innerConeDimen. Since the outer cone is "square" (height = radius),
|
||||
// we only need to compute this one dimension in order to compute the
|
||||
// volume of the inner cone.
|
||||
double innerConeDimen = 1.0 - Math.sqrt(2.0) / 2.0;
|
||||
double innerVolume = Math.PI / 3.0 * innerConeDimen * innerConeDimen * innerConeDimen;
|
||||
volume -= innerVolume;
|
||||
volume += Math.PI - Math.PI * 0.5 * 0.5;
|
||||
double mass = density * volume;
|
||||
|
||||
assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
|
||||
assertEquals(mass, nc.getMass(), epsilonPercent * mass);
|
||||
|
||||
assertEquals(1.2719, cg.x, epsilonPercent * 1.2719);
|
||||
assertEquals(mass, cg.weight, epsilonPercent * mass);
|
||||
checkCG(expectedCG, nc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVolumeSimpleTransitionFilled() {
|
||||
Transition nc = new Transition();
|
||||
|
||||
final double epsilonPercent = 0.001;
|
||||
final double length = 4.0;
|
||||
final double foreRadius = 1.0;
|
||||
final double aftRadius = 2.0;
|
||||
final double density = 2.0;
|
||||
|
||||
nc.setLength(4.0);
|
||||
|
||||
Transition nc = new Transition();
|
||||
nc.setLength(length);
|
||||
nc.setFilled(true);
|
||||
nc.setShapeType(Transition.Shape.CONICAL);
|
||||
nc.setForeRadius(1.0);
|
||||
nc.setAftRadius(2.0);
|
||||
nc.setForeRadius(foreRadius);
|
||||
nc.setAftRadius(aftRadius);
|
||||
nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
|
||||
|
||||
Coordinate cg = nc.getCG();
|
||||
double volume = Math.PI / 3.0 * (2.0 * 2.0 + 2.0 * 1.0 + 1.0 * 1.0) * 4.0;
|
||||
double mass = density * volume;
|
||||
Coordinate expectedCG = calculateConicalTransitionCG(length, foreRadius, aftRadius, aftRadius);
|
||||
|
||||
assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
|
||||
assertEquals(mass, nc.getMass(), epsilonPercent * mass);
|
||||
|
||||
assertEquals(2.4285, cg.x, epsilonPercent * 2.4285);
|
||||
assertEquals(mass, cg.weight, epsilonPercent * mass);
|
||||
checkCG(expectedCG, nc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVolumeSimpleTransitionWithShouldersFilled() {
|
||||
Transition nc = new Transition();
|
||||
|
||||
final double epsilonPercent = 0.001;
|
||||
final double transLength = 4.0;
|
||||
final double foreRadius = 1.0;
|
||||
final double foreShoulderLength = 1.0;
|
||||
final double aftRadius = 2.0;
|
||||
final double aftShoulderLength = 1.0;
|
||||
final double density = 2.0;
|
||||
|
||||
nc.setLength(4.0);
|
||||
Transition nc = new Transition();
|
||||
nc.setLength(transLength);
|
||||
nc.setFilled(true);
|
||||
nc.setShapeType(Transition.Shape.CONICAL);
|
||||
nc.setForeRadius(1.0);
|
||||
nc.setAftRadius(2.0);
|
||||
nc.setAftShoulderLength(1.0);
|
||||
nc.setAftShoulderRadius(2.0);
|
||||
nc.setAftShoulderThickness(2.0);
|
||||
nc.setForeShoulderLength(1.0);
|
||||
nc.setForeShoulderRadius(1.0);
|
||||
nc.setForeShoulderThickness(1.0);
|
||||
nc.setForeRadius(foreRadius);
|
||||
nc.setAftRadius(aftRadius);
|
||||
nc.setAftShoulderLength(aftShoulderLength);
|
||||
nc.setAftShoulderRadius(aftRadius);
|
||||
nc.setAftShoulderThickness(aftRadius);
|
||||
nc.setForeShoulderLength(foreShoulderLength);
|
||||
nc.setForeShoulderRadius(foreRadius);
|
||||
nc.setForeShoulderThickness(foreRadius);
|
||||
nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
|
||||
|
||||
Coordinate cg = nc.getCG();
|
||||
final Coordinate foreShoulderCG = calculateShoulderCG(-foreShoulderLength, foreShoulderLength, foreRadius, foreRadius);
|
||||
final Coordinate transCG = calculateConicalTransitionCG(transLength, foreRadius, aftRadius, aftRadius);
|
||||
final Coordinate aftShoulderCG = calculateShoulderCG(transLength, aftShoulderLength, aftRadius, aftRadius);
|
||||
final Coordinate expectedCG = combineCG(foreShoulderCG, transCG, aftShoulderCG);
|
||||
|
||||
double volume = Math.PI / 3.0 * (2.0 * 2.0 + 2.0 * 1.0 + 1.0 * 1.0) * 4.0;
|
||||
// plus aft shoulder:
|
||||
volume += Math.PI * 1.0 * 2.0 * 2.0;
|
||||
// plus fore shoulder:
|
||||
volume += Math.PI * 1.0 * 1.0 * 1.0;
|
||||
double mass = density * volume;
|
||||
|
||||
assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
|
||||
assertEquals(mass, nc.getMass(), epsilonPercent * mass);
|
||||
|
||||
assertEquals(2.8023, cg.x, epsilonPercent * 2.8023);
|
||||
assertEquals(mass, cg.weight, epsilonPercent * mass);
|
||||
checkCG(expectedCG, nc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVolumeSimpleTransitionHollow1() {
|
||||
Transition nc = new Transition();
|
||||
|
||||
final double epsilonPercent = 0.001;
|
||||
final double length = 1.0;
|
||||
final double foreRadius = 0.5;
|
||||
final double aftRadius = 0.5;
|
||||
final double thickness = 0.5;
|
||||
final double density = 2.0;
|
||||
|
||||
nc.setLength(1.0);
|
||||
Transition nc = new Transition();
|
||||
nc.setLength(length);
|
||||
nc.setShapeType(Transition.Shape.CONICAL);
|
||||
nc.setForeRadius(0.5);
|
||||
nc.setAftRadius(1.0);
|
||||
nc.setThickness(0.5);
|
||||
nc.setForeRadius(foreRadius);
|
||||
nc.setAftRadius(aftRadius);
|
||||
nc.setThickness(thickness);
|
||||
nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
|
||||
|
||||
final Coordinate expectedCG = calculateConicalTransitionCG(length, foreRadius, aftRadius, thickness);
|
||||
|
||||
Coordinate cg = nc.getCG();
|
||||
|
||||
// Volume of filled transition =
|
||||
double filledVolume = Math.PI / 3.0 * (1.0 * 1.0 + 1.0 * 0.5 + 0.5 * 0.5) * 1.0;
|
||||
|
||||
// magic 2D cad drawing...
|
||||
//
|
||||
// Since the thickness >= fore radius, the
|
||||
// hollowed out portion of the transition
|
||||
// forms a cone.
|
||||
// the dimensions of this cone were determined
|
||||
// using a 2d cad tool.
|
||||
|
||||
double innerConeRadius = 0.441;
|
||||
double innerConeLength = 0.882;
|
||||
double innerVolume = Math.PI / 3.0 * innerConeLength * innerConeRadius * innerConeRadius;
|
||||
double volume = filledVolume - innerVolume;
|
||||
double mass = density * volume;
|
||||
|
||||
assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
|
||||
assertEquals(mass, nc.getMass(), epsilonPercent * mass);
|
||||
|
||||
assertEquals(0.5884, cg.x, epsilonPercent * 0.5884);
|
||||
assertEquals(mass, cg.weight, epsilonPercent * mass);
|
||||
checkCG(expectedCG, nc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVolumeSimpleTransitionWithShouldersHollow1() {
|
||||
Transition nc = new Transition();
|
||||
|
||||
final double epsilonPercent = 0.001;
|
||||
final double length = 1.0; // length of transition itself and each shoulder
|
||||
final double foreRadius = 0.5;
|
||||
final double aftRadius = 1.0;
|
||||
final double thickness = 0.5;
|
||||
final double density = 2.0;
|
||||
|
||||
nc.setLength(1.0);
|
||||
|
||||
Transition nc = new Transition();
|
||||
nc.setLength(length);
|
||||
nc.setShapeType(Transition.Shape.CONICAL);
|
||||
nc.setForeRadius(0.5);
|
||||
nc.setAftRadius(1.0);
|
||||
nc.setThickness(0.5);
|
||||
nc.setAftShoulderLength(1.0);
|
||||
nc.setAftShoulderRadius(1.0);
|
||||
nc.setAftShoulderThickness(0.5);
|
||||
nc.setForeShoulderLength(1.0);
|
||||
nc.setForeShoulderRadius(0.5);
|
||||
nc.setForeShoulderThickness(0.5); // note this means fore shoulder is filled.
|
||||
nc.setForeRadius(foreRadius);
|
||||
nc.setAftRadius(aftRadius);
|
||||
nc.setThickness(thickness);
|
||||
nc.setAftShoulderLength(length);
|
||||
nc.setAftShoulderRadius(aftRadius);
|
||||
nc.setAftShoulderThickness(thickness);
|
||||
nc.setForeShoulderLength(length);
|
||||
nc.setForeShoulderRadius(foreRadius);
|
||||
nc.setForeShoulderThickness(thickness); // note this means fore shoulder is filled.
|
||||
nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
|
||||
|
||||
Coordinate cg = nc.getCG();
|
||||
final Coordinate foreShoulderCG = calculateShoulderCG(-length, length, foreRadius, thickness);
|
||||
final Coordinate transitionCG = calculateConicalTransitionCG(length, foreRadius, aftRadius, thickness);
|
||||
final Coordinate aftShoulderCG = calculateShoulderCG(length, length, aftRadius, thickness);
|
||||
final Coordinate expectedCG = combineCG(foreShoulderCG, transitionCG, aftShoulderCG);
|
||||
|
||||
// Volume of filled transition =
|
||||
double filledVolume = Math.PI / 3.0 * (1.0 * 1.0 + 1.0 * 0.5 + 0.5 * 0.5) * 1.0;
|
||||
|
||||
// magic 2D cad drawing...
|
||||
//
|
||||
// Since the thickness >= fore radius, the
|
||||
// hollowed out portion of the transition
|
||||
// forms a cone.
|
||||
// the dimensions of this cone were determined
|
||||
// using a 2d cad tool.
|
||||
|
||||
double innerConeRadius = 0.441;
|
||||
double innerConeLength = 0.882;
|
||||
double innerVolume = Math.PI / 3.0 * innerConeLength * innerConeRadius * innerConeRadius;
|
||||
|
||||
double volume = filledVolume - innerVolume;
|
||||
|
||||
// Now add aft shoulder
|
||||
volume += Math.PI * 1.0 * 1.0 * 1.0 - Math.PI * 1.0 * 0.5 * 0.5;
|
||||
// Now add fore shoulder
|
||||
volume += Math.PI * 1.0 * 0.5 * 0.5;
|
||||
|
||||
double mass = density * volume;
|
||||
|
||||
assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
|
||||
assertEquals(mass, nc.getMass(), epsilonPercent * mass);
|
||||
|
||||
assertEquals(0.8581, cg.x, epsilonPercent * 0.8581);
|
||||
assertEquals(mass, cg.weight, epsilonPercent * mass);
|
||||
checkCG(expectedCG, nc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVolumeSimpleTransitionHollow2() {
|
||||
Transition nc = new Transition();
|
||||
|
||||
final double epsilonPercent = 0.001;
|
||||
final double length = 1.0; // length of transition itself and each shoulder
|
||||
final double foreRadius = 0.5;
|
||||
final double aftRadius = 1.0;
|
||||
final double thickness = 0.25;
|
||||
final double density = 2.0;
|
||||
|
||||
nc.setLength(1.0);
|
||||
Transition nc = new Transition();
|
||||
nc.setLength(length);
|
||||
nc.setShapeType(Transition.Shape.CONICAL);
|
||||
nc.setForeRadius(0.5);
|
||||
nc.setAftRadius(1.0);
|
||||
nc.setThickness(0.25);
|
||||
nc.setForeRadius(foreRadius);
|
||||
nc.setAftRadius(aftRadius);
|
||||
nc.setThickness(thickness);
|
||||
nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
|
||||
|
||||
Coordinate cg = nc.getCG();
|
||||
final Coordinate expectedCG = calculateConicalTransitionCG(length, foreRadius, aftRadius, thickness);
|
||||
|
||||
// Volume of filled transition =
|
||||
double filledVolume = Math.PI / 3.0 * (1.0 * 1.0 + 1.0 * 0.5 + 0.5 * 0.5) * 1.0;
|
||||
|
||||
// magic 2D cad drawing...
|
||||
//
|
||||
// Since the thickness < fore radius, the
|
||||
// hollowed out portion of the transition
|
||||
// forms a transition.
|
||||
// the dimensions of this transition were determined
|
||||
// using a 2d cad tool.
|
||||
|
||||
double innerTransitionAftRadius = 0.7205;
|
||||
double innerTransitionForeRadius = 0.2205;
|
||||
double innerVolume = Math.PI / 3.0
|
||||
* (innerTransitionAftRadius * innerTransitionAftRadius + innerTransitionAftRadius * innerTransitionForeRadius + innerTransitionForeRadius * innerTransitionForeRadius);
|
||||
|
||||
double volume = filledVolume - innerVolume;
|
||||
double mass = density * volume;
|
||||
|
||||
assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
|
||||
assertEquals(mass, nc.getMass(), epsilonPercent * mass);
|
||||
|
||||
assertEquals(0.56827, cg.x, epsilonPercent * 0.56827);
|
||||
assertEquals(mass, cg.weight, epsilonPercent * mass);
|
||||
checkCG(expectedCG, nc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVolumeSimpleTransitionWithShouldersHollow2() {
|
||||
Transition nc = new Transition();
|
||||
|
||||
final double epsilonPercent = 0.001;
|
||||
final double length = 1.0;
|
||||
final double foreRadius = 0.5;
|
||||
final double aftRadius = 1.0;
|
||||
final double thickness = 0.25;
|
||||
final double density = 2.0;
|
||||
|
||||
nc.setLength(1.0);
|
||||
Transition nc = new Transition();
|
||||
nc.setLength(length);
|
||||
nc.setShapeType(Transition.Shape.CONICAL);
|
||||
nc.setForeRadius(0.5);
|
||||
nc.setAftRadius(1.0);
|
||||
nc.setThickness(0.25);
|
||||
nc.setAftShoulderLength(1.0);
|
||||
nc.setAftShoulderRadius(1.0);
|
||||
nc.setAftShoulderThickness(0.25);
|
||||
nc.setForeShoulderLength(1.0);
|
||||
nc.setForeShoulderRadius(0.5);
|
||||
nc.setForeShoulderThickness(0.25);
|
||||
|
||||
nc.setForeRadius(foreRadius);
|
||||
nc.setAftRadius(aftRadius);
|
||||
nc.setThickness(thickness);
|
||||
nc.setAftShoulderLength(length);
|
||||
nc.setAftShoulderRadius(aftRadius);
|
||||
nc.setAftShoulderThickness(thickness);
|
||||
nc.setForeShoulderLength(length);
|
||||
nc.setForeShoulderRadius(foreRadius);
|
||||
nc.setForeShoulderThickness(thickness);
|
||||
nc.setMaterial(Material.newMaterial(Material.Type.BULK, "test", density, true));
|
||||
|
||||
Coordinate cg = nc.getCG();
|
||||
final Coordinate foreShoulderCG = calculateShoulderCG(-length, length, foreRadius, thickness);
|
||||
final Coordinate transitionCG = calculateConicalTransitionCG(length, foreRadius, aftRadius, thickness);
|
||||
final Coordinate aftShoulderCG = calculateShoulderCG(length, length, aftRadius, thickness);
|
||||
final Coordinate expectedCG = combineCG(foreShoulderCG, transitionCG, aftShoulderCG);
|
||||
|
||||
// Volume of filled transition =
|
||||
double filledVolume = Math.PI / 3.0 * (1.0 * 1.0 + 1.0 * 0.5 + 0.5 * 0.5) * 1.0;
|
||||
|
||||
// magic 2D cad drawing...
|
||||
//
|
||||
// Since the thickness < fore radius, the
|
||||
// hollowed out portion of the transition
|
||||
// forms a transition.
|
||||
// the dimensions of this transition were determined
|
||||
// using a 2d cad tool.
|
||||
|
||||
double innerTransitionAftRadius = 0.7205;
|
||||
double innerTransitionForeRadius = 0.2205;
|
||||
double innerVolume = Math.PI / 3.0
|
||||
* (innerTransitionAftRadius * innerTransitionAftRadius + innerTransitionAftRadius * innerTransitionForeRadius + innerTransitionForeRadius * innerTransitionForeRadius);
|
||||
|
||||
double volume = filledVolume - innerVolume;
|
||||
|
||||
// now add aft shoulder
|
||||
volume += Math.PI * 1.0 * 1.0 * 1.0 - Math.PI * 1.0 * 0.75 * 0.75;
|
||||
// now add fore shoulder
|
||||
volume += Math.PI * 1.0 * 0.5 * 0.5 - Math.PI * 1.0 * 0.25 * 0.25;
|
||||
|
||||
|
||||
double mass = density * volume;
|
||||
|
||||
assertEquals(volume, nc.getComponentVolume(), epsilonPercent * volume);
|
||||
assertEquals(mass, nc.getMass(), epsilonPercent * mass);
|
||||
|
||||
assertEquals(0.7829, cg.x, epsilonPercent * 0.7829);
|
||||
assertEquals(mass, cg.weight, epsilonPercent * mass);
|
||||
checkCG(expectedCG, nc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransitionVsTubeFilled() {
|
||||
// BodyTubes use closed form solutions for mass properties, while Transitions use
|
||||
// numerical integration from SymmetricComponent. Properties should agree.
|
||||
final double radius = 1.0;
|
||||
final double length = 10.0;
|
||||
|
||||
BodyTube bt1 = new BodyTube(length, radius, true);
|
||||
|
||||
Transition trans1 = new Transition();
|
||||
trans1.setFilled(true);
|
||||
trans1.setLength(length);
|
||||
trans1.setForeRadius(radius, true);
|
||||
trans1.setAftRadius(radius, true);
|
||||
trans1.setShapeType(Transition.Shape.CONICAL);
|
||||
|
||||
assertEquals("Length is incorrect", bt1.getLength(), trans1.getLength(), EPSILON);
|
||||
assertEquals("Forward radius is incorrect", bt1.getRadius(0), trans1.getRadius(0), EPSILON);
|
||||
assertEquals("Aft radius is incorrect", bt1.getRadius(bt1.getLength()), trans1.getRadius(trans1.getLength()), EPSILON);
|
||||
assertEquals("Volume is incorrect", bt1.getComponentVolume(), trans1.getComponentVolume(), EPSILON);
|
||||
assertEquals("CG is incorrect", bt1.getComponentCG().x, trans1.getComponentCG().x, EPSILON);
|
||||
assertEquals("Longitudinal moment of inertia is incorrect", bt1.getLongitudinalUnitInertia(), trans1.getLongitudinalUnitInertia(), EPSILON);
|
||||
assertEquals("Rotational moment of inertia is incorrect", bt1.getRotationalUnitInertia(), trans1.getRotationalUnitInertia(), EPSILON);
|
||||
assertEquals("Wetted area is incorrect", bt1.getComponentWetArea(), trans1.getComponentWetArea(), EPSILON);
|
||||
assertEquals("Planform area is incorrect", bt1.getComponentPlanformArea(), trans1.getComponentPlanformArea(), EPSILON);
|
||||
assertEquals("Planform centroid is incorrect", bt1.getComponentPlanformCenter(), trans1.getComponentPlanformCenter(), EPSILON);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransitionVsTubeHollow() {
|
||||
final double radius = 1.0;
|
||||
final double innerRadius = 0.1;
|
||||
final double length = 10.0;
|
||||
|
||||
BodyTube bt2 = new BodyTube(length, radius, false);
|
||||
bt2.setInnerRadius(innerRadius);
|
||||
|
||||
Transition trans2 = new Transition();
|
||||
trans2.setFilled(false);
|
||||
trans2.setLength(length);
|
||||
trans2.setForeRadius(radius, true);
|
||||
trans2.setAftRadius(radius, true);
|
||||
trans2.setShapeType(Transition.Shape.CONICAL);
|
||||
trans2.setThickness(radius - innerRadius, true);
|
||||
|
||||
assertEquals("Volume is incorrect", bt2.getComponentVolume(), trans2.getComponentVolume(), EPSILON);
|
||||
assertEquals("CG is incorrect", bt2.getComponentCG().x, trans2.getComponentCG().x, EPSILON);
|
||||
assertEquals("Longitudinal unit moment of inertia is incorrect", bt2.getLongitudinalUnitInertia(), trans2.getLongitudinalUnitInertia(), EPSILON);
|
||||
assertEquals("Rotational unit moment of inertia is incorrect", bt2.getRotationalUnitInertia(), trans2.getRotationalUnitInertia(), EPSILON);
|
||||
assertEquals("Wetted area is incorrect", bt2.getComponentWetArea(), trans2.getComponentWetArea(), EPSILON);
|
||||
assertEquals("Planform area is incorrect", bt2.getComponentPlanformArea(), trans2.getComponentPlanformArea(), EPSILON);
|
||||
assertEquals("Planform centroid is incorrect", bt2.getComponentPlanformCenter(), trans2.getComponentPlanformCenter(), EPSILON);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user