X-Git-Url: https://gitweb.fperrin.net/?a=blobdiff_plain;ds=sidebyside;f=tim%2Fprune%2Fsave%2FPovExporter.java;fp=tim%2Fprune%2Fsave%2FPovExporter.java;h=c1f90fd24667003a9f3fa29d9c51d5b0c923d076;hb=23959e65a6a0d581e657b07186d18b7a1ac5afeb;hp=0000000000000000000000000000000000000000;hpb=d3679d647d57c2ee7376ddbf6def2d5b23c04307;p=GpsPrune.git diff --git a/tim/prune/save/PovExporter.java b/tim/prune/save/PovExporter.java new file mode 100644 index 0000000..c1f90fd --- /dev/null +++ b/tim/prune/save/PovExporter.java @@ -0,0 +1,570 @@ +package tim.prune.save; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +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.JTextField; +import javax.swing.SwingConstants; +import javax.swing.filechooser.FileFilter; + +import tim.prune.App; +import tim.prune.I18nManager; +import tim.prune.data.Track; +import tim.prune.threedee.ThreeDModel; + +/** + * Class to export track information + * into a specified Pov file + */ +public class PovExporter +{ + private App _app = null; + 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; + + // defaults + private static final double DEFAULT_CAMERA_DISTANCE = 30.0; + private static final String DEFAULT_FONT_FILE = "crystal.ttf"; + // alternative font: DejaVuSans-Bold.ttf + + + /** + * Constructor giving App object, frame and track + * @param inApp application object to inform of success + * @param inParentFrame parent frame + * @param inTrack track object to save + */ + public PovExporter(App inApp, JFrame inParentFrame, Track inTrack) + { + _app = inApp; + _parentFrame = inParentFrame; + _track = inTrack; + // Set default camera coordinates + _cameraX = "17"; _cameraY = "13"; _cameraZ = "-20"; + } + + + /** + * Set the coordinates for the camera (can be any scale) + * @param inX X coordinate of camera + * @param inY Y coordinate of camera + * @param inZ Z coordinate of camera + */ + public void setCameraCoordinates(double inX, double inY, double inZ) + { + // calculate distance from origin + 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); + // 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; + } + } + + + /** + * Show the dialog to select options and export file + */ + public void showDialog() + { + // Make dialog window to select angles, colours etc + if (_dialog == null) + { + _dialog = new JDialog(_parentFrame, I18nManager.getText("dialog.exportpov.title"), true); + _dialog.setLocationRelativeTo(_parentFrame); + _dialog.getContentPane().add(makeDialogComponents()); + } + + // Set angles + _cameraXField.setText(_cameraX); + _cameraYField.setText(_cameraY); + _cameraZField.setText(_cameraZ); + // Set vertical scale + _altitudeCapField.setText("" + _altitudeCap); + // Show dialog + _dialog.pack(); + _dialog.show(); + } + + + /** + * Make the dialog components to select the export options + * @return Component holding gui elements + */ + private Component makeDialogComponents() + { + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.add(new JLabel(I18nManager.getText("dialog.exportpov.text")), BorderLayout.NORTH); + // OK, Cancel buttons + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); + JButton okButton = new JButton(I18nManager.getText("button.ok")); + okButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + doExport(); + _dialog.dispose(); + } + }); + buttonPanel.add(okButton); + JButton cancelButton = new JButton(I18nManager.getText("button.cancel")); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + _dialog.dispose(); + } + }); + buttonPanel.add(cancelButton); + panel.add(buttonPanel, BorderLayout.SOUTH); + + // central panel + JPanel centralPanel = new JPanel(); + centralPanel.setLayout(new GridLayout(0, 2, 10, 4)); + + JLabel fontLabel = new JLabel(I18nManager.getText("dialog.exportpov.font")); + fontLabel.setHorizontalAlignment(SwingConstants.TRAILING); + centralPanel.add(fontLabel); + _fontName = new JTextField(DEFAULT_FONT_FILE, 12); + _fontName.setAlignmentX(Component.LEFT_ALIGNMENT); + centralPanel.add(_fontName); + //coordinates of camera + JLabel cameraXLabel = new JLabel(I18nManager.getText("dialog.exportpov.camerax")); + cameraXLabel.setHorizontalAlignment(SwingConstants.TRAILING); + centralPanel.add(cameraXLabel); + _cameraXField = new JTextField("" + _cameraX); + centralPanel.add(_cameraXField); + JLabel cameraYLabel = new JLabel(I18nManager.getText("dialog.exportpov.cameray")); + cameraYLabel.setHorizontalAlignment(SwingConstants.TRAILING); + centralPanel.add(cameraYLabel); + _cameraYField = new JTextField("" + _cameraY); + centralPanel.add(_cameraYField); + JLabel cameraZLabel = new JLabel(I18nManager.getText("dialog.exportpov.cameraz")); + cameraZLabel.setHorizontalAlignment(SwingConstants.TRAILING); + centralPanel.add(cameraZLabel); + _cameraZField = new JTextField("" + _cameraZ); + centralPanel.add(_cameraZField); + // Altitude capping + JLabel altitudeCapLabel = new JLabel(I18nManager.getText("dialog.3d.altitudecap")); + altitudeCapLabel.setHorizontalAlignment(SwingConstants.TRAILING); + centralPanel.add(altitudeCapLabel); + _altitudeCapField = new JTextField("" + _altitudeCap); + centralPanel.add(_altitudeCapField); + + JPanel flowPanel = new JPanel(); + flowPanel.add(centralPanel); + panel.add(flowPanel, BorderLayout.CENTER); + return panel; + } + + + /** + * Select the file and export data to it + */ + private void doExport() + { + // Copy camera coordinates + _cameraX = checkCoordinate(_cameraXField.getText()); + _cameraY = checkCoordinate(_cameraYField.getText()); + _cameraZ = checkCoordinate(_cameraZField.getText()); + + // OK pressed, so choose output file + boolean fileSaved = false; + 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); + + // Allow choose again if an existing file is selected + boolean chooseAgain = false; + do + { + chooseAgain = false; + if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION) + { + // OK pressed and file chosen + File file = _fileChooser.getSelectedFile(); + if (!file.getName().toLowerCase().endsWith(".pov")) + { + file = new File(file.getAbsolutePath() + ".pov"); + } + // Check if file exists and if necessary prompt for overwrite + Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")}; + if (!file.exists() || 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)) + { + fileSaved = true; + } + else + { + // export failed so need to choose again + chooseAgain = true; + } + } + else + { + // overwrite cancelled so need to choose again + chooseAgain = true; + } + } + } while (chooseAgain); + } + + + /** + * Export the track data to the specified file + * @param inFile File object to save to + * @return true if successful + */ + private boolean exportFile(File inFile) + { + FileWriter writer = null; + // find out the line separator for this system + String lineSeparator = System.getProperty("line.separator"); + try + { + // create and scale model + ThreeDModel model = new ThreeDModel(_track); + try + { + // try to use given altitude cap + _altitudeCap = Integer.parseInt(_altitudeCapField.getText()); + model.setAltitudeCap(_altitudeCap); + } + catch (NumberFormatException nfe) {} + model.scale(); + + // Create file and write basics + writer = new FileWriter(inFile); + writeStartOfFile(writer, model.getModelSize(), lineSeparator); + + // write out lat/long lines using model + writeLatLongLines(writer, model, lineSeparator); + + // write out points + writeDataPoints(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); + return true; + } + catch (IOException ioe) + { + JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("error.save.failed") + ioe.getMessage(), + I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE); + } + finally + { + // close file ignoring exceptions + try + { + writer.close(); + } + catch (Exception e) {} + } + return false; + } + + + /** + * 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 + * @throws IOException on file writing error + */ + private void writeStartOfFile(FileWriter inWriter, double inModelSize, String inLineSeparator) + throws IOException + { + inWriter.write("// Pov file produced by Prune - see http://activityworkshop.net/"); + inWriter.write(inLineSeparator); + inWriter.write(inLineSeparator); + // Select font based on user input + String fontPath = _fontName.getText(); + if (fontPath == null || fontPath.equals("")) + { + fontPath = DEFAULT_FONT_FILE; + } + // Set up output + String[] outputLines = { + "global_settings { ambient_light rgb <4, 4, 4> }", "", + "// Background and camera", + "background { color rgb <0, 0, 0> }", + // camera position + "camera {", + " location <" + _cameraX + ", " + _cameraY + ", " + _cameraZ + ">", + " look_at <0, 0, 0>", + "}", "", + // 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> }", + " }", "", + // TODO: 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 }", + " }", + " }", + "#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>}", + " finish { phong 1 }", + " }", + " }", + "#declare track_sphere1 =", + " sphere {", + " <0, 0, 0>, 0.3", // size should depend on model size + " texture {", + " pigment {color rgb <0.6 1.0 0.2>}", + " finish { phong 1 }", + " }", + " }", + "#declare track_sphere2 =", + " sphere {", + " <0, 0, 0>, 0.3", // size should depend on model size + " texture {", + " pigment {color rgb <1.0 1.0 0.1>}", + " finish { phong 1 }", + " }", + " }", + "#declare track_sphere3 =", + " sphere {", + " <0, 0, 0>, 0.3", // size should depend on model size + " texture {", + " pigment {color rgb <1.0 1.0 1.0>}", + " finish { phong 1 }", + " }", + " }", + "#declare track_sphere4 =", + " sphere {", + " <0, 0, 0>, 0.3", // size should depend on model size + " texture {", + " pigment {color rgb <0.1 1.0 1.0>}", + " finish { phong 1 }", + " }", + " }", "", + "// 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> }", + "}", "", + // 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 + ">", + "}", + "text {", + " ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.s") + "\" 0.3, 0", + " pigment { color rgb <1 1 1> }", + " translate <0, 0.2, -" + inModelSize + ">", + "}", + "text {", + " ttf \"" + fontPath + "\" \"" + I18nManager.getText("cardinal.e") + "\" 0.3, 0", + " pigment { color rgb <1 1 1> }", + " translate <" + (inModelSize * 0.97) + ", 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>", + "}", "", + // TODO: 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>}", + "light_source { <11, 12, 8> color rgb <0.3 0.3 0.3>}", + "", + }; + // write strings to file + int numLines = outputLines.length; + for (int i=0; i }"); + inWriter.write(inLineSeparator); + } + numlines = inModel.getNumLongitudeLines(); + for (int i=0; i }"); + inWriter.write(inLineSeparator); + } + inWriter.write(inLineSeparator); + } + + + /** + * Write out all the data points to the file + * @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) + throws IOException + { + inWriter.write("// Data points:"); + inWriter.write(inLineSeparator); + int numPoints = inModel.getNumPoints(); + for (int i=0; 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); + } + + + /** + * @param inCode height code to check + * @return validated height code within range 0 to max + */ + private static byte checkHeightCode(byte inCode) + { + final byte maxHeightCode = 4; + if (inCode < 0) return 0; + if (inCode > maxHeightCode) return maxHeightCode; + return inCode; + } + + + /** + * Check the given coordinate + * @param inString String entered by user + * @return validated String value + */ + private static String checkCoordinate(String inString) + { + double value = 0.0; + try + { + value = Double.parseDouble(inString); + } + catch (Exception e) {} // ignore parse failures + return "" + value; + } +}