/// <summary> /// Exports a model to the Wavefront OBJ format. /// </summary> /// <param name="model">The model to be exported</param> /// <param name="fileName">The output file name</param> /// <param name="modelIndex">The index of the model that should be exported</param> public static void export(RenderBase.OModelGroup model, string fileName, int modelIndex) { StringBuilder output = new StringBuilder(); RenderBase.OModel mdl = model.model[modelIndex]; int faceIndexBase = 1; for (int objIndex = 0; objIndex < mdl.mesh.Count; objIndex++) { output.AppendLine("g " + mdl.mesh[objIndex].name); output.AppendLine(null); output.AppendLine("usemtl " + mdl.material[mdl.mesh[objIndex].materialId].name0 + ".png"); output.AppendLine(null); MeshUtils.optimizedMesh obj = MeshUtils.optimizeMesh(mdl.mesh[objIndex]); foreach (RenderBase.OVertex vertex in obj.vertices) { output.AppendLine("v " + getString(vertex.position.x) + " " + getString(vertex.position.y) + " " + getString(vertex.position.z)); output.AppendLine("vn " + getString(vertex.normal.x) + " " + getString(vertex.normal.y) + " " + getString(vertex.normal.z)); output.AppendLine("vt " + getString(vertex.texture0.x) + " " + getString(vertex.texture0.y)); } output.AppendLine(null); for (int i = 0; i < obj.indices.Count; i += 3) { output.AppendLine( string.Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}", faceIndexBase + obj.indices[i], faceIndexBase + obj.indices[i + 1], faceIndexBase + obj.indices[i + 2])); } faceIndexBase += obj.vertices.Count; output.AppendLine(null); } File.WriteAllText(fileName, output.ToString()); }
/// <summary> /// Exports a Model to the Collada format. /// See: https://www.khronos.org/files/collada_spec_1_4.pdf for more information. /// </summary> /// <param name="model">The Model that will be exported</param> /// <param name="fileName">The output File Name</param> /// <param name="modelIndex">Index of the model to be exported</param> /// <param name="skeletalAnimationIndex">(Optional) Index of the skeletal animation</param> public static void export(RenderBase.OModelGroup model, string fileName, int modelIndex, int skeletalAnimationIndex = -1) { RenderBase.OModel mdl = model.model[modelIndex]; COLLADA dae = new COLLADA(); dae.asset.created = DateTime.Now.ToString("yyyy-MM-ddThh:mm:ssZ"); dae.asset.modified = dae.asset.created; foreach (RenderBase.OTexture tex in model.texture) { daeImage img = new daeImage(); img.id = tex.name + "_id"; img.name = tex.name; img.init_from = "./" + tex.name + ".png"; dae.library_images.Add(img); } foreach (RenderBase.OMaterial mat in mdl.material) { daeMaterial mtl = new daeMaterial(); mtl.name = mat.name + "_mat"; mtl.id = mtl.name + "_id"; mtl.instance_effect.url = "#eff_" + mat.name + "_id"; dae.library_materials.Add(mtl); daeEffect eff = new daeEffect(); eff.id = "eff_" + mat.name + "_id"; eff.name = "eff_" + mat.name; daeParam surface = new daeParam(); surface.surface = new daeParamSurfaceElement(); surface.sid = "img_surface"; surface.surface.type = "2D"; surface.surface.init_from = mat.name0 + "_id"; surface.surface.format = "PNG"; eff.profile_COMMON.newparam.Add(surface); daeParam sampler = new daeParam(); sampler.sampler2D = new daeParamSampler2DElement(); sampler.sid = "img_sampler"; sampler.sampler2D.source = "img_surface"; switch (mat.textureMapper[0].wrapU) { case RenderBase.OTextureWrap.repeat: sampler.sampler2D.wrap_s = "WRAP"; break; case RenderBase.OTextureWrap.mirroredRepeat: sampler.sampler2D.wrap_s = "MIRROR"; break; case RenderBase.OTextureWrap.clampToEdge: sampler.sampler2D.wrap_s = "CLAMP"; break; case RenderBase.OTextureWrap.clampToBorder: sampler.sampler2D.wrap_s = "BORDER"; break; default: sampler.sampler2D.wrap_s = "NONE"; break; } switch (mat.textureMapper[0].wrapV) { case RenderBase.OTextureWrap.repeat: sampler.sampler2D.wrap_t = "WRAP"; break; case RenderBase.OTextureWrap.mirroredRepeat: sampler.sampler2D.wrap_t = "MIRROR"; break; case RenderBase.OTextureWrap.clampToEdge: sampler.sampler2D.wrap_t = "CLAMP"; break; case RenderBase.OTextureWrap.clampToBorder: sampler.sampler2D.wrap_t = "BORDER"; break; default: sampler.sampler2D.wrap_t = "NONE"; break; } switch (mat.textureMapper[0].minFilter) { case RenderBase.OTextureMinFilter.linearMipmapLinear: sampler.sampler2D.minfilter = "LINEAR_MIPMAP_LINEAR"; break; case RenderBase.OTextureMinFilter.linearMipmapNearest: sampler.sampler2D.minfilter = "LINEAR_MIPMAP_NEAREST"; break; case RenderBase.OTextureMinFilter.nearestMipmapLinear: sampler.sampler2D.minfilter = "NEAREST_MIPMAP_LINEAR"; break; case RenderBase.OTextureMinFilter.nearestMipmapNearest: sampler.sampler2D.minfilter = "NEAREST_MIPMAP_NEAREST"; break; default: sampler.sampler2D.minfilter = "NONE"; break; } switch (mat.textureMapper[0].magFilter) { case RenderBase.OTextureMagFilter.linear: sampler.sampler2D.magfilter = "LINEAR"; break; case RenderBase.OTextureMagFilter.nearest: sampler.sampler2D.magfilter = "NEAREST"; break; default: sampler.sampler2D.magfilter = "NONE"; break; } sampler.sampler2D.mipfilter = sampler.sampler2D.magfilter; eff.profile_COMMON.newparam.Add(sampler); eff.profile_COMMON.technique.sid = "img_technique"; eff.profile_COMMON.technique.phong.emission.set(Color.Black); eff.profile_COMMON.technique.phong.ambient.set(Color.Black); eff.profile_COMMON.technique.phong.specular.set(Color.White); eff.profile_COMMON.technique.phong.diffuse.texture.texture = "img_sampler"; dae.library_effects.Add(eff); } string jointNames = null; string invBindPoses = null; for (int index = 0; index < mdl.skeleton.Count; index++) { RenderBase.OMatrix transform = new RenderBase.OMatrix(); transformSkeleton(mdl.skeleton, index, ref transform); jointNames += mdl.skeleton[index].name; daeMatrix mtx = new daeMatrix(); mtx.set(transform.invert()); invBindPoses += mtx.data; if (index < mdl.skeleton.Count - 1) { jointNames += " "; invBindPoses += " "; } } int meshIndex = 0; daeVisualScene vs = new daeVisualScene(); vs.name = "vs_" + mdl.name; vs.id = vs.name + "_id"; if (mdl.skeleton.Count > 0) { writeSkeleton(mdl.skeleton, 0, ref vs.node); } foreach (RenderBase.OMesh obj in mdl.mesh) { //Geometry daeGeometry geometry = new daeGeometry(); string meshName = "mesh_" + meshIndex++ + "_" + obj.name; geometry.id = meshName + "_id"; geometry.name = meshName; MeshUtils.optimizedMesh mesh = MeshUtils.optimizeMesh(obj); List <float> positions = new List <float>(); List <float> normals = new List <float>(); List <float> uv0 = new List <float>(); List <float> uv1 = new List <float>(); List <float> uv2 = new List <float>(); List <float> colors = new List <float>(); foreach (RenderBase.OVertex vtx in mesh.vertices) { positions.Add(vtx.position.x); positions.Add(vtx.position.y); positions.Add(vtx.position.z); if (mesh.hasNormal) { normals.Add(vtx.normal.x); normals.Add(vtx.normal.y); normals.Add(vtx.normal.z); } if (mesh.texUVCount > 0) { uv0.Add(vtx.texture0.x); uv0.Add(vtx.texture0.y); } if (mesh.texUVCount > 1) { uv1.Add(vtx.texture1.x); uv1.Add(vtx.texture1.y); } if (mesh.texUVCount > 2) { uv2.Add(vtx.texture2.x); uv2.Add(vtx.texture2.y); } if (mesh.hasColor) { colors.Add(((vtx.diffuseColor >> 16) & 0xff) / 255f); colors.Add(((vtx.diffuseColor >> 8) & 0xff) / 255f); colors.Add((vtx.diffuseColor & 0xff) / 255f); colors.Add(((vtx.diffuseColor >> 24) & 0xff) / 255f); } } daeSource position = new daeSource(); position.name = meshName + "_position"; position.id = position.name + "_id"; position.float_array = new daeFloatArray(); position.float_array.id = position.name + "_array_id"; position.float_array.set(positions); position.technique_common.accessor.source = "#" + position.float_array.id; position.technique_common.accessor.count = (uint)mesh.vertices.Count; position.technique_common.accessor.stride = 3; position.technique_common.accessor.addParam("X", "float"); position.technique_common.accessor.addParam("Y", "float"); position.technique_common.accessor.addParam("Z", "float"); geometry.mesh.source.Add(position); daeSource normal = new daeSource(); if (mesh.hasNormal) { normal.name = meshName + "_normal"; normal.id = normal.name + "_id"; normal.float_array = new daeFloatArray(); normal.float_array.id = normal.name + "_array_id"; normal.float_array.set(normals); normal.technique_common.accessor.source = "#" + normal.float_array.id; normal.technique_common.accessor.count = (uint)mesh.vertices.Count; normal.technique_common.accessor.stride = 3; normal.technique_common.accessor.addParam("X", "float"); normal.technique_common.accessor.addParam("Y", "float"); normal.technique_common.accessor.addParam("Z", "float"); geometry.mesh.source.Add(normal); } daeSource[] texUV = new daeSource[3]; for (int i = 0; i < mesh.texUVCount; i++) { texUV[i] = new daeSource(); texUV[i].name = meshName + "_uv" + i; texUV[i].id = texUV[i].name + "_id"; texUV[i].float_array = new daeFloatArray(); texUV[i].float_array.id = texUV[i].name + "_array_id"; texUV[i].technique_common.accessor.source = "#" + texUV[i].float_array.id; texUV[i].technique_common.accessor.count = (uint)mesh.vertices.Count; texUV[i].technique_common.accessor.stride = 2; texUV[i].technique_common.accessor.addParam("S", "float"); texUV[i].technique_common.accessor.addParam("T", "float"); geometry.mesh.source.Add(texUV[i]); } daeSource color = new daeSource(); if (mesh.hasColor) { color.name = meshName + "_color"; color.id = color.name + "_id"; color.float_array = new daeFloatArray(); color.float_array.id = color.name + "_array_id"; color.float_array.set(colors); color.technique_common.accessor.source = "#" + color.float_array.id; color.technique_common.accessor.count = (uint)mesh.vertices.Count; color.technique_common.accessor.stride = 4; color.technique_common.accessor.addParam("R", "float"); color.technique_common.accessor.addParam("G", "float"); color.technique_common.accessor.addParam("B", "float"); color.technique_common.accessor.addParam("A", "float"); geometry.mesh.source.Add(color); } geometry.mesh.vertices.id = meshName + "_vertices_id"; geometry.mesh.vertices.addInput("POSITION", "#" + position.id); geometry.mesh.triangles.material = mdl.material[obj.materialId].name; geometry.mesh.triangles.addInput("VERTEX", "#" + geometry.mesh.vertices.id); if (mesh.hasNormal) { geometry.mesh.triangles.addInput("NORMAL", "#" + normal.id); } if (mesh.hasColor) { geometry.mesh.triangles.addInput("COLOR", "#" + color.id); } if (mesh.texUVCount > 0) { texUV[0].float_array.set(uv0); geometry.mesh.triangles.addInput("TEXCOORD", "#" + texUV[0].id); } if (mesh.texUVCount > 1) { texUV[1].float_array.set(uv1); geometry.mesh.triangles.addInput("TEXCOORD", "#" + texUV[1].id, 0, 1); } if (mesh.texUVCount > 2) { texUV[2].float_array.set(uv2); geometry.mesh.triangles.addInput("TEXCOORD", "#" + texUV[2].id, 0, 2); } geometry.mesh.triangles.set(mesh.indices); dae.library_geometries.Add(geometry); bool hasNode = obj.vertices[0].node.Count > 0; bool hasWeight = obj.vertices[0].weight.Count > 0; bool hasController = hasNode && hasWeight; //Controller daeController controller = new daeController(); if (hasController) { controller.id = meshName + "_ctrl_id"; controller.skin.source = "#" + geometry.id; controller.skin.bind_shape_matrix.set(new RenderBase.OMatrix()); daeSource joints = new daeSource(); joints.id = meshName + "_ctrl_joint_names_id"; joints.Name_array = new daeNameArray(); joints.Name_array.id = meshName + "_ctrl_joint_names_array_id"; joints.Name_array.count = (uint)mdl.skeleton.Count; joints.Name_array.data = jointNames; joints.technique_common.accessor.source = "#" + joints.Name_array.id; joints.technique_common.accessor.count = joints.Name_array.count; joints.technique_common.accessor.stride = 1; joints.technique_common.accessor.addParam("JOINT", "Name"); controller.skin.src.Add(joints); daeSource bindPoses = new daeSource(); bindPoses.id = meshName + "_ctrl_inv_bind_poses_id"; bindPoses.float_array = new daeFloatArray(); bindPoses.float_array.id = meshName + "_ctrl_inv_bind_poses_array_id"; bindPoses.float_array.count = (uint)(mdl.skeleton.Count * 16); bindPoses.float_array.data = invBindPoses; bindPoses.technique_common.accessor.source = "#" + bindPoses.float_array.id; bindPoses.technique_common.accessor.count = (uint)mdl.skeleton.Count; bindPoses.technique_common.accessor.stride = 16; bindPoses.technique_common.accessor.addParam("TRANSFORM", "float4x4"); controller.skin.src.Add(bindPoses); daeSource weights = new daeSource(); weights.id = meshName + "_ctrl_weights_id"; weights.float_array = new daeFloatArray(); weights.float_array.id = meshName + "_ctrl_weights_array_id"; weights.technique_common.accessor.source = "#" + weights.float_array.id; weights.technique_common.accessor.stride = 1; weights.technique_common.accessor.addParam("WEIGHT", "float"); StringBuilder w = new StringBuilder(); StringBuilder vcount = new StringBuilder(); StringBuilder v = new StringBuilder(); float[] wLookBack = new float[32]; uint wLookBackIndex = 0; int buffLen = 0; int wIndex = 0; int wCount = 0; foreach (RenderBase.OVertex vtx in mesh.vertices) { int count = Math.Min(vtx.node.Count, vtx.weight.Count); vcount.Append(count + " "); for (int n = 0; n < count; n++) { v.Append(vtx.node[n] + " "); bool found = false; uint bPos = (wLookBackIndex - 1) & 0x1f; for (int i = 0; i < buffLen; i++) { if (wLookBack[bPos] == vtx.weight[n]) { v.Append(wIndex - (i + 1) + " "); found = true; break; } bPos = (bPos - 1) & 0x1f; } if (!found) { v.Append(wIndex++ + " "); w.Append(vtx.weight[n].ToString(CultureInfo.InvariantCulture) + " "); wCount++; wLookBack[wLookBackIndex] = vtx.weight[n]; wLookBackIndex = (wLookBackIndex + 1) & 0x1f; if (buffLen < wLookBack.Length) { buffLen++; } } } } weights.float_array.data = w.ToString().TrimEnd(); weights.float_array.count = (uint)wCount; weights.technique_common.accessor.count = (uint)wCount; controller.skin.src.Add(weights); controller.skin.vertex_weights.vcount = vcount.ToString().TrimEnd(); controller.skin.vertex_weights.v = v.ToString().TrimEnd(); controller.skin.vertex_weights.count = (uint)mesh.vertices.Count; controller.skin.joints.addInput("JOINT", "#" + joints.id); controller.skin.joints.addInput("INV_BIND_MATRIX", "#" + bindPoses.id); controller.skin.vertex_weights.addInput("JOINT", "#" + joints.id); controller.skin.vertex_weights.addInput("WEIGHT", "#" + weights.id, 1); if (dae.library_controllers == null) { dae.library_controllers = new List <daeController>(); } dae.library_controllers.Add(controller); } //Visual scene node daeNode node = new daeNode(); node.name = "vsn_" + meshName; node.id = node.name + "_id"; node.matrix.set(new RenderBase.OMatrix()); if (hasController) { node.instance_controller = new daeInstanceController(); node.instance_controller.url = "#" + controller.id; node.instance_controller.skeleton = "#" + mdl.skeleton[0].name + "_bone_id"; node.instance_controller.bind_material.technique_common.instance_material.symbol = mdl.material[obj.materialId].name; node.instance_controller.bind_material.technique_common.instance_material.target = "#" + mdl.material[obj.materialId].name + "_mat_id"; } else { node.instance_geometry = new daeInstanceGeometry(); node.instance_geometry.url = "#" + geometry.id; node.instance_geometry.bind_material.technique_common.instance_material.symbol = mdl.material[obj.materialId].name; node.instance_geometry.bind_material.technique_common.instance_material.target = "#" + mdl.material[obj.materialId].name + "_mat_id"; } vs.node.Add(node); } dae.library_visual_scenes.Add(vs); daeInstaceVisualScene scene = new daeInstaceVisualScene(); scene.url = "#" + vs.id; dae.scene.Add(scene); XmlWriterSettings settings = new XmlWriterSettings { Encoding = Encoding.UTF8, Indent = true }; XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); ns.Add("", "http://www.collada.org/2005/11/COLLADASchema"); XmlSerializer serializer = new XmlSerializer(typeof(COLLADA)); XmlWriter output = XmlWriter.Create(new FileStream(fileName, FileMode.Create), settings); serializer.Serialize(output, dae, ns); output.Close(); }
/// <summary> /// Exports a model to the Wavefront OBJ format. /// </summary> /// <param name="model">The model to be exported</param> /// <param name="fileName">The output file name</param> /// <param name="modelIndex">The index of the model that should be exported</param> public static void export(RenderBase.OModelGroup model, string fileName, int modelIndex) { for (int i = 0; i < model.texture.Count; i++) { if (model.texture[i].name.Contains("_alb")) { model.texture[i].texture.Save(Path.Combine(Path.GetDirectoryName(fileName), "Textures", model.texture[i].name + ".bmp")); } } if (model.model.Count == 0) { return; } StringBuilder output = new StringBuilder(); RenderBase.OModel mdl = model.model[modelIndex]; output.AppendLine($"mtllib {Path.GetFileNameWithoutExtension(fileName)}.mtl"); int faceIndexBase = 1; for (int objIndex = 0; objIndex < mdl.mesh.Count; objIndex++) { output.AppendLine("g " + mdl.mesh[objIndex].name); output.AppendLine(null); output.AppendLine("usemtl " + mdl.material[mdl.mesh[objIndex].materialId].name0); output.AppendLine(null); MeshUtils.optimizedMesh obj = MeshUtils.optimizeMesh(mdl.mesh[objIndex]); foreach (RenderBase.OVertex vertex in obj.vertices) { output.AppendLine("v " + getString(vertex.position.x) + " " + getString(vertex.position.y) + " " + getString(vertex.position.z)); output.AppendLine("vn " + getString(vertex.normal.x) + " " + getString(vertex.normal.y) + " " + getString(vertex.normal.z)); output.AppendLine("vt " + getString(vertex.texture0.x) + " " + getString(vertex.texture0.y)); } output.AppendLine(null); for (int i = 0; i < obj.indices.Count; i += 3) { output.AppendLine( string.Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}", faceIndexBase + obj.indices[i], faceIndexBase + obj.indices[i + 1], faceIndexBase + obj.indices[i + 2])); } faceIndexBase += obj.vertices.Count; output.AppendLine(null); } File.WriteAllText(fileName, output.ToString()); if (mdl.material.Count == 0) { return; } output = new StringBuilder(); List <string> MatNames = new List <string>(); foreach (RenderBase.OMaterial mat in mdl.material) { if (!MatNames.Contains(mat.name0)) { MatNames.Add(mat.name0); output.AppendLine(null); output.AppendLine("newmtl " + mat.name0); output.AppendLine("Ka " + string.Format("{0} {1} {2}", mat.materialColor.ambient.R.ToString(), mat.materialColor.ambient.G.ToString(), mat.materialColor.ambient.B.ToString())); output.AppendLine("Kd " + string.Format("{0} {1} {2}", mat.materialColor.diffuse.R.ToString(), mat.materialColor.diffuse.G.ToString(), mat.materialColor.diffuse.B.ToString())); output.AppendLine("Ks 0 0 0"); output.AppendLine("d 1"); if (mat.name0 != null) { output.AppendLine("map_Kd " + @"Textures\" + mat.name0 + ".bmp"); } } } File.WriteAllText(Path.GetDirectoryName(fileName) + "\\" + Path.GetFileNameWithoutExtension(fileName) + ".mtl", output.ToString()); }
private mesh createMesh(RenderBase.OMesh input) { mesh output; output.descriptor.nodes = new List <uint>(); output.descriptor.attributes = new List <attributeDescriptor>(); output.descriptor.attributes.Add(new attributeDescriptor(0, 0, 1f)); //Position if (input.hasNormal) { output.descriptor.attributes.Add(new attributeDescriptor(1, 0, 1f)); } if (input.texUVCount > 0) { output.descriptor.attributes.Add(new attributeDescriptor(3, 0, 1f)); } if (input.hasNode) { output.descriptor.attributes.Add(new attributeDescriptor(5, 1, 1f)); } if (input.hasWeight) { output.descriptor.attributes.Add(new attributeDescriptor(6, 1, 0.00392156862f)); } MeshUtils.optimizedMesh optimized = MeshUtils.optimizeMesh(input); using (MemoryStream vertexStream = new MemoryStream()) { BinaryWriter writer = new BinaryWriter(vertexStream); foreach (RenderBase.OVertex vtx in optimized.vertices) { writer.Write(vtx.position.x); writer.Write(vtx.position.y); writer.Write(vtx.position.z); if (optimized.hasNormal) { writer.Write(vtx.normal.x); writer.Write(vtx.normal.y); writer.Write(vtx.normal.z); } if (optimized.texUVCount > 0) { writer.Write(vtx.texture0.x); writer.Write(vtx.texture0.y); } if (optimized.hasNode) { for (int i = 0; i < 2; i++) { if (i < vtx.node.Count) { int nodeIndex = output.descriptor.nodes.IndexOf((uint)vtx.node[i]); if (nodeIndex == -1) { writer.Write((byte)output.descriptor.nodes.Count); output.descriptor.nodes.Add((uint)vtx.node[i]); } else { writer.Write((byte)nodeIndex); } } else { writer.Write((byte)0); } } } if (optimized.hasWeight) { for (int i = 0; i < 2; i++) { if (i < vtx.weight.Count) { writer.Write((byte)(vtx.weight[i] * byte.MaxValue)); } else { writer.Write((byte)0); } } } } output.vertexBuffer = vertexStream.ToArray(); } using (MemoryStream indexStream = new MemoryStream()) { BinaryWriter writer = new BinaryWriter(indexStream); foreach (uint index in optimized.indices) { writer.Write((ushort)index); } output.indexBuffer = indexStream.ToArray(); } return(output); }