diff --git a/core/src/net/sf/openrocket/rocketcomponent/MultipleComponent.java b/core/src/net/sf/openrocket/rocketcomponent/MultipleComponent.java new file mode 100644 index 000000000..eaeaa2764 --- /dev/null +++ b/core/src/net/sf/openrocket/rocketcomponent/MultipleComponent.java @@ -0,0 +1,24 @@ +package net.sf.openrocket.rocketcomponent; + +import net.sf.openrocket.util.Coordinate; + + +/** + * This interface is used to signal that the implementing interface contains multiple instances of its components. + * (Note: not all implementations replicate their children, but that is design intention.) + * + * @author teyrana ( Daniel Williams, equipoise@gmail.com ) + * + */ + +public interface MultipleComponent { + + + public int getInstanceCount(); + + // location of each instance relative to the component + // center-to-center vectors + public Coordinate[] getInstanceOffsets(); + + +} diff --git a/core/src/net/sf/openrocket/rocketcomponent/Stage.java b/core/src/net/sf/openrocket/rocketcomponent/Stage.java index a0678bff8..0750c5a1f 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/Stage.java +++ b/core/src/net/sf/openrocket/rocketcomponent/Stage.java @@ -2,11 +2,12 @@ package net.sf.openrocket.rocketcomponent; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.startup.Application; +import net.sf.openrocket.util.Coordinate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class Stage extends ComponentAssembly implements FlightConfigurableComponent, OutsideComponent { +public class Stage extends ComponentAssembly implements FlightConfigurableComponent, MultipleComponent, OutsideComponent { static final Translator trans = Application.getTranslator(); private static final Logger log = LoggerFactory.getLogger(Stage.class); @@ -18,8 +19,8 @@ public class Stage extends ComponentAssembly implements FlightConfigurableCompon private double radialPosition_m = 0; private double rotation_rad = 0; - private int count = 2; - private double separationAngle = Math.PI; + private int count = 1; + private double angularSeparation = Math.PI; public Stage() { @@ -84,9 +85,12 @@ public class Stage extends ComponentAssembly implements FlightConfigurableCompon this.relativePosition = Position.BOTTOM; this.position = 0; } - if (this.outside) { - fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + if (!this.outside) { + this.count = 1; } + + + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); } @Override @@ -98,7 +102,7 @@ public class Stage extends ComponentAssembly implements FlightConfigurableCompon public void setCount(final int _count) { mutex.verify(); this.count = _count; - this.separationAngle = Math.PI * 2 / this.count; + this.angularSeparation = Math.PI * 2 / this.count; if (this.outside) { fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); @@ -183,7 +187,26 @@ public class Stage extends ComponentAssembly implements FlightConfigurableCompon } } + @Override + public int getInstanceCount() { + return this.count; + } - - + @Override + public Coordinate[] getInstanceOffsets() { + Coordinate[] toReturn = new Coordinate[this.count]; + + double radius = this.radialPosition_m; + double angle0 = this.angularPosition_rad; + double angleIncr = this.angularSeparation; + + double thisAngle = angle0; + for (int instanceNumber = 0; instanceNumber < this.count; instanceNumber++) { + + toReturn[instanceNumber] = new Coordinate(0, radius * Math.cos(thisAngle), radius * Math.sin(thisAngle)); + thisAngle += angleIncr; + } + + return toReturn; + } } diff --git a/swing/src/net/sf/openrocket/gui/print/PrintableNoseCone.java b/swing/src/net/sf/openrocket/gui/print/PrintableNoseCone.java index dd6847108..220cf3103 100644 --- a/swing/src/net/sf/openrocket/gui/print/PrintableNoseCone.java +++ b/swing/src/net/sf/openrocket/gui/print/PrintableNoseCone.java @@ -1,8 +1,10 @@ package net.sf.openrocket.gui.print; import net.sf.openrocket.gui.print.visitor.PageFitPrintStrategy; +import net.sf.openrocket.gui.rocketfigure.RocketComponentShape; import net.sf.openrocket.gui.rocketfigure.TransitionShapes; import net.sf.openrocket.rocketcomponent.NoseCone; +import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.Transformation; import java.awt.Graphics2D; @@ -56,14 +58,14 @@ public class PrintableNoseCone extends AbstractPrintable { */ @Override protected void draw(Graphics2D g2) { - Shape[] shapes = TransitionShapes.getShapesSide(target, Transformation.rotate_x(0d), PrintUnit.METERS.toPoints(1)); + RocketComponentShape[] compShapes = TransitionShapes.getShapesSide(target, Transformation.rotate_x(0d), new Coordinate(0,0,0), PrintUnit.METERS.toPoints(1)); - if (shapes != null && shapes.length > 0) { - Rectangle r = shapes[0].getBounds(); + if (compShapes != null && compShapes.length > 0) { + Rectangle r = compShapes[0].shape.getBounds(); g2.translate(r.getHeight() / 2, 0); g2.rotate(Math.PI / 2); - for (Shape shape : shapes) { - g2.draw(shape); + for (RocketComponentShape shape : compShapes) { + g2.draw(shape.shape); } g2.rotate(-Math.PI / 2); } diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java index aa299490d..ab72c3a7b 100644 --- a/swing/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java @@ -8,38 +8,43 @@ import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; -public class BodyTubeShapes extends RocketComponentShapes { +public class BodyTubeShapes extends RocketComponentShape { - public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component, - Transformation transformation) { + public static RocketComponentShape[] getShapesSide( + net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation transformation, + Coordinate instanceOffset) { net.sf.openrocket.rocketcomponent.BodyTube tube = (net.sf.openrocket.rocketcomponent.BodyTube)component; double length = tube.getLength(); double radius = tube.getOuterRadius(); - Coordinate[] start = transformation.transform(tube.toAbsolute(new Coordinate(0,0,0))); + Coordinate[] start = transformation.transform(tube.toAbsolute(instanceOffset)); Shape[] s = new Shape[start.length]; for (int i=0; i < start.length; i++) { s[i] = new Rectangle2D.Double(start[i].x*S,(start[i].y-radius)*S, length*S,2*radius*S); } - return s; + + return RocketComponentShape.toArray(s, component); } - - public static Shape[] getShapesBack(net.sf.openrocket.rocketcomponent.RocketComponent component, - Transformation transformation) { + public static RocketComponentShape[] getShapesBack( + net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation transformation, + Coordinate instanceOffset) { net.sf.openrocket.rocketcomponent.BodyTube tube = (net.sf.openrocket.rocketcomponent.BodyTube)component; double or = tube.getOuterRadius(); - Coordinate[] start = transformation.transform(tube.toAbsolute(new Coordinate(0,0,0))); + Coordinate[] start = transformation.transform(tube.toAbsolute(instanceOffset)); Shape[] s = new Shape[start.length]; for (int i=0; i < start.length; i++) { s[i] = new Ellipse2D.Double((start[i].z-or)*S,(start[i].y-or)*S,2*or*S,2*or*S); } - return s; + + 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 0eb856f55..8ef726cf0 100644 --- a/swing/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/FinSetShapes.java @@ -8,16 +8,18 @@ import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.Transformation; -public class FinSetShapes extends RocketComponentShapes { +public class FinSetShapes extends RocketComponentShape { // TODO: LOW: Clustering is ignored (FinSet cannot currently be clustered) - public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component, - Transformation transformation) { + public static RocketComponentShape[] getShapesSide( + net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation transformation, + Coordinate instanceOffset) { net.sf.openrocket.rocketcomponent.FinSet finset = (net.sf.openrocket.rocketcomponent.FinSet)component; - int fins = finset.getFinCount(); + int finCount = finset.getFinCount(); Transformation cantRotation = finset.getCantRotation(); Transformation baseRotation = finset.getBaseRotationTransformation(); Transformation finRotation = finset.getFinRotationTransformation(); @@ -36,8 +38,8 @@ public class FinSetShapes extends RocketComponentShapes { // Generate shapes - Shape[] s = new Shape[fins]; - for (int fin=0; fin= 0.0012) && (ir > 0)) { // Draw outer and inner @@ -40,11 +43,11 @@ public class RingComponentShapes extends RocketComponentShapes { length*S,2*or*S); } } - return s; + return RocketComponentShape.toArray( s, component); } - public static Shape[] getShapesBack(net.sf.openrocket.rocketcomponent.RocketComponent component, + public static RocketComponentShape[] getShapesBack(net.sf.openrocket.rocketcomponent.RocketComponent component, Transformation transformation) { net.sf.openrocket.rocketcomponent.RingComponent tube = (net.sf.openrocket.rocketcomponent.RingComponent)component; Shape[] s; @@ -71,7 +74,7 @@ public class RingComponentShapes extends RocketComponentShapes { s[i] = new Ellipse2D.Double((start[i].z-or)*S,(start[i].y-or)*S,2*or*S,2*or*S); } } - return s; + return RocketComponentShape.toArray( s, component); } } diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShape.java b/swing/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShape.java new file mode 100644 index 000000000..972fea100 --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShape.java @@ -0,0 +1,83 @@ +package net.sf.openrocket.gui.rocketfigure; + + +import java.awt.Shape; + +import net.sf.openrocket.gui.scalefigure.RocketFigure; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.startup.Application; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.LineStyle; +import net.sf.openrocket.util.Transformation; + + +/** + * A catch-all, no-operation drawing component. + */ +public class RocketComponentShape { + + protected static final double S = RocketFigure.EXTRA_SCALE; + + final public boolean hasShape; + final public Shape shape; + final public net.sf.openrocket.util.Color color; + final public LineStyle lineStyle; + final public RocketComponent component; + + protected RocketComponentShape(){ + this.hasShape = false; + this.shape = null; + this.color = null; + this.lineStyle = null; + this.component=null; + + } + + public RocketComponentShape( final Shape _shape, final RocketComponent _comp){ + this.shape = _shape; + this.color = _comp.getColor(); + this.lineStyle = _comp.getLineStyle(); + this.component = _comp; + + if( null == _shape ){ + this.hasShape = false; + }else{ + this.hasShape = true; + } + } + + public RocketComponent getComponent(){ + return this.component; + } + + + public static RocketComponentShape[] getShapesSide( + net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation transformation, + Coordinate instanceOffset) { + // no-op + Application.getExceptionHandler().handleErrorCondition("ERROR: RocketComponent.getShapesSide called with " + + component); + return new RocketComponentShape[0]; + } + + public static RocketComponentShape[] getShapesBack( + net.sf.openrocket.rocketcomponent.RocketComponent component, + Transformation transformation, + Coordinate instanceOffset) { // no-op + Application.getExceptionHandler().handleErrorCondition("ERROR: RocketComponent.getShapesBack called with " + +component); + return new RocketComponentShape[0]; + } + + public static RocketComponentShape[] toArray( final Shape[] shapeArray, final RocketComponent rc){ + RocketComponentShape[] toReturn = new RocketComponentShape[ shapeArray.length]; + for ( int curShapeIndex=0;curShapeIndex figureShapes = new ArrayList(); - private final ArrayList figureComponents = - new ArrayList(); + private final ArrayList figureShapes = new ArrayList(); + private double minX = 0, maxX = 0, maxR = 0; // Figure width and height in SI-units and pixels @@ -172,7 +174,7 @@ public class RocketFigure extends AbstractScaleFigure { this.currentViewType = type; updateFigure(); } - + /** * Updates the figure shapes and figure size. @@ -180,20 +182,11 @@ public class RocketFigure extends AbstractScaleFigure { @Override public void updateFigure() { figureShapes.clear(); - figureComponents.clear(); calculateSize(); - - // Get shapes for all active components - for (RocketComponent c : configuration) { - Shape[] s = getShapes( this.currentViewType, c, this.transformation); - - - for (int i = 0; i < s.length; i++) { - figureShapes.add(s[i]); - figureComponents.add(c); - } - } + Rocket theRocket = configuration.getRocket(); + Coordinate zero = new Coordinate(0,0,0); + getShapeTree( figureShapes, theRocket, zero); System.err.println(" updating the RocketFigure."); repaint(); @@ -298,8 +291,8 @@ public class RocketFigure extends AbstractScaleFigure { // Draw all shapes for (int i = 0; i < figureShapes.size(); i++) { - RocketComponent c = figureComponents.get(i); - Shape s = figureShapes.get(i); + RocketComponentShape rcs = figureShapes.get(i); + RocketComponent c = rcs.getComponent(); boolean selected = false; // Check if component is in the selection @@ -311,13 +304,13 @@ public class RocketFigure extends AbstractScaleFigure { } // Set component color and line style - net.sf.openrocket.util.Color color = c.getColor(); + net.sf.openrocket.util.Color color = rcs.color; if (color == null) { color = Application.getPreferences().getDefaultColor(c.getClass()); } g2.setColor(ColorConversion.toAwtColor(color)); - LineStyle style = c.getLineStyle(); + LineStyle style = rcs.lineStyle; if (style == null) style = Application.getPreferences().getDefaultLineStyle(c.getClass()); @@ -337,7 +330,7 @@ public class RocketFigure extends AbstractScaleFigure { g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); } - g2.draw(s); + g2.draw(rcs.shape); } @@ -424,13 +417,78 @@ public class RocketFigure extends AbstractScaleFigure { LinkedHashSet l = new LinkedHashSet(); for (int i = 0; i < figureShapes.size(); i++) { - if (figureShapes.get(i).contains(p)) - l.add(figureComponents.get(i)); + RocketComponentShape rcs = this.figureShapes.get(i); + if (rcs.shape.contains(p)) + l.add(rcs.component); } return l.toArray(new RocketComponent[0]); } + // NOTE: Recursive function + private void getShapeTree( + ArrayList allShapes, // this is the output parameter + final RocketComponent comp, + final Coordinate parentOffset){ + RocketPanel.VIEW_TYPE viewType = this.currentViewType; + Transformation viewTransform = this.transformation; + + + // TODO: Implement actual locations in the components + Coordinate componentLocation = new Coordinate(comp.getPositionValue(),0,0); + + if( comp instanceof MultipleComponent ){ + MultipleComponent multi = (MultipleComponent)comp; + int instanceCount; + instanceCount = multi.getInstanceCount(); + + + // get m instance locations + Coordinate[] instanceOffsets = multi.getInstanceOffsets(); + assert(false); + assert( instanceOffsets.length == instanceCount ); + + // replicate n children m times each + int childCount = comp.getChildCount(); + ArrayList childrenToReplicate = new ArrayList(); + for ( int instanceNumber = 0; instanceNumber < instanceCount; instanceNumber++ ){ + childrenToReplicate.clear(); + Coordinate curInstanceOffset = componentLocation.add( instanceOffsets[instanceNumber] ); + + // get n children shapes toReplicate + for ( int childNumber = 0; childNumber < childCount; childNumber++ ){ + RocketComponent curChildComp = comp.getChild( childNumber); + getShapeTree( childrenToReplicate, curChildComp, curInstanceOffset); + } + + for ( RocketComponentShape curShape : childrenToReplicate ){ + allShapes.add( curShape); + } + + } + + }else{ + if( comp instanceof Rocket){ + // the Rocket doesn't have any graphics to get. + // Noop + }else{ + // for most RocketComponents + // TODO: HIGH: TEST that getThisShape will actually relocate by the given offset + RocketComponentShape[] childShapes = getThisShape( viewType, comp, parentOffset, viewTransform); + + for ( RocketComponentShape curShape : childShapes ){ + allShapes.add( curShape ); + } + } + + // recurse to each child + for( RocketComponent child: comp.getChildren() ){ + getShapeTree( allShapes, child, parentOffset); + } + } + + return; + } /** * Gets the shapes required to draw the component. @@ -439,32 +497,32 @@ public class RocketFigure extends AbstractScaleFigure { * @param params * @return */ - private static Shape[] getShapes(final RocketPanel.VIEW_TYPE type, final RocketComponent component, final Transformation transformation) { + private static RocketComponentShape[] getThisShape(final RocketPanel.VIEW_TYPE viewType, final RocketComponent component, final Coordinate instanceOffset, final Transformation transformation) { Reflection.Method m; // Find the appropriate method - switch (type) { + switch (viewType) { case SideView: m = Reflection.findMethod(ROCKET_FIGURE_PACKAGE, component, ROCKET_FIGURE_SUFFIX, "getShapesSide", - RocketComponent.class, Transformation.class); + RocketComponent.class, Transformation.class, Coordinate.class); break; case BackView: m = Reflection.findMethod(ROCKET_FIGURE_PACKAGE, component, ROCKET_FIGURE_SUFFIX, "getShapesBack", - RocketComponent.class, Transformation.class); + RocketComponent.class, Transformation.class, Coordinate.class); break; default: - throw new BugException("Unknown figure type = " + type); + throw new BugException("Unknown figure type = " + viewType); } if (m == null) { Application.getExceptionHandler().handleErrorCondition("ERROR: Rocket figure paint method not found for " + component); - return new Shape[0]; + return new RocketComponentShape[0]; } - return (Shape[]) m.invokeStatic(component, transformation); + return (RocketComponentShape[]) m.invokeStatic(component, transformation, instanceOffset); }