From 082187512b6ce10bbd75b15697e7b9c07a0c4313 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Tue, 6 Dec 2022 21:18:27 +0100 Subject: [PATCH 1/5] Some code refactoring --- .../sf/openrocket/rocketcomponent/FinSet.java | 151 +++++++++--------- 1 file changed, 72 insertions(+), 79 deletions(-) diff --git a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java index 0dc515dea..e5a2c5d39 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java @@ -1036,14 +1036,83 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona * @return points representing the fin-root points, relative to ( x: fin-front, y: centerline ) i.e. relto: fin Component reference point */ public Coordinate[] getRootPoints(){ + return getRootPoints(MAX_ROOT_DIVISIONS); + } + + /** + * use this for calculating physical properties, and routine drawing + * + * @return points representing the fin-root points, relative to ( x: fin-front, y: centerline ) i.e. relto: fin Component reference point + */ + public Coordinate[] getMountPoints() { if( null == parent){ + return null; + } + + return getMountPoints(0., parent.getLength(), 0,0); + } + + /** + * used to get calculate body profile points: + * + * @param xStart - xStart, in Mount-frame + * @param xEnd - xEnd, in Mount-frame + * @param xOffset - x-Offset to apply to returned points + * @param yOffset - y-Offset to apply to returned points + * + * @return points representing the mount's points + */ + private Coordinate[] getMountPoints(final double xStart, final double xEnd, final double xOffset, final double yOffset, + final int maximumBodyDivisionCount) { + if (parent == null) { return new Coordinate[]{Coordinate.ZERO}; } - final Coordinate finLead = getFinFront(); - final double xFinEnd = finLead.x + getLength(); + // for a simple body, one increment is perfectly accurate. + int divisionCount = 1; + final SymmetricComponent body = (SymmetricComponent) getParent(); + final double intervalLength = xEnd - xStart; - return getMountPoints( finLead.x, xFinEnd, -finLead.x, -finLead.y); + // for anything more complicated, increase the count: + if ((body instanceof Transition) && (((Transition)body).getType() != Shape.CONICAL)) { + // the maximum precision to enforce when calculating the areas of fins (especially on curved parent bodies) + final double xWidth = 0.0025; // width (in meters) of each individual iteration + divisionCount = (int) Math.ceil(intervalLength / xWidth); + + // When creating body curves, don't create more than this many divisions. -- only relevant on very large components + // a too high division count will cause the 3D render to have invisible faces because it can't deal with the geometry. + divisionCount = Math.min(maximumBodyDivisionCount, divisionCount); + } + + // Recalculate the x step increment, now with the (rounded) division count. + double xIncrement = intervalLength / divisionCount; + + // Create the points: step through the radius of the parent + double xCur = xStart; + Coordinate[] points = new Coordinate[divisionCount+1]; + for (int index = 0; index < points.length; index++) { + double yCur = body.getRadius(xCur); + points[index] = new Coordinate(xCur, yCur); + + xCur += xIncrement; + } + + // correct last point, if beyond a rounding error from body's end. + final int lastIndex = points.length - 1; + if (Math.abs(points[lastIndex].x - body.getLength()) < MathUtil.EPSILON) { + points[lastIndex] = points[lastIndex].setX(body.getLength()).setY(body.getAftRadius()); + } + + // translate the points if needed + if ((Math.abs(xOffset) + Math.abs(yOffset)) > MathUtil.EPSILON) { + points = translatePoints(points, xOffset, yOffset); + } + + return points; + } + + private Coordinate[] getMountPoints(final double xStart, final double xEnd, final double xOffset, final double yOffset) { + return getMountPoints(xStart, xEnd, xOffset, yOffset, MAX_ROOT_DIVISIONS); } /** @@ -1155,82 +1224,6 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona return combineCurves(tabPoints, rootPoints.toArray(new Coordinate[0])); } - - /** - * use this for calculating physical properties, and routine drawing - * - * @return points representing the fin-root points, relative to ( x: fin-front, y: centerline ) i.e. relto: fin Component reference point - */ - public Coordinate[] getMountPoints() { - if( null == parent){ - return null; - } - - return getMountPoints(0., parent.getLength(), 0,0); - } - - /** - * used to get calculate body profile points: - * - * @param xStart - xStart, in Mount-frame - * @param xEnd - xEnd, in Mount-frame - * @param xOffset - x-Offset to apply to returned points - * @param yOffset - y-Offset to apply to returned points - * - * @return points representing the mount's points - */ - private Coordinate[] getMountPoints(final double xStart, final double xEnd, final double xOffset, final double yOffset, - final int maximumBodyDivisionCount) { - if (parent == null) { - return new Coordinate[]{Coordinate.ZERO}; - } - - // for a simple body, one increment is perfectly accurate. - int divisionCount = 1; - final SymmetricComponent body = (SymmetricComponent) getParent(); - final double intervalLength = xEnd - xStart; - - // for anything more complicated, increase the count: - if ((body instanceof Transition) && (((Transition)body).getType() != Shape.CONICAL)) { - // the maximum precision to enforce when calculating the areas of fins (especially on curved parent bodies) - final double xWidth = 0.0025; // width (in meters) of each individual iteration - divisionCount = (int) Math.ceil(intervalLength / xWidth); - - // When creating body curves, don't create more than this many divisions. -- only relevant on very large components - // a too high division count will cause the 3D render to have invisible faces because it can't deal with the geometry. - divisionCount = Math.min(maximumBodyDivisionCount, divisionCount); - } - - // Recalculate the x step increment, now with the (rounded) division count. - double xIncrement = intervalLength / divisionCount; - - // Create the points: step through the radius of the parent - double xCur = xStart; - Coordinate[] points = new Coordinate[divisionCount+1]; - for (int index = 0; index < points.length; index++) { - double yCur = body.getRadius(xCur); - points[index] = new Coordinate(xCur, yCur); - - xCur += xIncrement; - } - - // correct last point, if beyond a rounding error from body's end. - final int lastIndex = points.length - 1; - if (Math.abs(points[lastIndex].x - body.getLength()) < MathUtil.EPSILON) { - points[lastIndex] = points[lastIndex].setX(body.getLength()).setY(body.getAftRadius()); - } - - // translate the points if needed - if ((Math.abs(xOffset) + Math.abs(yOffset)) > MathUtil.EPSILON) { - points = translatePoints(points, xOffset, yOffset); - } - - return points; - } - - private Coordinate[] getMountPoints(final double xStart, final double xEnd, final double xOffset, final double yOffset) { - return getMountPoints(xStart, xEnd, xOffset, yOffset, MAX_ROOT_DIVISIONS); - } @Override public double getAngleOffset() { From e7b728d8e84b5d00c68af52b2f313ecef3be240d Mon Sep 17 00:00:00 2001 From: SiboVG Date: Wed, 7 Dec 2022 16:03:59 +0100 Subject: [PATCH 2/5] Draw fin root in fin point figure --- swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java index 197ec421d..83d5f9600 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java @@ -226,7 +226,7 @@ public class FinPointFigure extends AbstractScaleFigure { private void paintFinShape(final Graphics2D g2){ // excludes fin tab points - final Coordinate[] drawPoints = finset.getFinPoints(); + final Coordinate[] drawPoints = finset.getFinPointsWithRoot(); Path2D.Double shape = new Path2D.Double(); Coordinate startPoint= drawPoints[0]; From 0956b9e956a731eb10cf75d230ec3752fb1da800 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Wed, 7 Dec 2022 16:05:39 +0100 Subject: [PATCH 3/5] [#1021] Add extra root point if fin is outside parent's bounds --- .../sf/openrocket/rocketcomponent/FinSet.java | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java index e5a2c5d39..ba14c64b9 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java @@ -1089,26 +1089,44 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona // Create the points: step through the radius of the parent double xCur = xStart; - Coordinate[] points = new Coordinate[divisionCount+1]; - for (int index = 0; index < points.length; index++) { + List points = new ArrayList<>(); + for (int index = 0; index < divisionCount+1; index++) { double yCur = body.getRadius(xCur); - points[index] = new Coordinate(xCur, yCur); + points.add(new Coordinate(xCur, yCur)); xCur += xIncrement; } + /* + If the front fin point is outside the parent's bounds, and the last point is still within the parent's bounds, + then we need to add an extra root point at the front of the parent. Same goes for the last point, but vice versa. + This ensures that fins are drawn correctly on transitions and nose cones (see GitHub issue #1021 for more info). + */ + // Front fin point is outside the parent's bounds and last point is still within the parent's bounds + if (xStart < 0 && xEnd > 0) { + points.add(1, new Coordinate(0, points.get(0).y)); + } + // End fin point is outside the parent's bounds and first point is still within the parent's bounds + if (xEnd > parent.length && xStart < parent.length) { + final double x = parent.length; + final double y = points.get(points.size() - 1).y; + points.add(points.size() - 1, new Coordinate(x, y)); + } + + Coordinate[] rootPoints = points.toArray(new Coordinate[0]); + // correct last point, if beyond a rounding error from body's end. - final int lastIndex = points.length - 1; - if (Math.abs(points[lastIndex].x - body.getLength()) < MathUtil.EPSILON) { - points[lastIndex] = points[lastIndex].setX(body.getLength()).setY(body.getAftRadius()); + final int lastIndex = rootPoints.length - 1; + if (Math.abs(rootPoints[lastIndex].x - body.getLength()) < MathUtil.EPSILON) { + rootPoints[lastIndex] = rootPoints[lastIndex].setX(body.getLength()).setY(body.getAftRadius()); } // translate the points if needed if ((Math.abs(xOffset) + Math.abs(yOffset)) > MathUtil.EPSILON) { - points = translatePoints(points, xOffset, yOffset); + rootPoints = translatePoints(rootPoints, xOffset, yOffset); } - return points; + return rootPoints; } private Coordinate[] getMountPoints(final double xStart, final double xEnd, final double xOffset, final double yOffset) { From 3501a4ff413c403aa78b261ac23695f660d0372e Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 8 Dec 2022 02:32:47 +0100 Subject: [PATCH 4/5] Add unit tests for root points when fin out of parent's bounds --- .../rocketcomponent/FreeformFinSetTest.java | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java b/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java index 8e17dfa71..5dfae5e5a 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java @@ -1310,6 +1310,119 @@ public class FreeformFinSetTest extends BaseTestCase { } } + @Test + public void testGenerateBodyPointsWhenFinOutsideParentBounds() { + final Rocket rkt = createTemplateRocket(); + final Transition tailCone = (Transition) rkt.getChild(0).getChild(2); + final FreeformFinSet fins = this.createFinOnConicalTransition(tailCone); + final Coordinate[] initialPoints = fins.getFinPoints(); + + assertEquals(1.0, tailCone.getLength(), EPSILON); + + { // move first point out of bounds, keep last point in bounds + fins.setAxialOffset(AxialMethod.TOP, 0); + fins.setPoints(initialPoints); + assertEquals(0f, fins.getFinFront().x, EPSILON); + assertEquals(1f, fins.getFinFront().y, EPSILON); + + // Move first point + fins.setPoint(0, -0.1, 0.1f); + + final Coordinate[] rootPoints = fins.getRootPoints(); + assertEquals(3, rootPoints.length); + + assertEquals("incorrect body points! ", 0f, rootPoints[0].x, EPSILON); + assertEquals("incorrect body points! ", 0f, rootPoints[0].y, EPSILON); + + assertEquals("incorrect body points! ", 0.1f, rootPoints[1].x, EPSILON); + assertEquals("incorrect body points! ", 0f, rootPoints[1].y, EPSILON); + + assertEquals("incorrect body points! ", 0.5f, rootPoints[2].x, EPSILON); + assertEquals("incorrect body points! ", -0.2f, rootPoints[2].y, EPSILON); + } { // move both first and last point out of bounds to the left + fins.setAxialOffset(AxialMethod.TOP, 0); + fins.setPoints(initialPoints); + assertEquals(0f, fins.getFinFront().x, EPSILON); + assertEquals(1f, fins.getFinFront().y, EPSILON); + + // Move first and last point + fins.setPoint(0, -0.2, 0.1f); + fins.setPoint(fins.getPointCount()-1, 0.1, 0.1f); + + final Coordinate[] rootPoints = fins.getRootPoints(); + assertEquals(2, rootPoints.length); + + assertEquals("incorrect body points! ", 0f, rootPoints[0].x, EPSILON); + assertEquals("incorrect body points! ", 0f, rootPoints[0].y, EPSILON); + + assertEquals("incorrect body points! ", 0.1f, rootPoints[1].x, EPSILON); + assertEquals("incorrect body points! ", 0f, rootPoints[1].y, EPSILON); + } { // move last point out of bounds, keep first point in bounds + fins.setPoints(initialPoints); + fins.setAxialOffset(AxialMethod.BOTTOM, fins.getLength()); + assertEquals(1f, fins.getFinFront().x, EPSILON); + assertEquals(0.5f, fins.getFinFront().y, EPSILON); + + // Move first point + fins.setPoint(0, -0.1f, 0.1f); + fins.setPoint(fins.getPointCount()-1, 0.2f, 0f); + + final Coordinate[] rootPoints = fins.getRootPoints(); + assertEquals(3, rootPoints.length); + + assertEquals("incorrect body points! ", 0f, rootPoints[0].x, EPSILON); + assertEquals("incorrect body points! ", 0f, rootPoints[0].y, EPSILON); + + assertEquals("incorrect body points! ", 0.1f, rootPoints[1].x, EPSILON); + assertEquals("incorrect body points! ", -0.05f, rootPoints[1].y, EPSILON); + + assertEquals("incorrect body points! ", 0.2f, rootPoints[2].x, EPSILON); + assertEquals("incorrect body points! ", -0.05f, rootPoints[2].y, EPSILON); + } { // move both first and last point out of bounds to the right + fins.setPoints(initialPoints); + fins.setAxialOffset(AxialMethod.BOTTOM, fins.getLength()); + assertEquals(1f, fins.getFinFront().x, EPSILON); + assertEquals(0.5f, fins.getFinFront().y, EPSILON); + + // Move first and last point + fins.setPoint(0, 0.1, 0.1f); + fins.setPoint(fins.getPointCount()-1, 0.2, 0.1f); + + final Coordinate[] rootPoints = fins.getRootPoints(); + assertEquals(2, rootPoints.length); + + assertEquals("incorrect body points! ", 0f, rootPoints[0].x, EPSILON); + assertEquals("incorrect body points! ", 0f, rootPoints[0].y, EPSILON); + + assertEquals("incorrect body points! ", 0.2f, rootPoints[1].x, EPSILON); + assertEquals("incorrect body points! ", 0f, rootPoints[1].y, EPSILON); + } { // move first point out of bounds to the left, and last point out of bounds to the right + fins.setAxialOffset(AxialMethod.TOP, 0); + fins.setPoints(initialPoints); + assertEquals(0, fins.getFinFront().x, EPSILON); + assertEquals(1, fins.getFinFront().y, EPSILON); + + // Move first and last point + fins.setPoint(0, -0.1, 0.1f); + fins.setPoint(fins.getPointCount()-1, 1.2, -0.1f); + + final Coordinate[] rootPoints = fins.getRootPoints(); + assertEquals(4, rootPoints.length); + + assertEquals("incorrect body points! ", 0f, rootPoints[0].x, EPSILON); + assertEquals("incorrect body points! ", 0f, rootPoints[0].y, EPSILON); + + assertEquals("incorrect body points! ", 0.1f, rootPoints[1].x, EPSILON); + assertEquals("incorrect body points! ", 0f, rootPoints[1].y, EPSILON); + + assertEquals("incorrect body points! ", 1.1f, rootPoints[2].x, EPSILON); + assertEquals("incorrect body points! ", -0.5f, rootPoints[2].y, EPSILON); + + assertEquals("incorrect body points! ", 1.2f, rootPoints[3].x, EPSILON); + assertEquals("incorrect body points! ", -0.5f, rootPoints[3].y, EPSILON); + } + } + @Test public void testFreeFormCMWithNegativeY() throws Exception { final Rocket rkt = createTemplateRocket(); From 1c4de75a7690e25464fefe638a2cd49e746ef4a9 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Thu, 8 Dec 2022 02:38:45 +0100 Subject: [PATCH 5/5] Add extra unit tests for fin mass --- .../openrocket/rocketcomponent/FreeformFinSetTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java b/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java index 5dfae5e5a..cd6c38e6a 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java @@ -1339,6 +1339,8 @@ public class FreeformFinSetTest extends BaseTestCase { assertEquals("incorrect body points! ", 0.5f, rootPoints[2].x, EPSILON); assertEquals("incorrect body points! ", -0.2f, rootPoints[2].y, EPSILON); + + assertEquals("incorrect fin mass! ", 0.306, fins.getMass(), EPSILON); } { // move both first and last point out of bounds to the left fins.setAxialOffset(AxialMethod.TOP, 0); fins.setPoints(initialPoints); @@ -1357,6 +1359,8 @@ public class FreeformFinSetTest extends BaseTestCase { assertEquals("incorrect body points! ", 0.1f, rootPoints[1].x, EPSILON); assertEquals("incorrect body points! ", 0f, rootPoints[1].y, EPSILON); + + assertEquals("incorrect fin mass! ", 0.034, fins.getMass(), EPSILON); } { // move last point out of bounds, keep first point in bounds fins.setPoints(initialPoints); fins.setAxialOffset(AxialMethod.BOTTOM, fins.getLength()); @@ -1378,6 +1382,8 @@ public class FreeformFinSetTest extends BaseTestCase { assertEquals("incorrect body points! ", 0.2f, rootPoints[2].x, EPSILON); assertEquals("incorrect body points! ", -0.05f, rootPoints[2].y, EPSILON); + + assertEquals("incorrect fin mass! ", 0.102, fins.getMass(), EPSILON); } { // move both first and last point out of bounds to the right fins.setPoints(initialPoints); fins.setAxialOffset(AxialMethod.BOTTOM, fins.getLength()); @@ -1396,6 +1402,8 @@ public class FreeformFinSetTest extends BaseTestCase { assertEquals("incorrect body points! ", 0.2f, rootPoints[1].x, EPSILON); assertEquals("incorrect body points! ", 0f, rootPoints[1].y, EPSILON); + + assertEquals("incorrect fin mass! ", 0.068, fins.getMass(), EPSILON); } { // move first point out of bounds to the left, and last point out of bounds to the right fins.setAxialOffset(AxialMethod.TOP, 0); fins.setPoints(initialPoints); @@ -1420,6 +1428,8 @@ public class FreeformFinSetTest extends BaseTestCase { assertEquals("incorrect body points! ", 1.2f, rootPoints[3].x, EPSILON); assertEquals("incorrect body points! ", -0.5f, rootPoints[3].y, EPSILON); + + assertEquals("incorrect fin mass! ", 0.833, fins.getMass(), EPSILON); } }