]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/save/PovExporter.java
Version 7, February 2009
[GpsPrune.git] / tim / prune / save / PovExporter.java
1 package tim.prune.save;
2
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.FlowLayout;
6 import java.awt.GridLayout;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.io.File;
10 import java.io.FileWriter;
11 import java.io.IOException;
12 import java.util.ArrayList;
13 import java.util.Iterator;
14
15 import javax.swing.BoxLayout;
16 import javax.swing.ButtonGroup;
17 import javax.swing.JButton;
18 import javax.swing.JDialog;
19 import javax.swing.JFileChooser;
20 import javax.swing.JLabel;
21 import javax.swing.JOptionPane;
22 import javax.swing.JPanel;
23 import javax.swing.JRadioButton;
24 import javax.swing.JTextField;
25 import javax.swing.SwingConstants;
26
27 import tim.prune.App;
28 import tim.prune.Config;
29 import tim.prune.GenericFunction;
30 import tim.prune.I18nManager;
31 import tim.prune.UpdateMessageBroker;
32 import tim.prune.data.Track;
33 import tim.prune.load.GenericFileFilter;
34 import tim.prune.threedee.LineDialog;
35 import tim.prune.threedee.ThreeDModel;
36
37 /**
38  * Class to export track information
39  * into a specified Pov file
40  */
41 public class PovExporter extends GenericFunction
42 {
43         private Track _track = null;
44         private JDialog _dialog = null;
45         private JFileChooser _fileChooser = null;
46         private String _cameraX = null, _cameraY = null, _cameraZ = null;
47         private JTextField _cameraXField = null, _cameraYField = null, _cameraZField = null;
48         private JTextField _fontName = null, _altitudeCapField = null;
49         private int _altitudeCap = ThreeDModel.MINIMUM_ALTITUDE_CAP;
50         private JRadioButton _ballsAndSticksButton = null;
51
52         // defaults
53         private static final double DEFAULT_CAMERA_DISTANCE = 30.0;
54         private static final String DEFAULT_FONT_FILE = "crystal.ttf";
55
56
57         /**
58          * Constructor giving frame and track
59          * @param inApp App object
60          */
61         public PovExporter(App inApp)
62         {
63                 super(inApp);
64                 _track = inApp.getTrackInfo().getTrack();
65                 // Set default camera coordinates
66                 _cameraX = "17"; _cameraY = "13"; _cameraZ = "-20";
67         }
68
69         /** Get the name key */
70         public String getNameKey() {
71                 return "function.exportpov";
72         }
73
74         /**
75          * Set the coordinates for the camera (can be any scale)
76          * @param inX X coordinate of camera
77          * @param inY Y coordinate of camera
78          * @param inZ Z coordinate of camera
79          */
80         public void setCameraCoordinates(double inX, double inY, double inZ)
81         {
82                 // calculate distance from origin
83                 double cameraDist = Math.sqrt(inX*inX + inY*inY + inZ*inZ);
84                 if (cameraDist > 0.0)
85                 {
86                         _cameraX = "" + (inX / cameraDist * DEFAULT_CAMERA_DISTANCE);
87                         _cameraY = "" + (inY / cameraDist * DEFAULT_CAMERA_DISTANCE);
88                         // Careful! Need to convert from java3d (right-handed) to povray (left-handed) coordinate system!
89                         _cameraZ = "" + (-inZ / cameraDist * DEFAULT_CAMERA_DISTANCE);
90                 }
91         }
92
93
94         /**
95          * @param inAltitudeCap altitude cap to use
96          */
97         public void setAltitudeCap(int inAltitudeCap)
98         {
99                 _altitudeCap = inAltitudeCap;
100                 if (_altitudeCap < ThreeDModel.MINIMUM_ALTITUDE_CAP)
101                 {
102                         _altitudeCap = ThreeDModel.MINIMUM_ALTITUDE_CAP;
103                 }
104         }
105
106
107         /**
108          * Show the dialog to select options and export file
109          */
110         public void begin()
111         {
112                 // Make dialog window to select angles, colours etc
113                 if (_dialog == null)
114                 {
115                         _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
116                         _dialog.setLocationRelativeTo(_parentFrame);
117                         _dialog.getContentPane().add(makeDialogComponents());
118                 }
119
120                 // Set angles
121                 _cameraXField.setText(_cameraX);
122                 _cameraYField.setText(_cameraY);
123                 _cameraZField.setText(_cameraZ);
124                 // Set vertical scale
125                 _altitudeCapField.setText("" + _altitudeCap);
126                 // Show dialog
127                 _dialog.pack();
128                 _dialog.setVisible(true);
129         }
130
131
132         /**
133          * Make the dialog components to select the export options
134          * @return Component holding gui elements
135          */
136         private Component makeDialogComponents()
137         {
138                 JPanel panel = new JPanel();
139                 panel.setLayout(new BorderLayout());
140                 panel.add(new JLabel(I18nManager.getText("dialog.exportpov.text")), BorderLayout.NORTH);
141                 // OK, Cancel buttons
142                 JPanel buttonPanel = new JPanel();
143                 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
144                 JButton okButton = new JButton(I18nManager.getText("button.ok"));
145                 okButton.addActionListener(new ActionListener() {
146                         public void actionPerformed(ActionEvent e)
147                         {
148                                 doExport();
149                                 _dialog.dispose();
150                         }
151                 });
152                 buttonPanel.add(okButton);
153                 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
154                 cancelButton.addActionListener(new ActionListener() {
155                         public void actionPerformed(ActionEvent e)
156                         {
157                                 _dialog.dispose();
158                         }
159                 });
160                 buttonPanel.add(cancelButton);
161                 panel.add(buttonPanel, BorderLayout.SOUTH);
162
163                 // central panel
164                 JPanel centralPanel = new JPanel();
165                 centralPanel.setLayout(new GridLayout(0, 2, 10, 4));
166
167                 JLabel fontLabel = new JLabel(I18nManager.getText("dialog.exportpov.font"));
168                 fontLabel.setHorizontalAlignment(SwingConstants.TRAILING);
169                 centralPanel.add(fontLabel);
170                 String defaultFont = Config.getPovrayFont();
171                 if (defaultFont == null || defaultFont.equals("")) {
172                         defaultFont = DEFAULT_FONT_FILE;
173                 }
174                 _fontName = new JTextField(defaultFont, 12);
175                 _fontName.setAlignmentX(Component.LEFT_ALIGNMENT);
176                 centralPanel.add(_fontName);
177                 //coordinates of camera
178                 JLabel cameraXLabel = new JLabel(I18nManager.getText("dialog.exportpov.camerax"));
179                 cameraXLabel.setHorizontalAlignment(SwingConstants.TRAILING);
180                 centralPanel.add(cameraXLabel);
181                 _cameraXField = new JTextField("" + _cameraX);
182                 centralPanel.add(_cameraXField);
183                 JLabel cameraYLabel = new JLabel(I18nManager.getText("dialog.exportpov.cameray"));
184                 cameraYLabel.setHorizontalAlignment(SwingConstants.TRAILING);
185                 centralPanel.add(cameraYLabel);
186                 _cameraYField = new JTextField("" + _cameraY);
187                 centralPanel.add(_cameraYField);
188                 JLabel cameraZLabel = new JLabel(I18nManager.getText("dialog.exportpov.cameraz"));
189                 cameraZLabel.setHorizontalAlignment(SwingConstants.TRAILING);
190                 centralPanel.add(cameraZLabel);
191                 _cameraZField = new JTextField("" + _cameraZ);
192                 centralPanel.add(_cameraZField);
193                 // Altitude capping
194                 JLabel altitudeCapLabel = new JLabel(I18nManager.getText("dialog.3d.altitudecap"));
195                 altitudeCapLabel.setHorizontalAlignment(SwingConstants.TRAILING);
196                 centralPanel.add(altitudeCapLabel);
197                 _altitudeCapField = new JTextField("" + _altitudeCap);
198                 centralPanel.add(_altitudeCapField);
199
200                 // Radio buttons for style - balls on sticks or tubes
201                 JPanel stylePanel = new JPanel();
202                 stylePanel.setLayout(new GridLayout(0, 2, 10, 4));
203                 JLabel styleLabel = new JLabel(I18nManager.getText("dialog.exportpov.modelstyle"));
204                 styleLabel.setHorizontalAlignment(SwingConstants.TRAILING);
205                 stylePanel.add(styleLabel);
206                 JPanel radioPanel = new JPanel();
207                 radioPanel.setLayout(new BoxLayout(radioPanel, BoxLayout.Y_AXIS));
208                 _ballsAndSticksButton = new JRadioButton(I18nManager.getText("dialog.exportpov.ballsandsticks"));
209                 _ballsAndSticksButton.setSelected(false);
210                 radioPanel.add(_ballsAndSticksButton);
211                 JRadioButton tubesButton = new JRadioButton(I18nManager.getText("dialog.exportpov.tubesandwalls"));
212                 tubesButton.setSelected(true);
213                 radioPanel.add(tubesButton);
214                 ButtonGroup group = new ButtonGroup();
215                 group.add(_ballsAndSticksButton); group.add(tubesButton);
216                 stylePanel.add(radioPanel);
217
218                 // add this grid to the holder panel
219                 JPanel holderPanel = new JPanel();
220                 holderPanel.setLayout(new BorderLayout(5, 5));
221                 JPanel boxPanel = new JPanel();
222                 boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS));
223                 boxPanel.add(centralPanel);
224                 boxPanel.add(stylePanel);
225                 holderPanel.add(boxPanel, BorderLayout.CENTER);
226
227                 // show lines button
228                 JButton showLinesButton = new JButton(I18nManager.getText("button.showlines"));
229                 showLinesButton.addActionListener(new ActionListener() {
230                         public void actionPerformed(ActionEvent e)
231                         {
232                                 // Need to scale model to find lines
233                                 ThreeDModel model = new ThreeDModel(_track);
234                                 model.scale();
235                                 double[] latLines = model.getLatitudeLines();
236                                 double[] lonLines = model.getLongitudeLines();
237                                 LineDialog dialog = new LineDialog(_parentFrame, latLines, lonLines);
238                                 dialog.showDialog();
239                         }
240                 });
241                 JPanel flowPanel = new JPanel();
242                 flowPanel.setLayout(new FlowLayout());
243                 flowPanel.add(showLinesButton);
244                 holderPanel.add(flowPanel, BorderLayout.EAST);
245                 panel.add(holderPanel, BorderLayout.CENTER);
246                 return panel;
247         }
248
249
250         /**
251          * Select the file and export data to it
252          */
253         private void doExport()
254         {
255                 // Copy camera coordinates
256                 _cameraX = checkCoordinate(_cameraXField.getText());
257                 _cameraY = checkCoordinate(_cameraYField.getText());
258                 _cameraZ = checkCoordinate(_cameraZField.getText());
259
260                 // OK pressed, so choose output file
261                 if (_fileChooser == null)
262                 {
263                         _fileChooser = new JFileChooser();
264                         _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
265                         _fileChooser.setFileFilter(new GenericFileFilter("filetype.pov", new String[] {"pov"}));
266                         _fileChooser.setAcceptAllFileFilterUsed(false);
267                         // start from directory in config which should be set
268                         File configDir = Config.getWorkingDirectory();
269                         if (configDir != null) {_fileChooser.setCurrentDirectory(configDir);}
270                 }
271
272                 // Allow choose again if an existing file is selected
273                 boolean chooseAgain = false;
274                 do
275                 {
276                         chooseAgain = false;
277                         if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
278                         {
279                                 // OK pressed and file chosen
280                                 File file = _fileChooser.getSelectedFile();
281                                 if (!file.getName().toLowerCase().endsWith(".pov"))
282                                 {
283                                         file = new File(file.getAbsolutePath() + ".pov");
284                                 }
285                                 // Check if file exists and if necessary prompt for overwrite
286                                 Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
287                                 if (!file.exists() || JOptionPane.showOptionDialog(_parentFrame,
288                                                 I18nManager.getText("dialog.save.overwrite.text"),
289                                                 I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
290                                                 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
291                                         == JOptionPane.YES_OPTION)
292                                 {
293                                         // Export the file
294                                         if (exportFile(file))
295                                         {
296                                                 // file saved
297                                                 // Store directory in config for later
298                                                 Config.setWorkingDirectory(file.getParentFile());
299                                         }
300                                         else
301                                         {
302                                                 // export failed so need to choose again
303                                                 chooseAgain = true;
304                                         }
305                                 }
306                                 else
307                                 {
308                                         // overwrite cancelled so need to choose again
309                                         chooseAgain = true;
310                                 }
311                         }
312                 } while (chooseAgain);
313         }
314
315
316         /**
317          * Export the track data to the specified file
318          * @param inFile File object to save to
319          * @return true if successful
320          */
321         private boolean exportFile(File inFile)
322         {
323                 FileWriter writer = null;
324                 // find out the line separator for this system
325                 String lineSeparator = System.getProperty("line.separator");
326                 try
327                 {
328                         // create and scale model
329                         ThreeDModel model = new ThreeDModel(_track);
330                         try
331                         {
332                                 // try to use given altitude cap
333                                 _altitudeCap = Integer.parseInt(_altitudeCapField.getText());
334                                 model.setAltitudeCap(_altitudeCap);
335                         }
336                         catch (NumberFormatException nfe) {}
337                         model.scale();
338
339                         // Create file and write basics
340                         writer = new FileWriter(inFile);
341                         writeStartOfFile(writer, model.getModelSize(), lineSeparator);
342
343                         // write out lat/long lines using model
344                         writeLatLongLines(writer, model, lineSeparator);
345
346                         // write out points
347                         if (_ballsAndSticksButton.isSelected()) {
348                                 writeDataPointsBallsAndSticks(writer, model, lineSeparator);
349                         }
350                         else {
351                                 writeDataPointsTubesAndWalls(writer, model, lineSeparator);
352                         }
353
354                         // everything worked
355                         UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
356                                  + " " + _track.getNumPoints() + " " + I18nManager.getText("confirm.save.ok2")
357                                  + " " + inFile.getAbsolutePath());
358                         return true;
359                 }
360                 catch (IOException ioe)
361                 {
362                         JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.save.failed") + ioe.getMessage(),
363                                 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
364                 }
365                 finally
366                 {
367                         // close file ignoring exceptions
368                         try
369                         {
370                                 writer.close();
371                         }
372                         catch (Exception e) {}
373                 }
374                 return false;
375         }
376
377
378         /**
379          * Write the start of the Pov file, including base plane and lights
380          * @param inWriter Writer to use for writing file
381          * @param inModelSize model size
382          * @param inLineSeparator line separator to use
383          * @throws IOException on file writing error
384          */
385         private void writeStartOfFile(FileWriter inWriter, double inModelSize, String inLineSeparator)
386         throws IOException
387         {
388                 inWriter.write("// Pov file produced by Prune - see http://activityworkshop.net/");
389                 inWriter.write(inLineSeparator);
390                 inWriter.write(inLineSeparator);
391                 // Select font based on user input
392                 String fontPath = _fontName.getText();
393                 if (fontPath == null || fontPath.equals(""))
394                 {
395                         fontPath = DEFAULT_FONT_FILE;
396                 }
397                 // Set up output
398                 String[] outputLines = {
399                   "global_settings { ambient_light rgb <4, 4, 4> }", "",
400                   "// Background and camera",
401                   "background { color rgb <0, 0, 0> }",
402                   // camera position
403                   "camera {",
404                   "  location <" + _cameraX + ", " + _cameraY + ", " + _cameraZ + ">",
405                   "  look_at  <0, 0, 0>",
406                   "}", "",
407                 // global declares
408                   "// Global declares",
409                   "#declare lat_line =",
410                   "  cylinder {",
411                   "   <-" + inModelSize + ", 0.1, 0>,",
412                   "   <" + inModelSize + ", 0.1, 0>,",
413                   "   0.1            // Radius",
414                   "   pigment { color rgb <0.5 0.5 0.5> }",
415                   "  }",
416                   "#declare lon_line =",
417                   "  cylinder {",
418                   "   <0, 0.1, -" + inModelSize + ">,",
419                   "   <0, 0.1, " + inModelSize + ">,",
420                   "   0.1            // Radius",
421                   "   pigment { color rgb <0.5 0.5 0.5> }",
422                   "  }",
423                   "#declare point_rod =",
424                   "  cylinder {",
425                   "   <0, 0, 0>,",
426                   "   <0, 1, 0>,",
427                   "   0.15",
428                   "   open",
429                   "   pigment { color rgb <0.5 0.5 0.5> }",
430                   "  }", "",
431                   // TODO: Export rods to POV?  How to store in data?
432                   "#declare waypoint_sphere =",
433                   "  sphere {",
434                   "   <0, 0, 0>, 0.4",
435                   "    texture {",
436                   "       pigment {color rgb <0.1 0.1 1.0>}",
437                   "       finish { phong 1 }",
438                   "    }",
439                   "  }",
440                   "#declare track_sphere0 =",
441                   "  sphere {",
442                   "   <0, 0, 0>, 0.3", // size should depend on model size
443                   "   texture {",
444                   "      pigment {color rgb <0.2 1.0 0.2>}",
445                   "      finish { phong 1 }",
446                   "   }",
447                   " }",
448                   "#declare track_sphere1 =",
449                   "  sphere {",
450                   "   <0, 0, 0>, 0.3", // size should depend on model size
451                   "   texture {",
452                   "      pigment {color rgb <0.6 1.0 0.2>}",
453                   "      finish { phong 1 }",
454                   "   }",
455                   " }",
456                   "#declare track_sphere2 =",
457                   "  sphere {",
458                   "   <0, 0, 0>, 0.3", // size should depend on model size
459                   "   texture {",
460                   "      pigment {color rgb <1.0 1.0 0.1>}",
461                   "      finish { phong 1 }",
462                   "   }",
463                   " }",
464                   "#declare track_sphere3 =",
465                   "  sphere {",
466                   "   <0, 0, 0>, 0.3", // size should depend on model size
467                   "   texture {",
468                   "      pigment {color rgb <1.0 1.0 1.0>}",
469                   "      finish { phong 1 }",
470                   "   }",
471                   " }",
472                   "#declare track_sphere4 =",
473                   "  sphere {",
474                   "   <0, 0, 0>, 0.3", // size should depend on model size
475                   "   texture {",
476                   "      pigment {color rgb <0.1 1.0 1.0>}",
477                   "      finish { phong 1 }",
478                   "   }",
479                   " }",
480                   "#declare track_sphere_t =",
481                   "  sphere {",
482                   "   <0, 0, 0>, 0.25", // size should depend on model size
483                   "   texture {",
484                   "      pigment {color rgb <0.6 1.0 0.2>}",
485                   "      finish { phong 1 }",
486                   "   } no_shadow",
487                   " }",
488                   "#declare wall_colour = rgbt <0.5, 0.5, 0.5, 0.3>;", "",
489                   "// Base plane",
490                   "box {",
491                   "   <-" + inModelSize + ", -0.15, -" + inModelSize + ">,  // Near lower left corner",
492                   "   <" + inModelSize + ", 0.15, " + inModelSize + ">   // Far upper right corner",
493                   "   pigment { color rgb <0.5 0.75 0.8> }",
494                   "}", "",
495                 // write cardinals
496                   "// Cardinal letters N,S,E,W",
497                   "text {",
498                   "  ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.n") + "\" 0.3, 0",
499                   "  pigment { color rgb <1 1 1> }",
500                   "  translate <0, 0.2, " + inModelSize + ">",
501                   "}",
502                   "text {",
503                   "  ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.s") + "\" 0.3, 0",
504                   "  pigment { color rgb <1 1 1> }",
505                   "  translate <0, 0.2, -" + inModelSize + ">",
506                   "}",
507                   "text {",
508                   "  ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.e") + "\" 0.3, 0",
509                   "  pigment { color rgb <1 1 1> }",
510                   "  translate <" + (inModelSize * 0.97) + ", 0.2, 0>",
511                   "}",
512                   "text {",
513                   "  ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.w") + "\" 0.3, 0",
514                   "  pigment { color rgb <1 1 1> }",
515                   "  translate <-" + (inModelSize * 1.03) + ", 0.2, 0>",
516                   "}", "",
517                   // TODO: Light positions should relate to model size
518                   "// lights",
519                   "light_source { <-1, 9, -4> color rgb <0.5 0.5 0.5>}",
520                   "light_source { <1, 6, -14> color rgb <0.6 0.6 0.6>}",
521                   "light_source { <11, 12, 8> color rgb <0.3 0.3 0.3>}",
522                   "",
523                 };
524                 // write strings to file
525                 int numLines = outputLines.length;
526                 for (int i=0; i<numLines; i++)
527                 {
528                         inWriter.write(outputLines[i]);
529                         inWriter.write(inLineSeparator);
530                 }
531         }
532
533
534         /**
535          * Write out all the lat and long lines to the file
536          * @param inWriter Writer to use for writing file
537          * @param inModel model object for getting lat/long lines
538          * @param inLineSeparator line separator to use
539          * @throws IOException on file writing error
540          */
541         private void writeLatLongLines(FileWriter inWriter, ThreeDModel inModel, String inLineSeparator)
542         throws IOException
543         {
544                 inWriter.write("// Latitude and longitude lines:");
545                 inWriter.write(inLineSeparator);
546                 int numlines = inModel.getLatitudeLines().length;
547                 for (int i=0; i<numlines; i++)
548                 {
549                         // write cylinder to file
550                         inWriter.write("object { lat_line translate <0, 0, " + inModel.getScaledLatitudeLine(i) + "> }");
551                         inWriter.write(inLineSeparator);
552                 }
553                 numlines = inModel.getLongitudeLines().length;
554                 for (int i=0; i<numlines; i++)
555                 {
556                         // write cylinder to file
557                         inWriter.write("object { lon_line translate <" + inModel.getScaledLongitudeLine(i) + ", 0, 0> }");
558                         inWriter.write(inLineSeparator);
559                 }
560                 inWriter.write(inLineSeparator);
561         }
562
563
564         /**
565          * Write out all the data points to the file in the balls-and-sticks style
566          * @param inWriter Writer to use for writing file
567          * @param inModel model object for getting data points
568          * @param inLineSeparator line separator to use
569          * @throws IOException on file writing error
570          */
571         private void writeDataPointsBallsAndSticks(FileWriter inWriter, ThreeDModel inModel, String inLineSeparator)
572         throws IOException
573         {
574                 inWriter.write("// Data points:");
575                 inWriter.write(inLineSeparator);
576                 int numPoints = inModel.getNumPoints();
577                 for (int i=0; i<numPoints; i++)
578                 {
579                         // ball (different according to type)
580                         if (inModel.getPointType(i) == ThreeDModel.POINT_TYPE_WAYPOINT)
581                         {
582                                 // waypoint ball
583                                 inWriter.write("object { waypoint_sphere translate <" + inModel.getScaledHorizValue(i)
584                                         + "," + inModel.getScaledAltValue(i) + "," + inModel.getScaledVertValue(i) + "> }");
585                         }
586                         else
587                         {
588                                 // normal track point ball
589                                 inWriter.write("object { track_sphere" + checkHeightCode(inModel.getPointHeightCode(i))
590                                         + " translate <" + inModel.getScaledHorizValue(i) + "," + inModel.getScaledAltValue(i)
591                                         + "," + inModel.getScaledVertValue(i) + "> }");
592                         }
593                         inWriter.write(inLineSeparator);
594                         // vertical rod (if altitude positive)
595                         if (inModel.getScaledAltValue(i) > 0.0)
596                         {
597                                 inWriter.write("object { point_rod translate <" + inModel.getScaledHorizValue(i) + ",0,"
598                                         + inModel.getScaledVertValue(i) + "> scale <1," + inModel.getScaledAltValue(i) + ",1> }");
599                                 inWriter.write(inLineSeparator);
600                         }
601                 }
602                 inWriter.write(inLineSeparator);
603         }
604
605
606         /**
607          * Write out all the data points to the file in the tubes-and-walls style
608          * @param inWriter Writer to use for writing file
609          * @param inModel model object for getting data points
610          * @param inLineSeparator line separator to use
611          * @throws IOException on file writing error
612          */
613         private void writeDataPointsTubesAndWalls(FileWriter inWriter, ThreeDModel inModel, String inLineSeparator)
614         throws IOException
615         {
616                 inWriter.write("// Data points:");
617                 inWriter.write(inLineSeparator);
618                 int numPoints = inModel.getNumPoints();
619                 int numTrackPoints = 0;
620                 // Loop over all points and write out waypoints as balls
621                 for (int i=0; i<numPoints; i++)
622                 {
623                         if (inModel.getPointType(i) == ThreeDModel.POINT_TYPE_WAYPOINT)
624                         {
625                                 // waypoint ball
626                                 inWriter.write("object { waypoint_sphere translate <" + inModel.getScaledHorizValue(i)
627                                         + "," + inModel.getScaledAltValue(i) + "," + inModel.getScaledVertValue(i) + "> }");
628                                 // vertical rod (if altitude positive)
629                                 if (inModel.getScaledAltValue(i) > 0.0)
630                                 {
631                                         inWriter.write(inLineSeparator);
632                                         inWriter.write("object { point_rod translate <" + inModel.getScaledHorizValue(i) + ",0,"
633                                                 + inModel.getScaledVertValue(i) + "> scale <1," + inModel.getScaledAltValue(i) + ",1> }");
634                                 }
635                                 inWriter.write(inLineSeparator);
636                         }
637                         else {numTrackPoints++;}
638                 }
639                 inWriter.write(inLineSeparator);
640
641                 // Loop over all the track segments
642                 ArrayList<ModelSegment> segmentList = getSegmentList(inModel);
643                 Iterator<ModelSegment> segmentIterator = segmentList.iterator();
644                 while (segmentIterator.hasNext())
645                 {
646                         ModelSegment segment = segmentIterator.next();
647                         int segLength = segment.getNumTrackPoints();
648
649                         // if the track segment is long enough, do a cubic spline sphere sweep
650                         if (segLength <= 1)
651                         {
652                                 // single point in segment - just draw sphere
653                                 int index = segment.getStartIndex();
654                                 inWriter.write("object { track_sphere_t"
655                                         + " translate <" + inModel.getScaledHorizValue(index) + "," + inModel.getScaledAltValue(index)
656                                         + "," + inModel.getScaledVertValue(index) + "> }");
657                                 // maybe draw some kind of polygon too or rod?
658                         }
659                         else
660                         {
661                                 writeSphereSweep(inWriter, inModel, segment, inLineSeparator);
662                         }
663
664                         // Write wall underneath segment
665                         if (segLength > 1)
666                         {
667                                 writePolygonWall(inWriter, inModel, segment, inLineSeparator);
668                         }
669                 }
670         }
671
672
673         /**
674          * Write out a single sphere sweep using either cubic spline or linear spline
675          * @param inWriter Writer to use for writing file
676          * @param inModel model object for getting data points
677          * @param inSegment model segment to draw
678          * @param inLineSeparator line separator to use
679          * @throws IOException on file writing error
680          */
681         private static void writeSphereSweep(FileWriter inWriter, ThreeDModel inModel, ModelSegment inSegment, String inLineSeparator)
682         throws IOException
683         {
684                 // 3d sphere sweep
685                 inWriter.write("// Sphere sweep:");
686                 inWriter.write(inLineSeparator);
687                 String splineType = inSegment.getNumTrackPoints() < 5?"linear_spline":"cubic_spline";
688                 inWriter.write("sphere_sweep { "); inWriter.write(splineType);
689                 inWriter.write(" " + inSegment.getNumTrackPoints() + ",");
690                 inWriter.write(inLineSeparator);
691                 // Loop over all points in this segment and write out sphere sweep
692                 for (int i=inSegment.getStartIndex(); i<=inSegment.getEndIndex(); i++)
693                 {
694                         if (inModel.getPointType(i) != ThreeDModel.POINT_TYPE_WAYPOINT)
695                         {
696                                 inWriter.write("  <" + inModel.getScaledHorizValue(i) + "," + inModel.getScaledAltValue(i)
697                                         + "," + inModel.getScaledVertValue(i) + ">, 0.25");
698                                 inWriter.write(inLineSeparator);
699                         }
700                 }
701                 inWriter.write("  tolerance 0.1");
702                 inWriter.write(inLineSeparator);
703                 inWriter.write("  texture { pigment {color rgb <0.6 1.0 0.2>}  finish {phong 1} }");
704                 inWriter.write(inLineSeparator);
705                 inWriter.write("  no_shadow");
706                 inWriter.write(inLineSeparator);
707                 inWriter.write("}");
708                 inWriter.write(inLineSeparator);
709         }
710
711
712         /**
713          * Write out a single polygon-based wall for the tubes-and-walls style
714          * @param inWriter Writer to use for writing file
715          * @param inModel model object for getting data points
716          * @param inSegment model segment to draw
717          * @param inLineSeparator line separator to use
718          * @throws IOException on file writing error
719          */
720         private static void writePolygonWall(FileWriter inWriter, ThreeDModel inModel, ModelSegment inSegment, String inLineSeparator)
721         throws IOException
722         {
723                 // wall
724                 inWriter.write(inLineSeparator);
725                 inWriter.write("// wall between sweep and floor:");
726                 inWriter.write(inLineSeparator);
727                 // Loop over all points in this segment again and write out polygons
728                 int prevIndex = -1;
729                 for (int i=inSegment.getStartIndex(); i<=inSegment.getEndIndex(); i++)
730                 {
731                         if (inModel.getPointType(i) != ThreeDModel.POINT_TYPE_WAYPOINT)
732                         {
733                                 if (prevIndex >= 0)
734                                 {
735                                         double xDiff = inModel.getScaledHorizValue(i) - inModel.getScaledHorizValue(prevIndex);
736                                         double yDiff = inModel.getScaledVertValue(i) - inModel.getScaledVertValue(prevIndex);
737                                         double dist = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
738                                         if (dist > 0)
739                                         {
740                                                 inWriter.write("polygon {");
741                                                 inWriter.write("  5, <" + inModel.getScaledHorizValue(prevIndex) + ", 0.0, " + inModel.getScaledVertValue(prevIndex) + ">,");
742                                                 inWriter.write(" <" + inModel.getScaledHorizValue(prevIndex) + ", " + inModel.getScaledAltValue(prevIndex) + ", "
743                                                         + inModel.getScaledVertValue(prevIndex) + ">,");
744                                                 inWriter.write(" <" + inModel.getScaledHorizValue(i) + ", " + inModel.getScaledAltValue(i) + ", "
745                                                         + inModel.getScaledVertValue(i) + ">,");
746                                                 inWriter.write(" <" + inModel.getScaledHorizValue(i) + ", 0.0, " + inModel.getScaledVertValue(i) + ">,");
747                                                 inWriter.write(" <" + inModel.getScaledHorizValue(prevIndex) + ", 0.0, " + inModel.getScaledVertValue(prevIndex) + ">");
748                                                 inWriter.write("  pigment { color wall_colour } no_shadow");
749                                                 inWriter.write("}");
750                                                 inWriter.write(inLineSeparator);
751                                         }
752                                 }
753                                 prevIndex = i;
754                         }
755                 }
756         }
757
758
759         /**
760          * @param inCode height code to check
761          * @return validated height code within range 0 to max
762          */
763         private static byte checkHeightCode(byte inCode)
764         {
765                 final byte maxHeightCode = 4;
766                 if (inCode < 0) return 0;
767                 if (inCode > maxHeightCode) return maxHeightCode;
768                 return inCode;
769         }
770
771
772         /**
773          * Check the given coordinate
774          * @param inString String entered by user
775          * @return validated String value
776          */
777         private static String checkCoordinate(String inString)
778         {
779                 double value = 0.0;
780                 try
781                 {
782                         value = Double.parseDouble(inString);
783                 }
784                 catch (Exception e) {} // ignore parse failures
785                 return "" + value;
786         }
787
788         /**
789          * Go through the points making a list of the segment starts and the number of track points in each segment
790          * @param inModel model containing data
791          * @return list of ModelSegment objects
792          */
793         private static ArrayList<ModelSegment> getSegmentList(ThreeDModel inModel)
794         {
795                 ArrayList<ModelSegment> segmentList = new ArrayList<ModelSegment>();
796                 if (inModel != null && inModel.getNumPoints() > 0)
797                 {
798                         ModelSegment currSegment = null;
799                         int numTrackPoints = 0;
800                         for (int i=0; i<inModel.getNumPoints(); i++)
801                         {
802                                 if (inModel.getPointType(i) != ThreeDModel.POINT_TYPE_WAYPOINT)
803                                 {
804                                         if (inModel.getPointType(i) == ThreeDModel.POINT_TYPE_SEGMENT_START || currSegment == null)
805                                         {
806                                                 // start of segment
807                                                 if (currSegment != null)
808                                                 {
809                                                         currSegment.setEndIndex(i-1);
810                                                         currSegment.setNumTrackPoints(numTrackPoints);
811                                                         segmentList.add(currSegment);
812                                                         numTrackPoints = 0;
813                                                 }
814                                                 currSegment = new ModelSegment(i);
815                                         }
816                                         numTrackPoints++;
817                                 }
818                         }
819                         // Add last segment to list
820                         if (currSegment != null && numTrackPoints > 0)
821                         {
822                                 currSegment.setEndIndex(inModel.getNumPoints()-1);
823                                 currSegment.setNumTrackPoints(numTrackPoints);
824                                 segmentList.add(currSegment);
825                         }
826                 }
827                 return segmentList;
828         }
829 }