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