From 26183af00dc38da33b965492c68b0214cd68608f Mon Sep 17 00:00:00 2001 From: dexy Date: Fri, 6 Mar 2020 03:02:40 +1100 Subject: [PATCH] Peds facial animations progress --- .../GameFiles/FileTypes/YedFile.cs | 12 +++ CodeWalker.Core/GameFiles/GameFileCache.cs | 51 +++++++++++ .../GameFiles/Resources/Expression.cs | 89 +++++++++++++++---- CodeWalker.Core/World/Ped.cs | 26 ++++++ CodeWalker/Rendering/Renderable.cs | 34 ++++++- CodeWalker/Rendering/Renderer.cs | 7 +- 6 files changed, 196 insertions(+), 23 deletions(-) diff --git a/CodeWalker.Core/GameFiles/FileTypes/YedFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YedFile.cs index 5caf37e..2081198 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YedFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YedFile.cs @@ -15,6 +15,10 @@ namespace CodeWalker.GameFiles public string LoadException { get; set; } + public Dictionary ExprMap { get; set; } + + + public YedFile() : base(null, GameFileType.Yed) { } @@ -48,6 +52,14 @@ namespace CodeWalker.GameFiles ExpressionDictionary = rd?.ReadBlock(); + + InitDictionaries(); + } + + public void InitDictionaries() + { + ExprMap = ExpressionDictionary?.ExprMap ?? new Dictionary(); + } } } diff --git a/CodeWalker.Core/GameFiles/GameFileCache.cs b/CodeWalker.Core/GameFiles/GameFileCache.cs index e37e7da..254a7c7 100644 --- a/CodeWalker.Core/GameFiles/GameFileCache.cs +++ b/CodeWalker.Core/GameFiles/GameFileCache.cs @@ -47,6 +47,7 @@ namespace CodeWalker.GameFiles public Dictionary YftDict { get; private set; } public Dictionary YbnDict { get; private set; } public Dictionary YcdDict { get; private set; } + public Dictionary YedDict { get; private set; } public Dictionary YnvDict { get; private set; } public Dictionary Gxt2Dict { get; private set; } @@ -925,6 +926,7 @@ namespace CodeWalker.GameFiles YtdDict = new Dictionary(); YftDict = new Dictionary(); YcdDict = new Dictionary(); + YedDict = new Dictionary(); foreach (var rpffile in AllRpfs) { if (rpffile.AllEntries == null) continue; @@ -958,6 +960,11 @@ namespace CodeWalker.GameFiles YcdDict[entry.NameHash] = fentry; YcdDict[entry.ShortNameHash] = fentry; } + else if (entry.NameLower.EndsWith(".yed")) + { + YedDict[entry.NameHash] = fentry; + YedDict[entry.ShortNameHash] = fentry; + } } } } @@ -2148,6 +2155,41 @@ namespace CodeWalker.GameFiles return ycd; } } + public YedFile GetYed(uint hash) + { + if (!IsInited) return null; + lock (requestSyncRoot) + { + var key = new GameFileCacheKey(hash, GameFileType.Yed); + YedFile yed = mainCache.TryGet(key) as YedFile; + if (yed == null) + { + var e = GetYedEntry(hash); + if (e != null) + { + yed = new YedFile(e); + if (mainCache.TryAdd(key, yed)) + { + TryLoadEnqueue(yed); + } + else + { + yed.LoadQueued = false; + //ErrorLog("Out of cache space - couldn't load yed: " + JenkIndex.GetString(hash)); //too spammy... + } + } + else + { + //ErrorLog("Yed not found: " + JenkIndex.GetString(hash)); //too spammy... + } + } + else if (!yed.Loaded) + { + TryLoadEnqueue(yed); + } + return yed; + } + } public YnvFile GetYnv(uint hash) { if (!IsInited) return null; @@ -2230,6 +2272,12 @@ namespace CodeWalker.GameFiles YcdDict.TryGetValue(hash, out entry); return entry; } + public RpfFileEntry GetYedEntry(uint hash) + { + RpfFileEntry entry; + YedDict.TryGetValue(hash, out entry); + return entry; + } public RpfFileEntry GetYnvEntry(uint hash) { RpfFileEntry entry; @@ -2319,6 +2367,9 @@ namespace CodeWalker.GameFiles case GameFileType.Ycd: req.Loaded = LoadFile(req as YcdFile); break; + case GameFileType.Yed: + req.Loaded = LoadFile(req as YedFile); + break; case GameFileType.Ynv: req.Loaded = LoadFile(req as YnvFile); break; diff --git a/CodeWalker.Core/GameFiles/Resources/Expression.cs b/CodeWalker.Core/GameFiles/Resources/Expression.cs index 016cc6e..a3ff470 100644 --- a/CodeWalker.Core/GameFiles/Resources/Expression.cs +++ b/CodeWalker.Core/GameFiles/Resources/Expression.cs @@ -47,6 +47,10 @@ namespace CodeWalker.GameFiles public ResourceSimpleList64_s ExpressionNameHashes { get; set; } public ResourcePointerList64 Expressions { get; set; } + + public Dictionary ExprMap { get; set; } + + public override void Read(ResourceDataReader reader, params object[] parameters) { base.Read(reader, parameters); @@ -58,6 +62,8 @@ namespace CodeWalker.GameFiles this.Unknown_1Ch = reader.ReadUInt32(); this.ExpressionNameHashes = reader.ReadBlock>(); this.Expressions = reader.ReadBlock>(); + + BuildMap(); } public override void Write(ResourceDataWriter writer, params object[] parameters) { @@ -84,6 +90,27 @@ namespace CodeWalker.GameFiles new Tuple(0x30, Expressions) }; } + + + public void BuildMap() + { + ExprMap = new Dictionary(); + + if ((Expressions?.data_items != null) && (ExpressionNameHashes?.data_items != null)) + { + var exprs = Expressions.data_items; + var names = ExpressionNameHashes.data_items; + + for (int i = 0; i < exprs.Length; i++) + { + var expr = exprs[i]; + var name = (i < names.Length) ? names[i] : (MetaHash)JenkHash.GenHash(expr?.Name?.ToString()?.ToLowerInvariant() ?? ""); + ExprMap[name] = expr; + } + } + + } + } @@ -104,7 +131,7 @@ namespace CodeWalker.GameFiles public uint Unknown_18h { get; set; } // 0x00000000 public uint Unknown_1Ch { get; set; } // 0x00000000 public ResourcePointerList64 Unknown_20h { get; set; } - public ResourceSimpleList64_s Unknown_30h { get; set; } // bone tags / animation tracks..?? + public ResourceSimpleList64_s BoneTracks { get; set; } // bone tags / animation tracks..?? public ResourceSimpleList64 Unknown_40h { get; set; } public ResourceSimpleList64_s Unknown_50h { get; set; } // only for: faceinit.expr, independent_mover.expr public ulong NamePointer { get; set; } @@ -124,6 +151,10 @@ namespace CodeWalker.GameFiles // reference data public string_r Name; + + public Dictionary BoneTracksDict { get; set; } + + public override void Read(ResourceDataReader reader, params object[] parameters) { // read structure data @@ -136,7 +167,7 @@ namespace CodeWalker.GameFiles this.Unknown_18h = reader.ReadUInt32(); this.Unknown_1Ch = reader.ReadUInt32(); this.Unknown_20h = reader.ReadBlock>(); - this.Unknown_30h = reader.ReadBlock>(); + this.BoneTracks = reader.ReadBlock>(); this.Unknown_40h = reader.ReadBlock>(); this.Unknown_50h = reader.ReadBlock>(); this.NamePointer = reader.ReadUInt64(); @@ -161,6 +192,8 @@ namespace CodeWalker.GameFiles //if (Unknown_50h?.data_items?.Length > 0) //{ } // faceinit.expr, independent_mover.expr + BuildBoneTracksDict(); + #region testing //long tlen = 0; //if (Unknown_20h?.data_items != null) foreach (var item in Unknown_20h.data_items) tlen = Math.Max(tlen, item.BlockLength); @@ -229,7 +262,7 @@ namespace CodeWalker.GameFiles writer.Write(this.Unknown_18h); writer.Write(this.Unknown_1Ch); writer.WriteBlock(this.Unknown_20h); - writer.WriteBlock(this.Unknown_30h); + writer.WriteBlock(this.BoneTracks); writer.WriteBlock(this.Unknown_40h); writer.WriteBlock(this.Unknown_50h); writer.Write(this.NamePointer); @@ -257,13 +290,37 @@ namespace CodeWalker.GameFiles { return new Tuple[] { new Tuple(0x20, Unknown_20h), - new Tuple(0x30, Unknown_30h), + new Tuple(0x30, BoneTracks), new Tuple(0x40, Unknown_40h), new Tuple(0x50, Unknown_50h) }; } + public void BuildBoneTracksDict() + { + BoneTracksDict = new Dictionary(); + + if (BoneTracks?.data_items == null) return; + + var mapto = new ExpressionBoneTrack(); + for(int i=0; i< BoneTracks.data_items.Length;i++) + { + var bt = BoneTracks.data_items[i]; + if ((bt.Flags & 128) == 0) + { + mapto = bt; + } + else if (bt.BoneTag != 0) + { + bt.Flags &= 0x7F; + BoneTracksDict[bt] = mapto; + } + } + + } + + public override string ToString() { return (Name?.ToString() ?? base.ToString()) + " " + Unknown_74h.ToString(); @@ -347,24 +404,24 @@ namespace CodeWalker.GameFiles case 0x0A: break; case 0x0B: break; case 0x0E: break; - case 0x10: item.Children = new[] { stack.Pop()/*, stack.Pop()*/ }; break; //####### maybe not + case 0x10: item.Children = new[] { stack.Pop() }; break; //####### maybe not case 0x11: item.Children = new[] { stack.Pop() }; break; case 0x1B: item.Children = new[] { stack.Pop() }; break; case 0x1D: item.Children = new[] { stack.Pop() }; break; case 0x1E: item.Children = new[] { stack.Pop() }; break; case 0x1F: item.Children = new[] { stack.Pop() }; break; case 0x20: break;//first in list - case 0x21: item.Children = new[] { stack.Pop()/*, stack.Pop()*/ }; break; + case 0x21: item.Children = new[] { stack.Pop() }; break; case 0x22: item.Children = new[] { stack.Pop() }; break; case 0x23: item.Children = new[] { stack.Pop() }; break; case 0x26: item.Children = new[] { stack.Pop() }; break; case 0x27: item.Children = new[] { stack.Pop() }; break; case 0x28: item.Children = new[] { stack.Pop() }; break; case 0x2A: item.Children = new[] { stack.Pop() }; break; - case 0x2B: item.Children = new[] { stack.Pop(), stack.Pop() }; break; - case 0x2C: item.Children = new[] { stack.Pop()/*, stack.Pop()*/ }; break; //########################## - case 0x2D: item.Children = new[] { stack.Pop(), stack.Pop() }; break; - case 0x2E: item.Children = new[] { stack.Pop(), stack.Pop()/*, stack.Pop()*/ }; break; //maybe + case 0x2B: item.Children = new[] { stack.Pop(), stack.Pop(), stack.Pop() }; break; + case 0x2C: item.Children = new[] { stack.Pop() }; break; + case 0x2D: item.Children = new[] { stack.Pop(), stack.Pop() }; break;//4 maybe? + case 0x2E: item.Children = new[] { stack.Pop(), stack.Pop() }; break; case 0x2F: item.Children = new[] { stack.Pop(), stack.Pop() }; break; case 0x30: item.Children = new[] { stack.Pop(), stack.Pop() }; break; case 0x31: item.Children = new[] { stack.Pop(), stack.Pop() }; break; @@ -372,7 +429,7 @@ namespace CodeWalker.GameFiles case 0x33: item.Children = new[] { stack.Pop(), stack.Pop() }; break; case 0x35: item.Children = new[] { stack.Pop(), stack.Pop() }; break;//can't be more than 2 case 0x36: item.Children = new[] { stack.Pop(), stack.Pop() }; break; //can't be more than 2 - case 0x37: item.Children = new[] { stack.Pop(), stack.Pop(), stack.Pop(), stack.Pop()/**/ }; break; + case 0x37: item.Children = new[] { stack.Pop(), stack.Pop(), stack.Pop(), stack.Pop() }; break; case 0x38: item.Children = new[] { stack.Pop(), stack.Pop(), stack.Pop() }; break; case 0x39: item.Children = new[] { stack.Pop(), stack.Pop(), stack.Pop() }; break; case 0x3A: item.Children = new[] { stack.Pop(), stack.Pop(), stack.Pop() }; break; @@ -1127,15 +1184,15 @@ namespace CodeWalker.GameFiles - [TypeConverter(typeof(ExpandableObjectConverter))] public struct ExpressionUnk3 + [TypeConverter(typeof(ExpandableObjectConverter))] public struct ExpressionBoneTrack { - public ushort Unk0 { get; set; } // bone tag? need to check - public byte Unk2 { get; set; } // animation track? - public byte Unk3 { get; set; } // ..flags? + public ushort BoneTag { get; set; } + public byte Track { get; set; } + public byte Flags { get; set; } public override string ToString() { - return Unk0.ToString() + ", " + Unk2.ToString() + ", " + Unk3.ToString(); + return BoneTag.ToString() + ", " + Track.ToString() + ", " + Flags.ToString(); } } diff --git a/CodeWalker.Core/World/Ped.cs b/CodeWalker.Core/World/Ped.cs index fc780fd..9665245 100644 --- a/CodeWalker.Core/World/Ped.cs +++ b/CodeWalker.Core/World/Ped.cs @@ -19,6 +19,7 @@ namespace CodeWalker.World public YtdFile Ytd { get; set; } = null; //ped textures public YldFile Yld { get; set; } = null; //ped clothes public YcdFile Ycd { get; set; } = null; //ped animations + public YedFile Yed { get; set; } = null; //ped expressions public YftFile Yft { get; set; } = null; //ped skeleton YFT public PedFile Ymt { get; set; } = null; //ped variation info public Dictionary DrawableFilesDict { get; set; } = null; @@ -28,9 +29,11 @@ namespace CodeWalker.World public RpfFileEntry[] TextureFiles { get; set; } = null; public RpfFileEntry[] ClothFiles { get; set; } = null; public ClipMapEntry AnimClip { get; set; } = null; + public Expression Expression { get; set; } = null; public string[] DrawableNames { get; set; } = new string[12]; public Drawable[] Drawables { get; set; } = new Drawable[12]; public Texture[] Textures { get; set; } = new Texture[12]; + public Expression[] Expressions { get; set; } = new Expression[12]; public ClothInstance[] Clothes { get; set; } = new ClothInstance[12]; public bool EnableRootMotion { get; set; } = false; //used to toggle whether or not to include root motion when playing animations public Skeleton Skeleton { get; set; } = null; @@ -55,7 +58,9 @@ namespace CodeWalker.World InitData = null; Ydd = null; Ytd = null; + Yld = null; Ycd = null; + Yed = null; Yft = null; Ymt = null; AnimClip = null; @@ -63,6 +68,7 @@ namespace CodeWalker.World { Drawables[i] = null; Textures[i] = null; + Expressions[i] = null; } @@ -70,6 +76,7 @@ namespace CodeWalker.World if (!gfc.PedsInitDict.TryGetValue(pedhash, out initdata)) return; var ycdhash = JenkHash.GenHash(initdata.ClipDictionaryName.ToLowerInvariant()); + var yedhash = JenkHash.GenHash(initdata.ExpressionDictionaryName.ToLowerInvariant()); //bool pedchange = NameHash != pedhash; //Name = pedname; @@ -78,6 +85,7 @@ namespace CodeWalker.World Ydd = gfc.GetYdd(pedhash); Ytd = gfc.GetYtd(pedhash); Ycd = gfc.GetYcd(ycdhash); + Yed = gfc.GetYed(yedhash); Yft = gfc.GetYft(pedhash); PedFile pedFile = null; @@ -123,6 +131,11 @@ namespace CodeWalker.World Thread.Sleep(1);//kinda hacky Ycd = gfc.GetYcd(ycdhash); } + while ((Yed != null) && (!Yed.Loaded)) + { + Thread.Sleep(1);//kinda hacky + Yed = gfc.GetYed(yedhash); + } while ((Yft != null) && (!Yft.Loaded)) { Thread.Sleep(1);//kinda hacky @@ -137,6 +150,11 @@ namespace CodeWalker.World Ycd?.ClipMap?.TryGetValue(cliphash, out cme); AnimClip = cme; + var exprhash = JenkHash.GenHash(initdata.ExpressionName.ToLowerInvariant()); + Expression expr = null; + Yed?.ExprMap?.TryGetValue(exprhash, out expr); + Expression = expr; + UpdateEntity(); } @@ -152,6 +170,7 @@ namespace CodeWalker.World DrawableNames[index] = null; Drawables[index] = null; Textures[index] = null; + Expressions[index] = null; return; } @@ -232,10 +251,17 @@ namespace CodeWalker.World c.Init(cc, Skeleton); } + Expression e = null; + if (Yed?.ExprMap != null) + { + Yed.ExprMap.TryGetValue(namehash, out e); + } + if (d != null) Drawables[index] = d.ShallowCopy() as Drawable; if (t != null) Textures[index] = t; if (c != null) Clothes[index] = c; + if (e != null) Expressions[index] = e; DrawableNames[index] = name; } diff --git a/CodeWalker/Rendering/Renderable.cs b/CodeWalker/Rendering/Renderable.cs index 81c41ee..fc36b70 100644 --- a/CodeWalker/Rendering/Renderable.cs +++ b/CodeWalker/Rendering/Renderable.cs @@ -83,6 +83,7 @@ namespace CodeWalker.Rendering public double CurrentAnimTime = 0; public YcdFile ClipDict; public ClipMapEntry ClipMapEntry; + public Expression Expression; public Dictionary ModelBoneLinks; public bool EnableRootMotion = false; //used to toggle whether or not to include root motion when playing animations @@ -520,10 +521,27 @@ namespace CodeWalker.Rendering for (int i = 0; i < anim.BoneIds.data_items.Length; i++) { var boneiditem = anim.BoneIds.data_items[i]; + var boneid = boneiditem.BoneId; var track = boneiditem.Track; + if (Expression?.BoneTracksDict != null) + { + var exprbt = new ExpressionBoneTrack() { BoneTag = boneid, Track = track, Flags = boneiditem.Unk0 }; + var exprbtmap = exprbt; + + if ((track == 24) || (track == 25) || (track == 26)) + { + if (Expression.BoneTracksDict.TryGetValue(exprbt, out exprbtmap)) + { + boneid = exprbtmap.BoneTag; + } + else + { } + } + } + Bone bone = null; - skel?.BonesMap?.TryGetValue(boneiditem.BoneId, out bone); + skel?.BonesMap?.TryGetValue(boneid, out bone); if (bone == null) { continue; @@ -564,11 +582,19 @@ namespace CodeWalker.Rendering break; case 8://quaternion... (camera rotation?) break; - case 24://face stuff? + case 24://face stuff + v = anim.EvaluateVector4(frame, i, interpolate); //single float + //bone.AnimTranslation = v.XYZ(); + //bone.AnimRotation = Quaternion.RotationYawPitchRoll(v.X, 0.0f, 0.0f); break; - case 25://face stuff? + case 25://face stuff + v = anim.EvaluateVector4(frame, i, interpolate); //vector3 roll/pitch/yaw + var mult = -0.314159265f; + bone.AnimRotation = Quaternion.RotationYawPitchRoll(v.Z * mult, v.Y * mult, v.X * mult); break; - case 26://face stuff? + case 26://face stuff + q = anim.EvaluateQuaternion(frame, i, interpolate); + //bone.AnimRotation = q; break; case 27: case 50: diff --git a/CodeWalker/Rendering/Renderer.cs b/CodeWalker/Rendering/Renderer.cs index 30cb1b4..5241a92 100644 --- a/CodeWalker/Rendering/Renderer.cs +++ b/CodeWalker/Rendering/Renderer.cs @@ -2833,7 +2833,7 @@ namespace CodeWalker.Rendering return res; } - public bool RenderDrawable(DrawableBase drawable, Archetype arche, YmapEntityDef entity, uint txdHash = 0, TextureDictionary txdExtra = null, Texture diffOverride = null, ClipMapEntry animClip = null, ClothInstance cloth = null) + public bool RenderDrawable(DrawableBase drawable, Archetype arche, YmapEntityDef entity, uint txdHash = 0, TextureDictionary txdExtra = null, Texture diffOverride = null, ClipMapEntry animClip = null, ClothInstance cloth = null, Expression expr = null) { //enqueue a single drawable for rendering. @@ -2859,7 +2859,7 @@ namespace CodeWalker.Rendering } rndbl.Cloth = cloth; - + rndbl.Expression = expr; return RenderRenderable(rndbl, arche, entity); } @@ -3150,6 +3150,7 @@ namespace CodeWalker.Rendering var drawable = ped.Drawables[i]; var texture = ped.Textures[i]; var cloth = ped.Clothes[i]; + var expr = ped.Expressions[i]; //if (compData == null) return; if (drawable == null) return; @@ -3192,7 +3193,7 @@ namespace CodeWalker.Rendering if (drawFlag) { - RenderDrawable(drawable, null, ped.RenderEntity, 0, td, texture, ac, cloth); + RenderDrawable(drawable, null, ped.RenderEntity, 0, td, texture, ac, cloth, expr); }