private static void ParseM2_Skin(string dataPath, M2Data m2Data) { // check how many skins files there are // string noExtension = Path.GetFileNameWithoutExtension(dataPath); string directoryPath = Path.GetDirectoryName(dataPath); int skinCount = 0; for (int i = 0; i <= 20; i++) { string fileNumber = i.ToString("00"); if (Casc.FileExists(directoryPath + @"\" + noExtension + fileNumber + ".skin")) { skinCount++; } else { break; } } if (skinCount > 0) { // Load only skin00 for now // string skinDataPath = directoryPath + @"\" + noExtension + "00" + ".skin"; string skinPath = Casc.GetFile(skinDataPath); byte[] M2SkinData = File.ReadAllBytes(skinPath); using (MemoryStream ms = new MemoryStream(M2SkinData)) { ParseSkin(ms, m2Data); } } }
public static void Load(string dataPath, int uniqueID, Vector3 position, Quaternion rotation, Vector3 scale) { M2Data m2Data = new M2Data(); M2Texture m2Tex = new M2Texture(); m2Data.dataPath = dataPath; m2Data.uniqueID = uniqueID; m2Data.position = position; m2Data.rotation = rotation; m2Data.scale = scale; try { ThreadWorking = true; ParseM2_Root(dataPath, m2Data, m2Tex); ParseM2_Skin(dataPath, m2Data); AllM2Data.Enqueue(m2Data); ThreadWorking = false; } catch (Exception ex) { Debug.Log("Error : Trying to parse M2 - " + dataPath); Debug.LogException(ex); } }
private static void ParseM2_Root(string dataPath, M2Data m2Data, M2Texture m2Tex) { StreamTools s = new StreamTools(); string path = Casc.GetFile(dataPath); byte[] M2MainData = File.ReadAllBytes(path); long streamPosition = 0; using (MemoryStream ms = new MemoryStream(M2MainData)) { while (streamPosition < ms.Length) { ms.Position = streamPosition; int chunkID = s.ReadLong(ms); int chunkSize = s.ReadLong(ms); streamPosition = ms.Position + chunkSize; switch (chunkID) { case (int)ChunkID.M2ChunkID.MD21: ReadMD21(ms, m2Data, m2Tex); break; default: SkipUnknownChunk(ms, chunkID, chunkSize); break; } } }; }
public static void ReadTXID(BinaryReader br, M2Data m2Data, CASCHandler CascHandler, uint Size) { LoadedBLPFileDataIds.Clear(); var numTextures = Size / 4; for (int i = 0; i < numTextures; i++) { uint texture = br.ReadUInt32(); M2Texture m2Texture = new M2Texture(); Texture2Ddata texture2Ddata = new Texture2Ddata(); if (!LoadedBLPFileDataIds.Contains(texture)) { var stream = CascHandler.OpenFile(texture); BLP blp = new BLP(); byte[] data = blp.GetUncompressed(stream, true); BLPinfo info = blp.Info(); texture2Ddata.hasMipmaps = info.hasMipmaps; texture2Ddata.width = info.width; texture2Ddata.height = info.height; texture2Ddata.textureFormat = info.textureFormat; texture2Ddata.TextureData = data; m2Texture.texture2Ddata = texture2Ddata; m2Texture.FileDataId = texture; stream.Close(); stream.Dispose(); LoadedBLPFileDataIds.Add(texture); } m2Data.m2Tex.Add(m2Texture); } }
public static void Load(uint FileDataId, int uniqueID, Vector3 position, Quaternion rotation, Vector3 scale, CASCHandler Handler) { M2Data m2Data = new M2Data(); M2Texture m2Tex = new M2Texture(); m2Data.FileDataId = FileDataId; m2Data.uniqueID = uniqueID; m2Data.position = position; m2Data.rotation = rotation; m2Data.scale = scale; try { ThreadWorking = true; ParseM2_Root(FileDataId, m2Data, m2Tex, Handler); foreach (uint skinFile in SkinFiles) { ParseM2_Skin(skinFile, m2Data, Handler); } AllM2Data.Enqueue(m2Data); ThreadWorking = false; } catch (Exception ex) { Debug.Log("Error : Trying to parse M2 - " + FileDataId); Debug.LogException(ex); } }
private static void ParseM2_Skin(uint skinFileId, M2Data m2Data, CASCHandler CascHandler) { var stream = CascHandler.OpenFile(skinFileId); using (BinaryReader reader = new BinaryReader(stream)) { ParseSkin(reader, m2Data); } }
private static void ParseM2_Root(uint fileDataID, M2Data m2Data, M2Texture m2Tex, CASCHandler CascHandler) { long streamPos = 0; using (var stream = CascHandler.OpenFile(fileDataID)) using (BinaryReader reader = new BinaryReader(stream)) { while (streamPos < stream.Length) { stream.Position = streamPos; M2ChunkId chunkID = (M2ChunkId)reader.ReadUInt32(); uint chunkSize = reader.ReadUInt32(); streamPos = stream.Position + chunkSize; switch (chunkID) { case M2ChunkId.MD21: ReadMD21(reader, m2Data, m2Tex); break; case M2ChunkId.SFID: ReadSFID(reader, chunkSize); break; case M2ChunkId.TXID: ReadTXID(reader, m2Data, CascHandler, chunkSize); break; default: SkipUnknownChunk(stream, chunkSize); break; } } }; }
public static void Load(uint FileDataId, int uniqueID, Vector3 position, Quaternion rotation, Vector3 scale, CASCHandler Handler) { M2Data m2Data = new M2Data(); M2Texture m2Tex = new M2Texture(); m2Data.FileDataId = FileDataId; m2Data.uniqueID = uniqueID; m2Data.position = position; m2Data.rotation = rotation; m2Data.scale = scale; ThreadWorking = true; ParseM2_Root(FileDataId, m2Data, m2Tex, Handler); foreach (uint skinFile in SkinFiles) { ParseM2_Skin(skinFile, m2Data, Handler); } AllM2Data.Enqueue(m2Data); ThreadWorking = false; }
public static void ParseSkin(BinaryReader reader, M2Data m2Data) { string magic = reader.ReadFourCC(); // 'SKIN' M2Array vertices = reader.ReadM2Array(); M2Array indices = reader.ReadM2Array(); M2Array bones = reader.ReadM2Array(); M2Array submeshes = reader.ReadM2Array(); M2Array batches = reader.ReadM2Array(); // nTexture_units int boneCountMax = reader.ReadInt32(); // WoW takes this and divides it by the number of bones in each submesh, then stores the biggest one. // Maximum number of bones per drawcall for each view. Related to (old) GPU numbers of registers. // Values seen : 256, 64, 53, 21 M2Array shadow_batches = reader.ReadM2Array(); /// Read Batches /// reader.BaseStream.Seek(batches.Offset, SeekOrigin.Begin); for (var batch = 0; batch < batches.Size; batch++) { M2BatchIndices m2BatchIndices = new M2BatchIndices(); m2BatchIndices.M2Batch_flags = reader.ReadByte(); // Usually 16 for static textures, and 0 for animated textures. &0x1: materials invert something; &0x2: transform &0x4: projected texture; &0x10: something batch compatible; &0x20: projected texture?; &0x40: use textureWeights m2BatchIndices.M2Batch_priorityPlane = reader.ReadByte(); m2BatchIndices.M2Batch_shader_id = reader.ReadUInt16(); // See below. m2BatchIndices.M2Batch_skinSectionIndex = reader.ReadUInt16(); // A duplicate entry of a submesh from the list above. m2BatchIndices.M2Batch_geosetIndex = reader.ReadUInt16(); // See below. m2BatchIndices.M2Batch_color_index = reader.ReadUInt16(); // A Color out of the Colors-Block or -1 if none. m2BatchIndices.M2Batch_materialIndex = reader.ReadUInt16(); // The renderflags used on this texture-unit. m2BatchIndices.M2Batch_materialLayer = reader.ReadUInt16(); // Capped at 7 (see CM2Scene::BeginDraw) m2BatchIndices.M2Batch_textureCount = reader.ReadUInt16(); // 1 to 4. See below. Also seems to be the number of textures to load, starting at the texture lookup in the next field (0x10). m2BatchIndices.M2Batch_textureComboIndex = reader.ReadUInt16(); // Index into Texture lookup table m2BatchIndices.M2Batch_textureCoordComboIndex = reader.ReadUInt16(); // Index into the texture unit lookup table. m2BatchIndices.M2Batch_textureWeightComboIndex = reader.ReadUInt16(); // Index into transparency lookup table. m2BatchIndices.M2Batch_textureTransformComboIndex = reader.ReadUInt16(); // Index into uvanimation lookup table. m2Data.m2BatchIndices.Add(m2BatchIndices); } // Read SubMesh Data // int[] Indices = new int[vertices.Size]; // Three indices which make up a triangle. int[] Triangles = new int[indices.Size]; // Bone indices (Index into BoneLookupTable) int[] skinSectionId = new int[submeshes.Size]; // Mesh part ID, see below. int[] submesh_StartVertex = new int[submeshes.Size]; // Starting vertex number. int[] submesh_NbrVerts = new int[submeshes.Size]; // Number of vertices. int[] submesh_StartTriangle = new int[submeshes.Size]; // Starting triangle index (that's 3* the number of triangles drawn so far). int[] submesh_NbrTris = new int[submeshes.Size]; // Number of triangle indices. int[] submesh_boneCount = new int[submeshes.Size]; // Number of elements in the bone lookup table. Max seems to be 256 in Wrath. Shall be ≠ 0. int[] submesh_boneComboIndex = new int[submeshes.Size]; // Starting index in the bone lookup table. int[] submesh_boneInfluences = new int[submeshes.Size]; // <= 4 // from <=BC documentation: Highest number of bones needed at one time in this Submesh --Tinyn (wowdev.org) // In 2.x this is the amount of of bones up the parent-chain affecting the submesh --NaK // Highest number of bones referenced by a vertex of this submesh. 3.3.5a and suspectedly all other client revisions. -- Skarn int[] submesh_centerBoneIndex = new int[submeshes.Size]; Vector3[] submesh_centerPosition = new Vector3[submeshes.Size]; // Average position of all the vertices in the sub mesh. Vector3[] submesh_sortCenterPosition = new Vector3[submeshes.Size]; // The center of the box when an axis aligned box is built around the vertices in the submesh. float[] submesh_sortRadius = new float[submeshes.Size]; // Distance of the vertex farthest from CenterBoundingBox. /// Indices /// reader.BaseStream.Seek(vertices.Offset, SeekOrigin.Begin); for (var ind = 0; ind < vertices.Size; ind++) { Indices[ind] = reader.ReadUInt16(); } /// triangles /// reader.BaseStream.Seek(indices.Offset, SeekOrigin.Begin); for (var tri = 0; tri < indices.Size; tri++) { Triangles[tri] = reader.ReadUInt16(); } /// submeshes /// reader.BaseStream.Seek(submeshes.Offset, SeekOrigin.Begin); for (var sub = 0; sub < submeshes.Size; sub++) { skinSectionId[sub] = reader.ReadUInt16(); int Level = reader.ReadUInt16(); // (level << 16) is added (|ed) to startTriangle and alike to avoid having to increase those fields to uint32s. submesh_StartVertex[sub] = reader.ReadUInt16() + (Level << 16); submesh_NbrVerts[sub] = reader.ReadUInt16(); submesh_StartTriangle[sub] = reader.ReadUInt16() + (Level << 16); submesh_NbrTris[sub] = reader.ReadUInt16(); submesh_boneCount[sub] = reader.ReadUInt16(); submesh_boneComboIndex[sub] = reader.ReadUInt16(); submesh_boneInfluences[sub] = reader.ReadUInt16(); submesh_centerBoneIndex[sub] = reader.ReadUInt16(); Vector3 canterPosition = new Vector3(reader.ReadSingle() / Settings.WORLD_SCALE, reader.ReadSingle() / Settings.WORLD_SCALE, reader.ReadSingle() / Settings.WORLD_SCALE); submesh_centerPosition[sub] = new Vector3(-canterPosition.x, canterPosition.z, -canterPosition.y); Vector3 sortCenterPosition = new Vector3(reader.ReadSingle() / Settings.WORLD_SCALE, reader.ReadSingle() / Settings.WORLD_SCALE, reader.ReadSingle() / Settings.WORLD_SCALE); submesh_sortCenterPosition[sub] = new Vector3(-sortCenterPosition.x, sortCenterPosition.z, -sortCenterPosition.y); submesh_sortRadius[sub] = reader.ReadSingle(); } /// Assemble Submeshes /// m2Data.submeshData = new List <SubmeshData>(); for (int sm = 0; sm < submeshes.Size; sm++) { Vector3[] vertList = new Vector3[submesh_NbrVerts[sm]]; Vector3[] normsList = new Vector3[submesh_NbrVerts[sm]]; Vector2[] uvsList = new Vector2[submesh_NbrVerts[sm]]; Vector2[] uvs2List = new Vector2[submesh_NbrVerts[sm]]; BoneWeights[] boneWeights = new BoneWeights[submesh_NbrVerts[sm]]; for (int vn = 0; vn < submesh_NbrVerts[sm]; vn++) { vertList[vn] = m2Data.meshData.pos[vn + submesh_StartVertex[sm]]; normsList[vn] = m2Data.meshData.normal[vn + submesh_StartVertex[sm]]; uvsList[vn] = m2Data.meshData.tex_coords[vn + submesh_StartVertex[sm]]; uvs2List[vn] = m2Data.meshData.tex_coords2[vn + submesh_StartVertex[sm]]; BoneWeights boneWeightVert = new BoneWeights(); int[] boneIndex = new int[4]; float[] boneWeight = new float[4]; for (int bn = 0; bn < 4; bn++) { boneIndex[bn] = m2Data.meshData.bone_indices[vn + submesh_boneComboIndex[sm]][bn]; boneWeight[bn] = m2Data.meshData.bone_weights[vn + submesh_boneComboIndex[sm]][bn]; } boneWeightVert.boneIndex = boneIndex; boneWeightVert.boneWeight = boneWeight; boneWeights[vn] = boneWeightVert; } int[] triList = new int[submesh_NbrTris[sm]]; for (var t = 0; t < submesh_NbrTris[sm]; t++) { //triList[t] = Triangles[t + submesh_StartTriangle[sm]] - submesh_StartVertex[sm]; // using Separate Meshes, reset first triangle to index 0; triList[t] = Triangles[t + submesh_StartTriangle[sm]]; // using Unity Submeshes, don't reset first triangle to index 0; } SubmeshData submeshData = new SubmeshData(); submeshData.ID = skinSectionId[sm]; submeshData.vertList = vertList; submeshData.normsList = normsList; submeshData.uvsList = uvsList; submeshData.uvs2List = uvs2List; Array.Reverse(triList); submeshData.triList = triList; submeshData.submesh_StartVertex = submesh_StartVertex[sm]; submeshData.boneWeights = boneWeights; submeshData.submesh_boneCount = submesh_boneCount[sm]; submeshData.submesh_boneInfluences = submesh_boneInfluences[sm]; m2Data.submeshData.Add(submeshData); } /// Read Bone Data /// // byte[] Properties = new byte[bones.Size]; // reader.BaseStream.Seek(bones.Offset, SeekOrigin.Current); // for (var bone = 0; bone < bones.Size; bone++) // { // Properties[bone] = reader.ReadByte(); // } }
public void CreateM2Object(M2Data data) { // M2 Object // GameObject M2Instance = new GameObject(); terrainHandler.LoadedM2Ids[data.FileDataId] = M2Instance; terrainHandler.LoadedM2Ids[data.FileDataId].name = data.name; // LoD Group // LODGroup Lodgroup = terrainHandler.LoadedM2Ids[data.FileDataId].AddComponent <LODGroup>(); LOD[] lods = new LOD[1]; Renderer[] renderers = new Renderer[data.submeshData.Count]; // Bones // GameObject BonesRoot = new GameObject(); BonesRoot.name = "Bones"; BonesRoot.transform.position = Vector3.zero; BonesRoot.transform.rotation = Quaternion.identity; BonesRoot.transform.SetParent(M2Instance.transform); Transform[] bones = new Transform[data.m2CompBone.Count]; Dictionary <int, GameObject> hierarchyList = new Dictionary <int, GameObject>(); for (int boneN = 0; boneN < data.m2CompBone.Count; boneN++) { // name // string name = "bone_" + boneN; int key_bone_id = data.m2CompBone[boneN].key_bone_id; if (key_bone_id != -1) { if (data.key_bone_lookup[key_bone_id] < (KeyBoneLookupList.Length - 1)) { name = "bone_" + KeyBoneLookupList[key_bone_id]; } } bones[boneN] = new GameObject(name).transform; if (data.m2CompBone[boneN].parent_bone == -1) { bones[boneN].SetParent(BonesRoot.transform); } else { bones[boneN].SetParent(bones[data.m2CompBone[boneN].parent_bone]); } bones[boneN].transform.position = data.m2CompBone[boneN].pivot; } // Mesh // GameObject MesheObject = new GameObject(); MesheObject.name = "Mesh"; MesheObject.transform.position = Vector3.zero; MesheObject.transform.rotation = Quaternion.identity; MesheObject.transform.SetParent(M2Instance.transform); Mesh m = new Mesh(); m.vertices = data.meshData.pos.ToArray(); m.normals = data.meshData.normal.ToArray(); m.uv = data.meshData.tex_coords.ToArray(); m.uv2 = data.meshData.tex_coords2.ToArray(); m.subMeshCount = data.submeshData.Count; // Submeshes // for (int batch = 0; batch < data.submeshData.Count; batch++) { m.SetTriangles(data.submeshData[batch].triList, batch, true); } // Skinned Mesh Renderer // SkinnedMeshRenderer rend = MesheObject.AddComponent <SkinnedMeshRenderer>(); rend.sharedMesh = m; rend.bones = bones; rend.rootBone = BonesRoot.transform.GetChild(0); // Bounds // Bounds meshBounds = new Bounds(); meshBounds.min = data.bounding_box.min; meshBounds.max = data.bounding_box.max; m.bounds = meshBounds; rend.localBounds = meshBounds; // Bone Weights // BoneWeight[] weights = new BoneWeight[m.vertices.Length]; for (int bw = 0; bw < m.vertices.Length; bw++) { weights[bw].boneIndex0 = data.meshData.bone_indices[bw][0]; weights[bw].weight0 = data.meshData.bone_weights[bw][0]; weights[bw].boneIndex1 = data.meshData.bone_indices[bw][1]; weights[bw].weight1 = data.meshData.bone_weights[bw][1]; weights[bw].boneIndex2 = data.meshData.bone_indices[bw][2]; weights[bw].weight2 = data.meshData.bone_weights[bw][2]; weights[bw].boneIndex3 = data.meshData.bone_indices[bw][3]; weights[bw].weight3 = data.meshData.bone_weights[bw][3]; } m.boneWeights = weights; // Bind Poses // Matrix4x4[] bindPoses = new Matrix4x4[bones.Length]; for (int bp = 0; bp < bindPoses.Length; bp++) { bindPoses[bp] = bones[bp].worldToLocalMatrix * transform.localToWorldMatrix; } m.bindposes = bindPoses; // Animations // Animation anim = M2Instance.AddComponent <Animation>(); AnimationClip[] clips = new AnimationClip[data.numberOfAnimations]; #region Anims /* * for (int a = 0; a < data.numberOfAnimations; a++) * { * AnimationClip clip = new AnimationClip(); * for (int p = 0; p < data.position_animations.Count; p++) * { * AnimationCurve position_x_curve = new AnimationCurve(); * AnimationCurve position_y_curve = new AnimationCurve(); * AnimationCurve position_z_curve = new AnimationCurve(); * Keyframe[] position_x_keyframes = new Keyframe[data.position_animations[p][a].timeStamps.Count]; * Keyframe[] position_y_keyframes = new Keyframe[data.position_animations[p][a].timeStamps.Count]; * Keyframe[] position_z_keyframes = new Keyframe[data.position_animations[p][a].timeStamps.Count]; * for (int t = 0; t < position_x_keyframes.Length; t++) * { * position_x_keyframes[t] = new Keyframe(data.position_animations[p][a].timeStamps[t], data.position_animations[p][a].values[t].x); * position_y_keyframes[t] = new Keyframe(data.position_animations[p][a].timeStamps[t], data.position_animations[p][a].values[t].y); * position_z_keyframes[t] = new Keyframe(data.position_animations[p][a].timeStamps[t], data.position_animations[p][a].values[t].z); * } * position_x_curve.keys = position_x_keyframes; * position_y_curve.keys = position_y_keyframes; * position_z_curve.keys = position_z_keyframes; * clip.SetCurve(bones[p].name, typeof(Transform), "m_LocalPosition.x", position_x_curve); * clip.SetCurve(bones[p].name, typeof(Transform), "m_LocalPosition.y", position_y_curve); * clip.SetCurve(bones[p].name, typeof(Transform), "m_LocalPosition.z", position_z_curve); * } * * clip.legacy = true; * clip.wrapMode = WrapMode.Loop; * anim.AddClip(clip * , "anim_" + a); * } * anim.Play("anim_0"); */ #endregion // Materials // Material[] materials = new Material[data.submeshData.Count]; for (int matD = 0; matD < materials.Length; matD++) { materials[matD] = new Material(Shader.Find("WoWEdit/WMO/S_Diffuse"));; } // fill with default material rend.materials = materials; // Textures // for (var tex = 0; tex < data.submeshData.Count; tex++) { uint FDID = data.m2Tex[data.textureLookupTable[data.m2BatchIndices[data.m2BatchIndices[tex].M2Batch_skinSectionIndex].M2Batch_textureComboIndex]].FileDataId; Texture2Ddata tdata = data.m2Tex[data.textureLookupTable[data.m2BatchIndices[data.m2BatchIndices[tex].M2Batch_skinSectionIndex].M2Batch_textureComboIndex]].texture2Ddata; if (FDID != 0 && tdata.TextureData != null) { if (LoadedM2TextureIds.ContainsKey(FDID)) { materials[tex].SetTexture("_MainTex", LoadedM2TextureIds[FDID]); } else { try { Texture2D texture = new Texture2D(tdata.width, tdata.height, tdata.textureFormat, tdata.hasMipmaps); texture.LoadRawTextureData(tdata.TextureData); texture.Apply(); LoadedM2TextureIds[FDID] = texture; materials[tex].SetTexture("_MainTex", texture); } catch { Debug.Log("Error: Loading RawTextureData @ M2handler"); } } } } // DEBUG - Draw Bones // BonesRoot.AddComponent <DrawBones>(); // Object Transforms // terrainHandler.LoadedM2Ids[data.FileDataId].transform.position = data.position; terrainHandler.LoadedM2Ids[data.FileDataId].transform.rotation = data.rotation; terrainHandler.LoadedM2Ids[data.FileDataId].transform.localScale = data.scale; if (data.uniqueID != -1) { if (terrainHandler.ADTBlockM2Parents[data.uniqueID].transform != null) { terrainHandler.LoadedM2Ids[data.FileDataId].transform.SetParent(terrainHandler.ADTBlockM2Parents[data.uniqueID].transform); } else { Destroy(terrainHandler.LoadedM2Ids[data.FileDataId]); } } terrainHandler.frameBusy = false; }
public static void ReadMD21(BinaryReader br, M2Data m2Data, M2Texture m2Tex) { long md20position = br.BaseStream.Position; int MD20 = br.ReadInt32(); // "MD20". Legion uses a chunked file format starting with MD21. int version = br.ReadInt32(); M2Array name = br.ReadM2Array(); // should be globally unique, used to reload by name in internal clients var flags = br.ReadInt32(); M2Array global_loops = br.ReadM2Array(); // Timestamps used in global looping animations. M2Array sequences = br.ReadM2Array(); // Information about the animations in the model. M2Array sequences_lookups = br.ReadM2Array(); // Mapping of sequence IDs to the entries in the Animation sequences block. M2Array bones = br.ReadM2Array(); // MAX_BONES = 0x100 => Creature\SlimeGiant\GiantSlime.M2 has 312 bones(Wrath) M2Array key_bone_lookup = br.ReadM2Array(); // Lookup table for key skeletal bones. M2Array vertices = br.ReadM2Array(); int num_skin_profiles = br.ReadInt32(); M2Array colors = br.ReadM2Array(); // Color and alpha animations definitions. M2Array textures = br.ReadM2Array(); M2Array texture_weights = br.ReadM2Array(); // Transparency of textures. M2Array texture_transforms = br.ReadM2Array(); M2Array replaceable_texture_lookup = br.ReadM2Array(); M2Array materials = br.ReadM2Array(); // Blending modes / render flags. M2Array bone_lookup_table = br.ReadM2Array(); M2Array texture_lookup_table = br.ReadM2Array(); M2Array tex_unit_lookup_table = br.ReadM2Array(); // ≥ Cata: unused M2Array transparency_lookup_table = br.ReadM2Array(); M2Array texture_transforms_lookup_table = br.ReadM2Array(); m2Data.bounding_box = br.ReadBoundingBoxes(); // min/max( [1].z, 2.0277779f ) - 0.16f seems to be the maximum camera height float bounding_sphere_radius = br.ReadSingle(); // detail doodad draw dist = clamp (bounding_sphere_radius * detailDoodadDensityFade * detailDoodadDist, …) BoundingBox collision_box = br.ReadBoundingBoxes(); float collision_sphere_radius = br.ReadSingle(); M2Array collision_triangles = br.ReadM2Array(); M2Array collision_vertices = br.ReadM2Array(); M2Array collision_normals = br.ReadM2Array(); M2Array attachments = br.ReadM2Array(); // position of equipped weapons or effects M2Array attachment_lookup_table = br.ReadM2Array(); M2Array events = br.ReadM2Array(); // Used for playing sounds when dying and a lot else. M2Array lights = br.ReadM2Array(); // Lights are mainly used in loginscreens but in wands and some doodads too. M2Array cameras = br.ReadM2Array(); // The cameras are present in most models for having a model in the character tab. M2Array camera_lookup_table = br.ReadM2Array(); M2Array ribbon_emitters = br.ReadM2Array(); // Things swirling around. See the CoT-entrance for light-trails. M2Array particle_emitters = br.ReadM2Array(); // Name // br.BaseStream.Position = name.Offset + md20position; for (int n = 0; n < name.Size; n++) { m2Data.name += Convert.ToChar(br.ReadByte()); } // Bones // br.BaseStream.Position = bones.Offset + md20position; M2TrackBase[] translationM2track = new M2TrackBase[bones.Size]; M2TrackBase[] rotationM22track = new M2TrackBase[bones.Size]; M2TrackBase[] scaleM22track = new M2TrackBase[bones.Size]; for (int cb = 0; cb < bones.Size; cb++) { M2CompBone m2CompBone = new M2CompBone(); m2CompBone.key_bone_id = br.ReadInt32(); // Back-reference to the key bone lookup table. -1 if this is no key bone. m2CompBone.flags = br.ReadInt32(); m2CompBone.parent_bone = br.ReadInt16(); m2CompBone.submesh_id = br.ReadUInt16(); m2CompBone.uDistToFurthDesc = br.ReadUInt16(); m2CompBone.uZRatioOfChain = br.ReadUInt16(); translationM2track[cb] = br.ReadM2Track(); rotationM22track[cb] = br.ReadM2Track(); scaleM22track[cb] = br.ReadM2Track(); Vector3 pivotRaw = new Vector3(br.ReadSingle() / Settings.WorldScale, br.ReadSingle() / Settings.WorldScale, br.ReadSingle() / Settings.WorldScale); m2CompBone.pivot = new Vector3(-pivotRaw.x, pivotRaw.z, -pivotRaw.y); m2Data.m2CompBone.Add(m2CompBone); } // Animations // int numberOfAnimations = 0; for (int ab = 0; ab < bones.Size; ab++) { List <Animation_Vector3> bone_position_animations = new List <Animation_Vector3>(); List <Animation_Quaternion> bone_rotation_animations = new List <Animation_Quaternion>(); List <Animation_Vector3> bone_scale_animations = new List <Animation_Vector3>(); // Position // int numberOfPositionAnimations = translationM2track[ab].Timestamps.Size; if (numberOfAnimations < numberOfPositionAnimations) { numberOfAnimations = numberOfPositionAnimations; } for (int at = 0; at < numberOfPositionAnimations; at++) { Animation bone_animation = new Animation(); Animation_Vector3 positions = new Animation_Vector3(); // Timestamps // List <int> timeStamps = new List <int>(); br.BaseStream.Position = translationM2track[ab].Timestamps.Offset + md20position; M2Array m2AnimationOffset = br.ReadM2Array(); br.BaseStream.Position = m2AnimationOffset.Offset; for (int t = 0; t < m2AnimationOffset.Size; t++) { timeStamps.Add(br.ReadInt32()); } positions.timeStamps = timeStamps; // Values // List <Vector3> values = new List <Vector3>(); br.BaseStream.Position = translationM2track[ab].Values.Offset + md20position; M2Array m2AnimationValues = br.ReadM2Array(); br.BaseStream.Position = m2AnimationValues.Offset; for (int t = 0; t < m2AnimationValues.Size; t++) { Vector3 rawPosition = new Vector3(br.ReadSingle() / Settings.WorldScale, br.ReadSingle() / Settings.WorldScale, br.ReadSingle() / Settings.WorldScale); values.Add(new Vector3(-rawPosition.x, rawPosition.z, -rawPosition.y)); } positions.values = values; bone_position_animations.Add(positions); } // Rotation // int numberOfRotationAnimations = rotationM22track[ab].Timestamps.Size; if (numberOfAnimations < numberOfRotationAnimations) { numberOfAnimations = numberOfRotationAnimations; } for (int ar = 0; ar < numberOfRotationAnimations; ar++) { Animation_Quaternion rotations = new Animation_Quaternion(); // Timestamps // List <int> timeStamps = new List <int>(); br.BaseStream.Position = rotationM22track[ab].Timestamps.Offset + md20position; M2Array m2AnimationOffset = br.ReadM2Array(); br.BaseStream.Position = m2AnimationOffset.Offset; for (int t = 0; t < m2AnimationOffset.Size; t++) { timeStamps.Add(br.ReadInt32()); } rotations.timeStamps = timeStamps; // Values // List <Quaternion> values = new List <Quaternion>(); br.BaseStream.Position = rotationM22track[ab].Values.Offset + md20position; M2Array m2AnimationValues = br.ReadM2Array(); br.BaseStream.Position = m2AnimationValues.Offset; for (int t = 0; t < m2AnimationValues.Size; t++) { Quaternion rawRotation = br.ReadQuaternion(); values.Add(new Quaternion(rawRotation.x, rawRotation.y, rawRotation.z, rawRotation.w)); } rotations.values = values; bone_rotation_animations.Add(rotations); } // Scale // int numberOfScaleAnimations = scaleM22track[ab].Timestamps.Size; if (numberOfAnimations < numberOfScaleAnimations) { numberOfAnimations = numberOfScaleAnimations; } for (int aS = 0; aS < numberOfScaleAnimations; aS++) { Animation_Vector3 scales = new Animation_Vector3(); // Timestamps // List <int> timeStamps = new List <int>(); br.BaseStream.Position = scaleM22track[ab].Timestamps.Offset + md20position; M2Array m2AnimationOffset = br.ReadM2Array(); br.BaseStream.Position = m2AnimationOffset.Offset; for (int t = 0; t < m2AnimationOffset.Size; t++) { timeStamps.Add(br.ReadInt32()); } scales.timeStamps = timeStamps; // Values // List <Vector3> values = new List <Vector3>(); br.BaseStream.Position = scaleM22track[ab].Values.Offset + md20position; M2Array m2AnimationValues = br.ReadM2Array(); br.BaseStream.Position = m2AnimationValues.Offset; for (int t = 0; t < m2AnimationValues.Size; t++) { Vector3 rawScale = new Vector3(br.ReadSingle() / Settings.WorldScale, br.ReadSingle() / Settings.WorldScale, br.ReadSingle() / Settings.WorldScale); values.Add(new Vector3(-rawScale.x, rawScale.z, -rawScale.y)); } scales.values = values; bone_scale_animations.Add(scales); } //Debug.Log(numberOfPositionAnimations + " " + numberOfRotationAnimations + " " + numberOfScaleAnimations); m2Data.position_animations.Add(bone_position_animations); m2Data.rotation_animations.Add(bone_rotation_animations); m2Data.scale_animations.Add(bone_scale_animations); } m2Data.numberOfAnimations = numberOfAnimations; // Bone Lookup Table // br.BaseStream.Position = bone_lookup_table.Offset + md20position; for (int blt = 0; blt < key_bone_lookup.Size; blt++) { m2Data.bone_lookup_table.Add(br.ReadUInt16()); } // Key-Bone Lookup // br.BaseStream.Position = key_bone_lookup.Offset + md20position; for (int kbl = 0; kbl < key_bone_lookup.Size; kbl++) { m2Data.key_bone_lookup.Add(br.ReadUInt16()); } // Vertices // br.BaseStream.Position = vertices.Offset + md20position; m2Data.meshData = new MeshData(); for (int v = 0; v < vertices.Size; v++) { Vector3 rawPosition = new Vector3(br.ReadSingle() / Settings.WorldScale, br.ReadSingle() / Settings.WorldScale, br.ReadSingle() / Settings.WorldScale); m2Data.meshData.pos.Add(new Vector3(-rawPosition.x, rawPosition.z, -rawPosition.y)); m2Data.meshData.bone_weights.Add(new float[] { br.ReadByte() / 255.0f, br.ReadByte() / 255.0f, br.ReadByte() / 255.0f, br.ReadByte() / 255.0f }); m2Data.meshData.bone_indices.Add(new int[] { br.ReadByte(), br.ReadByte(), br.ReadByte(), br.ReadByte() }); //Debug.Log(m2Data.meshData.bone_indices[v][0] + " " + m2Data.meshData.bone_indices[v][1] + " " + m2Data.meshData.bone_indices[v][2] + " " + m2Data.meshData.bone_indices[v][3]); Vector3 rawnormal = new Vector3(br.ReadSingle() * Settings.WorldScale, br.ReadSingle() * Settings.WorldScale, br.ReadSingle() * Settings.WorldScale); m2Data.meshData.normal.Add(new Vector3(-rawnormal.x, rawnormal.z, -rawnormal.y)); m2Data.meshData.tex_coords.Add(new Vector2(br.ReadSingle(), br.ReadSingle())); m2Data.meshData.tex_coords2.Add(new Vector2(br.ReadSingle(), br.ReadSingle())); } // texture_lookup_table // br.BaseStream.Position = texture_lookup_table.Offset + md20position; for (int tl = 0; tl < texture_lookup_table.Size; tl++) { m2Data.textureLookupTable.Add(br.ReadUInt16()); } }
public static void ReadMD21(MemoryStream ms, M2Data m2Data, M2Texture m2Tex) { long md20position = ms.Position; StreamTools s = new StreamTools(); int MD20 = s.ReadLong(ms); // "MD20". Legion uses a chunked file format starting with MD21. int version = s.ReadLong(ms); M2Array name = s.ReadM2Array(ms); // should be globally unique, used to reload by name in internal clients var flags = s.ReadLong(ms); M2Array global_loops = s.ReadM2Array(ms); // Timestamps used in global looping animations. M2Array sequences = s.ReadM2Array(ms); // Information about the animations in the model. M2Array sequences_lookups = s.ReadM2Array(ms); // Mapping of sequence IDs to the entries in the Animation sequences block. M2Array bones = s.ReadM2Array(ms); // MAX_BONES = 0x100 => Creature\SlimeGiant\GiantSlime.M2 has 312 bones(Wrath) M2Array key_bone_lookup = s.ReadM2Array(ms); // Lookup table for key skeletal bones. M2Array vertices = s.ReadM2Array(ms); int num_skin_profiles = s.ReadLong(ms); M2Array colors = s.ReadM2Array(ms); // Color and alpha animations definitions. M2Array textures = s.ReadM2Array(ms); M2Array texture_weights = s.ReadM2Array(ms); // Transparency of textures. M2Array texture_transforms = s.ReadM2Array(ms); M2Array replaceable_texture_lookup = s.ReadM2Array(ms); M2Array materials = s.ReadM2Array(ms); // Blending modes / render flags. M2Array bone_lookup_table = s.ReadM2Array(ms); M2Array texture_lookup_table = s.ReadM2Array(ms); M2Array tex_unit_lookup_table = s.ReadM2Array(ms); // ≥ Cata: unused M2Array transparency_lookup_table = s.ReadM2Array(ms); M2Array texture_transforms_lookup_table = s.ReadM2Array(ms); m2Data.bounding_box = s.ReadBoundingBox(ms); // min/max( [1].z, 2.0277779f ) - 0.16f seems to be the maximum camera height float bounding_sphere_radius = s.ReadFloat(ms); // detail doodad draw dist = clamp (bounding_sphere_radius * detailDoodadDensityFade * detailDoodadDist, …) BoundingBox collision_box = s.ReadBoundingBox(ms); float collision_sphere_radius = s.ReadFloat(ms); M2Array collision_triangles = s.ReadM2Array(ms); M2Array collision_vertices = s.ReadM2Array(ms); M2Array collision_normals = s.ReadM2Array(ms); M2Array attachments = s.ReadM2Array(ms); // position of equipped weapons or effects M2Array attachment_lookup_table = s.ReadM2Array(ms); M2Array events = s.ReadM2Array(ms); // Used for playing sounds when dying and a lot else. M2Array lights = s.ReadM2Array(ms); // Lights are mainly used in loginscreens but in wands and some doodads too. M2Array cameras = s.ReadM2Array(ms); // The cameras are present in most models for having a model in the character tab. M2Array camera_lookup_table = s.ReadM2Array(ms); M2Array ribbon_emitters = s.ReadM2Array(ms); // Things swirling around. See the CoT-entrance for light-trails. M2Array particle_emitters = s.ReadM2Array(ms); // Name // ms.Position = name.offset + md20position; for (int n = 0; n < name.size; n++) { m2Data.name += Convert.ToChar(ms.ReadByte()); } // Bones // ms.Position = bones.offset + md20position; M2TrackBase[] translationM2track = new M2TrackBase[bones.size]; M2TrackBase[] rotationM22track = new M2TrackBase[bones.size]; M2TrackBase[] scaleM22track = new M2TrackBase[bones.size]; for (int cb = 0; cb < bones.size; cb++) { M2CompBone m2CompBone = new M2CompBone(); m2CompBone.key_bone_id = s.ReadLong(ms); // Back-reference to the key bone lookup table. -1 if this is no key bone. m2CompBone.flags = s.ReadUint32(ms); m2CompBone.parent_bone = s.ReadShort(ms); m2CompBone.submesh_id = s.ReadUint16(ms); m2CompBone.uDistToFurthDesc = s.ReadUint16(ms); m2CompBone.uZRatioOfChain = s.ReadUint16(ms); translationM2track[cb] = s.ReadM2Track(ms); rotationM22track[cb] = s.ReadM2Track(ms); scaleM22track[cb] = s.ReadM2Track(ms); Vector3 pivotRaw = new Vector3(s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale); m2CompBone.pivot = new Vector3(-pivotRaw.x, pivotRaw.z, -pivotRaw.y); m2Data.m2CompBone.Add(m2CompBone); } // Animations // int numberOfAnimations = 0; for (int ab = 0; ab < bones.size; ab++) { List <Animation_Vector3> bone_position_animations = new List <Animation_Vector3>(); List <Animation_Quaternion> bone_rotation_animations = new List <Animation_Quaternion>(); List <Animation_Vector3> bone_scale_animations = new List <Animation_Vector3>(); // Position // int numberOfPositionAnimations = translationM2track[ab].Timestamps.size; if (numberOfAnimations < numberOfPositionAnimations) { numberOfAnimations = numberOfPositionAnimations; } for (int at = 0; at < numberOfPositionAnimations; at++) { Animation bone_animation = new Animation(); Animation_Vector3 positions = new Animation_Vector3(); // Timestamps // List <int> timeStamps = new List <int>(); ms.Position = translationM2track[ab].Timestamps.offset + md20position; M2Array m2AnimationOffset = s.ReadM2Array(ms); ms.Position = m2AnimationOffset.offset; for (int t = 0; t < m2AnimationOffset.size; t++) { timeStamps.Add(s.ReadLong(ms)); } positions.timeStamps = timeStamps; // Values // List <Vector3> values = new List <Vector3>(); ms.Position = translationM2track[ab].Values.offset + md20position; M2Array m2AnimationValues = s.ReadM2Array(ms); ms.Position = m2AnimationValues.offset; for (int t = 0; t < m2AnimationValues.size; t++) { Vector3 rawPosition = new Vector3(s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale); values.Add(new Vector3(-rawPosition.x, rawPosition.z, -rawPosition.y)); } positions.values = values; bone_position_animations.Add(positions); } // Rotation // int numberOfRotationAnimations = rotationM22track[ab].Timestamps.size; if (numberOfAnimations < numberOfRotationAnimations) { numberOfAnimations = numberOfRotationAnimations; } for (int ar = 0; ar < numberOfRotationAnimations; ar++) { Animation_Quaternion rotations = new Animation_Quaternion(); // Timestamps // List <int> timeStamps = new List <int>(); ms.Position = rotationM22track[ab].Timestamps.offset + md20position; M2Array m2AnimationOffset = s.ReadM2Array(ms); ms.Position = m2AnimationOffset.offset; for (int t = 0; t < m2AnimationOffset.size; t++) { timeStamps.Add(s.ReadLong(ms)); } rotations.timeStamps = timeStamps; // Values // List <Quaternion> values = new List <Quaternion>(); ms.Position = rotationM22track[ab].Values.offset + md20position; M2Array m2AnimationValues = s.ReadM2Array(ms); ms.Position = m2AnimationValues.offset; for (int t = 0; t < m2AnimationValues.size; t++) { Quaternion rawRotation = s.ReadQuaternion16(ms); values.Add(new Quaternion(rawRotation.x, rawRotation.y, rawRotation.z, rawRotation.w)); } rotations.values = values; bone_rotation_animations.Add(rotations); } // Scale // int numberOfScaleAnimations = scaleM22track[ab].Timestamps.size; if (numberOfAnimations < numberOfScaleAnimations) { numberOfAnimations = numberOfScaleAnimations; } for (int aS = 0; aS < numberOfScaleAnimations; aS++) { Animation_Vector3 scales = new Animation_Vector3(); // Timestamps // List <int> timeStamps = new List <int>(); ms.Position = scaleM22track[ab].Timestamps.offset + md20position; M2Array m2AnimationOffset = s.ReadM2Array(ms); ms.Position = m2AnimationOffset.offset; for (int t = 0; t < m2AnimationOffset.size; t++) { timeStamps.Add(s.ReadLong(ms)); } scales.timeStamps = timeStamps; // Values // List <Vector3> values = new List <Vector3>(); ms.Position = scaleM22track[ab].Values.offset + md20position; M2Array m2AnimationValues = s.ReadM2Array(ms); ms.Position = m2AnimationValues.offset; for (int t = 0; t < m2AnimationValues.size; t++) { Vector3 rawScale = new Vector3(s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale); values.Add(new Vector3(-rawScale.x, rawScale.z, -rawScale.y)); } scales.values = values; bone_scale_animations.Add(scales); } //Debug.Log(numberOfPositionAnimations + " " + numberOfRotationAnimations + " " + numberOfScaleAnimations); m2Data.position_animations.Add(bone_position_animations); m2Data.rotation_animations.Add(bone_rotation_animations); m2Data.scale_animations.Add(bone_scale_animations); } m2Data.numberOfAnimations = numberOfAnimations; // Bone Lookup Table // ms.Position = bone_lookup_table.offset + md20position; for (int blt = 0; blt < key_bone_lookup.size; blt++) { m2Data.bone_lookup_table.Add(s.ReadUint16(ms)); } // Key-Bone Lookup // ms.Position = key_bone_lookup.offset + md20position; for (int kbl = 0; kbl < key_bone_lookup.size; kbl++) { m2Data.key_bone_lookup.Add(s.ReadShort(ms)); } // Vertices // ms.Position = vertices.offset + md20position; m2Data.meshData = new MeshData(); for (int v = 0; v < vertices.size; v++) { Vector3 rawPosition = new Vector3(s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale); m2Data.meshData.pos.Add(new Vector3(-rawPosition.x, rawPosition.z, -rawPosition.y)); m2Data.meshData.bone_weights.Add(new float[] { ms.ReadByte() / 255.0f, ms.ReadByte() / 255.0f, ms.ReadByte() / 255.0f, ms.ReadByte() / 255.0f }); m2Data.meshData.bone_indices.Add(new int[] { ms.ReadByte(), ms.ReadByte(), ms.ReadByte(), ms.ReadByte() }); //Debug.Log(m2Data.meshData.bone_indices[v][0] + " " + m2Data.meshData.bone_indices[v][1] + " " + m2Data.meshData.bone_indices[v][2] + " " + m2Data.meshData.bone_indices[v][3]); Vector3 rawnormal = new Vector3(s.ReadFloat(ms) * Settings.worldScale, s.ReadFloat(ms) * Settings.worldScale, s.ReadFloat(ms) * Settings.worldScale); m2Data.meshData.normal.Add(new Vector3(-rawnormal.x, rawnormal.z, -rawnormal.y)); m2Data.meshData.tex_coords.Add(new Vector2(s.ReadFloat(ms), s.ReadFloat(ms))); m2Data.meshData.tex_coords2.Add(new Vector2(s.ReadFloat(ms), s.ReadFloat(ms))); } // Textures // ms.Position = textures.offset + md20position; for (int t = 0; t < textures.size; t++) { M2Texture m2Texture = new M2Texture(); m2Texture.type = s.ReadLong(ms); m2Texture.flags = s.ReadLong(ms); M2Array filename = s.ReadM2Array(ms); // seek to filename and read // long savePosition = ms.Position; ms.Position = filename.offset + md20position; string fileNameString = ""; for (int n = 0; n < filename.size; n++) { fileNameString += Convert.ToChar(ms.ReadByte()); } ms.Position = savePosition; string fileNameStringFix = fileNameString.TrimEnd(fileNameString[fileNameString.Length - 1]); m2Texture.filename = fileNameStringFix; Texture2Ddata texture2Ddata = new Texture2Ddata(); if (fileNameStringFix.Length > 1) { if (!LoadedBLPs.Contains(fileNameStringFix)) { string extractedPath = Casc.GetFile(fileNameStringFix); Stream stream = File.Open(extractedPath, FileMode.Open); BLP blp = new BLP(); byte[] data = blp.GetUncompressed(stream, true); BLPinfo info = blp.Info(); texture2Ddata.hasMipmaps = info.hasMipmaps; texture2Ddata.width = info.width; texture2Ddata.height = info.height; texture2Ddata.textureFormat = info.textureFormat; texture2Ddata.TextureData = data; m2Texture.texture2Ddata = texture2Ddata; stream.Close(); stream.Dispose(); stream = null; LoadedBLPs.Add(fileNameString); } } m2Data.m2Tex.Add(m2Texture); } // texture_lookup_table // ms.Position = texture_lookup_table.offset + md20position; for (int tl = 0; tl < texture_lookup_table.size; tl++) { m2Data.textureLookupTable.Add(s.ReadUint16(ms)); } }