Merge pull request #505 from teyrana/fix_500_cg

[fixes #500] May Calculate CG for fins on zero-dimension mounts
This commit is contained in:
Wes Cravens 2019-01-02 16:52:26 -06:00 committed by GitHub
commit c5733b9b68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 143 additions and 80 deletions

View File

@ -412,8 +412,12 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab
- outerArcAngle * filletRadius * filletRadius / 2 - outerArcAngle * filletRadius * filletRadius / 2
- innerArcAngle * bodyRadius * bodyRadius / 2); - innerArcAngle * bodyRadius * bodyRadius / 2);
// each fin has a fillet on each side if(Double.isNaN(crossSectionArea)) {
crossSectionArea *= 2; crossSectionArea = 0.;
}else {
// each fin has a fillet on each side
crossSectionArea *= 2;
}
// heuristic, relTo the body center // heuristic, relTo the body center
double yCentroid = bodyRadius + filletRadius /5; double yCentroid = bodyRadius + filletRadius /5;
@ -435,28 +439,26 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab
* 5. Return twice that since there is a fillet on each side of the fin. * 5. Return twice that since there is a fillet on each side of the fin.
*/ */
protected Coordinate calculateFilletVolumeCentroid() { protected Coordinate calculateFilletVolumeCentroid() {
Coordinate[] bodyPoints = this.getBodyPoints(); Coordinate[] mountPoints = this.getRootPoints();
if (0 == bodyPoints.length) { if( null == mountPoints ){
return Coordinate.ZERO; return Coordinate.ZERO;
} }
final SymmetricComponent sym = (SymmetricComponent) this.parent; final SymmetricComponent sym = (SymmetricComponent) this.parent;
if (!SymmetricComponent.class.isInstance(this.parent)) { if (!SymmetricComponent.class.isInstance(this.parent)) {
return Coordinate.ZERO; return Coordinate.ZERO;
} }
Coordinate filletVolumeCentroid = Coordinate.ZERO; Coordinate filletVolumeCentroid = Coordinate.ZERO;
Coordinate prev = mountPoints[0];
for (int index = 1; index < mountPoints.length; index++) {
Coordinate prev = bodyPoints[0]; final Coordinate cur = mountPoints[index];
for (int index = 1; index < bodyPoints.length; index++) {
final Coordinate cur = bodyPoints[index];
// cross section at mid-segment // cross section at mid-segment
final double xAvg = (prev.x + cur.x) / 2; final double xAvg = (prev.x + cur.x) / 2;
final double bodyRadius = sym.getRadius(xAvg); final double bodyRadius = sym.getRadius(xAvg);
final Coordinate segmentCrossSection = calculateFilletCrossSection(this.filletRadius, bodyRadius).setX(xAvg); final Coordinate segmentCrossSection = calculateFilletCrossSection(this.filletRadius, bodyRadius).setX(xAvg);
// final double xCentroid = xAvg; // final double xCentroid = xAvg;
// final double yCentroid = segmentCrossSection.y; ///< heuristic, not exact // final double yCentroid = segmentCrossSection.y; ///< heuristic, not exact
final double segmentLength = Point2D.Double.distance(prev.x, prev.y, cur.x, cur.y); final double segmentLength = Point2D.Double.distance(prev.x, prev.y, cur.x, cur.y);
@ -468,10 +470,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab
prev = cur; prev = cur;
} }
// translate to be relative to the fin-lead-root
filletVolumeCentroid = filletVolumeCentroid.sub(getAxialFront(), 0,0);
if (finCount == 1) { if (finCount == 1) {
Transformation rotation = Transformation.rotate_x( getAngleOffset()); Transformation rotation = Transformation.rotate_x( getAngleOffset());
return rotation.transform(filletVolumeCentroid); return rotation.transform(filletVolumeCentroid);
@ -541,7 +540,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab
final double xTabTrail_body = xFinFront_body + xTabTrail_fin; final double xTabTrail_body = xFinFront_body + xTabTrail_fin;
// always returns x coordinates relTo fin front: // always returns x coordinates relTo fin front:
Coordinate[] upperCurve = getBodyPoints( xTabFront_body, xTabTrail_body ); Coordinate[] upperCurve = getMountInterval( xTabFront_body, xTabTrail_body );
// locate relative to fin/body centerline // locate relative to fin/body centerline
upperCurve = translatePoints( upperCurve, -xFinFront_body, 0.0); upperCurve = translatePoints( upperCurve, -xFinFront_body, 0.0);
@ -572,15 +571,15 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab
*/ */
private Coordinate calculateSinglePlanformCentroid(){ private Coordinate calculateSinglePlanformCentroid(){
final Coordinate finFront = getFinFront(); final Coordinate finFront = getFinFront();
final Coordinate[] upperCurve = translatePoints( getFinPoints(), finFront.x, finFront.y ); final Coordinate[] upperCurve = getFinPoints();
final Coordinate[] lowerCurve = getBodyPoints(); final Coordinate[] lowerCurve = getRootPoints();
final Coordinate[] totalCurve = combineCurves( upperCurve, lowerCurve); final Coordinate[] totalCurve = combineCurves( upperCurve, lowerCurve);
Coordinate planformCentroid = calculateCurveIntegral( totalCurve ); Coordinate planformCentroid = calculateCurveIntegral( totalCurve );
// return as a position relative to fin-root // return as a position relative to fin-root
return planformCentroid.sub(finFront.x,0,0); return planformCentroid.add(0., finFront.y, 0);
} }
/** /**
@ -1042,11 +1041,12 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab
* *
* @return points representing the fin-root points, relative to ( x: fin-front, y: centerline ) i.e. relto: fin Component reference point * @return points representing the fin-root points, relative to ( x: fin-front, y: centerline ) i.e. relto: fin Component reference point
*/ */
public Coordinate[] getBodyPoints() { public Coordinate[] getMountPoints() {
final double xFinStart = getAxialFront(); if( null == parent){
final double xFinEnd = xFinStart+getLength(); return null;
}
return getBodyPoints( xFinStart, xFinEnd);
return getMountInterval(0., parent.getLength());
} }
/** /**
@ -1055,19 +1055,22 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab
* @return points representing the fin-root points, relative to ( x: fin-front, y: fin-root-radius ) * @return points representing the fin-root points, relative to ( x: fin-front, y: fin-root-radius )
*/ */
public Coordinate[] getRootPoints(){ public Coordinate[] getRootPoints(){
final Coordinate finLead = getFinFront();
final double finTailX = finLead.x + getLength();
final Coordinate[] bodyPoints = getBodyPoints( finLead.x, finTailX);
return translatePoints(bodyPoints, -finLead.x, -finLead.y);
}
private Coordinate[] getBodyPoints( final double xStart, final double xEnd ) {
if( null == parent){ if( null == parent){
return new Coordinate[]{Coordinate.ZERO}; return new Coordinate[]{Coordinate.ZERO};
} }
final Coordinate finLead = getFinFront();
final double finTailX = finLead.x + getLength();
final Coordinate[] bodyPoints = getMountInterval( finLead.x, finTailX);
return translatePoints(bodyPoints, -finLead.x, -finLead.y);
}
private Coordinate[] getMountInterval( final double xStart, final double xEnd ) {
// System.err.println(String.format(" .... >> mount interval/x: ( %g, %g)]", xStart, xEnd));
// for a simple bodies, one increment is perfectly accurate. // for a simple bodies, one increment is perfectly accurate.
int divisionCount = 1; int divisionCount = 1;
// cast-assert // cast-assert
@ -1101,7 +1104,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab
if( body.getLength()-0.000001 < points[lastIndex].x) { if( body.getLength()-0.000001 < points[lastIndex].x) {
points[lastIndex] = points[lastIndex].setX(body.getLength()).setY(body.getAftRadius()); points[lastIndex] = points[lastIndex].setX(body.getLength()).setY(body.getAftRadius());
} }
return points; return points;
} }
@ -1125,7 +1128,8 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab
buf.append( getPointDescr( this.getFinPoints(), "Fin Points", "")); buf.append( getPointDescr( this.getFinPoints(), "Fin Points", ""));
if (null != parent) { if (null != parent) {
buf.append( getPointDescr( this.getBodyPoints(), "Body Points", "")); buf.append( getPointDescr( this.getRootPoints(), "Root Points", ""));
buf.append( getPointDescr( this.getMountPoints(), "Mount Points", ""));
} }
if( ! this.isTabTrivial() ) { if( ! this.isTabTrivial() ) {
@ -1146,7 +1150,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab
final double tabVolume = tabCentroid.weight * thickness; final double tabVolume = tabCentroid.weight * thickness;
final double tabMass = tabVolume * material.getDensity(); final double tabMass = tabVolume * material.getDensity();
final Coordinate tabCM = tabCentroid.setWeight(tabMass); final Coordinate tabCM = tabCentroid.setWeight(tabMass);
Coordinate filletCentroid = calculateFilletVolumeCentroid(); Coordinate filletCentroid = calculateFilletVolumeCentroid();
double filletVolume = filletCentroid.weight; double filletVolume = filletCentroid.weight;
double filletMass = filletVolume * filletMaterial.getDensity(); double filletMass = filletVolume * filletMaterial.getDensity();
@ -1156,7 +1160,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab
final double eachFinMass = finBulkMass + tabMass + filletMass; final double eachFinMass = finBulkMass + tabMass + filletMass;
final Coordinate eachFinCenterOfMass = wettedCM.average(tabCM).average(filletCM).setWeight(eachFinMass); final Coordinate eachFinCenterOfMass = wettedCM.average(tabCM).average(filletCM).setWeight(eachFinMass);
// ^^ per fin // ^^ per fin
// vv per component // vv per component

View File

@ -1179,13 +1179,13 @@ public class FreeformFinSetTest extends BaseTestCase {
final Coordinate[] finPointsFromBody = FinSet.translatePoints( finPoints, 0.0, fins.getFinFront().y); final Coordinate[] finPointsFromBody = FinSet.translatePoints( finPoints, 0.0, fins.getFinFront().y);
{ // body points (relative to body) { // body points (relative to body)
final Coordinate[] bodyPoints = fins.getBodyPoints(); final Coordinate[] mountPoints = fins.getMountPoints();
assertEquals("Method should only generate minimal points for a conical transition fin body! ", 2, bodyPoints.length ); assertEquals("Method should only generate minimal points for a conical transition fin body! ", 2, mountPoints.length );
assertEquals("incorrect body points! ", finPointsFromBody[0].x, bodyPoints[0].x, EPSILON); assertEquals("incorrect body points! ", finPointsFromBody[0].x, mountPoints[0].x, EPSILON);
assertEquals("incorrect body points! ", finPointsFromBody[0].y, bodyPoints[0].y, EPSILON); assertEquals("incorrect body points! ", finPointsFromBody[0].y, mountPoints[0].y, EPSILON);
assertEquals("incorrect body points! ", finPointsFromBody[finPoints.length-1].x, bodyPoints[1].x, EPSILON); assertEquals("incorrect body points! ", finPointsFromBody[finPoints.length-1].x, mountPoints[1].x, EPSILON);
assertEquals("incorrect body points! ", finPointsFromBody[finPoints.length-1].y, bodyPoints[1].y, EPSILON); assertEquals("incorrect body points! ", finPointsFromBody[finPoints.length-1].y, mountPoints[1].y, EPSILON);
} }
{ // root points (relative to fin-front) { // root points (relative to fin-front)
final Coordinate[] rootPoints = fins.getRootPoints(); final Coordinate[] rootPoints = fins.getRootPoints();
@ -1203,18 +1203,16 @@ public class FreeformFinSetTest extends BaseTestCase {
final Rocket rkt = createTemplateRocket(); final Rocket rkt = createTemplateRocket();
final FreeformFinSet fins = (FreeformFinSet) rkt.getChild(0).getChild(2).getChild(0); final FreeformFinSet fins = (FreeformFinSet) rkt.getChild(0).getChild(2).getChild(0);
final Coordinate finFront = fins.getFinFront();
final Coordinate[] finPoints = fins.getFinPoints(); final Coordinate[] finPoints = fins.getFinPoints();
final Coordinate[] finPointsFromBody = FinSet.translatePoints( finPoints, finFront.x, finFront.y);
{ // body points (relative to body) { // body points (relative to body)
final Coordinate[] bodyPoints = fins.getBodyPoints(); final Coordinate[] bodyPoints = fins.getMountPoints();
assertEquals("Method should only generate minimal points for a conical transition fin body! ", 2, bodyPoints.length ); assertEquals("Method should only generate minimal points for a conical transition fin body! ", 2, bodyPoints.length );
assertEquals("incorrect body points! ", finPointsFromBody[0].x, bodyPoints[0].x, EPSILON); assertEquals("incorrect body points! ", 0.0, bodyPoints[0].x, EPSILON);
assertEquals("incorrect body points! ", finPointsFromBody[0].y, bodyPoints[0].y, EPSILON); assertEquals("incorrect body points! ", 1.0, bodyPoints[0].y, EPSILON);
assertEquals("incorrect body points! ", finPointsFromBody[finPoints.length-1].x, bodyPoints[1].x, EPSILON); assertEquals("incorrect body points! ", 1.0, bodyPoints[1].x, EPSILON);
assertEquals("incorrect body points! ", finPointsFromBody[finPoints.length-1].y, bodyPoints[1].y, EPSILON); assertEquals("incorrect body points! ", 0.5, bodyPoints[1].y, EPSILON);
} }
{ // body points (relative to root) { // body points (relative to root)
final Coordinate[] rootPoints = fins.getRootPoints(); final Coordinate[] rootPoints = fins.getRootPoints();
@ -1236,7 +1234,6 @@ public class FreeformFinSetTest extends BaseTestCase {
final Coordinate finFront = fins.getFinFront(); final Coordinate finFront = fins.getFinFront();
final Coordinate[] finPoints = fins.getFinPoints(); final Coordinate[] finPoints = fins.getFinPoints();
{ // fin points (relative to fin) // preconditions { // fin points (relative to fin) // preconditions
assertEquals(4, finPoints.length); assertEquals(4, finPoints.length);
@ -1281,35 +1278,30 @@ public class FreeformFinSetTest extends BaseTestCase {
} }
} }
}{ // body points (relative to body) }{ // body points (relative to body)
// translate from fin-frame to body-frame final Coordinate[] mountPoints = fins.getMountPoints();
final Coordinate[] finPointsFromBody = FinSet.translatePoints( fins.getFinPoints(), finFront.x, finFront.y ); assertEquals(101, mountPoints.length);
final Coordinate[] bodyPoints = fins.getBodyPoints();
assertEquals(101, bodyPoints.length);
final Coordinate expectedEndPoint = finPointsFromBody[ finPoints.length-1];
// trivial, and uninteresting: // trivial, and uninteresting:
assertEquals("incorrect body points! ", finPointsFromBody[0].x, bodyPoints[0].x, EPSILON); assertEquals("incorrect body points! ", 0.0, mountPoints[0].x, EPSILON);
assertEquals("incorrect body points! ", finPointsFromBody[0].y, bodyPoints[0].y, EPSILON); assertEquals("incorrect body points! ", 0.0, mountPoints[0].y, EPSILON);
// n.b.: This should match EXACTLY the end point of the fin. (in fin coordinates) // n.b.: This should match EXACTLY the end point of the fin. (in fin coordinates)
assertEquals("incorrect body points! ", expectedEndPoint.x, bodyPoints[bodyPoints.length-1].x, EPSILON); assertEquals("incorrect body points! ", 1.0, mountPoints[mountPoints.length-1].x, EPSILON);
assertEquals("incorrect body points! ", expectedEndPoint.y, bodyPoints[bodyPoints.length-1].y, EPSILON); assertEquals("incorrect body points! ", 1.0, mountPoints[mountPoints.length-1].y, EPSILON);
{// the tests within this scope is are rather fragile, and may break for reasons other than bugs :( {// the tests within this scope is are rather fragile, and may break for reasons other than bugs :(
// the number of points is somewhat arbitrary, but if this test fails, the rest *definitely* will. // the number of points is somewhat arbitrary, but if this test fails, the rest *definitely* will.
assertEquals("Method is generating how many points, in general? ", 101, bodyPoints.length ); assertEquals("Method is generating how many points, in general? ", 101, mountPoints.length );
final int[] testIndices = { 2, 5, 61, 88};
final double[] expectedX = { 0.036, 0.06, 0.508, 0.724};
final int[] testIndices = { 3, 12, 61, 88};
final double[] expectedX = { 0.03, 0.12, 0.61, 0.88};
for( int testCase = 0; testCase < testIndices.length; testCase++){ for( int testCase = 0; testCase < testIndices.length; testCase++){
final int testIndex = testIndices[testCase]; final int testIndex = testIndices[testCase];
assertEquals(String.format("Body points @ %d :: x coordinate mismatch!", testIndex), assertEquals(String.format("Body points @ %d :: x coordinate mismatch!", testIndex),
expectedX[testCase], bodyPoints[testIndex].x, EPSILON); expectedX[testCase], mountPoints[testIndex].x, EPSILON);
assertEquals(String.format("Body points @ %d :: y coordinate mismatch!", testIndex), assertEquals(String.format("Body points @ %d :: y coordinate mismatch!", testIndex),
body.getRadius(bodyPoints[testIndex].x), bodyPoints[testIndex].y, EPSILON); body.getRadius(mountPoints[testIndex].x), mountPoints[testIndex].y, EPSILON);
} }
} }
} }

View File

@ -90,7 +90,7 @@ public class TrapezoidFinSetTest extends BaseTestCase {
final Rocket rkt = createSimpleTrapezoidalFin(); final Rocket rkt = createSimpleTrapezoidalFin();
final TrapezoidFinSet fins = (TrapezoidFinSet)rkt.getChild(0).getChild(0).getChild(0); final TrapezoidFinSet fins = (TrapezoidFinSet)rkt.getChild(0).getChild(0).getChild(0);
// This is a simple square fin with sides of 1.0. // This is a simple square fin with sides of 0.1.
fins.setFinShape(0.1, 0.1, 0.0, 0.1, .005); fins.setFinShape(0.1, 0.1, 0.0, 0.1, .005);
// should return a single-fin-planform area // should return a single-fin-planform area
@ -170,14 +170,14 @@ public class TrapezoidFinSetTest extends BaseTestCase {
// / \ // / \
// [0] +--------+ [3] // [0] +--------+ [3]
// //
assertEquals(0.06, fins.getLength(), EPSILON);
assertEquals("Body radius doesn't match: ", 0.1, body.getOuterRadius(), EPSILON); assertEquals("Body radius doesn't match: ", 0.1, body.getOuterRadius(), EPSILON);
final Coordinate actVolume = fins.calculateFilletVolumeCentroid(); final Coordinate actVolume = fins.calculateFilletVolumeCentroid();
assertEquals("Line volume doesn't match: ", 5.973e-07, actVolume.weight, EPSILON); assertEquals("Fin volume doesn't match: ", 5.973e-07, actVolume.weight, EPSILON);
assertEquals("Fin mass center.x doesn't match: ", 0.03, actVolume.x, EPSILON);
assertEquals("Line mass center.x doesn't match: ", 0.03, actVolume.x, EPSILON); assertEquals("Fin mass center.y doesn't match: ", 0.101, actVolume.y, EPSILON);
assertEquals("Line mass center.y doesn't match: ", 0.101, actVolume.y, EPSILON);
{ // and then, check that the fillet volume feeds into a correct overall CG: { // and then, check that the fillet volume feeds into a correct overall CG:
@ -189,7 +189,6 @@ public class TrapezoidFinSetTest extends BaseTestCase {
@Test @Test
public void testTrapezoidCGComputation() { public void testTrapezoidCGComputation() {
{ {
// This is a simple square fin with sides of 1.0. // This is a simple square fin with sides of 1.0.
TrapezoidFinSet fins = new TrapezoidFinSet(); TrapezoidFinSet fins = new TrapezoidFinSet();
@ -200,9 +199,7 @@ public class TrapezoidFinSetTest extends BaseTestCase {
assertEquals(1.0, fins.getPlanformArea(), 0.001); assertEquals(1.0, fins.getPlanformArea(), 0.001);
assertEquals(0.5, coords.x, 0.001); assertEquals(0.5, coords.x, 0.001);
assertEquals(0.5, coords.y, 0.001); assertEquals(0.5, coords.y, 0.001);
} }{
{
// This is a trapezoid. Height 1, root 1, tip 1/2 no sweep. // This is a trapezoid. Height 1, root 1, tip 1/2 no sweep.
// It can be decomposed into a rectangle followed by a triangle // It can be decomposed into a rectangle followed by a triangle
// +---+ // +---+
@ -218,7 +215,77 @@ public class TrapezoidFinSetTest extends BaseTestCase {
assertEquals(0.3889, coords.x, 0.001); assertEquals(0.3889, coords.x, 0.001);
assertEquals(0.4444, coords.y, 0.001); assertEquals(0.4444, coords.y, 0.001);
} }
}
@Test
public void testGetBodyPoints_phantomMount() {
final Rocket rkt = createSimpleTrapezoidalFin();
// set mount to have zero-dimensions:
final BodyTube mount = (BodyTube)rkt.getChild(0).getChild(0);
mount.setLength(0.0);
mount.setOuterRadius(0.0);
assertEquals( 0, mount.getLength(), 0.00001);
assertEquals( 0, mount.getOuterRadius(), 0.00001);
assertEquals( 0, mount.getInnerRadius(), 0.00001);
final TrapezoidFinSet fins = (TrapezoidFinSet)mount.getChild(0);
final Coordinate[] mountPoints = fins.getMountPoints();
assertEquals(2, mountPoints.length );
assertEquals( 0.00, mountPoints[0].x, 0.00001);
assertEquals( 0.00, mountPoints[0].y, 0.00001);
assertEquals( 0.00, mountPoints[1].x, 0.00001);
assertEquals( 0.00, mountPoints[1].y, 0.00001);
}
@Test
public void testGetBodyPoints_zeroLengthMount() {
final Rocket rkt = createSimpleTrapezoidalFin();
// set mount to have zero-dimensions:
final BodyTube mount = (BodyTube)rkt.getChild(0).getChild(0);
mount.setLength(0.0);
mount.setOuterRadius(0.1);
mount.setInnerRadius(0.08);
assertEquals( 0, mount.getLength(), 0.00001);
assertEquals( 0.1, mount.getOuterRadius(), 0.00001);
assertEquals( 0.08, mount.getInnerRadius(), 0.00001);
final TrapezoidFinSet fins = (TrapezoidFinSet)mount.getChild(0);
final Coordinate[] mountPoints = fins.getMountPoints();
assertEquals(2, mountPoints.length );
assertEquals( 0.0, mountPoints[0].x, 0.00001);
assertEquals( 0.1, mountPoints[0].y, 0.00001);
assertEquals( 0.0, mountPoints[1].x, 0.00001);
assertEquals( 0.1, mountPoints[1].y, 0.00001);
}
@Test
public void testTrapezoidCGComputation_phantomMount() {
final Rocket rkt = createSimpleTrapezoidalFin();
// set mount to have zero-dimensions:
final BodyTube mount = (BodyTube)rkt.getChild(0).getChild(0);
mount.setLength(0.0);
mount.setOuterRadius(0.0);
assertEquals( 0, mount.getLength(), 0.00001);
assertEquals( 0, mount.getOuterRadius(), 0.00001);
assertEquals( 0, mount.getInnerRadius(), 0.00001);
final TrapezoidFinSet fins = (TrapezoidFinSet)mount.getChild(0);
assertEquals( 0.06, fins.getLength(), 0.00001);
assertEquals( 0.05, fins.getHeight(), 0.00001);
assertEquals( 0.06, fins.getRootChord(), 0.00001);
assertEquals( 0.02, fins.getTipChord(), 0.00001);
final Coordinate coords = fins.getCG();
assertEquals(0.002, fins.getPlanformArea(), 0.001);
assertEquals(0.03, coords.x, 0.001);
assertEquals(0.02, coords.y, 0.001);
} }
@Test @Test