create changelog entry
[debian/openrocket] / core / src / net / sf / openrocket / rocketcomponent / FreeformFinSet.java
1 package net.sf.openrocket.rocketcomponent;
2
3 import java.util.Arrays;
4 import java.util.Collections;
5 import java.util.List;
6
7 import net.sf.openrocket.l10n.Translator;
8 import net.sf.openrocket.logging.LogHelper;
9 import net.sf.openrocket.startup.Application;
10 import net.sf.openrocket.util.ArrayList;
11 import net.sf.openrocket.util.BugException;
12 import net.sf.openrocket.util.Coordinate;
13
14
15 public class FreeformFinSet extends FinSet {
16         private static final LogHelper log = Application.getLogger();
17         private static final Translator trans = Application.getTranslator();
18         
19         private ArrayList<Coordinate> points = new ArrayList<Coordinate>();
20         
21         public FreeformFinSet() {
22                 points.add(Coordinate.NUL);
23                 points.add(new Coordinate(0.025, 0.05));
24                 points.add(new Coordinate(0.075, 0.05));
25                 points.add(new Coordinate(0.05, 0));
26                 
27                 this.length = 0.05;
28         }
29         
30         
31         public FreeformFinSet(Coordinate[] finpoints) throws IllegalFinPointException {
32                 setPoints(finpoints);
33         }
34         
35         /*
36         public FreeformFinSet(FinSet finset) {
37                 Coordinate[] finpoints = finset.getFinPoints();
38                 this.copyFrom(finset);
39
40                 points.clear();
41                 for (Coordinate c: finpoints) {
42                         points.add(c);
43                 }
44                 this.length = points.get(points.size()-1).x - points.get(0).x;
45         }
46         */
47         
48         
49         /**
50          * Convert an existing fin set into a freeform fin set.  The specified
51          * fin set is taken out of the rocket tree (if any) and the new component
52          * inserted in its stead.
53          * <p>
54          * The specified fin set should not be used after the call!
55          *
56          * @param finset        the fin set to convert.
57          * @return                      the new freeform fin set.
58          */
59         public static FreeformFinSet convertFinSet(FinSet finset) {
60                 log.info("Converting " + finset.getComponentName() + " into freeform fin set");
61                 final RocketComponent root = finset.getRoot();
62                 FreeformFinSet freeform;
63                 List<RocketComponent> toInvalidate = Collections.emptyList();
64                 
65                 try {
66                         if (root instanceof Rocket) {
67                                 ((Rocket) root).freeze();
68                         }
69                         
70                         // Get fin set position and remove fin set
71                         final RocketComponent parent = finset.getParent();
72                         final int position;
73                         if (parent != null) {
74                                 position = parent.getChildPosition(finset);
75                                 parent.removeChild(position);
76                         } else {
77                                 position = -1;
78                         }
79                         
80                         
81                         // Create the freeform fin set
82                         Coordinate[] finpoints = finset.getFinPoints();
83                         try {
84                                 freeform = new FreeformFinSet(finpoints);
85                         } catch (IllegalFinPointException e) {
86                                 throw new BugException("Illegal fin points when converting existing fin to " +
87                                                 "freeform fin, fin=" + finset + " points=" + Arrays.toString(finpoints),
88                                                 e);
89                         }
90                         
91                         // Copy component attributes
92                         toInvalidate = freeform.copyFrom(finset);
93                         
94                         // Set name
95                         final String componentTypeName = finset.getComponentName();
96                         final String name = freeform.getName();
97                         
98                         if (name.startsWith(componentTypeName)) {
99                                 freeform.setName(freeform.getComponentName() +
100                                                 name.substring(componentTypeName.length()));
101                         }
102                         
103                         // Add freeform fin set to parent
104                         if (parent != null) {
105                                 parent.addChild(freeform, position);
106                         }
107                         
108                 } finally {
109                         if (root instanceof Rocket) {
110                                 ((Rocket) root).thaw();
111                         }
112                         // Invalidate components after events have been fired
113                         for (RocketComponent c : toInvalidate) {
114                                 c.invalidate();
115                         }
116                 }
117                 return freeform;
118         }
119         
120         
121         
122         /**
123          * Add a fin point between indices <code>index-1</code> and <code>index</code>.
124          * The point is placed at the midpoint of the current segment.
125          *
126          * @param index   the fin point before which to add the new point.
127          */
128         public void addPoint(int index) {
129                 double x0, y0, x1, y1;
130                 
131                 x0 = points.get(index - 1).x;
132                 y0 = points.get(index - 1).y;
133                 x1 = points.get(index).x;
134                 y1 = points.get(index).y;
135                 
136                 points.add(index, new Coordinate((x0 + x1) / 2, (y0 + y1) / 2));
137                 // adding a point within the segment affects neither mass nor aerodynamics
138                 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
139         }
140         
141         
142         /**
143          * Remove the fin point with the given index.  The first and last fin points
144          * cannot be removed, and will cause an <code>IllegalFinPointException</code>
145          * if attempted.
146          *
147          * @param index   the fin point index to remove
148          * @throws IllegalFinPointException if removing would result in invalid fin planform
149          */
150         public void removePoint(int index) throws IllegalFinPointException {
151                 if (index == 0 || index == points.size() - 1) {
152                         throw new IllegalFinPointException("cannot remove first or last point");
153                 }
154                 
155                 ArrayList<Coordinate> copy = this.points.clone();
156                 copy.remove(index);
157                 validate(copy);
158                 this.points = copy;
159                 
160                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
161         }
162         
163         
164         public int getPointCount() {
165                 return points.size();
166         }
167         
168         public void setPoints(Coordinate[] points) throws IllegalFinPointException {
169                 setPoints(Arrays.asList(points));
170         }
171         
172         public void setPoints(List<Coordinate> points) throws IllegalFinPointException {
173                 ArrayList<Coordinate> list = new ArrayList<Coordinate>(points);
174                 validate(list);
175                 this.points = list;
176                 
177                 this.length = points.get(points.size() - 1).x;
178                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
179         }
180         
181         
182         /**
183          * Set the point at position <code>i</code> to coordinates (x,y).
184          * <p>
185          * Note that this method enforces basic fin shape restrictions (non-negative y,
186          * first and last point locations) silently, but throws an
187          * <code>IllegalFinPointException</code> if the point causes fin segments to
188          * intersect.
189          * <p>
190          * Moving of the first point in the X-axis is allowed, but this actually moves
191          * all of the other points the corresponding distance back.
192          *
193          * @param index the point index to modify.
194          * @param x             the x-coordinate.
195          * @param y             the y-coordinate.
196          * @throws IllegalFinPointException     if the specified fin point would cause intersecting
197          *                                                                      segments
198          */
199         public void setPoint(int index, double x, double y) throws IllegalFinPointException {
200                 if (y < 0)
201                         y = 0;
202                 
203                 double x0, y0, x1, y1;
204                 
205                 if (index == 0) {
206                         
207                         // Restrict point
208                         x = Math.min(x, points.get(points.size() - 1).x);
209                         y = 0;
210                         x0 = Double.NaN;
211                         y0 = Double.NaN;
212                         x1 = points.get(1).x;
213                         y1 = points.get(1).y;
214                         
215                 } else if (index == points.size() - 1) {
216                         
217                         // Restrict point
218                         x = Math.max(x, 0);
219                         y = 0;
220                         x0 = points.get(index - 1).x;
221                         y0 = points.get(index - 1).y;
222                         x1 = Double.NaN;
223                         y1 = Double.NaN;
224                         
225                 } else {
226                         
227                         x0 = points.get(index - 1).x;
228                         y0 = points.get(index - 1).y;
229                         x1 = points.get(index + 1).x;
230                         y1 = points.get(index + 1).y;
231                         
232                 }
233                 
234                 
235                 
236                 // Check for intersecting
237                 double px0, py0, px1, py1;
238                 px0 = 0;
239                 py0 = 0;
240                 for (int i = 1; i < points.size(); i++) {
241                         px1 = points.get(i).x;
242                         py1 = points.get(i).y;
243                         
244                         if (i != index - 1 && i != index && i != index + 1) {
245                                 if (intersects(x0, y0, x, y, px0, py0, px1, py1)) {
246                                         throw new IllegalFinPointException("segments intersect");
247                                 }
248                         }
249                         if (i != index && i != index + 1 && i != index + 2) {
250                                 if (intersects(x, y, x1, y1, px0, py0, px1, py1)) {
251                                         throw new IllegalFinPointException("segments intersect");
252                                 }
253                         }
254                         
255                         px0 = px1;
256                         py0 = py1;
257                 }
258                 
259                 if (index == 0) {
260                         
261                         //System.out.println("Set point zero to x:" + x);
262                         for (int i = 1; i < points.size(); i++) {
263                                 Coordinate c = points.get(i);
264                                 points.set(i, c.setX(c.x - x));
265                         }
266                         
267                 } else {
268                         
269                         points.set(index, new Coordinate(x, y));
270                         
271                 }
272                 if (index == 0 || index == points.size() - 1) {
273                         this.length = points.get(points.size() - 1).x;
274                 }
275                 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
276         }
277         
278         
279         
280         private boolean intersects(double ax0, double ay0, double ax1, double ay1,
281                         double bx0, double by0, double bx1, double by1) {
282                 
283                 double d = ((by1 - by0) * (ax1 - ax0) - (bx1 - bx0) * (ay1 - ay0));
284                 
285                 double ua = ((bx1 - bx0) * (ay0 - by0) - (by1 - by0) * (ax0 - bx0)) / d;
286                 double ub = ((ax1 - ax0) * (ay0 - by0) - (ay1 - ay0) * (ax0 - bx0)) / d;
287                 
288                 return (ua >= 0) && (ua <= 1) && (ub >= 0) && (ub <= 1);
289         }
290         
291         
292         @Override
293         public Coordinate[] getFinPoints() {
294                 return points.toArray(new Coordinate[0]);
295         }
296         
297         @Override
298         public double getSpan() {
299                 double max = 0;
300                 for (Coordinate c : points) {
301                         if (c.y > max)
302                                 max = c.y;
303                 }
304                 return max;
305         }
306         
307         @Override
308         public String getComponentName() {
309                 //// Freeform fin set
310                 return trans.get("FreeformFinSet.FreeformFinSet");
311         }
312         
313         
314         @Override
315         protected RocketComponent copyWithOriginalID() {
316                 RocketComponent c = super.copyWithOriginalID();
317                 ((FreeformFinSet) c).points = this.points.clone();
318                 return c;
319         }
320         
321         private void validate(ArrayList<Coordinate> pts) throws IllegalFinPointException {
322                 final int n = pts.size();
323                 if (pts.get(0).x != 0 || pts.get(0).y != 0 ||
324                                 pts.get(n - 1).x < 0 || pts.get(n - 1).y != 0) {
325                         throw new IllegalFinPointException("Start or end point illegal.");
326                 }
327                 for (int i = 0; i < n - 1; i++) {
328                         for (int j = i + 2; j < n - 1; j++) {
329                                 if (intersects(pts.get(i).x, pts.get(i).y, pts.get(i + 1).x, pts.get(i + 1).y,
330                                                 pts.get(j).x, pts.get(j).y, pts.get(j + 1).x, pts.get(j + 1).y)) {
331                                         throw new IllegalFinPointException("segments intersect");
332                                 }
333                         }
334                         if (pts.get(i).z != 0) {
335                                 throw new IllegalFinPointException("z-coordinate not zero");
336                         }
337                 }
338         }
339         
340 }