create changelog entry
[debian/openrocket] / core / src / net / sf / openrocket / gui / scalefigure / FinPointFigure.java
1 package net.sf.openrocket.gui.scalefigure;
2
3 import java.awt.BasicStroke;
4 import java.awt.Color;
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;
16
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;
23
24
25 // TODO: MEDIUM:  the figure jumps and bugs when using automatic fitting
26
27 public class FinPointFigure extends AbstractScaleFigure {
28         
29         private static final int BOX_SIZE = 4;
30         
31         private final FreeformFinSet finset;
32         private int modID = -1;
33         
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;
39         
40         private AffineTransform transform;
41         private Rectangle2D.Double[] handles = null;
42         
43         
44         public FinPointFigure(FreeformFinSet finset) {
45                 this.finset = finset;
46         }
47         
48         
49         @Override
50         public void paintComponent(Graphics g) {
51                 super.paintComponent(g);
52                 Graphics2D g2 = (Graphics2D) g;
53                 
54                 if (modID != finset.getRocket().getAerodynamicModID()) {
55                         modID = finset.getRocket().getAerodynamicModID();
56                         calculateDimensions();
57                 }
58                 
59
60                 double tx, ty;
61                 // Calculate translation for figure centering
62                 if (figureWidth * scale + 2 * borderPixelsWidth < getWidth()) {
63                         
64                         // Figure fits in the viewport
65                         tx = (getWidth() - figureWidth * scale) / 2 - minX * scale;
66                         
67                 } else {
68                         
69                         // Figure does not fit in viewport
70                         tx = borderPixelsWidth - minX * scale;
71                         
72                 }
73                 
74
75                 if (figureHeight * scale + 2 * borderPixelsHeight < getHeight()) {
76                         ty = getHeight() - borderPixelsHeight;
77                 } else {
78                         ty = borderPixelsHeight + figureHeight * scale;
79                 }
80                 
81                 if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) {
82                         // Origin has changed, fire event
83                         translateX = tx;
84                         translateY = ty;
85                         fireChangeEvent();
86                 }
87                 
88
89                 if (Math.abs(translateX - tx) > 1 || Math.abs(translateY - ty) > 1) {
90                         // Origin has changed, fire event
91                         translateX = tx;
92                         translateY = ty;
93                         fireChangeEvent();
94                 }
95                 
96
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);
101                 
102                 // TODO: HIGH:  border Y-scale upwards
103                 
104                 g2.transform(transform);
105                 
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);
113                 
114
115
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;
121                 
122
123                 // Background grid
124                 
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));
128                 
129                 Unit unit;
130                 if (this.getParent() != null &&
131                                 this.getParent().getParent() instanceof ScaleScrollPane) {
132                         unit = ((ScaleScrollPane) this.getParent().getParent()).getCurrentUnit();
133                 } else {
134                         unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
135                 }
136                 
137                 // vertical
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) {
143                         if (t.major) {
144                                 line.setLine(t.value * EXTRA_SCALE, y0 * EXTRA_SCALE,
145                                                 t.value * EXTRA_SCALE, y1 * EXTRA_SCALE);
146                                 g2.draw(line);
147                         }
148                 }
149                 
150                 // horizontal
151                 ticks = unit.getTicks(y0, y1,
152                                 ScaleScrollPane.MINOR_TICKS / scale,
153                                 ScaleScrollPane.MAJOR_TICKS / scale);
154                 for (Tick t : ticks) {
155                         if (t.major) {
156                                 line.setLine(x0 * EXTRA_SCALE, t.value * EXTRA_SCALE,
157                                                 x1 * EXTRA_SCALE, t.value * EXTRA_SCALE);
158                                 g2.draw(line);
159                         }
160                 }
161                 
162
163
164
165
166                 // Base rocket line
167                 g2.setStroke(new BasicStroke((float) (3.0 * EXTRA_SCALE / scale),
168                                 BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
169                 g2.setColor(Color.GRAY);
170                 
171                 g2.drawLine((int) (x0 * EXTRA_SCALE), 0, (int) (x1 * EXTRA_SCALE), 0);
172                 
173
174                 // Fin shape
175                 Coordinate[] points = finset.getFinPoints();
176                 Path2D.Double shape = new Path2D.Double();
177                 shape.moveTo(0, 0);
178                 for (int i = 1; i < points.length; i++) {
179                         shape.lineTo(points[i].x * EXTRA_SCALE, points[i].y * EXTRA_SCALE);
180                 }
181                 
182                 g2.setStroke(new BasicStroke((float) (1.0 * EXTRA_SCALE / scale),
183                                 BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
184                 g2.setColor(Color.BLACK);
185                 g2.draw(shape);
186                 
187
188                 // Fin point boxes
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);
195                         g2.draw(handles[i]);
196                 }
197                 
198         }
199         
200         
201
202         public int getIndexByPoint(double x, double y) {
203                 if (handles == null)
204                         return -1;
205                 
206                 // Calculate point in shapes' coordinates
207                 Point2D.Double p = new Point2D.Double(x, y);
208                 try {
209                         transform.inverseTransform(p, p);
210                 } catch (NoninvertibleTransformException e) {
211                         return -1;
212                 }
213                 
214                 for (int i = 0; i < handles.length; i++) {
215                         if (handles[i].contains(p))
216                                 return i;
217                 }
218                 return -1;
219         }
220         
221         
222         public int getSegmentByPoint(double x, double y) {
223                 if (handles == null)
224                         return -1;
225                 
226                 // Calculate point in shapes' coordinates
227                 Point2D.Double p = new Point2D.Double(x, y);
228                 try {
229                         transform.inverseTransform(p, p);
230                 } catch (NoninvertibleTransformException e) {
231                         return -1;
232                 }
233                 
234                 double x0 = p.x / EXTRA_SCALE;
235                 double y0 = p.y / EXTRA_SCALE;
236                 double delta = BOX_SIZE / scale;
237                 
238                 //System.out.println("Point: " + x0 + "," + y0);
239                 //System.out.println("delta: " + (BOX_SIZE / scale));
240                 
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;
247                         
248                         //                      System.out.println("point1:"+x1+","+y1+" point2:"+x2+","+y2);
249                         
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);
253                         if (u < delta)
254                                 return i;
255                 }
256                 
257                 return -1;
258         }
259         
260         
261         public Point2D.Double convertPoint(double x, double y) {
262                 Point2D.Double p = new Point2D.Double(x, y);
263                 try {
264                         transform.inverseTransform(p, p);
265                 } catch (NoninvertibleTransformException e) {
266                         assert (false) : "Should not occur";
267                         return new Point2D.Double(0, 0);
268                 }
269                 
270                 p.setLocation(p.x / EXTRA_SCALE, p.y / EXTRA_SCALE);
271                 return p;
272         }
273         
274         
275
276         @Override
277         public Dimension getOrigin() {
278                 if (modID != finset.getRocket().getAerodynamicModID()) {
279                         modID = finset.getRocket().getAerodynamicModID();
280                         calculateDimensions();
281                 }
282                 return new Dimension((int) translateX, (int) translateY);
283         }
284         
285         @Override
286         public double getFigureWidth() {
287                 if (modID != finset.getRocket().getAerodynamicModID()) {
288                         modID = finset.getRocket().getAerodynamicModID();
289                         calculateDimensions();
290                 }
291                 return figureWidth;
292         }
293         
294         @Override
295         public double getFigureHeight() {
296                 if (modID != finset.getRocket().getAerodynamicModID()) {
297                         modID = finset.getRocket().getAerodynamicModID();
298                         calculateDimensions();
299                 }
300                 return figureHeight;
301         }
302         
303         
304         private void calculateDimensions() {
305                 minX = 0;
306                 maxX = 0;
307                 maxY = 0;
308                 
309                 for (Coordinate c : finset.getFinPoints()) {
310                         if (c.x < minX)
311                                 minX = c.x;
312                         if (c.x > maxX)
313                                 maxX = c.x;
314                         if (c.y > maxY)
315                                 maxY = c.y;
316                 }
317                 
318                 if (maxX < 0.01)
319                         maxX = 0.01;
320                 
321                 figureWidth = maxX - minX;
322                 figureHeight = maxY;
323                 
324
325                 Dimension d = new Dimension((int) (figureWidth * scale + 2 * borderPixelsWidth),
326                                 (int) (figureHeight * scale + 2 * borderPixelsHeight));
327                 
328                 if (!d.equals(getPreferredSize()) || !d.equals(getMinimumSize())) {
329                         setPreferredSize(d);
330                         setMinimumSize(d);
331                         revalidate();
332                 }
333         }
334         
335         
336
337         @Override
338         public void updateFigure() {
339                 repaint();
340         }
341         
342
343
344 }