diff --git a/PregnancyPlus/PregnancyPlus.Core/PPCharaController.BlendShape.cs b/PregnancyPlus/PregnancyPlus.Core/PPCharaController.BlendShape.cs index 401b3cd..7f6a695 100644 --- a/PregnancyPlus/PregnancyPlus.Core/PPCharaController.BlendShape.cs +++ b/PregnancyPlus/PregnancyPlus.Core/PPCharaController.BlendShape.cs @@ -30,9 +30,9 @@ public class MeshBlendShape public string MeshName;//like SkinnedMeshRenderer.name public int VertCount;//To differentiate 2 meshes with the same names use vertex count comparison public string UncensorGUID;//Stores the uncensorGUID used with this blendshape - public BlendShapeController.BlendShape BlendShape;//Store just a single Frame for now, though its possible to have multiple frames. Preg+ only uses 1 + public BlendShape BlendShape;//Store just a single Frame for now, though its possible to have multiple frames. Preg+ only uses 1 - public MeshBlendShape(string meshName, BlendShapeController.BlendShape blendShape, int vertCount, string uncensorGUID) + public MeshBlendShape(string meshName, BlendShape blendShape, int vertCount, string uncensorGUID) { MeshName = meshName; BlendShape = blendShape; @@ -259,7 +259,7 @@ internal List LoopAndCreateBlendShape(List /// /// Convert a BlendShape to MeshBlendShape, used for storing as character card data /// - internal MeshBlendShape ConvertToMeshBlendShape(string smrMeshName, BlendShapeController.BlendShape blendShape, string uncensorGUID) + internal MeshBlendShape ConvertToMeshBlendShape(string smrMeshName, BlendShape blendShape, string uncensorGUID) { if (blendShape == null) return null; return new MeshBlendShape(smrMeshName, blendShape, blendShape.vertexCount, uncensorGUID); diff --git a/PregnancyPlus/PregnancyPlus.Core/PPCharaController.ClothingOffset.cs b/PregnancyPlus/PregnancyPlus.Core/PPCharaController.ClothingOffset.cs index 262e7e4..4dd3966 100644 --- a/PregnancyPlus/PregnancyPlus.Core/PPCharaController.ClothingOffset.cs +++ b/PregnancyPlus/PregnancyPlus.Core/PPCharaController.ClothingOffset.cs @@ -141,8 +141,13 @@ internal float[] DoClothMeasurement(SkinnedMeshRenderer clothSmr, SkinnedMeshRen //Get the pre calculated preg verts for this mesh var renderKey = GetMeshKey(clothSmr); - md.TryGetValue(renderKey, out MeshData _md); - + var exists = md.TryGetValue(renderKey, out MeshData _md); + if (!exists) + { + if (PregnancyPlusPlugin.DebugLog.Value) PregnancyPlusPlugin.Logger.LogWarning($" DoClothMeasurement cant find MeshData for {renderKey}"); + return new float[clothSmr.sharedMesh.vertexCount]; + } + //Check for existing offset values, init if none found var clothingOffsetsHasValue = md[renderKey].HasClothingOffsets; var clothOffsets = new float[0]; diff --git a/PregnancyPlus/PregnancyPlus.Core/PPCharaController.MeshInflation.Main.cs b/PregnancyPlus/PregnancyPlus.Core/PPCharaController.MeshInflation.Main.cs index 6afccf9..a60ebae 100644 --- a/PregnancyPlus/PregnancyPlus.Core/PPCharaController.MeshInflation.Main.cs +++ b/PregnancyPlus/PregnancyPlus.Core/PPCharaController.MeshInflation.Main.cs @@ -211,12 +211,14 @@ internal Task[] LoopAndApplyMeshChanges(List smrs, MeshInfl /// /// For a skinned mesh renderer check for cached mesh info, then apply inflation when needed + /// This is technically the core logic loop for a single mesh /// /// skinnedMeshRender to change /// Contains any flags needed for mesh computation /// If this smr is a cloth mesh + /// The characters body mesh + /// Whether this mesh is the character body mesh /// Any smr matching key will be ignored (Prevent computing main body mesh twice) - /// boolean true if threaded internal async Task StartMeshChanges(SkinnedMeshRenderer smr, MeshInflateFlags meshInflateFlags, bool isClothingMesh = false, SkinnedMeshRenderer bodySmr = null, bool isMainBody = false, string ignoreKey = null) { @@ -229,7 +231,11 @@ internal async Task StartMeshChanges(SkinnedMeshRenderer smr, MeshInflateFlags m if (needsComputeVerts) { //If it turns out that the current mesh is cached, then this will just return early - await ComputeMeshVerts(smr, isClothingMesh, bodySmr, meshInflateFlags, renderKey, isMainBody); + var vertsChanged = await ComputeVerts(smr, isClothingMesh, bodySmr, meshInflateFlags, renderKey, isMainBody); + + //Compute the deltas used to make blendshapes + if (vertsChanged) + await ComputeDeltas(smr, renderKey, meshInflateFlags); } if (ignoreMeshList.Contains(renderKey)) return; @@ -267,11 +273,11 @@ public bool NeedsComputeVerts(SkinnedMeshRenderer smr, string renderKey, MeshInf /// - /// Just a helper function to combine searching for verts in a mesh, and then applying the transforms + /// Searching for valid verts in a mesh, and compute the mesh shapes /// - /// Will return true if threaded - internal async Task ComputeMeshVerts(SkinnedMeshRenderer smr, bool isClothingMesh, SkinnedMeshRenderer bodyMeshRenderer, MeshInflateFlags meshInflateFlags, - string renderKey, bool isMainBody = false) + /// Will return true if verts were re-computed + internal async Task ComputeVerts(SkinnedMeshRenderer smr, bool isClothingMesh, SkinnedMeshRenderer bodyMeshRenderer, + MeshInflateFlags meshInflateFlags, string renderKey, bool isMainBody = false) { //The list of bones to get verticies for (Belly area verts). If a mesh does not contain one of these bones in smr.bones, it is skipped #if KKS @@ -288,10 +294,10 @@ internal async Task ComputeMeshVerts(SkinnedMeshRenderer smr, bool isClothingMes hasVertsToProcess = await GetFilteredVerticieIndexes(smr, PregnancyPlusPlugin.MakeBalloon.Value ? null : boneFilters); //If mesh was just added to the ignore list, stop here - if (ignoreMeshList.Contains(renderKey)) return; + if (ignoreMeshList.Contains(renderKey)) return false; //If no belly verts found, or verts already cached, then we can skip this mesh - if (!hasVertsToProcess) return; + if (!hasVertsToProcess) return false; //Get the newly created/or existing MeshData obj md.TryGetValue(renderKey, out _md); @@ -305,13 +311,12 @@ internal async Task ComputeMeshVerts(SkinnedMeshRenderer smr, bool isClothingMes //Inflate the mesh to give it the round appearance await GetInflatedVerticies(smr, bellyInfo.SphereRadius, isClothingMesh, bodyMeshRenderer, meshInflateFlags); - //Compute the deltas used to make blendshapes - await ComputeDeltas(smr, renderKey, meshInflateFlags); + return true; } /// - /// Apply the inflation to a blendshape once done computing inflated verts and deltas + /// Apply the computed shape to a blendshape. If the shape didn't change, apply the new blendshape weights /// internal void FinalizeInflation(SkinnedMeshRenderer smr, MeshInflateFlags meshInflateFlags, string blendShapeTag = null) { @@ -320,7 +325,7 @@ internal void FinalizeInflation(SkinnedMeshRenderer smr, MeshInflateFlags meshIn //Apply computed mesh back to body as a blendshape var appliedMeshChanges = ApplyInflation(smr, rendererName, meshInflateFlags.OverWriteMesh, blendShapeTempTagName, meshInflateFlags.bypassWhen0); - //When inflation is actively happening as clothing changes, make sure the new clothing grows too + //When HScene inflation is actively happening while user changes clothing, make sure the new clothing updates shape too if (isDuringInflationScene) AppendToQuickInflateList(smr); //If the inflation is applied, update the previous slider config values @@ -329,11 +334,11 @@ internal void FinalizeInflation(SkinnedMeshRenderer smr, MeshInflateFlags meshIn /// - /// Compute and cache the bind pose (T-pose) mesh vertex positions, we need this to ignore all character animations when computing the belly shape, - /// and to align the meshes together. Results get cached for subsiquent passes + /// Compute and cache the bind pose (T-pose) mesh vertex positions, we need this in order to ignore all character animations when computing the belly shape. + /// It also aligns the meshes together. Cache the computed results /// - /// Will return true if threaded - internal async Task ComputeBindPoseMesh(SkinnedMeshRenderer smr, SkinnedMeshRenderer bodySmr, bool isClothingMesh, MeshInflateFlags meshInflateFlags, bool isMainBody = false) + internal async Task ComputeBindPoseMesh(SkinnedMeshRenderer smr, SkinnedMeshRenderer bodySmr, bool isClothingMesh, + MeshInflateFlags meshInflateFlags, bool isMainBody = false) { if (smr == null) { @@ -381,6 +386,14 @@ internal async Task ComputeBindPoseMesh(SkinnedMeshRenderer smr, SkinnedMeshRend //Put threadpool work inside task and await the results await Task.Run(() => { + //Check again since some time will have passed since task start + var _exists = md.TryGetValue(rendererName, out MeshData _meshData); + if (!_exists) + { + if (PregnancyPlusPlugin.DebugLog.Value) PregnancyPlusPlugin.Logger.LogWarning($" ComputeBindPoseMesh.Task.Run cant find MeshData for {rendererName}"); + return; + } + //Spread work across multiple threads md[rendererName].originalVertices = Threading.RunParallel(unskinnedVerts, (_, i) => { //Get the skinned vert position from the bindpose matrix we computed earlier @@ -480,6 +493,14 @@ internal async Task GetInflatedVerticies(SkinnedMeshRenderer smr, float sphereRa //Put threadpool work inside task and await the results await Task.Run(() => { + //Check again since some time will have passed since task start + var _exists = md.TryGetValue(rendererName, out MeshData _meshData); + if (!_exists) + { + if (PregnancyPlusPlugin.DebugLog.Value) PregnancyPlusPlugin.Logger.LogWarning($" GetInflatedVerticies.Task.Run cant find MeshData for {rendererName}"); + return; + } + //Spread work across multiple threads md[rendererName].inflatedVertices = Threading.RunParallel(origVerts, (_, i) => { diff --git a/PregnancyPlus/PregnancyPlus.Core/Shared.projitems b/PregnancyPlus/PregnancyPlus.Core/Shared.projitems index 2b2e9b8..43afeaa 100644 --- a/PregnancyPlus/PregnancyPlus.Core/Shared.projitems +++ b/PregnancyPlus/PregnancyPlus.Core/Shared.projitems @@ -39,6 +39,7 @@ + diff --git a/PregnancyPlus/PregnancyPlus.Core/tools/AnimationCurve.cs b/PregnancyPlus/PregnancyPlus.Core/tools/AnimationCurve.cs index b33eb25..0808714 100644 --- a/PregnancyPlus/PregnancyPlus.Core/tools/AnimationCurve.cs +++ b/PregnancyPlus/PregnancyPlus.Core/tools/AnimationCurve.cs @@ -5,7 +5,7 @@ namespace KK_PregnancyPlus { - //This partial class contains the animation curves used in mesh transformations, much better curvature than a simple linear mathf.lerp + //This contains the animation curves used in mesh transformations, much better curvature than linear mathf.lerp public static class AnimCurve { diff --git a/PregnancyPlus/PregnancyPlus.Core/tools/BlendShape/BlendShape.cs b/PregnancyPlus/PregnancyPlus.Core/tools/BlendShape/BlendShape.cs new file mode 100644 index 0000000..e41e9e3 --- /dev/null +++ b/PregnancyPlus/PregnancyPlus.Core/tools/BlendShape/BlendShape.cs @@ -0,0 +1,45 @@ +using UnityEngine; +using MessagePack; + +namespace KK_PregnancyPlus +{ + //This format contains all the info a blendshape needs to be created (For a single blendshape frame). It also server as the format we will save to a character card later + [MessagePackObject(keyAsPropertyName: true)] + public class BlendShape //This is technically a blendshape frame, but w/e. Its already set in stone + { + public string name; + private float _frameWeight = 100;//The range that _weight has to stay within + public float frameWeight + { + set { _frameWeight = Mathf.Clamp(value, 0, 100); } + get { return _frameWeight <= 0 ? 100 : _frameWeight; }//Fix for old cards not having frameWeight prop, set them to 100 + } + private float _weight = 100;//The current weight + public float weight + { + set { _weight = value; } + get { return _weight; } + } + public Vector3[] verticies; + public Vector3[] normals; + public Vector3[] tangents; + + [IgnoreMember] + public bool isInitilized + { + get { return name != null; } + } + + [IgnoreMember] + public int vertexCount + { + get { return verticies.Length; } + } + + [IgnoreMember] + public string log + { + get { return $"name {name} weight {_weight} frameWeight {_frameWeight} vertexCount {vertexCount}"; } + } + } +} \ No newline at end of file diff --git a/PregnancyPlus/PregnancyPlus.Core/tools/BlendShape/BlendShapeController.cs b/PregnancyPlus/PregnancyPlus.Core/tools/BlendShape/BlendShapeController.cs index 3431242..1b1e977 100644 --- a/PregnancyPlus/PregnancyPlus.Core/tools/BlendShape/BlendShapeController.cs +++ b/PregnancyPlus/PregnancyPlus.Core/tools/BlendShape/BlendShapeController.cs @@ -2,7 +2,6 @@ using System; using System.Collections; using System.Collections.Generic; -using MessagePack; namespace KK_PregnancyPlus { @@ -12,50 +11,10 @@ public class BlendShapeController public BlendShape blendShape = new BlendShape(); public SkinnedMeshRenderer smr = null; - - //This format contains all the info a blendshape needs to be created (For a single blendshape frame). It also server as the format we will save to a character card later - [MessagePackObject(keyAsPropertyName: true)] - public class BlendShape //This is technically a blendshape frame, but w/e. Its already set in stone - { - public string name; - private float _frameWeight = 100;//The range that _weight has to stay within - public float frameWeight - { - set { _frameWeight = Mathf.Clamp(value, 0, 100); } - get { return _frameWeight <= 0 ? 100 : _frameWeight; }//Fix for old cards not having frameWeight prop, set them to 100 - } - private float _weight = 100;//The current weight - public float weight - { - set { _weight = value; } - get { return _weight; } - } - public Vector3[] verticies; - public Vector3[] normals; - public Vector3[] tangents; - - [IgnoreMember] - public bool isInitilized - { - get { return name != null; } - } - - [IgnoreMember] - public int vertexCount - { - get { return verticies.Length; } - } - - [IgnoreMember] - public string log - { - get { return $"name {name} weight {_weight} frameWeight {_frameWeight} vertexCount {vertexCount}"; } - } - } - +#region Constructors /// - /// Constructor that takes in the pre computed blendshape deltas, and applies them to a target SMR + /// Constructor that takes the pre computed blendshape deltas, and applies them to a target SMR /// /// The meshData obhect that has our computed blendshape deltas /// Desired name of the blend shape, should be unique @@ -79,7 +38,7 @@ public BlendShapeController(MeshData md, string blendShapeName, SkinnedMeshRende /// - /// Constructor overload that takes a saved blendshape and sets it to the correct mesh + /// Constructor overload that takes an existing Preg+ blendshape and sets it to an SMR /// Typically used when loading BlendShape from card /// /// The current active mesh @@ -106,12 +65,12 @@ public BlendShapeController(SkinnedMeshRenderer _smr, string blendShapeName) /// - /// Use this constructor just to access any methods inside + /// Use this constructor just to access any methods inside this class /// public BlendShapeController() { } - +#endregion Constructors /// @@ -330,6 +289,7 @@ public int GetBlendShapeIndex(SkinnedMeshRenderer smr, string blendShapeName) /// /// Remove a single blendshape from a mesh + /// Unfortunately Unity makes this difficult, by not having an API to remove 1 at a time. So we reomve all and add back all -1 /// /// The mesh containing the blendshapes public bool RemoveBlendShape(SkinnedMeshRenderer smr) @@ -366,7 +326,7 @@ public bool RemoveBlendShape(SkinnedMeshRenderer smr) /// - /// Copy all existing blendshape frames on a mesh + /// Copy all blendshape frames from an SMR /// /// The mesh containing the blendshapes internal Dictionary CopyAllBlendShapeFrames(SkinnedMeshRenderer smr)