package net.sf.openrocket.gui.print;
import net.sf.openrocket.gui.print.visitor.Dimension;
import net.sf.openrocket.rocketcomponent.CenteringRing;
import net.sf.openrocket.rocketcomponent.ClusterConfiguration;
import net.sf.openrocket.rocketcomponent.InnerTube;
import net.sf.openrocket.util.ArrayList;
import net.sf.openrocket.util.Coordinate;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* This class creates a renderable centering ring. It depends only on AWT/Swing and can be called from other actors
* (like iText handlers) to render the centering ring on different graphics contexts.
*/
public class PrintableCenteringRing extends AbstractPrintable<CenteringRing> {
/**
* If the component to be drawn is a centering ring, save a reference to it.
*/
private CenteringRing target;
/**
* The line length of the cross hairs.
*/
private final int lineLength = 10;
/**
* A set of the inner 'holes'. At least one, but will have many if clustered.
*/
private Set<Dimension> innerCenterPoints = new HashSet<Dimension>();
/**
* Construct a simple, non-clustered, printable centering ring, or if the motor mount represents a clustered
* configuration then get the cluster points to create the centering ring.
*
* @param theRing the component to print
* @param theMotorMount the motor mount if clustered, else null
*/
private PrintableCenteringRing(CenteringRing theRing, InnerTube theMotorMount) {
super(theRing);
if (theMotorMount == null || theMotorMount.getClusterConfiguration().equals(ClusterConfiguration.SINGLE)) {
//Single motor.
final float v = (float) PrintUnit.METERS.toPoints(target.getOuterRadius());
innerCenterPoints.add(
new Dimension(v, v,
(float) PrintUnit.METERS.toPoints((target.getInnerRadius()))));
}
else {
List<Coordinate> coords = theMotorMount.getClusterPoints();
List<Coordinate> points = new ArrayList<Coordinate>();
for (Coordinate coordinate : coords) {
points.add(coordinate.setX(theMotorMount.getOuterRadius()));
}
populateCenterPoints(points);
}
}
/**
* Constructor for a clustered centering ring. This version is for a "split cluster", where each motor mount tube
* is a distinct entity.
*
* @param theRing the centering ring component
* @param theMotorMounts a list of the motor mount tubes that are physically supported by the centering ring
*/
private PrintableCenteringRing(CenteringRing theRing, List<InnerTube> theMotorMounts) {
super(theRing);
List<Coordinate> points = new ArrayList<Coordinate>();
//Transform the radial positions of the tubes.
for (InnerTube it : theMotorMounts) {
if (it.getClusterCount() > 1) {
List<Coordinate> c = it.getClusterPoints();
for (Coordinate coordinate : c) {
points.add(coordinate.setX(it.getOuterRadius()));
}
}
else {
double y = it.getRadialShiftY();
double z = it.getRadialShiftZ();
Coordinate coordinate = new Coordinate(it.getOuterRadius(), y, z);
points.add(coordinate);
}
}
populateCenterPoints(points);
}
/**
* Factory method to create a printable centering ring.
*
* @param theRing the component to print
* @param theMotorMounts the motor mount if clustered, else null
*/
public static PrintableCenteringRing create(CenteringRing theRing, List<InnerTube> theMotorMounts) {
if (theMotorMounts == null) {
return new PrintableCenteringRing(theRing, (InnerTube) null);
}
else if (theMotorMounts.size() <= 1) {
return new PrintableCenteringRing(theRing, theMotorMounts.isEmpty() ? null : theMotorMounts.get(0));
}
else {
return new PrintableCenteringRing(theRing, theMotorMounts);
}
}
/**
* Initialize the set of center points for each motor mount tube, based on the tube coordinates.
*
* @param theCoords the list of tube coordinates; each coordinate is in the OR units (meters) and must be
* transformed to the printing (points) coordinate system
*/
private void populateCenterPoints(final List<Coordinate> theCoords) {
float radius = (float) PrintUnit.METERS.toPoints(target.getOuterRadius());
for (Coordinate coordinate : theCoords) {
innerCenterPoints.add(new Dimension(
(float) PrintUnit.METERS.toPoints(coordinate.y) + radius, //center point x
(float) PrintUnit.METERS.toPoints(coordinate.z) + radius, //center point y
(float) PrintUnit.METERS.toPoints(coordinate.x))); //radius of motor mount
}
}
/**
* @param component the centering ring component
*/
@Override
protected void init(final CenteringRing component) {
target = component;
double radius = target.getOuterRadius();
setSize((int) PrintUnit.METERS.toPoints(2 * radius),
(int) PrintUnit.METERS.toPoints(2 * radius));
}
/**
* Draw a centering ring.
*
* @param g2 the graphics context
*/
@Override
protected void draw(Graphics2D g2) {
double radius = PrintUnit.METERS.toPoints(target.getOuterRadius());
Color original = g2.getBackground();
Shape outerCircle = new Ellipse2D.Double(0, 0, radius * 2, radius * 2);
g2.setColor(Color.lightGray);
g2.fill(outerCircle);
g2.setColor(Color.black);
g2.draw(outerCircle);
for (Dimension next : innerCenterPoints) {
drawInnerCircle(g2, next.getWidth(), next.getHeight(), next.getBreadth());
}
g2.setColor(original);
}
/**
* Draw one inner circle, representing the motor mount tube, with cross hairs in the center.
*
* @param g2 the graphics context
* @param theCenterX the center x in points
* @param theCenterY the center y in points
*/
private void drawInnerCircle(final Graphics2D g2, final double theCenterX, final double theCenterY,
final double innerRadius) {
Shape innerCircle = new Ellipse2D.Double(theCenterX - innerRadius, theCenterY - innerRadius, innerRadius * 2, innerRadius * 2);
g2.setColor(Color.white);
g2.fill(innerCircle);
g2.setColor(Color.black);
g2.draw(innerCircle);
drawCross(g2, (int) theCenterX, (int) theCenterY, lineLength, lineLength);
}
/**
* Draw the center cross-hair.
*
* @param g the graphics context
* @param x the x coordinate of the center point
* @param y the y coordinate of the center point
* @param width the width in pixels of the horizontal hair
* @param height the width in pixels of the vertical hair
*/
private void drawCross(Graphics g, int x, int y, int width, int height) {
g.setColor(Color.black);
((Graphics2D) g).setStroke(thinStroke);
g.drawLine(x - width / 2, y, x + width / 2, y);
g.drawLine(x, y - height / 2, x, y + height / 2);
}
}