Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| using System; | |
| using System.Collections.Generic; | |
| using System.Drawing; | |
| using System.Drawing.Imaging; | |
| using System.IO; | |
| using System.Linq; | |
| using System.Runtime.InteropServices; | |
| using System.Text; | |
| using Sledge.DataStructures.Geometric; | |
| using Sledge.DataStructures.MapObjects; | |
| using Sledge.DataStructures.Models; | |
| using Sledge.FileSystem; | |
| namespace Sledge.Providers.Model | |
| { | |
| [Flags] | |
| public enum ModelLoadItems | |
| { | |
| Bones = 1 << 0, | |
| Meshes = 1 << 1, | |
| Animations = 1 << 2, | |
| TextureInfo = 1 << 3, | |
| TextureData = 1 << 4, | |
| Textures = TextureInfo | TextureData, | |
| AllStaticNoTextures = Bones | Meshes, | |
| AllStatic = Bones | Meshes | Textures, | |
| All = Bones | Meshes | Animations | Textures | |
| } | |
| /// <summary> | |
| /// Loader for Source and GoldSource MDL files. | |
| /// // TODO: Source animation support! Currently reads animations fine, but displays them incorrectly! | |
| /// </summary> | |
| public class MdlProvider : ModelProvider | |
| { | |
| protected override bool IsValidForFile(IFile file) | |
| { | |
| return file.Extension.ToLowerInvariant() == "mdl"; | |
| } | |
| protected override DataStructures.Models.Model LoadFromFile(IFile file) | |
| { | |
| return LoadMDL(file, ModelLoadItems.AllStatic | ModelLoadItems.Animations); | |
| } | |
| // Model loader for MDL files. Reference Valve's studiohdr_t struct definition for the most part. | |
| public DataStructures.Models.Model LoadMDL(IFile file, ModelLoadItems loadItems) | |
| { | |
| using (var fs = new MemoryStream(file.ReadAll())) | |
| { | |
| using(var br = new BinaryReader(fs)) | |
| { | |
| return ReadModel(br, file, loadItems); | |
| } | |
| } | |
| } | |
| private const string MagicStringIDST = "IDST"; | |
| private const string MagicStringIDSQ = "IDSQ"; | |
| private const string MagicStringIDSV = "IDSV"; | |
| private const int MDLVersionGoldsource = 10; // All GS games | |
| private const int MDLVersionSource2006 = 44; // HL2, CSS, EP1, LC | |
| private const int MDLVersionSourceEpisode2 = 45; // EP2 | |
| private const int MDLVersionSourcePortal = 46; // Portal | |
| private const int MDLVersionSource2007 = 48; // TF2 | |
| private const int MDLVersionSource2012 = 49; // AS, CSGO, DOTA2, L4D, L4D2, Portal 2 | |
| private const int VVDVersionSource = 4; | |
| private const int VTXVersionSource = 7; | |
| private const byte VTXStripGroupTriListFlag = 0x01; | |
| private const byte VTXStripGroupTriStripFlag = 0x02; | |
| private static DataStructures.Models.Model ReadModel(BinaryReader br, IFile file, ModelLoadItems loadItems) | |
| { | |
| // int id - Not really an int. This is a magic string, either "IDST" or "IDSQ". | |
| var magicString = br.ReadFixedLengthString(Encoding.UTF8, 4); | |
| if (magicString != MagicStringIDST && magicString != MagicStringIDSQ) | |
| { | |
| throw new ProviderException("Bad magic number for model. Expected [IDST,IDSQ], got: " + magicString); | |
| } | |
| // int version - Half-life 1 models are version 10. | |
| var version = br.ReadInt32(); | |
| if (version != MDLVersionGoldsource | |
| && version != MDLVersionSource2006 | |
| && version != MDLVersionSourceEpisode2 | |
| && version != MDLVersionSourcePortal | |
| && version != MDLVersionSource2007 | |
| && version != MDLVersionSource2012) | |
| { | |
| throw new ProviderException("Bad version number for model. Expected [10,44,45,46,48,49], got: " + version); | |
| } | |
| var modelData = new ModelData {Version = version}; | |
| if (version >= MDLVersionSource2006) | |
| { | |
| if (loadItems.HasFlag(ModelLoadItems.Meshes)) | |
| { | |
| // Source vertex and mesh info is stored in flat file structures, preload these separately. | |
| LoadSourceMeshData(modelData, file); | |
| } | |
| } | |
| long checksum = 0; | |
| if (version >= MDLVersionSource2006) | |
| { | |
| checksum = br.ReadInt32(); // This is a long in the headers but is only written to the file in 4 bytes. Why? I don't know. | |
| } | |
| // char name[64] - The name of the model (file path) | |
| var path = br.ReadFixedLengthString(Encoding.UTF8, 64); | |
| // int length - The size of the model file in bytes | |
| var fileSize = br.ReadInt32(); | |
| var eyePosition = br.ReadCoordinateF(); | |
| var illumPosition = CoordinateF.Zero; | |
| if (version >= MDLVersionSource2006) | |
| { | |
| illumPosition = br.ReadCoordinateF(); | |
| } | |
| var hullMin = br.ReadCoordinateF(); | |
| var hullMax = br.ReadCoordinateF(); | |
| var bbMin = br.ReadCoordinateF(); | |
| var bbMax = br.ReadCoordinateF(); | |
| // int flags - Unknown. | |
| var flags = br.ReadInt32(); | |
| var numBones = br.ReadInt32(); | |
| var boneIndex = br.ReadInt32(); | |
| var numBoneControllers = br.ReadInt32(); | |
| var boneControllerIndex = br.ReadInt32(); | |
| var numHitBoxes = br.ReadInt32(); | |
| var hitboxIndex = br.ReadInt32(); | |
| if (version >= MDLVersionSource2006) | |
| { | |
| var numAnim = br.ReadInt32(); | |
| var animIndex = br.ReadInt32(); | |
| if (loadItems.HasFlag(ModelLoadItems.Animations)) | |
| { | |
| // Source animation data is stored on their own instead of inside the sequence | |
| LoadSourceAnimationData(br, modelData, numAnim, animIndex); | |
| } | |
| } | |
| var numSeq = br.ReadInt32(); | |
| var seqIndex = br.ReadInt32(); | |
| int numSeqGroups = 0, seqGroupIndex = 0, activitylistversion = 0, eventsindexed = 0; | |
| if (version >= MDLVersionSource2006) | |
| { | |
| activitylistversion = br.ReadInt32(); | |
| eventsindexed = br.ReadInt32(); | |
| } | |
| else if (version == MDLVersionGoldsource) | |
| { | |
| numSeqGroups = br.ReadInt32(); | |
| seqGroupIndex = br.ReadInt32(); | |
| } | |
| var numTextures = br.ReadInt32(); | |
| var textureIndex = br.ReadInt32(); | |
| var textureDataIndex = 0; | |
| if (version == MDLVersionGoldsource) | |
| { | |
| textureDataIndex = br.ReadInt32(); | |
| } | |
| if (version >= MDLVersionSource2006) | |
| { | |
| var numcdtextures = br.ReadInt32(); | |
| var cdtextureindex = br.ReadInt32(); | |
| } | |
| var numSkinRef = br.ReadInt32(); | |
| var numSkinFamilies = br.ReadInt32(); | |
| var skinIndex = br.ReadInt32(); | |
| var numBodyParts = br.ReadInt32(); | |
| var bodyPartIndex = br.ReadInt32(); | |
| var numAttachments = br.ReadInt32(); | |
| var attachmentIndex = br.ReadInt32(); | |
| if (version >= MDLVersionSource2006) | |
| { | |
| var numlocalnodes = br.ReadInt32(); | |
| var localnodeindex = br.ReadInt32(); | |
| var localnodenameindex = br.ReadInt32(); | |
| var numflexdesc = br.ReadInt32(); | |
| var flexdescindex = br.ReadInt32(); | |
| var numflexcontrollers = br.ReadInt32(); | |
| var flexcontrollerindex = br.ReadInt32(); | |
| var numflexrules = br.ReadInt32(); | |
| var flexruleindex = br.ReadInt32(); | |
| var numikchains = br.ReadInt32(); | |
| var ikchainindex = br.ReadInt32(); | |
| var nummouths = br.ReadInt32(); | |
| var mouthindex = br.ReadInt32(); | |
| var numlocalposeparameters = br.ReadInt32(); | |
| var localposeparamindex = br.ReadInt32(); | |
| var surfacepropindex = br.ReadInt32(); | |
| var keyvalueindex = br.ReadInt32(); | |
| var keyvaluesize = br.ReadInt32(); | |
| var numlocalikautoplaylocks = br.ReadInt32(); | |
| var localikautoplaylockindex = br.ReadInt32(); | |
| var mass = br.ReadSingle(); | |
| var contents = br.ReadInt32(); | |
| var numincludemodels = br.ReadInt32(); | |
| var includemodelindex = br.ReadInt32(); | |
| var virtualModelPointer = br.ReadInt32(); | |
| var szanimblocknameindex = br.ReadInt32(); | |
| var numanimblocks = br.ReadInt32(); | |
| var animblockindex = br.ReadInt32(); | |
| var animblockModelPointer = br.ReadInt32(); | |
| var bonetablebynameindex = br.ReadInt32(); | |
| var pVertexBasePointer = br.ReadInt32(); | |
| var pIndexBasePointer = br.ReadInt32(); | |
| var constdirectionallightdot = br.ReadByte(); | |
| var rootLod = br.ReadByte(); | |
| var numAllowedRootLods = br.ReadByte(); // Unused in Source2006 | |
| br.ReadByte(); // Unused | |
| var zeroframecacheindex = br.ReadInt32(); // Unused in Source2007 | |
| if (version == MDLVersionSource2006) | |
| { | |
| br.ReadBytes(6); // Unused | |
| } | |
| else if (version == MDLVersionSource2007) | |
| { | |
| var numflexcontrollerui = br.ReadInt32(); | |
| var flexcontrolleruiindex = br.ReadInt32(); | |
| br.ReadIntArray(2); // Unused | |
| var studiohdr2Index = br.ReadInt32(); | |
| br.ReadInt32(); // Unused | |
| } | |
| } | |
| else if (version == MDLVersionGoldsource) | |
| { | |
| var soundTable = br.ReadInt32(); | |
| var soundIndex = br.ReadInt32(); | |
| var soundGroups = br.ReadInt32(); | |
| var soundGroupIndex = br.ReadInt32(); | |
| var numTransitions = br.ReadInt32(); | |
| var transitionIndex = br.ReadInt32(); | |
| } | |
| var model = new DataStructures.Models.Model(); | |
| model.Name = file.NameWithoutExtension; | |
| model.BonesTransformMesh = modelData.Version == MDLVersionGoldsource; | |
| if (loadItems.HasFlag(ModelLoadItems.Bones)) | |
| { | |
| // Bones | |
| br.BaseStream.Position = boneIndex; | |
| for (var i = 0; i < numBones; i++) ReadBone(br, i, modelData, model); | |
| } | |
| // Controllers | |
| // TODO | |
| // Attachments | |
| // TODO | |
| // Hitboxes | |
| // TODO | |
| if (loadItems.HasFlag(ModelLoadItems.Animations)) | |
| { | |
| if (version >= MDLVersionSource2006) | |
| { | |
| throw new ProviderException("Source animations are currently not supported."); | |
| } | |
| // Sequence Groups | |
| var groups = new List<SequenceGroup>(); | |
| br.BaseStream.Position = seqGroupIndex; | |
| for (var i = 0; i < numSeqGroups; i++) groups.Add(ReadSequenceGroup(br, modelData)); | |
| // Sequences | |
| br.BaseStream.Position = seqIndex; | |
| for (var i = 0; i < numSeq; i++) ReadSequence(br, i, modelData, model, groups); | |
| } | |
| // Transitions | |
| // TODO | |
| if (loadItems.HasFlag(ModelLoadItems.Meshes)) | |
| { | |
| // Body parts | |
| br.BaseStream.Position = bodyPartIndex; | |
| for (var i = 0; i < numBodyParts; i++) ReadBodyPart(br, i, modelData, model); | |
| } | |
| // Texture Info | |
| if (loadItems.HasFlag(ModelLoadItems.TextureInfo) || loadItems.HasFlag(ModelLoadItems.TextureData)) | |
| { | |
| ReadTextureInfo(file, br, modelData, model, numTextures, textureIndex); | |
| } | |
| // Textures | |
| return model; | |
| } | |
| #region Textures | |
| private static void ReadTextureInfo(IFile file, BinaryReader br, ModelData data, DataStructures.Models.Model model, int numTextures, int textureIndex) | |
| { | |
| if (data.Version == MDLVersionGoldsource) | |
| { | |
| var tempBr = br; | |
| var disp = false; | |
| if (numTextures == 0) | |
| { | |
| disp = true; | |
| var texFile = file.Parent.GetFile(file.NameWithoutExtension + "T." + file.Extension); | |
| br = new BinaryReader(texFile.Open()); | |
| br.BaseStream.Position = 180; // skip all the unused nonsense in the T file | |
| numTextures = br.ReadInt32(); | |
| textureIndex = br.ReadInt32(); | |
| var textureDataIndex = br.ReadInt32(); | |
| var numSkinRef = br.ReadInt32(); | |
| var numSkinFamilies = br.ReadInt32(); | |
| var skinIndex = br.ReadInt32(); | |
| } | |
| br.BaseStream.Position = textureIndex; | |
| for (var i = 0; i < numTextures; i++) | |
| { | |
| var name = br.ReadFixedLengthString(Encoding.ASCII, 64); | |
| var flags = br.ReadInt32(); | |
| var width = br.ReadInt32(); | |
| var height = br.ReadInt32(); | |
| var index = br.ReadInt32(); | |
| var savedPosition = br.BaseStream.Position; | |
| br.BaseStream.Position = index; | |
| var indices = br.ReadBytes(width * height); | |
| var palette = br.ReadBytes((byte.MaxValue + 1) * 3); | |
| var bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed); | |
| var pal = bmp.Palette; | |
| for (var j = 0; j <= byte.MaxValue; j++) | |
| { | |
| var k = j * 3; | |
| pal.Entries[j] = Color.FromArgb(255, palette[k], palette[k + 1], palette[k + 2]); | |
| } | |
| bmp.Palette = pal; | |
| var bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, bmp.PixelFormat); | |
| Marshal.Copy(indices, 0, bmpData.Scan0, indices.Length); | |
| bmp.UnlockBits(bmpData); | |
| var tex = new DataStructures.Models.Texture | |
| { | |
| Name = name, | |
| Index = i, | |
| Width = width, | |
| Height = height, | |
| Flags = flags, | |
| Image = bmp | |
| }; | |
| model.Textures.Add(tex); | |
| br.BaseStream.Position = savedPosition; | |
| } | |
| // | |
| if (disp) | |
| { | |
| br.BaseStream.Dispose(); | |
| br.Dispose(); | |
| br = tempBr; | |
| } | |
| } | |
| else if (data.Version >= MDLVersionSource2006) | |
| { | |
| } | |
| } | |
| #endregion | |
| #region Animation/Sequences | |
| private static void ReadSequence(BinaryReader br, int index, ModelData data, DataStructures.Models.Model model, IList<SequenceGroup> groups) | |
| { | |
| var startReadIndex = br.BaseStream.Position; | |
| var name = ""; | |
| var fps = 0f; | |
| if (data.Version == MDLVersionGoldsource) | |
| { | |
| name = br.ReadFixedLengthString(Encoding.ASCII, 32); | |
| fps = br.ReadSingle(); | |
| } | |
| else if (data.Version >= MDLVersionSource2006) | |
| { | |
| var baseIndex = br.ReadInt32(); | |
| var labelIndex = br.ReadInt32(); | |
| var activityNameIndex = br.ReadInt32(); | |
| } | |
| var flags = br.ReadInt32(); | |
| var activity = br.ReadInt32(); | |
| var actweight = br.ReadInt32(); | |
| var numevents = br.ReadInt32(); | |
| var eventindex = br.ReadInt32(); | |
| var numframes = 0; | |
| if (data.Version == MDLVersionGoldsource) | |
| { | |
| numframes = br.ReadInt32(); | |
| var numpivots = br.ReadInt32(); | |
| var pivotindex = br.ReadInt32(); | |
| var motiontype = br.ReadInt32(); | |
| var motionbone = br.ReadInt32(); | |
| var linearmovement = br.ReadCoordinateF(); | |
| var automoveposindex = br.ReadInt32(); | |
| var automoveangleindex = br.ReadInt32(); | |
| } | |
| var bbmin = br.ReadCoordinateF(); | |
| var bbmax = br.ReadCoordinateF(); | |
| var numblends = br.ReadInt32(); | |
| var animindex = br.ReadInt32(); | |
| var groupsize = new int[0]; | |
| if (data.Version >= MDLVersionSource2006) | |
| { | |
| var movementindex = br.ReadInt32(); | |
| groupsize = br.ReadIntArray(2); | |
| } | |
| var blendtype = br.ReadIntArray(2); // paramindex in source | |
| var blendstart = br.ReadSingleArray(2); // paramstart | |
| var blendend = br.ReadSingleArray(2); // paramend | |
| var blendparent = br.ReadInt32(); // paramparent | |
| var seqgroup = 0; | |
| if (data.Version == MDLVersionGoldsource) | |
| { | |
| seqgroup = br.ReadInt32(); | |
| } | |
| if (data.Version >= MDLVersionSource2006) | |
| { | |
| var fadeintime = br.ReadSingle(); | |
| var fadeouttime = br.ReadSingle(); | |
| } | |
| var entrynode = br.ReadInt32(); | |
| var exitnode = br.ReadInt32(); | |
| var nodeflags = br.ReadInt32(); | |
| if (data.Version >= MDLVersionSource2006) | |
| { | |
| var entryphase = br.ReadSingle(); | |
| var exitphase = br.ReadSingle(); | |
| var lastframe = br.ReadSingle(); | |
| } | |
| var nextseq = br.ReadInt32(); | |
| if (data.Version >= MDLVersionSource2006) | |
| { | |
| var pose = br.ReadInt32(); | |
| var numikrules = br.ReadInt32(); | |
| var numautolayers = br.ReadInt32(); | |
| var autolayerindex = br.ReadInt32(); | |
| var weightlistindex = br.ReadInt32(); | |
| var wlpos = br.BaseStream.Position; | |
| br.BaseStream.Position = startReadIndex + weightlistindex; | |
| var weightList = br.ReadSingleArray(model.Bones.Count); | |
| br.BaseStream.Position = wlpos; | |
| var posekeyindex = br.ReadInt32(); | |
| var numiklocks = br.ReadInt32(); | |
| var iklockindex = br.ReadInt32(); | |
| var keyvalueindex = br.ReadInt32(); | |
| var keyvaluesize = br.ReadInt32(); | |
| var cycleposeindex = br.ReadInt32(); | |
| br.ReadIntArray(7); // Unused | |
| } | |
| // Load animtion values | |
| var pos = br.BaseStream.Position; | |
| if (data.Version == MDLVersionGoldsource) | |
| { | |
| if (seqgroup > 0) | |
| { | |
| //TODO: load animations from other files | |
| return; | |
| // sub out br for another br against the new SG file | |
| // br = new BinaryReader(file....etc) | |
| // br.BaseStream.Position = animindex; | |
| } | |
| br.BaseStream.Position = groups[seqgroup].GroupZeroDataIndex + animindex; | |
| ReadAnimationGoldsource(br, model, numframes); | |
| } | |
| else if (data.Version >= MDLVersionSource2006) | |
| { | |
| br.BaseStream.Position = startReadIndex + animindex; | |
| ReadAnimationSource(br, data, model, groupsize); | |
| } | |
| br.BaseStream.Position = pos; | |
| } | |
| private static void ReadAnimationSource(BinaryReader br, ModelData data, DataStructures.Models.Model model, int[] groupsize) | |
| { | |
| if (groupsize.Length == 0) return; | |
| var animIndices = br.ReadShortArray(groupsize[0] * groupsize[1]); | |
| // Just use the first animation for now | |
| var srcAnim = data.SourceAnimations.FirstOrDefault(x => x.AnimID == animIndices[0]); | |
| if (srcAnim == null) return; | |
| var numframes = srcAnim.NumFrames; | |
| var anim = new Animation(); | |
| // Add all the empty frames | |
| for (var i = 0; i < numframes; i++) anim.Frames.Add(new AnimationFrame()); | |
| foreach (var bone in model.Bones) | |
| { | |
| var animBone = srcAnim.AnimationBones.FirstOrDefault(x => x.Bone == bone.BoneIndex); | |
| for (var f = 0; f < numframes; f++) | |
| { | |
| var fpos = CoordinateF.Zero; | |
| var fang = CoordinateF.Zero; | |
| if (animBone != null) | |
| { | |
| if (animBone.FixedPosition != null) fpos = animBone.FixedPosition; | |
| else if (animBone.FramePositions.Count > f) fpos = animBone.FramePositions[f]; | |
| if (animBone.FixedQuaternion == null) fang = animBone.FrameAngles[f]; | |
| } | |
| fpos = fpos.ComponentMultiply(bone.DefaultPositionScale);// +bone.DefaultPosition; | |
| var fangq = animBone != null && animBone.FixedQuaternion != null | |
| ? animBone.FixedQuaternion | |
| : QuaternionF.EulerAngles(fang.ComponentMultiply(bone.DefaultAnglesScale));// + bone.DefaultAngles); | |
| anim.Frames[f].Bones.Add(new BoneAnimationFrame(bone, fpos, fangq)); | |
| } | |
| } | |
| model.Animations.Add(anim); | |
| } | |
| private static void LoadSourceAnimationData(BinaryReader br, ModelData modelData, int numAnim, int animIndex) | |
| { | |
| modelData.SourceAnimations = new List<SourceAnimation>(); | |
| var restorePoint = br.BaseStream.Position; | |
| br.BaseStream.Position = animIndex; | |
| for (var i = 0; i < numAnim; i++) | |
| { | |
| var animStartPos = br.BaseStream.Position; | |
| var basePointer = br.ReadInt32(); | |
| var szNameIndex = br.ReadInt32(); | |
| var fps = br.ReadSingle(); | |
| var animDescFlags = br.ReadInt32(); | |
| var numframes = br.ReadInt32(); | |
| var numMovements = br.ReadInt32(); | |
| var movementIndex = br.ReadInt32(); | |
| br.ReadCoordinateFArray(2); // bounding box; unused | |
| var ablock = br.ReadInt32(); | |
| var aindex = br.ReadInt32(); | |
| var numIkRules = br.ReadInt32(); | |
| var ikRuleIndex = br.ReadInt32(); | |
| var animBlockIkRuleIndex = br.ReadInt32(); | |
| br.ReadIntArray(7); // Unused | |
| var animEndPos = br.BaseStream.Position; | |
| var sourceAnim = new SourceAnimation(i, numframes, fps, animDescFlags, numMovements, movementIndex, | |
| ablock, aindex, numIkRules, ikRuleIndex, animBlockIkRuleIndex); | |
| var currentOffset = aindex; | |
| short nextOffset; | |
| do | |
| { | |
| br.BaseStream.Position = animStartPos + currentOffset; | |
| var animBone = br.ReadByte(); | |
| var animFlags = br.ReadByte(); | |
| nextOffset = br.ReadInt16(); | |
| currentOffset += nextOffset; | |
| var aniBone = new SourceAnimationBone(animBone, animFlags, numframes); | |
| aniBone.ReadData(br); | |
| sourceAnim.AnimationBones.Add(aniBone); | |
| } while (nextOffset != 0); | |
| modelData.SourceAnimations.Add(sourceAnim); | |
| br.BaseStream.Position = animEndPos; | |
| } | |
| br.BaseStream.Position = restorePoint; | |
| } | |
| private static void ReadAnimationGoldsource(BinaryReader br, DataStructures.Models.Model model, int numframes) | |
| { | |
| var anim = new Animation(); | |
| // Add all the empty frames | |
| for (var i = 0; i < numframes; i++) anim.Frames.Add(new AnimationFrame()); | |
| // Now we have a reader with the position at the start of the animation data | |
| // First up is the list of offset indexes for the data, one for each bone in the model. | |
| // Bones are already loaded up, so loop through those. | |
| foreach (var bone in model.Bones) | |
| { | |
| var offsetPos = br.BaseStream.Position; | |
| var offsets = br.ReadShortArray(6); | |
| var restorePoint = br.BaseStream.Position; | |
| var position = bone.DefaultPosition; | |
| var angles = bone.DefaultAngles; | |
| var boneFrames = new List<float[]>(); | |
| for (var i = 0; i < numframes; i++) boneFrames.Add(new float[] {0, 0, 0, 0, 0, 0}); | |
| for (var i = 0; i < 6; i++) // For each offset [X, Y, Z, XR, YR, ZR] | |
| { | |
| if (offsets[i] <= 0) continue; | |
| br.BaseStream.Position = offsetPos + offsets[i]; | |
| var values = ReadRLEEncodedAnimationFrameValues(br, numframes); | |
| for (var f = 0; f < numframes; f++) | |
| { | |
| boneFrames[f][i] += values[f]; | |
| } | |
| } | |
| for (var f = 0; f < numframes; f++) | |
| { | |
| var frame = boneFrames[f]; | |
| var fpos = new CoordinateF(frame[0], frame[1], frame[2]).ComponentMultiply(bone.DefaultPositionScale) + bone.DefaultPosition; | |
| var fang = new CoordinateF(frame[3], frame[4], frame[5]).ComponentMultiply(bone.DefaultAnglesScale) + bone.DefaultAngles; | |
| anim.Frames[f].Bones.Add(new BoneAnimationFrame(bone, fpos, QuaternionF.EulerAngles(fang))); | |
| } | |
| br.BaseStream.Position = restorePoint; | |
| } | |
| model.Animations.Add(anim); | |
| } | |
| private static List<short> ReadRLEEncodedAnimationFrameValues(BinaryReader br, int numframes) | |
| { | |
| byte valid = 0, total = 0, cindex = 1; | |
| short lastValue = -1; | |
| var skip = false; | |
| var values = new List<short>(); | |
| for (var f = 0; f < numframes; f++) | |
| { | |
| if (cindex > total) | |
| { | |
| valid = br.ReadByte(); | |
| total = br.ReadByte(); | |
| cindex = 0; | |
| if (total == 0) | |
| { | |
| // zero out the remaining values | |
| skip = true; | |
| lastValue = valid = cindex = 0; | |
| } | |
| } | |
| if (cindex < valid) lastValue = br.ReadInt16(); | |
| if (!skip) cindex++; | |
| values.Add(lastValue); | |
| } | |
| return values; | |
| } | |
| private static SequenceGroup ReadSequenceGroup(BinaryReader br, ModelData data) | |
| { | |
| var name = br.ReadFixedLengthString(Encoding.ASCII, 32); | |
| var filename = br.ReadFixedLengthString(Encoding.ASCII, 64); | |
| var cachepointer = br.ReadInt32(); | |
| var groupZeroDataIndex = br.ReadInt32(); | |
| return new SequenceGroup | |
| { | |
| Name = name, | |
| FileName = filename, | |
| CachePointer = cachepointer, | |
| GroupZeroDataIndex = groupZeroDataIndex | |
| }; | |
| } | |
| #endregion | |
| #region Bones | |
| private static void ReadBone(BinaryReader br, int index, ModelData data, DataStructures.Models.Model model) | |
| { | |
| var name = ""; | |
| var nameIndex = 0; | |
| if (data.Version >= MDLVersionSource2006) | |
| { | |
| nameIndex = br.ReadInt32(); | |
| } | |
| else if (data.Version == MDLVersionGoldsource) | |
| { | |
| name = br.ReadFixedLengthString(Encoding.UTF8, 32); | |
| } | |
| var parent = br.ReadInt32(); | |
| int flags = 0; | |
| if (data.Version == MDLVersionGoldsource) | |
| { | |
| flags = br.ReadInt32(); | |
| } | |
| var boneController = br.ReadIntArray(6); // 3 pos, 3 rot | |
| var defPos = br.ReadCoordinateF(); | |
| QuaternionF quat = null; | |
| if (data.Version >= MDLVersionSource2006) | |
| { | |
| // quaternion | |
| quat = new QuaternionF(br.ReadCoordinateF(), br.ReadSingle()); | |
| } | |
| var defAng = br.ReadCoordinateF(); | |
| var defPosScale = br.ReadCoordinateF(); | |
| var defAngScale = br.ReadCoordinateF(); | |
| if (data.Version >= MDLVersionSource2006) | |
| { | |
| var poseToBone = br.ReadIntArray(12); // 3x4 matrix | |
| var qAlignment = new QuaternionF(br.ReadCoordinateF(), br.ReadSingle()); | |
| flags = br.ReadInt32(); | |
| var proctype = br.ReadInt32(); | |
| var procindex = br.ReadInt32(); | |
| var physicsbone = br.ReadInt32(); | |
| var surfacepropidx = br.ReadInt32(); | |
| var contents = br.ReadInt32(); | |
| br.ReadIntArray(8); // Unused | |
| } | |
| var parentBone = parent < 0 ? null : model.Bones[parent]; | |
| model.Bones.Add(new Bone(index, parent, parentBone, name, defPos, defAng, defPosScale, defAngScale)); | |
| } | |
| #endregion | |
| #region Body Parts/Vertices | |
| private static void ReadBodyPart(BinaryReader br, int bodyPartIndex, ModelData data, DataStructures.Models.Model model) | |
| { | |
| var startIndex = data.Version == MDLVersionGoldsource ? 0 : br.BaseStream.Position; | |
| var name = ""; | |
| var nameIndex = 0; | |
| if (data.Version >= MDLVersionSource2006) | |
| { | |
| nameIndex = br.ReadInt32(); | |
| var idx = br.BaseStream.Position; | |
| br.BaseStream.Position = startIndex + nameIndex; | |
| name = br.ReadNullTerminatedString(); | |
| br.BaseStream.Position = idx; | |
| } | |
| else if (data.Version == MDLVersionGoldsource) | |
| { | |
| name = br.ReadFixedLengthString(Encoding.UTF8, 64); | |
| } | |
| var numModels = br.ReadInt32(); | |
| var baseIndex = br.ReadInt32(); | |
| var modelIndex = br.ReadInt32(); | |
| var endIndex = br.BaseStream.Position; | |
| br.BaseStream.Position = modelIndex + startIndex; | |
| for (var i = 0; i < numModels; i++) | |
| { | |
| ReadStudioModel(br, name, bodyPartIndex, i, data, model); | |
| } | |
| br.BaseStream.Position = endIndex; | |
| } | |
| private static void ReadStudioModel(BinaryReader br, string groupName, int bodyPartIndex, int modelIndex, ModelData data, DataStructures.Models.Model model) | |
| { | |
| var startModelPos = br.BaseStream.Position; | |
| var name = br.ReadFixedLengthString(Encoding.ASCII, 64); | |
| var type = br.ReadInt32(); | |
| var radius = br.ReadSingle(); | |
| var numMesh = br.ReadInt32(); | |
| var meshIndex = br.ReadInt32(); | |
| var numVerts = br.ReadInt32(); | |
| var vertInfoIndex = 0; | |
| if (data.Version == MDLVersionGoldsource) | |
| { | |
| vertInfoIndex = br.ReadInt32(); | |
| } | |
| var vertIndex = br.ReadInt32(); | |
| int numNorms = 0, normInfoIndex = 0, normIndex = 0; | |
| if (data.Version == MDLVersionGoldsource) | |
| { | |
| numNorms = br.ReadInt32(); | |
| normInfoIndex = br.ReadInt32(); | |
| normIndex = br.ReadInt32(); | |
| } | |
| else if (data.Version >= MDLVersionSource2006) | |
| { | |
| var tangentsIndex = br.ReadInt32(); | |
| } | |
| var numGroups = br.ReadInt32(); // Attachments in source | |
| var groupIndex = br.ReadInt32(); // Attachments | |
| if (data.Version >= MDLVersionSource2006) | |
| { | |
| var numEyeballs = br.ReadInt32(); | |
| var eyeballIndex = br.ReadInt32(); | |
| var vertexDataPointer = br.ReadInt32(); | |
| var tangentDataPointer = br.ReadInt32(); | |
| br.ReadIntArray(8); // Unused | |
| } | |
| var endPos = br.BaseStream.Position; | |
| if (data.Version == MDLVersionGoldsource) | |
| { | |
| ReadVerticesGoldSource(br, groupName, modelIndex, model, numVerts, vertInfoIndex, vertIndex, numMesh, meshIndex, numNorms, normInfoIndex, normIndex); | |
| } | |
| else if (data.Version >= MDLVersionSource2006) | |
| { | |
| ReadVerticesSource(br, groupName, bodyPartIndex, modelIndex, data, model, numMesh, startModelPos + meshIndex); | |
| } | |
| br.BaseStream.Position = endPos; | |
| } | |
| private static void ReadVerticesGoldSource(BinaryReader br, string bodyPartName, int modelIndex, DataStructures.Models.Model model, int numVerts, int vertInfoIndex, int vertIndex, int numMesh, int meshIndex, int numNorms, int normInfoIndex, int normIndex) | |
| { | |
| br.BaseStream.Position = vertInfoIndex; | |
| var vertInfoData = br.ReadByteArray(numVerts); | |
| br.BaseStream.Position = normInfoIndex; | |
| var normInfoData = br.ReadByteArray(numNorms); | |
| br.BaseStream.Position = vertIndex; | |
| var vertices = br.ReadCoordinateFArray(numVerts); | |
| br.BaseStream.Position = normIndex; | |
| var normals = br.ReadCoordinateFArray(numNorms); | |
| br.BaseStream.Position = meshIndex; | |
| for (var i = 0; i < numMesh; i++) | |
| { | |
| var mesh = new Mesh(0); // GoldSource meshes don't have LODs | |
| var meshNumTris = br.ReadInt32(); | |
| var meshTriIndex = br.ReadInt32(); | |
| var meshSkinRef = br.ReadInt32(); | |
| var meshNumNorms = br.ReadInt32(); | |
| var meshNormIndex = br.ReadInt32(); | |
| mesh.SkinRef = meshSkinRef; | |
| var pos = br.BaseStream.Position; | |
| br.BaseStream.Position = meshTriIndex; | |
| int sh; | |
| // Read all the triangle strips and fans from the mesh and convert into easy-to-render 3-point triangles | |
| while ((sh = br.ReadInt16()) != 0) | |
| { | |
| var list = new List<MdlProviderSequenceDataPoint>(); | |
| var fan = sh < 0; | |
| if (fan) sh = -sh; // Negative value flags a fan, otherwise it is a strip | |
| for (var j = 0; j < sh; j++) // Read the points in the sequence | |
| { | |
| list.Add(new MdlProviderSequenceDataPoint | |
| { | |
| Vertex = br.ReadInt16(), // Vertex index in the vertices array | |
| Normal = br.ReadInt16(), // Normal index in the normals array | |
| TextureS = br.ReadInt16(), | |
| TextureT = br.ReadInt16() | |
| }); | |
| } | |
| for (var j = 0; j < list.Count - 2; j++) | |
| { | |
| // Get the vert indices to use for the various types of strip/fan | |
| // |TRIANGLE FAN | |TRIANGLE STRIP (ODD)| |TRIANGLE STRIP (EVEN)| | |
| var add = fan ? new[] {0, j + 1, j + 2} : (j % 2 == 1 ? new[] {j + 1, j, j + 2 } : new[] {j, j + 1, j + 2 }); | |
| foreach (var idx in add) | |
| { | |
| var vi = list[idx]; | |
| var boneIndex = vertInfoData[vi.Vertex]; // Vertinfo tells what bone the vert belongs to | |
| mesh.Vertices.Add(new MeshVertex( | |
| vertices[vi.Vertex], | |
| normals[vi.Normal], | |
| model.Bones[boneIndex], | |
| vi.TextureS, | |
| vi.TextureT)); | |
| } | |
| } | |
| } | |
| model.AddMesh(bodyPartName, modelIndex, mesh); | |
| br.BaseStream.Position = pos; | |
| } | |
| } | |
| private static void ReadVerticesSource(BinaryReader br, string groupName, int bodyPartIndex, int modelIndex, ModelData modelData, DataStructures.Models.Model model, int numMesh, long meshIndex) | |
| { | |
| br.BaseStream.Position = meshIndex; | |
| for (var i = 0; i < numMesh; i++) | |
| { | |
| var material = br.ReadInt32(); | |
| var modelOffset = br.ReadInt32(); | |
| var numVerts = br.ReadInt32(); | |
| var vertexOffset = br.ReadInt32(); | |
| var numFlexes = br.ReadInt32(); | |
| var flexIndex = br.ReadInt32(); | |
| var materialType = br.ReadInt32(); | |
| var materialParam = br.ReadInt32(); | |
| var meshId = br.ReadInt32(); | |
| var center = br.ReadCoordinateF(); | |
| var modelVertexDataPointer = br.ReadInt32(); | |
| var numLODVertices = br.ReadIntArray(8); | |
| br.ReadIntArray(8); // Unused | |
| foreach (var mm in modelData.Meshes.Where(mm => mm.BodyPart == bodyPartIndex | |
| && mm.Model == modelIndex | |
| && mm.LOD == 0 | |
| && mm.MeshIndex == meshId)) | |
| { | |
| var mesh = new Mesh(mm.LOD); | |
| foreach (var point in mm.Mesh.Points) | |
| { | |
| var vert = modelData.Vertices[point.VertexIndex + vertexOffset]; | |
| var boneWeights = new List<BoneWeighting>(); | |
| for (var j = 0; j < vert.NumBones; j++) | |
| { | |
| boneWeights.Add(new BoneWeighting(model.Bones[vert.Bones[j]], vert.BoneWeights[j])); | |
| } | |
| var mv = new MeshVertex(vert.Position, vert.Normal, boneWeights, vert.TextureS, vert.TextureT); | |
| mesh.Vertices.Add(mv); | |
| } | |
| model.AddMesh(groupName, modelIndex, mesh); | |
| } | |
| } | |
| } | |
| #endregion | |
| #region LoadSourceMeshData | |
| private static void LoadSourceMeshData(ModelData modelData, IFile file) | |
| { | |
| modelData.Meshes = new List<VTXModel>(); | |
| // In Source the vertices are saved to the VVD file | |
| // The vertex windings are saved in the VTX file | |
| var vvd = file.GetRelatedFile("vvd"); | |
| var vtx = file.GetRelatedFile("vtx"); | |
| if (vvd == null) throw new ProviderException("Unable to locate " + file.NameWithoutExtension + ".vvd"); | |
| if (vtx == null) throw new ProviderException("Unable to locate " + file.NameWithoutExtension + ".vtx"); | |
| var vertices = new List<VVDPoint>(); | |
| using (var fs = vvd.Open()) | |
| { | |
| using (var vbr = new BinaryReader(fs)) | |
| { | |
| var magicString = vbr.ReadFixedLengthString(Encoding.UTF8, 4); | |
| if (magicString != MagicStringIDSV) | |
| { | |
| throw new ProviderException("Bad magic number for vertex file. Expected IDSV, got: " + magicString); | |
| } | |
| var version = vbr.ReadInt32(); | |
| if (version != VVDVersionSource) | |
| { | |
| throw new ProviderException("Bad version number for vertex file. Expected 4, got: " + version); | |
| } | |
| long checksum = vbr.ReadInt32(); | |
| var numLods = vbr.ReadInt32(); | |
| var numLodVertices = vbr.ReadIntArray(8); | |
| var numFixups = vbr.ReadInt32(); | |
| var fixupTableStart = vbr.ReadInt32(); | |
| var vertexDataStart = vbr.ReadInt32(); | |
| var tangentDataStart = vbr.ReadInt32(); | |
| vbr.BaseStream.Position = vertexDataStart; | |
| // Read all the vertices from LOD 0 (this should contain the vertices for all LODs) | |
| for (var i = 0; i < numLodVertices[0]; i++) | |
| { | |
| var boneWeights = vbr.ReadSingleArray(3); | |
| var bones = vbr.ReadBytes(3); | |
| var numBones = vbr.ReadByte(); | |
| var position = vbr.ReadCoordinateF(); | |
| var normal = vbr.ReadCoordinateF(); | |
| var textureS = vbr.ReadSingle(); | |
| var textureT = vbr.ReadSingle(); | |
| vertices.Add(new VVDPoint(boneWeights, bones, numBones, position, normal, textureS, textureT)); | |
| } | |
| // Apply the fixup table, this re-orders the indices in reverse LOD order for performance reasons | |
| if (numFixups > 0) | |
| { | |
| vbr.BaseStream.Position = fixupTableStart; | |
| var newVerts = new List<VVDPoint>(); | |
| for (var i = 0; i < numFixups; i++) | |
| { | |
| var fuLod = vbr.ReadInt32(); | |
| var fuvertid = vbr.ReadInt32(); | |
| var funumverts = vbr.ReadInt32(); | |
| newVerts.AddRange(vertices.GetRange(fuvertid, funumverts)); | |
| } | |
| vertices.Clear(); | |
| vertices.AddRange(newVerts); | |
| } | |
| modelData.Vertices = vertices; | |
| } | |
| } | |
| using (var fs = vtx.Open()) | |
| { | |
| using (var vbr = new BinaryReader(fs)) | |
| { | |
| var version = vbr.ReadInt32(); // 7 | |
| if (version != VTXVersionSource) | |
| { | |
| throw new ProviderException("Bad version number for vertex file. Expected 7, got: " + version); | |
| } | |
| var vertCacheSize = vbr.ReadInt32(); | |
| var maxBonesPerStrip = vbr.ReadUInt16(); | |
| var maxBonesPerTri = vbr.ReadUInt16(); | |
| var maxBonesPerVert = vbr.ReadInt32(); | |
| long checksum = vbr.ReadInt32(); | |
| var numLods = vbr.ReadInt32(); | |
| var materialReplacementListOffset = vbr.ReadInt32(); | |
| var numBodyParts = vbr.ReadInt32(); | |
| var bodyPartOffset = vbr.ReadInt32(); | |
| // BODY PARTS | |
| long posbp = bodyPartOffset; | |
| for (var bp = 0; bp < numBodyParts; bp++) | |
| { | |
| vbr.BaseStream.Position = posbp; | |
| var numModels = vbr.ReadInt32(); | |
| var modelOffset = vbr.ReadInt32(); | |
| var posmdl = posbp + modelOffset; | |
| posbp = vbr.BaseStream.Position; | |
| // MODELS | |
| for (var mdl = 0; mdl < numModels; mdl++) | |
| { | |
| vbr.BaseStream.Position = posmdl; | |
| var numLod = vbr.ReadInt32(); | |
| var lodOffset = vbr.ReadInt32(); | |
| var poslod = posmdl + lodOffset; | |
| posmdl = vbr.BaseStream.Position; | |
| // LODS | |
| for (var lod = 0; lod < numLod; lod++) | |
| { | |
| vbr.BaseStream.Position = poslod; | |
| var meshNum = vbr.ReadInt32(); | |
| var meshOffset = vbr.ReadInt32(); | |
| var switchPoint = vbr.ReadSingle(); | |
| var posmesh = poslod + meshOffset; | |
| poslod = vbr.BaseStream.Position; | |
| // MESHES | |
| for (var msh = 0; msh < meshNum; msh++) | |
| { | |
| vbr.BaseStream.Position = posmesh; | |
| var sgNum = vbr.ReadInt32(); | |
| var sgOffset = vbr.ReadInt32(); | |
| var meshFlags = vbr.ReadByte(); | |
| var possg = posmesh + sgOffset; | |
| posmesh = vbr.BaseStream.Position; | |
| var mesh = new VTXModel(bp, mdl, lod, msh); | |
| // STRIP GROUPS | |
| for (var sg = 0; sg < sgNum; sg++) | |
| { | |
| vbr.BaseStream.Position = possg; | |
| var vertNum = vbr.ReadInt32(); | |
| var vertOffset = vbr.ReadInt32(); | |
| var indexNum = vbr.ReadInt32(); | |
| var indexOffset = vbr.ReadInt32(); | |
| var stripNum = vbr.ReadInt32(); | |
| var stripOffset = vbr.ReadInt32(); | |
| var sgFlags = vbr.ReadByte(); | |
| // vbr.ReadIntArray(2); //TODO FIXME Newer model format 49's (DOTA2, CSGO) have two extra integers here, (num + offset, purpose unknown) | |
| var posvert = possg + vertOffset; | |
| var posidx = possg + indexOffset; | |
| var posstrip = possg + stripOffset; | |
| possg = vbr.BaseStream.Position; | |
| var vertinfo = new List<VTXPoint>(); | |
| vbr.BaseStream.Position = posvert; | |
| for (var vert = 0; vert < vertNum; vert++) | |
| { | |
| var boneWeightIndices = vbr.ReadBytes(3); | |
| var numBones = vbr.ReadByte(); | |
| var meshVertex = vbr.ReadInt16(); | |
| var boneIDs = vbr.ReadBytes(3); | |
| vertinfo.Add(new VTXPoint(boneWeightIndices, numBones, meshVertex, boneIDs)); | |
| } | |
| vbr.BaseStream.Position = posidx; | |
| var indices = vbr.ReadShortArray(indexNum); | |
| // The strips hold info about whether this is a triangle strip or just a list | |
| vbr.BaseStream.Position = posstrip; | |
| for (var st = 0; st < stripNum; st++) | |
| { | |
| var numStIndices = vbr.ReadInt32(); | |
| var stIndexOffset = vbr.ReadInt32(); | |
| var numStVerts = vbr.ReadInt32(); | |
| var stVertOffset = vbr.ReadInt32(); | |
| var numStBones = vbr.ReadInt16(); | |
| var stFlags = vbr.ReadByte(); | |
| var numStBoneStateChanges = vbr.ReadInt32(); | |
| var stBoneStateChangeOffset = vbr.ReadInt32(); | |
| // vbr.ReadIntArray(2); //TODO FIXME Newer model format 49's (DOTA2, CSGO) have two extra integers here, (num + offset, purpose unknown) | |
| if ((stFlags & VTXStripGroupTriListFlag) > 0) | |
| { | |
| for (var j = stIndexOffset; j < stIndexOffset + numStIndices; j++) | |
| { | |
| mesh.Mesh.Points.Add(vertinfo[indices[j]]); | |
| //mesh.Vertices.Add(vertices[vertinfo[indices[j]]]); | |
| } | |
| } | |
| else if ((stFlags & VTXStripGroupTriStripFlag) > 0) | |
| { | |
| for (var j = stIndexOffset; j < stIndexOffset + numStIndices - 2; j++) | |
| { | |
| var add = j % 2 == 1 ? new[] { j + 1, j, j + 2 } : new[] { j, j + 1, j + 2 }; | |
| foreach (var idx in add) | |
| { | |
| mesh.Mesh.Points.Add(vertinfo[indices[idx]]); | |
| //mesh.Vertices.Add(vertices[vertinfo[indices[idx]]]); | |
| } | |
| } | |
| } | |
| } // Strips | |
| } // Strip Groups | |
| modelData.Meshes.Add(mesh); | |
| } // Meshes | |
| } // LODs | |
| } // Models | |
| } // Body Parts | |
| } // using (var br) | |
| } // using (var fs) | |
| } | |
| #endregion | |
| #region Data Structures | |
| private class ModelData | |
| { | |
| public int Version { get; set; } | |
| public List<VTXModel> Meshes { get; set; } | |
| public List<VVDPoint> Vertices { get; set; } | |
| public List<SourceAnimation> SourceAnimations { get; set; } | |
| } | |
| private class VTXModel | |
| { | |
| public int BodyPart { get; private set; } | |
| public int Model { get; private set; } | |
| public int LOD { get; private set; } | |
| public int MeshIndex { get; private set; } | |
| public VTXMesh Mesh { get; private set; } | |
| public VTXModel(int bodyPart, int model, int lod, int meshIndex) | |
| { | |
| BodyPart = bodyPart; | |
| Model = model; | |
| LOD = lod; | |
| MeshIndex = meshIndex; | |
| Mesh = new VTXMesh(); | |
| } | |
| } | |
| private class VTXMesh | |
| { | |
| public List<VTXPoint> Points { get; private set; } | |
| public VTXMesh() | |
| { | |
| Points = new List<VTXPoint>(); | |
| } | |
| } | |
| private class VTXPoint | |
| { | |
| public byte[] BoneWeightIndices { get; private set; } | |
| public int NumBones { get; private set; } | |
| public short VertexIndex { get; private set; } | |
| public byte[] BoneIDs { get; private set; } | |
| public VTXPoint(byte[] boneWeightIndices, int numBones, short vertexIndex, byte[] boneIDs) | |
| { | |
| BoneWeightIndices = boneWeightIndices; | |
| NumBones = numBones; | |
| VertexIndex = vertexIndex; | |
| BoneIDs = boneIDs; | |
| } | |
| } | |
| private class VVDPoint | |
| { | |
| public float[] BoneWeights { get; private set; } | |
| public byte[] Bones { get; private set; } | |
| public int NumBones { get; private set; } | |
| public CoordinateF Position { get; private set; } | |
| public CoordinateF Normal { get; private set; } | |
| public float TextureS { get; private set; } | |
| public float TextureT { get; private set; } | |
| public VVDPoint(float[] boneWeights, byte[] bones, int numBones, | |
| CoordinateF position, CoordinateF normal, | |
| float textureS, float textureT) | |
| { | |
| BoneWeights = boneWeights; | |
| Bones = bones; | |
| NumBones = numBones; | |
| Position = position; | |
| Normal = normal; | |
| TextureS = textureS; | |
| TextureT = textureT; | |
| } | |
| } | |
| private class SourceAnimation | |
| { | |
| public int AnimID { get; set; } | |
| public int NumFrames { get; set; } | |
| public float FPS { get; set; } | |
| public int Flags { get; set; } | |
| public int NumMovements { get; private set; } | |
| public int MovementIndex { get; private set; } | |
| public int AnimBlock { get; private set; } | |
| public int AnimIndex { get; private set; } | |
| public int NumIKRules { get; private set; } | |
| public int IKRuleIndex { get; private set; } | |
| public int AnimBlockIKRuleIndex { get; private set; } | |
| public List<SourceAnimationBone> AnimationBones { get; private set; } | |
| public SourceAnimation(int id, int numFrames, float fps, int flags, int numMovements, int movementIndex, int animBlock, int animIndex, int numIKRules, int ikRuleIndex, int animBlockIKRuleIndex) | |
| { | |
| AnimID = id; | |
| NumFrames = numFrames; | |
| FPS = fps; | |
| Flags = flags; | |
| NumMovements = numMovements; | |
| MovementIndex = movementIndex; | |
| AnimBlock = animBlock; | |
| AnimIndex = animIndex; | |
| NumIKRules = numIKRules; | |
| IKRuleIndex = ikRuleIndex; | |
| AnimBlockIKRuleIndex = animBlockIKRuleIndex; | |
| AnimationBones = new List<SourceAnimationBone>(); | |
| } | |
| } | |
| private class SourceAnimationBone | |
| { | |
| private const byte StudioAnimRawpos = 0x01; | |
| private const byte StudioAnimRawrot = 0x02; | |
| private const byte StudioAnimAnimpos = 0x04; | |
| private const byte StudioAnimAnimrot = 0x08; | |
| private const byte StudioAnimDelta = 0x10; | |
| const float Half = 0x8000; | |
| const float Quarter = 0x4000; | |
| public byte Bone { get; private set; } | |
| public byte Flags { get; private set; } | |
| public int NumFrames { get; private set; } | |
| public QuaternionF FixedQuaternion { get; set; } | |
| public CoordinateF FixedPosition { get; private set; } | |
| public List<CoordinateF> FrameAngles { get; private set; } | |
| public List<CoordinateF> FramePositions { get; private set; } | |
| public SourceAnimationBone(byte bone, byte flags, int numFrames) | |
| { | |
| Bone = bone; | |
| Flags = flags; | |
| NumFrames = numFrames; | |
| FixedPosition = null; | |
| FixedQuaternion = null; | |
| FramePositions = new List<CoordinateF>(); | |
| FrameAngles = new List<CoordinateF>(); | |
| } | |
| public void ReadData(BinaryReader br) | |
| { | |
| var delta = (Flags & StudioAnimDelta) > 0; | |
| if ((Flags & StudioAnimRawrot) > 0) | |
| { | |
| // WTF is this messy format :| | |
| // 48-bit Quaternion: 16-bit x, 16-bit y, 15-bit z, 1 bit to flag if w is negative | |
| // Convert into real quaternion with the algorithm below (taken from compressed_vector.h) | |
| int x = br.ReadUInt16(); | |
| int y = br.ReadUInt16(); | |
| var temp = br.ReadUInt16(); | |
| var z = temp & 0x7FFF; // Get the last 15 bits from the short | |
| var isWneg = (temp & 0x8000) > 0; // The first bit is the boolean value | |
| var w = (isWneg ? -1 : 1) * (float) Math.Sqrt(1 - x * x - y * y - z * z); | |
| FixedQuaternion = new QuaternionF((x - Half) / Half, (y - Half) / Half, (z - Quarter) / Quarter, w); | |
| } | |
| if ((Flags & StudioAnimRawpos) > 0) | |
| { | |
| // What's this? A custom made, 16-bit floating point implementation? WHAT DID I DO TO DESERVE THIS??? | |
| var bytes = br.ReadBytes(6); | |
| // Wait a minute..... | |
| var x = OpenTK.Half.FromBytes(bytes, 0).ToSingle(); | |
| var y = OpenTK.Half.FromBytes(bytes, 2).ToSingle(); | |
| var z = OpenTK.Half.FromBytes(bytes, 4).ToSingle(); | |
| // Ha ha, screw you, custom floating-point implementation! Thanks, OpenTK! | |
| FixedPosition = new CoordinateF(x, y, z); | |
| } | |
| if ((Flags & StudioAnimAnimrot) > 0) | |
| { | |
| // Why is this so painful :( | |
| // Read the per-frame data using RLE, just like GoldSource models | |
| var startPos = br.BaseStream.Position; | |
| var offsets = br.ReadShortArray(3); | |
| var endPos = br.BaseStream.Position; | |
| var rotFrames = new List<float[]>(); | |
| for (var i = 0; i < NumFrames; i++) rotFrames.Add(new float[] {0, 0, 0}); | |
| for (var i = 0; i < 3; i++) | |
| { | |
| if (offsets[i] == 0) continue; | |
| br.BaseStream.Position = startPos + offsets[i]; | |
| var values = ReadRLEEncodedAnimationFrameValues(br, NumFrames); | |
| for (var f = 0; f < values.Count; f++) | |
| { | |
| rotFrames[f][i] =+ values[f]; | |
| if (f > 0 && delta) rotFrames[f][i] += values[f - 1]; | |
| } | |
| } | |
| FrameAngles.AddRange(rotFrames.Select(x => new CoordinateF(x[0], x[1], x[2]))); | |
| br.BaseStream.Position = endPos; | |
| } | |
| if ((Flags & StudioAnimAnimpos) > 0) | |
| { | |
| // Same as above, except for the position coordinate | |
| var startPos = br.BaseStream.Position; | |
| var offsets = br.ReadShortArray(3); | |
| var endPos = br.BaseStream.Position; | |
| var posFrames = new List<float[]>(); | |
| for (var i = 0; i < NumFrames; i++) posFrames.Add(new float[] { 0, 0, 0 }); | |
| for (var i = 0; i < 3; i++) | |
| { | |
| if (offsets[i] == 0) continue; | |
| br.BaseStream.Position = startPos + offsets[i]; | |
| var values = ReadRLEEncodedAnimationFrameValues(br, NumFrames); | |
| for (var f = 0; f < values.Count; f++) | |
| { | |
| posFrames[f][i] = +values[f]; | |
| if (f > 0 && delta) posFrames[f][i] += values[f - 1]; | |
| } | |
| } | |
| FramePositions.AddRange(posFrames.Select(x => new CoordinateF(x[0], x[1], x[2]))); | |
| br.BaseStream.Position = endPos; | |
| } | |
| } | |
| } | |
| private class MdlProviderSequenceDataPoint | |
| { | |
| public short Vertex { get; set; } | |
| public short Normal { get; set; } | |
| public short TextureS { get; set; } | |
| public short TextureT { get; set; } | |
| } | |
| private class SequenceGroup | |
| { | |
| public string Name { get; set; } | |
| public string FileName { get; set; } | |
| public int CachePointer { get; set; } | |
| public int GroupZeroDataIndex { get; set; } | |
| } | |
| #endregion | |
| } | |
| public interface IModelTextureInfo | |
| { | |
| } | |
| } |