X-Git-Url: https://gitweb.fperrin.net/?a=blobdiff_plain;f=tim%2Fprune%2Fsave%2FPovExporter.java;h=ba6605853f560b62eb3df3b3aabdfdd5fe7e8c70;hb=7f5ed2be62905bd37717376dc22d09e5ea7edb4d;hp=aacee115adecd4c95fdaa3b0ef0bcc6d0f803a04;hpb=da0b1f449260a0b4a94318006382a9039726ef3e;p=GpsPrune.git diff --git a/tim/prune/save/PovExporter.java b/tim/prune/save/PovExporter.java index aacee11..ba66058 100644 --- a/tim/prune/save/PovExporter.java +++ b/tim/prune/save/PovExporter.java @@ -9,57 +9,78 @@ import java.awt.event.ActionListener; import java.io.File; import java.io.FileWriter; import java.io.IOException; - +import java.util.ArrayList; +import java.util.Iterator; + +import javax.imageio.ImageIO; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFileChooser; -import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; +import javax.swing.JRadioButton; import javax.swing.JTextField; import javax.swing.SwingConstants; -import javax.swing.filechooser.FileFilter; +import javax.swing.border.EtchedBorder; +import tim.prune.App; +import tim.prune.DataSubscriber; import tim.prune.I18nManager; +import tim.prune.UpdateMessageBroker; +import tim.prune.config.Config; +import tim.prune.data.NumberUtils; import tim.prune.data.Track; -import tim.prune.threedee.LineDialog; +import tim.prune.function.Export3dFunction; +import tim.prune.gui.DialogCloser; +import tim.prune.gui.map.MapSource; +import tim.prune.gui.map.MapSourceLibrary; +import tim.prune.load.GenericFileFilter; import tim.prune.threedee.ThreeDModel; /** - * Class to export track information - * into a specified Pov file + * Class to export a 3d scene of the track to a specified Pov file + * Note: Subscriber interface only used for callback from image config dialog */ -public class PovExporter +public class PovExporter extends Export3dFunction implements DataSubscriber { - private JFrame _parentFrame = null; private Track _track = null; private JDialog _dialog = null; private JFileChooser _fileChooser = null; private String _cameraX = null, _cameraY = null, _cameraZ = null; private JTextField _cameraXField = null, _cameraYField = null, _cameraZField = null; - private JTextField _fontName = null, _altitudeCapField = null; - private int _altitudeCap = ThreeDModel.MINIMUM_ALTITUDE_CAP; + private JTextField _fontName = null, _altitudeFactorField = null; + private JRadioButton _ballsAndSticksButton = null; + private JLabel _baseImageLabel = null; + private JButton _baseImageButton = null; + private BaseImageConfigDialog _baseImageConfig = null; // defaults private static final double DEFAULT_CAMERA_DISTANCE = 30.0; + private static final double MODEL_SCALE_FACTOR = 20.0; private static final String DEFAULT_FONT_FILE = "crystal.ttf"; - // alternative font: DejaVuSans-Bold.ttf /** - * Constructor giving frame and track - * @param inParentFrame parent frame - * @param inTrack track object to save + * Constructor + * @param inApp App object */ - public PovExporter(JFrame inParentFrame, Track inTrack) + public PovExporter(App inApp) { - _parentFrame = inParentFrame; - _track = inTrack; + super(inApp); + _track = inApp.getTrackInfo().getTrack(); // Set default camera coordinates _cameraX = "17"; _cameraY = "13"; _cameraZ = "-20"; } + /** Get the name key */ + public String getNameKey() { + return "function.exportpov"; + } /** * Set the coordinates for the camera (can be any scale) @@ -73,23 +94,10 @@ public class PovExporter double cameraDist = Math.sqrt(inX*inX + inY*inY + inZ*inZ); if (cameraDist > 0.0) { - _cameraX = "" + (inX / cameraDist * DEFAULT_CAMERA_DISTANCE); - _cameraY = "" + (inY / cameraDist * DEFAULT_CAMERA_DISTANCE); + _cameraX = NumberUtils.formatNumberUk(inX / cameraDist * DEFAULT_CAMERA_DISTANCE, 5); + _cameraY = NumberUtils.formatNumberUk(inY / cameraDist * DEFAULT_CAMERA_DISTANCE, 5); // Careful! Need to convert from java3d (right-handed) to povray (left-handed) coordinate system! - _cameraZ = "" + (-inZ / cameraDist * DEFAULT_CAMERA_DISTANCE); - } - } - - - /** - * @param inAltitudeCap altitude cap to use - */ - public void setAltitudeCap(int inAltitudeCap) - { - _altitudeCap = inAltitudeCap; - if (_altitudeCap < ThreeDModel.MINIMUM_ALTITUDE_CAP) - { - _altitudeCap = ThreeDModel.MINIMUM_ALTITUDE_CAP; + _cameraZ = NumberUtils.formatNumberUk(-inZ / cameraDist * DEFAULT_CAMERA_DISTANCE, 5); } } @@ -97,25 +105,30 @@ public class PovExporter /** * Show the dialog to select options and export file */ - public void showDialog() + public void begin() { // Make dialog window to select angles, colours etc if (_dialog == null) { - _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.exportpov.title"), true); + _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true); _dialog.setLocationRelativeTo(_parentFrame); _dialog.getContentPane().add(makeDialogComponents()); } + // Make base image dialog + if (_baseImageConfig == null) + { + _baseImageConfig = new BaseImageConfigDialog(this, _dialog, _track); + } // Set angles _cameraXField.setText(_cameraX); _cameraYField.setText(_cameraY); _cameraZField.setText(_cameraZ); - // Set vertical scale - _altitudeCapField.setText("" + _altitudeCap); + _altitudeFactorField.setText("" + _altFactor); + updateBaseImageDetails(); // Show dialog _dialog.pack(); - _dialog.show(); + _dialog.setVisible(true); } @@ -126,8 +139,10 @@ public class PovExporter private Component makeDialogComponents() { JPanel panel = new JPanel(); - panel.setLayout(new BorderLayout()); - panel.add(new JLabel(I18nManager.getText("dialog.exportpov.text")), BorderLayout.NORTH); + panel.setLayout(new BorderLayout(4, 4)); + JLabel introLabel = new JLabel(I18nManager.getText("dialog.exportpov.text")); + introLabel.setBorder(BorderFactory.createEmptyBorder(4, 4, 6, 4)); + panel.add(introLabel, BorderLayout.NORTH); // OK, Cancel buttons JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); @@ -136,6 +151,7 @@ public class PovExporter public void actionPerformed(ActionEvent e) { doExport(); + MapGrouter.clearMapImage(); _dialog.dispose(); } }); @@ -144,6 +160,7 @@ public class PovExporter cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { + MapGrouter.clearMapImage(); _dialog.dispose(); } }); @@ -157,8 +174,13 @@ public class PovExporter JLabel fontLabel = new JLabel(I18nManager.getText("dialog.exportpov.font")); fontLabel.setHorizontalAlignment(SwingConstants.TRAILING); centralPanel.add(fontLabel); - _fontName = new JTextField(DEFAULT_FONT_FILE, 12); + String defaultFont = Config.getConfigString(Config.KEY_POVRAY_FONT); + if (defaultFont == null || defaultFont.equals("")) { + defaultFont = DEFAULT_FONT_FILE; + } + _fontName = new JTextField(defaultFont, 12); _fontName.setAlignmentX(Component.LEFT_ALIGNMENT); + _fontName.addKeyListener(new DialogCloser(_dialog)); centralPanel.add(_fontName); //coordinates of camera JLabel cameraXLabel = new JLabel(I18nManager.getText("dialog.exportpov.camerax")); @@ -176,35 +198,103 @@ public class PovExporter centralPanel.add(cameraZLabel); _cameraZField = new JTextField("" + _cameraZ); centralPanel.add(_cameraZField); - // Altitude capping - JLabel altitudeCapLabel = new JLabel(I18nManager.getText("dialog.3d.altitudecap")); + // Altitude exaggeration + JLabel altitudeCapLabel = new JLabel(I18nManager.getText("dialog.3d.altitudefactor")); altitudeCapLabel.setHorizontalAlignment(SwingConstants.TRAILING); centralPanel.add(altitudeCapLabel); - _altitudeCapField = new JTextField("" + _altitudeCap); - centralPanel.add(_altitudeCapField); - - JPanel flowPanel = new JPanel(); - flowPanel.add(centralPanel); - - // show lines button - JButton showLinesButton = new JButton(I18nManager.getText("button.showlines")); - showLinesButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) - { - // Need to scale model to find lines - ThreeDModel model = new ThreeDModel(_track); - model.scale(); - double[] latLines = model.getLatitudeLines(); - double[] lonLines = model.getLongitudeLines(); - LineDialog dialog = new LineDialog(_parentFrame, latLines, lonLines); - dialog.showDialog(); + _altitudeFactorField = new JTextField("1.0"); + centralPanel.add(_altitudeFactorField); + + // Radio buttons for style - balls on sticks or tubes + JPanel stylePanel = new JPanel(); + stylePanel.setLayout(new GridLayout(0, 2, 10, 4)); + JLabel styleLabel = new JLabel(I18nManager.getText("dialog.exportpov.modelstyle")); + styleLabel.setHorizontalAlignment(SwingConstants.TRAILING); + stylePanel.add(styleLabel); + JPanel radioPanel = new JPanel(); + radioPanel.setLayout(new BoxLayout(radioPanel, BoxLayout.Y_AXIS)); + _ballsAndSticksButton = new JRadioButton(I18nManager.getText("dialog.exportpov.ballsandsticks")); + _ballsAndSticksButton.setSelected(false); + radioPanel.add(_ballsAndSticksButton); + JRadioButton tubesButton = new JRadioButton(I18nManager.getText("dialog.exportpov.tubesandwalls")); + tubesButton.setSelected(true); + radioPanel.add(tubesButton); + ButtonGroup group = new ButtonGroup(); + group.add(_ballsAndSticksButton); group.add(tubesButton); + stylePanel.add(radioPanel); + + // Panel for the base image + JPanel imagePanel = new JPanel(); + imagePanel.setLayout(new BorderLayout(10, 4)); + imagePanel.add(new JLabel(I18nManager.getText("dialog.exportpov.baseimage") + ": "), BorderLayout.WEST); + _baseImageLabel = new JLabel("Typical sourcename"); + imagePanel.add(_baseImageLabel, BorderLayout.CENTER); + _baseImageButton = new JButton(I18nManager.getText("button.edit")); + _baseImageButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + changeBaseImage(); } }); - flowPanel.add(showLinesButton); - panel.add(flowPanel, BorderLayout.CENTER); + imagePanel.add(_baseImageButton, BorderLayout.EAST); + // Put these image controls inside a holder panel with an outline + JPanel imageHolderPanel = new JPanel(); + imageHolderPanel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(4, 4, 4, 4)) + ); + imageHolderPanel.setLayout(new BorderLayout()); + imageHolderPanel.add(imagePanel, BorderLayout.NORTH); + + // add these panels to the holder panel + JPanel holderPanel = new JPanel(); + holderPanel.setLayout(new BorderLayout(5, 5)); + JPanel boxPanel = new JPanel(); + boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS)); + boxPanel.add(centralPanel); + boxPanel.add(Box.createVerticalStrut(4)); + boxPanel.add(stylePanel); + boxPanel.add(Box.createVerticalStrut(4)); + boxPanel.add(imageHolderPanel); + holderPanel.add(boxPanel, BorderLayout.CENTER); + + panel.add(holderPanel, BorderLayout.CENTER); return panel; } + /** + * Change the base image by calling the BaseImageConfigDialog + */ + private void changeBaseImage() + { + // Check if there is a cache to use + if (BaseImageConfigDialog.isImagePossible()) + { + // Show new dialog to choose image details + _baseImageConfig.begin(); + } + else { + _app.showErrorMessage(getNameKey(), "dialog.exportimage.noimagepossible"); + } + } + + /** + * Update the description label according to the selected base image details + */ + private void updateBaseImageDetails() + { + String desc = null; + if (_baseImageConfig.useImage()) + { + MapSource source = MapSourceLibrary.getSource(_baseImageConfig.getSourceIndex()); + if (source != null) { + desc = source.getName() + " (" + + _baseImageConfig.getZoomLevel() + ")"; + } + } + if (desc == null) { + desc = I18nManager.getText("dialog.about.no"); + } + _baseImageLabel.setText(desc); + } /** * Select the file and export data to it @@ -218,19 +308,15 @@ public class PovExporter // OK pressed, so choose output file if (_fileChooser == null) + { _fileChooser = new JFileChooser(); - _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG); - _fileChooser.setFileFilter(new FileFilter() { - public boolean accept(File f) - { - return (f != null && (f.isDirectory() || f.getName().toLowerCase().endsWith(".pov"))); - } - public String getDescription() - { - return I18nManager.getText("dialog.exportpov.filetype"); - } - }); - _fileChooser.setAcceptAllFileFilterUsed(false); + _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG); + _fileChooser.setFileFilter(new GenericFileFilter("filetype.pov", new String[] {"pov"})); + _fileChooser.setAcceptAllFileFilterUsed(false); + // start from directory in config which should be set + final String configDir = Config.getConfigString(Config.KEY_TRACK_DIR); + if (configDir != null) {_fileChooser.setCurrentDirectory(new File(configDir));} + } // Allow choose again if an existing file is selected boolean chooseAgain = false; @@ -240,23 +326,27 @@ public class PovExporter if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION) { // OK pressed and file chosen - File file = _fileChooser.getSelectedFile(); - if (!file.getName().toLowerCase().endsWith(".pov")) + File povFile = _fileChooser.getSelectedFile(); + if (!povFile.getName().toLowerCase().endsWith(".pov")) { - file = new File(file.getAbsolutePath() + ".pov"); + povFile = new File(povFile.getAbsolutePath() + ".pov"); } - // Check if file exists and if necessary prompt for overwrite + final int nameLen = povFile.getName().length() - 4; + final File imageFile = new File(povFile.getParentFile(), povFile.getName().substring(0, nameLen) + "_base.png"); + final boolean imageExists = _baseImageConfig.useImage() && imageFile.exists(); + // Check if files exist and if necessary prompt for overwrite Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")}; - if (!file.exists() || JOptionPane.showOptionDialog(_parentFrame, + if ((!povFile.exists() && !imageExists) || JOptionPane.showOptionDialog(_parentFrame, I18nManager.getText("dialog.save.overwrite.text"), I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1]) == JOptionPane.YES_OPTION) { // Export the file - if (exportFile(file)) + if (exportFile(povFile, imageFile)) { - // file saved + // file saved - store directory in config for later + Config.setConfigString(Config.KEY_TRACK_DIR, povFile.getParentFile().getAbsolutePath()); } else { @@ -276,47 +366,71 @@ public class PovExporter /** * Export the track data to the specified file - * @param inFile File object to save to + * @param inPovFile File object to save pov file to + * @param inImageFile file object to save image to * @return true if successful */ - private boolean exportFile(File inFile) + private boolean exportFile(File inPovFile, File inImageFile) { FileWriter writer = null; // find out the line separator for this system - String lineSeparator = System.getProperty("line.separator"); + final String lineSeparator = System.getProperty("line.separator"); try { // create and scale model ThreeDModel model = new ThreeDModel(_track); + model.setModelSize(MODEL_SCALE_FACTOR); try { // try to use given altitude cap - _altitudeCap = Integer.parseInt(_altitudeCapField.getText()); - model.setAltitudeCap(_altitudeCap); + double altFactor = Double.parseDouble(_altitudeFactorField.getText()); + model.setAltitudeFactor(altFactor); + } + catch (NumberFormatException nfe) { // parse failed, reset + _altitudeFactorField.setText("1.0"); } - catch (NumberFormatException nfe) {} model.scale(); - // Create file and write basics - writer = new FileWriter(inFile); - writeStartOfFile(writer, model.getModelSize(), lineSeparator); + boolean useImage = _baseImageConfig.useImage(); + if (useImage) + { + // Get base image from grouter + MapSource mapSource = MapSourceLibrary.getSource(_baseImageConfig.getSourceIndex()); + GroutedImage baseImage = MapGrouter.getMapImage(_track, mapSource, _baseImageConfig.getZoomLevel()); + try + { + useImage = ImageIO.write(baseImage.getImage(), "png", inImageFile); + } + catch (IOException ioe) { + System.err.println("Can't write image: " + ioe.getClass().getName()); + useImage = false; + } + if (!useImage) { + _app.showErrorMessage(getNameKey(), "dialog.exportpov.cannotmakebaseimage"); + } + } - // write out lat/long lines using model - writeLatLongLines(writer, model, lineSeparator); + // Create file and write basics + writer = new FileWriter(inPovFile); + writeStartOfFile(writer, lineSeparator, useImage ? inImageFile : null); // write out points - writeDataPoints(writer, model, lineSeparator); + if (_ballsAndSticksButton.isSelected()) { + writeDataPointsBallsAndSticks(writer, model, lineSeparator); + } + else { + writeDataPointsTubesAndWalls(writer, model, lineSeparator); + } // everything worked - JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.save.ok1") - + " " + _track.getNumPoints() + " " + I18nManager.getText("dialog.save.ok2") - + " " + inFile.getAbsolutePath(), - I18nManager.getText("dialog.save.oktitle"), JOptionPane.INFORMATION_MESSAGE); + UpdateMessageBroker.informSubscribers(I18nManager.getText("confirm.save.ok1") + + " " + _track.getNumPoints() + " " + I18nManager.getText("confirm.save.ok2") + + " " + inPovFile.getAbsolutePath()); return true; } catch (IOException ioe) { - JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.save.failed") + ioe.getMessage(), + JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.save.failed") + " : " + ioe.getMessage(), I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE); } finally @@ -335,14 +449,14 @@ public class PovExporter /** * Write the start of the Pov file, including base plane and lights * @param inWriter Writer to use for writing file - * @param inModelSize model size * @param inLineSeparator line separator to use + * @param inImageFile image file to reference (or null if none) * @throws IOException on file writing error */ - private void writeStartOfFile(FileWriter inWriter, double inModelSize, String inLineSeparator) + private void writeStartOfFile(FileWriter inWriter, String inLineSeparator, File inImageFile) throws IOException { - inWriter.write("// Pov file produced by Prune - see http://activityworkshop.net/"); + inWriter.write("// Pov file produced by GpsPrune - see http://activityworkshop.net/"); inWriter.write(inLineSeparator); inWriter.write(inLineSeparator); // Select font based on user input @@ -351,6 +465,23 @@ public class PovExporter { fontPath = DEFAULT_FONT_FILE; } + else { + Config.setConfigString(Config.KEY_POVRAY_FONT, fontPath); + } + + // Make the definition of the base plane depending on whether there's an image or not + final boolean useImage = (inImageFile != null); + final String boxDefinition = (inImageFile == null ? + " <-10.0, -0.15, -10.0>," + inLineSeparator + + " <10.0, 0.15, 10.0>" + inLineSeparator + + " pigment { color rgb <0.5 0.75 0.8> }" + : + " <0, 0, 0>, <1, 1, 0.001>" + inLineSeparator + + " pigment {image_map { png \"" + inImageFile.getName() + "\" map_type 0 interpolate 2 once } }" + inLineSeparator + + " scale 20.0 rotate <90, 0, 0>" + inLineSeparator + + " translate <-10.0, 0, -10.0>"); + // TODO: Maybe could use the same geometry for the imageless case, would simplify code a bit + // Set up output String[] outputLines = { "global_settings { ambient_light rgb <4, 4, 4> }", "", @@ -363,42 +494,30 @@ public class PovExporter "}", "", // global declares "// Global declares", - "#declare lat_line =", - " cylinder {", - " <-" + inModelSize + ", 0.1, 0>,", - " <" + inModelSize + ", 0.1, 0>,", - " 0.1 // Radius", - " pigment { color rgb <0.5 0.5 0.5> }", - " }", - "#declare lon_line =", - " cylinder {", - " <0, 0.1, -" + inModelSize + ">,", - " <0, 0.1, " + inModelSize + ">,", - " 0.1 // Radius", - " pigment { color rgb <0.5 0.5 0.5> }", - " }", "#declare point_rod =", " cylinder {", " <0, 0, 0>,", " <0, 1, 0>,", " 0.15", " open", - " pigment { color rgb <0.5 0.5 0.5> }", + " texture {", + " pigment { color rgb <0.5 0.5 0.5> }", + useImage ? " } no_shadow" : " }", " }", "", - // TODO: Export rods to POV? How to store in data? + // MAYBE: Export rods to POV? How to store in data? "#declare waypoint_sphere =", " sphere {", " <0, 0, 0>, 0.4", " texture {", " pigment {color rgb <0.1 0.1 1.0>}", " finish { phong 1 }", - " }", + useImage ? " } no_shadow" : " }", " }", "#declare track_sphere0 =", " sphere {", " <0, 0, 0>, 0.3", // size should depend on model size " texture {", - " pigment {color rgb <0.2 1.0 0.2>}", + " pigment {color rgb <0.1 0.6 0.1>}", // dark green " finish { phong 1 }", " }", " }", @@ -406,7 +525,7 @@ public class PovExporter " sphere {", " <0, 0, 0>, 0.3", // size should depend on model size " texture {", - " pigment {color rgb <0.6 1.0 0.2>}", + " pigment {color rgb <0.4 0.9 0.2>}", // green " finish { phong 1 }", " }", " }", @@ -414,7 +533,7 @@ public class PovExporter " sphere {", " <0, 0, 0>, 0.3", // size should depend on model size " texture {", - " pigment {color rgb <1.0 1.0 0.1>}", + " pigment {color rgb <0.7 0.8 0.2>}", // yellow " finish { phong 1 }", " }", " }", @@ -422,7 +541,7 @@ public class PovExporter " sphere {", " <0, 0, 0>, 0.3", // size should depend on model size " texture {", - " pigment {color rgb <1.0 1.0 1.0>}", + " pigment {color rgb <0.5 0.8 0.6>}", // greeny " finish { phong 1 }", " }", " }", @@ -430,39 +549,54 @@ public class PovExporter " sphere {", " <0, 0, 0>, 0.3", // size should depend on model size " texture {", - " pigment {color rgb <0.1 1.0 1.0>}", + " pigment {color rgb <0.2 0.9 0.9>}", // cyan " finish { phong 1 }", " }", - " }", "", + " }", + "#declare track_sphere5 =", + " sphere {", + " <0, 0, 0>, 0.3", // size should depend on model size + " texture {", + " pigment {color rgb <1.0 1.0 1.0>}", // white + " finish { phong 1 }", + " }", + " }", + "#declare track_sphere_t =", + " sphere {", + " <0, 0, 0>, 0.25", // size should depend on model size + " texture {", + " pigment {color rgb <0.6 1.0 0.2>}", + " finish { phong 1 }", + " } no_shadow", + " }", + "#declare wall_colour = rgbt <0.5, 0.5, 0.5, 0.3>;", "", "// Base plane", "box {", - " <-" + inModelSize + ", -0.15, -" + inModelSize + ">, // Near lower left corner", - " <" + inModelSize + ", 0.15, " + inModelSize + "> // Far upper right corner", - " pigment { color rgb <0.5 0.75 0.8> }", + boxDefinition, "}", "", // write cardinals "// Cardinal letters N,S,E,W", "text {", " ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.n") + "\" 0.3, 0", " pigment { color rgb <1 1 1> }", - " translate <0, 0.2, " + inModelSize + ">", + " translate <0, 0.2, 10.0>", "}", "text {", " ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.s") + "\" 0.3, 0", " pigment { color rgb <1 1 1> }", - " translate <0, 0.2, -" + inModelSize + ">", + " translate <0, 0.2, -10.0>", "}", "text {", " ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.e") + "\" 0.3, 0", " pigment { color rgb <1 1 1> }", - " translate <" + (inModelSize * 0.97) + ", 0.2, 0>", + " translate <9.7, 0.2, 0>", "}", "text {", " ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.w") + "\" 0.3, 0", " pigment { color rgb <1 1 1> }", - " translate <-" + (inModelSize * 1.03) + ", 0.2, 0>", + " translate <-10.3, 0.2, 0>", "}", "", - // TODO: Light positions should relate to model size + // MAYBE: Light positions should relate to model size "// lights", "light_source { <-1, 9, -4> color rgb <0.5 0.5 0.5>}", "light_source { <1, 6, -14> color rgb <0.6 0.6 0.6>}", @@ -480,74 +614,197 @@ public class PovExporter /** - * Write out all the lat and long lines to the file + * Write out all the data points to the file in the balls-and-sticks style * @param inWriter Writer to use for writing file - * @param inModel model object for getting lat/long lines + * @param inModel model object for getting data points * @param inLineSeparator line separator to use * @throws IOException on file writing error */ - private void writeLatLongLines(FileWriter inWriter, ThreeDModel inModel, String inLineSeparator) + private static void writeDataPointsBallsAndSticks(FileWriter inWriter, ThreeDModel inModel, String inLineSeparator) throws IOException { - inWriter.write("// Latitude and longitude lines:"); + inWriter.write("// Data points:"); inWriter.write(inLineSeparator); - int numlines = inModel.getLatitudeLines().length; - for (int i=0; i }"); - inWriter.write(inLineSeparator); - } - numlines = inModel.getLongitudeLines().length; - for (int i=0; i }"); + // ball (different according to type) + if (inModel.getPointType(i) == ThreeDModel.POINT_TYPE_WAYPOINT) + { + // waypoint ball + inWriter.write("object { waypoint_sphere translate <" + inModel.getScaledHorizValue(i) + + "," + inModel.getScaledAltValue(i) + "," + inModel.getScaledVertValue(i) + "> }"); + } + else + { + // normal track point ball + inWriter.write("object { track_sphere" + checkHeightCode(inModel.getPointHeightCode(i)) + + " translate <" + inModel.getScaledHorizValue(i) + "," + inModel.getScaledAltValue(i) + + "," + inModel.getScaledVertValue(i) + "> }"); + } inWriter.write(inLineSeparator); + // vertical rod (if altitude positive) + if (inModel.getScaledAltValue(i) > 0.0) + { + inWriter.write("object { point_rod translate <" + inModel.getScaledHorizValue(i) + ",0," + + inModel.getScaledVertValue(i) + "> scale <1," + inModel.getScaledAltValue(i) + ",1> }"); + inWriter.write(inLineSeparator); + } } inWriter.write(inLineSeparator); } /** - * Write out all the data points to the file + * Write out all the data points to the file in the tubes-and-walls style * @param inWriter Writer to use for writing file * @param inModel model object for getting data points * @param inLineSeparator line separator to use * @throws IOException on file writing error */ - private void writeDataPoints(FileWriter inWriter, ThreeDModel inModel, String inLineSeparator) + private static void writeDataPointsTubesAndWalls(FileWriter inWriter, ThreeDModel inModel, String inLineSeparator) throws IOException { inWriter.write("// Data points:"); inWriter.write(inLineSeparator); int numPoints = inModel.getNumPoints(); + int numTrackPoints = 0; + // Loop over all points and write out waypoints as balls for (int i=0; i }"); + // vertical rod (if altitude positive) + if (inModel.getScaledAltValue(i) > 0.0) + { + inWriter.write(inLineSeparator); + inWriter.write("object { point_rod translate <" + inModel.getScaledHorizValue(i) + ",0," + + inModel.getScaledVertValue(i) + "> scale <1," + inModel.getScaledAltValue(i) + ",1> }"); + } + inWriter.write(inLineSeparator); + } + else {numTrackPoints++;} + } + inWriter.write(inLineSeparator); + + // Loop over all the track segments + ArrayList segmentList = getSegmentList(inModel); + Iterator segmentIterator = segmentList.iterator(); + while (segmentIterator.hasNext()) + { + ModelSegment segment = segmentIterator.next(); + int segLength = segment.getNumTrackPoints(); + + // if the track segment is long enough, do a cubic spline sphere sweep + if (segLength <= 1) + { + // single point in segment - just draw sphere + int index = segment.getStartIndex(); + inWriter.write("object { track_sphere_t" + + " translate <" + inModel.getScaledHorizValue(index) + "," + inModel.getScaledAltValue(index) + + "," + inModel.getScaledVertValue(index) + "> }"); + // maybe draw some kind of polygon too or rod? } else { - // normal track point ball - inWriter.write("object { track_sphere" + checkHeightCode(inModel.getPointHeightCode(i)) - + " translate <" + inModel.getScaledHorizValue(i) + "," + inModel.getScaledAltValue(i) - + "," + inModel.getScaledVertValue(i) + "> }"); + writeSphereSweep(inWriter, inModel, segment, inLineSeparator); } - inWriter.write(inLineSeparator); - // vertical rod (if altitude positive) - if (inModel.getScaledAltValue(i) > 0.0) + + // Write wall underneath segment + if (segLength > 1) { - inWriter.write("object { point_rod translate <" + inModel.getScaledHorizValue(i) + ",0," - + inModel.getScaledVertValue(i) + "> scale <1," + inModel.getScaledAltValue(i) + ",1> }"); + writePolygonWall(inWriter, inModel, segment, inLineSeparator); + } + } + } + + + /** + * Write out a single sphere sweep using either cubic spline or linear spline + * @param inWriter Writer to use for writing file + * @param inModel model object for getting data points + * @param inSegment model segment to draw + * @param inLineSeparator line separator to use + * @throws IOException on file writing error + */ + private static void writeSphereSweep(FileWriter inWriter, ThreeDModel inModel, ModelSegment inSegment, String inLineSeparator) + throws IOException + { + // 3d sphere sweep + inWriter.write("// Sphere sweep:"); + inWriter.write(inLineSeparator); + String splineType = inSegment.getNumTrackPoints() < 5?"linear_spline":"cubic_spline"; + inWriter.write("sphere_sweep { "); inWriter.write(splineType); + inWriter.write(" " + inSegment.getNumTrackPoints() + ","); + inWriter.write(inLineSeparator); + // Loop over all points in this segment and write out sphere sweep + for (int i=inSegment.getStartIndex(); i<=inSegment.getEndIndex(); i++) + { + if (inModel.getPointType(i) != ThreeDModel.POINT_TYPE_WAYPOINT) + { + inWriter.write(" <" + inModel.getScaledHorizValue(i) + "," + inModel.getScaledAltValue(i) + + "," + inModel.getScaledVertValue(i) + ">, 0.25"); inWriter.write(inLineSeparator); } } + inWriter.write(" tolerance 0.1"); inWriter.write(inLineSeparator); + inWriter.write(" texture { pigment {color rgb <0.6 1.0 0.2>} finish {phong 1} }"); + inWriter.write(inLineSeparator); + inWriter.write(" no_shadow"); + inWriter.write(inLineSeparator); + inWriter.write("}"); + inWriter.write(inLineSeparator); + } + + + /** + * Write out a single polygon-based wall for the tubes-and-walls style + * @param inWriter Writer to use for writing file + * @param inModel model object for getting data points + * @param inSegment model segment to draw + * @param inLineSeparator line separator to use + * @throws IOException on file writing error + */ + private static void writePolygonWall(FileWriter inWriter, ThreeDModel inModel, ModelSegment inSegment, String inLineSeparator) + throws IOException + { + // wall + inWriter.write(inLineSeparator); + inWriter.write("// wall between sweep and floor:"); + inWriter.write(inLineSeparator); + // Loop over all points in this segment again and write out polygons + int prevIndex = -1; + for (int i=inSegment.getStartIndex(); i<=inSegment.getEndIndex(); i++) + { + if (inModel.getPointType(i) != ThreeDModel.POINT_TYPE_WAYPOINT) + { + if (prevIndex >= 0) + { + double xDiff = inModel.getScaledHorizValue(i) - inModel.getScaledHorizValue(prevIndex); + double yDiff = inModel.getScaledVertValue(i) - inModel.getScaledVertValue(prevIndex); + double dist = Math.sqrt(xDiff * xDiff + yDiff * yDiff); + if (dist > 0) + { + inWriter.write("polygon {"); + inWriter.write(" 5, <" + inModel.getScaledHorizValue(prevIndex) + ", 0.0, " + inModel.getScaledVertValue(prevIndex) + ">,"); + inWriter.write(" <" + inModel.getScaledHorizValue(prevIndex) + ", " + inModel.getScaledAltValue(prevIndex) + ", " + + inModel.getScaledVertValue(prevIndex) + ">,"); + inWriter.write(" <" + inModel.getScaledHorizValue(i) + ", " + inModel.getScaledAltValue(i) + ", " + + inModel.getScaledVertValue(i) + ">,"); + inWriter.write(" <" + inModel.getScaledHorizValue(i) + ", 0.0, " + inModel.getScaledVertValue(i) + ">,"); + inWriter.write(" <" + inModel.getScaledHorizValue(prevIndex) + ", 0.0, " + inModel.getScaledVertValue(prevIndex) + ">"); + inWriter.write(" pigment { color wall_colour } no_shadow"); + inWriter.write("}"); + inWriter.write(inLineSeparator); + } + } + prevIndex = i; + } + } } @@ -557,7 +814,7 @@ public class PovExporter */ private static byte checkHeightCode(byte inCode) { - final byte maxHeightCode = 4; + final byte maxHeightCode = 5; if (inCode < 0) return 0; if (inCode > maxHeightCode) return maxHeightCode; return inCode; @@ -579,4 +836,58 @@ public class PovExporter catch (Exception e) {} // ignore parse failures return "" + value; } + + /** + * Go through the points making a list of the segment starts and the number of track points in each segment + * @param inModel model containing data + * @return list of ModelSegment objects + */ + private static ArrayList getSegmentList(ThreeDModel inModel) + { + ArrayList segmentList = new ArrayList(); + if (inModel != null && inModel.getNumPoints() > 0) + { + ModelSegment currSegment = null; + int numTrackPoints = 0; + for (int i=0; i 0) + { + currSegment.setEndIndex(inModel.getNumPoints()-1); + currSegment.setNumTrackPoints(numTrackPoints); + segmentList.add(currSegment); + } + } + return segmentList; + } + + /** + * Callback from base image config dialog + */ + public void dataUpdated(byte inUpdateType) + { + updateBaseImageDetails(); + } + + /** Not required */ + public void actionCompleted(String inMessage) { + } }