diff options
author | Julien Gouesse <[email protected]> | 2013-11-11 20:21:50 +0100 |
---|---|---|
committer | Julien Gouesse <[email protected]> | 2013-11-11 20:21:50 +0100 |
commit | 9c9cc3506f3e52f5fc9d74d0d5c1dab9755a1b68 (patch) | |
tree | 0f460ff37cdddf38961a65c548ff877774eac2e3 /ardor3d-extras/src/main | |
parent | 36a9bc300d85a4453b86d5f5ba7497471ef430ee (diff) |
WaveFront OBJ exporter (work in progress)
Diffstat (limited to 'ardor3d-extras/src/main')
-rw-r--r-- | ardor3d-extras/src/main/java/com/ardor3d/extension/model/obj/ObjExporter.java | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/ardor3d-extras/src/main/java/com/ardor3d/extension/model/obj/ObjExporter.java b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/obj/ObjExporter.java new file mode 100644 index 0000000..3c2bfa0 --- /dev/null +++ b/ardor3d-extras/src/main/java/com/ardor3d/extension/model/obj/ObjExporter.java @@ -0,0 +1,371 @@ +/** + * Copyright (c) 2008-2012 Ardor Labs, Inc. + * + * This file is part of Ardor3D. + * + * Ardor3D is free software: you can redistribute it and/or modify it + * under the terms of its license which may be found in the accompanying + * LICENSE file or at <http://www.ardor3d.com/LICENSE>. + */ + +package com.ardor3d.extension.model.obj; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import com.ardor3d.extension.model.util.KeyframeController; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.renderer.state.TextureState; +import com.ardor3d.scenegraph.FloatBufferData; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.MeshData; +import com.ardor3d.scenegraph.hint.LightCombineMode; + +/** + * WaveFront OBJ exporter. It supports only the meshes. Several meshes can be exported into the same OBJ file. Only a + * few kinds of primitives are supported + * + * @author Julien Gouesse + */ +public class ObjExporter { + + private static final Logger logger = Logger.getLogger(ObjExporter.class.getName()); + + public ObjExporter() { + super(); + } + + /** + * Save a mesh to a single WaveFront OBJ file and a MTL file + * + * @param mesh + * mesh to export + * @param objFile + * WaveFront OBJ file + * @param mtlFile + * material file, optional + * @throws IOException + */ + public void save(final Mesh mesh, final File objFile, final File mtlFile) throws IOException { + if (mesh.getControllerCount() == 0 || !(mesh.getController(0) instanceof KeyframeController)) { + save(mesh, objFile, mtlFile, false, 0, true, null, null); + } else { + final KeyframeController<?> controller = (KeyframeController<?>) mesh.getController(0); + final ArrayList<Mesh> meshList = new ArrayList<Mesh>(); + for (final KeyframeController.PointInTime pit : controller._keyframes) { + if (pit != null && pit._newShape != null) { + meshList.add(pit._newShape); + } + } + save(meshList, objFile, mtlFile, getLocalMeshTextureName(mesh)); + } + } + + /** + * Save several meshes to a single WaveFront OBJ file and a MTL file + * + * @param meshList + * meshes to export + * @param objFile + * WaveFront OBJ file + * @param mtlFile + * material file, optional + * @param customTextureName + * texture name that overrides the one of the mesh (except in key frames), optional + * @throws IOException + */ + public void save(final List<Mesh> meshList, final File objFile, final File mtlFile, final String customTextureName) + throws IOException { + int firstVertexIndex = 0; + boolean firstFiles = true; + final List<ObjMaterial> materialList = new ArrayList<ObjMaterial>(); + for (final Mesh mesh : meshList) { + if (mesh != null) { + if (mesh.getControllerCount() == 0 || !(mesh.getController(0) instanceof KeyframeController)) { + save(mesh, objFile, mtlFile, !firstFiles, firstVertexIndex, firstFiles, materialList, + customTextureName); + firstFiles = false; + firstVertexIndex += mesh.getMeshData().getVertexCount(); + } else { + final KeyframeController<?> controller = (KeyframeController<?>) mesh.getController(0); + final ArrayList<Mesh> subMeshList = new ArrayList<Mesh>(); + for (final KeyframeController.PointInTime pit : controller._keyframes) { + if (pit != null && pit._newShape != null) { + subMeshList.add(pit._newShape); + } + } + final String textureName = getLocalMeshTextureName(mesh); + for (final Mesh submesh : subMeshList) { + save(submesh, objFile, mtlFile, !firstFiles, firstVertexIndex, firstFiles, materialList, + textureName); + firstFiles = false; + firstVertexIndex += submesh.getMeshData().getVertexCount(); + } + } + } + } + } + + /** + * Save a mesh to the given files. + * + * @param mesh + * mesh to export + * @param objFile + * WaveFront OBJ file + * @param mtlFile + * material file, optional + * @param append + * indicates whether the data are written to the end of the OBJ file + * @param firstVertexIndex + * first vertex index used for this mesh during the export + * @param firstFiles + * indicates whether the couple of files is used for the first time, i.e there is nothing to append + * despite the value of <code>append</code> + * @param materialList + * list of materials already exported in this material file + * @param customTextureName + * texture name that overrides the one of the mesh, optional + * @throws IOException + */ + protected void save(final Mesh mesh, final File objFile, final File mtlFile, final boolean append, + final int firstVertexIndex, final boolean firstFiles, final List<ObjMaterial> materialList, + final String customTextureName) throws IOException { + File parentDirectory = objFile.getParentFile(); + if (parentDirectory != null && !parentDirectory.exists()) { + parentDirectory.mkdirs(); + } + if (mtlFile != null) { + parentDirectory = mtlFile.getParentFile(); + if (parentDirectory != null && !parentDirectory.exists()) { + parentDirectory.mkdirs(); + } + } + PrintWriter objPw = null, mtlPw = null; + try { + // fills the MTL file + final String mtlName; + if (mtlFile != null) { + final FileOutputStream mtlOs = new FileOutputStream(mtlFile, append); + mtlPw = new PrintWriter(new BufferedOutputStream(mtlOs)); + // writes some comments + if (firstFiles) { + mtlPw.println("# Ardor3D 1.0 MTL file"); + } + if (mesh.getLocalRenderState(StateType.Material) != null) { + // TODO + } + if (mesh.getLocalRenderState(StateType.Blend) != null) { + // TODO + } + final String currentTextureName; + if (customTextureName == null) { + currentTextureName = getLocalMeshTextureName(mesh); + } else { + currentTextureName = customTextureName; + } + + final int currentIllumination; + if (mesh.getSceneHints().getLightCombineMode() == LightCombineMode.Off) { + // Color on and Ambient off + currentIllumination = 0; + } else { + // Color on and Ambient on + currentIllumination = 1; + } + ObjMaterial sameObjMtl = null; + if (materialList != null && !materialList.isEmpty()) { + for (final ObjMaterial mtl : materialList) { + // TODO support more parameters + if (mtl.illumType == currentIllumination + && (currentTextureName == null && mtl.textureName == null || currentTextureName != null + && mtl.textureName != null && currentTextureName.equals(mtl.textureName))) { + sameObjMtl = mtl; + break; + } + } + } + if (sameObjMtl == null) { + // writes the new material library + mtlName = mtlFile.getName().trim().replaceAll(" ", "") + "_" + + (materialList == null ? 1 : materialList.size() + 1); + if (materialList != null) { + final ObjMaterial mtl = new ObjMaterial(mtlName); + mtl.illumType = currentIllumination; + mtl.textureName = currentTextureName; + materialList.add(mtl); + } + mtlPw.println("newmtl " + mtlName); + if (currentTextureName != null) { + mtlPw.println("map_Kd " + currentTextureName); + } + mtlPw.println("illum " + currentIllumination); + } else { + mtlName = sameObjMtl.getName(); + } + } else { + mtlName = null; + } + + final FileOutputStream objOs = new FileOutputStream(objFile, append); + objPw = new PrintWriter(new BufferedOutputStream(objOs)); + // writes some comments + if (firstFiles) { + objPw.println("# Ardor3D 1.0 OBJ file"); + objPw.println("# www.ardor3d.com"); + // writes the material file name if any + if (mtlFile != null) { + final String mtlLibFilename = mtlFile.getName(); + objPw.println("mtllib " + mtlLibFilename); + } + } + // writes the object name + final String objName; + String meshName = mesh.getName(); + // removes all spaces from the mesh name + if (meshName != null && !meshName.isEmpty()) { + meshName = meshName.trim().replaceAll(" ", ""); + } + if (meshName != null && !meshName.isEmpty()) { + objName = meshName; + } else { + objName = "obj_mesh" + mesh.hashCode(); + } + objPw.println("o " + objName); + final MeshData meshData = mesh.getMeshData(); + // writes the coordinates + final FloatBufferData verticesData = meshData.getVertexCoords(); + if (verticesData == null) { + throw new IllegalArgumentException("cannot export a mesh with no vertices"); + } + final int expectedTupleCount = verticesData.getTupleCount(); + saveFloatBufferData(verticesData, objPw, "v", expectedTupleCount); + final FloatBufferData texCoordsData = meshData.getTextureCoords(0); + saveFloatBufferData(texCoordsData, objPw, "vt", expectedTupleCount); + final FloatBufferData normalsData = meshData.getNormalCoords(); + saveFloatBufferData(normalsData, objPw, "vn", expectedTupleCount); + // writes the used material library + if (mtlFile != null) { + objPw.println("usemtl " + mtlName); + } + // writes the faces + for (int sectionIndex = 0; sectionIndex < meshData.getSectionCount(); sectionIndex++) { + final IndexMode indexMode = meshData.getIndexMode(sectionIndex); + final int[] indices = new int[indexMode.getVertexCount()]; + switch (indexMode) { + case TriangleFan: + case Triangles: + case TriangleStrip: + case Quads: + case QuadStrip: + for (int primIndex = 0, primCount = meshData.getPrimitiveCount(sectionIndex); primIndex < primCount; primIndex++) { + meshData.getPrimitiveIndices(primIndex, sectionIndex, indices); + // FIXME it should be done in MeshData.getVertexIndex() to preserve the order of the + // vertices + if (indexMode == IndexMode.TriangleStrip && primIndex % 2 == 1) { + // swaps the first index and the second index + final int tmp = indices[0]; + indices[0] = indices[1]; + indices[1] = tmp; + } + objPw.println("# section index: " + sectionIndex + " primitive index: " + primIndex + + " primitive count: " + primCount + " mode: " + indexMode); + objPw.print("f"); + for (int vertexIndex = 0; vertexIndex < indices.length; vertexIndex++) { + // indices start at 1 in the WaveFront OBJ format whereas indices start at 0 in + // Ardor3D + final int shiftedIndex = indices[vertexIndex] + 1 + firstVertexIndex; + // vertex index + objPw.print(" " + shiftedIndex); + // texture coordinate index + if (texCoordsData != null) { + objPw.print("/" + shiftedIndex); + } + // normal coordinate index + if (normalsData != null) { + objPw.print("/" + shiftedIndex); + } + } + objPw.println(); + } + break; + default: + throw new IllegalArgumentException("index mode " + indexMode + " not supported"); + } + } + } catch (final Throwable t) { + throw new Error("Unable to save the mesh into an obj", t); + } finally { + if (objPw != null) { + objPw.flush(); + objPw.close(); + } + if (mtlPw != null) { + mtlPw.flush(); + mtlPw.close(); + } + } + } + + private String getLocalMeshTextureName(final Mesh mesh) { + final String textureName; + if (mesh.getLocalRenderState(StateType.Texture) != null) { + final TextureState textureState = (TextureState) mesh.getLocalRenderState(StateType.Texture); + if (textureState.isEnabled() && textureState.getTexture() != null) { + final String tmpTextureName = textureState.getTexture().getTextureKey().getSource().getName(); + final int lastIndexOfUnixPathSeparator = tmpTextureName.lastIndexOf('/'); + final int lastIndexOfWindowsPathSeparator = tmpTextureName.lastIndexOf('\\'); + if (lastIndexOfUnixPathSeparator != -1) { + textureName = tmpTextureName.substring(lastIndexOfUnixPathSeparator + 1); + } else { + if (lastIndexOfWindowsPathSeparator != -1) { + textureName = tmpTextureName.substring(lastIndexOfWindowsPathSeparator + 1); + } else { + textureName = tmpTextureName; + } + } + } else { + textureName = null; + } + } else { + textureName = null; + } + return textureName; + } + + private void saveFloatBufferData(final FloatBufferData data, final PrintWriter objPw, final String keyword, + final int expectedTupleCount) { + if (data != null) { + if (keyword == null || keyword.isEmpty()) { + throw new IllegalArgumentException("null or empty keyword not supported"); + } else { + final int tupleSize = data.getValuesPerTuple(); + final int tupleCount = data.getTupleCount(); + if (tupleCount < expectedTupleCount) { + throw new IllegalArgumentException("[" + keyword + + "] not enough data to match with the vertex count: " + tupleCount + " < " + + expectedTupleCount); + } else { + if (tupleCount > expectedTupleCount) { + ObjExporter.logger.warning("[" + keyword + "] too much data to match with the vertex count: " + + tupleCount + " > " + expectedTupleCount + ". Skips useless tuple(s)"); + } + } + for (int tupleIndex = 0; tupleIndex < expectedTupleCount; tupleIndex++) { + objPw.print(keyword); + for (int valueIndex = 0; valueIndex < tupleSize; valueIndex++) { + objPw.print(" " + data.getBuffer().get(tupleIndex * tupleSize + valueIndex)); + } + objPw.println(); + } + } + } + } +} |