diff --git a/core/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java b/core/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java index 0b2176112..499b62181 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java @@ -20,10 +20,11 @@ public class FreeformFinSet extends FinSet { private static final Logger log = LoggerFactory.getLogger(FreeformFinSet.class); private static final Translator trans = Application.getTranslator(); - public static final double MIN_ROOT_CHORD=0.01; // enforce this to prevent erroneous 'intersection' exceptions. - private List points = new ArrayList<>(); + private static final double SNAP_SMALLER_THAN = 1e-6; + private static final double IGNORE_SMALLER_THAN = 1e-12; + public FreeformFinSet() { points.add(Coordinate.ZERO); points.add(new Coordinate(0.025, 0.05)); @@ -32,11 +33,7 @@ public class FreeformFinSet extends FinSet { this.length = 0.05; } - - public FreeformFinSet(Coordinate[] finpoints) { - setPoints(finpoints); - } - + /** * Convert an existing fin set into a freeform fin set. The specified * fin set is taken out of the rocket tree (if any) and the new component @@ -51,7 +48,7 @@ public class FreeformFinSet extends FinSet { final RocketComponent root = finset.getRoot(); FreeformFinSet freeform; List toInvalidate = Collections.emptyList(); - + try { if (root instanceof Rocket) { ((Rocket) root).freeze(); @@ -66,15 +63,16 @@ public class FreeformFinSet extends FinSet { } else { position = -1; } - + // Create the freeform fin set - Coordinate[] finpoints = finset.getFinPoints(); - freeform = new FreeformFinSet(finpoints); + Coordinate[] finPoints = finset.getFinPoints(); + freeform = new FreeformFinSet(); + freeform.setPoints(Arrays.asList(finPoints)); freeform.setAxialOffset(finset.getAxialMethod(), finset.getAxialOffset()); - + // Copy component attributes toInvalidate = freeform.copyFrom(finset); - + // Set name final String componentTypeName = finset.getComponentName(); final String name = freeform.getName(); @@ -83,9 +81,9 @@ public class FreeformFinSet extends FinSet { freeform.setName(freeform.getComponentName() + name.substring(componentTypeName.length())); } - + freeform.setAppearance(finset.getAppearance()); - + // Add freeform fin set to parent if (parent != null) { parent.addChild(freeform, position); @@ -113,11 +111,11 @@ public class FreeformFinSet extends FinSet { public void addPoint(int index, Point2D.Double location) { // new method: add new point at closest point points.add(index, new Coordinate(location.x, location.y)); - + // adding a point within the segment affects neither mass nor aerodynamics fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); } - + /** * Remove the fin point with the given index. The first and last fin points * cannot be removed, and will cause an IllegalFinPointException @@ -135,7 +133,7 @@ public class FreeformFinSet extends FinSet { List copy = new ArrayList<>(this.points); this.points.remove(index); - if( ! validate()){ + if (!validate()) { // if error, rollback. this.points = copy; } @@ -147,23 +145,19 @@ public class FreeformFinSet extends FinSet { public int getPointCount() { return points.size(); } - - /** - * The first point is assumed to be at the origin. If it isn't, it will be moved there. - * - * @param newPoints new fin points ; replaces previous fin points + + /** maintained just for backwards compatibility: */ public void setPoints(Coordinate[] newPoints) { + // move to zero, if applicable if( ! Coordinate.ZERO.equals(newPoints[0])) { final Coordinate p0 = newPoints[0]; - newPoints = translatePoints(newPoints, p0.x, p0.y); + newPoints = translatePoints( newPoints, -p0.x, -p0.y); } - - ArrayList newList = new ArrayList<>(Arrays.asList( newPoints)); - setPoints( newList ); + + setPoints(new ArrayList<>(Arrays.asList(newPoints))); } - /** * The first point is assumed to be at the origin. If it isn't, it will be moved there. * @@ -172,33 +166,39 @@ public class FreeformFinSet extends FinSet { public void setPoints( List newPoints) { // copy the old points, in case validation fails List copy = new ArrayList<>(this.points); - this.points = newPoints; + this.length = newPoints.get(newPoints.size() -1).x; + update(); - + +// StackTraceElement[] stacktrack = Thread.currentThread().getStackTrace(); + if("Canard fins, mounted to transition".equals(this.getName())) { + log.error(String.format("starting to set %d points @ %s", newPoints.size(), this.getName()), new NullPointerException()); + System.err.println( toDebugDetail()); + } + if( ! validate()){ // on error, reset to the old points this.points = copy; } - this.length = points.get(points.size() - 1).x; fireComponentChangeEvent(ComponentChangeEvent.AEROMASS_CHANGE); } - private double y_body( final double x){ - return y_body( x, 0.0 ); + private double y_body(final double x) { + return y_body(x, 0.0); } - private double y_body( final double x_target, final double x_ref){ - final SymmetricComponent sym = (SymmetricComponent)getParent(); - return ( sym.getRadius(x_target) - sym.getRadius( x_ref)); + private double y_body(final double x_target, final double x_ref) { + final SymmetricComponent sym = (SymmetricComponent) getParent(); + return (sym.getRadius(x_target) - sym.getRadius(x_ref)); } - public void setPointRelToFin( final int index, final double x_request_fin, final double y_request_fin) throws IllegalFinPointException { + public void setPointRelToFin(final int index, final double x_request_fin, final double y_request_fin) throws IllegalFinPointException { final double x_finStart_body = getAxialFront(); // x @ fin start, body frame - final double y_finStart_body = y_body( x_finStart_body); + final double y_finStart_body = y_body(x_finStart_body); - setPoint( index, x_request_fin + x_finStart_body , y_request_fin + y_finStart_body); + setPoint(index, x_request_fin + x_finStart_body, y_request_fin + y_finStart_body); } /** @@ -221,83 +221,40 @@ public class FreeformFinSet extends FinSet { * @param index the point index to modify. * @param xRequest the x-coordinate. * @param yRequest the y-coordinate. - */ - public void setPoint( final int index, final double xRequest, final double yRequest) { - final SymmetricComponent body = (SymmetricComponent)getParent(); + */ + public void setPoint(final int index, final double xRequest, final double yRequest) { - final int lastPointIndex = this.points.size() - 1; - final double xFinEnd = points.get(lastPointIndex).x; - final double xFinStart = getAxialFront(); // x of fin start, body-frame - final double yFinStart = body.getRadius( xFinStart); // y of fin start, body-frame - final double xBodyStart = -xFinStart; // x-offset from fin to body; fin-frame - - // initial guess at these values; further checks follow. - double xAgreed = xRequest; - double yAgreed = yRequest; - - // clamp x coordinates: - // within bounds, and consistent with the rest of the fin (at this time). - if( 0 == index ) { - // restrict the first point to be between the parent's start, and the last fin point - xAgreed = Math.max( xBodyStart, Math.min( xAgreed, xFinEnd - MIN_ROOT_CHORD )); - }else if( lastPointIndex == index ){ - // restrict the last point to be between the first fin point, and the parent's end length. - xAgreed = Math.max( MIN_ROOT_CHORD, Math.min( xAgreed, xBodyStart + body.getLength())); - } - - // adjust y-value to be consistent with body - final double yBody_atPoint= body.getRadius( xFinStart + xAgreed) - yFinStart; - if (index == 0 || index == lastPointIndex) { - // for the first and last points: set y-value to *exactly* match parent body: - yAgreed = yBody_atPoint; - }else{ - // for all other points, merely insist that the point is outside the body... - yAgreed = Math.max( yAgreed, yBody_atPoint); - } - - // if moving either begin or end points, we'll probably have to update the position, as well. - final AxialMethod locationMethod = getAxialMethod(); - final double priorXOffset = getAxialOffset(); - - if( 0 == index){ - movePoints( xAgreed); - this.length = points.get( lastPointIndex ).x; - - if( AxialMethod.TOP == locationMethod){ - setAxialOffset( AxialMethod.TOP, priorXOffset + xAgreed ); - }else if(AxialMethod.MIDDLE == locationMethod){ - setAxialOffset( AxialMethod.MIDDLE, priorXOffset + xAgreed/2 ); + if(null != this.getParent()) { + if (0 == index) { + clampFirstPoint(new Coordinate(xRequest, yRequest)); + } else if ((this.points.size() - 1) == index) { + Coordinate priorPoint = points.get(index); + points.set(index, new Coordinate(xRequest, yRequest)); + clampLastPoint(priorPoint); + } else { + // interior points can never change the + points.set(index, new Coordinate(xRequest, yRequest)); + clampInteriorPoint(index); } - }else if( lastPointIndex == index ){ - points.set(index, new Coordinate( xAgreed, yAgreed )); - this.length = xAgreed; - - if( AxialMethod.MIDDLE == locationMethod){ - setAxialOffset( AxialMethod.MIDDLE, priorXOffset + (xAgreed - xFinEnd)/2 ); - }else if(AxialMethod.BOTTOM== locationMethod){ - setAxialOffset( AxialMethod.BOTTOM, priorXOffset + (xAgreed - xFinEnd) ); - } - }else{ - points.set(index, new Coordinate( xAgreed, yAgreed )); } // this maps the last index and the next-to-last-index to the same 'testIndex' - int testIndex = Math.min( index, (points.size() - 2)); - if( intersects( testIndex)){ + int testIndex = Math.min(index, (points.size() - 2)); + if (intersects(testIndex)) { // intersection found! log error and abort! log.error(String.format("ERROR: found an intersection while setting fin point #%d to [%6.4g, %6.4g] : ABORTING setPoint(..) !! ", index, xRequest, yRequest)); return; } - + fireComponentChangeEvent(ComponentChangeEvent.AEROMASS_CHANGE); } - - private void movePoints( final double delta_x){ + + private void movePoints(final double delta_x, final double delta_y) { // skip 0th index -- it's the local origin and is always (0,0) - for( int index=1; index < points.size(); ++index){ - final Coordinate oldPoint = this.points.get( index); - final Coordinate newPoint = oldPoint.sub( delta_x, 0.0f, 0.0f); - points.set( index, newPoint); + for (int index = 1; index < points.size(); ++index) { + final Coordinate oldPoint = this.points.get(index); + final Coordinate newPoint = oldPoint.add(delta_x, delta_y, 0.0f); + points.set(index, newPoint); } } @@ -322,7 +279,6 @@ public class FreeformFinSet extends FinSet { return trans.get("FreeformFinSet.FreeformFinSet"); } - @SuppressWarnings("unchecked") @Override protected RocketComponent copyWithOriginalID() { RocketComponent c = super.copyWithOriginalID(); @@ -333,10 +289,10 @@ public class FreeformFinSet extends FinSet { } @Override - public void setAxialOffset( final AxialMethod newAxialMethod, final double newOffsetRequest){ - super.setAxialOffset( newAxialMethod, newOffsetRequest); - - if( null != parent ) { + public void setAxialOffset(final AxialMethod newAxialMethod, final double newOffsetRequest) { + super.setAxialOffset(newAxialMethod, newOffsetRequest); + + if (null != parent) { // if the new position would cause fin overhang, only allow movement up to the end of the parent component. // N.B. if you want a fin to overhang, add & adjust interior points. final double backOverhang = getAxialOffset(AxialMethod.BOTTOM); @@ -353,96 +309,181 @@ public class FreeformFinSet extends FinSet { } @Override - public void update(){ - final int lastPointIndex = this.points.size() - 1; - this.length = points.get(lastPointIndex).x; + public void update() { + this.setAxialOffset(this.axialMethod, this.axialOffset); - this.setAxialOffset( this.axialMethod, this.axialOffset); - - clampFirstPoint(); - clampInteriorPoints(); - clampLastPoint(); - - validateFinTab(); - } - - // if we translate the points, correct the first point, because it may be inconsistent - private void clampFirstPoint(){ - double xFinStart = getAxialFront(); // x @ fin start, body frame - final double xFinOffset = getAxialOffset(); - if( 0 > xFinStart ){ - setAxialOffset( xFinOffset - xFinStart); + if(null != this.getParent()) { + clampFirstPoint(points.get(0)); + for(int i=1; i < points.size()-1; i++) { + clampInteriorPoint(i); + } + + clampLastPoint(null); + + validateFinTab(); } } - private void clampInteriorPoints(){ - if( null == this.parent ){ - // this is bad, but seems to only occur during unit tests. - return; - } - final SymmetricComponent symmetricParent = (SymmetricComponent)this.getParent(); + private void clampFirstPoint(final Coordinate newPoint) { + final SymmetricComponent body = (SymmetricComponent) getParent(); final Coordinate finFront = getFinFront(); + final double xFinFront = finFront.x; // x of fin start, body-frame + final double yFinFront = finFront.y; // y of fin start, body-frame + final double xBodyStart = -getAxialFront(); // x-offset from start-to-start; fin-frame - // omit end points index - for( int index=1; index < (points.size()-1); ++index){ - final Coordinate oldPoint = this.points.get( index); + double xDelta; + double yDelta; + + if(IGNORE_SMALLER_THAN > Math.abs(newPoint.x)){ + return; + }else if (xBodyStart > newPoint.x) { + // attempt to place point in front of the start of the body - final double yBody = symmetricParent.getRadius( oldPoint.x + finFront.x); - final double yFinPoint = finFront.y+ oldPoint.y; + // delta for new zeroth point + xDelta = xBodyStart; + yDelta = body.getForeRadius() - yFinFront; + points.set(0, newPoint); + points.add(0, Coordinate.ZERO); + movePoints(-xDelta, -yDelta); + + //System.err.println(String.format(".... @[0]//A: delta= %f, %f", xDelta, yDelta)); + + }else if (xFinFront > body.getLength()) { + final double xNew = body.getLength(); + final double yNew = yFinFront - body.getAftRadius(); + points.set(0, points.set(0, new Coordinate(xNew, yNew))); - if( yBody > yFinPoint ){ - final Coordinate newPoint = oldPoint.setY( yBody - finFront.y ); - points.set( index, newPoint); - } + xDelta = xNew - xFinFront; + yDelta = yNew - yFinFront; + movePoints(-xDelta, -yDelta); + //System.err.println(String.format(".... @[0]//B: delta= %f, %f", xDelta, yDelta)); + + }else { + // distance to move the entire fin by: + xDelta = newPoint.x; + yDelta = body.getRadius(xFinFront + xDelta) - yFinFront; + movePoints(-xDelta, -yDelta); + + //System.err.println(String.format(".... @[0]//C: delta= %f, %f", xDelta, yDelta)); + } + + final int lastIndex = points.size()-1; + this.length = points.get(lastIndex).x; + + if (AxialMethod.TOP == getAxialMethod()) { + setAxialOffset(AxialMethod.TOP, getAxialOffset() + xDelta); + } else if (AxialMethod.MIDDLE == getAxialMethod()) { + setAxialOffset(AxialMethod.MIDDLE, getAxialOffset() + xDelta / 2); } } + + private void clampInteriorPoint(final int index) { + final SymmetricComponent sym = (SymmetricComponent) this.getParent(); + + final double xPrior = points.get(index).x; + final double yPrior = points.get(index).y; - // if we translate the points, the final point may become inconsistent - private void clampLastPoint(){ - if( null == this.parent ){ - // this is bad, but seems to only occur during unit tests. - return; + final Coordinate finFront = getFinFront(); + final double xFinFront = finFront.x; // x of fin start, body-frame + final double yFinFront = finFront.y; // y of fin start, body-frame + + final double yBody = sym.getRadius(xPrior + xFinFront) - yFinFront; + + // ensure that an interior point is outside of its mounting body: + if (yBody > yPrior) { + points.set(index, points.get(index).setY(yBody)); + } + } + + private void clampLastPoint(final Coordinate prior) { + final SymmetricComponent body = (SymmetricComponent) getParent(); + + final double xFinStart = getAxialFront(); // x of fin start, body-frame + final double yFinStart = body.getRadius(xFinStart); // y of fin start, body-frame + + final double xBodyStart = -getAxialFront(); // x-offset from start-to-start; fin-frame + final double xBodyEnd = xBodyStart + body.getLength(); /// x-offset from start-to-body; fin-frame + + int lastIndex = points.size() - 1; + final Coordinate cur = points.get(lastIndex); + + double xDelta=0; + + if (xBodyEnd < cur.x) { + if(SNAP_SMALLER_THAN > Math.abs(xBodyEnd - cur.x)){ + points.set( lastIndex, new Coordinate(xBodyEnd, body.getAftRadius() - yFinStart)); + }else { + // the last point is placed after the end of the mount-body + points.add(new Coordinate(xBodyEnd, body.getAftRadius() - yFinStart)); + } + + if(null != prior) { + xDelta = xBodyEnd - prior.x; + }else{ + xDelta = xBodyEnd - cur.x; + } + //System.err.println(String.format(".... @[-1]//A: delta= %f", xDelta)); + + }else if (cur.x < 0) { + // the last point is positioned ahead of the first point. + points.set(lastIndex, Coordinate.ZERO); + + xDelta = cur.x; + + //System.err.println(String.format(".... @[-1]//B: delta= %f", xDelta)); + + } else { + if(null != prior) { + xDelta = cur.x - prior.x; + } + double yBody = body.getRadius(xFinStart + cur.x) - yFinStart; + if(IGNORE_SMALLER_THAN < Math.abs(yBody - cur.y)) { + // for the first and last points: set y-value to *exactly* match parent body: + points.set(lastIndex, new Coordinate(cur.x, yBody)); + + } + + + //System.err.println(String.format(".... @[-1]//C: delta = %f", xDelta)); } - final SymmetricComponent body = (SymmetricComponent)getParent(); - // clamp the final x coord to the end of the parent body. - final int lastPointIndex = points.size() - 1; - final Coordinate oldPoint = points.get( lastPointIndex); + if(IGNORE_SMALLER_THAN < Math.abs(xDelta)) { + lastIndex = points.size()-1; + this.length = points.get(lastIndex).x; - final double xFinStart_body = getAxialFront(); // x @ fin start, body frame - final double xBodyEnd_fin = body.getLength() - xFinStart_body; - - double x_clamped = Math.min( oldPoint.x, xBodyEnd_fin); - double y_clamped = body.getRadius( x_clamped+xFinStart_body) - body.getRadius( xFinStart_body); - - - points.set( lastPointIndex, new Coordinate( x_clamped, y_clamped, 0)); + if (AxialMethod.MIDDLE == getAxialMethod()) { + setAxialOffset(AxialMethod.MIDDLE, getAxialOffset() + xDelta / 2); + } else if (AxialMethod.BOTTOM == getAxialMethod()) { + setAxialOffset(AxialMethod.BOTTOM, getAxialOffset() + xDelta); + } + } } private boolean validate() { final Coordinate firstPoint = this.points.get(0); - if (firstPoint.x != 0 || firstPoint.y != 0 ){ - log.error("Start point illegal -- not located at (0,0): "+firstPoint+ " ("+ getName()+")"); + if (firstPoint.x != 0 || firstPoint.y != 0) { + log.error("Start point illegal -- not located at (0,0): " + firstPoint + " (" + getName() + ")"); return false; } - - final Coordinate lastPoint = this.points.get( points.size() -1); - if( lastPoint.x < 0){ - log.error("End point illegal: end point starts in front of start point: "+lastPoint.x); + + final Coordinate lastPoint = this.points.get(points.size() - 1); + if (lastPoint.x < 0) { + log.error("End point illegal: end point starts in front of start point: " + lastPoint.x); return false; } - + // the last point *is* restricted to be on the surface of its owning component: - SymmetricComponent symBody = (SymmetricComponent)this.getParent(); - if( null != symBody ){ + SymmetricComponent symBody = (SymmetricComponent) this.getParent(); + if (null != symBody) { final double startOffset = this.getAxialFront(); - final Coordinate finStart = new Coordinate( startOffset, symBody.getRadius(startOffset) ); + final Coordinate finStart = new Coordinate(startOffset, symBody.getRadius(startOffset)); // campare x-values - final Coordinate finAtLast = lastPoint.add(finStart); - if( symBody.getLength() < finAtLast.x ){ - log.error("End point falls after parent body ends: ["+symBody.getName()+"]. Exception: ", new IllegalFinPointException("Fin ends after its parent body \""+symBody.getName()+"\". Ignoring.")); + final Coordinate finAtLast = lastPoint.add(finStart); + if (symBody.getLength() < finAtLast.x) { + log.error("End point falls after parent body ends: [" + symBody.getName() + "]. Exception: ", + new IllegalFinPointException("Fin ends after its parent body \"" + symBody.getName() + "\". Ignoring.")); log.error(String.format(" ..fin position: (x: %12.10f via: %s)", this.axialOffset, this.axialMethod.name())); log.error(String.format(" ..Body Length: %12.10f finLength: %12.10f", symBody.getLength(), this.getLength())); log.error(String.format(" ..fin endpoint: (x: %12.10f, y: %12.10f)", finAtLast.x, finAtLast.y)); @@ -450,20 +491,21 @@ public class FreeformFinSet extends FinSet { } // compare the y-values - final Coordinate bodyAtLast = finAtLast.setY( symBody.getRadius( finAtLast.x ) ); - if( 0.0001 < Math.abs( finAtLast.y - bodyAtLast.y) ){ - String numbers = String.format( "finStart=(%6.2g,%6.2g) // fin_end=(%6.2g,%6.2g) // body=(%6.2g,%6.2g)", finStart.x, finStart.y, finAtLast.x, finAtLast.y, bodyAtLast.x, bodyAtLast.y ); - log.error("End point does not touch its parent body ["+symBody.getName()+"]. exception: ", new IllegalFinPointException("End point does not touch its parent body! Expected: "+numbers)); - log.error(" .."+numbers); + final Coordinate bodyAtLast = finAtLast.setY(symBody.getRadius(finAtLast.x)); + if (0.0001 < Math.abs(finAtLast.y - bodyAtLast.y)) { + String numbers = String.format("finStart=(%6.2g,%6.2g) // fin_end=(%6.2g,%6.2g) // body=(%6.2g,%6.2g)", finStart.x, finStart.y, finAtLast.x, finAtLast.y, bodyAtLast.x, bodyAtLast.y); + log.error("End point does not touch its parent body [" + symBody.getName() + "]. exception: ", + new IllegalFinPointException("End point does not touch its parent body! Expected: " + numbers)); + log.error(" .." + numbers); return false; } } - - if( intersects()){ + + if (intersects()) { log.error("found intersection in finset points!"); return false; } - + final int lastIndex = points.size() - 1; final List pts = this.points; for (int i = 0; i < lastIndex; i++) { @@ -481,9 +523,9 @@ public class FreeformFinSet extends FinSet { * * @return true if an intersection is found */ - public boolean intersects( ){ - for( int index=0; index < (this.points.size()-1); ++index ){ - if( intersects( index )){ + public boolean intersects() { + for (int index = 0; index < (this.points.size() - 1); ++index) { + if (intersects(index)) { return true; } } @@ -493,35 +535,41 @@ public class FreeformFinSet extends FinSet { /** * Check if the line segment from targetIndex to targetIndex+1 intersects with any other part of the fin. * - * - * * @return true if an intersection was found */ - private boolean intersects( final int targetIndex){ - if( (points.size()-2) < targetIndex ){ - throw new IndexOutOfBoundsException("request validate of non-existent fin edge segment: "+ targetIndex + "/"+points.size()); + private boolean intersects(final int targetIndex) { + if ((points.size() - 2) < targetIndex) { + throw new IndexOutOfBoundsException("request validate of non-existent fin edge segment: " + targetIndex + "/" + points.size()); } // (pre-check the indices above.) - Point2D.Double p1 = new Point2D.Double( points.get(targetIndex).x, points.get(targetIndex).y); - Point2D.Double p2 = new Point2D.Double( points.get(targetIndex+1).x, points.get(targetIndex+1).y); - Line2D.Double targetLine = new Line2D.Double( p1, p2); + final Point2D.Double pt1 = new Point2D.Double(points.get(targetIndex).x, points.get(targetIndex).y); + final Point2D.Double pt2 = new Point2D.Double(points.get(targetIndex + 1).x, points.get(targetIndex + 1).y); + final Line2D.Double targetLine = new Line2D.Double(pt1, pt2); - for (int comparisonIndex = 0; comparisonIndex < (points.size()-1); ++comparisonIndex ) { - if( 2 > Math.abs( targetIndex - comparisonIndex) ){ + for (int comparisonIndex = targetIndex+1; comparisonIndex < (points.size() - 1); ++comparisonIndex) { + if (2 > Math.abs(targetIndex - comparisonIndex)) { // a line segment will trivially not intersect with itself // nor can adjacent line segments intersect with each other, because they share a common endpoint. - continue; + continue; + } + final Point2D.Double pc1 = new Point2D.Double(points.get(comparisonIndex).x, points.get(comparisonIndex).y); // p1 + final Point2D.Double pc2 = new Point2D.Double(points.get(comparisonIndex + 1).x, points.get(comparisonIndex + 1).y); // p2 + + // special case for when the first and last points are co-located. + if((0==targetIndex)&&(points.size()==comparisonIndex+2)&&(IGNORE_SMALLER_THAN > Math.abs(pt1.distance(pc2)))){ + continue; } - Line2D.Double comparisonLine = new Line2D.Double( points.get(comparisonIndex).x, points.get(comparisonIndex).y, // p1 - points.get(comparisonIndex+1).x, points.get(comparisonIndex+1).y); // p2 - - if ( targetLine.intersectsLine( comparisonLine ) ) { + final Line2D.Double comparisonLine = new Line2D.Double(pc1, pc2); + if (targetLine.intersectsLine(comparisonLine)) { + log.error(String.format("Found intersection at %d-%d and %d-%d", targetIndex, targetIndex+1, comparisonIndex, comparisonIndex+1)); + log.error(String.format(" between (%g, %g) => (%g, %g)", pt1.x, pt1.y, pt2.x, pt2.y)); + log.error(String.format(" and (%g, %g) => (%g, %g)", pc1.x, pc1.y, pc2.x, pc2.y)); return true; } } return false; } - + } diff --git a/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java b/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java index 15457dd03..b5bc9f158 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FreeformFinSetTest.java @@ -117,7 +117,7 @@ public class FreeformFinSetTest extends BaseTestCase { new Coordinate( 0.0, 0.0), new Coordinate( 0.4, 1.0), new Coordinate( 0.6, 1.0), - new Coordinate( 0.8, 0.9) + new Coordinate( 0.8, 0.9) // y-value should be automaticaly adjusted to snap to body }; fins.setPoints(points); @@ -324,14 +324,16 @@ public class FreeformFinSetTest extends BaseTestCase { // assert preconditions assertEquals(Shape.ELLIPSOID, body.getType()); assertEquals(1.0, body.getLength(), EPSILON); - + + assertEquals(AxialMethod.TOP, fins.getAxialMethod()); + assertEquals(0.02, fins.getAxialOffset(), EPSILON); assertEquals(0.8, fins.getLength(), EPSILON); final Coordinate[] finPoints = fins.getFinPoints(); assertEquals(4, finPoints.length); assertEquals(finPoints[0], Coordinate.ZERO); assertEquals(finPoints[1], new Coordinate(0.4, 1.0)); assertEquals(finPoints[2], new Coordinate(0.6, 1.0)); - assertEquals(finPoints[3], new Coordinate(0.8, 0.78466912)); + assertEquals(finPoints[3], new Coordinate(0.8, 0.78466912)); // [1] [2] // +======+ // / \ [3] @@ -347,10 +349,10 @@ public class FreeformFinSetTest extends BaseTestCase { final double expectedWettedArea = 0.13397384; final double actualWettedArea = fins.getPlanformArea(); - Coordinate wcg = fins.getCG(); // relative to parent - assertEquals("Calculated fin area is wrong: ", expectedWettedArea, actualWettedArea, EPSILON); - assertEquals("Calculated fin centroid is wrong! ", 0.4793588, wcg.x, EPSILON); - assertEquals("Calculated fin centroid is wrong! ", 0.996741, wcg.y, EPSILON); + Coordinate wcg = fins.getCG(); // relative to parent + assertEquals("Calculated fin area is wrong: ", expectedWettedArea, actualWettedArea, EPSILON); + assertEquals("Calculated fin centroid is wrong! ", 0.4793588, wcg.x, EPSILON); + assertEquals("Calculated fin centroid is wrong! ", 0.996741, wcg.y, EPSILON); } @@ -425,30 +427,6 @@ public class FreeformFinSetTest extends BaseTestCase { assertEquals(0.8, added.y, 0.1); } - @Test - public void testSetPoint_firstPoint_boundsCheck() throws IllegalFinPointException { - // more transitions trigger more complicated positioning math: - Rocket rkt = createTemplateRocket(); - FreeformFinSet fins = (FreeformFinSet) rkt.getChild(0).getChild(0).getChild(0); - final int startIndex = 0; - final int lastIndex = fins.getPointCount()-1; - // assert pre-conditions - assertEquals( 1, fins.getFinCount()); - assertEquals( 3, lastIndex); - - fins.setPoint( startIndex, -1, -1); - final Coordinate act_p_0 = fins.getFinPoints()[0]; - { // first point x is restricted to the front of the parent body: - assertEquals( 0.0, act_p_0.x, EPSILON); - assertEquals( AxialMethod.TOP, fins.getAxialMethod() ); - assertEquals( 0.0, fins.getAxialOffset(), EPSILON); - } - - {// first point y is restricted to the body - assertEquals( 0.0, act_p_0.y, EPSILON); - } - } - @Test public void testSetFirstPoint() throws IllegalFinPointException { // more transitions trigger more complicated positioning math: @@ -458,7 +436,6 @@ public class FreeformFinSetTest extends BaseTestCase { final Coordinate[] initialPoints = fins.getFinPoints(); // assert pre-conditions: - assertEquals(0.4, fins.getAxialFront(), EPSILON); assertEquals(0.4, fins.getLength(), EPSILON); assertEquals(initialPoints[0], Coordinate.ZERO); assertEquals(initialPoints[1], new Coordinate(0.4, 0.2)); @@ -466,48 +443,146 @@ public class FreeformFinSetTest extends BaseTestCase { assertEquals(1.0, finMount.getLength(), EPSILON); assertEquals(0.8, finMount.getRadius(fins.getAxialFront()), EPSILON); - // for a fin at these positions: - final AxialMethod[] inputMethods = { AxialMethod.TOP, AxialMethod.TOP, AxialMethod.MIDDLE, AxialMethod.MIDDLE, AxialMethod.BOTTOM, AxialMethod.BOTTOM}; - final double[] inputOffsets = { 0.1, 0.1, 0.0, 0.0, 0.0, 0.0}; + { // case 1: + fins.setAxialOffset( AxialMethod.TOP, 0.1); + fins.setPoints(initialPoints); - // move first by this delta... - final double[] xDelta = { 0.2, -0.2, 0.1, -0.1, 0.1, -0.1}; - - // and check against these expected values: - final double[] expectedFinStartx = { 0.3, 0.0, 0.4, 0.2, 0.7, 0.5}; - final double[] expectedFinStarty = { 0.85, 1.0, 0.8, 0.9, 0.65, 0.75}; - final double[] expectedFinLength = { 0.2, 0.5, 0.3, 0.5, 0.3, 0.5}; - final double[] expectedFinOffset = { 0.3, 0.0, 0.05, -0.05, 0.0, 0.0}; - final double[] expectedMidpointOffset = { 0.2, 0.5, 0.3, 0.5, 0.3, 0.5}; - final double[] expectedFinalPointOffset = { 0.2, 0.5, 0.3, 0.5, 0.3, 0.5}; + // vvvv function under test vvvv + fins.setPoint( 0, 0.2, 0.1f); + // ^^^^ function under test ^^^^ + + assertEquals(0.3, fins.getFinFront().x, EPSILON); + assertEquals(0.85, fins.getFinFront().y, EPSILON); - for( int caseIndex=0; caseIndex < inputMethods.length; ++caseIndex ){ - // this builds a string to describe this particular iteration, and provide useful output, should it fail. - StringBuilder buf = new StringBuilder(); - buf.append(String.format("\n## For Case #%d: [%s]@%4.2f \n", caseIndex, inputMethods[caseIndex].name(), inputOffsets[caseIndex])); - buf.append(String.format(" >> setting initial point to: ( %6.4f, %6.4f) \n", xDelta[caseIndex], 0.0f)); - final String dump = buf.toString(); + final Coordinate[] postPoints = fins.getFinPoints(); + assertEquals(postPoints.length, 3); - fins.setAxialOffset( inputMethods[caseIndex], inputOffsets[caseIndex]); - fins.setPoints(initialPoints); + // middle point: + assertEquals(0.2, postPoints[1].x, EPSILON); + assertEquals(0.3, postPoints[1].y, EPSILON); + + assertEquals(0.3f, fins.getAxialOffset(), EPSILON); + assertEquals(0.2f, fins.getLength(), EPSILON); + }{ // case 2: + fins.setAxialOffset( AxialMethod.TOP, 0.1); + fins.setPoints(initialPoints); - // vvvv function under test vvvv - fins.setPoint( 0, xDelta[caseIndex], 0.1f); + // vvvv function under test vvvv + fins.setPoint( 0, -0.2, 0.1f); + // ^^^^ function under test ^^^^ + + assertEquals(0.0, fins.getFinFront().x, EPSILON); + assertEquals(1.0, fins.getFinFront().y, EPSILON); + + final Coordinate[] postPoints = fins.getFinPoints(); + assertEquals(postPoints.length, 4); + + // pseudo-front point + assertEquals(-0.1, postPoints[1].x, EPSILON); + assertEquals(0.05, postPoints[1].y, EPSILON); + + assertEquals(0.5, postPoints[2].x, EPSILON); + assertEquals(0.15, postPoints[2].y, EPSILON); + + assertEquals(0.0f, fins.getAxialOffset(), EPSILON); + assertEquals(0.5f, fins.getLength(), EPSILON); + }{ // case 3: + fins.setAxialOffset( AxialMethod.MIDDLE, 0.0); + fins.setPoints(initialPoints); + assertEquals(0.3, fins.getFinFront().x, EPSILON); + + // vvvv function under test vvvv + fins.setPoint( 0, 0.1, 0.1f); + // ^^^^ function under test ^^^^ + + assertEquals(0.4, fins.getFinFront().x, EPSILON); + assertEquals(0.8, fins.getFinFront().y, EPSILON); + + final Coordinate[] postPoints = fins.getFinPoints(); + assertEquals(postPoints.length, 3); + + // mid-point + assertEquals(0.3, postPoints[1].x, EPSILON); + assertEquals(0.25, postPoints[1].y, EPSILON); + + assertEquals(0.3, postPoints[2].x, EPSILON); + assertEquals(-0.15, postPoints[2].y, EPSILON); + + assertEquals(0.05f, fins.getAxialOffset(), EPSILON); + assertEquals(0.3f, fins.getLength(), EPSILON); + + }{ // case 4: + fins.setAxialOffset( AxialMethod.MIDDLE, 0.0); + fins.setPoints(initialPoints); + + // vvvv function under test vvvv + fins.setPoint( 0, -0.1, 0.1f); // ^^^^ function under test ^^^^ - //System.err.println(String.format("@[%d]:", caseIndex)); - //System.err.println(fins.toDebugDetail()); + assertEquals(0.2, fins.getFinFront().x, EPSILON); + assertEquals(0.9, fins.getFinFront().y, EPSILON); - assertEquals("Fin front not updated as expected..."+dump, expectedFinStartx[caseIndex], fins.getAxialFront(), EPSILON); - assertEquals("Fin front not updated as expected..."+dump, expectedFinStarty[caseIndex], fins.getFinFront().y, EPSILON); + final Coordinate[] postPoints = fins.getFinPoints(); + assertEquals(postPoints.length, 3); - final Coordinate[] postPoints = fins.getFinPoints(); - assertEquals("Middle fin point has moved!: "+dump, expectedMidpointOffset[caseIndex], postPoints[1].x, EPSILON); - assertEquals("Final fin point has moved!: "+dump, expectedFinalPointOffset[caseIndex], postPoints[2].x, EPSILON); + // mid point + assertEquals(0.5, postPoints[1].x, EPSILON); + //assertEquals(0.15, postPoints[1].y, EPSILON); + + assertEquals(0.5, postPoints[2].x, EPSILON); + //assertEquals(0.15, postPoints[2].y, EPSILON); + + assertEquals(-0.05f, fins.getAxialOffset(), EPSILON); + assertEquals(0.5f, fins.getLength(), EPSILON); + }{ // case 5: + fins.setAxialOffset( AxialMethod.BOTTOM, 0.0); + fins.setPoints(initialPoints); + + // vvvv function under test vvvv + fins.setPoint( 0, 0.1, 0.1f); + // ^^^^ function under test ^^^^ + + assertEquals(0.7, fins.getFinFront().x, EPSILON); + assertEquals(0.65, fins.getFinFront().y, EPSILON); + + final Coordinate[] postPoints = fins.getFinPoints(); + assertEquals(postPoints.length, 3); + + // mid-point + assertEquals(0.3, postPoints[1].x, EPSILON); + //assertEquals(0.05, postPoints[1].y, EPSILON); + + assertEquals(0.3, postPoints[2].x, EPSILON); + //assertEquals(0.15, postPoints[2].y, EPSILON); + + assertEquals(0.0f, fins.getAxialOffset(), EPSILON); + assertEquals(0.3f, fins.getLength(), EPSILON); + }{ // case 6: + fins.setAxialOffset( AxialMethod.BOTTOM, 0.0); + fins.setPoints(initialPoints); + assertEquals(3, fins.getPointCount()); + + // vvvv function under test vvvv + fins.setPoint( 0, -0.1, 0.1f); + // ^^^^ function under test ^^^^ + + assertEquals(0.5, fins.getFinFront().x, EPSILON); + assertEquals(0.75, fins.getFinFront().y, EPSILON); + + final Coordinate[] postPoints = fins.getFinPoints(); + assertEquals(3, postPoints.length); + + // mid-point + assertEquals(0.5, postPoints[1].x, EPSILON); + assertEquals(0.15, postPoints[1].y, EPSILON); + + assertEquals(0.5, postPoints[2].x, EPSILON); + assertEquals(-0.25, postPoints[2].y, EPSILON); + + assertEquals(0.0f, fins.getAxialOffset(), EPSILON); + assertEquals(0.5f, fins.getLength(), EPSILON); + } - assertEquals("Fin offset not updated as expected..."+dump, expectedFinOffset[caseIndex], fins.getAxialOffset(), EPSILON); - assertEquals("Fin length not updated as expected..."+dump, expectedFinLength[caseIndex], fins.getLength(), EPSILON); - } } @Test @@ -516,142 +591,299 @@ public class FreeformFinSetTest extends BaseTestCase { Transition finMount = (Transition) rkt.getChild(0).getChild(2); FreeformFinSet fins = (FreeformFinSet) rkt.getChild(0).getChild(2).getChild(0); final Coordinate[] initialPoints = fins.getFinPoints(); - + final int lastIndex = initialPoints.length - 1; + final double xf = initialPoints[lastIndex].x; + // assert pre-conditions: assertEquals(0.4, fins.getLength(), EPSILON); - assertEquals(0.4, fins.getAxialFront(), EPSILON); assertEquals(initialPoints[0], Coordinate.ZERO); assertEquals(initialPoints[1], new Coordinate(0.4, 0.2)); assertEquals(initialPoints[2], new Coordinate(0.4, -0.2)); - assertEquals(1.0, finMount.getLength(), EPSILON); + assertEquals(1.0, finMount.getLength(), EPSILON); assertEquals(0.8, finMount.getRadius(fins.getAxialFront()), EPSILON); - final int lastIndex = initialPoints.length - 1; + { // case 1: + fins.setAxialOffset( AxialMethod.TOP, 0.1); + fins.setPoints(initialPoints); + + // vvvv function under test vvvv + fins.setPoint( lastIndex, xf+0.2, -0.3f); + // ^^^^ function under test ^^^^ + + final Coordinate[] postPoints = fins.getFinPoints(); + assertEquals(postPoints.length, 3); - // set to these positions - final AxialMethod[] inputMethods = {AxialMethod.TOP, AxialMethod.TOP, AxialMethod.MIDDLE, AxialMethod.MIDDLE, AxialMethod.BOTTOM, AxialMethod.BOTTOM}; - final double[] inputOffsets = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; - - // set first point to this location... - final double[] xDelta= { 0.1, -0.1, 0.1, -0.1, 0.1, -0.1}; - - // and check against these expected values: - final double[] expectedFinStartx = { 0.0, 0.0, 0.3, 0.3, 0.6, 0.6}; - final double[] expectedFinStarty = { 1.0, 1.0, 0.85, 0.85, 0.7, 0.7}; - final double[] expectedFinOffset = { 0.0, 0.0, 0.05, -0.05, 0.0, -0.1}; - final double[] expectedFinalPointOffset = { 0.5, 0.3, 0.5, 0.3, 0.4, 0.3}; + // middle point: + assertEquals(0.4, postPoints[1].x, EPSILON); + assertEquals(0.2, postPoints[1].y, EPSILON); - for( int caseIndex=0; caseIndex < inputMethods.length; ++caseIndex ){ - final double xRequest = initialPoints[lastIndex].x + xDelta[caseIndex]; - final double yRequest = Double.NaN; // irrelevant; will be clamped to the body regardless + // last point: + assertEquals(0.6, postPoints[2].x, EPSILON); + assertEquals(-0.3, postPoints[2].y, EPSILON); - // this builds a string to describe this particular iteration, and provide useful output, should it fail. - StringBuilder buf = new StringBuilder(); - buf.append(String.format("\n## For Case #%d: [%s]@%4.2f \n", caseIndex, inputMethods[caseIndex].name(), inputOffsets[caseIndex])); - buf.append(String.format(" >> setting last point to: ( %6.4f, %6.4f) \n", xRequest, yRequest)); - final String dump = buf.toString(); + assertEquals(0.1, fins.getFinFront().x, EPSILON); + assertEquals(0.95, fins.getFinFront().y, EPSILON); + assertEquals(0.6, fins.getLength(), EPSILON); + + }{ // case 2: + fins.setAxialOffset( AxialMethod.TOP, 0.1); + fins.setPoints(initialPoints); - fins.setAxialOffset( inputMethods[caseIndex], inputOffsets[caseIndex]); - fins.setPoints(initialPoints); + // vvvv function under test vvvv + fins.setPoint( lastIndex, xf - 0.2, 0.1f); + // ^^^^ function under test ^^^^ + + final Coordinate[] postPoints = fins.getFinPoints(); + assertEquals(postPoints.length, 3); - // vvvv function under test vvvv - fins.setPoint( lastIndex, xRequest, yRequest); - // ^^^^ function under test ^^^^ + // middle point: + assertEquals(0.4, postPoints[1].x, EPSILON); + assertEquals(0.2, postPoints[1].y, EPSILON); - // System.err.println(String.format("@[%d]:", caseIndex)); - // System.err.println(fins.toDebugDetail()); + // last point: + assertEquals(0.2, postPoints[2].x, EPSILON); + assertEquals(-0.1, postPoints[2].y, EPSILON); - assertEquals("Fin offset not updated as expected..."+dump, expectedFinOffset[caseIndex], fins.getAxialOffset(), EPSILON); - assertEquals("Fin front not updated as expected..."+dump, expectedFinStartx[caseIndex], fins.getAxialFront(), EPSILON); - assertEquals("Fin front not updated as expected..."+dump, expectedFinStarty[caseIndex], fins.getFinFront().y, EPSILON); + assertEquals(0.1, fins.getFinFront().x, EPSILON); + assertEquals(0.95, fins.getFinFront().y, EPSILON); + assertEquals(0.2f, fins.getLength(), EPSILON); + + }{ // case 3: + fins.setAxialOffset( AxialMethod.MIDDLE, 0.0); + fins.setPoints(initialPoints); - final Coordinate[] postPoints = fins.getFinPoints(); - assertEquals("Middle fin point has moved!: "+dump, initialPoints[1].x, postPoints[1].x, EPSILON); - assertEquals("Final fin point has moved!: "+dump, expectedFinalPointOffset[caseIndex], postPoints[2].x, EPSILON); - assertEquals("Fin length not updated as expected..."+dump, expectedFinalPointOffset[caseIndex], fins.getLength(), EPSILON); - } + // vvvv function under test vvvv + fins.setPoint( lastIndex, xf + 0.1, 0.1f); + // ^^^^ function under test ^^^^ + + final Coordinate[] postPoints = fins.getFinPoints(); + assertEquals(postPoints.length, 3); + + // mid-point + assertEquals(0.4, postPoints[1].x, EPSILON); + assertEquals(0.2, postPoints[1].y, EPSILON); + + // last point + assertEquals(0.5, postPoints[2].x, EPSILON); + assertEquals(-0.25, postPoints[2].y, EPSILON); + + assertEquals(0.3, fins.getFinFront().x, EPSILON); + assertEquals(0.85, fins.getFinFront().y, EPSILON); + assertEquals(0.05, fins.getAxialOffset(), EPSILON); + assertEquals(0.5, fins.getLength(), EPSILON); + + }{ // case 4: + fins.setAxialOffset( AxialMethod.MIDDLE, 0.0); + fins.setPoints(initialPoints); + + // vvvv function under test vvvv + fins.setPoint( lastIndex, xf - 0.1, 0.1f); + // ^^^^ function under test ^^^^ + + final Coordinate[] postPoints = fins.getFinPoints(); + assertEquals(postPoints.length, 3); + + // mid point + assertEquals(0.4, postPoints[1].x, EPSILON); + assertEquals(0.2, postPoints[1].y, EPSILON); + + // last point + assertEquals(0.3, postPoints[2].x, EPSILON); + assertEquals(-0.15, postPoints[2].y, EPSILON); + + assertEquals(0.3, fins.getFinFront().x, EPSILON); + assertEquals(0.85, fins.getFinFront().y, EPSILON); + assertEquals(-0.05, fins.getAxialOffset(), EPSILON); + assertEquals(0.3, fins.getLength(), EPSILON); + + }{ // case 5: + fins.setAxialOffset( AxialMethod.BOTTOM, 0.0); + fins.setPoints(initialPoints); + + // vvvv function under test vvvv + fins.setPoint( lastIndex, xf + 0.1, 0.1f); + // ^^^^ function under test ^^^^ + + final Coordinate[] postPoints = fins.getFinPoints(); + assertEquals(postPoints.length, 4); + + // mid-point + assertEquals(0.4, postPoints[1].x, EPSILON); + assertEquals(0.2, postPoints[1].y, EPSILON); + + // pseudo last point + assertEquals(0.5, postPoints[2].x, EPSILON); + assertEquals(0.1, postPoints[2].y, EPSILON); + + // last point + assertEquals(0.4, postPoints[3].x, EPSILON); + assertEquals(-0.2, postPoints[3].y, EPSILON); + + assertEquals(0.6, fins.getFinFront().x, EPSILON); + assertEquals(0.7, fins.getFinFront().y, EPSILON); + assertEquals(0.0, fins.getAxialOffset(), EPSILON); + assertEquals(0.4f, fins.getLength(), EPSILON); + + }{ // case 6: + fins.setAxialOffset( AxialMethod.BOTTOM, 0.0); + fins.setPoints(initialPoints); + + // vvvv function under test vvvv + fins.setPoint( lastIndex, xf - 0.1, 0.1f); + // ^^^^ function under test ^^^^ + + final Coordinate[] postPoints = fins.getFinPoints(); + assertEquals(postPoints.length, 3); + + // mid-point + assertEquals(0.4, postPoints[1].x, EPSILON); + assertEquals(0.2, postPoints[1].y, EPSILON); + + // last point + assertEquals(0.3, postPoints[2].x, EPSILON); + assertEquals(-0.15, postPoints[2].y, EPSILON); + + assertEquals(0.6, fins.getFinFront().x, EPSILON); + assertEquals(0.7, fins.getFinFront().y, EPSILON); + assertEquals(-0.1, fins.getAxialOffset(), EPSILON); + assertEquals(0.3, fins.getLength(), EPSILON); + } + } + + @Test + public void testSetInteriorPoint() { + final Rocket rkt = createTemplateRocket(); + FreeformFinSet fins = (FreeformFinSet) rkt.getChild(0).getChild(2).getChild(0); + + { // preconditions // initial points + final Coordinate[] initialPoints = fins.getFinPoints(); + assertEquals(initialPoints[0], Coordinate.ZERO); + assertEquals(initialPoints[1], new Coordinate(0.4, 0.2)); + assertEquals(initialPoints[2], new Coordinate(0.4, -0.2)); + assertEquals(0.4, fins.getLength(), EPSILON); + }{ // preconditions // mount + Transition finMount = (Transition) rkt.getChild(0).getChild(2); + assertEquals(0.4, fins.getFinFront().x, EPSILON); + assertEquals(0.8, fins.getFinFront().y, EPSILON); + assertEquals(0.8, finMount.getRadius(fins.getAxialFront()), EPSILON); + + assertEquals(AxialMethod.TOP, fins.getAxialMethod()); + assertEquals(0.4, fins.getAxialOffset(), EPSILON); + + }{ // test target + final Coordinate p1 = fins.getFinPoints()[1]; + + // vvvv function under test vvvv + fins.setPoint( 1, p1.x + 0.1, p1.y + 0.1f); + // ^^^^ function under test ^^^^ + + }{ // postconditions + final Coordinate[] postPoints = fins.getFinPoints(); + assertEquals(postPoints.length, 3); + + // middle point: + assertEquals(0.5, postPoints[1].x, EPSILON); + assertEquals(0.3, postPoints[1].y, EPSILON); + + // last point: + assertEquals(0.4, postPoints[2].x, EPSILON); + assertEquals(-0.2, postPoints[2].y, EPSILON); + + assertEquals(0.4, fins.getLength(), EPSILON); + assertEquals(0.4, fins.getFinFront().x, EPSILON); + assertEquals(0.8, fins.getFinFront().y, EPSILON); + } + } + + @Test + public void testSetAllPoints() { + final Rocket rkt = createTemplateRocket(); + final AxialStage stage = (AxialStage) rkt.getChild(0); + + { // setup // mount + BodyTube body = new BodyTube(0.0, 1.0, 0.002); + body.setName("Phantom Body Tube"); + body.setOuterRadiusAutomatic(true); + stage.addChild(body, 2); + assertEquals(1.0, body.getOuterRadius(), EPSILON); + assertEquals(0.0, body.getLength(), EPSILON); + + FreeformFinSet fins = new FreeformFinSet(); + fins.setFinCount(4); + Coordinate[] points = new Coordinate[]{ + new Coordinate(0.0, 0.0), + new Coordinate(-0.0508, 0.007721), + new Coordinate(0.0, 0.01544), + new Coordinate(0.0254, 0.007721), + new Coordinate(1.1e-4, 0.0) // final point is within the testing thresholds :/ + }; + fins.setPoints(points); + + body.addChild(fins); + + }{ // postconditions + FreeformFinSet fins = (FreeformFinSet) rkt.getChild(0).getChild(2).getChild(0); + + final Coordinate[] postPoints = fins.getFinPoints(); + assertEquals(6, postPoints.length); + + // p1 + assertEquals(-0.0508, postPoints[1].x, EPSILON); + assertEquals(0.007721, postPoints[1].y, EPSILON); + + // p2 + assertEquals(0.0, postPoints[2].x, EPSILON); + assertEquals(0.01544, postPoints[2].y, EPSILON); + + // p3 + assertEquals(0.0254, postPoints[3].x, EPSILON); + assertEquals(0.007721, postPoints[3].y, EPSILON); + + // p4 + assertEquals(0.00011, postPoints[4].x, EPSILON); + assertEquals(0.0, postPoints[4].y, EPSILON); + + // p/last: generated by loading code: + assertEquals(0.0, postPoints[5].x, EPSILON); + assertEquals(0.0, postPoints[5].y, EPSILON); + + assertEquals(0.0, fins.getLength(), EPSILON); + assertEquals(0.0, fins.getFinFront().x, EPSILON); + assertEquals(1.0, fins.getFinFront().y, EPSILON); + } } @Test public void testSetFirstPoint_testNonIntersection() { final Rocket rkt = createTemplateRocket(); - FreeformFinSet fins = (FreeformFinSet) rkt.getChild(0).getChild(2).getChild(0); - - assertEquals( 1, fins.getFinCount()); - final int lastIndex = fins.getPointCount()-1; - assertEquals( 2, lastIndex); - final double initialOffset = fins.getAxialOffset(); - assertEquals( 0.4, initialOffset, EPSILON); // pre-condition + final FreeformFinSet fins = (FreeformFinSet) rkt.getChild(0).getChild(2).getChild(0); + final Transition mount = (Transition) rkt.getChild(0).getChild(2); + + assertEquals( 1, fins.getFinCount()); + assertEquals( 3, fins.getPointCount()); + assertEquals( AxialMethod.TOP, fins.getAxialMethod()); + assertEquals( 0.4, fins.getAxialOffset(), EPSILON); // pre-condition + assertEquals( 1.0, mount.getLength(), EPSILON); - final double attemptedDelta = 0.6; - fins.setPoint( 0, attemptedDelta, 0); // fin offset: 0.4 -> 0.59 (just short of prev fin end) // fin end: 0.4 ~> min root chord - + // vv Test Target vv + fins.setPoint( 0, 0.6, 0); + // ^^ Test Target ^^ + assertEquals(fins.getFinPoints()[ 0], Coordinate.ZERO); // setting the first point actually offsets the whole fin by that amount: - final double expFinOffset = 0.79; + final double expFinOffset = 1.0; assertEquals("Resultant fin offset does not match!", expFinOffset, fins.getAxialOffset(), EPSILON); - // SHOULD NOT CHANGE (in this case): - Coordinate actualLastPoint = fins.getFinPoints()[ lastIndex]; - assertEquals("last point did not adjust correctly: ", FreeformFinSet.MIN_ROOT_CHORD, actualLastPoint.x, EPSILON); - assertEquals("last point did not adjust correctly: ", -0.005, actualLastPoint.y, EPSILON); // magic number - assertEquals("New fin length is wrong: ", FreeformFinSet.MIN_ROOT_CHORD, fins.getLength(), EPSILON); + assertEquals( 3, fins.getPointCount()); + Coordinate actualLastPoint = fins.getFinPoints()[2]; + assertEquals("last point did not adjust correctly: ", 0f, actualLastPoint.x, EPSILON); + assertEquals("last point did not adjust correctly: ", 0f, actualLastPoint.y, EPSILON); + assertEquals("New fin length is wrong: ", 0.0, fins.getLength(), EPSILON); } - - @Test - public void testSetLastPoint_TubeBody() throws IllegalFinPointException { - Rocket rkt = createTemplateRocket(); - - // combine the simple case with the complicated to ensure that the simple case is - // flagged, tested, and debugged before running the more complicated case... - { // setting points on a Tube Body is the simpler case. Test this first: - FreeformFinSet fins = (FreeformFinSet) rkt.getChild(0).getChild(1).getChild(0); - - // verify preconditions - assertEquals(AxialMethod.BOTTOM, fins.getAxialMethod()); - assertEquals(0.0, fins.getAxialOffset(), EPSILON); - - // last point is restricted to the body - final int lastIndex = fins.getPointCount()-1; - final Coordinate expectedFinalPoint = new Coordinate( 1.0, 0.0, 0.0); - - // vvvv function under test vvvv - fins.setPoint( lastIndex, 10.0, Double.NaN); - // ^^^^ function under test ^^^^ - - Coordinate actualFinalPoint = fins.getFinPoints()[lastIndex]; - assertEquals( expectedFinalPoint.x, actualFinalPoint.x, EPSILON); - assertEquals( expectedFinalPoint.y, actualFinalPoint.y, EPSILON); - } - - { // a transitions will trigger more complicated positioning math: - FreeformFinSet fins = (FreeformFinSet) rkt.getChild(0).getChild(2).getChild(0); - final int lastIndex = fins.getPointCount()-1; - - Coordinate expectedLastPoint; - Coordinate actualLastPoint; - { // this is where the point starts off at: - actualLastPoint = fins.getFinPoints()[lastIndex]; - assertEquals( 0.4, actualLastPoint.x, EPSILON); - assertEquals( -0.2, actualLastPoint.y, EPSILON); - } - - { // (1): move point within bounds - // move last point, and verify that its y-value is still clamped to the body ( at the new location) - expectedLastPoint = new Coordinate( 0.6, -0.3, 0.0); - fins.setPoint(lastIndex, 0.6, 0.0); // w/ incorrect y-val. The function should correct the y-value as above. - - actualLastPoint = fins.getFinPoints()[lastIndex]; - assertEquals( expectedLastPoint.x, actualLastPoint.x, EPSILON); - assertEquals( expectedLastPoint.y, actualLastPoint.y, EPSILON); - } - } - } - @Test public void testSetPoint_otherPoint() throws IllegalFinPointException { // combine the simple case with the complicated to ensure that the simple case is flagged, tested, and debugged before running the more complicated case... @@ -785,7 +1017,7 @@ public class FreeformFinSetTest extends BaseTestCase { } @Test - public void testForNonIntersection() { + public void testForIntersection_false() { final Rocket rkt = new Rocket(); final AxialStage stg = new AxialStage(); rkt.addChild(stg); @@ -811,10 +1043,10 @@ public class FreeformFinSetTest extends BaseTestCase { body.addChild(fins); assertFalse( " Fin detects false positive intersection in fin points: ", fins.intersects()); - } - + } + @Test - public void testForIntersection() { + public void testForIntersection_true() { final Rocket rkt = new Rocket(); final AxialStage stg = new AxialStage(); rkt.addChild(stg); @@ -849,6 +1081,53 @@ public class FreeformFinSetTest extends BaseTestCase { assertThat( "Fin Set failed to detect an intersection! ", p1.y, not(equalTo(initPoints[1].y))); } + @Test + public void testForIntersectionAtFirstLast() { + final Rocket rkt = new Rocket(); + final AxialStage stg = new AxialStage(); + rkt.addChild(stg); + BodyTube body = new BodyTube(2.0, 0.01); + stg.addChild(body); + // + // An obviously intersecting fin: + // [2] +---+ [1] + // | / + // | / + // [0]|/ [3] + // +---+-----+---+ + // = +x => + FreeformFinSet fins = new FreeformFinSet(); + fins.setFinCount(1); + Coordinate[] initPoints = new Coordinate[] { + new Coordinate(0, 0), + new Coordinate(0, 1), + new Coordinate(1, 1), + new Coordinate(0, 0) + }; + // this line throws an exception? + fins.setPoints(initPoints); + body.addChild(fins); + + final Coordinate[] finPoints = fins.getFinPoints(); + + // p0 + assertEquals("incorrect body points! ", 0., finPoints[0].x, EPSILON); + assertEquals("incorrect body points! ", 0., finPoints[0].y, EPSILON); + + // p1 + assertEquals("incorrect body points! ", 0., finPoints[1].x, EPSILON); + assertEquals("incorrect body points! ", 1., finPoints[1].y, EPSILON); + + // p2 + assertEquals("incorrect body points! ", 1., finPoints[2].x, EPSILON); + assertEquals("incorrect body points! ", 1., finPoints[2].y, EPSILON); + + // pf + assertEquals("incorrect body points! ", 0., finPoints[3].x, EPSILON); + assertEquals("incorrect body points! ", 0., finPoints[3].y, EPSILON); + } + + @Test public void testWildmanVindicatorShape() throws Exception { @@ -953,20 +1232,66 @@ public class FreeformFinSetTest extends BaseTestCase { final Rocket rocket = createTemplateRocket(); final Transition body = (Transition)rocket.getChild(0).getChild(0); final FinSet fins = (FreeformFinSet) body.getChild(0); - + final Coordinate finFront = fins.getFinFront(); final Coordinate[] finPoints = fins.getFinPoints(); - // translate from fin-frame to body-frame - final Coordinate[] finPointsFromBody = FinSet.translatePoints( fins.getFinPoints(), finFront.x, finFront.y ); - final Coordinate expectedStartPoint = finPointsFromBody[0]; - final Coordinate expectedEndPoint = finPointsFromBody[ finPoints.length-1]; + + + { // fin points (relative to fin) // preconditions + assertEquals(4, finPoints.length); + + assertEquals("incorrect body points! ", 0f, finPoints[0].x, EPSILON); + assertEquals("incorrect body points! ", 0f, finPoints[0].y, EPSILON); - { // body points (relative to body) - final Coordinate[] bodyPoints = fins.getBodyPoints(); - + assertEquals("incorrect body points! ", 0.8, finPoints[3].x, EPSILON); + +// ?? SMOKING GUN: +// ?? is this y-value of the fin not getting snapped to the body? + + assertEquals(body.getRadius(0.8+finFront.x) - finFront.y, finPoints[3].y, EPSILON); + + assertEquals("incorrect body points! ", 0.78466912, finPoints[3].y, EPSILON); + + }{ // body points (relative to fin) + final Coordinate[] rootPoints = fins.getRootPoints(); + assertEquals(101, rootPoints.length); + final int lastIndex = 100; + // trivial, and uninteresting: - assertEquals("incorrect body points! ", expectedStartPoint.x, bodyPoints[0].x, EPSILON); - assertEquals("incorrect body points! ", expectedStartPoint.y, bodyPoints[0].y, EPSILON); + assertEquals("incorrect body points! ", finPoints[0].x, rootPoints[0].x, EPSILON); + assertEquals("incorrect body points! ", finPoints[0].y, rootPoints[0].y, EPSILON); + + // n.b.: This should match EXACTLY the end point of the fin. (in fin coordinates) + assertEquals("incorrect body points! ", finPoints[finPoints.length -1].x, rootPoints[lastIndex].x, EPSILON); + assertEquals("incorrect body points! ", finPoints[finPoints.length -1].y, rootPoints[lastIndex].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? ", 101, rootPoints.length ); + + final int[] testIndices = { 2, 5, 61, 88}; + final double[] expectedX = { 0.016, 0.04, 0.488, 0.704}; + + for( int testCase = 0; testCase < testIndices.length; testCase++){ + final int testIndex = testIndices[testCase]; + assertEquals(String.format("Root points @ %d :: x coordinate mismatch!", testIndex), + expectedX[testCase], rootPoints[testIndex].x, EPSILON); + assertEquals(String.format("Root points @ %d :: y coordinate mismatch!", testIndex), + body.getRadius(rootPoints[testIndex].x + finFront.x) - finFront.y, rootPoints[testIndex].y, EPSILON); + } + } + }{ // 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[] 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); // 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); @@ -988,33 +1313,6 @@ public class FreeformFinSetTest extends BaseTestCase { } } } - { // body points (relative to fin) - final Coordinate[] rootPoints = fins.getRootPoints(); - - // trivial, and uninteresting: - assertEquals("incorrect body points! ", finPoints[0].x, rootPoints[0].x, EPSILON); - assertEquals("incorrect body points! ", finPoints[0].y, rootPoints[0].y, EPSILON); - - // n.b.: This should match EXACTLY the end point of the fin. (in fin coordinates) - assertEquals("incorrect body points! ", finPoints[finPoints.length-1].x, rootPoints[rootPoints.length-1].x, EPSILON); - assertEquals("incorrect body points! ", finPoints[finPoints.length-1].y, rootPoints[rootPoints.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? ", 101, rootPoints.length ); - - final int[] testIndices = { 2, 5, 61, 88}; - final double[] expectedX = { 0.016, 0.04, 0.488, 0.704}; - - for( int testCase = 0; testCase < testIndices.length; testCase++){ - final int testIndex = testIndices[testCase]; - assertEquals(String.format("Root points @ %d :: x coordinate mismatch!", testIndex), - expectedX[testCase], rootPoints[testIndex].x, EPSILON); - assertEquals(String.format("Root points @ %d :: y coordinate mismatch!", testIndex), - body.getRadius(rootPoints[testIndex].x + finFront.x) - finFront.y, rootPoints[testIndex].y, EPSILON); - } - } - } } @Test