1ddb5677ef9dc82d6f70dec1e9264bdb288b4441
[debian/openrocket] / core / src / net / sf / openrocket / file / rocksim / importt / FinSetHandler.java
1 /*
2  * FinSetHandler.java
3  */
4 package net.sf.openrocket.file.rocksim.importt;
5
6 import net.sf.openrocket.aerodynamics.WarningSet;
7 import net.sf.openrocket.file.simplesax.ElementHandler;
8 import net.sf.openrocket.file.simplesax.PlainTextHandler;
9 import net.sf.openrocket.material.Material;
10 import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
11 import net.sf.openrocket.rocketcomponent.ExternalComponent;
12 import net.sf.openrocket.rocketcomponent.FinSet;
13 import net.sf.openrocket.rocketcomponent.FreeformFinSet;
14 import net.sf.openrocket.rocketcomponent.IllegalFinPointException;
15 import net.sf.openrocket.rocketcomponent.RocketComponent;
16 import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
17 import net.sf.openrocket.util.Coordinate;
18 import org.xml.sax.SAXException;
19
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.List;
24
25 /**
26  * A SAX handler for Rocksim fin sets.  Because the type of fin may not be known first (in Rocksim file format, the fin
27  * shape type is in the middle of the XML structure), and because we're using SAX not DOM, all of the fin
28  * characteristics are kept here until the closing FinSet tag. At that point, <code>asOpenRocket</code> method is called
29  * to construct the corresponding OpenRocket FinSet.
30  */
31 class FinSetHandler extends ElementHandler {
32     /**
33      * The parent component.
34      */
35     private final RocketComponent component;
36
37     /**
38      * The name of the fin.
39      */
40     private String name;
41     /**
42      * The Rocksim fin shape code.
43      */
44     private int shapeCode;
45     /**
46      * The location of the fin on its parent.
47      */
48     private double location = 0.0d;
49     /**
50      * The OpenRocket Position which gives the absolute/relative positioning for location.
51      */
52     private RocketComponent.Position position;
53     /**
54      * The number of fins in this fin set.
55      */
56     private int finCount;
57     /**
58      * The length of the root chord.
59      */
60     private double rootChord = 0.0d;
61     /**
62      * The length of the tip chord.
63      */
64     private double tipChord = 0.0d;
65     /**
66      * The length of the mid-chord (aka height).
67      */
68     private double midChordLen = 0.0d;
69     /**
70      * The distance of the leading edge from root to top.
71      */
72     private double sweepDistance = 0.0d;
73     /**
74      * The angle the fins have been rotated from the y-axis, if looking down the tube, in radians.
75      */
76     private double radialAngle = 0.0d;
77     /**
78      * The thickness of the fins.
79      */
80     private double thickness;
81     /**
82      * The finish of the fins.
83      */
84     private ExternalComponent.Finish finish;
85     /**
86      * The shape of the tip.
87      */
88     private int tipShapeCode;
89     /**
90      * The length of the TTW tab.
91      */
92     private double tabLength = 0.0d;
93     /**
94      * The depth of the TTW tab.
95      */
96     private double tabDepth = 0.0d;
97     /**
98      * The offset of the tab, from the front of the fin.
99      */
100     private double taboffset = 0.0d;
101     /**
102      * The elliptical semi-span (height).
103      */
104     private double semiSpan;
105     /**
106      * The list of custom points.
107      */
108     private String pointList;
109     /**
110      * Override the Cg and mass.
111      */
112     private boolean override = false;
113     /**
114      * The overridden mass.
115      */
116     private Double mass = 0d;
117     /**
118      * The overridden Cg.
119      */
120     private Double cg = 0d;
121     /**
122      * The density of the material in the component.
123      */
124     private Double density = 0d;
125     /**
126      * The material name.
127      */
128     private String materialName = "";
129     /**
130      * The Rocksim calculated mass.
131      */
132     private Double calcMass = 0d;
133     /**
134      * The Rocksim calculated cg.
135      */
136     private Double calcCg = 0d;
137
138
139     /**
140      * Constructor.
141      *
142      * @param c the parent
143      *
144      * @throws IllegalArgumentException thrown if <code>c</code> is null
145      */
146     public FinSetHandler (RocketComponent c) throws IllegalArgumentException {
147         if (c == null) {
148             throw new IllegalArgumentException("The parent component of a fin set may not be null.");
149         }
150         component = c;
151     }
152
153     @Override
154     public ElementHandler openElement (String element, HashMap<String, String> attributes, WarningSet warnings) {
155         return PlainTextHandler.INSTANCE;
156     }
157
158     @Override
159     public void closeElement (String element, HashMap<String, String> attributes, String content, WarningSet warnings)
160             throws SAXException {
161         try {
162             if ("Name".equals(element)) {
163                 name = content;
164             }
165             if ("Material".equals(element)) {
166                 materialName = content;
167             }
168             if ("FinishCode".equals(element)) {
169                 finish = RocksimFinishCode.fromCode(Integer.parseInt(content)).asOpenRocket();
170             }
171             if ("Xb".equals(element)) {
172                 location = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
173             }
174             if ("LocationMode".equals(element)) {
175                 position = RocksimLocationMode.fromCode(Integer.parseInt(content)).asOpenRocket();
176             }
177             if ("FinCount".equals(element)) {
178                 finCount = Integer.parseInt(content);
179             }
180             if ("RootChord".equals(element)) {
181                 rootChord = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
182             }
183             if ("TipChord".equals(element)) {
184                 tipChord = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
185             }
186             if ("SemiSpan".equals(element)) {
187                 semiSpan = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
188             }
189             if ("MidChordLen".equals(element)) {
190                 midChordLen = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
191             }
192             if ("SweepDistance".equals(element)) {
193                 sweepDistance = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
194             }
195             if ("Thickness".equals(element)) {
196                 thickness = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
197             }
198             if ("TipShapeCode".equals(element)) {
199                 tipShapeCode = Integer.parseInt(content);
200             }
201             if ("TabLength".equals(element)) {
202                 tabLength = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
203             }
204             if ("TabDepth".equals(element)) {
205                 tabDepth = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
206             }
207             if ("TabOffset".equals(element)) {
208                 taboffset = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
209             }
210             if ("RadialAngle".equals(element)) {
211                 radialAngle = Double.parseDouble(content);
212             }
213             if ("ShapeCode".equals(element)) {
214                 shapeCode = Integer.parseInt(content);
215             }
216             if ("PointList".equals(element)) {
217                 pointList = content;
218             }
219             if ("KnownMass".equals(element)) {
220                 mass = Math.max(0d, Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_MASS);
221             }
222             if ("Density".equals(element)) {
223                 density = Math.max(0d, Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_BULK_DENSITY);
224             }
225             if ("KnownCG".equals(element)) {
226                 cg = Math.max(0d, Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_MASS);
227             }
228             if ("UseKnownCG".equals(element)) {
229                 override = "1".equals(content);
230             }
231             if ("CalcMass".equals(element)) {
232                 calcMass = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_MASS;
233             }
234             if ("CalcCg".equals(element)) {
235                 calcCg = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH;
236             }
237         }
238         catch (NumberFormatException nfe) {
239             warnings.add("Could not convert " + element + " value of " + content + ".  It is expected to be a number.");
240         }
241     }
242
243     @Override
244     public void endHandler (String element, HashMap<String, String> attributes,
245             String content, WarningSet warnings) throws SAXException {
246         //Create the fin set and correct for overrides and actual material densities
247         final FinSet finSet = asOpenRocket(warnings);
248         if (component.isCompatible(finSet)) {
249             BaseHandler.setOverride(finSet, override, mass, cg);
250             if (!override && finSet.getCrossSection().equals(FinSet.CrossSection.AIRFOIL)) {
251                 //Override mass anyway.  This is done only for AIRFOIL because Rocksim does not compute different
252                 //mass/cg for different cross sections, but OpenRocket does.  This can lead to drastic differences
253                 //in mass.  To counteract that, the cross section value is retained but the mass/cg is overridden
254                 //with the calculated values from Rocksim.  This will best approximate the Rocksim design in OpenRocket.
255                 BaseHandler.setOverride(finSet, true, calcMass, calcCg);
256             }
257             BaseHandler.updateComponentMaterial(finSet, materialName, Material.Type.BULK, density);
258             component.addChild(finSet);
259         }
260         else {
261             warnings.add(finSet.getComponentName() + " can not be attached to "
262                          + component.getComponentName() + ", ignoring component.");
263         }
264     }
265
266
267     /**
268      * Convert the parsed Rocksim data values in this object to an instance of OpenRocket's FinSet.
269      *
270      * @param warnings the warning set to convey incompatibilities to the user
271      *
272      * @return a FinSet instance
273      */
274     public FinSet asOpenRocket (WarningSet warnings) {
275         FinSet result;
276
277         if (shapeCode == 0) {
278             //Trapezoidal
279             result = new TrapezoidFinSet();
280             ((TrapezoidFinSet) result).setFinShape(rootChord, tipChord, sweepDistance, semiSpan, thickness);
281         }
282         else if (shapeCode == 1) {
283             //Elliptical
284             result = new EllipticalFinSet();
285             ((EllipticalFinSet) result).setHeight(semiSpan);
286             ((EllipticalFinSet) result).setLength(rootChord);
287         }
288         else if (shapeCode == 2) {
289
290             result = new FreeformFinSet();
291             try {
292                 ((FreeformFinSet) result).setPoints(toCoordinates(pointList, warnings));
293             }
294             catch (IllegalFinPointException e) {
295                 warnings.add("Illegal fin point set. " + e.getMessage() + " Ignoring.");
296             }
297         }
298         else {
299             return null;
300         }
301         result.setThickness(thickness);
302         result.setName(name);
303         result.setFinCount(finCount);
304         result.setFinish(finish);
305         //All TTW tabs in Rocksim are relative to the front of the fin.
306         result.setTabRelativePosition(FinSet.TabRelativePosition.FRONT);
307         result.setTabHeight(tabDepth);
308         result.setTabLength(tabLength);
309         result.setTabShift(taboffset);
310         result.setBaseRotation(radialAngle);
311         result.setCrossSection(convertTipShapeCode(tipShapeCode));
312         result.setRelativePosition(position);
313         PositionDependentHandler.setLocation(result, position, location);
314         return result;
315
316     }
317
318     /**
319      * Convert a Rocksim string that represents fin plan points into an array of OpenRocket coordinates.
320      *
321      * @param pointList a comma and pipe delimited string of X,Y coordinates from Rocksim.  This is of the format:
322      *                  <pre>x0,y0|x1,y1|x2,y2|... </pre>
323      * @param warnings  the warning set to convey incompatibilities to the user
324      *
325      * @return an array of OpenRocket Coordinates
326      */
327     private Coordinate[] toCoordinates (String pointList, WarningSet warnings) {
328         List<Coordinate> result = new ArrayList<Coordinate>();
329         if (pointList != null && !pointList.isEmpty()) {
330             String[] points = pointList.split("\\Q|\\E");
331             for (String point : points) {
332                 String[] aPoint = point.split(",");
333                 try {
334                     if (aPoint.length > 1) {
335                         Coordinate c = new Coordinate(
336                                 Double.parseDouble(aPoint[0]) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH,
337                                 Double.parseDouble(aPoint[1]) / RocksimHandler.ROCKSIM_TO_OPENROCKET_LENGTH);
338                         result.add(c);
339                     }
340                     else {
341                         warnings.add("Invalid fin point pair.");
342                     }
343                 }
344                 catch (NumberFormatException nfe) {
345                     warnings.add("Fin point not in numeric format.");
346                 }
347             }
348             if (!result.isEmpty()) {
349                 //OpenRocket requires fin plan points be ordered from leading root chord to trailing root chord in the
350                 //Coordinate array.
351                 Coordinate last = result.get(result.size() - 1);
352                 if (last.x == 0 && last.y == 0) {
353                     Collections.reverse(result);
354                 }
355             }
356         }
357         final Coordinate[] coords = new Coordinate[result.size()];
358         return result.toArray(coords);
359     }
360
361
362     /**
363      * Convert a Rocksim tip shape to an OpenRocket CrossSection.
364      *
365      * @param tipShape the tip shape code from Rocksim
366      *
367      * @return a CrossSection instance
368      */
369     public static FinSet.CrossSection convertTipShapeCode (int tipShape) {
370         switch (tipShape) {
371             case 0:
372                 return FinSet.CrossSection.SQUARE;
373             case 1:
374                 return FinSet.CrossSection.ROUNDED;
375             case 2:
376                 return FinSet.CrossSection.AIRFOIL;
377             default:
378                 return FinSet.CrossSection.SQUARE;
379         }
380     }
381     
382     public static int convertTipShapeCode (FinSet.CrossSection cs) {
383         if (FinSet.CrossSection.ROUNDED.equals(cs)) {
384             return 1;
385         }
386         if (FinSet.CrossSection.AIRFOIL.equals(cs)) {
387             return 2;
388         }
389         return 0;
390     }
391
392 }
393