From 959bbfaac3f30c7d20ef7127628c878519854c55 Mon Sep 17 00:00:00 2001 From: SiboVG Date: Sat, 16 Jul 2022 02:40:12 +0200 Subject: [PATCH] [#1146] Add 2D visualization for pod sets --- .../sf/openrocket/rocketcomponent/Rocket.java | 11 ++ .../gui/rocketfigure/EmptyShapes.java | 122 ++++++++++++++++++ .../gui/rocketfigure/PodSetShapes.java | 49 +++++++ .../rocketfigure/RocketComponentShape.java | 4 + .../gui/scalefigure/RocketFigure.java | 62 ++++++++- 5 files changed, 244 insertions(+), 4 deletions(-) create mode 100644 swing/src/net/sf/openrocket/gui/rocketfigure/EmptyShapes.java create mode 100644 swing/src/net/sf/openrocket/gui/rocketfigure/PodSetShapes.java diff --git a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java index 5411440f7..90fa5a5a7 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java +++ b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -304,6 +304,17 @@ public class Rocket extends ComponentAssembly { fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); } } + + @Override + public double getBoundingRadius() { + double bounding = 0; + for (RocketComponent comp : children) { + if (comp instanceof ComponentAssembly) { + bounding = Math.max(bounding, ((ComponentAssembly) comp).getBoundingRadius()); + } + } + return bounding; + } diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/EmptyShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/EmptyShapes.java new file mode 100644 index 000000000..35b048551 --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/EmptyShapes.java @@ -0,0 +1,122 @@ +package net.sf.openrocket.gui.rocketfigure; + +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.Transformation; + +import java.awt.Shape; +import java.awt.geom.Line2D; +import java.awt.geom.Rectangle2D; + +/** + * Shapes of an "empty"/virtual object, e.g. a podset without any children. + * The shape is a center square with additional lines on the north, east, south and west side of the square. + * + * @author Sibo Van Gool + */ +public class EmptyShapes extends RocketComponentShape { + /** + * Returns the empty shape in the side view. + * @param radius radius of the center square + */ + public static Shape[] getShapesSide(final Transformation transformation, final double radius) { + final Coordinate instanceAbsoluteLocation = transformation.transform(Coordinate.ZERO); + double x = instanceAbsoluteLocation.x; + double y = instanceAbsoluteLocation.y; + + double lineLength = getLineLength(radius); // Length of the line protruding the center square + + final Shape[] s = new Shape[5]; + // Center square + s[0] = new Rectangle2D.Double(x - radius, y - radius, 2 * radius, 2 * radius); + // Line North + s[1] = new Line2D.Double(x, y + radius, x, y + radius + lineLength); + // Line East + s[2] = new Line2D.Double(x + radius, y, x + radius + lineLength, y); + // Line South + s[3] = new Line2D.Double(x, y - radius, x, y - radius - lineLength); + // Line West + s[4] = new Line2D.Double(x - radius, y, x - radius - lineLength, y); + + return s; + } + + /** + * Returns the empty shape in the side view, with an additional square encompassing the shape that can be used + * for selecting the object. + * @param radius radius of the center square + */ + public static Shape[] getShapesSideWithSelectionSquare(final Transformation transformation, final double radius) { + final Coordinate instanceAbsoluteLocation = transformation.transform(Coordinate.ZERO); + double x = instanceAbsoluteLocation.x; + double y = instanceAbsoluteLocation.y; + + double lineLength = getLineLength(radius); // Length of the line protruding the center square + + Shape[] shapes = getShapesSide(transformation, radius); + + // Invisible shape for selecting the component (= a square encompassing the component) + Shape selectionShape = new Rectangle2D.Double(x - radius - lineLength, y - radius - lineLength, + lineLength * 2 + radius * 2, lineLength * 2 + radius * 2); + + Shape[] finalShapes = new Shape[shapes.length + 1]; + System.arraycopy(shapes, 0, finalShapes, 0, shapes.length); + finalShapes[finalShapes.length - 1] = selectionShape; + + return finalShapes; + } + + /** + * Returns the empty shape in the side view. + * @param radius radius of the center square + */ + public static Shape[] getShapesBack(final Transformation transformation, final double radius) { + final Coordinate instanceAbsoluteLocation = transformation.transform(Coordinate.ZERO); + double z = instanceAbsoluteLocation.z; + double y = instanceAbsoluteLocation.y; + + double lineLength = getLineLength(radius); // Length of the line protruding the center square + + final Shape[] s = new Shape[5]; + // Center square + s[0] = new Rectangle2D.Double(z - radius, y - radius, 2 * radius, 2 * radius); + // Line North + s[1] = new Line2D.Double(z, y + radius, z, y + radius + lineLength); + // Line East + s[2] = new Line2D.Double(z + radius, y, z + radius + lineLength, y); + // Line South + s[3] = new Line2D.Double(z, y - radius, z, y - radius - lineLength); + // Line West + s[4] = new Line2D.Double(z - radius, y, z - radius - lineLength, y); + + return s; + } + + /** + * Returns the empty shape in the back view, with an additional square encompassing the shape that can be used + * for selecting the object. + * @param radius radius of the center square + */ + public static Shape[] getShapesBackWithSelectionSquare(final Transformation transformation, final double radius) { + final Coordinate instanceAbsoluteLocation = transformation.transform(Coordinate.ZERO); + double z = instanceAbsoluteLocation.z; + double y = instanceAbsoluteLocation.y; + + double lineLength = getLineLength(radius); // Length of the line protruding the center square + + Shape[] shapes = getShapesBack(transformation, radius); + + // Invisible shape for selecting the component (= a square encompassing the component) + Shape selectionShape = new Rectangle2D.Double(z - radius - lineLength, y - radius - lineLength, + lineLength * 2 + radius * 2, lineLength * 2 + radius * 2); + + Shape[] finalShapes = new Shape[shapes.length + 1]; + System.arraycopy(shapes, 0, finalShapes, 0, shapes.length); + finalShapes[finalShapes.length - 1] = selectionShape; + + return finalShapes; + } + + private static double getLineLength(double radius) { + return radius * 3; + } +} diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/PodSetShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/PodSetShapes.java new file mode 100644 index 000000000..27955db60 --- /dev/null +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/PodSetShapes.java @@ -0,0 +1,49 @@ +package net.sf.openrocket.gui.rocketfigure; + +import net.sf.openrocket.rocketcomponent.PodSet; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.util.Color; +import net.sf.openrocket.util.Transformation; + +import java.awt.Shape; + +public class PodSetShapes extends RocketComponentShape { + public static final Color podsetColor = new Color(160, 160, 160); // Normal color for the podset shape + public static final Color centerColor = new Color(200, 200, 200); // Color for the center shape of the podset + + public static RocketComponentShape[] getShapesSide(final RocketComponent component, final Transformation transformation) { + PodSet podset = (PodSet)component; + double radius = getDisplayRadius(podset); + + Shape[] s = EmptyShapes.getShapesSideWithSelectionSquare(transformation, radius); + RocketComponentShape[] shapes = RocketComponentShape.toArray(s, component); + + // Set the color of the shapes + for (int i = 0; i < shapes.length - 1; i++) { + shapes[i].setColor(podsetColor); + } + shapes[shapes.length - 1].setColor(Color.INVISIBLE); + + return shapes; + } + + public static RocketComponentShape[] getShapesBack(final RocketComponent component, final Transformation transformation) { + PodSet podset = (PodSet)component; + double radius = getDisplayRadius(podset); + + Shape[] s = EmptyShapes.getShapesBackWithSelectionSquare(transformation, radius); + RocketComponentShape[] shapes = RocketComponentShape.toArray(s, component); + + // Set the color of the shapes + for (int i = 0; i < shapes.length - 1; i++) { + shapes[i].setColor(podsetColor); + } + shapes[shapes.length - 1].setColor(Color.INVISIBLE); + + return shapes; + } + + private static double getDisplayRadius(PodSet podset) { + return podset.getRocket().getBoundingRadius() * 0.03; + } +} diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShape.java b/swing/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShape.java index 725713b80..c132bfd3d 100644 --- a/swing/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShape.java +++ b/swing/src/net/sf/openrocket/gui/rocketfigure/RocketComponentShape.java @@ -64,6 +64,10 @@ public class RocketComponentShape { return new RocketComponentShape[0]; } + public Color getColor() { + return color; + } + public void setColor(Color color) { this.color = color; } diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java index 3226abee2..e9b8f6de0 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java @@ -17,6 +17,8 @@ import java.awt.geom.Rectangle2D; import java.util.*; import java.util.Map.Entry; +import net.sf.openrocket.gui.rocketfigure.PodSetShapes; +import net.sf.openrocket.rocketcomponent.PodSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -385,13 +387,38 @@ public class RocketFigure extends AbstractScaleFigure { final Transformation currentTransform = this.axialRotation.applyTransformation(context.transform); allShapes = addThisShape( allShapes, this.currentViewType, comp, currentTransform); } + + // PodSets require an additional shape for the center of the podset. + if (comp instanceof PodSet) { + Transformation parentTransform = null; + for (Entry> entry2: config.getActiveInstances().entrySet()) { + final RocketComponent parent = entry2.getKey(); + if (parent == comp.getParent()) { + parentTransform = entry2.getValue().get(0).transform; // TODO: normally only the first context should be used, unless we start doing fancy stuff like adding pods to individual fins + break; + } + } + if (parentTransform == null) { + parentTransform = Transformation.IDENTITY; + } + + final Transformation compLocTransform = Transformation.getTranslationTransform(comp.getPosition()); + final Transformation componentTransform = parentTransform.applyTransformation(compLocTransform); + + final Transformation currentTransform = this.axialRotation.applyTransformation(componentTransform); + allShapes = addThisShape(allShapes, this.currentViewType, comp, currentTransform, PodSetShapes.centerColor); + } } } /** * Gets the shapes required to draw the component. - * - * @param component + * + * @param allShapes output buffer for the shapes to add to + * @param viewType the view type to draw the component in + * @param component component to draw and add to + * @param transformation transformation to apply to the component before drawing it + * @param color color to draw the component in * * @return the ArrayList containing all the shapes to draw. */ @@ -399,10 +426,11 @@ public class RocketFigure extends AbstractScaleFigure { PriorityQueue allShapes, // this is the output parameter final RocketPanel.VIEW_TYPE viewType, final RocketComponent component, - final Transformation transformation) { + final Transformation transformation, + final net.sf.openrocket.util.Color color) { Reflection.Method m; - if(( component instanceof Rocket)||( component instanceof ComponentAssembly )){ + if(( component instanceof Rocket)||( component instanceof ComponentAssembly && !(component instanceof PodSet))){ // no-op; no shapes here return allShapes; } @@ -431,9 +459,35 @@ public class RocketFigure extends AbstractScaleFigure { RocketComponentShape[] returnValue = (RocketComponentShape[]) m.invokeStatic(component, transformation); + + if (color != null) { + for (RocketComponentShape rcs : returnValue) { + if (rcs.getColor() == net.sf.openrocket.util.Color.INVISIBLE) continue; // don't change the color of invisible (often selection) components + rcs.setColor(color); + } + } + allShapes.addAll(Arrays.asList(returnValue)); return allShapes; } + + /** + * Gets the shapes required to draw the component. + * + * @param allShapes output buffer for the shapes to add to + * @param viewType the view type to draw the component in + * @param component component to draw and add to + * @param transformation transformation to apply to the component before drawing it + * + * @return the ArrayList containing all the shapes to draw. + */ + private static PriorityQueue addThisShape( + PriorityQueue allShapes, // this is the output parameter + final RocketPanel.VIEW_TYPE viewType, + final RocketComponent component, + final Transformation transformation) { + return addThisShape(allShapes, viewType, component, transformation, null); + } /**