1 package tim.prune.save;
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;
10 import java.io.FileWriter;
11 import java.io.IOException;
12 import java.util.ArrayList;
13 import java.util.Iterator;
15 import javax.imageio.ImageIO;
16 import javax.swing.BorderFactory;
17 import javax.swing.Box;
18 import javax.swing.BoxLayout;
19 import javax.swing.ButtonGroup;
20 import javax.swing.JButton;
21 import javax.swing.JDialog;
22 import javax.swing.JFileChooser;
23 import javax.swing.JLabel;
24 import javax.swing.JOptionPane;
25 import javax.swing.JPanel;
26 import javax.swing.JRadioButton;
27 import javax.swing.JTextField;
28 import javax.swing.SwingConstants;
29 import javax.swing.border.EtchedBorder;
32 import tim.prune.DataSubscriber;
33 import tim.prune.I18nManager;
34 import tim.prune.UpdateMessageBroker;
35 import tim.prune.config.Config;
36 import tim.prune.data.NumberUtils;
37 import tim.prune.data.Track;
38 import tim.prune.function.Export3dFunction;
39 import tim.prune.gui.DialogCloser;
40 import tim.prune.gui.map.MapSource;
41 import tim.prune.gui.map.MapSourceLibrary;
42 import tim.prune.load.GenericFileFilter;
43 import tim.prune.threedee.ThreeDModel;
46 * Class to export a 3d scene of the track to a specified Pov file
47 * Note: Subscriber interface only used for callback from image config dialog
49 public class PovExporter extends Export3dFunction implements DataSubscriber
51 private Track _track = null;
52 private JDialog _dialog = null;
53 private JFileChooser _fileChooser = null;
54 private String _cameraX = null, _cameraY = null, _cameraZ = null;
55 private JTextField _cameraXField = null, _cameraYField = null, _cameraZField = null;
56 private JTextField _fontName = null, _altitudeFactorField = null;
57 private JRadioButton _ballsAndSticksButton = null;
58 private JLabel _baseImageLabel = null;
59 private JButton _baseImageButton = null;
60 private BaseImageConfigDialog _baseImageConfig = null;
63 private static final double DEFAULT_CAMERA_DISTANCE = 30.0;
64 private static final double MODEL_SCALE_FACTOR = 20.0;
65 private static final String DEFAULT_FONT_FILE = "crystal.ttf";
70 * @param inApp App object
72 public PovExporter(App inApp)
75 _track = inApp.getTrackInfo().getTrack();
76 // Set default camera coordinates
77 _cameraX = "17"; _cameraY = "13"; _cameraZ = "-20";
80 /** Get the name key */
81 public String getNameKey() {
82 return "function.exportpov";
86 * Set the coordinates for the camera (can be any scale)
87 * @param inX X coordinate of camera
88 * @param inY Y coordinate of camera
89 * @param inZ Z coordinate of camera
91 public void setCameraCoordinates(double inX, double inY, double inZ)
93 // calculate distance from origin
94 double cameraDist = Math.sqrt(inX*inX + inY*inY + inZ*inZ);
97 _cameraX = NumberUtils.formatNumberUk(inX / cameraDist * DEFAULT_CAMERA_DISTANCE, 5);
98 _cameraY = NumberUtils.formatNumberUk(inY / cameraDist * DEFAULT_CAMERA_DISTANCE, 5);
99 // Careful! Need to convert from java3d (right-handed) to povray (left-handed) coordinate system!
100 _cameraZ = NumberUtils.formatNumberUk(-inZ / cameraDist * DEFAULT_CAMERA_DISTANCE, 5);
106 * Show the dialog to select options and export file
110 // Make dialog window to select angles, colours etc
113 _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
114 _dialog.setLocationRelativeTo(_parentFrame);
115 _dialog.getContentPane().add(makeDialogComponents());
117 // Make base image dialog
118 if (_baseImageConfig == null)
120 _baseImageConfig = new BaseImageConfigDialog(this, _dialog, _track);
124 _cameraXField.setText(_cameraX);
125 _cameraYField.setText(_cameraY);
126 _cameraZField.setText(_cameraZ);
127 _altitudeFactorField.setText("" + _altFactor);
128 updateBaseImageDetails();
131 _dialog.setVisible(true);
136 * Make the dialog components to select the export options
137 * @return Component holding gui elements
139 private Component makeDialogComponents()
141 JPanel panel = new JPanel();
142 panel.setLayout(new BorderLayout(4, 4));
143 JLabel introLabel = new JLabel(I18nManager.getText("dialog.exportpov.text"));
144 introLabel.setBorder(BorderFactory.createEmptyBorder(4, 4, 6, 4));
145 panel.add(introLabel, BorderLayout.NORTH);
146 // OK, Cancel buttons
147 JPanel buttonPanel = new JPanel();
148 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
149 JButton okButton = new JButton(I18nManager.getText("button.ok"));
150 okButton.addActionListener(new ActionListener() {
151 public void actionPerformed(ActionEvent e)
154 MapGrouter.clearMapImage();
158 buttonPanel.add(okButton);
159 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
160 cancelButton.addActionListener(new ActionListener() {
161 public void actionPerformed(ActionEvent e)
163 MapGrouter.clearMapImage();
167 buttonPanel.add(cancelButton);
168 panel.add(buttonPanel, BorderLayout.SOUTH);
171 JPanel centralPanel = new JPanel();
172 centralPanel.setLayout(new GridLayout(0, 2, 10, 4));
174 JLabel fontLabel = new JLabel(I18nManager.getText("dialog.exportpov.font"));
175 fontLabel.setHorizontalAlignment(SwingConstants.TRAILING);
176 centralPanel.add(fontLabel);
177 String defaultFont = Config.getConfigString(Config.KEY_POVRAY_FONT);
178 if (defaultFont == null || defaultFont.equals("")) {
179 defaultFont = DEFAULT_FONT_FILE;
181 _fontName = new JTextField(defaultFont, 12);
182 _fontName.setAlignmentX(Component.LEFT_ALIGNMENT);
183 _fontName.addKeyListener(new DialogCloser(_dialog));
184 centralPanel.add(_fontName);
185 //coordinates of camera
186 JLabel cameraXLabel = new JLabel(I18nManager.getText("dialog.exportpov.camerax"));
187 cameraXLabel.setHorizontalAlignment(SwingConstants.TRAILING);
188 centralPanel.add(cameraXLabel);
189 _cameraXField = new JTextField("" + _cameraX);
190 centralPanel.add(_cameraXField);
191 JLabel cameraYLabel = new JLabel(I18nManager.getText("dialog.exportpov.cameray"));
192 cameraYLabel.setHorizontalAlignment(SwingConstants.TRAILING);
193 centralPanel.add(cameraYLabel);
194 _cameraYField = new JTextField("" + _cameraY);
195 centralPanel.add(_cameraYField);
196 JLabel cameraZLabel = new JLabel(I18nManager.getText("dialog.exportpov.cameraz"));
197 cameraZLabel.setHorizontalAlignment(SwingConstants.TRAILING);
198 centralPanel.add(cameraZLabel);
199 _cameraZField = new JTextField("" + _cameraZ);
200 centralPanel.add(_cameraZField);
201 // Altitude exaggeration
202 JLabel altitudeCapLabel = new JLabel(I18nManager.getText("dialog.3d.altitudefactor"));
203 altitudeCapLabel.setHorizontalAlignment(SwingConstants.TRAILING);
204 centralPanel.add(altitudeCapLabel);
205 _altitudeFactorField = new JTextField("1.0");
206 centralPanel.add(_altitudeFactorField);
208 // Radio buttons for style - balls on sticks or tubes
209 JPanel stylePanel = new JPanel();
210 stylePanel.setLayout(new GridLayout(0, 2, 10, 4));
211 JLabel styleLabel = new JLabel(I18nManager.getText("dialog.exportpov.modelstyle"));
212 styleLabel.setHorizontalAlignment(SwingConstants.TRAILING);
213 stylePanel.add(styleLabel);
214 JPanel radioPanel = new JPanel();
215 radioPanel.setLayout(new BoxLayout(radioPanel, BoxLayout.Y_AXIS));
216 _ballsAndSticksButton = new JRadioButton(I18nManager.getText("dialog.exportpov.ballsandsticks"));
217 _ballsAndSticksButton.setSelected(false);
218 radioPanel.add(_ballsAndSticksButton);
219 JRadioButton tubesButton = new JRadioButton(I18nManager.getText("dialog.exportpov.tubesandwalls"));
220 tubesButton.setSelected(true);
221 radioPanel.add(tubesButton);
222 ButtonGroup group = new ButtonGroup();
223 group.add(_ballsAndSticksButton); group.add(tubesButton);
224 stylePanel.add(radioPanel);
226 // Panel for the base image
227 JPanel imagePanel = new JPanel();
228 imagePanel.setLayout(new BorderLayout(10, 4));
229 imagePanel.add(new JLabel(I18nManager.getText("dialog.exportpov.baseimage") + ": "), BorderLayout.WEST);
230 _baseImageLabel = new JLabel("Typical sourcename");
231 imagePanel.add(_baseImageLabel, BorderLayout.CENTER);
232 _baseImageButton = new JButton(I18nManager.getText("button.edit"));
233 _baseImageButton.addActionListener(new ActionListener() {
234 public void actionPerformed(ActionEvent event) {
238 imagePanel.add(_baseImageButton, BorderLayout.EAST);
239 // Put these image controls inside a holder panel with an outline
240 JPanel imageHolderPanel = new JPanel();
241 imageHolderPanel.setBorder(BorderFactory.createCompoundBorder(
242 BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(4, 4, 4, 4))
244 imageHolderPanel.setLayout(new BorderLayout());
245 imageHolderPanel.add(imagePanel, BorderLayout.NORTH);
247 // add these panels to the holder panel
248 JPanel holderPanel = new JPanel();
249 holderPanel.setLayout(new BorderLayout(5, 5));
250 JPanel boxPanel = new JPanel();
251 boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS));
252 boxPanel.add(centralPanel);
253 boxPanel.add(Box.createVerticalStrut(4));
254 boxPanel.add(stylePanel);
255 boxPanel.add(Box.createVerticalStrut(4));
256 boxPanel.add(imageHolderPanel);
257 holderPanel.add(boxPanel, BorderLayout.CENTER);
259 panel.add(holderPanel, BorderLayout.CENTER);
264 * Change the base image by calling the BaseImageConfigDialog
266 private void changeBaseImage()
268 // Check if there is a cache to use
269 if (BaseImageConfigDialog.isImagePossible())
271 // Show new dialog to choose image details
272 _baseImageConfig.begin();
275 _app.showErrorMessage(getNameKey(), "dialog.exportimage.noimagepossible");
280 * Update the description label according to the selected base image details
282 private void updateBaseImageDetails()
285 if (_baseImageConfig.useImage())
287 MapSource source = MapSourceLibrary.getSource(_baseImageConfig.getSourceIndex());
288 if (source != null) {
289 desc = source.getName() + " ("
290 + _baseImageConfig.getZoomLevel() + ")";
294 desc = I18nManager.getText("dialog.about.no");
296 _baseImageLabel.setText(desc);
300 * Select the file and export data to it
302 private void doExport()
304 // Copy camera coordinates
305 _cameraX = checkCoordinate(_cameraXField.getText());
306 _cameraY = checkCoordinate(_cameraYField.getText());
307 _cameraZ = checkCoordinate(_cameraZField.getText());
309 // OK pressed, so choose output file
310 if (_fileChooser == null)
312 _fileChooser = new JFileChooser();
313 _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
314 _fileChooser.setFileFilter(new GenericFileFilter("filetype.pov", new String[] {"pov"}));
315 _fileChooser.setAcceptAllFileFilterUsed(false);
316 // start from directory in config which should be set
317 final String configDir = Config.getConfigString(Config.KEY_TRACK_DIR);
318 if (configDir != null) {_fileChooser.setCurrentDirectory(new File(configDir));}
321 // Allow choose again if an existing file is selected
322 boolean chooseAgain = false;
326 if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION)
328 // OK pressed and file chosen
329 File povFile = _fileChooser.getSelectedFile();
330 if (!povFile.getName().toLowerCase().endsWith(".pov"))
332 povFile = new File(povFile.getAbsolutePath() + ".pov");
334 final int nameLen = povFile.getName().length() - 4;
335 final File imageFile = new File(povFile.getParentFile(), povFile.getName().substring(0, nameLen) + "_base.png");
336 final boolean imageExists = _baseImageConfig.useImage() && imageFile.exists();
337 // Check if files exist and if necessary prompt for overwrite
338 Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")};
339 if ((!povFile.exists() && !imageExists) || JOptionPane.showOptionDialog(_parentFrame,
340 I18nManager.getText("dialog.save.overwrite.text"),
341 I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION,
342 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
343 == JOptionPane.YES_OPTION)
346 if (exportFile(povFile, imageFile))
348 // file saved - store directory in config for later
349 Config.setConfigString(Config.KEY_TRACK_DIR, povFile.getParentFile().getAbsolutePath());
353 // export failed so need to choose again
359 // overwrite cancelled so need to choose again
363 } while (chooseAgain);
368 * Export the track data to the specified file
369 * @param inPovFile File object to save pov file to
370 * @param inImageFile file object to save image to
371 * @return true if successful
373 private boolean exportFile(File inPovFile, File inImageFile)
375 FileWriter writer = null;
376 // find out the line separator for this system
377 final String lineSeparator = System.getProperty("line.separator");
380 // create and scale model
381 ThreeDModel model = new ThreeDModel(_track);
382 model.setModelSize(MODEL_SCALE_FACTOR);
385 // try to use given altitude cap
386 double altFactor = Double.parseDouble(_altitudeFactorField.getText());
387 model.setAltitudeFactor(altFactor);
389 catch (NumberFormatException nfe) { // parse failed, reset
390 _altitudeFactorField.setText("1.0");
394 boolean useImage = _baseImageConfig.useImage();
397 // Get base image from grouter
398 MapSource mapSource = MapSourceLibrary.getSource(_baseImageConfig.getSourceIndex());
399 GroutedImage baseImage = MapGrouter.getMapImage(_track, mapSource, _baseImageConfig.getZoomLevel());
402 useImage = ImageIO.write(baseImage.getImage(), "png", inImageFile);
404 catch (IOException ioe) {
405 System.err.println("Can't write image: " + ioe.getClass().getName());
409 _app.showErrorMessage(getNameKey(), "dialog.exportpov.cannotmakebaseimage");
413 // Create file and write basics
414 writer = new FileWriter(inPovFile);
415 writeStartOfFile(writer, lineSeparator, useImage ? inImageFile : null);
418 if (_ballsAndSticksButton.isSelected()) {
419 writeDataPointsBallsAndSticks(writer, model, lineSeparator);
422 writeDataPointsTubesAndWalls(writer, model, lineSeparator);
426 UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1")
427 + " " + _track.getNumPoints() + " " + I18nManager.getText("confirm.save.ok2")
428 + " " + inPovFile.getAbsolutePath());
431 catch (IOException ioe)
433 JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(),
434 I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE);
438 // close file ignoring exceptions
443 catch (Exception e) {}
450 * Write the start of the Pov file, including base plane and lights
451 * @param inWriter Writer to use for writing file
452 * @param inLineSeparator line separator to use
453 * @param inImageFile image file to reference (or null if none)
454 * @throws IOException on file writing error
456 private void writeStartOfFile(FileWriter inWriter, String inLineSeparator, File inImageFile)
459 inWriter.write("// Pov file produced by GpsPrune - see http://activityworkshop.net/");
460 inWriter.write(inLineSeparator);
461 inWriter.write(inLineSeparator);
462 // Select font based on user input
463 String fontPath = _fontName.getText();
464 if (fontPath == null || fontPath.equals(""))
466 fontPath = DEFAULT_FONT_FILE;
469 Config.setConfigString(Config.KEY_POVRAY_FONT, fontPath);
472 // Make the definition of the base plane depending on whether there's an image or not
473 final boolean useImage = (inImageFile != null);
474 final String boxDefinition = (inImageFile == null ?
475 " <-10.0, -0.15, -10.0>," + inLineSeparator
476 + " <10.0, 0.15, 10.0>" + inLineSeparator
477 + " pigment { color rgb <0.5 0.75 0.8> }"
479 " <0, 0, 0>, <1, 1, 0.001>" + inLineSeparator
480 + " pigment {image_map { png \"" + inImageFile.getName() + "\" map_type 0 interpolate 2 once } }" + inLineSeparator
481 + " scale 20.0 rotate <90, 0, 0>" + inLineSeparator
482 + " translate <-10.0, 0, -10.0>");
483 // TODO: Maybe could use the same geometry for the imageless case, would simplify code a bit
486 String[] outputLines = {
487 "global_settings { ambient_light rgb <4, 4, 4> }", "",
488 "// Background and camera",
489 "background { color rgb <0, 0, 0> }",
492 " location <" + _cameraX + ", " + _cameraY + ", " + _cameraZ + ">",
493 " look_at <0, 0, 0>",
496 "// Global declares",
497 "#declare point_rod =",
504 " pigment { color rgb <0.5 0.5 0.5> }",
505 useImage ? " } no_shadow" : " }",
507 // MAYBE: Export rods to POV? How to store in data?
508 "#declare waypoint_sphere =",
512 " pigment {color rgb <0.1 0.1 1.0>}",
513 " finish { phong 1 }",
514 useImage ? " } no_shadow" : " }",
516 "#declare track_sphere0 =",
518 " <0, 0, 0>, 0.3", // size should depend on model size
520 " pigment {color rgb <0.1 0.6 0.1>}", // dark green
521 " finish { phong 1 }",
524 "#declare track_sphere1 =",
526 " <0, 0, 0>, 0.3", // size should depend on model size
528 " pigment {color rgb <0.4 0.9 0.2>}", // green
529 " finish { phong 1 }",
532 "#declare track_sphere2 =",
534 " <0, 0, 0>, 0.3", // size should depend on model size
536 " pigment {color rgb <0.7 0.8 0.2>}", // yellow
537 " finish { phong 1 }",
540 "#declare track_sphere3 =",
542 " <0, 0, 0>, 0.3", // size should depend on model size
544 " pigment {color rgb <0.5 0.8 0.6>}", // greeny
545 " finish { phong 1 }",
548 "#declare track_sphere4 =",
550 " <0, 0, 0>, 0.3", // size should depend on model size
552 " pigment {color rgb <0.2 0.9 0.9>}", // cyan
553 " finish { phong 1 }",
556 "#declare track_sphere5 =",
558 " <0, 0, 0>, 0.3", // size should depend on model size
560 " pigment {color rgb <1.0 1.0 1.0>}", // white
561 " finish { phong 1 }",
564 "#declare track_sphere_t =",
566 " <0, 0, 0>, 0.25", // size should depend on model size
568 " pigment {color rgb <0.6 1.0 0.2>}",
569 " finish { phong 1 }",
572 "#declare wall_colour = rgbt <0.5, 0.5, 0.5, 0.3>;", "",
578 "// Cardinal letters N,S,E,W",
580 " ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.n") + "\" 0.3, 0",
581 " pigment { color rgb <1 1 1> }",
582 " translate <0, 0.2, 10.0>",
585 " ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.s") + "\" 0.3, 0",
586 " pigment { color rgb <1 1 1> }",
587 " translate <0, 0.2, -10.0>",
590 " ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.e") + "\" 0.3, 0",
591 " pigment { color rgb <1 1 1> }",
592 " translate <9.7, 0.2, 0>",
595 " ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.w") + "\" 0.3, 0",
596 " pigment { color rgb <1 1 1> }",
597 " translate <-10.3, 0.2, 0>",
599 // MAYBE: Light positions should relate to model size
601 "light_source { <-1, 9, -4> color rgb <0.5 0.5 0.5>}",
602 "light_source { <1, 6, -14> color rgb <0.6 0.6 0.6>}",
603 "light_source { <11, 12, 8> color rgb <0.3 0.3 0.3>}",
606 // write strings to file
607 int numLines = outputLines.length;
608 for (int i=0; i<numLines; i++)
610 inWriter.write(outputLines[i]);
611 inWriter.write(inLineSeparator);
617 * Write out all the data points to the file in the balls-and-sticks style
618 * @param inWriter Writer to use for writing file
619 * @param inModel model object for getting data points
620 * @param inLineSeparator line separator to use
621 * @throws IOException on file writing error
623 private static void writeDataPointsBallsAndSticks(FileWriter inWriter, ThreeDModel inModel, String inLineSeparator)
626 inWriter.write("// Data points:");
627 inWriter.write(inLineSeparator);
628 int numPoints = inModel.getNumPoints();
629 for (int i=0; i<numPoints; i++)
631 // ball (different according to type)
632 if (inModel.getPointType(i) == ThreeDModel.POINT_TYPE_WAYPOINT)
635 inWriter.write("object { waypoint_sphere translate <" + inModel.getScaledHorizValue(i)
636 + "," + inModel.getScaledAltValue(i) + "," + inModel.getScaledVertValue(i) + "> }");
640 // normal track point ball
641 inWriter.write("object { track_sphere" + checkHeightCode(inModel.getPointHeightCode(i))
642 + " translate <" + inModel.getScaledHorizValue(i) + "," + inModel.getScaledAltValue(i)
643 + "," + inModel.getScaledVertValue(i) + "> }");
645 inWriter.write(inLineSeparator);
646 // vertical rod (if altitude positive)
647 if (inModel.getScaledAltValue(i) > 0.0)
649 inWriter.write("object { point_rod translate <" + inModel.getScaledHorizValue(i) + ",0,"
650 + inModel.getScaledVertValue(i) + "> scale <1," + inModel.getScaledAltValue(i) + ",1> }");
651 inWriter.write(inLineSeparator);
654 inWriter.write(inLineSeparator);
659 * Write out all the data points to the file in the tubes-and-walls style
660 * @param inWriter Writer to use for writing file
661 * @param inModel model object for getting data points
662 * @param inLineSeparator line separator to use
663 * @throws IOException on file writing error
665 private static void writeDataPointsTubesAndWalls(FileWriter inWriter, ThreeDModel inModel, String inLineSeparator)
668 inWriter.write("// Data points:");
669 inWriter.write(inLineSeparator);
670 int numPoints = inModel.getNumPoints();
671 int numTrackPoints = 0;
672 // Loop over all points and write out waypoints as balls
673 for (int i=0; i<numPoints; i++)
675 if (inModel.getPointType(i) == ThreeDModel.POINT_TYPE_WAYPOINT)
678 inWriter.write("object { waypoint_sphere translate <" + inModel.getScaledHorizValue(i)
679 + "," + inModel.getScaledAltValue(i) + "," + inModel.getScaledVertValue(i) + "> }");
680 // vertical rod (if altitude positive)
681 if (inModel.getScaledAltValue(i) > 0.0)
683 inWriter.write(inLineSeparator);
684 inWriter.write("object { point_rod translate <" + inModel.getScaledHorizValue(i) + ",0,"
685 + inModel.getScaledVertValue(i) + "> scale <1," + inModel.getScaledAltValue(i) + ",1> }");
687 inWriter.write(inLineSeparator);
689 else {numTrackPoints++;}
691 inWriter.write(inLineSeparator);
693 // Loop over all the track segments
694 ArrayList<ModelSegment> segmentList = getSegmentList(inModel);
695 Iterator<ModelSegment> segmentIterator = segmentList.iterator();
696 while (segmentIterator.hasNext())
698 ModelSegment segment = segmentIterator.next();
699 int segLength = segment.getNumTrackPoints();
701 // if the track segment is long enough, do a cubic spline sphere sweep
704 // single point in segment - just draw sphere
705 int index = segment.getStartIndex();
706 inWriter.write("object { track_sphere_t"
707 + " translate <" + inModel.getScaledHorizValue(index) + "," + inModel.getScaledAltValue(index)
708 + "," + inModel.getScaledVertValue(index) + "> }");
709 // maybe draw some kind of polygon too or rod?
713 writeSphereSweep(inWriter, inModel, segment, inLineSeparator);
716 // Write wall underneath segment
719 writePolygonWall(inWriter, inModel, segment, inLineSeparator);
726 * Write out a single sphere sweep using either cubic spline or linear spline
727 * @param inWriter Writer to use for writing file
728 * @param inModel model object for getting data points
729 * @param inSegment model segment to draw
730 * @param inLineSeparator line separator to use
731 * @throws IOException on file writing error
733 private static void writeSphereSweep(FileWriter inWriter, ThreeDModel inModel, ModelSegment inSegment, String inLineSeparator)
737 inWriter.write("// Sphere sweep:");
738 inWriter.write(inLineSeparator);
739 String splineType = inSegment.getNumTrackPoints() < 5?"linear_spline":"cubic_spline";
740 inWriter.write("sphere_sweep { "); inWriter.write(splineType);
741 inWriter.write(" " + inSegment.getNumTrackPoints() + ",");
742 inWriter.write(inLineSeparator);
743 // Loop over all points in this segment and write out sphere sweep
744 for (int i=inSegment.getStartIndex(); i<=inSegment.getEndIndex(); i++)
746 if (inModel.getPointType(i) != ThreeDModel.POINT_TYPE_WAYPOINT)
748 inWriter.write(" <" + inModel.getScaledHorizValue(i) + "," + inModel.getScaledAltValue(i)
749 + "," + inModel.getScaledVertValue(i) + ">, 0.25");
750 inWriter.write(inLineSeparator);
753 inWriter.write(" tolerance 0.1");
754 inWriter.write(inLineSeparator);
755 inWriter.write(" texture { pigment {color rgb <0.6 1.0 0.2>} finish {phong 1} }");
756 inWriter.write(inLineSeparator);
757 inWriter.write(" no_shadow");
758 inWriter.write(inLineSeparator);
760 inWriter.write(inLineSeparator);
765 * Write out a single polygon-based wall for the tubes-and-walls style
766 * @param inWriter Writer to use for writing file
767 * @param inModel model object for getting data points
768 * @param inSegment model segment to draw
769 * @param inLineSeparator line separator to use
770 * @throws IOException on file writing error
772 private static void writePolygonWall(FileWriter inWriter, ThreeDModel inModel, ModelSegment inSegment, String inLineSeparator)
776 inWriter.write(inLineSeparator);
777 inWriter.write("// wall between sweep and floor:");
778 inWriter.write(inLineSeparator);
779 // Loop over all points in this segment again and write out polygons
781 for (int i=inSegment.getStartIndex(); i<=inSegment.getEndIndex(); i++)
783 if (inModel.getPointType(i) != ThreeDModel.POINT_TYPE_WAYPOINT)
787 double xDiff = inModel.getScaledHorizValue(i) - inModel.getScaledHorizValue(prevIndex);
788 double yDiff = inModel.getScaledVertValue(i) - inModel.getScaledVertValue(prevIndex);
789 double dist = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
792 inWriter.write("polygon {");
793 inWriter.write(" 5, <" + inModel.getScaledHorizValue(prevIndex) + ", 0.0, " + inModel.getScaledVertValue(prevIndex) + ">,");
794 inWriter.write(" <" + inModel.getScaledHorizValue(prevIndex) + ", " + inModel.getScaledAltValue(prevIndex) + ", "
795 + inModel.getScaledVertValue(prevIndex) + ">,");
796 inWriter.write(" <" + inModel.getScaledHorizValue(i) + ", " + inModel.getScaledAltValue(i) + ", "
797 + inModel.getScaledVertValue(i) + ">,");
798 inWriter.write(" <" + inModel.getScaledHorizValue(i) + ", 0.0, " + inModel.getScaledVertValue(i) + ">,");
799 inWriter.write(" <" + inModel.getScaledHorizValue(prevIndex) + ", 0.0, " + inModel.getScaledVertValue(prevIndex) + ">");
800 inWriter.write(" pigment { color wall_colour } no_shadow");
802 inWriter.write(inLineSeparator);
812 * @param inCode height code to check
813 * @return validated height code within range 0 to max
815 private static byte checkHeightCode(byte inCode)
817 final byte maxHeightCode = 5;
818 if (inCode < 0) return 0;
819 if (inCode > maxHeightCode) return maxHeightCode;
825 * Check the given coordinate
826 * @param inString String entered by user
827 * @return validated String value
829 private static String checkCoordinate(String inString)
834 value = Double.parseDouble(inString);
836 catch (Exception e) {} // ignore parse failures
841 * Go through the points making a list of the segment starts and the number of track points in each segment
842 * @param inModel model containing data
843 * @return list of ModelSegment objects
845 private static ArrayList<ModelSegment> getSegmentList(ThreeDModel inModel)
847 ArrayList<ModelSegment> segmentList = new ArrayList<ModelSegment>();
848 if (inModel != null && inModel.getNumPoints() > 0)
850 ModelSegment currSegment = null;
851 int numTrackPoints = 0;
852 for (int i=0; i<inModel.getNumPoints(); i++)
854 if (inModel.getPointType(i) != ThreeDModel.POINT_TYPE_WAYPOINT)
856 if (inModel.getPointType(i) == ThreeDModel.POINT_TYPE_SEGMENT_START || currSegment == null)
859 if (currSegment != null)
861 currSegment.setEndIndex(i-1);
862 currSegment.setNumTrackPoints(numTrackPoints);
863 segmentList.add(currSegment);
866 currSegment = new ModelSegment(i);
871 // Add last segment to list
872 if (currSegment != null && numTrackPoints > 0)
874 currSegment.setEndIndex(inModel.getNumPoints()-1);
875 currSegment.setNumTrackPoints(numTrackPoints);
876 segmentList.add(currSegment);
883 * Callback from base image config dialog
885 public void dataUpdated(byte inUpdateType)
887 updateBaseImageDetails();
891 public void actionCompleted(String inMessage) {