package com.vitco.layout.content.menu; import com.jidesoft.action.DefaultDockableBarDockableHolder; import com.sun.imageio.plugins.gif.GIFImageReader; import com.sun.imageio.plugins.gif.GIFImageReaderSpi; import com.vitco.core.data.container.Voxel; import com.vitco.export.*; import com.vitco.export.collada.ColladaExportWrapper; import com.vitco.export.generic.ExportDataManager; import com.vitco.importer.*; import com.vitco.layout.content.mainview.MainView; import com.vitco.low.CubeIndexer; import com.vitco.low.hull.HullManagerExt; import com.vitco.manager.action.types.StateActionPrototype; import com.vitco.settings.VitcoSettings; import com.vitco.util.components.dialog.UserInputDialog; import com.vitco.util.components.dialog.UserInputDialogListener; import com.vitco.util.components.dialog.components.*; import com.vitco.util.components.progressbar.ProgressDialog; import com.vitco.util.components.progressbar.ProgressWorker; import com.vitco.util.file.FileTools; import com.vitco.util.misc.CFileDialog; import com.vitco.util.misc.ColorTools; import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.stream.ImageInputStream; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.io.*; import java.util.ArrayList; /** * Handles the main menu logic. */ public class MainMenuLogic extends MenuLogicPrototype implements MenuLogicInterface { // var & setter protected MainView mainView; @Autowired(required=true) public final void setMainView(MainView mainView) { this.mainView = mainView; } // util for save/load/new file // ====================================== // the location of active file (or null if none active) private String location = null; public final void setSaveLocation(String location) { this.location = location; for (ActionListener el : saveLocationListener) { el.actionPerformed(new ActionEvent(this, 0, hasSaveLocation() ? getSaveLocation().getName() : null )); } } public final boolean hasSaveLocation() { return this.location != null; } public final File getSaveLocation() { return hasSaveLocation() ? new File(this.location) : null; } private ArrayList<ActionListener > saveLocationListener = new ArrayList<ActionListener >(); public final void addSaveLocationListener(ActionListener listener) { saveLocationListener.add(listener); } public final void removeSaveLocationListener(ActionListener listener) { saveLocationListener.remove(listener); } // the file chooser private final CFileDialog fc_vsd = new CFileDialog(); // import file chooser private final CFileDialog fc_import = new CFileDialog(); // save file prompt (and overwrite prompt): true iff save was successful private boolean handleSaveDialog(Frame frame) { boolean result = false; File saveTo = fc_vsd.saveFile(frame); if (saveTo != null) { // make sure filename ends with *.vsd String dir = saveTo.getPath(); // query if file already exists if (!saveTo.exists() || JOptionPane.showConfirmDialog(frame, dir + " " + langSelector.getString("replace_file_query"), langSelector.getString("replace_file_query_title"), JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) { // save file and remember it result = data.saveToFile(saveTo); if (result) { setSaveLocation(dir); } } } return result; } // handles unsaved changes: true iff we are save to discard after this was called private boolean checkUnsavedChanges(Frame frame) { boolean result = false; if (data.hasChanged()) { // option to save changes / erase changes / cancel switch (JOptionPane.showConfirmDialog(frame, langSelector.getString("save_current_changes_query"), langSelector.getString("save_current_changes_title"), JOptionPane.YES_NO_CANCEL_OPTION)) { case JOptionPane.YES_OPTION: // save changes if (hasSaveLocation()) { // we already know where to save (ok) File file = getSaveLocation(); result = data.saveToFile(file); } else { // we dont know where if (handleSaveDialog(frame)) { result = true; } } break; case JOptionPane.NO_OPTION: // don't save option result = true; break; case JOptionPane.CANCEL_OPTION: // cancel = do nothing // cancel break; default: break; } } else { // no unsaved changes result = true; } return result; } // import an image file private void importImage(BufferedImage img) { int width = img.getWidth(); int height = img.getHeight(); boolean stop = false; int voxelCount = 1; for (int y=height-1; y >= 0 && !stop; y--) { for (int x = 0; x < width && !stop; x++) { int rgb = img.getRGB(x,y); int alpha = (rgb & 0xff000000) >> 24; int red = (rgb & 0x00ff0000) >> 16; int green = (rgb & 0x0000ff00) >> 8; int blue = rgb & 0x000000ff; if (alpha != 0) { data.addVoxelDirect(new Color(red, green, blue), new int[]{ x - img.getWidth() / 2, y + Math.round(VitcoSettings.VOXEL_GROUND_DISTANCE / VitcoSettings.VOXEL_SIZE) - img.getHeight(), 0 }); if (voxelCount >= VitcoSettings.MAX_VOXEL_COUNT_PER_LAYER) { stop = true; console.addLine( langSelector.getString("import_voxel_limit_reached_pre") + " " + VitcoSettings.MAX_VOXEL_COUNT_PER_LAYER + " " + langSelector.getString("import_voxel_limit_reached_post")); } voxelCount++; } } } } // import helper for voxel file private void importVoxelData(AbstractImporter importer, boolean shiftToCenter) { if (importer.hasLoaded()) { if (shiftToCenter) { int[] center = importer.getWeightedCenter(); int[] highest = importer.getHighest(); for (AbstractImporter.Layer layer : importer.getVoxel()) { int layerId = data.createLayer(layer.name); data.selectLayer(layerId); data.setVisible(layerId, layer.isVisible()); for (int[] vox; layer.hasNext();) { vox = layer.next(); data.addVoxelDirect(new Color(vox[3]), new int[] {vox[0] - center[0], vox[1] - highest[1], vox[2] - center[2]}); } } } else { for (AbstractImporter.Layer layer : importer.getVoxel()) { int layerId = data.createLayer(layer.name); data.selectLayer(layerId); data.setVisible(layerId, layer.isVisible()); for (int[] vox; layer.hasNext();) { vox = layer.next(); data.addVoxelDirect(new Color(vox[3]),new int[] {vox[0], vox[1], vox[2]}); } } } } } // ====================================== public final void openFile(File file) { if (!data.loadFromFile(file)) { console.addLine(langSelector.getString("error_on_file_load")); } setSaveLocation(file.getPath()); // remember load location } public final void registerLogic(final Frame frame) { // initialize the filter fc_vsd.addFileType("vsd", "PS4k File"); // save file actionManager.registerAction("save_file_action", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { handleSaveDialog(frame); } }); // load file actionManager.registerAction("load_file_action", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (checkUnsavedChanges(frame)) { File toOpen = fc_vsd.openFile(frame); if (toOpen != null) { openFile(toOpen); } } } }); fc_import.addFileType(new String[] {"png", "jpg", "jpeg", "bmp"}, "Image"); fc_import.addFileType("gif", "Animated Image"); fc_import.addFileType("binvox"); fc_import.addFileType("kv6"); fc_import.addFileType("pnx", "Pnx Exchange File"); fc_import.addFileType("kvx"); fc_import.addFileType("qb", "Qubicle Binary"); fc_import.addFileType("vox", "Voxlap Engine File"); fc_import.addFileType("vox", "MagicaVoxel File"); fc_import.addFileType("vox", "Vox Game File"); fc_import.addFileType("vxl", "C&C File Format"); fc_import.addFileType("rawvox", "Raw Voxel Format"); // import file actionManager.registerAction("import_file_action", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (checkUnsavedChanges(frame)) { final File toOpen = fc_import.openFile(frame); if (toOpen != null) { String ext = fc_import.getCurrentExt(); // todo: rework import so that history can stay (!) //data.freshStart(); //data.deleteLayer(data.getSelectedLayer()); final ProgressDialog dialog = new ProgressDialog(frame); try { if ("png".equals(ext) || "jpg".equals(ext) || "jpeg".equals(ext) || "bmp".equals(ext)) { // ----------------- // import image data BufferedImage img = ImageIO.read(toOpen); if (img != null) { data.selectLayer(data.createLayer(FileTools.extractNameWithoutExtension(toOpen))); importImage(img); } else { throw new IOException("Failed to load image with ImageIO.read()"); } } else if ("gif".equals(ext)) { // ---------------- // import gif image data (including frame animation) ImageInputStream inputStream = ImageIO.createImageInputStream(toOpen); try { ImageReader ir = new GIFImageReader(new GIFImageReaderSpi()); ir.setInput(inputStream); int count = ir.getNumImages(true); if (count > 1) { for (int i = 0; i < count; i++) { int layerId = data.createLayer("Frame" + i); data.selectLayer(layerId); if (i < count - 1) { data.setVisible(layerId, false); } importImage(ir.read(i)); } } else { data.selectLayer(data.createLayer(FileTools.extractNameWithoutExtension(toOpen))); importImage(ir.read(0)); } } finally { inputStream.close(); } } else if ("binvox".equals(ext)) { // ---------------- // import .binvox files dialog.start(new ProgressWorker() { @Override protected Object doInBackground() throws Exception { dialog.setActivity("Importing File...", true); AbstractImporter importer = new BinVoxImporter(toOpen, FileTools.extractNameWithoutExtension(toOpen)); importVoxelData(importer, true); return null; } }); } else if ("kv6".equals(ext)) { // ---------------- // import .kv6 files dialog.start(new ProgressWorker() { @Override protected Object doInBackground() throws Exception { dialog.setActivity("Importing File...", true); AbstractImporter importer = new Kv6Importer(toOpen, FileTools.extractNameWithoutExtension(toOpen)); importVoxelData(importer, false); return null; } }); } else if ("pnx".equals(ext)) { // ---------------- // import .pnx files dialog.start(new ProgressWorker() { @Override protected Object doInBackground() throws Exception { dialog.setActivity("Importing File...", true); AbstractImporter importer = new PnxImporter(toOpen, FileTools.extractNameWithoutExtension(toOpen)); importVoxelData(importer, false); return null; } }); } else if ("kvx".equals(ext)) { // ---------------- // import .kvx files dialog.start(new ProgressWorker() { @Override protected Object doInBackground() throws Exception { dialog.setActivity("Importing File...", true); AbstractImporter importer = new KvxImporter(toOpen, FileTools.extractNameWithoutExtension(toOpen)); importVoxelData(importer, false); return null; } }); } else if ("qb".equals(ext)) { // ---------------- // import .qb files dialog.start(new ProgressWorker() { @Override protected Object doInBackground() throws Exception { dialog.setActivity("Importing File...", true); AbstractImporter importer = new QbImporter(toOpen, FileTools.extractNameWithoutExtension(toOpen)); importVoxelData(importer, false); return null; } }); } else if ("vox".equals(ext)) { // ---------------- // import .vox files dialog.start(new ProgressWorker() { @Override protected Object doInBackground() throws Exception { dialog.setActivity("Importing File...", true); AbstractImporter importer = new VoxImporter(toOpen, FileTools.extractNameWithoutExtension(toOpen)); importVoxelData(importer, true); return null; } }); } else if ("rawvox".equals(ext)) { // ---------------- // import .rawvox files dialog.start(new ProgressWorker() { @Override protected Object doInBackground() throws Exception { dialog.setActivity("Importing File...", true); AbstractImporter importer = new RawVoxImporter(toOpen, FileTools.extractNameWithoutExtension(toOpen)); importVoxelData(importer, true); return null; } }); } else if ("vxl".equals(ext)) { // ---------------- // import .vxl files (C&C) dialog.start(new ProgressWorker() { @Override protected Object doInBackground() throws Exception { dialog.setActivity("Importing File...", true); AbstractImporter importer = new CCVxlImporter(toOpen, FileTools.extractNameWithoutExtension(toOpen)); importVoxelData(importer, true); return null; } }); } } catch (IOException e1) { console.addLine(langSelector.getString("error_on_file_import")); //errorHandler.handle(e1); } // force a refresh of the data (redraw) // todo: do this properly int layerId = data.getSelectedLayer(); boolean vis = data.getLayerVisible(layerId); data.setVisible(layerId, !vis); data.setVisible(layerId, vis); data.clearHistoryV(); } } } }); // ========================== // set up exporter dialog final UserInputDialog dialog = new UserInputDialog(frame, "Export Voxels", JOptionPane.CANCEL_OPTION); // add submit buttons dialog.addButton("Export", JOptionPane.OK_OPTION); dialog.addButton("Cancel", JOptionPane.CANCEL_OPTION); // add help links dialog.addLink(console, "Export to Blender", "https://github.com/simlu/voxelshop/wiki/Export-for-Blender"); dialog.addLink(console, "Export to Unity", "https://github.com/simlu/voxelshop/wiki/Export-for-Unity"); dialog.addLink(console, "Export to Stonehearth", "https://github.com/simlu/voxelshop/wiki/Export-for-Stonehearth"); // add file select FieldSet location = new FieldSet("location", "Location"); location.addComponent(new FileSelectModule("file", new File("exported"), frame)); dialog.addFieldSet(location); // --------------- // set up Collada format final FieldSet collada = new FieldSet("collada", "Collada (*.dae)"); collada.addComponent(new SeparatorModule("Algorithm")); collada.addComponent(new ComboBoxModule("type", new String[][] { new String[] {"poly2tri", "Optimal (Poly2Tri)"}, new String[] {"minimal", "Low Poly (Rectangular)"}, new String[] {"naive", "Naive (Unoptimized)"} }, 0)); // add information for "poly2tri" LabelModule poly2triInfo = new LabelModule("Info: This is the preferred exporter. The mesh is highly " + "optimized and no rendering artifacts can appear if T-Junction problems are fixed."); poly2triInfo.setVisibleLookup("collada.type=poly2tri"); collada.addComponent(poly2triInfo); // add information for "optimalGreedy" LabelModule minimalInfo = new LabelModule("Info: This exporter results in a very low triangle " + "count, however rendering artifacts can appear (T-Junction problems)."); minimalInfo.setVisibleLookup("collada.type=minimal"); collada.addComponent(minimalInfo); // add information for "naive" LabelModule naiveInfo = new LabelModule("Info: Unoptimized exporter. Creates two triangles for each voxel. " + "This might be useful if the voxel mesh needs further processing."); naiveInfo.setVisibleLookup("collada.type=naive"); collada.addComponent(naiveInfo); collada.addComponent(new SeparatorModule("Textures")); // option: exported textured voxels CheckBoxModule exportTexturedVoxels = new CheckBoxModule("export_textured_voxels", "Export textured Voxels", false); exportTexturedVoxels.setEnabledLookup("collada.triangulate_by_color=false"); collada.addComponent(exportTexturedVoxels); // option: triangulate by color CheckBoxModule triangulateByColor = new CheckBoxModule("triangulate_by_color", "Triangulate by Color (higher triangle count)", false); triangulateByColor.setEnabledLookup("collada.export_textured_voxels=false"); collada.addComponent(triangulateByColor); // option: use vertex colors CheckBoxModule useVertexColors = new CheckBoxModule("use_vertex_coloring", "Use Vertex Coloring", false); useVertexColors.setEnabledLookup("collada.triangulate_by_color=true&collada.export_textured_voxels=false"); collada.addComponent(useVertexColors); // option: make uvs overlapping CheckBoxModule useOverlappingUvs = new CheckBoxModule("use_overlapping_uvs", "Use overlapping UVs", true); useOverlappingUvs.setEnabledLookup("collada.use_vertex_coloring=false"); collada.addComponent(useOverlappingUvs); // option: use skewed uvs CheckBoxModule useSkewedUvs = new CheckBoxModule("use_skewed_uvs", "Use skewed UVs", true); useSkewedUvs.setEnabledLookup("collada.use_vertex_coloring=false"); collada.addComponent(useSkewedUvs); // option: make texture edges save (pad textures) CheckBoxModule padTextures = new CheckBoxModule("pad_textures", "Use Texture Padding", true); padTextures.setEnabledLookup("collada.use_vertex_coloring=false"); collada.addComponent(padTextures); // option: force power of two textures CheckBoxModule forcePOT = new CheckBoxModule("force_pot", "Use Power of Two textures", false); forcePOT.setEnabledLookup("collada.use_vertex_coloring=false"); collada.addComponent(forcePOT); collada.addComponent(new SeparatorModule("Misc")); // option: fix t junction problems CheckBoxModule fixTJunctions = new CheckBoxModule("fix_tjunctions", "Fix all T-Junction problems", true); fixTJunctions.setEnabledLookup("collada.type=poly2tri"); collada.addComponent(fixTJunctions); // option: layer as object CheckBoxModule layersAsObjects = new CheckBoxModule("layers_as_objects", "Create a new Object for every Layer", true); collada.addComponent(layersAsObjects); // option: remove holes CheckBoxModule removeEnclosed = new CheckBoxModule("remove_holes", "Fill in enclosed Holes", true); collada.addComponent(removeEnclosed); collada.addComponent(new SeparatorModule("Format")); // option: export with y-up or z-up CheckBoxModule useYup = new CheckBoxModule("use_yup", "Set Y instead of Z as the up axis", false); collada.addComponent(useYup); // option: export orthogonal vertex normals CheckBoxModule exportOrthogonalVertexNormals = new CheckBoxModule( "export_orthogonal_vertex_normals", "Export orthogonal Vertex Normals", false); collada.addComponent(exportOrthogonalVertexNormals); collada.addComponent(new SeparatorModule("Origin")); ComboBoxModule setOriginModeSelect = new ComboBoxModule("origin_mode", new String[][]{ new String[]{"cross", "Use Cross"}, new String[]{"center", "Use Object Center"}, new String[]{"plane_center", "Use Object Center Projected onto Plane"}, new String[]{"box_center", "Use Bounding Box Center"}, new String[]{"box_plane_center", "Use Bounding Box Center Projected onto Plane"} }, 0); collada.addComponent(setOriginModeSelect); // --------------- // add "render" export FieldSet imageRenderer = new FieldSet("image_renderer", "Render (*.png)"); imageRenderer.addComponent(new LabelModule("Select Export Options:")); TextInputModule depthMapFileName = new TextInputModule("depth_map", "Name (Depth Render):", "depth", true); depthMapFileName.setEnabledLookup("export_type=image_renderer&image_renderer.render_depth=true"); depthMapFileName.setVisibleLookup("export_type=image_renderer"); imageRenderer.addComponent(depthMapFileName); imageRenderer.addComponent(new CheckBoxModule("render_depth", "Render Depth Image", true)); // --------------- // add "vox (the game)" exporter FieldSet voxExporter = new FieldSet("vox_game_format", "Vox Game Format (*.vox)"); // add information for voxel format LabelModule label_vox_game = new LabelModule("Note: Does not support textured voxels. This file format is used by vox-game.com"); label_vox_game.setVisibleLookup("export_type=vox_game_format"); voxExporter.addComponent(label_vox_game); // --------------- // add "kv6" exporter FieldSet kv6Exporter = new FieldSet("kv6_format", "Kv6 Format (*.kv6)"); // add option to set weighted center final CheckBoxModule use_weighted_center = new CheckBoxModule("use_weighted_center", "Use weighted center instead of origin", false); kv6Exporter.addComponent(use_weighted_center); // add information for voxel format LabelModule label_kv6 = new LabelModule("Note: Does not support textured voxels. This file format is used by Ace of Spades and Slab6."); label_kv6.setVisibleLookup("export_type=kv6_format"); kv6Exporter.addComponent(label_kv6); // --------------- // add "vox (voxlap)" exporter FieldSet voxVoxLapExporter = new FieldSet("voxlap_format", "VoxLap Format (*.vox)"); // --------------- // add "pnx" exporter FieldSet pnxExporter = new FieldSet("pnx_format", "Pnx Format (*.pnx)"); // add information for voxel format LabelModule label_pnx = new LabelModule("Highly compressed and flexible format that is easy to import."); label_pnx.setVisibleLookup("export_type=pnx_format"); pnxExporter.addComponent(label_pnx); // --------------- // add "qb" exporter FieldSet qbExporter = new FieldSet("qb_format", "QB Format (*.qb)"); // add information for voxel format LabelModule label_qb = new LabelModule("Qubicle 1.0 exchange format."); label_qb.setVisibleLookup("export_type=qb_format"); qbExporter.addComponent(label_qb); final CheckBoxModule use_compression = new CheckBoxModule("use_compression", "Use compression", true); qbExporter.addComponent(use_compression); LabelModule compression_info = new LabelModule("Info: Compression saves a lot of space and makes opening and " + "saving the file faster. Un-check for StoneHearth."); qbExporter.addComponent(compression_info); final CheckBoxModule use_box_as_matrix = new CheckBoxModule("use_box_as_matrix", "Use bounding box as matrix", false); qbExporter.addComponent(use_box_as_matrix); LabelModule box_as_matrix_info = new LabelModule( "Warning: This option will result in loss of information for voxels outside the bounding box. " + "Use this setting to gain control over the matrix size. " + "Check for StoneHearth and set bounding box to 31 41 31." ); qbExporter.addComponent(box_as_matrix_info); final CheckBoxModule use_origin_as_zero = new CheckBoxModule("use_origin_as_zero", "Use origin as zero", true); qbExporter.addComponent(use_origin_as_zero); LabelModule origin_as_zero_info = new LabelModule( "Info: Un-checking will move exported voxel into positive space. This means voxels are " + "shifted when re-importing the exported file. Un-check for StoneHearth." ); qbExporter.addComponent(origin_as_zero_info); final CheckBoxModule use_vis_mask_encoding = new CheckBoxModule("use_vis_mask_encoding", "Use visibility mask encoding.", true); qbExporter.addComponent(use_vis_mask_encoding); LabelModule vis_mask_encoding_info = new LabelModule( "Info: This will encode voxel side visibility information, which can " + "result in faster load time. Un-check for StoneHearth." ); qbExporter.addComponent(vis_mask_encoding_info); final CheckBoxModule use_right_handed_z_axis_orientation = new CheckBoxModule("use_right_handed_z_axis_orientation", "Use right handed z-axis orientation.", true); qbExporter.addComponent(use_right_handed_z_axis_orientation); LabelModule right_handed_z_axis_orientation_info = new LabelModule( "Info: This option can affect file size. Check for StoneHearth." ); qbExporter.addComponent(right_handed_z_axis_orientation_info); // --------------- // add all formats dialog.addComboBox("export_type", new FieldSet[] { collada, voxExporter, voxVoxLapExporter, kv6Exporter, pnxExporter, qbExporter, imageRenderer }, 0); // --------------- // try to load the serialization Object serialization = preferences.loadObject("export_dialog_serialization"); if (serialization != null && serialization instanceof ArrayList) { ArrayList data = (ArrayList) serialization; ArrayList<String[]> validatedData = new ArrayList<String[]>(); for (Object pair : data) { if (pair instanceof String[]) { String[] confirmedPair = (String[]) pair; if (((String[]) pair).length == 2) { validatedData.add(confirmedPair); } } } dialog.loadSerialization(validatedData); } // listen to events dialog.setListener(new UserInputDialogListener() { @Override public boolean onClose(int resultFlag) { // user approved the dialog if (resultFlag == JOptionPane.OK_OPTION) { final String baseName = dialog.getValue("location.file"); // validate folder (that it exists and is actually a folder) File toValidateFolder = new File(baseName); if (!toValidateFolder.getParentFile().exists() || !toValidateFolder.getParentFile().isDirectory()) { JOptionPane.showMessageDialog(frame, langSelector.getString("error_invalid_folder")); return false; } // handle logic if (dialog.is("export_type=image_renderer")) { // =========== // -- export render // extract file name final File exportRenderTo = new File(baseName + (baseName.endsWith(".png") ? "" : ".png")); // check if file exists if (exportRenderTo.exists()) { if (JOptionPane.showConfirmDialog(frame, exportRenderTo.getPath() + " " + langSelector.getString("replace_file_query"), langSelector.getString("replace_file_query_title"), JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) { return false; } } // -- export depth map boolean exportDepthMap = false; File exportDepthMapTo = null; if (dialog.is("image_renderer.render_depth=true")) { // extract file name String depthBaseName = dialog.getValue("image_renderer.depth_map"); exportDepthMapTo = new File(FileTools.ensureTrailingSeparator(exportRenderTo.getParent()) + depthBaseName + (depthBaseName.endsWith(".png") ? "" : ".png")); // check if file exists if (exportDepthMapTo.exists()) { if (JOptionPane.showConfirmDialog(frame, exportDepthMapTo.getPath() + " " + langSelector.getString("replace_file_query"), langSelector.getString("replace_file_query_title"), JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) { return false; } } exportDepthMap = true; } // create progress dialog final ProgressDialog progressDialog = new ProgressDialog(frame); final File finalExportDepthMapTo = exportDepthMapTo; final boolean finalExportDepthMap = exportDepthMap; progressDialog.start(new ProgressWorker() { @Override protected Object doInBackground() throws Exception { // export color render (image) progressDialog.setActivity("Writing Render...", true); BufferedImage image = mainView.getImage(); try { ImageIO.write(image,"png", exportRenderTo); } catch (IOException e) { errorHandler.handle(e); } // export depth map if (finalExportDepthMap) { progressDialog.setActivity("Writing Depth Render...", true); BufferedImage depth = mainView.getDepthImage(); try { ImageIO.write(depth,"png", finalExportDepthMapTo); } catch (IOException e) { errorHandler.handle(e); } } return null; } }); // =========== } else if (dialog.is("export_type=collada")) { // =========== // -- export collada // extract file name final File exportColladaTo = new File(baseName + (baseName.endsWith(".dae") ? "" : ".dae")); // check if file exists if (exportColladaTo.exists()) { if (JOptionPane.showConfirmDialog(frame, exportColladaTo.getPath() + " " + langSelector.getString("replace_file_query"), langSelector.getString("replace_file_query_title"), JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) { return false; } } // extract texture name and query the user if the file should be overwritten final File exportTextureTo = new File(FileTools.changeExtension(exportColladaTo.getPath(), "_texture0.png")); // check if file exists if (exportTextureTo.exists()) { if (JOptionPane.showConfirmDialog(frame, exportTextureTo.getPath() + " " + langSelector.getString("replace_file_query"), langSelector.getString("replace_file_query_title"), JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) { return false; } } // -- default export // create progress dialog final ProgressDialog progressDialog = new ProgressDialog(frame); // do the exporting progressDialog.start(new ProgressWorker() { @Override protected Object doInBackground() throws Exception { ColladaExportWrapper colladaExportWrapper = new ColladaExportWrapper(progressDialog, console); // set the "use layers" flag colladaExportWrapper.setUseLayers(dialog.is("collada.layers_as_objects=true")); // set remove holes flag colladaExportWrapper.setRemoveHoles(dialog.is("collada.remove_holes=true")); // set pad textures flag colladaExportWrapper.setPadTextures(dialog.is("collada.pad_textures=true")); // set triangulate by color colladaExportWrapper.setTriangulateByColor(dialog.is("collada.triangulate_by_color=true")); // set use vertex coloring colladaExportWrapper.setUseVertexColoring(dialog.is("collada.use_vertex_coloring=true")); // set export textured voxels colladaExportWrapper.setExportTexturedVoxels(dialog.is("collada.export_textured_voxels=true")); // set force power of two force textures colladaExportWrapper.setForcePOT(dialog.is("collada.force_pot=true")); // set the file name (only used if the layers are not used) colladaExportWrapper.setObjectName(FileTools.extractNameWithoutExtension(exportColladaTo)); // set the YUP flag (whether to use z-up or y-up) colladaExportWrapper.setUseYUP(dialog.is("collada.use_yup=true")); // set the YUP flag (whether to use z-up or y-up) colladaExportWrapper.setFixTJunctions(dialog.is("collada.fix_tjunctions=true")); // set "export exportOrthogonalVertexNormals vertex normals" flag colladaExportWrapper.setExportOrthogonalVertexNormals(dialog.is("collada.export_orthogonal_vertex_normals=true")); // set "use overlapping uvs" option colladaExportWrapper.setUseOverlappingUvs(dialog.is("collada.use_overlapping_uvs=true")); // set "use skewed uvs" option colladaExportWrapper.setUseSkewedUvs(dialog.is("collada.use_skewed_uvs=true")); // set the center mode if (dialog.is("collada.origin_mode=cross")) { colladaExportWrapper.setOriginMode(ColladaExportWrapper.ORIGIN_CROSS); } else if (dialog.is("collada.origin_mode=center")) { colladaExportWrapper.setOriginMode(ColladaExportWrapper.ORIGIN_CENTER); } else if (dialog.is("collada.origin_mode=plane_center")) { colladaExportWrapper.setOriginMode(ColladaExportWrapper.ORIGIN_PLANE_CENTER); } else if (dialog.is("collada.origin_mode=box_center")) { colladaExportWrapper.setOriginMode(ColladaExportWrapper.ORIGIN_BOX_CENTER); } else if (dialog.is("collada.origin_mode=box_plane_center")) { colladaExportWrapper.setOriginMode(ColladaExportWrapper.ORIGIN_BOX_PLANE_CENTER); } // set the algorithm type if (dialog.is("collada.type=minimal")) { colladaExportWrapper.setAlgorithm(ExportDataManager.MINIMAL_RECT_ALGORITHM); } else if (dialog.is("collada.type=poly2tri")) { colladaExportWrapper.setAlgorithm(ExportDataManager.POLY2TRI_ALGORITHM); } else if (dialog.is("collada.type=naive")) { colladaExportWrapper.setAlgorithm(ExportDataManager.NAIVE_ALGORITHM); } long time = System.currentTimeMillis(); if (colladaExportWrapper.export(data, errorHandler, exportColladaTo)) { console.addLine( String.format(langSelector.getString("export_file_successful"), System.currentTimeMillis() - time) ); } else { console.addLine(langSelector.getString("export_file_error")); } return null; } }); // =========== } else if (dialog.is("export_type=vox_game_format")) { // =========== // -- handle vox file format // create progress dialog final ProgressDialog progressDialog = new ProgressDialog(frame); // do the exporting progressDialog.start(new ProgressWorker() { @Override protected Object doInBackground() throws Exception { // extract file name final File exportTo = new File(baseName + (baseName.endsWith(".vox") ? "" : ".vox")); // check if file exists if (exportTo.exists()) { if (JOptionPane.showConfirmDialog(frame, exportTo.getPath() + " " + langSelector.getString("replace_file_query"), langSelector.getString("replace_file_query_title"), JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) { return false; } } // export vox engine format boolean success; long time = System.currentTimeMillis(); try { VoxGameExporter exporter = new VoxGameExporter(exportTo, data, progressDialog, console); success = exporter.writeData(); } catch (IOException ignored) { success = false; } if (success) { console.addLine( String.format(langSelector.getString("export_file_successful"), System.currentTimeMillis() - time) ); } else { console.addLine(langSelector.getString("export_file_error")); } return null; } }); // =========== } else if (dialog.is("export_type=kv6_format")) { // =========== // -- handle kv6 file format // create progress dialog final ProgressDialog progressDialog = new ProgressDialog(frame); // do the exporting progressDialog.start(new ProgressWorker() { @Override protected Object doInBackground() throws Exception { // extract file name final File exportTo = new File(baseName + (baseName.endsWith(".kv6") ? "" : ".kv6")); // check if file exists if (exportTo.exists()) { if (JOptionPane.showConfirmDialog(frame, exportTo.getPath() + " " + langSelector.getString("replace_file_query"), langSelector.getString("replace_file_query_title"), JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) { return false; } } // export kv6 engine format boolean success; long time = System.currentTimeMillis(); try { Kv6Exporter exporter = new Kv6Exporter(exportTo, data, progressDialog, console); exporter.setUseWeightedCenter(dialog.is("kv6_format.use_weighted_center=true")); success = exporter.writeData(); } catch (IOException ignored) { success = false; } if (success) { console.addLine( String.format(langSelector.getString("export_file_successful"), System.currentTimeMillis() - time) ); } else { console.addLine(langSelector.getString("export_file_error")); } return null; } }); // =========== } else if (dialog.is("export_type=voxlap_format")) { // =========== // -- handle vox voxlap file format // create progress dialog final ProgressDialog progressDialog = new ProgressDialog(frame); // do the exporting progressDialog.start(new ProgressWorker() { @Override protected Object doInBackground() throws Exception { // extract file name final File exportTo = new File(baseName + (baseName.endsWith(".vox") ? "" : ".vox")); // check if file exists if (exportTo.exists()) { if (JOptionPane.showConfirmDialog(frame, exportTo.getPath() + " " + langSelector.getString("replace_file_query"), langSelector.getString("replace_file_query_title"), JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) { return false; } } // export vox voxlap format boolean success; long time = System.currentTimeMillis(); try { VoxVoxLapExporter exporter = new VoxVoxLapExporter(exportTo, data, progressDialog, console); success = exporter.writeData(); } catch (IOException ignored) { success = false; } if (success) { console.addLine( String.format(langSelector.getString("export_file_successful"), System.currentTimeMillis() - time) ); } else { console.addLine(langSelector.getString("export_file_error")); } return null; } }); // =========== } else if (dialog.is("export_type=pnx_format")) { // =========== // -- handle pnx file format // create progress dialog final ProgressDialog progressDialog = new ProgressDialog(frame); // do the exporting progressDialog.start(new ProgressWorker() { @Override protected Object doInBackground() throws Exception { // extract file name final File exportTo = new File(baseName + (baseName.endsWith(".pnx") ? "" : ".pnx")); // check if file exists if (exportTo.exists()) { if (JOptionPane.showConfirmDialog(frame, exportTo.getPath() + " " + langSelector.getString("replace_file_query"), langSelector.getString("replace_file_query_title"), JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) { return false; } } // export pnx file format boolean success; long time = System.currentTimeMillis(); try { PnxExporter exporter = new PnxExporter(exportTo, data, progressDialog, console); success = exporter.writeData(); } catch (IOException ignored) { success = false; } if (success) { console.addLine( String.format(langSelector.getString("export_file_successful"), System.currentTimeMillis() - time) ); } else { console.addLine(langSelector.getString("export_file_error")); } return null; } }); // =========== } else if (dialog.is("export_type=qb_format")) { // =========== // -- handle qb file format // create progress dialog final ProgressDialog progressDialog = new ProgressDialog(frame); // do the exporting progressDialog.start(new ProgressWorker() { @Override protected Object doInBackground() throws Exception { // extract file name final File exportTo = new File(baseName + (baseName.endsWith(".qb") ? "" : ".qb")); // check if file exists if (exportTo.exists()) { if (JOptionPane.showConfirmDialog(frame, exportTo.getPath() + " " + langSelector.getString("replace_file_query"), langSelector.getString("replace_file_query_title"), JOptionPane.OK_CANCEL_OPTION) != JOptionPane.OK_OPTION) { return false; } } // export qb file format boolean success; long time = System.currentTimeMillis(); try { QbExporter exporter = new QbExporter(exportTo, data, progressDialog, console); exporter.setUseCompression(dialog.is("qb_format.use_compression=true")); exporter.setUseBoxAsMatrix(dialog.is("qb_format.use_box_as_matrix=true")); exporter.setUseOriginAsZero(dialog.is("qb_format.use_origin_as_zero=true")); exporter.setUseVisMaskEncoding(dialog.is("qb_format.use_vis_mask_encoding=true")); exporter.setUseRightHandedZAxisOrientation(dialog.is("qb_format.use_right_handed_z_axis_orientation=true")); success = exporter.writeData(); } catch (IOException ignored) { success = false; } if (success) { console.addLine( String.format(langSelector.getString("export_file_successful"), System.currentTimeMillis() - time) ); } else { console.addLine(langSelector.getString("export_file_error")); } return null; } }); // =========== } // ----- // store serialization preferences.storeObject("export_dialog_serialization", dialog.getSerialization()); } return true; } }); // ----------------- // export file actionManager.registerAction("export_file_action", new StateActionPrototype() { @Override public void action(ActionEvent actionEvent) { if (getStatus()) { // show dialog dialog.setVisible(true); } } @Override public boolean getStatus() { return data.anyLayerVoxelVisible(); } }); // ========================= // quick save actionManager.registerAction("quick_save_file_action", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (hasSaveLocation()) { // make sure we can save File file = getSaveLocation(); data.saveToFile(file); } else { actionManager.getAction("save_file_action").actionPerformed(e); } } }); // new file actionManager.registerAction("new_file_action", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (checkUnsavedChanges(frame)) { data.freshStart(); setSaveLocation(null); } } }); // register close event actionManager.registerAction("program_closing_event", new StateActionPrototype() { boolean shutdown = false; @Override public void action(ActionEvent actionEvent) { shutdown = true; } @Override public boolean getStatus() { return shutdown; } }); // register closing action actionManager.registerActionIsUsed("program_closing_event"); actionManager.registerAction("close_program_action", new AbstractAction() { @Override public void actionPerformed(final ActionEvent e) { if (checkUnsavedChanges(frame)) { // fire closing action actionManager.performWhenActionIsReady("program_closing_event", new Runnable() { @Override public void run() { actionManager.getAction("program_closing_event").actionPerformed(e); } }); // save layout data preferences.storeObject("custom_raw_layout_data", ((DefaultDockableBarDockableHolder) frame).getLayoutPersistence().getLayoutRawData()); // do not print any thread errors (JFileChooser thread can cause this!) try { PrintStream nullStream = new PrintStream(new OutputStream() { @Override public void write(int b) throws IOException {} @Override public void write(@SuppressWarnings("NullableProblems") byte b[]) throws IOException {} @Override public void write(@SuppressWarnings("NullableProblems") byte b[], int off, int len) throws IOException {} }, true, "utf-8"); System.setErr(nullStream); System.setOut(nullStream); } catch (UnsupportedEncodingException e1) { errorHandler.handle(e1); } // and exit ((DefaultDockableBarDockableHolder) frame).setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.dispose(); } } }); // fill voxels action actionManager.registerAction("fill_voxels_action", new StateActionPrototype() { @Override public void action(ActionEvent actionEvent) { synchronized (VitcoSettings.SYNC) { // compute the hull manager HullManagerExt<String> hullManager = new HullManagerExt<String>(); for (Voxel voxel : data.getVisibleLayerVoxel()) { hullManager.update(voxel.posId, null); } hullManager.computeExterior(); // fetch the empty interior int[] emptyInterior = hullManager.getEmptyInterior(); // create and add the missing voxels Voxel[] voxels = new Voxel[emptyInterior.length]; Color color = ColorTools.hsbToColor((float[]) preferences.loadObject("currently_used_color")); for (int i = 0; i < emptyInterior.length; i++) { short[] pos = CubeIndexer.getPos(emptyInterior[i]); voxels[i] = new Voxel(-1, new int[]{pos[0], pos[1], pos[2]}, color, false, null, data.getSelectedLayer()); } data.massAddVoxel(voxels); } } @Override public boolean getStatus() { return true; } }); // hollow voxels action actionManager.registerAction("hollow_voxels_action", new StateActionPrototype() { @Override public void action(ActionEvent actionEvent) { synchronized (VitcoSettings.SYNC) { // compute the hull manager HullManagerExt<String> hullManager = new HullManagerExt<String>(); for (Voxel voxel : data.getVisibleLayerVoxel()) { hullManager.update(voxel.posId, null); } hullManager.computeExterior(); // fetch the filled interior int[] filledInterior = hullManager.getFilledInterior(); // search for the interior voxels and remove Integer[] voxelIds = new Integer[filledInterior.length]; // todo: This will only remove the top voxel, change it so that it removes all voxels in all layers at this position for (int i = 0; i < filledInterior.length; i++) { short[] pos = CubeIndexer.getPos(filledInterior[i]); Voxel voxel = data.searchVoxel(new int[]{pos[0], pos[1], pos[2]}, false); assert voxel != null; voxelIds[i] = voxel.id; } data.massRemoveVoxel(voxelIds); } } @Override public boolean getStatus() { return true; } }); } @PreDestroy public final void finish() { // store folder locations (for open / close / import / export) preferences.storeString("file_open_close_dialog_last_directory", fc_vsd.getDialogPath()); preferences.storeString("file_import_dialog_last_directory", fc_import.getDialogPath()); } @PostConstruct public final void init() { // load folder locations (for open / close / import / export) if (preferences.contains("file_open_close_dialog_last_directory")) { File file = new File(preferences.loadString("file_open_close_dialog_last_directory")); fc_vsd.setDialogPath(file); } if (preferences.contains("file_import_dialog_last_directory")) { File file = new File(preferences.loadString("file_import_dialog_last_directory")); fc_import.setDialogPath(file); } } }