private void LoadFromStream(Stream dataStream) { using (BinaryReader br = new BinaryReader(dataStream)) { string dataSignature = new string(br.ReadBinarySignature().Reverse().ToArray()); if (dataSignature != Signature) { throw new ArgumentException("The provided data stream does not contain a valid MDX signature. " + "It might be a Legion file, or you may have omitted the signature, which should be \"MD20\"."); } Version = GetModelVersion(br.ReadUInt32()); Name = new string(br.ReadMDXArray <char>().GetValues().ToArray()); GlobalModelFlags = (ModelObjectFlags)br.ReadUInt32(); GlobalSequenceTimestamps = br.ReadMDXArray <uint>(); AnimationSequences = br.ReadMDXArray <MDXAnimationSequence>(Version); AnimationSequenceLookupTable = br.ReadMDXArray <ushort>(); if (Version < WarcraftVersion.Wrath) { PlayableAnimationLookupTable = br.ReadMDXArray <MDXPlayableAnimationLookupTableEntry>(); } Bones = br.ReadMDXArray <MDXBone>(Version); BoneSocketLookupTable = br.ReadMDXArray <ushort>(); Vertices = br.ReadMDXArray <MDXVertex>(); if (Version < WarcraftVersion.Wrath) { Skins = br.ReadMDXArray <MDXSkin>(Version); } else { // Skins are stored out of file, figure out a clean solution SkinCount = br.ReadUInt32(); } ColourAnimations = br.ReadMDXArray <MDXColourAnimation>(Version); Textures = br.ReadMDXArray <MDXTexture>(); TransparencyAnimations = br.ReadMDXArray <MDXTextureWeight>(Version); if (Version <= WarcraftVersion.BurningCrusade) { // There's an array of something here, but we've no idea what type of data it is. Thus, we'll skip // over it. br.BaseStream.Position += 8; } TextureTransformations = br.ReadMDXArray <MDXTextureTransform>(Version); ReplaceableTextureLookupTable = br.ReadMDXArray <short>(); Materials = br.ReadMDXArray <MDXMaterial>(Version); BoneLookupTable = br.ReadMDXArray <short>(); TextureLookupTable = br.ReadMDXArray <short>(); TextureMappingLookupTable = br.ReadMDXArray <EMDXTextureMappingType>(); TransparencyLookupTable = br.ReadMDXArray <short>(); TextureTransformationLookupTable = br.ReadMDXArray <short>(); BoundingBox = br.ReadBox(); BoundingSphereRadius = br.ReadSingle(); CollisionBox = br.ReadBox(); CollisionSphereRadius = br.ReadSingle(); CollisionTriangles = br.ReadMDXArray <ushort>(); CollisionVertices = br.ReadMDXArray <Vector3>(); CollisionNormals = br.ReadMDXArray <Vector3>(); Attachments = br.ReadMDXArray <MDXAttachment>(Version); AttachmentLookupTable = br.ReadMDXArray <MDXAttachmentType>(); AnimationEvents = br.ReadMDXArray <MDXAnimationEvent>(Version); Lights = br.ReadMDXArray <MDXLight>(Version); Cameras = br.ReadMDXArray <MDXCamera>(Version); CameraTypeLookupTable = br.ReadMDXArray <MDXCameraType>(); RibbonEmitters = br.ReadMDXArray <MDXRibbonEmitter>(Version); // TODO: Particle Emitters // Skip for now br.BaseStream.Position += 8; if (Version >= WarcraftVersion.Wrath && GlobalModelFlags.HasFlag(ModelObjectFlags.HasBlendModeOverrides)) { BlendMapOverrides = br.ReadMDXArray <BlendingMode>(); } } }
public MDX(Stream MDXStream) { using (BinaryReader br = new BinaryReader(MDXStream)) { // Read Wrath header or read pre-wrath header WarcraftVersion Format = PeekFormat(br); if (Format < WarcraftVersion.Wrath) { this.Header = new MDXHeader(br.ReadBytes(324)); } else { ModelObjectFlags Flags = PeekFlags(br); if (Flags.HasFlag(ModelObjectFlags.HasBlendModeOverrides)) { this.Header = new MDXHeader(br.ReadBytes(308)); } else { this.Header = new MDXHeader(br.ReadBytes(312)); } } // Seek and read model name br.BaseStream.Position = this.Header.NameOffset; this.Name = new string(br.ReadChars((int)this.Header.NameLength)); // Seek to Global Sequences br.BaseStream.Position = this.Header.GlobalSequencesOffset; for (int i = 0; i < this.Header.GlobalSequenceCount; ++i) { this.GlobalSequenceTimestamps.Add(br.ReadUInt32()); } // Seek to Animation Sequences br.BaseStream.Position = this.Header.AnimationSequencesOffset; int sequenceSize = MDXAnimationSequence.GetSize(); for (int i = 0; i < this.Header.AnimationSequenceCount; ++i) { this.AnimationSequences.Add(new MDXAnimationSequence(br.ReadBytes(sequenceSize))); } // Seek to Animation Sequence Lookup Table br.BaseStream.Position = this.Header.AnimationLookupTableOffset; for (int i = 0; i < this.Header.AnimationLookupTableEntryCount; ++i) { this.AnimationSequenceLookupTable.Add(br.ReadInt16()); } if (MDXHeader.GetModelVersion(this.Header.Version) < WarcraftVersion.Wrath) { // Seek to Playable Animations Lookup Table br.BaseStream.Position = this.Header.PlayableAnimationLookupTableOffset; for (int i = 0; i < this.Header.PlayableAnimationLookupTableEntryCount; ++i) { this.PlayableAnimationLookupTable.Add(new MDXPlayableAnimationLookupTableEntry(br.ReadBytes(4))); } } // Seek to bone block br.BaseStream.Position = this.Header.BonesOffset; for (int i = 0; i < this.Header.BoneCount; ++i) { // TODO: properly skip to the next bone record, data is not aligned MDXBone Bone = new MDXBone(); Bone.AnimationID = br.ReadInt32(); Bone.Flags = (MDXBoneFlags)br.ReadUInt32(); Bone.ParentBone = br.ReadInt16(); Bone.SubmeshID = br.ReadUInt16(); if (MDXHeader.GetModelVersion(this.Header.Version) >= WarcraftVersion.BurningCrusade) { Bone.Unknown1 = br.ReadUInt16(); Bone.Unknown1 = br.ReadUInt16(); } // TODO: Rework animation track reading // Read bone animation header block //Bone.AnimatedTranslation = new MDXTrack<Vector3f>(br, MDXHeader.GetModelVersion(Header.Version)); //Bone.AnimatedRotation = new MDXTrack<Quaternion>(br, MDXHeader.GetModelVersion(Header.Version)); //Bone.AnimatedScale = new MDXTrack<Vector3f>(br, MDXHeader.GetModelVersion(Header.Version)); Bone.PivotPoint = br.ReadVector3f(); this.Bones.Add(Bone); } /* * // Read bone animation data * foreach (MDXBone Bone in Bones) * { * // Read animation translation block * br.BaseStream.Position = Bone.AnimatedTranslation.Values.ElementsOffset; * for (int j = 0; j < Bone.AnimatedTranslation.Values.Count; ++j) * { * Bone.AnimatedTranslation.Values.Add(br.ReadVector3f()); * } * * // Read animation rotation block * br.BaseStream.Position = Bone.AnimatedRotation.ValuesOffset; * for (int j = 0; j < Bone.AnimatedRotation.ValueCount; ++j) * { * if (MDXHeader.GetModelVersion(Header.Version) > MDXFormat.Classic) * { * Bone.AnimatedRotation.Values.Add(br.ReadQuaternion16()); * } * else * { * Bone.AnimatedRotation.Values.Add(br.ReadQuaternion32()); * } * } * * // Read animation scale block * br.BaseStream.Position = Bone.AnimatedScale.ValuesOffset; * for (int j = 0; j < Bone.AnimatedScale.ValueCount; ++j) * { * Bone.AnimatedScale.Values.Add(br.ReadVector3f()); * } * } */ // Seek to Skeletal Bone Lookup Table br.BaseStream.Position = this.Header.KeyedBoneLookupTablesOffset; for (int i = 0; i < this.Header.KeyedBoneLookupTableCount; ++i) { this.KeyedBoneLookupTable.Add(br.ReadInt16()); } // Seek to vertex block br.BaseStream.Position = this.Header.VerticesOffset; for (int i = 0; i < this.Header.VertexCount; ++i) { this.Vertices.Add(new MDXVertex(br.ReadBytes(48))); } // Seek to view block if (MDXHeader.GetModelVersion(this.Header.Version) < WarcraftVersion.Wrath) { br.BaseStream.Position = this.Header.LODViewsOffset; // Read the view headers for (int i = 0; i < this.Header.LODViewsCount; ++i) { MDXViewHeader ViewHeader = new MDXViewHeader(br.ReadBytes(44)); MDXView View = new MDXView(); View.Header = ViewHeader; this.LODViews.Add(View); } // Read view data foreach (MDXView View in this.LODViews) { // Read view vertex indices View.VertexIndices = new List <ushort>(); br.BaseStream.Position = View.Header.VertexIndicesOffset; for (int j = 0; j < View.Header.VertexIndexCount; ++j) { View.VertexIndices.Add(br.ReadUInt16()); } // Read view triangles View.Triangles = new List <MDXTriangle>(); br.BaseStream.Position = View.Header.TriangleVertexIndicesOffset; for (int j = 0; j < View.Header.TriangleVertexCount / 3; ++j) { MDXTriangle Triangle = new MDXTriangle(br.ReadUInt16(), br.ReadUInt16(), br.ReadUInt16()); View.Triangles.Add(Triangle); } // Read view vertex properties View.VertexProperties = new List <MDXVertexProperty>(); br.BaseStream.Position = View.Header.VertexPropertiesOffset; for (int j = 0; j < View.Header.VertexPropertyCount; ++j) { View.VertexProperties.Add(new MDXVertexProperty(br.ReadByte(), br.ReadByte(), br.ReadByte(), br.ReadByte())); } // Read view submeshes View.Submeshes = new List <MDXSubmesh>(); br.BaseStream.Position = View.Header.SubmeshesOffset; for (int j = 0; j < View.Header.SubmeshCount; ++j) { byte[] submeshData; if (MDXHeader.GetModelVersion(this.Header.Version) >= WarcraftVersion.BurningCrusade) { submeshData = br.ReadBytes(48); } else { submeshData = br.ReadBytes(32); } View.Submeshes.Add(new MDXSubmesh(submeshData)); } View.TextureUnits = new List <MDXTextureUnit>(); br.BaseStream.Position = View.Header.TexturesOffset; for (int j = 0; j < View.Header.TextureCount; ++j) { View.TextureUnits.Add(new MDXTextureUnit(br.ReadBytes(24))); } } } else { throw new NotImplementedException(); } /* * // TODO: Rework animation track reading * // Seek to submesh animation block * br.BaseStream.Position = Header.SubmeshColourAnimationsOffset; * for (int i = 0; i < Header.SubmeshColourAnimationCount; ++i) * { * MDXTrack<RGB> ColourTrack = new MDXTrack<RGB>(br, MDXHeader.GetModelVersion(Header.Version)); * MDXTrack<short> OpacityTrack = new MDXTrack<short>(br, MDXHeader.GetModelVersion(Header.Version)); * * MDXSubmeshColourAnimation ColourAnimation = new MDXSubmeshColourAnimation(); * ColourAnimation.ColourTrack = ColourTrack; * ColourAnimation.OpacityTrack = OpacityTrack; * * ColourAnimations.Add(ColourAnimation); * } * // Read submesh animation values * foreach (MDXSubmeshColourAnimation ColourAnimation in ColourAnimations) * { * // Read the colour track * br.BaseStream.Position = ColourAnimation.ColourTrack.ValuesOffset; * for (int j = 0; j < ColourAnimation.ColourTrack.ValueCount; ++j) * { * ColourAnimation.ColourTrack.Values.Add(new RGB(br.ReadVector3f())); * } * * // Read the opacity track * br.BaseStream.Position = ColourAnimation.OpacityTrack.ValuesOffset; * for (int j = 0; j < ColourAnimation.OpacityTrack.ValueCount; ++j) * { * ColourAnimation.OpacityTrack.Values.Add(br.ReadInt16()); * } * } */ // TODO: Use this pattern for the tracks as well, where values are outreferenced // from the block // Seek to Texture definition block br.BaseStream.Position = this.Header.TexturesOffset; for (int i = 0; i < this.Header.TextureCount; ++i) { MDXTexture Texture = new MDXTexture(br.ReadBytes(16)); this.Textures.Add(Texture); } // Read the texture definition strings foreach (MDXTexture Texture in this.Textures) { br.BaseStream.Position = Texture.FilenameOffset; Texture.Filename = new string(br.ReadChars((int)Texture.FilenameLength)); } /* * // TODO: Rework animation track reading * // Seek to transparency block * br.BaseStream.Position = Header.TransparencyAnimationsOffset; * for (int i = 0; i < Header.TransparencyAnimationCount; ++i) * { * TransparencyAnimations.Add(new MDXTrack<short>(br, MDXHeader.GetModelVersion(Header.Version))); * } * // Read transparency animation block data * foreach (MDXTrack<short> TransparencyTrack in TransparencyAnimations) * { * // Read the opacity track * br.BaseStream.Position = TransparencyTrack.ValuesOffset; * for (int j = 0; j < TransparencyTrack.ValueCount; ++j) * { * TransparencyTrack.Values.Add(br.ReadInt16()); * } * } * * // TODO: Rework animation track reading * // UV Animations * br.BaseStream.Position = Header.UVTextureAnimationsOffset; * for (int i = 0; i < Header.UVTextureAnimationCount; ++i) * { * br.BaseStream.Position = Header.UVTextureAnimationsOffset + (i * 84); * * MDXUVAnimation UVAnimation = new MDXUVAnimation(); * UVAnimation.TranslationTrack = new MDXTrack<Vector3f>(br, MDXHeader.GetModelVersion(Header.Version)); * UVAnimation.RotationTrack = new MDXTrack<Quaternion>(br, MDXHeader.GetModelVersion(Header.Version)); * UVAnimation.ScaleTrack = new MDXTrack<Vector3f>(br, MDXHeader.GetModelVersion(Header.Version)); * * UVAnimations.Add(UVAnimation); * } * // Read UV animation track data * foreach (MDXUVAnimation UVAnimation in UVAnimations) * { * // Read animation translation block * br.BaseStream.Position = UVAnimation.TranslationTrack.ValuesOffset; * for (int j = 0; j < UVAnimation.TranslationTrack.ValueCount; ++j) * { * UVAnimation.TranslationTrack.Values.Add(br.ReadVector3f()); * } * * // Read animation rotation block * br.BaseStream.Position = UVAnimation.RotationTrack.ValuesOffset; * for (int j = 0; j < UVAnimation.RotationTrack.ValueCount; ++j) * { * if (MDXHeader.GetModelVersion(Header.Version) > MDXFormat.Classic) * { * UVAnimation.RotationTrack.Values.Add(br.ReadQuaternion16()); * } * else * { * UVAnimation.RotationTrack.Values.Add(br.ReadQuaternion32()); * } * } * * // Read animation scale block * br.BaseStream.Position = UVAnimation.ScaleTrack.ValuesOffset; * for (int j = 0; j < UVAnimation.ScaleTrack.ValueCount; ++j) * { * UVAnimation.ScaleTrack.Values.Add(br.ReadVector3f()); * } * } */ // Replaceable textures // Render flags // Seek to render flag block br.BaseStream.Position = this.Header.RenderFlagsOffset; for (int i = 0; i < this.Header.RenderFlagCount; ++i) { this.RenderFlags.Add(new MDXRenderFlagPair(br.ReadBytes(4))); } // Bone lookup // Texture lookup // Texture unit lookup // Seek to texture unit lookup block br.BaseStream.Position = this.Header.TextureUnitsOffset; for (int i = 0; i < this.Header.TextureUnitCount; ++i) { this.TextureUnitLookupTable.Add(br.ReadInt16()); } // Transparency lookup // Seek to transparency lookup table br.BaseStream.Position = this.Header.TransparencyLookupTablesOffset; for (int i = 0; i < this.Header.TransparencyLookupTableCount; ++i) { this.TransparencyLookupTable.Add(br.ReadInt16()); } // UV animation lookup // Bounding box // Bounding radius // Collision box // Collision radius // Bounding tris // Bounding verts // Bounding normals // Attachments // Attachment lookup // Anim notifies (events) // Lights // Cameras // Camera lookup // Ribbon Emitters // Particle Emitters // Blend maps (if flags say they exist) } }