diff --git a/core/src/net/sf/openrocket/gui/print/FinMarkingGuide.java b/core/src/net/sf/openrocket/gui/print/FinMarkingGuide.java
index 64b617ab1..d2509c145 100644
--- a/core/src/net/sf/openrocket/gui/print/FinMarkingGuide.java
+++ b/core/src/net/sf/openrocket/gui/print/FinMarkingGuide.java
@@ -1,15 +1,5 @@
package net.sf.openrocket.gui.print;
-import net.sf.openrocket.l10n.Translator;
-import net.sf.openrocket.rocketcomponent.BodyTube;
-import net.sf.openrocket.rocketcomponent.ExternalComponent;
-import net.sf.openrocket.rocketcomponent.FinSet;
-import net.sf.openrocket.rocketcomponent.LaunchLug;
-import net.sf.openrocket.rocketcomponent.Rocket;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.startup.Application;
-
-import javax.swing.JPanel;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
@@ -21,12 +11,22 @@ import java.awt.geom.Path2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import javax.swing.JPanel;
+
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.rocketcomponent.BodyTube;
+import net.sf.openrocket.rocketcomponent.ExternalComponent;
+import net.sf.openrocket.rocketcomponent.FinSet;
+import net.sf.openrocket.rocketcomponent.LaunchLug;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.startup.Application;
+
/**
* This is the core Swing representation of a fin marking guide. It can handle multiple fin sets on the same or
* different body tubes. One marking guide will be created for any body tube that has a finset. If a tube has multiple
@@ -36,448 +36,446 @@ import java.util.Map;
*
*/
public class FinMarkingGuide extends JPanel {
-
- /**
- * The stroke of normal lines.
- */
- private final static BasicStroke thinStroke = new BasicStroke(1.0f);
-
- /**
- * The size of the arrow in points.
- */
- private static final int ARROW_SIZE = 10;
-
- /**
- * Typical thickness of a piece of printer paper (~20-24 lb paper). Wrapping paper around a tube results in the
- * radius being increased by the thickness of the paper. The smaller the tube, the more pronounced this becomes as a
- * percentage of circumference. Using 1/10mm as an approximation here.
- */
- private static final double PAPER_THICKNESS_IN_METERS = PrintUnit.MILLIMETERS.toMeters(0.1d);
-
- /**
- * The default guide width in inches.
- */
- public final static double DEFAULT_GUIDE_WIDTH = 3d;
-
- /**
- * 2 PI radians (represents a circle).
- */
- public final static double TWO_PI = 2 * Math.PI;
-
- /**
- * The I18N translator.
- */
- private static final Translator trans = Application.getTranslator();
-
- /**
- * The margin.
- */
- private static final int MARGIN = (int) PrintUnit.INCHES.toPoints(0.25f);
-
- /**
- * The height (circumference) of the biggest body tube with a finset.
- */
- private int maxHeight = 0;
-
- /**
- * A map of body tubes, to a list of components that contains finsets and launch lugs.
- */
- private Map> markingGuideItems;
-
- /**
- * Constructor.
- *
- * @param rocket the rocket instance
- */
- public FinMarkingGuide(Rocket rocket) {
- super(false);
- setBackground(Color.white);
- markingGuideItems = init(rocket);
- //Max of 2 drawing guides horizontally per page.
- setSize((int) PrintUnit.INCHES.toPoints(DEFAULT_GUIDE_WIDTH) * 2 + 3 * MARGIN, maxHeight);
- }
-
- /**
- * Initialize the marking guide class by iterating over a rocket and finding all finsets.
- *
- * @param component the root rocket component - this is iterated to find all finset and launch lugs
- *
- * @return a map of body tubes to lists of finsets and launch lugs.
- */
- private Map> init(Rocket component) {
- Iterator iter = component.iterator(false);
- Map> results = new LinkedHashMap>();
- BodyTube current = null;
- int totalHeight = 0;
- int iterationHeight = 0;
- int count = 0;
-
- while (iter.hasNext()) {
- RocketComponent next = iter.next();
- if (next instanceof BodyTube) {
- current = (BodyTube) next;
- }
- else if (next instanceof FinSet || next instanceof LaunchLug) {
- java.util.List list = results.get(current);
- if (list == null && current != null) {
- list = new ArrayList();
- results.put(current, list);
-
- double radius = current.getOuterRadius();
- int circumferenceInPoints = (int) PrintUnit.METERS.toPoints(radius * TWO_PI);
-
- // Find the biggest body tube circumference.
- if (iterationHeight < (circumferenceInPoints + MARGIN)) {
- iterationHeight = circumferenceInPoints + MARGIN;
- }
- //At most, two marking guides horizontally. After that, move down and back to the left margin.
- count++;
- if (count % 2 == 0) {
- totalHeight += iterationHeight;
- iterationHeight = 0;
- }
- }
- if (list != null) {
- list.add((ExternalComponent) next);
- }
- }
- }
- maxHeight = totalHeight + iterationHeight;
- return results;
- }
-
- /**
- * Returns a generated image of the fin marking guide. May then be used wherever AWT images can be used, or
- * converted to another image/picture format and used accordingly.
- *
- * @return an awt image of the fin marking guide
- */
- public Image createImage() {
- int width = getWidth() + 25;
- int height = getHeight() + 25;
- // Create a buffered image in which to draw
- BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
- // Create a graphics context on the buffered image
- Graphics2D g2d = bufferedImage.createGraphics();
- // Draw graphics
- g2d.setBackground(Color.white);
- g2d.clearRect(0, 0, width, height);
- paintComponent(g2d);
- // Graphics context no longer needed so dispose it
- g2d.dispose();
- return bufferedImage;
- }
-
- /**
- *
- * ---------------------- Page Edge --------------------------------------------------------
- * | ^
- * | |
- * |
- * | y
- * |
- * | |
- * P v
- * a --- +----------------------------+ ------------
- * g<------^-- x ------------>+ + ^
- * e | + + |
- * | + + baseYOffset
- * E | + + v
- * d | +<----------Fin------------->+ -------------
- * g | + +
- * e | + +
- * | | + +
- * | | + +
- * | | + + baseSpacing
- * | | + +
- * | | + +
- * | | + +
- * | | + +
- * | | +<----------Fin------------->+ --------------
- * | | + +
- * | circumferenceInPoints + +
- * | | + +
- * | | + +
- * | | + + baseSpacing
- * | | +<------Launch Lug --------->+ -----
- * | | + + \
- * | | + + + yLLOffset
- * | | + + /
- * | | +<----------Fin------------->+ --------------
- * | | + + ^
- * | | + + |
- * | | + + baseYOffset
- * | v + + v
- * | --- +----------------------------+ --------------
- *
- * |<-------- width ----------->|
- *
- * yLLOffset is computed from the difference between the base rotation of the fin and the radial direction of the
- * lug.
- *
- * Note: There is a current limitation that a tube with multiple launch lugs may not render the lug lines
- * correctly.
- *
- *
- * @param g the Graphics context
- */
- @Override
- public void paintComponent(Graphics g) {
- super.paintComponent(g);
- Graphics2D g2 = (Graphics2D) g;
- paintFinMarkingGuide(g2);
- }
-
- private void paintFinMarkingGuide(Graphics2D g2) {
- g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
- RenderingHints.VALUE_ANTIALIAS_ON);
-
- g2.setColor(Color.BLACK);
- g2.setStroke(thinStroke);
- int x = MARGIN;
- int y = MARGIN;
-
- int width = (int) PrintUnit.INCHES.toPoints(DEFAULT_GUIDE_WIDTH);
-
- int column = 0;
-
- for (BodyTube next : markingGuideItems.keySet()) {
- double circumferenceInPoints = PrintUnit.METERS.toPoints((next.getOuterRadius() + PAPER_THICKNESS_IN_METERS) *
- TWO_PI);
- List componentList = markingGuideItems.get(next);
- //Don't draw the lug if there are no fins.
- if (hasFins(componentList)) {
-
- drawMarkingGuide(g2, x, y, (int) Math.ceil(circumferenceInPoints), width);
-
- //Sort so that fins always precede lugs
- sort(componentList);
-
- boolean hasMultipleComponents = componentList.size() > 1;
-
- double baseSpacing = 0d;
- double baseYOrigin = 0;
- double finRadial = 0d;
- int yFirstFin = y;
- int yLastFin = y;
- boolean firstFinSet = true;
-
- //fin1: 42 fin2: 25
- for (ExternalComponent externalComponent : componentList) {
- if (externalComponent instanceof FinSet) {
- FinSet fins = (FinSet) externalComponent;
- int finCount = fins.getFinCount();
- int offset = 0;
- baseSpacing = (circumferenceInPoints / finCount);
- double baseRotation = fins.getBaseRotation();
- if (!firstFinSet) {
- //Adjust the rotation to a positive number.
- while (baseRotation < 0) {
- baseRotation += TWO_PI / finCount;
- }
- offset = computeYOffset(y, circumferenceInPoints, baseSpacing, baseYOrigin, finRadial, baseRotation);
- }
- else {
- //baseYOrigin is the distance from the top of the marking guide to the first fin of the first fin set.
- //This measurement is used to base all subsequent finsets and lugs off of.
- baseYOrigin = baseSpacing / 2;
- offset = (int) (baseYOrigin) + y;
- firstFinSet = false;
- }
- yFirstFin = y;
- yLastFin = y;
- finRadial = baseRotation;
-
- //Draw the fin marking lines.
- for (int fin = 0; fin < finCount; fin++) {
- if (fin > 0) {
- offset += baseSpacing;
- yLastFin = offset;
- }
- else {
- yFirstFin = offset;
- }
- drawDoubleArrowLine(g2, x, offset, x + width, offset);
- // if (hasMultipleComponents) {
- g2.drawString(externalComponent.getName(), x + (width / 3), offset - 2);
- // }
- }
- }
- else if (externalComponent instanceof LaunchLug) {
- LaunchLug lug = (LaunchLug) externalComponent;
- double yLLOffset = (lug.getRadialDirection() - finRadial) / TWO_PI;
- //The placement of the lug line must respect the boundary of the outer marking guide. In order
- //to do that, place it above or below either the top or bottom fin line, based on the difference
- //between their rotational directions.
- if (yLLOffset < 0) {
- yLLOffset = yLLOffset * circumferenceInPoints + yLastFin;
- }
- else {
- yLLOffset = yLLOffset * circumferenceInPoints + yFirstFin;
- }
- drawDoubleArrowLine(g2, x, (int) yLLOffset, x + width, (int) yLLOffset);
- g2.drawString(lug.getName(), x + (width / 3), (int) yLLOffset - 2);
-
- }
- }
- //Only if the tube has a lug or multiple finsets does the orientation of the marking guide matter. So print 'Front'.
- if (hasMultipleComponents) {
- drawFrontIndication(g2, x, y, (int) baseSpacing, (int) circumferenceInPoints, width);
- }
-
- //At most, two marking guides horizontally. After that, move down and back to the left margin.
- column++;
- if (column % 2 == 0) {
- x = MARGIN;
- y += circumferenceInPoints + MARGIN;
- }
- else {
- x += MARGIN + width;
- }
- }
- }
- }
-
- /**
- * Compute the y offset for the next fin line.
- *
- * @param y the top margin
- * @param circumferenceInPoints the circumference (height) of the guide
- * @param baseSpacing the circumference / fin count
- * @param baseYOrigin the offset from the top of the guide to the first fin of the first fin set drawn
- * @param prevBaseRotation the rotation of the previous finset
- * @param baseRotation the rotation of the current finset
- *
- * @return number of points from the top of the marking guide to the line to be drawn
- */
- private int computeYOffset(int y, double circumferenceInPoints, double baseSpacing, double baseYOrigin, double prevBaseRotation, double baseRotation) {
- int offset;
- double finRadialDifference = (baseRotation - prevBaseRotation) / TWO_PI;
- //If the fin line would be off the top of the marking guide, then readjust.
- if (baseYOrigin + finRadialDifference * circumferenceInPoints < 0) {
- offset = (int) (baseYOrigin + baseSpacing + finRadialDifference * circumferenceInPoints) + y;
- }
- else if (baseYOrigin - finRadialDifference * circumferenceInPoints > 0) {
- offset = (int) (finRadialDifference * circumferenceInPoints + baseYOrigin) + y;
- }
- else {
- offset = (int) (finRadialDifference * circumferenceInPoints - baseYOrigin) + y;
- }
- return offset;
- }
-
- /**
- * Determines if the list contains a FinSet.
- *
- * @param list a list of ExternalComponent
- *
- * @return true if the list contains at least one FinSet
- */
- private boolean hasFins(List list) {
- for (ExternalComponent externalComponent : list) {
- if (externalComponent instanceof FinSet) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Sort a list of ExternalComponent in-place. Forces FinSets to precede Launch Lugs.
- *
- * @param componentList a list of ExternalComponent
- */
- private void sort(List componentList) {
- Collections.sort(componentList, new Comparator() {
- @Override
- public int compare(ExternalComponent o1, ExternalComponent o2) {
- if (o1 instanceof FinSet) {
- return -1;
- }
- if (o2 instanceof FinSet) {
- return 1;
- }
- return 0;
- }
- });
- }
-
- /**
- * Draw the marking guide outline.
- *
- * @param g2 the graphics context
- * @param x the starting x coordinate
- * @param y the starting y coordinate
- * @param length the length, or height, in print units of the marking guide; should be equivalent to the outer tube
- * circumference
- * @param width the width of the marking guide in print units; somewhat arbitrary
- */
- private void drawMarkingGuide(Graphics2D g2, int x, int y, int length, int width) {
- Path2D outline = new Path2D.Float(GeneralPath.WIND_EVEN_ODD, 4);
- outline.moveTo(x, y);
- outline.lineTo(width + x, y);
- outline.lineTo(width + x, length + y);
- outline.lineTo(x, length + y);
- outline.closePath();
- g2.draw(outline);
-
- //Draw tick marks for alignment, 1/4 of the width in from either edge
- int fromEdge = (width) / 4;
- final int tickLength = 8;
- //Upper left
- g2.drawLine(x + fromEdge, y, x + fromEdge, y + tickLength);
- //Upper right
- g2.drawLine(x + width - fromEdge, y, x + width - fromEdge, y + tickLength);
- //Lower left
- g2.drawLine(x + fromEdge, y + length - tickLength, x + fromEdge, y + length);
- //Lower right
- g2.drawLine(x + width - fromEdge, y + length - tickLength, x + width - fromEdge, y + length);
- }
-
- /**
- * Draw a vertical string indicating the front of the rocket. This is necessary when a launch lug exists to give
- * proper orientation of the guide (assuming that the lug is asymmetrically positioned with respect to a fin).
- *
- * @param g2 the graphics context
- * @param x the starting x coordinate
- * @param y the starting y coordinate
- * @param spacing the space between fin lines
- * @param length the length, or height, in print units of the marking guide; should be equivalent to the outer tube
- * circumference
- * @param width the width of the marking guide in print units; somewhat arbitrary
- */
- private void drawFrontIndication(Graphics2D g2, int x, int y, int spacing, int length, int width) {
- //The magic numbers here are fairly arbitrary. They're chosen in a manner that best positions 'Front' to be
- //readable, without going to complex string layout prediction logic.
- int rotateX = x + width - 16;
- int rotateY = y + (int) (spacing * 1.5) + 20;
- if (rotateY > y + length + 14) {
- rotateY = y + length / 2 - 10;
- }
- g2.translate(rotateX, rotateY);
- g2.rotate(Math.PI / 2);
- g2.drawString(trans.get("FinMarkingGuide.lbl.Front"), 0, 0);
- g2.rotate(-Math.PI / 2);
- g2.translate(-rotateX, -rotateY);
- }
-
- /**
- * Draw a horizontal line with arrows on both endpoints. Depicts a fin alignment.
- *
- * @param g2 the graphics context
- * @param x1 the starting x coordinate
- * @param y1 the starting y coordinate
- * @param x2 the ending x coordinate
- * @param y2 the ending y coordinate
- */
- void drawDoubleArrowLine(Graphics2D g2, int x1, int y1, int x2, int y2) {
- int len = x2 - x1;
-
- g2.drawLine(x1, y1, x1 + len, y2);
- g2.fillPolygon(new int[]{x1 + len, x1 + len - ARROW_SIZE, x1 + len - ARROW_SIZE, x1 + len},
- new int[]{y2, y2 - ARROW_SIZE / 2, y2 + ARROW_SIZE / 2, y2}, 4);
-
- g2.fillPolygon(new int[]{x1, x1 + ARROW_SIZE, x1 + ARROW_SIZE, x1},
- new int[]{y1, y1 - ARROW_SIZE / 2, y1 + ARROW_SIZE / 2, y1}, 4);
- }
+
+ /**
+ * The stroke of normal lines.
+ */
+ private final static BasicStroke thinStroke = new BasicStroke(1.0f);
+
+ /**
+ * The size of the arrow in points.
+ */
+ private static final int ARROW_SIZE = 10;
+
+ /**
+ * Typical thickness of a piece of printer paper (~20-24 lb paper). Wrapping paper around a tube results in the
+ * radius being increased by the thickness of the paper. The smaller the tube, the more pronounced this becomes as a
+ * percentage of circumference. Using 1/10mm as an approximation here.
+ */
+ private static final double PAPER_THICKNESS_IN_METERS = PrintUnit.MILLIMETERS.toMeters(0.1d);
+
+ /**
+ * The default guide width in inches.
+ */
+ public final static double DEFAULT_GUIDE_WIDTH = 3d;
+
+ /**
+ * 2 PI radians (represents a circle).
+ */
+ public final static double TWO_PI = 2 * Math.PI;
+ public final static double PI = Math.PI;
+
+ /**
+ * The I18N translator.
+ */
+ private static final Translator trans = Application.getTranslator();
+
+ /**
+ * The margin.
+ */
+ private static final int MARGIN = (int) PrintUnit.INCHES.toPoints(0.25f);
+
+ /**
+ * The height (circumference) of the biggest body tube with a finset.
+ */
+ private int maxHeight = 0;
+
+ /**
+ * A map of body tubes, to a list of components that contains finsets and launch lugs.
+ */
+ private Map> markingGuideItems;
+
+ /**
+ * Constructor.
+ *
+ * @param rocket the rocket instance
+ */
+ public FinMarkingGuide(Rocket rocket) {
+ super(false);
+ setBackground(Color.white);
+ markingGuideItems = init(rocket);
+ //Max of 2 drawing guides horizontally per page.
+ setSize((int) PrintUnit.INCHES.toPoints(DEFAULT_GUIDE_WIDTH) * 2 + 3 * MARGIN, maxHeight);
+ }
+
+ /**
+ * Initialize the marking guide class by iterating over a rocket and finding all finsets.
+ *
+ * @param component the root rocket component - this is iterated to find all finset and launch lugs
+ *
+ * @return a map of body tubes to lists of finsets and launch lugs.
+ */
+ private Map> init(Rocket component) {
+ Iterator iter = component.iterator(false);
+ Map> results = new LinkedHashMap>();
+ BodyTube current = null;
+ int totalHeight = 0;
+ int iterationHeight = 0;
+ int count = 0;
+
+ while (iter.hasNext()) {
+ RocketComponent next = iter.next();
+ if (next instanceof BodyTube) {
+ current = (BodyTube) next;
+ }
+ else if (next instanceof FinSet || next instanceof LaunchLug) {
+ java.util.List list = results.get(current);
+ if (list == null && current != null) {
+ list = new ArrayList();
+ results.put(current, list);
+
+ double radius = current.getOuterRadius();
+ int circumferenceInPoints = (int) PrintUnit.METERS.toPoints(radius * TWO_PI);
+
+ // Find the biggest body tube circumference.
+ if (iterationHeight < (circumferenceInPoints + MARGIN)) {
+ iterationHeight = circumferenceInPoints + MARGIN;
+ }
+ //At most, two marking guides horizontally. After that, move down and back to the left margin.
+ count++;
+ if (count % 2 == 0) {
+ totalHeight += iterationHeight;
+ iterationHeight = 0;
+ }
+ }
+ if (list != null) {
+ list.add((ExternalComponent) next);
+ }
+ }
+ }
+ maxHeight = totalHeight + iterationHeight;
+ return results;
+ }
+
+ /**
+ * Returns a generated image of the fin marking guide. May then be used wherever AWT images can be used, or
+ * converted to another image/picture format and used accordingly.
+ *
+ * @return an awt image of the fin marking guide
+ */
+ public Image createImage() {
+ int width = getWidth() + 25;
+ int height = getHeight() + 25;
+ // Create a buffered image in which to draw
+ BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ // Create a graphics context on the buffered image
+ Graphics2D g2d = bufferedImage.createGraphics();
+ // Draw graphics
+ g2d.setBackground(Color.white);
+ g2d.clearRect(0, 0, width, height);
+ paintComponent(g2d);
+ // Graphics context no longer needed so dispose it
+ g2d.dispose();
+ return bufferedImage;
+ }
+
+ /**
+ *
+ * ---------------------- Page Edge --------------------------------------------------------
+ * | ^
+ * | |
+ * |
+ * | y
+ * |
+ * | |
+ * P v
+ * a --- +----------------------------+ <- radialOrigin (in radians)
+ * g<------^-- x ------------>+ +
+ * e | + +
+ * | + +
+ * E | + +
+ * d | +<----------Fin------------->+ <- y+ (finRadialPosition - radialOrigin) / TWO_PI * circumferenceInPoints
+ * g | + +
+ * e | + +
+ * | | + +
+ * | | + +
+ * | | + +
+ * | | + +
+ * | | + +
+ * | | + +
+ * | | + +
+ * | | +<----------Fin------------->+
+ * | | + +
+ * | circumferenceInPoints + +
+ * | | + +
+ * | | + +
+ * | | + +
+ * | | +<------Launch Lug --------->+
+ * | | + +
+ * | | + +
+ * | | + +
+ * | | +<----------Fin------------->+
+ * | | + +
+ * | | + +
+ * | | + +
+ * | v + +
+ * | --- +----------------------------+ <- radialOrigin + TWO_PI
+ *
+ * |<-------- width ----------->|
+ *
+ * yLLOffset is computed from the difference between the base rotation of the fin and the radial direction of the
+ * lug.
+ *
+ * Note: There is a current limitation that a tube with multiple launch lugs may not render the lug lines
+ * correctly.
+ *
+ *
+ * @param g the Graphics context
+ */
+ @Override
+ public void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ Graphics2D g2 = (Graphics2D) g;
+ paintFinMarkingGuide(g2);
+ }
+
+ private void paintFinMarkingGuide(Graphics2D g2) {
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+
+ g2.setColor(Color.BLACK);
+ g2.setStroke(thinStroke);
+ int x = MARGIN;
+ int y = MARGIN;
+
+ int width = (int) PrintUnit.INCHES.toPoints(DEFAULT_GUIDE_WIDTH);
+
+ int column = 0;
+
+ for (BodyTube next : markingGuideItems.keySet()) {
+ double circumferenceInPoints = PrintUnit.METERS.toPoints((next.getOuterRadius() + PAPER_THICKNESS_IN_METERS) *
+ TWO_PI);
+ List componentList = markingGuideItems.get(next);
+ //Don't draw the lug if there are no fins.
+ if (hasFins(componentList)) {
+
+ drawMarkingGuide(g2, x, y, (int) Math.ceil(circumferenceInPoints), width);
+
+ double radialOrigin = findRadialOrigin(componentList);
+
+ boolean hasMultipleComponents = componentList.size() > 1;
+
+ //fin1: 42 fin2: 25
+ for (ExternalComponent externalComponent : componentList) {
+ if (externalComponent instanceof FinSet) {
+ FinSet fins = (FinSet) externalComponent;
+ int finCount = fins.getFinCount();
+ double baseAngularSpacing = (TWO_PI / finCount);
+ double baseAngularOffset = fins.getBaseRotation();
+ //Draw the fin marking lines.
+ for (int fin = 0; fin < finCount; fin++) {
+ double angle = baseAngularOffset + fin * baseAngularSpacing - radialOrigin;
+ // Translate angle into pixels using a linear transformation:
+ // radialOrigin -> y
+ // radialOrigin + TWO_PI -> y + circumferenceInPoints
+
+ while (angle < 0) {
+ angle += TWO_PI;
+ }
+ while (angle > TWO_PI) {
+ angle -= TWO_PI;
+ }
+
+ int offset = (int) Math.round(y + angle / TWO_PI * circumferenceInPoints);
+
+ drawDoubleArrowLine(g2, x, offset, x + width, offset);
+ // if (hasMultipleComponents) {
+ g2.drawString(externalComponent.getName(), x + (width / 3), offset - 2);
+ // }
+ }
+ }
+ else if (externalComponent instanceof LaunchLug) {
+ LaunchLug lug = (LaunchLug) externalComponent;
+ double angle = lug.getRadialDirection() - radialOrigin;
+ while (angle < 0) {
+ angle += TWO_PI;
+ }
+ int yLLOffset = (int) Math.round(y + angle / TWO_PI * circumferenceInPoints);
+ drawDoubleArrowLine(g2, x, (int) yLLOffset, x + width, (int) yLLOffset);
+ g2.drawString(lug.getName(), x + (width / 3), (int) yLLOffset - 2);
+
+ }
+ }
+ //Only if the tube has a lug or multiple finsets does the orientation of the marking guide matter. So print 'Front'.
+ if (hasMultipleComponents) {
+ drawFrontIndication(g2, x, y, 0, (int) circumferenceInPoints, width);
+ }
+
+ //At most, two marking guides horizontally. After that, move down and back to the left margin.
+ column++;
+ if (column % 2 == 0) {
+ x = MARGIN;
+ y += circumferenceInPoints + MARGIN;
+ }
+ else {
+ x += MARGIN + width;
+ }
+ }
+ }
+ }
+
+ /**
+ * This function finds a origin in radians for the template so no component is on the template seam.
+ *
+ * If no fin or launch lug is at 0.0 radians, then the origin is 0. If there is one, then half the distance
+ * between the two are taken.
+ *
+ * @param components
+ * @return
+ */
+ private double findRadialOrigin(List components) {
+
+ ArrayList positions = new ArrayList(3 * components.size());
+
+ for (ExternalComponent component : components) {
+
+ if (component instanceof LaunchLug) {
+ double componentPosition = ((LaunchLug) component).getRadialDirection();
+
+ positions.add(makeZeroTwoPi(componentPosition));
+ }
+
+ if (component instanceof FinSet) {
+ FinSet fins = (FinSet) component;
+ double basePosition = fins.getBaseRotation();
+ double angle = TWO_PI / fins.getFinCount();
+ for (int i = fins.getFinCount(); i > 0; i--) {
+ positions.add(makeZeroTwoPi(basePosition));
+ basePosition += angle;
+ }
+ }
+ }
+
+ Collections.sort(positions);
+
+ Double[] pos = positions.toArray(new Double[0]);
+
+ if (pos.length == 1) {
+
+ return makeZeroTwoPi(pos[0] + PI);
+
+ }
+
+ double biggestDistance = TWO_PI - pos[pos.length - 1] + pos[0];
+ double center = makeZeroTwoPi(pos[0] - biggestDistance / 2.0);
+
+ for (int i = 1; i < pos.length; i++) {
+
+ double d = pos[i] - pos[i - 1];
+ if (d > biggestDistance) {
+ biggestDistance = d;
+ center = makeZeroTwoPi(pos[i - 1] + biggestDistance / 2.0);
+ }
+ }
+
+ return center;
+ }
+
+ private static double makeZeroTwoPi(double value) {
+
+ double v = value;
+ while (v < 0) {
+ v += TWO_PI;
+ }
+ while (v > TWO_PI) {
+ v -= TWO_PI;
+ }
+
+ return v;
+ }
+
+ /**
+ * Determines if the list contains a FinSet.
+ *
+ * @param list a list of ExternalComponent
+ *
+ * @return true if the list contains at least one FinSet
+ */
+ private boolean hasFins(List list) {
+ for (ExternalComponent externalComponent : list) {
+ if (externalComponent instanceof FinSet) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Draw the marking guide outline.
+ *
+ * @param g2 the graphics context
+ * @param x the starting x coordinate
+ * @param y the starting y coordinate
+ * @param length the length, or height, in print units of the marking guide; should be equivalent to the outer tube
+ * circumference
+ * @param width the width of the marking guide in print units; somewhat arbitrary
+ */
+ private void drawMarkingGuide(Graphics2D g2, int x, int y, int length, int width) {
+ Path2D outline = new Path2D.Float(GeneralPath.WIND_EVEN_ODD, 4);
+ outline.moveTo(x, y);
+ outline.lineTo(width + x, y);
+ outline.lineTo(width + x, length + y);
+ outline.lineTo(x, length + y);
+ outline.closePath();
+ g2.draw(outline);
+
+ //Draw tick marks for alignment, 1/4 of the width in from either edge
+ int fromEdge = (width) / 4;
+ final int tickLength = 8;
+ //Upper left
+ g2.drawLine(x + fromEdge, y, x + fromEdge, y + tickLength);
+ //Upper right
+ g2.drawLine(x + width - fromEdge, y, x + width - fromEdge, y + tickLength);
+ //Lower left
+ g2.drawLine(x + fromEdge, y + length - tickLength, x + fromEdge, y + length);
+ //Lower right
+ g2.drawLine(x + width - fromEdge, y + length - tickLength, x + width - fromEdge, y + length);
+ }
+
+ /**
+ * Draw a vertical string indicating the front of the rocket. This is necessary when a launch lug exists to give
+ * proper orientation of the guide (assuming that the lug is asymmetrically positioned with respect to a fin).
+ *
+ * @param g2 the graphics context
+ * @param x the starting x coordinate
+ * @param y the starting y coordinate
+ * @param spacing the space between fin lines
+ * @param length the length, or height, in print units of the marking guide; should be equivalent to the outer tube
+ * circumference
+ * @param width the width of the marking guide in print units; somewhat arbitrary
+ */
+ private void drawFrontIndication(Graphics2D g2, int x, int y, int spacing, int length, int width) {
+ //The magic numbers here are fairly arbitrary. They're chosen in a manner that best positions 'Front' to be
+ //readable, without going to complex string layout prediction logic.
+ int rotateX = x + width - 16;
+ int rotateY = y + (int) (spacing * 1.5) + 20;
+ if (rotateY > y + length + 14) {
+ rotateY = y + length / 2 - 10;
+ }
+ g2.translate(rotateX, rotateY);
+ g2.rotate(Math.PI / 2);
+ g2.drawString(trans.get("FinMarkingGuide.lbl.Front"), 0, 0);
+ g2.rotate(-Math.PI / 2);
+ g2.translate(-rotateX, -rotateY);
+ }
+
+ /**
+ * Draw a horizontal line with arrows on both endpoints. Depicts a fin alignment.
+ *
+ * @param g2 the graphics context
+ * @param x1 the starting x coordinate
+ * @param y1 the starting y coordinate
+ * @param x2 the ending x coordinate
+ * @param y2 the ending y coordinate
+ */
+ void drawDoubleArrowLine(Graphics2D g2, int x1, int y1, int x2, int y2) {
+ int len = x2 - x1;
+
+ g2.drawLine(x1, y1, x1 + len, y2);
+ g2.fillPolygon(new int[] { x1 + len, x1 + len - ARROW_SIZE, x1 + len - ARROW_SIZE, x1 + len },
+ new int[] { y2, y2 - ARROW_SIZE / 2, y2 + ARROW_SIZE / 2, y2 }, 4);
+
+ g2.fillPolygon(new int[] { x1, x1 + ARROW_SIZE, x1 + ARROW_SIZE, x1 },
+ new int[] { y1, y1 - ARROW_SIZE / 2, y1 + ARROW_SIZE / 2, y1 }, 4);
+ }
}
\ No newline at end of file