diff --git a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java index 37b2663cf..2f99e1091 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java @@ -412,8 +412,12 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab - outerArcAngle * filletRadius * filletRadius / 2 - innerArcAngle * bodyRadius * bodyRadius / 2); - // each fin has a fillet on each side - crossSectionArea *= 2; + if(Double.isNaN(crossSectionArea)) { + crossSectionArea = 0.; + }else { + // each fin has a fillet on each side + crossSectionArea *= 2; + } // heuristic, relTo the body center 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. */ protected Coordinate calculateFilletVolumeCentroid() { - Coordinate[] bodyPoints = this.getBodyPoints(); - if (0 == bodyPoints.length) { + Coordinate[] mountPoints = this.getRootPoints(); + if( null == mountPoints ){ return Coordinate.ZERO; } - + final SymmetricComponent sym = (SymmetricComponent) this.parent; if (!SymmetricComponent.class.isInstance(this.parent)) { return Coordinate.ZERO; } Coordinate filletVolumeCentroid = Coordinate.ZERO; - - - Coordinate prev = bodyPoints[0]; - for (int index = 1; index < bodyPoints.length; index++) { - final Coordinate cur = bodyPoints[index]; + Coordinate prev = mountPoints[0]; + for (int index = 1; index < mountPoints.length; index++) { + final Coordinate cur = mountPoints[index]; // cross section at mid-segment final double xAvg = (prev.x + cur.x) / 2; final double bodyRadius = sym.getRadius(xAvg); final Coordinate segmentCrossSection = calculateFilletCrossSection(this.filletRadius, bodyRadius).setX(xAvg); - + // final double xCentroid = xAvg; // final double yCentroid = segmentCrossSection.y; ///< heuristic, not exact 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; } - - // translate to be relative to the fin-lead-root - filletVolumeCentroid = filletVolumeCentroid.sub(getAxialFront(), 0,0); - + if (finCount == 1) { Transformation rotation = Transformation.rotate_x( getAngleOffset()); return rotation.transform(filletVolumeCentroid); @@ -541,7 +540,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab final double xTabTrail_body = xFinFront_body + xTabTrail_fin; // 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 upperCurve = translatePoints( upperCurve, -xFinFront_body, 0.0); @@ -572,15 +571,15 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab */ private Coordinate calculateSinglePlanformCentroid(){ final Coordinate finFront = getFinFront(); - - final Coordinate[] upperCurve = translatePoints( getFinPoints(), finFront.x, finFront.y ); - final Coordinate[] lowerCurve = getBodyPoints(); + + final Coordinate[] upperCurve = getFinPoints(); + final Coordinate[] lowerCurve = getRootPoints(); final Coordinate[] totalCurve = combineCurves( upperCurve, lowerCurve); - + Coordinate planformCentroid = calculateCurveIntegral( totalCurve ); // 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 */ - public Coordinate[] getBodyPoints() { - final double xFinStart = getAxialFront(); - final double xFinEnd = xFinStart+getLength(); - - return getBodyPoints( xFinStart, xFinEnd); + public Coordinate[] getMountPoints() { + if( null == parent){ + return null; + } + + 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 ) */ 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){ 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. int divisionCount = 1; // cast-assert @@ -1101,7 +1104,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab if( body.getLength()-0.000001 < points[lastIndex].x) { points[lastIndex] = points[lastIndex].setX(body.getLength()).setY(body.getAftRadius()); } - + return points; } @@ -1125,7 +1128,8 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab buf.append( getPointDescr( this.getFinPoints(), "Fin Points", "")); 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() ) { @@ -1146,7 +1150,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab final double tabVolume = tabCentroid.weight * thickness; final double tabMass = tabVolume * material.getDensity(); final Coordinate tabCM = tabCentroid.setWeight(tabMass); - + Coordinate filletCentroid = calculateFilletVolumeCentroid(); double filletVolume = filletCentroid.weight; double filletMass = filletVolume * filletMaterial.getDensity(); @@ -1156,7 +1160,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab final double eachFinMass = finBulkMass + tabMass + filletMass; final Coordinate eachFinCenterOfMass = wettedCM.average(tabCM).average(filletCM).setWeight(eachFinMass); - + // ^^ per fin // vv per component diff --git a/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java b/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java index b5bc9f158..84b0a668b 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java @@ -1179,13 +1179,13 @@ public class FreeformFinSetTest extends BaseTestCase { final Coordinate[] finPointsFromBody = FinSet.translatePoints( finPoints, 0.0, fins.getFinFront().y); { // 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("incorrect body points! ", finPointsFromBody[0].x, bodyPoints[0].x, EPSILON); - assertEquals("incorrect body points! ", finPointsFromBody[0].y, bodyPoints[0].y, EPSILON); - assertEquals("incorrect body points! ", finPointsFromBody[finPoints.length-1].x, bodyPoints[1].x, EPSILON); - assertEquals("incorrect body points! ", finPointsFromBody[finPoints.length-1].y, bodyPoints[1].y, EPSILON); + assertEquals("Method should only generate minimal points for a conical transition fin body! ", 2, mountPoints.length ); + assertEquals("incorrect body points! ", finPointsFromBody[0].x, mountPoints[0].x, EPSILON); + assertEquals("incorrect body points! ", finPointsFromBody[0].y, mountPoints[0].y, EPSILON); + assertEquals("incorrect body points! ", finPointsFromBody[finPoints.length-1].x, mountPoints[1].x, EPSILON); + assertEquals("incorrect body points! ", finPointsFromBody[finPoints.length-1].y, mountPoints[1].y, EPSILON); } { // root points (relative to fin-front) final Coordinate[] rootPoints = fins.getRootPoints(); @@ -1203,18 +1203,16 @@ public class FreeformFinSetTest extends BaseTestCase { final Rocket rkt = createTemplateRocket(); final FreeformFinSet fins = (FreeformFinSet) rkt.getChild(0).getChild(2).getChild(0); - final Coordinate finFront = fins.getFinFront(); final Coordinate[] finPoints = fins.getFinPoints(); - final Coordinate[] finPointsFromBody = FinSet.translatePoints( finPoints, finFront.x, finFront.y); - + { // 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("incorrect body points! ", finPointsFromBody[0].x, bodyPoints[0].x, EPSILON); - assertEquals("incorrect body points! ", finPointsFromBody[0].y, bodyPoints[0].y, EPSILON); - assertEquals("incorrect body points! ", finPointsFromBody[finPoints.length-1].x, bodyPoints[1].x, EPSILON); - assertEquals("incorrect body points! ", finPointsFromBody[finPoints.length-1].y, bodyPoints[1].y, EPSILON); + assertEquals("incorrect body points! ", 0.0, bodyPoints[0].x, EPSILON); + assertEquals("incorrect body points! ", 1.0, bodyPoints[0].y, EPSILON); + assertEquals("incorrect body points! ", 1.0, bodyPoints[1].x, EPSILON); + assertEquals("incorrect body points! ", 0.5, bodyPoints[1].y, EPSILON); } { // body points (relative to root) final Coordinate[] rootPoints = fins.getRootPoints(); @@ -1236,7 +1234,6 @@ public class FreeformFinSetTest extends BaseTestCase { final Coordinate finFront = fins.getFinFront(); final Coordinate[] finPoints = fins.getFinPoints(); - { // fin points (relative to fin) // preconditions assertEquals(4, finPoints.length); @@ -1281,35 +1278,30 @@ public class FreeformFinSetTest extends BaseTestCase { } } }{ // body points (relative to body) - // translate from fin-frame to body-frame - final Coordinate[] finPointsFromBody = FinSet.translatePoints( fins.getFinPoints(), finFront.x, finFront.y ); + final Coordinate[] mountPoints = fins.getMountPoints(); + assertEquals(101, mountPoints.length); - final Coordinate[] bodyPoints = fins.getBodyPoints(); - assertEquals(101, bodyPoints.length); - - final Coordinate expectedEndPoint = finPointsFromBody[ finPoints.length-1]; - // trivial, and uninteresting: - assertEquals("incorrect body points! ", finPointsFromBody[0].x, bodyPoints[0].x, EPSILON); - assertEquals("incorrect body points! ", finPointsFromBody[0].y, bodyPoints[0].y, EPSILON); + assertEquals("incorrect body points! ", 0.0, mountPoints[0].x, 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) - assertEquals("incorrect body points! ", expectedEndPoint.x, bodyPoints[bodyPoints.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].x, 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 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 ); - - final int[] testIndices = { 2, 5, 61, 88}; - final double[] expectedX = { 0.036, 0.06, 0.508, 0.724}; + assertEquals("Method is generating how many points, in general? ", 101, mountPoints.length ); + 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++){ final int testIndex = testIndices[testCase]; 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), - body.getRadius(bodyPoints[testIndex].x), bodyPoints[testIndex].y, EPSILON); + body.getRadius(mountPoints[testIndex].x), mountPoints[testIndex].y, EPSILON); } } } diff --git a/core/test/net/sf/openrocket/rocketcomponent/TrapezoidFinSetTest.java b/core/test/net/sf/openrocket/rocketcomponent/TrapezoidFinSetTest.java index 96d51332b..ae2635a76 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/TrapezoidFinSetTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/TrapezoidFinSetTest.java @@ -90,7 +90,7 @@ public class TrapezoidFinSetTest extends BaseTestCase { final Rocket rkt = createSimpleTrapezoidalFin(); 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); // should return a single-fin-planform area @@ -170,14 +170,14 @@ public class TrapezoidFinSetTest extends BaseTestCase { // / \ // [0] +--------+ [3] // + assertEquals(0.06, fins.getLength(), EPSILON); assertEquals("Body radius doesn't match: ", 0.1, body.getOuterRadius(), EPSILON); final Coordinate actVolume = fins.calculateFilletVolumeCentroid(); - assertEquals("Line volume doesn't match: ", 5.973e-07, actVolume.weight, EPSILON); - - assertEquals("Line mass center.x doesn't match: ", 0.03, actVolume.x, EPSILON); - assertEquals("Line mass center.y doesn't match: ", 0.101, actVolume.y, 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("Fin mass center.y doesn't match: ", 0.101, actVolume.y, EPSILON); { // and then, check that the fillet volume feeds into a correct overall CG: @@ -189,7 +189,6 @@ public class TrapezoidFinSetTest extends BaseTestCase { @Test public void testTrapezoidCGComputation() { - { // This is a simple square fin with sides of 1.0. TrapezoidFinSet fins = new TrapezoidFinSet(); @@ -200,9 +199,7 @@ public class TrapezoidFinSetTest extends BaseTestCase { assertEquals(1.0, fins.getPlanformArea(), 0.001); assertEquals(0.5, coords.x, 0.001); assertEquals(0.5, coords.y, 0.001); - } - - { + }{ // This is a trapezoid. Height 1, root 1, tip 1/2 no sweep. // 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.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