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