package com.vitco.export.collada; import com.vitco.export.generic.ExportDataManager; import com.vitco.export.generic.container.TexTriangleManager; import com.vitco.export.generic.container.TriTexture; import com.vitco.export.generic.container.TriTextureManager; import com.vitco.layout.content.console.ConsoleInterface; import com.vitco.manager.error.ErrorHandlerInterface; import com.vitco.settings.VitcoSettings; import com.vitco.util.components.progressbar.ProgressDialog; import com.vitco.util.components.progressbar.ProgressReporter; import com.vitco.util.file.FileTools; import com.vitco.util.misc.DateTools; import com.vitco.util.xml.XmlFile; import gnu.trove.iterator.TIntIterator; import gnu.trove.map.hash.TIntIntHashMap; import gnu.trove.set.hash.TIntHashSet; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.HashSet; /** * Export data to COLLADA file ( with optional settings ) */ public class ColladaFileExporter extends ProgressReporter { // contains the data that will be used to write this collada file private final ExportDataManager exportDataManager; // the xml Collada file that we're building private final XmlFile xmlFile = new XmlFile("COLLADA"); // prefix for the texture files (this should include the file name to prevent // overwriting of textures that belong to different files) private final String texturePrefix; // constructor public ColladaFileExporter(ProgressDialog dialog, ConsoleInterface console, ExportDataManager exportDataManager, String texturePrefix, String name, boolean useYUP, boolean exportOrthogonalVertexNormals, boolean useVertexColoring) { super(dialog, console); this.exportDataManager = exportDataManager; this.texturePrefix = texturePrefix; // initialize the xml file setActivity("Creating File Data...", true); initXmlFile(useYUP, useVertexColoring); // create the object in the scene setActivity("Creating Objects...", true); writeObject(name); if (!useVertexColoring) { // write the texture information setActivity("Creating Textures...", true); writeTextures(); } // write the mesh + uv of the object (triangles) setActivity("Creating Coordinates and UVs / Vertex Colors...", true); writeCoordinates(useYUP, exportOrthogonalVertexNormals, useVertexColoring); } // create the object in the scene private void writeObject(String name) { String cleanName = name.replace(" ", "_").replaceAll("[^a-zA-Z0-9_\\-\\.]", "").toLowerCase(); String[] layerNames = exportDataManager.getLayerNames(); HashSet<String> knownObjectIds = new HashSet<String>(); for (int layerRef = 0; layerRef < layerNames.length; layerRef++) { String layerName = layerNames[layerRef]; String cleanLayerName = layerName.replace(" ", "_").replaceAll("[^a-zA-Z0-9_\\-\\.]", "").toLowerCase(); String objectId = cleanLayerName; int count = 1; while (knownObjectIds.contains(objectId)) { objectId = cleanLayerName + "." + String.format("%03d", count); count++; } knownObjectIds.add(objectId); // create the object xmlFile.resetTopNode("library_visual_scenes/visual_scene/node[-1]"); xmlFile.addAttributes("", new String[]{ "id=" + cleanName + "." + objectId, "name=" + cleanName + "." + objectId, "type=NODE" }); xmlFile.addAttrAndTextContent("translate", new String[]{"sid=location"}, "0 0 0"); xmlFile.addAttrAndTextContent("rotate[-1]", new String[]{"sid=rotationZ"}, "0 0 1 0"); xmlFile.addAttrAndTextContent("rotate[-1]", new String[]{"sid=rotationY"}, "0 1 0 0"); xmlFile.addAttrAndTextContent("rotate[-1]", new String[]{"sid=rotationX"}, "1 0 0 0"); // scale the object down xmlFile.addAttrAndTextContent("scale", new String[]{"sid=scale"}, "0.05 0.05 0.05"); // add the material to the object xmlFile.setTopNode("instance_geometry[-1]"); xmlFile.addAttributes("", new String[]{ "url=#Plane-tex-mesh-" + layerRef, "sid=" + objectId, "name=" + cleanLayerName }); } } // helper to register a texture image private void addTexture(int id) { // write the image to the library xmlFile.resetTopNode("library_images/image[-1]"); xmlFile.addAttributes("", new String[]{ "id=" + texturePrefix + id + "-image", "name=" + texturePrefix + id + "-image" }); xmlFile.addTextContent("init_from", "file://" + texturePrefix + id + ".png"); TexTriangleManager[] triangleManager = exportDataManager.getTriangleManager(); for (int layerRef = 0; layerRef < triangleManager.length; layerRef++) { // create texture reference in object xmlFile.resetTopNode("library_visual_scenes/visual_scene/node[" + layerRef + "]/instance_geometry" + "/bind_material/technique_common/instance_material[-1]"); xmlFile.addAttributes("", new String[]{ "symbol=lambert" + id + "-material", "target=#lambert" + id + "-material" }); // add the uv mapping xmlFile.addAttributes("bind_vertex_input", new String[]{ "semantic=TEX0", "input_semantic=TEXCOORD", "input_set=0" }); } // add the material xmlFile.resetTopNode("library_materials/material[-1]"); xmlFile.addAttributes("", new String[]{ "id=lambert" + id + "-material", "name=lambert" + id }); xmlFile.addAttributes("instance_effect", new String[]{ "url=#lambert" + id + "-fx" }); // write the image effect xmlFile.resetTopNode("library_effects/effect[-1]"); xmlFile.addAttributes("", new String[]{ "id=lambert" + id + "-fx" }); xmlFile.setTopNode("profile_COMMON"); // ---- xmlFile.setTopNode("newparam[-1]"); xmlFile.addAttributes("", new String[]{"sid=" + texturePrefix + id + "-surface"}); xmlFile.addAttributes("surface", new String[]{"type=2D"}); xmlFile.addTextContent("surface/init_from", texturePrefix + id + "-image"); // ---- xmlFile.goUp(); xmlFile.setTopNode("newparam[-1]"); xmlFile.addAttributes("", new String[]{"sid=" + texturePrefix + id + "-sampler"}); xmlFile.addTextContent("sampler2D/source", texturePrefix + id + "-surface"); xmlFile.addTextContent("sampler2D/wrap_s", "WRAP"); xmlFile.addTextContent("sampler2D/wrap_t", "WRAP"); xmlFile.addTextContent("sampler2D/minfilter", "NEAREST"); xmlFile.addTextContent("sampler2D/magfilter", "NEAREST"); // ---- xmlFile.goUp(); xmlFile.setTopNode("technique[-1]"); xmlFile.addAttributes("", new String[]{"sid=common"}); xmlFile.addTextContent("lambert/emission/color", "0 0 0 1"); xmlFile.addTextContent("lambert/ambient/color", "0 0 0 1"); xmlFile.addAttributes("lambert/diffuse/texture", new String[]{ "texture=" + texturePrefix + id + "-sampler", "texcoord=TEX0" }); } // write the different textures private void writeTextures() { // obtain all texture ids TIntHashSet textureIds = new TIntHashSet(); for (TexTriangleManager texTriangleManager : exportDataManager.getTriangleManager()) { for (int[] textureId : texTriangleManager.getTextureIds()) { textureIds.add(textureId[0]); } } // write texture file names to xml file for (TIntIterator it = textureIds.iterator(); it.hasNext(); ) { addTexture(it.next()); } } // write the coordinates private void writeCoordinates(boolean useYUP, boolean exportOrthogonalVertexNormals, boolean useVertexColoring) { TexTriangleManager[] triangleManager = exportDataManager.getTriangleManager(); for (int layerRef = 0; layerRef < triangleManager.length; layerRef++) { TexTriangleManager texTriangleManager = triangleManager[layerRef]; TIntIntHashMap colorMap = new TIntIntHashMap(); String gId = "Plane-tex-mesh-" + layerRef; // reset top node xmlFile.resetTopNode("library_geometries/geometry[-1]"); xmlFile.addAttributes("", new String[]{ "id=" + gId, "name=Plane-tex" }); xmlFile.setTopNode("mesh"); // Object-positions xmlFile.addAttributes("source[-1]", new String[]{ "id=" + gId + "-positions" }); xmlFile.addAttrAndTextContent("source[" + 0 + "]/float_array", new String[]{ "id=" + gId + "-positions-array", "count=" + texTriangleManager.getUniquePointCount() * 3}, texTriangleManager.getUniquePointString(true)); xmlFile.addAttributes("source[" + 0 + "]/technique_common/accessor", new String[]{ "source=#" + gId + "-positions-array", "count=" + texTriangleManager.getUniquePointCount(), "stride=3" }); xmlFile.addAttributes("source[" + 0 + "]/technique_common/accessor/param[-1]", new String[]{"name=X", "type=float"}); xmlFile.addAttributes("source[" + 0 + "]/technique_common/accessor/param[-1]", new String[]{"name=Y", "type=float"}); xmlFile.addAttributes("source[" + 0 + "]/technique_common/accessor/param[-1]", new String[]{"name=Z", "type=float"}); if (useVertexColoring) { // --- write the colors int[][] colors = texTriangleManager.getSampleRgbs(); StringBuilder colorString = new StringBuilder(); for (int i = 0; i < colors.length; i++) { int[] color = colors[i]; colorMap.put(color[0], i); Color rgb = new Color(color[0]); int r = rgb.getRed(); int g = rgb.getGreen(); int b = rgb.getBlue(); if (i > 0) { colorString.append(" "); } colorString.append(r / (float) 255).append(" ").append(g / (float) 255).append(" ").append(b / (float) 255); } xmlFile.addAttributes("source[-1]", new String[]{ "id=" + gId + "-colors" }); xmlFile.addAttrAndTextContent("source[" + 1 + "]/float_array", new String[]{ "id=" + gId + "-colors-array", "count=" + (colors.length * 3)}, colorString.toString()); xmlFile.addAttributes("source[" + 1 + "]/technique_common/accessor", new String[]{ "source=#" + gId + "-colors-array", "count=" + colors.length, "stride=3" }); xmlFile.addAttributes("source[" + 1 + "]/technique_common/accessor/param[-1]", new String[]{"name=R", "type=float"}); xmlFile.addAttributes("source[" + 1 + "]/technique_common/accessor/param[-1]", new String[]{"name=G", "type=float"}); xmlFile.addAttributes("source[" + 1 + "]/technique_common/accessor/param[-1]", new String[]{"name=B", "type=float"}); } else { // --- write the uvs xmlFile.addAttributes("source[-1]", new String[]{ "id=" + gId + "-uvs" }); xmlFile.addAttrAndTextContent("source[" + 1 + "]/float_array", new String[]{ "id=" + gId + "-uvs-array", "count=" + (texTriangleManager.getUniqueUVCount() * 2)}, texTriangleManager.getUniqueUVString(false)); xmlFile.addAttributes("source[" + 1 + "]/technique_common/accessor", new String[]{ "source=#" + gId + "-uvs-array", "count=" + texTriangleManager.getUniqueUVCount(), "stride=2" }); xmlFile.addAttributes("source[" + 1 + "]/technique_common/accessor/param[-1]", new String[]{"name=S", "type=float"}); xmlFile.addAttributes("source[" + 1 + "]/technique_common/accessor/param[-1]", new String[]{"name=T", "type=float"}); } if (exportOrthogonalVertexNormals) { // -- write the normals xmlFile.addAttributes("source[-1]", new String[]{ "id=" + gId + "-normals" }); xmlFile.addAttrAndTextContent("source[" + 2 + "]/float_array", new String[]{"id=" + gId + "-normals-array", "count=18"}, useYUP ? "-1 0 0 1 0 0 0 -1 0 0 1 0 0 0 1 0 0 -1 " : "-1 0 0 1 0 0 0 0 -1 0 0 1 0 -1 0 0 1 0 " ); xmlFile.addAttributes("source[" + 2 + "]/technique_common/accessor", new String[]{ "source=#" + gId + "-normals-array", "count=6", "stride=3" }); xmlFile.addAttributes("source[" + 2 + "]/technique_common/accessor/param[-1]", new String[]{"name=X", "type=float"}); xmlFile.addAttributes("source[" + 2 + "]/technique_common/accessor/param[-1]", new String[]{"name=Y", "type=float"}); xmlFile.addAttributes("source[" + 2 + "]/technique_common/accessor/param[-1]", new String[]{"name=Z", "type=float"}); } // vertices (generic information) xmlFile.addAttributes("vertices", new String[]{ "id=" + gId + "-vertices" }); xmlFile.addAttributes("vertices/input", new String[]{ "semantic=POSITION", "source=#" + gId + "-positions" }); if (useVertexColoring) { // write data xmlFile.resetTopNode("library_geometries/geometry[" + layerRef + "]/mesh/triangles[-1]"); xmlFile.addAttributes("", new String[]{ "material=" + gId + "-colors-material", "count=" + texTriangleManager.getTriangleCount() }); xmlFile.addAttributes("input[-1]", new String[]{ "semantic=VERTEX", "source=#" + gId + "-vertices", "offset=" + 0 }); xmlFile.addAttributes("input[-1]", new String[]{ "semantic=COLOR", "source=#" + gId + "-colors", "offset=" + 1 }); if (exportOrthogonalVertexNormals) { xmlFile.addAttributes("input[-1]", new String[]{ "semantic=NORMAL", "source=#" + gId + "-normals", "offset=" + 2 }); } xmlFile.addTextContent("p", texTriangleManager.getTrianglePolygonList( null, colorMap, exportOrthogonalVertexNormals, true)); } else { // write one poly list for each texture for (int[] identifier : texTriangleManager.getTextureIds()) { // write data xmlFile.resetTopNode("library_geometries/geometry[" + layerRef + "]/mesh/triangles[-1]"); xmlFile.addAttributes("", new String[]{ "material=" + gId + "-lambert" + identifier[0] + "-material", "count=" + identifier[1] }); xmlFile.addAttributes("input[-1]", new String[]{ "semantic=VERTEX", "source=#" + gId + "-vertices", "offset=" + 0 }); xmlFile.addAttributes("input[-1]", new String[]{ "semantic=TEXCOORD", "source=#" + gId + "-uvs", "offset=" + 1, "set=0" }); if (exportOrthogonalVertexNormals) { xmlFile.addAttributes("input[-1]", new String[]{ "semantic=NORMAL", "source=#" + gId + "-normals", "offset=" + 2 }); } xmlFile.addTextContent("p", texTriangleManager.getTrianglePolygonList( identifier[0], null, exportOrthogonalVertexNormals, false)); } } } } // ----------------------- // initialize the XML file private void initXmlFile(boolean useYUP, boolean useVertexColoring) { // basic information xmlFile.addAttributes("", new String[] { "xmlns=http://www.collada.org/2005/11/COLLADASchema", "version=1.4.1"}); // basic information xmlFile.resetTopNode("asset"); xmlFile.addTextContent("contributor/author", "VoxelShop User"); xmlFile.addTextContent("contributor/authoring_tool", "VoxelShop V" + VitcoSettings.VERSION_ID); String now = DateTools.now("yyyy-MM-dd'T'HH:mm:ss"); xmlFile.addTextContent("created", now); xmlFile.addTextContent("modified", now); xmlFile.addAttributes("unit", new String[] {"name=meter","meter=1"}); // blender default (can not be changed since blender ignores it!) if (useYUP) { xmlFile.addTextContent("up_axis", "Y_UP"); } else { xmlFile.addTextContent("up_axis", "Z_UP"); } // ========================= if (!useVertexColoring) { // will hold the used images (library) // (this will be deleted later on if there is no // texture image used for this dae file) xmlFile.resetTopNode("library_images"); // will hold all the color "effect" information xmlFile.resetTopNode("library_effects"); // will contain the materials linked to the color information xmlFile.resetTopNode("library_materials"); } // will contain geometries (mesh) of the object xmlFile.resetTopNode("library_geometries"); // ========================= // contains the scene xmlFile.resetTopNode("library_visual_scenes/visual_scene"); xmlFile.addAttributes("", new String[] { "id=Scene", "name=Scene" }); // link the library_visual_scenes node xmlFile.resetTopNode("scene"); xmlFile.addAttributes("instance_visual_scene", new String[]{"url=#Scene"}); } // save this file public boolean writeToFile(File file, ErrorHandlerInterface errorHandler) { return xmlFile.writeToFile(file, errorHandler); } // write texture files public boolean writeTexturesToFolder(File folder, ErrorHandlerInterface errorHandler) { // obtain all texture ids TIntHashSet textureIds = new TIntHashSet(); for (TexTriangleManager texTriangleManager : exportDataManager.getTriangleManager()) { for (int[] textureId : texTriangleManager.getTextureIds()) { textureIds.add(textureId[0]); } } // write files to disk TriTextureManager triTextureManager = exportDataManager.getTextureManager(); try { for (TIntIterator it = textureIds.iterator(); it.hasNext(); ) { int textureId = it.next(); TriTexture texture = triTextureManager.getTexture(textureId); BufferedImage textureImage = texture.getImage(); ImageIO.write(textureImage, "png", new File( FileTools.ensureTrailingSeparator(folder.getAbsolutePath()) + texturePrefix + textureId+ ".png" )); } return true; } catch (IOException e) { errorHandler.handle(e); } return false; } }