1 package net.sf.openrocket.gui.scalefigure;
3 import java.awt.BasicStroke;
5 import java.awt.Dimension;
6 import java.awt.Graphics;
7 import java.awt.Graphics2D;
8 import java.awt.Rectangle;
9 import java.awt.RenderingHints;
10 import java.awt.geom.AffineTransform;
11 import java.awt.geom.Line2D;
12 import java.awt.geom.NoninvertibleTransformException;
13 import java.awt.geom.Path2D;
14 import java.awt.geom.Point2D;
15 import java.awt.geom.Rectangle2D;
17 import net.sf.openrocket.rocketcomponent.FreeformFinSet;
18 import net.sf.openrocket.unit.Tick;
19 import net.sf.openrocket.unit.Unit;
20 import net.sf.openrocket.unit.UnitGroup;
21 import net.sf.openrocket.util.Coordinate;
22 import net.sf.openrocket.util.MathUtil;
25 // TODO: MEDIUM: the figure jumps and bugs when using automatic fitting
27 public class FinPointFigure extends AbstractScaleFigure {
29 private static final int BOX_SIZE = 4;
31 private final FreeformFinSet finset;
32 private int modID = -1;
34 private double minX, maxX, maxY;
35 private double figureWidth = 0;
36 private double figureHeight = 0;
37 private double translateX = 0;
38 private double translateY = 0;
40 private AffineTransform transform;
41 private Rectangle2D.Double[] handles = null;
44 public FinPointFigure(FreeformFinSet finset) {
50 public void paintComponent(Graphics g) {
51 super.paintComponent(g);
52 Graphics2D g2 = (Graphics2D) g;
54 if (modID != finset.getRocket().getAerodynamicModID()) {
55 modID = finset.getRocket().getAerodynamicModID();
56 calculateDimensions();
61 // Calculate translation for figure centering
62 if (figureWidth * scale + 2 * borderPixelsWidth < getWidth()) {
64 // Figure fits in the viewport
65 tx = (getWidth() - figureWidth * scale) / 2 - minX * scale;
69 // Figure does not fit in viewport
70 tx = borderPixelsWidth - minX * scale;
75 if (figureHeight * scale + 2 * borderPixelsHeight < getHeight()) {
76 ty = getHeight() - borderPixelsHeight;
78 ty = borderPixelsHeight + figureHeight * scale;
81 if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) {
82 // Origin has changed, fire event
89 if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) {
90 // Origin has changed, fire event
97 // Calculate and store the transformation used
98 transform = new AffineTransform();
99 transform.translate(translateX, translateY);
100 transform.scale(scale / EXTRA_SCALE, -scale / EXTRA_SCALE);
102 // TODO: HIGH: border Y-scale upwards
104 g2.transform(transform);
106 // Set rendering hints appropriately
107 g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
108 RenderingHints.VALUE_STROKE_NORMALIZE);
109 g2.setRenderingHint(RenderingHints.KEY_RENDERING,
110 RenderingHints.VALUE_RENDER_QUALITY);
111 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
112 RenderingHints.VALUE_ANTIALIAS_ON);
116 Rectangle visible = g2.getClipBounds();
117 double x0 = ((double) visible.x - 3) / EXTRA_SCALE;
118 double x1 = ((double) visible.x + visible.width + 4) / EXTRA_SCALE;
119 double y0 = ((double) visible.y - 3) / EXTRA_SCALE;
120 double y1 = ((double) visible.y + visible.height + 4) / EXTRA_SCALE;
125 g2.setStroke(new BasicStroke((float) (1.0 * EXTRA_SCALE / scale),
126 BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
127 g2.setColor(new Color(0, 0, 255, 30));
130 if (this.getParent() != null &&
131 this.getParent().getParent() instanceof ScaleScrollPane) {
132 unit = ((ScaleScrollPane) this.getParent().getParent()).getCurrentUnit();
134 unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
138 Tick[] ticks = unit.getTicks(x0, x1,
139 ScaleScrollPane.MINOR_TICKS / scale,
140 ScaleScrollPane.MAJOR_TICKS / scale);
141 Line2D.Double line = new Line2D.Double();
142 for (Tick t : ticks) {
144 line.setLine(t.value * EXTRA_SCALE, y0 * EXTRA_SCALE,
145 t.value * EXTRA_SCALE, y1 * EXTRA_SCALE);
151 ticks = unit.getTicks(y0, y1,
152 ScaleScrollPane.MINOR_TICKS / scale,
153 ScaleScrollPane.MAJOR_TICKS / scale);
154 for (Tick t : ticks) {
156 line.setLine(x0 * EXTRA_SCALE, t.value * EXTRA_SCALE,
157 x1 * EXTRA_SCALE, t.value * EXTRA_SCALE);
167 g2.setStroke(new BasicStroke((float) (3.0 * EXTRA_SCALE / scale),
168 BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
169 g2.setColor(Color.GRAY);
171 g2.drawLine((int) (x0 * EXTRA_SCALE), 0, (int) (x1 * EXTRA_SCALE), 0);
175 Coordinate[] points = finset.getFinPoints();
176 Path2D.Double shape = new Path2D.Double();
178 for (int i = 1; i < points.length; i++) {
179 shape.lineTo(points[i].x * EXTRA_SCALE, points[i].y * EXTRA_SCALE);
182 g2.setStroke(new BasicStroke((float) (1.0 * EXTRA_SCALE / scale),
183 BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
184 g2.setColor(Color.BLACK);
189 g2.setColor(new Color(150, 0, 0));
190 double s = BOX_SIZE * EXTRA_SCALE / scale;
191 handles = new Rectangle2D.Double[points.length];
192 for (int i = 0; i < points.length; i++) {
193 Coordinate c = points[i];
194 handles[i] = new Rectangle2D.Double(c.x * EXTRA_SCALE - s, c.y * EXTRA_SCALE - s, 2 * s, 2 * s);
202 public int getIndexByPoint(double x, double y) {
206 // Calculate point in shapes' coordinates
207 Point2D.Double p = new Point2D.Double(x, y);
209 transform.inverseTransform(p, p);
210 } catch (NoninvertibleTransformException e) {
214 for (int i = 0; i < handles.length; i++) {
215 if (handles[i].contains(p))
222 public int getSegmentByPoint(double x, double y) {
226 // Calculate point in shapes' coordinates
227 Point2D.Double p = new Point2D.Double(x, y);
229 transform.inverseTransform(p, p);
230 } catch (NoninvertibleTransformException e) {
234 double x0 = p.x / EXTRA_SCALE;
235 double y0 = p.y / EXTRA_SCALE;
236 double delta = BOX_SIZE / scale;
238 //System.out.println("Point: " + x0 + "," + y0);
239 //System.out.println("delta: " + (BOX_SIZE / scale));
241 Coordinate[] points = finset.getFinPoints();
242 for (int i = 1; i < points.length; i++) {
243 double x1 = points[i - 1].x;
244 double y1 = points[i - 1].y;
245 double x2 = points[i].x;
246 double y2 = points[i].y;
248 // System.out.println("point1:"+x1+","+y1+" point2:"+x2+","+y2);
250 double u = Math.abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1)) /
251 MathUtil.hypot(x2 - x1, y2 - y1);
252 //System.out.println("Distance of segment " + i + " is " + u);
261 public Point2D.Double convertPoint(double x, double y) {
262 Point2D.Double p = new Point2D.Double(x, y);
264 transform.inverseTransform(p, p);
265 } catch (NoninvertibleTransformException e) {
266 assert (false) : "Should not occur";
267 return new Point2D.Double(0, 0);
270 p.setLocation(p.x / EXTRA_SCALE, p.y / EXTRA_SCALE);
277 public Dimension getOrigin() {
278 if (modID != finset.getRocket().getAerodynamicModID()) {
279 modID = finset.getRocket().getAerodynamicModID();
280 calculateDimensions();
282 return new Dimension((int) translateX, (int) translateY);
286 public double getFigureWidth() {
287 if (modID != finset.getRocket().getAerodynamicModID()) {
288 modID = finset.getRocket().getAerodynamicModID();
289 calculateDimensions();
295 public double getFigureHeight() {
296 if (modID != finset.getRocket().getAerodynamicModID()) {
297 modID = finset.getRocket().getAerodynamicModID();
298 calculateDimensions();
304 private void calculateDimensions() {
309 for (Coordinate c : finset.getFinPoints()) {
321 figureWidth = maxX - minX;
325 Dimension d = new Dimension((int) (figureWidth * scale + 2 * borderPixelsWidth),
326 (int) (figureHeight * scale + 2 * borderPixelsHeight));
328 if (!d.equals(getPreferredSize()) || !d.equals(getMinimumSize())) {
338 public void updateFigure() {