public M2Array <M2Sequence> GetSequences() { M2Array <M2Sequence> sequences = new M2Array <M2Sequence>(); foreach (var s in _model.Get <SEQS>()) { M2Sequence sequence = new M2Sequence() { AnimationId = 0, //Todo match animation ids Length = (uint)(s.MaxTime - s.MinTime), MovingSpeed = s.MoveSpeed, BlendTimeStart = (ushort)s.BlendTime, BlendTimeEnd = (ushort)s.BlendTime, Probability = (short)(short.MaxValue * (1f - s.Frequency)), MinimumRepetitions = (uint)s.MinReplay, MaximumRepetitions = (uint)s.MaxReplay, Bounds = s.Bounds.Extent.ToCAaBox, }; if (!s.NonLooping) { sequence.Flags |= M2Sequence.SequenceFlags.Looped; } sequences.Add(sequence); } return(sequences); }
public M2Array <M2Color> GetColors() { M2Array <M2Color> colors = new M2Array <M2Color>(); if (!_model.Has <GEOA>()) { return(colors); } foreach (var geoa in _model.Get <GEOA>()) { var color = new M2Color(); //geoa.AlphaTrack?.PopulateM2Track(color.Alpha, _model.Get<SEQS>()); if (geoa.ColorKeys != null) { geoa.ColorKeys.PopulateM2Track(color.Color, _model.Get <SEQS>()); } else { color.Color.Timestamps.Add(new M2Array <uint>() { 0 }); color.Color.Values.Add(new M2Array <C3Vector>() { geoa.Color.ToC3Vector }); } colors.Add(color); } return(colors); }
public M2Array <M2TextureWeight> GetTextureWeights() { M2Array <M2TextureWeight> textureweights = new M2Array <M2TextureWeight>(); if (!_model.Has <MTLS>()) { return(textureweights); } foreach (var lay in _model.Get <MTLS>().SelectMany(x => x.Layers)) { M2TextureWeight weight = new M2TextureWeight(); if (lay.AlphaKeys != null) { lay.AlphaKeys.PopulateM2Track(weight.Weight, _model.Get <SEQS>()); } else { weight.Weight.Timestamps.Add(new M2Array <uint>() { 0 }); weight.Weight.Values.Add(new M2Array <FixedPoint_0_15>() { new FixedPoint_0_15(lay.Alpha.ToShort()) }); } textureweights.Add(weight); } return(textureweights); }
public M2Array <M2Attachment> GetAttachments() { M2Array <M2Attachment> attachments = new M2Array <M2Attachment>(); if (!_model.Has <ATCH>()) { return(attachments); } foreach (var a in _model.Get <ATCH>()) { M2Attachment attach = new M2Attachment() { Id = (uint)a.AttachmentId, Position = _model.Get <PIVT>().ElementAt(a.ObjectId).ToC3Vector, Bone = (uint)a.ParentId, }; attach.AnimateAttached.Timestamps.Add(new M2Array <uint>() { 0 }); attach.AnimateAttached.Values.Add(new M2Array <bool>() { true }); attachments.Add(attach); } return(attachments); }
public static M2Array <T> ToM2Array <T>(this IEnumerable <T> values) where T : new() { var array = new M2Array <T>(); if (values != null) { array.AddRange(values); } return(array); }
public M2Array ReadM2Array(MemoryStream stream) { M2Array m2array = new M2Array { size = ReadLong(stream), offset = ReadLong(stream) }; return(m2array); }
public static M2Array ReadM2Array(this BinaryReader reader) { M2Array array = new M2Array() { Size = reader.ReadInt32(), Offset = reader.ReadInt32() }; return(array); }
public M2(string path, bool fixHelm) : base(path) { FixHelmOffset = fixHelm; if (Read <uint>(0xC) <= 264) { NeedFix = false; } Reader.BaseStream.Position = 0; while (Reader.BaseStream.Position < Reader.BaseStream.Length) { var chunk = (M2Chunk)Read <uint>(); var size = Read <uint>(); if (chunk != M2Chunk.MD21) { Chunks.Add(chunk, size); } switch (chunk) { case M2Chunk.MD21: // Skip to modelname Array var modelName = Read <M2Array>(0x10); // Read modelname Reader.BaseStream.Position = modelName.Offset + 8; ModelName = new string(Reader.ReadChars((int)modelName.Size)); // Read M2Array<Texture>. Textures = Read <M2Array>(0x58); // Skip entire MD20 chunk. Reader.BaseStream.Position = 0x8 + size; break; case M2Chunk.TXID: ReadTXID(size); break; default: Reader.BaseStream.Position += size; Console.WriteLine($"Skipping chunk: {chunk}"); break; } } DataSize = Size - (int)CalculateChunksSize() - 8; TextureSize = CalculateTexturesSize(); // Read the Particle M2 Array. Particles = Read <M2Array>(0x130); }
public M2Array <M2Material> GetMaterials() { M2Array <M2Material> materials = new M2Array <M2Material>(); if (!_model.Has <MTLS>()) { return(materials); } var layers = _model.Get <MTLS>().SelectMany(x => x.Layers); foreach (var layer in layers) { M2Material material = new M2Material(); if (layer.Flags.HasFlag(MDLGEO.MODEL_GEO_UNSHADED)) { material.Flags |= M2Material.RenderFlags.Unlit; } if (layer.Flags.HasFlag(MDLGEO.MODEL_GEO_TWOSIDED)) { material.Flags |= M2Material.RenderFlags.TwoSided; } if (layer.Flags.HasFlag(MDLGEO.MODEL_GEO_UNFOGGED)) { material.Flags |= M2Material.RenderFlags.Unfogged; } switch (layer.BlendMode) { case MDLTEXOP.TEXOP_ADD: material.BlendMode |= M2Material.BlendingMode.Add; break; case MDLTEXOP.TEXOP_TRANSPARENT: // ?? case MDLTEXOP.TEXOP_MODULATE: material.BlendMode |= M2Material.BlendingMode.Mod; break; case MDLTEXOP.TEXOP_MODULATE2X: material.BlendMode |= M2Material.BlendingMode.Mod2X; break; case MDLTEXOP.TEXOP_ADD_ALPHA: material.BlendMode |= M2Material.BlendingMode.Decal; break; } materials.Add(material); } return(materials); }
public static M2Array<short> GenerateTexReplaceLookup(M2Array<M2Texture> textures) { var lookup = new M2Array<short>(); if (textures.Count == 0) return lookup; var maxId = (short) textures.Max(x => x.Type); for (short i = 0; i <= maxId; i++) lookup.Add(-1); for (short i = 0; i < textures.Count; i++) { var id = (short) textures[i].Type; if (lookup[id] == -1) lookup[id] = i; } return lookup; }
public static M2Array<short> GenerateLookup(M2Array<M2Attachment> attachments) { var lookup = new M2Array<short>(); if (attachments.Count == 0) return lookup; var maxId = attachments.Max(x => x.Id); for (short i = 0; i <= maxId; i++) lookup.Add(-1); for (short i = 0; i < attachments.Count; i++) { var id = (short) attachments[i].Id; if (lookup[id] == -1) lookup[id] = i; } return lookup; }
public M2Array <M2Bone> GetBones() { var keybones = Enum.GetNames(typeof(M2Bone.KeyBone)).Select(x => x.ToLower()).ToList(); M2Array <M2Bone> bones = new M2Array <M2Bone>(); for (int i = 0; i < _model.Get <PIVT>().Count; i++) { var obj = _model.Hierachy[i]; M2Bone bone = new M2Bone() { KeyBoneId = (M2Bone.KeyBone)keybones.IndexOf(obj.Name.ToLower().Replace("_", "").TrimStart('$')), //Dirty but works ParentBone = (short)obj.ParentId, Pivot = _model.Get <PIVT>().ElementAt(i).ToC3Vector, }; obj.RotationKeys?.PopulateM2Track(bone.Rotation, _model.Get <SEQS>()); obj.ScaleKeys?.PopulateM2Track(bone.Scale, _model.Get <SEQS>()); obj.TranslationKeys?.PopulateM2Track(bone.Translation, _model.Get <SEQS>()); if (obj.TranslationKeys != null) { bone.Flags |= M2Bone.BoneFlags.Transformed; } if (obj is Bone b) { if (b.Flags.HasFlag(GENOBJECTFLAGS.BILLBOARD_LOCK_X)) { bone.Flags |= M2Bone.BoneFlags.CylindricalBillboardLockX; } if (b.Flags.HasFlag(GENOBJECTFLAGS.BILLBOARD_LOCK_Y)) { bone.Flags |= M2Bone.BoneFlags.CylindricalBillboardLockY; } if (b.Flags.HasFlag(GENOBJECTFLAGS.BILLBOARD_LOCK_Z)) { bone.Flags |= M2Bone.BoneFlags.CylindricalBillboardLockZ; } } bones.Add(bone); } return(bones); }
private void FixAnimations() { Animations = Read <M2Array>(0x1C); AnimationLookup = Read <M2Array>(0x24); for (var i = 0; i < Animations.Size; ++i) { var offset = (int)Animations.Offset + i * 0x40; var AnimId = Read <ushort>(offset); if (AnimId > 505) //< Max TLK Anim Id { var newAnimId = AnimId; switch (AnimId) { case 564: newAnimId = 37; break; case 548: newAnimId = 41; break; case 556: newAnimId = 42; break; case 552: newAnimId = 43; break; case 554: newAnimId = 44; break; case 562: newAnimId = 45; break; case 572: newAnimId = 39; break; case 574: newAnimId = 187; break; } if (AnimId != newAnimId) { ReplaceAnimLookup(AnimationIndex(newAnimId), newAnimId, (short)i); Write <ushort>(newAnimId, offset); } } offset += 0x1C; // Fix Animation Speed Write <uint>(Read <uint>(offset) & 0xFFFF, offset); } }
public M2Array <M2Vertex> GetVertices() { M2Array <M2Vertex> vertices = new M2Array <M2Vertex>(); Func <int, byte[]> GetWeights = (c) => { byte[] w = new byte[4]; for (int i = 0; i < c; i++) { w[i] = (byte)Math.Ceiling(255f / c); } if ((255 % c) != 0) { w[c - 1]--; //Must add up to 255 } return(w); }; foreach (var geo in _model.Get <GEOS>()) { var indicies = geo.GetIndicies(); for (int i = 0; i < geo.Vertices.Count; i++) { //Calculate weights int count = indicies[i].Skip(1).TakeWhile(x => x != 0).Count() + 1; //Count non-zero indicies excluding first byte[] weights = GetWeights(count); //Build weights M2Vertex vertex = new M2Vertex() { BoneIndices = indicies[i], BoneWeights = weights, Normal = geo.Normals[i].ToC3Vector, Position = geo.Vertices[i].ToC3Vector, TexCoords = new[] { geo.TexCoords[i].ToC2Vector, new C2Vector() } }; vertices.Add(vertex); } } return(vertices); }
public M2Array <short> GetBoneLookup() { List <short> Bones = new List <short>(); foreach (var geo in _model.Get <GEOS>()) { foreach (var triangle in geo.FaceVertices) { var tri = triangle.ToArray(); short[] triBones = new short[12]; for (int i = 0; i < 3; i++) //Iterate triangle { byte[] vertexproperty = new byte[4]; var vertex = Model.GlobalVertexList[tri[i]]; for (int j = 0; j < 4; j++) //Iterate vertices { if (vertex.BoneWeights[j] != 0) { if (!Bones.Contains(vertex.BoneIndices[j])) { Bones.Add(vertex.BoneIndices[j]); //Store Bone if new } vertexproperty[j] = (byte)Bones.IndexOf(vertex.BoneIndices[j]); //Build Vertex Property } } Model.Views[0].Properties.Add(new VertexProperty(vertexproperty)); } } } var result = new M2Array <short>(); for (int i = 0; i < 4; i++) { result.AddRange(Bones); } return(result); }
public M2Array <M2TextureTransform> GetTextureTransform() { M2Array <M2TextureTransform> transforms = new M2Array <M2TextureTransform>(); if (!_model.Has <TXAN>()) { return(transforms); } foreach (var txan in _model.Get <TXAN>()) { var anim = new M2TextureTransform(); txan.RotationKeys?.PopulateM2Track(anim.Rotation, _model.Get <SEQS>()); txan.ScaleKeys?.PopulateM2Track(anim.Scale, _model.Get <SEQS>()); txan.TranslationKeys?.PopulateM2Track(anim.Translation, _model.Get <SEQS>()); transforms.Add(anim); } return(transforms); }
public M2Array <M2Event> GetEvents() { M2Array <M2Event> events = new M2Array <M2Event>(); if (!_model.Has <EVTS>()) { return(events); } foreach (var e in _model.Get <EVTS>()) { events.Add(new M2Event() { Identifier = e.Name, Position = _model.Get <PIVT>().ElementAt(e.ObjectId).ToC3Vector }); } return(events); }
public M2Array <M2Texture> GetTextures() { M2Array <M2Texture> textures = new M2Array <M2Texture>(); if (!_model.Has <TEXS>()) { return(textures); } foreach (var t in _model.Get <TEXS>()) { M2Texture texture = new M2Texture() { Flags = (M2Texture.TextureFlags)t.Flags, Name = t.Image, Type = (M2Texture.TextureType)t.ReplaceableId }; textures.Add(texture); } return(textures); }
public static M2Array<short> GenerateKeyBoneLookup(M2Array<M2Bone> bones) { var lookup = new M2Array<short>(); var maxId = (int) bones.Max(x => x.KeyBoneId); for (short i = 0; i < maxId + 1; i++) lookup.Add(-1); for (short i = 0; i < bones.Count; i++) { var id = (int) bones[i].KeyBoneId; if (id >= 0 && lookup[id] == -1) lookup[id] = i; } return lookup; }
/// <summary> /// Create data of a WoW M2 Bone with the values of this class. /// </summary> public M2Bone ToBone() { var bone = new M2Bone { KeyBoneId = GetBoneType(), ParentBone = (short)(Parent?.GlobalIndex ?? -1), Pivot = ConvertVector(AxisInvert(GetPivot())) }; foreach (var list in Translation) { var timestamps = new M2Array <uint>(); var values = new M2Array <C3Vector>(); foreach (var pair in list) { timestamps.Add(pair.Item1); values.Add(ConvertVector(AxisInvert(pair.Item2 - BaseTranslation))); } bone.Translation.Timestamps.Add(timestamps); bone.Translation.Values.Add(values); } foreach (var list in Rotation) { var timestamps = new M2Array <uint>(); var values = new M2Array <C4Quaternion>(); foreach (var pair in list) { timestamps.Add(pair.Item1); values.Add(ConvertVector(AxisInvert(pair.Item2))); } bone.Rotation.Timestamps.Add(timestamps); bone.Rotation.Values.Add(values); } foreach (var list in Scale) { var timestamps = new M2Array <uint>(); var values = new M2Array <C3Vector>(); foreach (var pair in list) { timestamps.Add(pair.Item1); values.Add(ConvertVector(ScaleAxisInvert(pair.Item2))); } bone.Scale.Timestamps.Add(timestamps); bone.Scale.Values.Add(values); } //TODO maybe bezier ? if (Translation.Count > 0) { bone.Translation.InterpolationType = M2Track <C3Vector> .InterpolationTypes.Linear; } if (Rotation.Count > 0) { bone.Rotation.InterpolationType = M2Track <C4Quaternion> .InterpolationTypes.Linear; } if (Scale.Count > 0) { bone.Scale.InterpolationType = M2Track <C3Vector> .InterpolationTypes.Linear; } if (Translation.Count > 0 || Rotation.Count > 0 || Scale.Count > 0) { bone.Flags |= M2Bone.BoneFlags.Transformed; } return(bone); }
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)); } }
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 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 M2Array <M2Camera> GetCameras() { M2Array <M2Camera> cameras = new M2Array <M2Camera>(); if (!_model.Has <CAMS>()) { return(cameras); } foreach (var c in _model.Get <CAMS>()) { M2Camera camera = new M2Camera() { FarClip = c.FarClip, FieldOfView = new M2Track <C3Vector>(), NearClip = c.NearClip, PositionBase = c.Pivot.ToC3Vector, TargetPositionBase = c.TargetPosition.ToC3Vector }; camera.FieldOfView.Timestamps.Add(new M2Array <uint>() { 0 }); camera.FieldOfView.Values.Add(new M2Array <C3Vector>() { new C3Vector(c.FieldOfView, 0, 0) }); switch (c.Name.ToUpper()) { case "PORTRAIT": camera.Type = M2Camera.CameraType.Portrait; break; case "PAPERDOLL": camera.Type = M2Camera.CameraType.CharacterInfo; break; default: continue; } cameras.Add(camera); } //Add missing CharacterInfo camera if (!cameras.Any(x => x.Type == M2Camera.CameraType.CharacterInfo) && cameras.Any(x => x.Type == M2Camera.CameraType.Portrait)) { var c = cameras.First(x => x.Type == M2Camera.CameraType.Portrait); M2Camera camera = new M2Camera() { FarClip = c.FarClip, FieldOfView = c.FieldOfView, NearClip = c.NearClip, PositionBase = c.PositionBase, TargetPositionBase = c.TargetPositionBase, Type = M2Camera.CameraType.CharacterInfo }; cameras.Add(camera); } return(cameras); }
public static M2Array <T> ReadM2Array <T>(this BinaryReader reader, uint version) { var array = new M2Array <T>(reader, version); return(array.Count > 0 ? array : null); }