using System.Collections.Generic; using System.IO; using System.Linq; using LibMMD.Motion; using LibMMD.Util; using UnityEngine; namespace LibMMD.Reader { public class VmdReader { public MmdMotion Read(string path) { using (var binaryReader = MmdReaderUtil.OpenBinaryReader(path)) { return Read(binaryReader); } } public MmdMotion Read(BinaryReader reader) { return ReadMotion(reader, true); } private MmdMotion ReadMotion(BinaryReader reader, bool readExtendedSections) { var motion = new TempMmdMotion(); var magic = MmdReaderUtil.ReadStringFixedLength(reader, 30, Tools.JapaneseEncoding); if (!"Vocaloid Motion Data 0002".Equals(magic)) { throw new MmdFileParseException("File is not a VMD file."); } motion.Name = MmdReaderUtil.ReadStringFixedLength(reader, 20, Tools.JapaneseEncoding); var boneMotionNum = reader.ReadInt32(); for (var i = 0; i < boneMotionNum; ++i) { var b = ReadVmdBone(reader); var keyFrame = motion.GetOrCreateBoneKeyFrame(b.BoneName, b.NFrame); keyFrame.Translation = b.Translation; keyFrame.Rotation = b.Rotation; Vector2 c0, c1; const float r = 1.0f / 127.0f; c0.x = b.XInterpolator[0] * r; c0.y = b.XInterpolator[4] * r; c1.x = b.XInterpolator[8] * r; c1.y = b.XInterpolator[12] * r; keyFrame.XInterpolator = new Interpolator(); keyFrame.XInterpolator.SetC(c0, c1); c0.x = b.YInterpolator[0] * r; c0.y = b.YInterpolator[4] * r; c1.x = b.YInterpolator[8] * r; c1.y = b.YInterpolator[12] * r; keyFrame.YInterpolator = new Interpolator(); keyFrame.YInterpolator.SetC(c0, c1); c0.x = b.ZInterpolator[0] * r; c0.y = b.ZInterpolator[4] * r; c1.x = b.ZInterpolator[8] * r; c1.y = b.ZInterpolator[12] * r; keyFrame.ZInterpolator = new Interpolator(); keyFrame.ZInterpolator.SetC(c0, c1); c0.x = b.RInterpolator[0] * r; c0.y = b.RInterpolator[4] * r; c1.x = b.RInterpolator[8] * r; c1.y = b.RInterpolator[12] * r; keyFrame.RInterpolator = new Interpolator(); keyFrame.RInterpolator.SetC(c0, c1); } var morphMotionNum = reader.ReadInt32(); for (var i = 0; i < morphMotionNum; ++i) { var vmdMorph = ReadVmdMorph(reader); var keyFrame = motion.GetOrCreateMorphKeyFrame(vmdMorph.MorphName, vmdMorph.NFrame); keyFrame.Weight = vmdMorph.Weight; keyFrame.WInterpolator = new Interpolator(); } if (readExtendedSections) { ReadExtendedSections(reader, motion); } return motion.BuildMmdMotion(); } public CameraMotion ReadCameraMotion(string path) { using (var binaryReader = MmdReaderUtil.OpenBinaryReader(path)) { return ReadCameraMotion(binaryReader); } } public CameraMotion ReadCameraMotion(BinaryReader reader, bool motionReadAlready = false) { if (!motionReadAlready) { ReadMotion(reader, false); } var ret = new CameraMotion(); var cameraMotionNum = reader.ReadInt32(); Dictionary keyframes = new Dictionary(); for(var i=0;i entry).ToList().OrderBy(kv => kv.Key).ToList(); ret.KeyFrames = frameList; return ret; } private VmdBone ReadVmdBone(BinaryReader reader) { return new VmdBone { BoneName = MmdReaderUtil.ReadStringFixedLength(reader, 15, Tools.JapaneseEncoding), NFrame = reader.ReadInt32(), Translation = MmdReaderUtil.ReadVector3(reader), Rotation = MmdReaderUtil.ReadQuaternion(reader), XInterpolator = reader.ReadBytes(16), YInterpolator = reader.ReadBytes(16), ZInterpolator = reader.ReadBytes(16), RInterpolator = reader.ReadBytes(16) }; } private VmdMorph ReadVmdMorph(BinaryReader reader) { return new VmdMorph { MorphName = MmdReaderUtil.ReadStringFixedLength(reader, 15, Tools.JapaneseEncoding), NFrame = reader.ReadInt32(), Weight = reader.ReadSingle() }; } private static void ReadExtendedSections(BinaryReader reader, TempMmdMotion motion) { // Old/minimal VMD files may end immediately after morph data. if (!TryReadCount(reader, out var cameraCount)) return; SkipRecords(reader, cameraCount, 61, "camera"); if (!TryReadCount(reader, out var lightCount)) return; SkipRecords(reader, lightCount, 28, "light"); if (!TryReadCount(reader, out var shadowCount)) return; SkipRecords(reader, shadowCount, 9, "self-shadow"); if (!TryReadCount(reader, out var ikFrameCount)) return; for (var i = 0; i < ikFrameCount; ++i) { EnsureRemaining(reader, 9, "IK frame"); var frame = reader.ReadInt32(); reader.ReadByte(); // model visibility var ikCount = reader.ReadUInt32(); if (ikCount > int.MaxValue) throw new MmdFileParseException("Invalid VMD IK entry count."); for (var j = 0; j < (int) ikCount; ++j) { EnsureRemaining(reader, 21, "IK entry"); var name = MmdReaderUtil.ReadStringFixedLength(reader, 20, Tools.JapaneseEncoding); motion.SetIkEnabled(name, frame, reader.ReadByte() != 0); } } } private static bool TryReadCount(BinaryReader reader, out int count) { count = 0; if (reader.BaseStream.Length - reader.BaseStream.Position < 4) return false; var value = reader.ReadUInt32(); if (value > int.MaxValue) throw new MmdFileParseException("Invalid VMD section count."); count = (int) value; return true; } private static void SkipRecords(BinaryReader reader, int count, int recordSize, string section) { var byteCount = (long) count * recordSize; EnsureRemaining(reader, byteCount, section); reader.BaseStream.Seek(byteCount, SeekOrigin.Current); } private static void EnsureRemaining(BinaryReader reader, long byteCount, string section) { if (byteCount < 0 || reader.BaseStream.Length - reader.BaseStream.Position < byteCount) throw new MmdFileParseException("Unexpected end of VMD " + section + " data."); } private class VmdBone { public string BoneName { get; set; } //15 public int NFrame { get; set; } public Vector3 Translation { get; set; } public Quaternion Rotation { get; set; } public byte[] XInterpolator { get; set; } //16 public byte[] YInterpolator { get; set; } //16 public byte[] ZInterpolator { get; set; } //16 public byte[] RInterpolator { get; set; } //16 } private class VmdMorph { public string MorphName { get; set; } //15 public int NFrame { get; set; } public float Weight { get; set; } } private class TempMmdMotion { public string Name { get; set; } public int Length { get; private set; } public Dictionary> BoneMotions { get; set; } public Dictionary> MorphMotions { get; set; } public Dictionary> IkMotions { get; set; } public TempMmdMotion() { BoneMotions = new Dictionary>(); MorphMotions = new Dictionary>(); IkMotions = new Dictionary>(); } public BoneKeyframe GetOrCreateBoneKeyFrame(string boneName, int frame) { Dictionary framesForBone; if (frame > Length) { Length = frame; } if (!BoneMotions.TryGetValue(boneName, out framesForBone)) { framesForBone = new Dictionary(); BoneMotions.Add(boneName, framesForBone); } BoneKeyframe boneKeyframe; if (!framesForBone.TryGetValue(frame, out boneKeyframe)) { boneKeyframe = new BoneKeyframe(); framesForBone.Add(frame, boneKeyframe); } return boneKeyframe; } public MorphKeyframe GetOrCreateMorphKeyFrame(string boneName, int frame) { Dictionary framesForBone; if (frame > Length) { Length = frame; } if (!MorphMotions.TryGetValue(boneName, out framesForBone)) { framesForBone = new Dictionary(); MorphMotions.Add(boneName, framesForBone); } MorphKeyframe morphKeyframe; if (!framesForBone.TryGetValue(frame, out morphKeyframe)) { morphKeyframe = new MorphKeyframe(); framesForBone.Add(frame, morphKeyframe); } return morphKeyframe; } public void SetIkEnabled(string boneName, int frame, bool enabled) { if (frame > Length) Length = frame; if (!IkMotions.TryGetValue(boneName, out var frames)) { frames = new Dictionary(); IkMotions.Add(boneName, frames); } frames[frame] = enabled; } public MmdMotion BuildMmdMotion() { var ret = new MmdMotion(); ret.BoneMotions = new Dictionary>>(); foreach (var entry in BoneMotions) { var value = entry.Value.ToList(); value = value.OrderBy(kv => kv.Key).ToList(); ret.BoneMotions.Add(entry.Key, value); } ret.MorphMotions = new Dictionary>>(); foreach (var entry in MorphMotions) { var value = entry.Value.ToList(); value = value.OrderBy(kv => kv.Key).ToList(); ret.MorphMotions.Add(entry.Key, value); } ret.IkMotions = new Dictionary>>(); foreach (var entry in IkMotions) { ret.IkMotions.Add(entry.Key, entry.Value.OrderBy(kv => kv.Key).ToList()); } ret.Length = Length; ret.Name = Name; return ret; } } } }