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.WORLD_SCALE, br.ReadSingle() / Settings.WORLD_SCALE, br.ReadSingle() / Settings.WORLD_SCALE); 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.WORLD_SCALE, br.ReadSingle() / Settings.WORLD_SCALE, br.ReadSingle() / Settings.WORLD_SCALE); 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.WORLD_SCALE, br.ReadSingle() / Settings.WORLD_SCALE, br.ReadSingle() / Settings.WORLD_SCALE); 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.WORLD_SCALE, br.ReadSingle() / Settings.WORLD_SCALE, br.ReadSingle() / Settings.WORLD_SCALE); 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.WORLD_SCALE, br.ReadSingle() * Settings.WORLD_SCALE, br.ReadSingle() * Settings.WORLD_SCALE); 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()); } }