diff --git a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java index ffd691746..4e5b65552 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FinSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FinSet.java @@ -1,6 +1,5 @@ package net.sf.openrocket.rocketcomponent; -import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -12,6 +11,7 @@ import net.sf.openrocket.material.Material; import net.sf.openrocket.rocketcomponent.position.AxialPositionable; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.ArrayUtils; +import net.sf.openrocket.util.BoundingBox; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.Transformation; @@ -168,7 +168,6 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab if (n > 8) n = 8; fins = n; - finRotation = Transformation.rotate_x(2 * Math.PI / fins); fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); } @@ -186,7 +185,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab /** * Sets the base rotation amount of the first fin. - * @param r The base rotation amount. + * @param r The base rotation in radians */ public void setBaseRotation(double r) { setAngularOffset(r); @@ -596,59 +595,18 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab */ @Override public Collection getComponentBounds() { - Collection bounds = new ArrayList(8); + BoundingBox singleFinBounds= new BoundingBox( getFinPoints()); + final double finLength = singleFinBounds.max.x; + final double finHeight = singleFinBounds.max.y; - // should simply return this component's bounds in this component's body frame. + BoundingBox compBox = new BoundingBox( getComponentLocations() ); - double x_min = Double.MAX_VALUE; - double x_max = Double.MIN_VALUE; - double r_max = 0.0; + BoundingBox finSetBox = new BoundingBox( compBox.min.sub( 0, finHeight, finHeight ), + compBox.max.add( finLength, finHeight, finHeight )); - for (Coordinate point : getFinPoints()) { - double hypot = MathUtil.hypot(point.y, point.z); - double x_cur = point.x; - if (x_min > x_cur) { - x_min = x_cur; - } - if (x_max < x_cur) { - x_max = x_cur; - } - if (r_max < hypot) { - r_max = hypot; - } - } - - addBoundingBox(bounds, x_min, x_max, r_max); - return bounds; + return finSetBox.toCollection(); } -// /** -// * Adds the 2d-coordinate bound (x,y) to the collection for both z-components and for -// * all fin rotations. -// */ -// private void addFinBound(Collection set, double x, double y) { -// Coordinate c; -// int i; -// -// c = new Coordinate(x, y, thickness / 2); -// c = baseRotation.transform(c); -// set.add(c); -// for (i = 1; i < fins; i++) { -// c = finRotation.transform(c); -// set.add(c); -// } -// -// c = new Coordinate(x, y, -thickness / 2); -// c = baseRotation.transform(c); -// set.add(c); -// for (i = 1; i < fins; i++) { -// c = finRotation.transform(c); -// set.add(c); -// } -// } -// -// - @Override public void componentChanged(ComponentChangeEvent e) { if (e.isAerodynamicChange()) { @@ -672,8 +630,7 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab s = this.getParent(); while (s != null) { if (s instanceof SymmetricComponent) { - double x = this.toRelative(new Coordinate(0, 0, 0), s)[0].x; - return ((SymmetricComponent) s).getRadius(x); + return ((SymmetricComponent) s).getRadius( this.position.x); } s = s.getParent(); } @@ -747,16 +704,6 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab @Override public double getAngularOffset() { - ComponentAssembly stage = this.getAssembly(); - if( PodSet.class.isAssignableFrom( stage.getClass() )){ - PodSet assembly= (PodSet)stage; - return assembly.getAngularOffset() + baseRotationValue; - }else if( ParallelStage.class.isAssignableFrom( stage.getClass())){ - ParallelStage assembly = (ParallelStage)stage; - log.debug("detected p-stage with position: "+assembly.getAngularOffset()); - return assembly.getAngularOffset() + baseRotationValue; - } - return baseRotationValue; } @@ -782,12 +729,32 @@ public abstract class FinSet extends ExternalComponent implements RingInstanceab double[] result = new double[ getFinCount()]; for( int i=0; i motors = new HashMap(); private int boundsModID = -1; - private ArrayList cachedBounds = new ArrayList(); + private BoundingBox cachedBounds = new BoundingBox(); private double cachedLength = -1; private int refLengthModID = -1; @@ -159,8 +160,8 @@ public class FlightConfiguration implements FlightConfigurableParameter getBounds() { if (rocket.getModID() != boundsModID) { boundsModID = rocket.getModID(); - cachedBounds.clear(); - double minX = Double.POSITIVE_INFINITY, maxX = Double.NEGATIVE_INFINITY; +// System.err.println(String.format(">> generating bounds for configuration: %s (%d)(%s)", getName(), this.instanceNumber, getId() )); + + BoundingBox bounds = new BoundingBox(); + for (RocketComponent component : this.getActiveComponents()) { - for (Coordinate coord : component.getComponentBounds()) { - cachedBounds.add(coord); - if (coord.x < minX){ - minX = coord.x; - }else if (coord.x > maxX){ - maxX = coord.x; - } - } + BoundingBox componentBounds = new BoundingBox( component.getComponentBounds() ); + + bounds.compare( componentBounds ); + +// System.err.println(String.format(" [%s] %s >> %s", component.getName(), componentBounds.toString(), bounds.toString() )); } - if (Double.isInfinite(minX) || Double.isInfinite(maxX)) { - cachedLength = 0; - } else { - cachedLength = maxX - minX; - } + cachedLength = bounds.span().x; + + cachedBounds.compare( bounds ); } - return cachedBounds.clone(); + + return cachedBounds.toCollection(); } - /** * Returns the length of the rocket configuration, from the foremost bound X-coordinate * to the aft-most X-coordinate. The value is cached. diff --git a/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java b/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java index 92b5719e1..216129cd5 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java +++ b/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java @@ -145,7 +145,7 @@ public class InnerTube extends ThicknessRingComponent implements Clusterable, Ra @Override public int getInstanceCount() { - return this.getLocations().length; + return cluster.getClusterCount(); } @Override @@ -226,23 +226,12 @@ public class InnerTube extends ThicknessRingComponent implements Clusterable, Ra @Override public Coordinate[] getInstanceOffsets(){ - int instanceCount = getClusterCount(); - if (instanceCount == 1) - return super.getInstanceOffsets(); + if ( 1 == getClusterCount()) + return new Coordinate[] { Coordinate.ZERO }; List points = getClusterPoints(); - if (points.size() != instanceCount) { - throw new BugException("Inconsistent cluster configuration, cluster count(" + instanceCount + - ") != point count(" + points.size()+")"); - } - - Coordinate[] newArray = new Coordinate[ instanceCount]; - for (int instanceNumber = 0; instanceNumber < instanceCount; instanceNumber++) { - newArray[ instanceNumber] = this.position.add( points.get(instanceNumber)); - } - - return newArray; + return points.toArray( new Coordinate[ points.size()]); } // @Override diff --git a/core/src/net/sf/openrocket/rocketcomponent/Instanceable.java b/core/src/net/sf/openrocket/rocketcomponent/Instanceable.java index 59703615c..47d5c5913 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/Instanceable.java +++ b/core/src/net/sf/openrocket/rocketcomponent/Instanceable.java @@ -3,17 +3,21 @@ package net.sf.openrocket.rocketcomponent; import net.sf.openrocket.util.Coordinate; public interface Instanceable { - - /** - * Note: this.getLocation().length == this.getInstanceCount() should ALWAYS be true. If getInstanceCount() returns anything besides 1, - * this function should be override as well. - * - * Note: This is function has a concrete implementation in RocketComponent.java ... it is included here only as a reminder. - * - * @return coordinates of each instance of this component -- specifically the front center of each instance in global coordinates - */ + + @Deprecated public Coordinate[] getLocations(); + /** + * Returns vector coordinates of each instance of this component relative to this component's parent + * + * Note: this.getOffsets().length == this.getInstanceCount() should ALWAYS be true. + * If getInstanceCount() returns anything besides 1 this function should be overridden as well. + * + * + * @return coordinates location of each instance relative to component's parent + */ + public Coordinate[] getInstanceLocations(); + /** * Returns vector coordinates of each instance of this component relative to this component's reference point (typically front center) * diff --git a/core/src/net/sf/openrocket/rocketcomponent/LaunchLug.java b/core/src/net/sf/openrocket/rocketcomponent/LaunchLug.java index c10e147c7..6d0f30e65 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/LaunchLug.java +++ b/core/src/net/sf/openrocket/rocketcomponent/LaunchLug.java @@ -127,17 +127,15 @@ public class LaunchLug extends ExternalComponent implements Coaxial, LineInstanc return ComponentPreset.Type.LAUNCH_LUG; } - @Override public Coordinate[] getInstanceOffsets(){ Coordinate[] toReturn = new Coordinate[this.getInstanceCount()]; - final double xOffset = this.position.x; final double yOffset = Math.cos(radialDirection) * (radialDistance); final double zOffset = Math.sin(radialDirection) * (radialDistance); for ( int index=0; index < this.getInstanceCount(); index++){ - toReturn[index] = new Coordinate(xOffset + index*this.instanceSeparation, yOffset, zOffset); + toReturn[index] = new Coordinate(index*this.instanceSeparation, yOffset, zOffset); } return toReturn; diff --git a/core/src/net/sf/openrocket/rocketcomponent/ParallelStage.java b/core/src/net/sf/openrocket/rocketcomponent/ParallelStage.java index 4f7da4848..c234a1b92 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/ParallelStage.java +++ b/core/src/net/sf/openrocket/rocketcomponent/ParallelStage.java @@ -128,28 +128,7 @@ public class ParallelStage extends AxialStage implements FlightConfigurableCompo public double getRadialOffset() { return this.radialPosition_m; } - - @Override - public Coordinate[] getInstanceOffsets(){ - checkState(); - - Coordinate center = Coordinate.ZERO; - Coordinate[] toReturn = new Coordinate[this.instanceCount]; - final double[] angles = getInstanceAngles(); - for (int instanceNumber = 0; instanceNumber < this.instanceCount; instanceNumber++) { - final double curY = this.radialPosition_m * Math.cos(angles[instanceNumber]); - final double curZ = this.radialPosition_m * Math.sin(angles[instanceNumber]); - toReturn[instanceNumber] = center.add(0, curY, curZ ); - } - - return toReturn; - } - - @Override - public double getInstanceAngleIncrement(){ - return this.angularSeparation; - } - + @Override public double[] getInstanceAngles(){ final double baseAngle = getAngularOffset(); @@ -163,23 +142,21 @@ public class ParallelStage extends AxialStage implements FlightConfigurableCompo return result; } + @Override + public double getInstanceAngleIncrement(){ + return this.angularSeparation; + } + @Override - public Coordinate[] getLocations() { - if (null == this.parent) { - throw new BugException(" Attempted to get absolute position Vector of a Stage without a parent. "); - } + public Coordinate[] getInstanceOffsets(){ + checkState(); - Coordinate[] parentInstances = this.parent.getLocations(); - if (1 != parentInstances.length) { - throw new BugException(" OpenRocket does not (yet) support external stages attached to external stages. " + - "(assumed reason for getting multiple parent locations into an external stage.)"); - } - - final Coordinate center = parentInstances[0].add( this.position); - Coordinate[] instanceLocations = this.getInstanceOffsets(); - Coordinate[] toReturn = new Coordinate[ instanceLocations.length]; - for( int i = 0; i < toReturn.length; i++){ - toReturn[i] = center.add( instanceLocations[i]); + Coordinate[] toReturn = new Coordinate[this.instanceCount]; + final double[] angles = getInstanceAngles(); + for (int instanceNumber = 0; instanceNumber < this.instanceCount; instanceNumber++) { + final double curY = this.radialPosition_m * Math.cos(angles[instanceNumber]); + final double curZ = this.radialPosition_m * Math.sin(angles[instanceNumber]); + toReturn[instanceNumber] = new Coordinate(0, curY, curZ ); } return toReturn; diff --git a/core/src/net/sf/openrocket/rocketcomponent/PodSet.java b/core/src/net/sf/openrocket/rocketcomponent/PodSet.java index b83bc9cae..65c2fec1e 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/PodSet.java +++ b/core/src/net/sf/openrocket/rocketcomponent/PodSet.java @@ -76,22 +76,7 @@ public class PodSet extends ComponentAssembly implements RingInstanceable { public boolean isCompatible(Class type) { return BodyComponent.class.isAssignableFrom(type); } - - @Override - public Coordinate[] getInstanceOffsets(){ - checkState(); - - Coordinate center = Coordinate.ZERO; - Coordinate[] toReturn = new Coordinate[this.instanceCount]; - final double[] angles = getInstanceAngles(); - for (int instanceNumber = 0; instanceNumber < this.instanceCount; instanceNumber++) { - final double curY = this.radialPosition_m * Math.cos(angles[instanceNumber]); - final double curZ = this.radialPosition_m * Math.sin(angles[instanceNumber]); - toReturn[instanceNumber] = center.add(0, curY, curZ ); - } - - return toReturn; - } + @Override public double getInstanceAngleIncrement(){ @@ -112,30 +97,18 @@ public class PodSet extends ComponentAssembly implements RingInstanceable { } @Override - public Coordinate[] getLocations() { - if (null == this.parent) { - throw new BugException(" Attempted to get absolute position Vector of a Stage without a parent. "); - } - - if (this.isAfter()) { - return super.getLocations(); - } else { - Coordinate[] parentInstances = this.parent.getLocations(); - if (1 != parentInstances.length) { - throw new BugException(" OpenRocket does not (yet) support external stages attached to external stages. " + - "(assumed reason for getting multiple parent locations into an external stage.)"); - } - - final Coordinate center = parentInstances[0].add( this.position); - Coordinate[] instanceLocations = this.getInstanceOffsets(); - Coordinate[] toReturn = new Coordinate[ instanceLocations.length]; - for( int i = 0; i < toReturn.length; i++){ - toReturn[i] = center.add( instanceLocations[i]); - } - - return toReturn; + public Coordinate[] getInstanceOffsets(){ + checkState(); + + Coordinate[] toReturn = new Coordinate[this.instanceCount]; + final double[] angles = getInstanceAngles(); + for (int instanceNumber = 0; instanceNumber < this.instanceCount; instanceNumber++) { + final double curY = this.radialPosition_m * Math.cos(angles[instanceNumber]); + final double curZ = this.radialPosition_m * Math.sin(angles[instanceNumber]); + toReturn[instanceNumber] = new Coordinate(0, curY, curZ ); } + return toReturn; } @Override diff --git a/core/src/net/sf/openrocket/rocketcomponent/RailButton.java b/core/src/net/sf/openrocket/rocketcomponent/RailButton.java index df7b2e872..85d925d26 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RailButton.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RailButton.java @@ -196,24 +196,15 @@ public class RailButton extends ExternalComponent implements LineInstanceable { fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE); } - -// @Override -// public void setPositionValue(double value) { -// super.setPositionValue(value); -// fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); -// } - - @Override public Coordinate[] getInstanceOffsets(){ Coordinate[] toReturn = new Coordinate[this.getInstanceCount()]; - final double xOffset = this.position.x; final double yOffset = Math.cos(this.angle_rad) * ( this.radialDistance_m ); final double zOffset = Math.sin(this.angle_rad) * ( this.radialDistance_m ); for ( int index=0; index < this.getInstanceCount(); index++){ - toReturn[index] = new Coordinate(xOffset + index*this.instanceSeparation, yOffset, zOffset); + toReturn[index] = new Coordinate(index*this.instanceSeparation, yOffset, zOffset); } return toReturn; diff --git a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java index 0638bfc7f..cfa7f56bf 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java +++ b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -210,6 +210,11 @@ public class Rocket extends ComponentAssembly { return (AxialStage) children.get( children.size()-1 ); } + @Override + public int getStageNumber() { + // invalid, error value + return -1; + } private int getNewStageNumber() { int guess = 0; while (stageMap.containsKey(guess)) { diff --git a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java index 8b78e9934..9a575acf2 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java @@ -1174,20 +1174,47 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab * of the passed array and return the array itself. */ // @Override Me ! + public Coordinate[] getInstanceLocations(){ + int instanceCount = getInstanceCount(); + if( 0 == instanceCount ){ + return new Coordinate[]{this.position}; + } + + checkState(); + + Coordinate center = this.position; + Coordinate[] offsets = getInstanceOffsets(); + + Coordinate[] locations= new Coordinate[offsets.length]; + for (int instanceNumber = 0; instanceNumber < locations.length; instanceNumber++) { + locations[instanceNumber] = center.add( offsets[instanceNumber] ); + } + + return locations; + } + public Coordinate[] getInstanceOffsets(){ - return new Coordinate[]{this.position}; + // According to the language specification, Java will initialized double values to 0.0 + return new Coordinate[]{Coordinate.ZERO}; + } + + // this is an inefficient way to calculate all of the locations; + // it also breaks locality, (i.e. is a rocket-wide calculation ) + @Deprecated + public Coordinate[] getLocations() { + return getComponentLocations(); } - public Coordinate[] getLocations() { + public Coordinate[] getComponentLocations() { if (null == this.parent) { // == improperly initialized components OR the root Rocket instance - return new Coordinate[] { Coordinate.ZERO }; + return getInstanceOffsets(); } else { Coordinate[] parentPositions = this.parent.getLocations(); int parentCount = parentPositions.length; // override .getInstanceOffsets() in the subclass you want to fix. - Coordinate[] instanceOffsets = this.getInstanceOffsets(); + Coordinate[] instanceOffsets = this.getInstanceLocations(); int instanceCount = instanceOffsets.length; // usual case optimization @@ -1209,6 +1236,10 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab } } + public double[] getInstanceAngles(){ + return new double[getInstanceCount()]; + } + /////////// Coordinate changes /////////// /** @@ -1954,11 +1985,11 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab /** - * Helper method to add eight bounds in a box around the rocket centerline. This box will be (x_max - x_min) long, and 2*r wide/high. + * Helper method to add two points on opposite corners of a box around the rocket centerline. This box will be (x_max - x_min) long, and 2*r wide/high. */ protected static final void addBoundingBox(Collection bounds, double x_min, double x_max, double r) { - addBound(bounds, x_min, r); - addBound(bounds, x_max, r); + bounds.add(new Coordinate(x_min, -r, -r)); + bounds.add(new Coordinate(x_max, r, r)); } /** diff --git a/core/src/net/sf/openrocket/util/BoundingBox.java b/core/src/net/sf/openrocket/util/BoundingBox.java new file mode 100644 index 000000000..b6b30e12c --- /dev/null +++ b/core/src/net/sf/openrocket/util/BoundingBox.java @@ -0,0 +1,91 @@ +package net.sf.openrocket.util; + +import java.util.ArrayList; +import java.util.Collection; + +public class BoundingBox { + public Coordinate min; + public Coordinate max; + + public BoundingBox() { + min = Coordinate.MAX.setWeight( 0.0); + max = Coordinate.MIN.setWeight( 0.0); + } + + public BoundingBox( Coordinate _min, Coordinate _max) { + this(); + this.min = _min.clone(); + this.max = _max.clone(); + } + + public BoundingBox( Coordinate[] list ) { + this(); + this.compare( list); + } + + public BoundingBox( Collection list ) { + this(); + this.compare( list.toArray( new Coordinate[0] )); + } + + @Override + public BoundingBox clone() { + return new BoundingBox( this.min, this.max ); + } + + public void compare( Coordinate c ) { + compare_against_min(c); + compare_against_max(c); + } + + protected void compare_against_min( Coordinate c ) { + if( min.x > c.x ) + min = min.setX( c.x ); + if( min.y > c.y ) + min = min.setY( c.y ); + if( min.z > c.z ) + min = min.setZ( c.z ); + } + + protected void compare_against_max( Coordinate c) { + if( max.x < c.x ) + max = max.setX( c.x ); + if( max.y < c.y ) + max = max.setY( c.y ); + if( max.z < c.z ) + max = max.setZ( c.z ); + } + + public BoundingBox compare( Coordinate[] list ) { + for( Coordinate c: list ) { + compare( c ); + } + return this; + } + + public void compare( BoundingBox other ) { + compare_against_min( other.min); + compare_against_max( other.max); + } + + public Coordinate span() { return max.sub( min ); } + + public Coordinate[] toArray() { + return new Coordinate[] { this.min, this.max }; + } + + public Collection toCollection(){ + Collection toReturn = new ArrayList(); + toReturn.add( this.max); + toReturn.add( this.min); + return toReturn; + } + + @Override + public String toString() { +// return String.format("[( %6.4f, %6.4f, %6.4f) < ( %6.4f, %6.4f, %6.4f)]", + return String.format("[( %g, %g, %g) < ( %g, %g, %g)]", + min.x, min.y, min.z, + max.x, max.y, max.z ); + } +} diff --git a/core/src/net/sf/openrocket/util/Coordinate.java b/core/src/net/sf/openrocket/util/Coordinate.java index 9032c8e5c..cd4b2844b 100644 --- a/core/src/net/sf/openrocket/util/Coordinate.java +++ b/core/src/net/sf/openrocket/util/Coordinate.java @@ -63,6 +63,8 @@ public final class Coordinate implements Cloneable, Serializable { public static final Coordinate NUL = new Coordinate(0, 0, 0, 0); public static final Coordinate NaN = new Coordinate(Double.NaN, Double.NaN, Double.NaN, Double.NaN); + public static final Coordinate MAX = new Coordinate(Double.MAX_VALUE,Double.MAX_VALUE,Double.MAX_VALUE,Double.MAX_VALUE); + public static final Coordinate MIN = new Coordinate(Double.MIN_VALUE,Double.MIN_VALUE,Double.MIN_VALUE,Double.MIN_VALUE); public final double x, y, z; public final double weight; diff --git a/core/src/net/sf/openrocket/util/Transformation.java b/core/src/net/sf/openrocket/util/Transformation.java index 8656a4f25..2b0419789 100644 --- a/core/src/net/sf/openrocket/util/Transformation.java +++ b/core/src/net/sf/openrocket/util/Transformation.java @@ -242,6 +242,19 @@ public class Transformation implements java.io.Serializable { System.out.println(); } + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + + sb.append(String.format("[%3.2f %3.2f %3.2f] [%3.2f]\n", + rotation[X][X],rotation[X][Y],rotation[X][Z],translate.x)); + sb.append(String.format("[%3.2f %3.2f %3.2f] + [%3.2f]\n", + rotation[Y][X],rotation[Y][Y],rotation[Y][Z],translate.y)); + sb.append(String.format("[%3.2f %3.2f %3.2f] [%3.2f]\n", + rotation[Z][X],rotation[Z][Y],rotation[Z][Z],translate.z)); + return sb.toString(); + } + @Override public boolean equals(Object other) { diff --git a/core/test/net/sf/openrocket/rocketcomponent/FinSetTest.java b/core/test/net/sf/openrocket/rocketcomponent/FinSetTest.java index a0595e9f7..18daf211f 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/FinSetTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/FinSetTest.java @@ -57,6 +57,83 @@ public class FinSetTest extends BaseTestCase { } + @Test + public void testInstancePoints_PI_2_BaseRotation() { + // This is a simple square fin with sides of 1.0. + TrapezoidFinSet fins = new TrapezoidFinSet(); + fins.setFinCount(4); + fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005); + fins.setBaseRotation( Math.PI/2 ); + + BodyTube body = new BodyTube(1.0, 0.05 ); + body.addChild( fins ); + + Coordinate[] points = fins.getInstanceOffsets(); + + assertEquals( 0, points[0].x, 0.00001); + assertEquals( 0, points[0].y, 0.00001); + assertEquals( 0.05, points[0].z, 0.00001); + + assertEquals( 0, points[1].x, 0.00001); + assertEquals( -0.05, points[1].y, 0.00001); + assertEquals( 0, points[1].z, 0.00001); + } + + @Test + public void testInstancePoints_PI_4_BaseRotation() { + // This is a simple square fin with sides of 1.0. + TrapezoidFinSet fins = new TrapezoidFinSet(); + fins.setFinCount(4); + fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005); + fins.setBaseRotation( Math.PI/4 ); + + BodyTube body = new BodyTube(1.0, 0.05 ); + body.addChild( fins ); + + Coordinate[] points = fins.getInstanceOffsets(); + + assertEquals( 0, points[0].x, 0.0001); + assertEquals( 0.03535, points[0].y, 0.0001); + assertEquals( 0.03535, points[0].z, 0.0001); + + assertEquals( 0, points[1].x, 0.0001); + assertEquals( -0.03535, points[1].y, 0.0001); + assertEquals( 0.03535, points[1].z, 0.0001); + } + + + @Test + public void testInstanceAngles_zeroBaseRotation() { + // This is a simple square fin with sides of 1.0. + TrapezoidFinSet fins = new TrapezoidFinSet(); + fins.setFinCount(4); + fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005); + fins.setBaseRotation( 0.0 ); + + double[] angles = fins.getInstanceAngles(); + + assertEquals( angles[0], 0, 0.000001 ); + assertEquals( angles[1], Math.PI/2, 0.000001 ); + assertEquals( angles[2], Math.PI, 0.000001 ); + assertEquals( angles[3], 1.5*Math.PI, 0.000001 ); + } + + @Test + public void testInstanceAngles_90_BaseRotation() { + // This is a simple square fin with sides of 1.0. + TrapezoidFinSet fins = new TrapezoidFinSet(); + fins.setFinCount(4); + fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005); + fins.setBaseRotation( Math.PI/2 ); + + double[] angles = fins.getInstanceAngles(); + + assertEquals( angles[0], Math.PI/2, 0.000001 ); + assertEquals( angles[1], Math.PI, 0.000001 ); + assertEquals( angles[2], 1.5*Math.PI, 0.000001 ); + assertEquals( angles[3], 0, 0.000001 ); + } + @Test public void testFreeformCGComputation() throws Exception { diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java index f1bfc2bc8..33ba2cc9c 100644 --- a/swing/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java @@ -1,60 +1,41 @@ package net.sf.openrocket.gui.rocketfigure; import java.awt.Shape; -import java.awt.geom.Ellipse2D; -import java.awt.geom.Rectangle2D; +import net.sf.openrocket.rocketcomponent.BodyTube; +import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.Transformation; - public class BodyTubeShapes extends RocketComponentShape { public static RocketComponentShape[] getShapesSide( - net.sf.openrocket.rocketcomponent.RocketComponent component, + RocketComponent component, Transformation transformation, - Coordinate componentAbsoluteLocation) { - net.sf.openrocket.rocketcomponent.BodyTube tube = (net.sf.openrocket.rocketcomponent.BodyTube)component; - + Coordinate componentAbsoluteLocation){ + + BodyTube tube = (BodyTube)component; + double length = tube.getLength(); double radius = tube.getOuterRadius(); - // old version - //Coordinate[] instanceOffsets = new Coordinate[]{ transformation.transform( componentAbsoluteLocation )}; - //instanceOffsets = component.shiftCoordinates(instanceOffsets); - - // new version - Coordinate[] instanceOffsets = transformation.transform( component.getLocations()); + Shape[] s = new Shape[1]; + s[0] = TubeShapes.getShapesSide( transformation, componentAbsoluteLocation, length, radius ); - Shape[] s = new Shape[instanceOffsets.length]; - for (int i=0; i < instanceOffsets.length; i++) { - s[i] = new Rectangle2D.Double((instanceOffsets[i].x)*S, //x - the X coordinate of the upper-left corner of the newly constructed Rectangle2D - (instanceOffsets[i].y-radius)*S, // y - the Y coordinate of the upper-left corner of the newly constructed Rectangle2D - length*S, // w - the width of the newly constructed Rectangle2D - 2*radius*S); // h - the height of the newly constructed Rectangle2D - } - - return RocketComponentShape.toArray(s, component); + return RocketComponentShape.toArray(s, component); } public static RocketComponentShape[] getShapesBack( - net.sf.openrocket.rocketcomponent.RocketComponent component, + RocketComponent component, Transformation transformation, Coordinate componentAbsoluteLocation) { - net.sf.openrocket.rocketcomponent.BodyTube tube = (net.sf.openrocket.rocketcomponent.BodyTube)component; - double or = tube.getOuterRadius(); - - Coordinate[] instanceOffsets = new Coordinate[]{ transformation.transform( componentAbsoluteLocation )}; - //instanceOffsets = component.shiftCoordinates(instanceOffsets); - - instanceOffsets = component.getLocations(); + BodyTube tube = (BodyTube)component; + + double radius = tube.getOuterRadius(); - - Shape[] s = new Shape[instanceOffsets.length]; - for (int i=0; i < instanceOffsets.length; i++) { - s[i] = new Ellipse2D.Double((instanceOffsets[i].z-or)*S,(instanceOffsets[i].y-or)*S,2*or*S,2*or*S); - } + Shape[] s = new Shape[1]; + s[0] = TubeShapes.getShapesBack( transformation, componentAbsoluteLocation, radius); return RocketComponentShape.toArray(s, component); } diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java index d6c81ce32..8f5979a8e 100644 --- a/swing/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java @@ -15,56 +15,34 @@ public class FinSetShapes extends RocketComponentShape { public static RocketComponentShape[] getShapesSide( net.sf.openrocket.rocketcomponent.RocketComponent component, Transformation transformation, - Coordinate componentAbsoluteLocation) { + Coordinate instanceAbsoluteLocation) { net.sf.openrocket.rocketcomponent.FinSet finset = (net.sf.openrocket.rocketcomponent.FinSet)component; - - int finCount = finset.getFinCount(); - Transformation cantRotation = finset.getCantRotation(); - Transformation baseRotation = Transformation.rotate_x(finset.getAngularOffset()); // rotation about x-axis - Transformation finRotation = finset.getFinRotationTransformation(); - - Coordinate finSetFront = componentAbsoluteLocation; + Coordinate finSetFront = instanceAbsoluteLocation; Coordinate finPoints[] = finset.getFinPointsWithTab(); - // TODO: MEDIUM: sloping radius - double radius = finset.getBodyRadius(); - - // Translate & rotate the coordinates - for (int i=0; i= 0.0012) && (ir > 0)) { + if ((outerRadius-innerRadius >= 0.0012) && (innerRadius > 0)) { // Draw outer and inner - s = new Shape[instanceOffsets.length*2]; - for (int i=0; i < instanceOffsets.length; i++) { - s[2*i] = new Rectangle2D.Double(instanceOffsets[i].x*S,(instanceOffsets[i].y-or)*S, - length*S,2*or*S); - s[2*i+1] = new Rectangle2D.Double(instanceOffsets[i].x*S,(instanceOffsets[i].y-ir)*S, - length*S,2*ir*S); - } + s = new Shape[] { + TubeShapes.getShapesSide(transformation, instanceAbsoluteLocation, length, outerRadius), + TubeShapes.getShapesSide(transformation, instanceAbsoluteLocation, length, innerRadius) + }; } else { // Draw only outer - s = new Shape[instanceOffsets.length]; - for (int i=0; i < instanceOffsets.length; i++) { - s[i] = new Rectangle2D.Double(instanceOffsets[i].x*S,(instanceOffsets[i].y-or)*S, - length*S,2*or*S); - } + s = new Shape[] { + TubeShapes.getShapesSide(transformation, instanceAbsoluteLocation, length, outerRadius) + }; } return RocketComponentShape.toArray( s, component); } @@ -55,37 +42,24 @@ public class RingComponentShapes extends RocketComponentShape { public static RocketComponentShape[] getShapesBack( net.sf.openrocket.rocketcomponent.RocketComponent component, Transformation transformation, - Coordinate componentAbsoluteLocation) { + Coordinate instanceAbsoluteLocation) { net.sf.openrocket.rocketcomponent.RingComponent tube = (net.sf.openrocket.rocketcomponent.RingComponent)component; Shape[] s; - double or = tube.getOuterRadius(); - double ir = tube.getInnerRadius(); - - Coordinate[] instanceOffsets = new Coordinate[]{ transformation.transform( componentAbsoluteLocation )}; + double outerRadius = tube.getOuterRadius(); + double innerRadius = tube.getInnerRadius(); + + if ((outerRadius-innerRadius >= 0.0012) && (innerRadius > 0)) { + s = new Shape[] { + TubeShapes.getShapesBack(transformation, instanceAbsoluteLocation, outerRadius), + TubeShapes.getShapesBack(transformation, instanceAbsoluteLocation, innerRadius) + }; + }else { + s = new Shape[] { + TubeShapes.getShapesBack(transformation, instanceAbsoluteLocation, outerRadius) + }; + } - // old version - //instanceOffsets = component.shiftCoordinates(instanceOffsets); - - // new version - instanceOffsets = component.getLocations(); - - if ((ir < or) && (ir > 0)) { - // Draw inner and outer - s = new Shape[instanceOffsets.length*2]; - for (int i=0; i < instanceOffsets.length; i++) { - s[2*i] = new Ellipse2D.Double((instanceOffsets[i].z-or)*S, (instanceOffsets[i].y-or)*S, - 2*or*S, 2*or*S); - s[2*i+1] = new Ellipse2D.Double((instanceOffsets[i].z-ir)*S, (instanceOffsets[i].y-ir)*S, - 2*ir*S, 2*ir*S); - } - } else { - // Draw only outer - s = new Shape[instanceOffsets.length]; - for (int i=0; i < instanceOffsets.length; i++) { - s[i] = new Ellipse2D.Double((instanceOffsets[i].z-or)*S,(instanceOffsets[i].y-or)*S,2*or*S,2*or*S); - } - } return RocketComponentShape.toArray( s, component); } diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java index 0aa8ef72a..d48c6a066 100644 --- a/swing/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/TransitionShapes.java @@ -1,5 +1,6 @@ package net.sf.openrocket.gui.rocketfigure; +import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.Transition; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.Transformation; @@ -17,21 +18,21 @@ public class TransitionShapes extends RocketComponentShape { public static RocketComponentShape[] getShapesSide( net.sf.openrocket.rocketcomponent.RocketComponent component, Transformation transformation, - Coordinate instanceOffset) { - return getShapesSide(component, transformation, instanceOffset, S); + Coordinate instanceLocation) { + return getShapesSide(component, transformation, instanceLocation, S); } public static RocketComponentShape[] getShapesSide( - net.sf.openrocket.rocketcomponent.RocketComponent component, + RocketComponent component, Transformation transformation, - Coordinate componentAbsoluteLocation, + Coordinate instanceAbsoluteLocation, final double scaleFactor) { - net.sf.openrocket.rocketcomponent.Transition transition = (net.sf.openrocket.rocketcomponent.Transition)component; + + Transition transition = (Transition)component; RocketComponentShape[] mainShapes; - Coordinate frontCenter = transformation.transform( componentAbsoluteLocation ); - // this component type does not allow multiple instances + Coordinate frontCenter = instanceAbsoluteLocation; // Simpler shape for conical transition, others use the method from SymmetricComponent if (transition.getType() == Transition.Shape.CONICAL) { @@ -48,14 +49,14 @@ public class TransitionShapes extends RocketComponentShape { mainShapes = new RocketComponentShape[] { new RocketComponentShape( path, component) }; } else { - mainShapes = SymmetricComponentShapes.getShapesSide(component, transformation, componentAbsoluteLocation, scaleFactor); + mainShapes = SymmetricComponentShapes.getShapesSide(component, transformation, instanceAbsoluteLocation, scaleFactor); } Rectangle2D.Double foreShoulder=null, aftShoulder=null; int arrayLength = mainShapes.length; if (transition.getForeShoulderLength() > 0.0005) { - Coordinate foreTransitionShoulderCenter = componentAbsoluteLocation.sub( transition.getForeShoulderLength()/2, 0, 0); + Coordinate foreTransitionShoulderCenter = instanceAbsoluteLocation.sub( transition.getForeShoulderLength()/2, 0, 0); frontCenter = transformation.transform( foreTransitionShoulderCenter); double rad = transition.getForeShoulderRadius(); @@ -64,7 +65,7 @@ public class TransitionShapes extends RocketComponentShape { arrayLength++; } if (transition.getAftShoulderLength() > 0.0005) { - Coordinate aftTransitionShoulderCenter = componentAbsoluteLocation.add( transition.getLength() + (transition.getAftShoulderLength())/2, 0, 0); + Coordinate aftTransitionShoulderCenter = instanceAbsoluteLocation.add( transition.getLength() + (transition.getAftShoulderLength())/2, 0, 0); frontCenter= transformation.transform( aftTransitionShoulderCenter ); double rad = transition.getAftShoulderRadius(); diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/TubeShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/TubeShapes.java new file mode 100644 index 000000000..57ed060cc --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/TubeShapes.java @@ -0,0 +1,33 @@ +package net.sf.openrocket.gui.rocketfigure; + +import java.awt.Shape; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Rectangle2D; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.Transformation; + + +public class TubeShapes extends RocketComponentShape { + + public static Shape getShapesSide( + Transformation transformation, + Coordinate instanceAbsoluteLocation, + final double length, final double radius ){ + + return new Rectangle2D.Double((instanceAbsoluteLocation.x)*S, //x - the X coordinate of the upper-left corner of the newly constructed Rectangle2D + (instanceAbsoluteLocation.y-radius)*S, // y - the Y coordinate of the upper-left corner of the newly constructed Rectangle2D + length*S, // w - the width of the newly constructed Rectangle2D + 2*radius*S); // h - the height of the newly constructed Rectangle2D + } + + public static Shape getShapesBack( + Transformation transformation, + Coordinate instanceAbsoluteLocation, + final double radius ) { + + return new Ellipse2D.Double((instanceAbsoluteLocation.z-radius)*S, (instanceAbsoluteLocation.y-radius)*S, 2*radius*S, 2*radius*S); + } + + +} diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java index 12439066c..eb302feb2 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java @@ -183,9 +183,9 @@ public class RocketFigure extends AbstractScaleFigure { figureShapes.clear(); calculateSize(); - FlightConfiguration config = rocket.getSelectedConfiguration(); - getShapes( figureShapes, config); + getShapeTree( this.figureShapes, rocket, this.transformation, Coordinate.ZERO); + repaint(); fireChangeEvent(); } @@ -347,23 +347,23 @@ public class RocketFigure extends AbstractScaleFigure { // .getLocation() will return all the parent instances of this owning component, AND all of it's own instances as well. // so, just draw a motor once for each Coordinate returned... - Coordinate[] mountLocations = mountComponent.getLocations(); + Coordinate[] mountLocations = mount.getLocations(); double mountLength = mountComponent.getLength(); - //System.err.println("Drawing motor from here. Motor: "+motor.getDesignation()+" of length: "+motor.getLength()); +// System.err.println("Drawing Motor: "+motor.getDesignation()+" (x"+mountLocations.length+")"); for ( Coordinate curMountLocation : mountLocations ){ - Coordinate curMotorLocation = curMountLocation.add( mountLength - motorLength + mount.getMotorOverhang(), 0, 0); - - Coordinate coord = curMotorLocation; + Coordinate curMotorLocation = curMountLocation.add( mountLength - motorLength + mount.getMotorOverhang(), 0, 0); +// System.err.println(String.format(" mount instance: %s => %s", curMountLocation.toString(), curMotorLocation.toString() )); + { Shape s; if (currentViewType == RocketPanel.VIEW_TYPE.SideView) { - s = new Rectangle2D.Double(EXTRA_SCALE * coord.x, - EXTRA_SCALE * (coord.y - motorRadius), EXTRA_SCALE * motorLength, + s = new Rectangle2D.Double(EXTRA_SCALE * curMotorLocation.x, + EXTRA_SCALE * (curMotorLocation.y - motorRadius), EXTRA_SCALE * motorLength, EXTRA_SCALE * 2 * motorRadius); } else { - s = new Ellipse2D.Double(EXTRA_SCALE * (coord.z - motorRadius), - EXTRA_SCALE * (coord.y - motorRadius), EXTRA_SCALE * 2 * motorRadius, + s = new Ellipse2D.Double(EXTRA_SCALE * (curMotorLocation.z - motorRadius), + EXTRA_SCALE * (curMotorLocation.y - motorRadius), EXTRA_SCALE * 2 * motorRadius, EXTRA_SCALE * 2 * motorRadius); } g2.setColor(fillColor); @@ -420,41 +420,52 @@ public class RocketFigure extends AbstractScaleFigure { return l.toArray(new RocketComponent[0]); } - // facade for the recursive function below - private void getShapes(ArrayList allShapes, FlightConfiguration configuration){ - for( AxialStage stage : configuration.getActiveStages()){ - getShapeTree( allShapes, stage, Coordinate.ZERO); - } - } - // NOTE: Recursive function private void getShapeTree( - ArrayList allShapes, // output parameter - final RocketComponent comp, - final Coordinate parentLocation){ - - RocketPanel.VIEW_TYPE viewType = this.currentViewType; - Transformation viewTransform = this.transformation; - Coordinate[] locs = comp.getLocations(); - - // generate shapes - for( Coordinate curLocation : locs){ - allShapes = addThisShape( allShapes, viewType, comp, curLocation, viewTransform); - } + ArrayList allShapes, // output parameter + final RocketComponent comp, + final Transformation parentTransform, + final Coordinate parentLocation){ - // recurse into component's children - for( RocketComponent child: comp.getChildren() ){ - if( child instanceof AxialStage ){ - // recursing into BoosterSet here would double count its tree - continue; - } - - for( Coordinate curLocation : locs){ - getShapeTree( allShapes, child, curLocation); - } - } - return; + final int instanceCount = comp.getInstanceCount(); + Coordinate[] instanceLocations = comp.getInstanceLocations(); + instanceLocations = parentTransform.transform( instanceLocations ); + double[] instanceAngles = comp.getInstanceAngles(); + if( instanceLocations.length != instanceAngles.length ){ + throw new ArrayIndexOutOfBoundsException(String.format("lengths of location array (%d) and angle arrays (%d) differs! (in: %s) ", instanceLocations.length, instanceAngles.length, comp.getName())); + } + + // iterate over the aggregated instances *for the whole* tree. + for( int index = 0; instanceCount > index ; ++index ){ + final double currentAngle = instanceAngles[index]; + + Transformation currentTransform = parentTransform; + if( 0.00001 < Math.abs( currentAngle )) { + Transformation currentAngleTransform = Transformation.rotate_x( currentAngle ); + currentTransform = currentAngleTransform.applyTransformation( parentTransform ); + } + + Coordinate currentLocation = parentLocation.add( instanceLocations[index] ); + +// System.err.println(String.format("@%s: %s -- inst: [%d/%d]", comp.getClass().getSimpleName(), comp.getName(), index+1, instanceCount)); +// System.err.println(String.format(" -- stage: %d, active: %b, config: (%d) %s", comp.getStageNumber(), this.getConfiguration().isComponentActive(comp), this.getConfiguration().instanceNumber, this.getConfiguration().getId())); +// System.err.println(String.format(" -- %s + %s = %s", parentLocation.toString(), instanceLocations[index].toString(), currentLocation.toString())); +// if( 0.00001 < Math.abs( currentAngle )) { +// System.err.println(String.format(" -- at: %6.4f radians", currentAngle)); +// } + + // generate shape for this component, if active + if( this.getConfiguration().isComponentActive( comp )){ + allShapes = addThisShape( allShapes, this.currentViewType, comp, currentLocation, currentTransform); + } + + // recurse into component's children + for( RocketComponent child: comp.getChildren() ){ + // draw a tree for each instance subcomponent + getShapeTree( allShapes, child, currentTransform, currentLocation ); + } + } } /**