package net.sf.openrocket.gui.scalefigure; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import net.sf.openrocket.rocketcomponent.FreeformFinSet; import net.sf.openrocket.unit.Tick; import net.sf.openrocket.unit.Unit; import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; // TODO: MEDIUM: the figure jumps and bugs when using automatic fitting public class FinPointFigure extends AbstractScaleFigure { private static final int BOX_SIZE = 4; private final FreeformFinSet finset; private int modID = -1; private double minX, maxX, maxY; private double figureWidth = 0; private double figureHeight = 0; private double translateX = 0; private double translateY = 0; private AffineTransform transform; private Rectangle2D.Double[] handles = null; public FinPointFigure(FreeformFinSet finset) { this.finset = finset; } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; if (modID != finset.getRocket().getAerodynamicModID()) { modID = finset.getRocket().getAerodynamicModID(); calculateDimensions(); } double tx, ty; // Calculate translation for figure centering if (figureWidth * scale + 2 * borderPixelsWidth < getWidth()) { // Figure fits in the viewport tx = (getWidth() - figureWidth * scale) / 2 - minX * scale; } else { // Figure does not fit in viewport tx = borderPixelsWidth - minX * scale; } if (figureHeight * scale + 2 * borderPixelsHeight < getHeight()) { ty = getHeight() - borderPixelsHeight; } else { ty = borderPixelsHeight + figureHeight * scale; } if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) { // Origin has changed, fire event translateX = tx; translateY = ty; fireChangeEvent(); } if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) { // Origin has changed, fire event translateX = tx; translateY = ty; fireChangeEvent(); } // Calculate and store the transformation used transform = new AffineTransform(); transform.translate(translateX, translateY); transform.scale(scale / EXTRA_SCALE, -scale / EXTRA_SCALE); // TODO: HIGH: border Y-scale upwards g2.transform(transform); // Set rendering hints appropriately g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Rectangle visible = g2.getClipBounds(); double x0 = ((double) visible.x - 3) / EXTRA_SCALE; double x1 = ((double) visible.x + visible.width + 4) / EXTRA_SCALE; double y0 = ((double) visible.y - 3) / EXTRA_SCALE; double y1 = ((double) visible.y + visible.height + 4) / EXTRA_SCALE; // Background grid g2.setStroke(new BasicStroke((float) (1.0 * EXTRA_SCALE / scale), BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); g2.setColor(new Color(0, 0, 255, 30)); Unit unit; if (this.getParent() != null && this.getParent().getParent() instanceof ScaleScrollPane) { unit = ((ScaleScrollPane) this.getParent().getParent()).getCurrentUnit(); } else { unit = UnitGroup.UNITS_LENGTH.getDefaultUnit(); } // vertical Tick[] ticks = unit.getTicks(x0, x1, ScaleScrollPane.MINOR_TICKS / scale, ScaleScrollPane.MAJOR_TICKS / scale); Line2D.Double line = new Line2D.Double(); for (Tick t : ticks) { if (t.major) { line.setLine(t.value * EXTRA_SCALE, y0 * EXTRA_SCALE, t.value * EXTRA_SCALE, y1 * EXTRA_SCALE); g2.draw(line); } } // horizontal ticks = unit.getTicks(y0, y1, ScaleScrollPane.MINOR_TICKS / scale, ScaleScrollPane.MAJOR_TICKS / scale); for (Tick t : ticks) { if (t.major) { line.setLine(x0 * EXTRA_SCALE, t.value * EXTRA_SCALE, x1 * EXTRA_SCALE, t.value * EXTRA_SCALE); g2.draw(line); } } // Base rocket line g2.setStroke(new BasicStroke((float) (3.0 * EXTRA_SCALE / scale), BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); g2.setColor(Color.GRAY); g2.drawLine((int) (x0 * EXTRA_SCALE), 0, (int) (x1 * EXTRA_SCALE), 0); // Fin shape Coordinate[] points = finset.getFinPoints(); Path2D.Double shape = new Path2D.Double(); shape.moveTo(0, 0); for (int i = 1; i < points.length; i++) { shape.lineTo(points[i].x * EXTRA_SCALE, points[i].y * EXTRA_SCALE); } g2.setStroke(new BasicStroke((float) (1.0 * EXTRA_SCALE / scale), BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); g2.setColor(Color.BLACK); g2.draw(shape); // Fin point boxes g2.setColor(new Color(150, 0, 0)); double s = BOX_SIZE * EXTRA_SCALE / scale; handles = new Rectangle2D.Double[points.length]; for (int i = 0; i < points.length; i++) { Coordinate c = points[i]; handles[i] = new Rectangle2D.Double(c.x * EXTRA_SCALE - s, c.y * EXTRA_SCALE - s, 2 * s, 2 * s); g2.draw(handles[i]); } } public int getIndexByPoint(double x, double y) { if (handles == null) return -1; // Calculate point in shapes' coordinates Point2D.Double p = new Point2D.Double(x, y); try { transform.inverseTransform(p, p); } catch (NoninvertibleTransformException e) { return -1; } for (int i = 0; i < handles.length; i++) { if (handles[i].contains(p)) return i; } return -1; } public int getSegmentByPoint(double x, double y) { if (handles == null) return -1; // Calculate point in shapes' coordinates Point2D.Double p = new Point2D.Double(x, y); try { transform.inverseTransform(p, p); } catch (NoninvertibleTransformException e) { return -1; } double x0 = p.x / EXTRA_SCALE; double y0 = p.y / EXTRA_SCALE; double delta = BOX_SIZE / scale; //System.out.println("Point: " + x0 + "," + y0); //System.out.println("delta: " + (BOX_SIZE / scale)); Coordinate[] points = finset.getFinPoints(); for (int i = 1; i < points.length; i++) { double x1 = points[i - 1].x; double y1 = points[i - 1].y; double x2 = points[i].x; double y2 = points[i].y; // System.out.println("point1:"+x1+","+y1+" point2:"+x2+","+y2); double u = Math.abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1)) / MathUtil.hypot(x2 - x1, y2 - y1); //System.out.println("Distance of segment " + i + " is " + u); if (u < delta) return i; } return -1; } public Point2D.Double convertPoint(double x, double y) { Point2D.Double p = new Point2D.Double(x, y); try { transform.inverseTransform(p, p); } catch (NoninvertibleTransformException e) { assert (false) : "Should not occur"; return new Point2D.Double(0, 0); } p.setLocation(p.x / EXTRA_SCALE, p.y / EXTRA_SCALE); return p; } @Override public Dimension getOrigin() { if (modID != finset.getRocket().getAerodynamicModID()) { modID = finset.getRocket().getAerodynamicModID(); calculateDimensions(); } return new Dimension((int) translateX, (int) translateY); } @Override public double getFigureWidth() { if (modID != finset.getRocket().getAerodynamicModID()) { modID = finset.getRocket().getAerodynamicModID(); calculateDimensions(); } return figureWidth; } @Override public double getFigureHeight() { if (modID != finset.getRocket().getAerodynamicModID()) { modID = finset.getRocket().getAerodynamicModID(); calculateDimensions(); } return figureHeight; } private void calculateDimensions() { minX = 0; maxX = 0; maxY = 0; for (Coordinate c : finset.getFinPoints()) { if (c.x < minX) minX = c.x; if (c.x > maxX) maxX = c.x; if (c.y > maxY) maxY = c.y; } if (maxX < 0.01) maxX = 0.01; figureWidth = maxX - minX; figureHeight = maxY; Dimension d = new Dimension((int) (figureWidth * scale + 2 * borderPixelsWidth), (int) (figureHeight * scale + 2 * borderPixelsHeight)); if (!d.equals(getPreferredSize()) || !d.equals(getMinimumSize())) { setPreferredSize(d); setMinimumSize(d); revalidate(); } } @Override public void updateFigure() { repaint(); } }