private void LoadMeshFromObj(string filename) { // Force garbage collection to ensure that unmanaged resources are released. // Temporary workaround until unmanaged resource leak is identified GC.Collect(); bool objectFound = false; //Dictionary<string, ObjectNode> objectTable = new Dictionary<string, ObjectNode>(); List<ObjectNode> objects = new List<ObjectNode>(); ObjectNode currentObject = new ObjectNode(); currentObject.Name = "Default"; int triangleCount = 0; int vertexCount = 0; // List<Mesh.Group> matGroups = new List<Mesh.Group>(); List<PositionNormalTextured> vertexList = new List<PositionNormalTextured>(); List<Vector3> vertList = new List<Vector3>(); List<Vector3> normList = new List<Vector3>(); List<Vector2> uvList = new List<Vector2>(); vertList.Add(new Vector3()); normList.Add(new Vector3()); uvList.Add(new Vector2()); List<int> indexList = new List<int>(); List<int> attribList = new List<int>(); List<int[]> applyLists = new List<int[]>(); List<int> applyListsIndex = new List<int>(); List<string> materialNames = new List<string>(); int currentMaterialIndex = -1; Material currentMaterial = new Material(); Mesh.Group currentGroup = new Mesh.Group(); int currentIndex = 0; //initialize the default material currentMaterial = new Material(); currentMaterial.Diffuse = Color; currentMaterial.Ambient = Color; currentMaterial.Specular = System.Drawing.Color.White; currentMaterial.SpecularSharpness = 30.0f; currentMaterial.Opacity = 1.0f; currentMaterial.Default = true; //initialize the group currentGroup.startIndex = 0; currentGroup.indexCount = 0; currentGroup.materialIndex = 0; using (Stream fs = new FileStream(filename, FileMode.Open,FileAccess.Read)) { StreamReader sr = new StreamReader(fs); while (!sr.EndOfStream) { string line = sr.ReadLine().Replace(" ", " "); string[] parts = line.Trim().Split(new char[] { ' ' }); if (parts.Length > 0) { switch (parts[0]) { case "mtllib": { string path = filename.Substring(0, filename.LastIndexOf('\\') + 1); string matFile = path + "\\" + parts[1]; LoadMatLib(matFile); } break; case "usemtl": string materialName = parts[1]; if (matLib.ContainsKey(materialName)) { if (currentMaterialIndex == -1 && currentIndex > 0) { addMaterial(currentMaterial); currentMaterialIndex++; } if (currentMaterialIndex > -1) { currentGroup.indexCount = currentIndex - currentGroup.startIndex; // matGroups.Add(currentGroup); currentObject.DrawGroup.Add(currentGroup); } currentMaterialIndex++; if (matLib.ContainsKey(materialName)) { currentMaterial = matLib[materialName]; if (textureLib.ContainsKey(materialName)) { try { if (!TextureCache.ContainsKey(textureLib[materialName])) { string path = filename.Substring(0, filename.LastIndexOf('\\') + 1); Texture11 tex = LoadTexture(path + textureLib[materialName]); if (tex != null) { meshFilenames.Add(textureLib[materialName]); TextureCache.Add(textureLib[materialName], tex); } } meshTextures.Add(TextureCache[textureLib[materialName]]); } catch { } } addMaterial(currentMaterial); currentGroup = new Mesh.Group(); currentGroup.startIndex = currentIndex; currentGroup.indexCount = 0; currentGroup.materialIndex = currentMaterialIndex; } } break; case "v": vertexCount++; if (FlipHandedness) { vertList.Add(new Vector3(-float.Parse(parts[1]), float.Parse(parts[2]), float.Parse(parts[3]))); } else { vertList.Add(new Vector3(float.Parse(parts[1]), float.Parse(parts[2]), float.Parse(parts[3]))); } break; case "vn": if (FlipHandedness) { normList.Add(new Vector3(-float.Parse(parts[1]), float.Parse(parts[2]), float.Parse(parts[3]))); } else { normList.Add(new Vector3(float.Parse(parts[1]), float.Parse(parts[2]), float.Parse(parts[3]))); } break; case "vt": uvList.Add(new Vector2(float.Parse(parts[1]), FlipV ? (1 - float.Parse(parts[2])) : float.Parse(parts[2]))); break; case "g": case "o": if (objectFound) { if (currentMaterialIndex > -1) { currentGroup.indexCount = currentIndex - currentGroup.startIndex; // matGroups.Add(currentGroup); currentObject.DrawGroup.Add(currentGroup); currentGroup = new Mesh.Group(); currentGroup.startIndex = currentIndex; currentGroup.indexCount = 0; currentGroup.materialIndex = currentMaterialIndex; } currentObject = new ObjectNode(); } objectFound = true; if (parts.Length > 1) { currentObject.Name = parts[1]; } else { currentObject.Name = "Unnamed"; } objects.Add(currentObject); //if (!objectTable.ContainsKey(currentObject.Name)) //{ // objectTable.Add(currentObject.Name, currentObject); //} break; case "f": int[] indexiesA = GetIndexies(parts[1]); int[] indexiesB = GetIndexies(parts[2]); int[] indexiesC = GetIndexies(parts[3]); vertexList.Add(new PositionNormalTextured(vertList[indexiesA[0]], normList[indexiesA[2]], uvList[indexiesA[1]])); vertexList.Add(new PositionNormalTextured(vertList[indexiesB[0]], normList[indexiesB[2]], uvList[indexiesB[1]])); vertexList.Add(new PositionNormalTextured(vertList[indexiesC[0]], normList[indexiesC[2]], uvList[indexiesC[1]])); if (FlipHandedness) { indexList.Add(currentIndex); indexList.Add(currentIndex + 2); indexList.Add(currentIndex + 1); } else { indexList.Add(currentIndex); indexList.Add(currentIndex + 1); indexList.Add(currentIndex + 2); } triangleCount++; currentIndex += 3; //bool flip = true; if (parts.Length > 4) { int partIndex = 4; while (partIndex < (parts.Length)) { if (FlipHandedness) { indexiesA = GetIndexies(parts[1]); indexiesC = GetIndexies(parts[partIndex]); indexiesB = GetIndexies(parts[partIndex - 1]); } else { indexiesA = GetIndexies(parts[1]); indexiesB = GetIndexies(parts[partIndex - 1]); indexiesC = GetIndexies(parts[partIndex]); } vertexList.Add(new PositionNormalTextured(vertList[indexiesA[0]], normList[indexiesA[2]], uvList[indexiesA[1]])); vertexList.Add(new PositionNormalTextured(vertList[indexiesB[0]], normList[indexiesB[2]], uvList[indexiesB[1]])); vertexList.Add(new PositionNormalTextured(vertList[indexiesC[0]], normList[indexiesC[2]], uvList[indexiesC[1]])); indexList.Add(currentIndex); indexList.Add(currentIndex + 1); indexList.Add(currentIndex + 2); triangleCount++; currentIndex += 3; partIndex++; } } break; } } } } if (!objectFound) { // add the default object objects.Add(currentObject); } if (currentMaterialIndex == -1 && currentIndex > 0) { addMaterial(currentMaterial); currentMaterialIndex++; } if (currentMaterialIndex > -1) { currentGroup.indexCount = currentIndex - currentGroup.startIndex; currentObject.DrawGroup.Add(currentGroup); } if (normList.Count < 2) { float creaseAngleRad = MathUtil.DegreesToRadians(Smooth ? 170.0f : 45.0f); Vector3[] vertexNormals = CalculateVertexNormalsMerged(vertexList, indexList, creaseAngleRad); List<PositionNormalTextured> newVertexList = new List<PositionNormalTextured>(); int newVertexCount = indexList.Count; for (int vertexIndex = 0; vertexIndex < newVertexCount; ++vertexIndex) { PositionNormalTextured v = vertexList[indexList[vertexIndex]]; v.Normal = vertexNormals[vertexIndex]; newVertexList.Add(v); } vertexList = newVertexList; } mesh = new Mesh(vertexList.ToArray(), indexList.ToArray()); ObjectNode rootDummy = new ObjectNode(); rootDummy.Name = "Root"; rootDummy.Parent = null; rootDummy.Level = -1; rootDummy.DrawGroup = null; rootDummy.Children = objects; Objects = new List<ObjectNode>(); Objects.Add(rootDummy); mesh.setObjects(Objects); //List<ObjectNode> objects = new List<ObjectNode>(); //ObjectNode node = new ObjectNode(); //node.Name = "Default"; //node.DrawGroup = matGroups; //objects.Add(node); //mesh.setObjects(objects); //Objects = objects; mesh.commitToDevice(RenderContext11.PrepDevice); dirty = false; GC.Collect(); }
private void LoadMeshFrom3ds(string filename, float scale) { // Force garbage collection to ensure that unmanaged resources are released. // Temporary workaround until unmanaged resource leak is identified GC.Collect(); int i; ushort sectionID; uint sectionLength; string name = ""; string material = ""; int triangleCount = 0; int vertexCount = 0; List<PositionNormalTextured> vertexList = new List<PositionNormalTextured>(); List<int> indexList = new List<int>(); List<int> attribList = new List<int>(); //List<int[]> applyLists = new List<int[]>(); //List<int> applyListsIndex = new List<int>(); List<string> materialNames = new List<string>(); int currentMaterialIndex = -1; Material currentMaterial = new Material(); int attributeID = 0; int count = 0; UInt16 lastID = 0; bool exit = false; bool normalMapFound = false; float offsetX = 0; float offsetY = 0; float offsetZ = 0; List<ObjectNode> objects = new List<ObjectNode>(); ObjectNode currentObject = null; List<int> objHierarchy = new List<int>(); List<string> objNames = new List<string>(); Dictionary<string, ObjectNode> objectTable = new Dictionary<string, ObjectNode>(); int dummyCount = 0; using (Stream fs = new FileStream(filename, FileMode.Open)) { BinaryReader br = new BinaryReader(fs); long length = fs.Length - 1; int startMapIndex = 0; int startTriangleIndex = 0; while (br.BaseStream.Position < length && !exit) //Loop to scan the whole file { sectionID = br.ReadUInt16(); sectionLength = br.ReadUInt32(); switch (sectionID) { //This section the begining of the file case 0x4d4d: break; // This section marks the edit section containing the 3d models (3d3d get it? very punny!) case 0x3d3d: break; // Object section contains meshes, etc. case 0x4000: name = ""; Byte b; do { b = br.ReadByte(); if (b > 0) { name += (char)b; } } while (b != '\0'); currentObject = new ObjectNode(); currentObject.Name = name; objects.Add(currentObject); if (!objectTable.ContainsKey(currentObject.Name)) { objectTable.Add(currentObject.Name, currentObject); } break; // Marks the start of a mesh section case 0x4100: startMapIndex = vertexList.Count; startTriangleIndex = indexList.Count / 3; break; // This section has the vertex list.. Maps to Vertext buffer in Direct3d case 0x4110: vertexCount = br.ReadUInt16(); for (i = 0; i < vertexCount; i++) { float x = br.ReadSingle() - offsetX; float y = br.ReadSingle() - offsetY; float z = br.ReadSingle() - offsetZ; PositionNormalTextured vert = new PositionNormalTextured(x * scale, z * scale, y * scale, 0, 0, 0, 0, 0); vertexList.Add(vert); } break; // This section is a tiangle index list. Maps to Index Buffer in Direct3d case 0x4120: { int triCount = br.ReadUInt16(); triangleCount += triCount; for (i = 0; i < triCount; i++) { int aa = br.ReadUInt16() + startMapIndex; int bb = br.ReadUInt16() + startMapIndex; int cc = br.ReadUInt16() + startMapIndex; indexList.Add(cc); indexList.Add(bb); indexList.Add(aa); UInt16 flags = br.ReadUInt16(); } } break; // Material for face from start face to triCount case 0x4130: { material = ""; i = 0; byte b1; do { b1 = br.ReadByte(); if (b1 > 0) { material += (char)b1; } i++; } while (b1 != '\0'); int triCount = br.ReadUInt16(); int[] applyList = new int[triCount]; attributeID = GetMaterialID(material, materialNames); for (i = 0; i < triCount; i++) { applyList[i] = br.ReadUInt16() + startTriangleIndex; } currentObject.ApplyLists.Add(applyList); currentObject.ApplyListsIndex.Add(attributeID); } break; // Section for UV texture maps case 0x4140: count = br.ReadUInt16(); for (i = 0; i < count; i++) { PositionNormalTextured vert = vertexList[startMapIndex + i]; Vector2 texCoord = new Vector2(br.ReadSingle(), FlipV ? (1.0f - br.ReadSingle()) : (br.ReadSingle())); vertexList[startMapIndex + i] = new PositionNormalTextured(vert.Position, Vector3.Zero, texCoord); } break; // Section for Smoothing Groups //case 0x4150: // count = br.ReadUInt16(); // for (i = 0; i < count; i++) // { // CustomVertex.PositionNormalTextured vert = vertexList[startMapIndex + i]; // vertexList[startMapIndex + i] = new CustomVertex.PositionNormalTextured(vert.Position, new Vector3(0,0,0), br.ReadSingle(), FlipV ? (1.0f - br.ReadSingle() ) : (br.ReadSingle())); // } // break; case 0x4160: float[] mat = new float[12]; for (i = 0; i < 12; i++) { mat[i] = br.ReadSingle(); } //offsetX = mat[9]; //offsetY = mat[11]; //offsetZ = mat[10]; if (objectTable.ContainsKey(name)) { objectTable[name].LocalMat = new Matrix3d( mat[0], mat[1], mat[2], 0, mat[3], mat[4], mat[5], 0, mat[6], mat[7], mat[8], 0, mat[9], mat[10], mat[11], 1); objectTable[name].LocalMat.Invert(); //objectTable[name].PivotPoint = new Vector3(mat[9]*mat[0],mat[10]*mat[1],mat[11]*mat[2]); } break; // Materials library section case 0xAFFF: break; // Material Name case 0xA000: { string matName = ""; i = 0; byte b2; do { b2 = br.ReadByte(); if (b2 > 0) { matName += (char)b2; } i++; } while (b2 != '\0'); materialNames.Add(matName); if (currentMaterialIndex > -1) { addMaterial(currentMaterial); } currentMaterialIndex++; currentMaterial = new Material(); currentMaterial.Diffuse = System.Drawing.Color.White; currentMaterial.Ambient = System.Drawing.Color.White; currentMaterial.Specular = System.Drawing.Color.Black; currentMaterial.SpecularSharpness = 30.0f; currentMaterial.Opacity = 1.0f; } break; // Ambient color case 0xA010: currentMaterial.Ambient = LoadColorChunk(br); break; // Diffuse color case 0xA020: currentMaterial.Diffuse = LoadColorChunk(br); break; // Specular color case 0xA030: currentMaterial.Specular = LoadColorChunk(br); break; // Specular power case 0xA040: // This is just a reasonable guess at the mapping from percentage to // specular exponent used by 3D Studio. currentMaterial.SpecularSharpness = 1.0f + 2 * LoadPercentageChunk(br); // Minimum sharpness of 10 enforced here because of bad specular exponents // in ISS model. // TODO: Fix ISS and permit lower specular exponents here currentMaterial.SpecularSharpness = Math.Max(10.0f, currentMaterial.SpecularSharpness); break; //Texture map file case 0xA200: break; // Texture file name case 0xA300: { string textureFilename = ""; i = 0; byte b2; do { b2 = br.ReadByte(); if (b2 > 0) { textureFilename += (char)b2; } i++; } while (b2 != '\0'); string path = filename.Substring(0, filename.LastIndexOf('\\') + 1); try { Texture11 tex = LoadTexture(path + textureFilename); if (tex != null) { meshTextures.Add(tex); meshFilenames.Add(textureFilename); // The ISS model has black for the diffuse color; to work around this // we'll set the diffuse color to white when there's a texture present. // The correct fix is to modify the 3ds model of ISS. currentMaterial.Diffuse = System.Drawing.Color.White; } else { meshTextures.Add(null); } } catch { meshTextures.Add(null); } } break; // Bump map case 0xA230: { float percentage = LoadPercentageChunk(br); int nameId = br.ReadUInt16(); uint nameBlockLength = br.ReadUInt32(); string textureFilename = ""; i = 0; byte b2; do { b2 = br.ReadByte(); if (b2 > 0) { textureFilename += (char)b2; } i++; } while (b2 != '\0'); string path = filename.Substring(0, filename.LastIndexOf('\\') + 1); try { Texture11 tex = LoadTexture(path + textureFilename); if (tex != null) { meshNormalMaps.Add(tex); meshFilenames.Add(textureFilename); // Indicate that we have a normal map so that we know to generate tangent vectors for the mesh normalMapFound = true; } else { meshNormalMaps.Add(null); } } catch { meshNormalMaps.Add(null); } } break; // Specular map case 0xA204: { float strength = LoadPercentageChunk(br); int nameId = br.ReadUInt16(); uint nameBlockLength = br.ReadUInt32(); string textureFilename = ""; i = 0; byte b2; do { b2 = br.ReadByte(); if (b2 > 0) { textureFilename += (char)b2; } i++; } while (b2 != '\0'); string path = filename.Substring(0, filename.LastIndexOf('\\') + 1); try { Texture11 tex = LoadTexture(path + textureFilename); if (tex != null) { meshSpecularTextures.Add(tex); meshFilenames.Add(textureFilename); // Set the current specular color from the specular texture strength int gray = (int)(255.99f * strength / 100.0f); currentMaterial.Specular = System.Drawing.Color.FromArgb(255, gray, gray, gray); } else { meshSpecularTextures.Add(null); } } catch { meshSpecularTextures.Add(null); } } break; case 0xB000: break; case 0xB002: break; case 0xB010: { name = ""; i = 0; byte b1; do { b1 = br.ReadByte(); if (b1 > 0) { name += (char)b1; } i++; } while (b1 != '\0'); int dum1 = (int)br.ReadUInt16(); int dum2 = (int)br.ReadUInt16(); int level = (int)br.ReadUInt16(); if (level == 65535) { level = -1; } if (name.StartsWith("$")) { dummyCount++; } else { objNames.Add(name); } objHierarchy.Add(level); if (objectTable.ContainsKey(name)) { objectTable[name].Level = level; } } break; case 0xB011: { name = ""; i = 0; byte b1; do { b1 = br.ReadByte(); if (b1 > 0) { name += (char)b1; } i++; } while (b1 != '\0'); objNames.Add( "$$$" + name); } break; case 0xB013: //pivot point float[] points = new float[3]; for (i = 0; i < 3; i++) { points[i] = br.ReadSingle(); } if (objectTable.ContainsKey(name)) { objectTable[name].PivotPoint = -new Vector3(points[0], points[1], points[2]); } break; case 0xB020: { float[] pos = new float[8]; for (i = 0; i < 8; i++) { pos[i] = br.ReadSingle(); } } break; // If we don't recognize a section then jump over it. Subract the header from the section length default: br.BaseStream.Seek(sectionLength - 6, SeekOrigin.Current); break; } lastID = sectionID; } br.Close(); if (currentMaterialIndex > -1) { addMaterial(currentMaterial); } } ////debug //for ( i = 0; i < 99; i++) //{ // System.Diagnostics.Debug.WriteLine(objNames[i]); //} //foreach (ObjectNode node in objects) //{ // System.Diagnostics.Debug.WriteLine(node.Name); //} ////debug // Generate vertex normals // Vertex normals are computed by averaging the normals of all faces // with an angle between them less than the crease angle. By setting // the crease angle to 0 degrees, the model will have a faceted appearance. // Right now, the smooth flag selects between one of two crease angles, // but some smoothing is always applied. float creaseAngleRad = MathUtil.DegreesToRadians(Smooth ? 70.0f : 45.0f); Vector3[] vertexNormals = CalculateVertexNormalsMerged(vertexList, indexList, creaseAngleRad); List<PositionNormalTextured> newVertexList = new List<PositionNormalTextured>(); int newVertexCount = triangleCount * 3; for (int vertexIndex = 0; vertexIndex < newVertexCount; ++vertexIndex) { PositionNormalTextured v = vertexList[indexList[vertexIndex]]; v.Normal = vertexNormals[vertexIndex]; newVertexList.Add(v); } // Use the triangle mesh and material assignments to create a single // index list for the mesh. List<uint> newIndexList = new List<uint>(); foreach (ObjectNode node in objects) { List<Mesh.Group> materialGroups = new List<Mesh.Group>(); for (i = 0; i < node.ApplyLists.Count; i++) { int matId = node.ApplyListsIndex[i]; int startIndex = newIndexList.Count; foreach (int triangleIndex in node.ApplyLists[i]) { newIndexList.Add((uint)(triangleIndex * 3)); newIndexList.Add((uint)(triangleIndex * 3 + 1)); newIndexList.Add((uint)(triangleIndex * 3 + 2)); } var group = new Mesh.Group(); group.startIndex = startIndex; group.indexCount = node.ApplyLists[i].Length * 3; group.materialIndex = matId; materialGroups.Add(group); } node.DrawGroup = materialGroups; } // Turn objects into tree Stack<ObjectNode> nodeStack = new Stack<ObjectNode>(); List<ObjectNode> nodeTreeRoot = new List<ObjectNode>(); ObjectNode rootDummy = new ObjectNode(); rootDummy.Name = "Root"; rootDummy.Parent = null; rootDummy.Level = -1; rootDummy.DrawGroup = null; int currentLevel = -1; nodeStack.Push(rootDummy); nodeTreeRoot.Add(rootDummy); for (i = 0; i < objHierarchy.Count; i++) { int level = objHierarchy[i]; if (level <= currentLevel) { // pop out all the nodes to intended parent while (level <= nodeStack.Peek().Level && nodeStack.Count > 1) { nodeStack.Pop(); } currentLevel = level; } if (objNames[i].StartsWith("$$$")) { ObjectNode dummy = new ObjectNode(); dummy.Name = objNames[i].Replace("$$$", ""); dummy.Parent = nodeStack.Peek(); dummy.Parent.Children.Add(dummy); dummy.Level = currentLevel = level; dummy.DrawGroup = null; nodeStack.Push(dummy); } else { objectTable[objNames[i]].Level = currentLevel = level; objectTable[objNames[i]].Parent = nodeStack.Peek(); objectTable[objNames[i]].Parent.Children.Add(objectTable[objNames[i]]); nodeStack.Push(objectTable[objNames[i]]); } } if (objHierarchy.Count == 0) { foreach (ObjectNode node in objects) { rootDummy.Children.Add(node); node.Parent = rootDummy; } } if (normalMapFound) { // If we've got a normal map, we want to generate tangent vectors for the mesh // Mapping of vertices to geometry is extremely straightforward now, but this could // change when a mesh optimization step is introduced. var tangentIndexList = new List<uint>(); for (uint tangentIndex = 0; tangentIndex < newVertexCount; ++tangentIndex) { tangentIndexList.Add(tangentIndex); } Vector3[] tangents = CalculateVertexTangents(newVertexList, tangentIndexList, creaseAngleRad); // Copy the tangents in the vertex data list var vertices = new PositionNormalTexturedTangent[newVertexList.Count]; int vertexIndex = 0; foreach (PositionNormalTextured v in newVertexList) { PositionNormalTexturedTangent tvertex = new PositionNormalTexturedTangent(v.Position, v.Normal, new Vector2(v.Tu, v.Tv), tangents[vertexIndex]); vertices[vertexIndex] = tvertex; ++vertexIndex; } mesh = new Mesh(vertices, newIndexList.ToArray()); } else { mesh = new Mesh(newVertexList.ToArray(), newIndexList.ToArray()); } Objects = nodeTreeRoot; mesh.setObjects(nodeTreeRoot); mesh.commitToDevice(RenderContext11.PrepDevice); dirty = false; GC.Collect(); }