private void loadModelData(BlenderFile file) { models = new List <BlenderModel>(); transparentModels = new List <BlenderModel>(); currentLayer = 1; Structure curscene = file.GetStructuresOfType("FileGlobal")[0]["curscene"].Dereference()[0]; ulong next = curscene["base.first"].Value; while (next != 0) { Structure objBase = file.GetStructuresByAddress(next)[0]; Structure obj = objBase["object"].Dereference()[0]; IField data = obj["data"]; int SDNAIndex = file.GetBlockByAddress((data as Field <ulong>).Value).SDNAIndex; while (file.StructureDNA.StructureList[SDNAIndex].StructureTypeName != "Mesh") { ulong nextPointer = (objBase["next"] as Field <ulong>).Value; if (nextPointer == 0) { return; // we've run out of objects in the list, and haven't found any meshes } objBase = file.GetStructuresByAddress(nextPointer)[0]; obj = objBase["object"].Dereference()[0]; data = obj["data"]; SDNAIndex = file.GetBlockByAddress((data as Field <ulong>).Value).SDNAIndex; } Structure mesh = data.Dereference()[0]; BlenderModel model = new BlenderModel(mesh, obj, GraphicsDevice, file); if (model.TextureHasTransparency) { transparentModels.Add(model); } else { models.Add(model); } next = (objBase["next"] as Field <ulong>).Value; } }
internal Scene(BlenderFile file, PopulatedStructure scene) { Name = new string(scene["id.name"].GetValueAsCharArray()).Split('\0')[0].Substring(2); // todo: add lamp importing here string[] validTypeNames = new[] { "Mesh" }; List <BlenderObject> objects = new List <BlenderObject>(); int i = 0; ulong next = scene["base.first"].GetValueAsPointer(); ulong basact = scene["basact"].GetValueAsPointer(); // this is the address of the Base object while (next != 0) { if (next == basact) { activeObjectIndex = i; } PopulatedStructure objBase = file.GetStructuresByAddress(next)[0]; PopulatedStructure obj = file.GetStructuresByAddress(objBase["object"].GetValueAsPointer())[0]; Field data = obj["data"]; ulong ptr = data.GetValueAsPointer(); // this will be 0 for objects of the Empty type if (ptr == 0) { objects.Add(new BlenderObject(file, obj)); } else { int SDNAIndex = file.GetBlockByAddress(ptr).SDNAIndex; if (validTypeNames.Contains(file.StructureDNA.StructureList[SDNAIndex].StructureTypeName)) { objects.Add(new BlenderObject(file, obj)); } } next = objBase["next"].GetValueAsPointer(); i++; } }
internal Mesh(PopulatedStructure mesh, BlenderFile file) { FileBlock materialArray; int pointerSize = mesh["mat"].Size; Name = new string(mesh["id.name"].GetValueAsCharArray()).Split('\0')[0].Substring(2); ulong mat = mesh["mat"].GetValueAsPointer(); if (mat == 0 || (materialArray = file.GetBlockByAddress(mat)).Size % pointerSize != 0) { Materials = new Material[0]; } else { int count = materialArray.Size % pointerSize; Materials = new Material[count]; for (int i = 0; i < count; i++) { Materials[i] = Material.GetOrCreateMaterial( file, file.GetStructuresByAddress( pointerSize == 4 ? BitConverter.ToUInt32(materialArray.Data, count * pointerSize) : BitConverter.ToUInt64(materialArray.Data, count * pointerSize) )[0] ); } } float[] vectorTemp = mesh["loc"].GetValueAsFloatArray(); Location = new Vector3(vectorTemp[0], vectorTemp[1], vectorTemp[2]); vectorTemp = mesh["rot"].GetValueAsFloatArray(); Rotation = new Vector3(vectorTemp[0], vectorTemp[1], vectorTemp[2]); vectorTemp = mesh["size"].GetValueAsFloatArray(); Size = new Vector3(vectorTemp[0], vectorTemp[1], vectorTemp[2]); MeshBuilder primordialMesh = MeshBuilder.StartMesh(Name); // both structures use the same vertex structure List <Vector3> verts = new List <Vector3>(); List <short[]> unconvertedNormals = new List <short[]>(); foreach (PopulatedStructure s in file.GetStructuresByAddress(mesh["mvert"].GetValueAsPointer())) { float[] vector = s["co"].GetValueAsFloatArray(); unconvertedNormals.Add(s["no"].GetValueAsShortArray()); verts.Add(new Vector3(vector[0], vector[1], vector[2])); } List <Vector3> normals = convertNormals(unconvertedNormals); VertexPositionNormalTexture[] vertices; BasicMaterialContent bmc; // todo: not yet sure which format versions of Blender between 2.62 and 2.65 use. if (float.Parse(file.VersionNumber) >= 2.66f) // uses edges, loops, and polys (Blender 2.66+) { vertices = loadNewModel(file, mesh, verts, normals, out bmc); } else // uses MFace (Blender 2.49-2.61) { vertices = loadOldModel(file, mesh, verts, normals, out bmc); } MeshBuilder mb = MeshBuilder.StartMesh(Name); foreach (VertexPositionNormalTexture v in vertices) { mb.CreatePosition(v.Position); } int uvChannel = mb.CreateVertexChannel <Vector2>(VertexChannelNames.TextureCoordinate(0)); int normalChannel = mb.CreateVertexChannel <Vector3>(VertexChannelNames.Normal()); int j = 0; foreach (VertexPositionNormalTexture v in vertices) { mb.SetVertexChannelData(uvChannel, v.TextureCoordinate); mb.SetVertexChannelData(normalChannel, v.Normal); mb.AddTriangleVertex(j++); } }
private VertexPositionNormalTexture[] loadOldModel(BlenderFile file, PopulatedStructure mesh, List <Vector3> verts, List <Vector3> normals, out BasicMaterialContent bmc) { // I believe this function has a bug when used on a mesh that has unconnected chunks of vertices; // however the only file I currently have that exhibits this problem decompresses to 240MB when I use the HTML // renderer tool, so I can't feasibly poke through the data to see what's going wrong. List <VertexPositionNormalTexture> output = new List <VertexPositionNormalTexture>(); bmc = new BasicMaterialContent(); List <int[]> faces = new List <int[]>(); List <float[, ]> tFaces = new List <float[, ]>(); foreach (PopulatedStructure s in file.GetStructuresByAddress(mesh["mface"].GetValueAsPointer())) { faces.Add(new[] { s["v1"].GetValueAsInt(), s["v2"].GetValueAsInt(), s["v3"].GetValueAsInt(), s["v4"].GetValueAsInt() }); } foreach (PopulatedStructure s in file.GetStructuresByAddress(mesh["mtface"].GetValueAsPointer())) { tFaces.Add((float[, ])s["uv"].GetValueAsMultidimensionalArray()); } // assume all faces use same texture PopulatedStructure image = file.GetStructuresByAddress(file.GetStructuresByAddress(mesh["mtface"].GetValueAsPointer())[0]["tpage"].GetValueAsPointer())[0]; if (image["packedfile"].GetValueAsPointer() != 0) { byte[] rawImage = file.GetBlockByAddress(file.GetStructuresByAddress(image["packedfile"].GetValueAsPointer())[0]["data"].GetValueAsPointer()).Data; string filename = Name + "_" + new string(image["id.name"].GetValueAsCharArray()).Split('\0')[0].Substring(2); using (BinaryWriter s = new BinaryWriter(File.Open(filename, FileMode.Create))) s.Write(rawImage); bmc.Texture = new ExternalReference <TextureContent>(filename); } else { try { string texturePath = image["name"].ToString().Split('\0')[0].Replace("/", "\\").Replace("\\\\", "\\"); string filePath = file.GetStructuresOfType("FileGlobal")[0]["filename"].ToString(); filePath = filePath.Substring(0, filePath.LastIndexOf('\\')); File.Copy((filePath + texturePath).Replace("\'", ""), Name + "_" + new string(image["id.name"].GetValueAsCharArray()).Split('\0')[0].Substring(2)); } catch { //texture = defaultTex; } } int j = 0; foreach (int[] face in faces) { Vector3[] faceVerts = new Vector3[face.Length]; Vector3[] faceVertNormals = new Vector3[face.Length]; Vector2[] faceUVs = new Vector2[face.Length]; for (int i = 0; i < face.Length; i++) { faceVerts[i] = verts[face[i]]; faceVertNormals[i] = normals[face[i]]; faceUVs[i] = new Vector2(tFaces[j][i, 0], tFaces[j][i, 1]); } j++; // 2, 1, 0 for (int i = 2; i >= 0; i--) { output.Add(new VertexPositionNormalTexture(faceVerts[i], faceVertNormals[i], faceUVs[i])); } // 3, 2, 0 for (int i = 3; i >= 1; i--) { output.Add(new VertexPositionNormalTexture(faceVerts[i == 1 ? 0 : i], faceVertNormals[i == 1 ? 0 : i], faceUVs[i == 1 ? 0 : i])); } } return(output.ToArray()); }
private void writeField(StreamWriter writer, IField field, bool odd, int fieldNumber) { string fieldVal = field.ToString(); if (field.IsPointer) { if (field.IsArray) { for (int i = 0; i < fieldVal.Length; i++) { if (fieldVal[i] == '0') // we can assume this is the start of a pointer, so the next few chars are valid { int j = 0; do { j++; } while(fieldVal[i + j] != ',' && fieldVal[i + j] != ' '); if (fieldVal.Substring(i, j) != "0x0") { string newString = "<a href=\"#" + fieldVal.Substring(i, j) + "\">" + fieldVal.Substring(i, j) + "</a>"; fieldVal = fieldVal.Replace(fieldVal.Substring(i, j), newString); j = newString.Length; } i += j; } } } else { if (fieldVal != "0x0") { fieldVal = "<a href=\"#" + fieldVal + "\">" + fieldVal + "</a>"; if (field.IsPointerToPointer) { fieldVal += " (pointer to pointer array: "; FileBlock pointed = parsedFile.GetBlockByAddress((field as Field <ulong>).Value); // this is probably a safe cast if (pointed != null && pointed.Size % parsedFile.PointerSize == 0) // probably a pointer { ulong[] pointers = new ulong[pointed.Size / parsedFile.PointerSize]; int index = 0; while (index != pointed.Size) { pointers[index / parsedFile.PointerSize] = parsedFile.PointerSize == 4 ? BitConverter.ToUInt32(pointed.Data, index) : BitConverter.ToUInt64(pointed.Data, index); index += parsedFile.PointerSize; } string[] temp = pointers.Select(i => { return("0x" + (i == 0 ? "0" : i.ToString("X" + (parsedFile.PointerSize * 2)))); }).ToArray(); temp = temp.Select(i => { return(i == "0x0" ? i : "<a href=\"#" + i + "\">" + i + "</a>"); }).ToArray(); fieldVal += "{ " + string.Join(", ", temp) + " })"; } else { fieldVal += "...but target doesn't look like a pointer array)"; // probably will crash instead of getting to this } } } } } string typeName = field.TypeName + (field.IsArray ? (field.Is2DArray ? "[]" : "") + "[]" : ""); if (!field.IsArray && field.IsPointer && field.IsPrimitive && (field as Field <ulong>).Value != 0) { FileBlock associatedBlock = parsedFile.GetBlockByAddress((field as Field <ulong>).Value); if (associatedBlock != null) { typeName += " (points to " + (associatedBlock.Size == associatedBlock.Count * parsedFile.StructureDNA.StructureList[associatedBlock.SDNAIndex].StructureTypeSize ? parsedFile.StructureDNA.StructureList[associatedBlock.SDNAIndex].StructureTypeName : "raw data") + ")"; } } writeTableRow(odd ? "odd" : "even", writer, fieldNumber.ToString(), field.FullyQualifiedName, field.Parent.Parent == null ? "(this)" : field.ParentType, typeName, field.Length > 1 ? field.Size + " * " + field.Length + " (" + (field.Size * field.Length) + ")" : field.Size.ToString(), fieldVal); }
public BlenderModel(Structure mesh, Structure obj, GraphicsDevice GraphicsDevice, BlenderFile file) { // If I was less sloppy, I would have used casts to generics instead of the raw dynamic Value on IField, // but I'm lazy and this code is soon to go away anyway. if(defaultTex == null) { defaultTex = new Texture2D(GraphicsDevice, 1, 1); defaultTex.SetData(new Color[] { Color.Gray }); } this.GraphicsDevice = GraphicsDevice; // both structures use the same vertex structure List<Vector3> verts = new List<Vector3>(); List<short[]> unconvertedNormals = new List<short[]>(); foreach(Structure s in mesh["mvert"].Dereference()) { float[] vector = s["co"].Value; unconvertedNormals.Add(s["no"].Value); verts.Add(new Vector3(vector[0], vector[1], vector[2])); } List<Vector3> normals = convertNormals(unconvertedNormals); VertexPositionNormalTexture[] vertices; Texture2D texture; // todo: not yet sure which format versions of Blender between 2.62 and 2.65 use. if(float.Parse(file.VersionNumber) >= 2.66f) // uses edges, loops, and polys (Blender 2.66+) vertices = loadNewModel(file, mesh, verts, normals, out texture); else // uses MFace (Blender 2.49-2.61) vertices = loadOldModel(file, mesh, verts, normals, out texture); VertexPositionColor[] normalVerts = new VertexPositionColor[normals.Count * 2]; for(int i = 0; i < verts.Count * 2; i += 2) { normalVerts[i] = new VertexPositionColor(verts[i / 2], Color.MidnightBlue); normalVerts[i + 1] = new VertexPositionColor(verts[i / 2] + normals[i / 2] * 0.25f, Color.MidnightBlue); } float[] posVector = obj["loc"].Value; this.Position = new Vector3(posVector[0], posVector[1], posVector[2]); float[] scaleVector = obj["size"].Value; this.Scale = new Vector3(scaleVector[0], scaleVector[1], scaleVector[2]); float[] rotVector = obj["rot"].Value; this.Rotation = Quaternion.CreateFromYawPitchRoll(rotVector[1], rotVector[0], rotVector[2]); this.Vertices = vertices; this.NormalVerts = normalVerts; this.VertexBuffer = new VertexBuffer(GraphicsDevice, VertexPositionNormalTexture.VertexDeclaration, this.Vertices.Length, BufferUsage.None); this.NormalBuffer = new VertexBuffer(GraphicsDevice, VertexPositionColor.VertexDeclaration, this.NormalVerts.Length, BufferUsage.None); this.Texture = texture; this.Name = new string(obj["id.name"].Value).Split('\0')[0].Substring(2); // remove null term, remove first two characters // LSB on represents layer 1, next bit is layer 2, etc this.Layer = obj["lay"].Value; // the "mat" field is a pointer to a pointer (technically, a pointer to an array of pointers) // I'm not sure what to do with multiple materials, so just use the first one ulong blockaddr = mesh["mat"].Value; if(blockaddr != 0) { Structure mat = file.GetStructuresByAddress(BitConverter.ToUInt32(file.GetBlockByAddress(blockaddr).Data, 0))[0]; this.TextureHasTransparency = mat["game.alpha_blend"].Value != 0; int mode = mat["mode"].Value; this.LightingEnabled = (mode & 4) == 0; // as far as I can tell, this is where "shadeless" is stored. } else this.LightingEnabled = true; }
private VertexPositionNormalTexture[] loadNewModel(BlenderFile file, Structure mesh, List<Vector3> verts, List<Vector3> normals, out Texture2D texture) { List<VertexPositionNormalTexture> output = new List<VertexPositionNormalTexture>(); List<Vector2> edges = new List<Vector2>(); // using x as index1 and y as index2 foreach(Structure s in mesh["medge"].Dereference()) edges.Add(new Vector2((s["v1"] as IField<int>).Value, (s["v2"] as IField<int>).Value)); // a "loop" is a vertex index and an edge index. Groups of these are used to define a "poly", which is a face. List<Vector2> loops = new List<Vector2>(); // using x as "v" and y as "e" foreach(Structure s in mesh["mloop"].Dereference()) loops.Add(new Vector2((s["v"] as IField<int>).Value, (s["e"] as IField<int>).Value)); List<Vector2> uvLoops = null; // using x as u and y as v Vector2[] backupUVs = new[] { new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 1), new Vector2(1, 0) }; // in case uvLoops is null if(mesh["mloopuv"].Value != 0) { uvLoops = new List<Vector2>(); foreach(Structure s in mesh["mloopuv"].Dereference()) { float[] uv = s["uv"].Value; uvLoops.Add(new Vector2(uv[0], uv[1])); } } List<Vector2> polys = new List<Vector2>(); // using x as "loopstart" and y as "totloop" (loop length) foreach(Structure s in mesh["mpoly"].Dereference()) polys.Add(new Vector2((s["loopstart"] as IField<int>).Value, (s["totloop"] as IField<int>).Value)); // assume all faces use same texture for now if(mesh["mtpoly"].Value != 0) { try { // todo: sometimes this line fails, probably due to "assume all faces use same texture" Structure image = mesh["mtpoly"].Dereference()[0]["tpage"].Dereference()[0]; if(image["packedfile"].Value != 0) { byte[] rawImage = file.GetBlockByAddress(image["packedfile"].Dereference()[0]["data"].Value).Data; using(Stream s = new MemoryStream(rawImage)) texture = Texture2D.FromStream(GraphicsDevice, s); } else { string texturePath = image["name"].ToString().Split('\0')[0].Replace("/", "\\").Replace("\\\\", "\\"); string filePath = file.GetStructuresOfType("FileGlobal")[0]["filename"].ToString(); filePath = filePath.Substring(0, filePath.LastIndexOf('\\')); using(Stream s = File.Open((filePath + texturePath).Replace("\'", ""), FileMode.Open, FileAccess.Read)) texture = Texture2D.FromStream(GraphicsDevice, s); } } catch { texture = defaultTex; } } else { texture = new Texture2D(GraphicsDevice, 1, 1); texture.SetData(new Color[] { Color.Gray }); } // loops of length 3 are triangles and can be directly added to the vertex list. loops of length 4 // are quads, and have to be split into two triangles. foreach(Vector2 poly in polys) { Vector2[] faceEdges = new Vector2[(int)poly.Y]; Vector2[] faceUVs = new Vector2[faceEdges.Length]; int j = 0; int loopOffset = (int)poly.X; for(int i = loopOffset; i < (int)poly.Y + loopOffset; i++) { faceEdges[j] = edges[(int)loops[i].Y]; faceUVs[j] = uvLoops == null ? backupUVs[i - loopOffset] : uvLoops[i]; j++; } Vector3[] faceVerts = new Vector3[faceEdges.Length]; Vector3[] faceVertNormals = new Vector3[faceEdges.Length]; for(int i = 0; i < faceEdges.Length; i++) { int index = (int)(loops[loopOffset + i].X == faceEdges[i].X ? faceEdges[i].Y : faceEdges[i].X); faceVerts[i] = verts[index]; faceVertNormals[i] = normals[index]; } if(faceVerts.Length == 3) // already a triangle { // push 0 to the end Vector2 temp = faceUVs[0]; faceUVs[0] = faceUVs[1]; faceUVs[1] = temp; temp = faceUVs[1]; faceUVs[1] = faceUVs[2]; faceUVs[2] = temp; for(int i = 2; i >= 0; i--) // 2, 1, 0 output.Add(new VertexPositionNormalTexture(faceVerts[i], faceVertNormals[i], faceUVs[i])); } else if(faceVerts.Length == 4) // quad, split into tris { // swap 3 with 1 and 2 with 3 Vector2 temp = faceUVs[1]; faceUVs[1] = faceUVs[3]; faceUVs[3] = temp; temp = faceUVs[2]; faceUVs[2] = faceUVs[3]; faceUVs[3] = temp; // 2, 1, 0 for(int i = 2; i >= 0; i--) output.Add(new VertexPositionNormalTexture(faceVerts[i], faceVertNormals[i], faceUVs[i])); // 3, 2, 0 for(int i = 3; i >= 1; i--) output.Add(new VertexPositionNormalTexture(faceVerts[i == 1 ? 0 : i], faceVertNormals[i == 1 ? 0 : i], faceUVs[i == 1 ? 0 : i])); } } return output.ToArray(); }
private VertexPositionNormalTexture[] loadOldModel(BlenderFile file, Structure mesh, List<Vector3> verts, List<Vector3> normals, out Texture2D texture) { // I believe this function has a bug when used on a mesh that has unconnected chunks of vertices; // however the only file I currently have that exhibits this problem decompresses to 240MB when I use the HTML // renderer tool, so I can't feasibly poke through the data to see what's going wrong. List<VertexPositionNormalTexture> output = new List<VertexPositionNormalTexture>(); List<int[]> faces = new List<int[]>(); List<float[][]> tFaces = new List<float[][]>(); foreach(Structure s in mesh["mface"].Dereference()) faces.Add(new int[] { s["v1"].Value, s["v2"].Value, s["v3"].Value, s["v4"].Value }); foreach(Structure s in mesh["mtface"].Dereference()) tFaces.Add((float[][])s["uv"].Value); // assume all faces use same texture Structure image = mesh["mtface"].Dereference()[0]["tpage"].Dereference()[0]; if(image["packedfile"].Value != 0) { byte[] rawImage = file.GetBlockByAddress(image["packedfile"].Dereference()[0]["data"].Value).Data; using(Stream s = new MemoryStream(rawImage)) texture = Texture2D.FromStream(GraphicsDevice, s); } else { try { string texturePath = image["name"].ToString().Split('\0')[0].Replace("/", "\\").Replace("\\\\", "\\"); string filePath = file.GetStructuresOfType("FileGlobal")[0]["filename"].ToString(); filePath = filePath.Substring(0, filePath.LastIndexOf('\\')); using(Stream s = File.Open((filePath + texturePath).Replace("\'", ""), FileMode.Open, FileAccess.Read)) texture = Texture2D.FromStream(GraphicsDevice, s); } catch { texture = defaultTex; } } int j = 0; foreach(int[] face in faces) { Vector3[] faceVerts = new Vector3[face.Length]; Vector3[] faceVertNormals = new Vector3[face.Length]; Vector2[] faceUVs = new Vector2[face.Length]; for(int i = 0; i < face.Length; i++) { faceVerts[i] = verts[face[i]]; faceVertNormals[i] = normals[face[i]]; faceUVs[i] = new Vector2(tFaces[j][i][0], tFaces[j][i][1]); } j++; // 2, 1, 0 for(int i = 2; i >= 0; i--) output.Add(new VertexPositionNormalTexture(faceVerts[i], faceVertNormals[i], faceUVs[i])); // 3, 2, 0 for(int i = 3; i >= 1; i--) output.Add(new VertexPositionNormalTexture(faceVerts[i == 1 ? 0 : i], faceVertNormals[i == 1 ? 0 : i], faceUVs[i == 1 ? 0 : i])); } return output.ToArray(); }