Beispiel #1
0
        /// <summary>
        /// Will fetch number of weeks from KK_Pregnancy data for this character
        /// </summary>
        internal static int GetWeeksFromPregnancyPluginData(ChaControl chaControl, string targetBehaviorId)
        {
            var kkPregCtrlInst = PregnancyPlusHelper.GetCharacterBehaviorController <CharaCustomFunctionController>(chaControl, targetBehaviorId);

            if (kkPregCtrlInst == null)
            {
                return(-1);
            }

            //Get the pregnancy data object
            var data = kkPregCtrlInst.GetType().GetProperty("Data")?.GetValue(kkPregCtrlInst, null);

            if (data == null)
            {
                return(-1);
            }

            var week = Traverse.Create(data).Field("Week").GetValue <int>();

            if (week.Equals(null) || week < -1)
            {
                return(-1);
            }

            return(week);
        }
        /// <summary>
        /// fetch KK_Pregnancy Data.Week value for KK story mode integration (It works if you don't mind the clipping)
        /// </summary>
        internal void GetWeeksAndSetInflation(bool checkNewMesh = false, bool slidersChanged = false)
        {
            //If a card value is set for inflation size, use that first, otherwise check KK_Pregnancy for Weeks value
            var cardData = GetCardData();

            if (cardData.inflationSize > 0 && cardData.GameplayEnabled)
            {
                MeshInflate(cardData, checkNewMesh, slidersChanged);
                return;
            }

            var week = PregnancyPlusHelper.GetWeeksFromPregnancyPluginData(ChaControl, KK_PregnancyPluginName);

            if (PregnancyPlusPlugin.DebugLog.Value)
            {
                PregnancyPlusPlugin.Logger.LogInfo($" GetWeeksAndSetInflation {ChaControl.name} >  Week:{week} checkNewMesh:{checkNewMesh} slidersChanged:{slidersChanged}");
            }
            if (week < 0)
            {
                return;
            }

            //Compute the additonal belly size added based on user configured vallue from 0-40
            var additionalPregPlusSize = Mathf.Lerp(0, week, PregnancyPlusPlugin.MaxStoryModeBelly.Value / 40);

            MeshInflate(additionalPregPlusSize, checkNewMesh, slidersChanged);
        }
        /// <summary>
        /// In special cases we need to apply a small offset to the sphereCenter to align the mesh correctly with the other meshes.  Otherwise you get tons of clipping
        ///  Mostly used to fix the default KK body which seems to be mis aligned from uncensor, and AI/HS2 meshes
        /// </summary>
        public void ApplyConditionalSphereCenterOffset(Transform meshRootTf, bool isClothingMesh, Vector3 _sphereCenter, out Vector3 sphereCenter, out Vector3 bodySphereCenterOffset)
        {
            //Fixes for different mesh localspace positions/rotations between KK and HS2/AI
            #if KK
            //When mesh is the default kk body, we have to adjust the mesh to match some strange offset that comes up
            var isDefaultBody        = !PregnancyPlusHelper.IsUncensorBody(ChaControl, UncensorCOMName);
            var defaultBodyOffsetFix = 0.0277f * bellyInfo.TotalCharScale.y * bellyInfo.NHeightScale.y;    //Where does this offset even come from?

            if (!isClothingMesh && isDefaultBody)
            {
                bodySphereCenterOffset = meshRootTf.position + GetUserMoveTransform(meshRootTf) - meshRootTf.up * defaultBodyOffsetFix; ////at 0,0,0, once again what is this crazy small offset?
                sphereCenter           = meshRootTf.position + GetUserMoveTransform(meshRootTf);                                        //at belly button - offset from meshroot
            }
            else
            {
                //For uncensor body mesh, and any clothing
                bodySphereCenterOffset = sphereCenter = _sphereCenter;    //at belly button
            }
            if (PregnancyPlusPlugin.DebugLog.Value)
            {
                PregnancyPlusPlugin.Logger.LogInfo($" [KK only] corrected sphereCenter {_sphereCenter} isDefaultBody {isDefaultBody}");
            }
            #elif HS2 || AI
            //Its so simple when its not KK default mesh :/
            bodySphereCenterOffset = sphereCenter = _sphereCenter;
            #endif
        }
Beispiel #4
0
        /// <summary>
        /// If the current characters mesh is set by the Uncensor plugin we need to know this to correctly shift the mesh's localspace vertex positions
        /// The LS positions for uncensor match that of HS2 and AI, but not the defulat KK body mesh (This took forever to track down!)
        /// </summary>
        internal static bool IsUncensorBody(ChaControl chaControl, string UncensorCOMName)
        {
            //grab the active uncensor controller of it exists
            var uncensorController = PregnancyPlusHelper.GetCharacterBehaviorController <CharaCustomFunctionController>(chaControl, UncensorCOMName);

            if (uncensorController == null)
            {
                return(false);
            }

            //Get the body type name, and see if it is the default mesh name
            var bodyData = uncensorController.GetType().GetProperty("BodyData")?.GetValue(uncensorController, null);

            if (bodyData == null)
            {
                return(false);
            }

            var bodyGUID = Traverse.Create(bodyData).Field("BodyGUID")?.GetValue <string>();

            if (bodyGUID == null)
            {
                return(false);
            }

            return(bodyGUID != PregnancyPlusCharaController.DefaultBodyFemaleGUID && bodyGUID != PregnancyPlusCharaController.DefaultBodyMaleGUID);
        }
        /// <summary>
        /// Loads a blendshape from character card and sets it to the correct mesh
        /// </summary>
        /// <param name="data">The characters card data for this plugin</param>
        internal void LoadBlendShapes(PregnancyPlusData data)
        {
            if (data.meshBlendShape == null)
            {
                return;
            }
            if (PregnancyPlusPlugin.DebugLog.Value)
            {
                PregnancyPlusPlugin.Logger.LogInfo($" MeshBlendShape size > {data.meshBlendShape.Length/1024}KB ");
            }

            meshWithBlendShapes = new List <SkinnedMeshRenderer>();

            //Unserialize the blendshape from characters card
            var meshBlendShapes = MessagePack.LZ4MessagePackSerializer.Deserialize <List <MeshBlendShape> >(data.meshBlendShape);

            if (meshBlendShapes == null || meshBlendShapes.Count <= 0)
            {
                return;
            }

            //For each stores meshBlendShape
            foreach (var meshBlendShape in meshBlendShapes)
            {
                //Loop through all meshes and find matching name
                var clothRenderers = PregnancyPlusHelper.GetMeshRenderers(ChaControl.objClothes, true);
                LoopMeshAndAddExistingBlendShape(clothRenderers, meshBlendShape, true);

                //do the same for body meshs
                var bodyRenderers = PregnancyPlusHelper.GetMeshRenderers(ChaControl.objBody, true);
                LoopMeshAndAddExistingBlendShape(bodyRenderers, meshBlendShape);
            }
        }
        /// <summary>
        /// Will reset all meshes stored in the mesh dictionaries to default positons
        /// </summary>
        internal void ResetInflation()
        {
            //Resets all mesh inflations
            var keyList = new List <string>(originalVertices.Keys);

            //For every active meshRenderer key we have created
            foreach (var renderKey in keyList)
            {
                var smr = PregnancyPlusHelper.GetMeshRenderer(ChaControl, renderKey);
                //Normally triggered when user changes clothes, the old clothes render wont be found
                if (smr == null)
                {
                    if (PregnancyPlusPlugin.DebugLog.Value)
                    {
                        PregnancyPlusPlugin.Logger.LogWarning($" ResetInflation > smr was not found {renderKey}");
                    }
                    continue;
                }

                //Create an instance of sharedMesh so we don't modify the mesh shared between characters, that was a fun issue
                Mesh meshCopy = (Mesh)UnityEngine.Object.Instantiate(smr.sharedMesh);
                smr.sharedMesh = meshCopy;

                var sharedMesh = smr.sharedMesh;
                var hasValue   = originalVertices.TryGetValue(renderKey, out Vector3[] origVerts);

                //On change clothes original verts become useless, so skip this
                if (!hasValue)
                {
                    return;
                }

                //Some meshes are not readable and cant be touched...
                if (!sharedMesh.isReadable)
                {
                    PregnancyPlusPlugin.errorCodeCtrl.LogErrorCode(ChaControl.chaID, ErrorCode.PregPlus_MeshNotReadable,
                                                                   $"ResetInflation > smr '{renderKey}' is not readable, skipping");
                    continue;
                }

                if (!sharedMesh || origVerts.Equals(null) || origVerts.Length == 0)
                {
                    continue;
                }

                if (origVerts.Length != sharedMesh.vertexCount)
                {
                    PregnancyPlusPlugin.errorCodeCtrl.LogErrorCode(ChaControl.chaID, ErrorCode.PregPlus_IncorrectVertCount,
                                                                   $"ResetInflation > smr '{renderKey}' has incorrect vert count {origVerts.Length}|{sharedMesh.vertexCount}");
                    continue;
                }

                sharedMesh.vertices = origVerts;
                sharedMesh.RecalculateBounds();
                NormalSolver.RecalculateNormals(sharedMesh, 40f, alteredVerticieIndexes[renderKey]);
                //sharedMesh.RecalculateNormals(); //old way that leaves skin seams
                sharedMesh.RecalculateTangents();
            }
        }
Beispiel #7
0
        /// <summary>
        /// Get the distance from the characters feet to the belly button collapsed into a straight Y line.null  (Ignores animation and scale, gives true measurement)
        /// </summary>
        internal float GetBellyButtonLocalHeight()
        {
            //Calculate the belly button height by getting each bone distance from foot to belly button (even during animation the height is correct!)
            #if KK
            var bbHeight = PregnancyPlusHelper.BoneChainStraigntenedDistance(ChaControl, bellyInfo.TotalCharScale, "cf_j_foot_L", "cf_j_waist01");
            #elif HS2 || AI
            var bbHeight = PregnancyPlusHelper.BoneChainStraigntenedDistance(ChaControl, bellyInfo.TotalCharScale, "cf_J_Toes01_L", "cf_J_Kosi01");
            #endif

            return(bbHeight);
        }
        /// <summary>
        /// This will create a blendshape frame for a mesh, that can be used in timeline, required there be a renderKey for inflatedVertices for this smr
        /// </summary>
        /// <param name="smr">Target mesh renderer to update (original shape)</param>
        /// <param name="renderKey">The Shared Mesh render name, used in dictionary keys to get the current verticie values</param>
        /// <returns>Returns the MeshBlendShape that is created. Can be null</returns>
        internal MeshBlendShape CreateBlendShape(SkinnedMeshRenderer smr, string renderKey)
        {
            //Make a copy of the mesh. We dont want to affect the existing for this
            var meshCopyOrig = PregnancyPlusHelper.CopyMesh(smr.sharedMesh);

            if (!meshCopyOrig.isReadable)
            {
                PregnancyPlusPlugin.errorCodeCtrl.LogErrorCode(ChaControl.chaID, ErrorCode.PregPlus_MeshNotReadable,
                                                               $"CreateBlendShape > smr '{renderKey}' is not readable, skipping");
                return(null);
            }

            //Make sure we have an existing belly shape to work with (can be null if user hasnt used sliders yet)
            var exists = originalVertices.TryGetValue(renderKey, out var val);

            if (!exists)
            {
                if (PregnancyPlusPlugin.DebugLog.Value)
                {
                    PregnancyPlusPlugin.Logger.LogInfo(
                        $"CreateBlendShape > smr '{renderKey}' does not exists, skipping");
                }
                return(null);
            }

            if (originalVertices[renderKey].Length != meshCopyOrig.vertexCount)
            {
                PregnancyPlusPlugin.errorCodeCtrl.LogErrorCode(ChaControl.chaID, ErrorCode.PregPlus_IncorrectVertCount,
                                                               $"CreateBlendShape > smr.sharedMesh '{renderKey}' has incorrect vert count {originalVertices[renderKey].Length}|{meshCopyOrig.vertexCount}");
                return(null);
            }

            //Calculate the original normals, but don't show them.  We just want it for the blendshape shape destination
            meshCopyOrig.vertices = originalVertices[renderKey];
            meshCopyOrig.RecalculateBounds();
            NormalSolver.RecalculateNormals(meshCopyOrig, 40f, bellyVerticieIndexes[renderKey]);
            meshCopyOrig.RecalculateTangents();

            // LogMeshBlendShapes(smr);

            //Create a blend shape object on the mesh
            var bsc = new BlendShapeController(meshCopyOrig, smr, $"{renderKey}_{PregnancyPlusPlugin.GUID}");

            //Return the blendshape format that can be saved to character card
            return(ConvertToMeshBlendShape(smr.name, bsc.blendShape));
        }
        /// <summary>
        /// Get the root position of the mesh, so we can calculate the true position of its mesh verticies later
        /// </summary>
        internal void GetMeshRoot(out Transform meshRootTf, out float distanceMoved)
        {
            distanceMoved = 0f;
            meshRootTf    = null;

            #if KK
            //Get normal mesh root attachment position, and if its not near 0,0,0 fix it so that it is (Match it to the chacontrol y pos)
            var kkMeshRoot = PregnancyPlusHelper.GetBoneGO(ChaControl, "p_cf_body_00.cf_o_root");
            if (kkMeshRoot == null)
            {
                return;
            }

            //If the mesh root y is too far from the ChaControl origin
            if (ChaControl.transform.InverseTransformPoint(kkMeshRoot.transform.position).y > 0.01f)
            {
                // if (PregnancyPlusPlugin.DebugLog.Value) PregnancyPlusPlugin.Logger.LogInfo($"$ GetMeshRoot pos {kkMeshRoot.transform.position}");
                // if (PregnancyPlusPlugin.DebugLog.Value) PregnancyPlusPlugin.Logger.LogInfo($"$ char pos {ChaControl.transform.position}");
                distanceMoved = FastDistance(ChaControl.transform.position, kkMeshRoot.transform.position);
                if (PregnancyPlusPlugin.DebugLog.Value)
                {
                    PregnancyPlusPlugin.Logger.LogInfo($" MeshRoot moved to charRoot by {distanceMoved}f");
                }

                //Set the meshroot.pos to the chaControl.pos to make it more in line with HS2/AI, and KK Uncensor mesh
                kkMeshRoot.transform.position = ChaControl.transform.position;
                bellyInfo.MeshRootDidMove     = true;

                // if (PregnancyPlusPlugin.DebugLog.Value) PregnancyPlusPlugin.Logger.LogInfo($"$ GetMeshRoot pos after {meshRoot.transform.position}");
            }

            meshRootTf = kkMeshRoot.transform;
            #elif HS2 || AI
            //For HS2, get the equivalent position game object (near bellybutton)
            var meshRootGo = PregnancyPlusHelper.GetBoneGO(ChaControl, "p_cf_body_00.n_o_root");
            if (meshRootGo == null)
            {
                return;
            }
            meshRootTf = meshRootGo.transform;
            #endif
        }
        /// <summary>
        /// On user button click. Create blendshape from current belly state.  Add it to infConfig so it will be saved to char card if the user chooses save scene
        /// </summary>
        /// <param name="temporary">If Temporary, the blendshape will not be saved to char card</param>
        /// <returns>boolean true if any blendshapes were created</returns>
        internal bool OnCreateBlendShapeSelected(bool temporary = false)
        {
            if (PregnancyPlusPlugin.DebugLog.Value)
            {
                PregnancyPlusPlugin.Logger.LogInfo($" ");
            }
            if (PregnancyPlusPlugin.DebugLog.Value)
            {
                PregnancyPlusPlugin.Logger.LogInfo($" OnCreateBlendShapeSelected ");
            }

            var meshBlendShapes = new List <MeshBlendShape>();

            meshWithBlendShapes = new List <SkinnedMeshRenderer>();

            //Get all cloth renderes and attempt to create blendshapes from preset inflatedVerticies
            var clothRenderers = PregnancyPlusHelper.GetMeshRenderers(ChaControl.objClothes);

            meshBlendShapes = LoopAndCreateBlendShape(clothRenderers, meshBlendShapes, true);

            //do the same for body meshs
            var bodyRenderers = PregnancyPlusHelper.GetMeshRenderers(ChaControl.objBody);

            meshBlendShapes = LoopAndCreateBlendShape(bodyRenderers, meshBlendShapes);

            //Save any meshBlendShapes to card
            if (!temporary)
            {
                AddBlendShapesToData(meshBlendShapes);
            }

            //Reset belly size to 0 so the blendshape can be used with out interference
            PregnancyPlusGui.ResetSlider(PregnancyPlusGui.inflationSize, 0);

            //Append the smrs that have new blendspahes to the GUI to be seen
            blendShapeGui.OnSkinnedMeshRendererBlendShapesCreated(meshWithBlendShapes);

            return(meshBlendShapes.Count > 0);
        }
Beispiel #11
0
 /// <summary>
 /// Creates a mesh dictionary key based on mesh name and vert count. (because mesh names can be the same, vertex count makes it almost always unique)
 /// </summary>
 internal string GetMeshKey(SkinnedMeshRenderer smr)
 {
     return(PregnancyPlusHelper.KeyFromNameAndVerts(smr));
 }
Beispiel #12
0
        /// <summary>
        /// On Restore, set sliders to last non zero shape, and set characters belly state
        /// </summary>
        public static void OnRestore(List <MakerSlider> _sliders, PregnancyPlusData restoreToState = null)
        {
            if (!MakerAPI.InsideAndLoaded)
            {
                return;
            }
            if (_sliders == null || _sliders.Count <= 0 || !_sliders[0].Exists)
            {
                return;
            }

            var chaControl      = MakerAPI.GetCharacterControl();
            var charCustFunCtrl = PregnancyPlusHelper.GetCharacterBehaviorController <PregnancyPlusCharaController>(chaControl, PregnancyPlusPlugin.GUID);

            if (charCustFunCtrl == null)
            {
                return;
            }

            var _infConfig = restoreToState != null ? restoreToState : PregnancyPlusPlugin.lastBellyState;

            //For each slider, set to default which will reset the belly shape
            foreach (var slider in _sliders)
            {
                //Get the private slider object name from the game GUI
                var settingName = Traverse.Create(slider).Field("_settingName").GetValue <string>();
                if (settingName == null)
                {
                    continue;
                }

                if (PregnancyPlusPlugin.DebugLog.Value)
                {
                    PregnancyPlusPlugin.Logger.LogInfo($" Restoring slider > {settingName}");
                }

                //Set the correct slider with it's old config value
                switch (settingName)
                {
                    #region Look away! im being lazy again
                case var _ when settingName == inflationSizeMaker:    //Ohh boy, cant have const and static strings in switch case, thus this was created!
                    slider.SetValue(_infConfig.inflationSize);
                    continue;

                case var _ when settingName == inflationMultiplierMaker:
                    slider.SetValue(_infConfig.inflationMultiplier);
                    continue;

                case var _ when settingName == inflationMoveYMaker:
                    slider.SetValue(_infConfig.inflationMoveY);
                    continue;

                case var _ when settingName == inflationMoveZMaker:
                    slider.SetValue(_infConfig.inflationMoveZ);
                    continue;

                case var _ when settingName == inflationStretchXMaker:
                    slider.SetValue(_infConfig.inflationStretchX);
                    continue;

                case var _ when settingName == inflationStretchYMaker:
                    slider.SetValue(_infConfig.inflationStretchY);
                    continue;

                case var _ when settingName == inflationShiftYMaker:
                    slider.SetValue(_infConfig.inflationShiftY);
                    continue;

                case var _ when settingName == inflationShiftZMaker:
                    slider.SetValue(_infConfig.inflationShiftZ);
                    continue;

                case var _ when settingName == inflationTaperYMaker:
                    slider.SetValue(_infConfig.inflationTaperY);
                    continue;

                case var _ when settingName == inflationTaperZMaker:
                    slider.SetValue(_infConfig.inflationTaperZ);
                    continue;

                case var _ when settingName == inflationClothOffsetMaker:
                    slider.SetValue(_infConfig.inflationClothOffset);
                    continue;

                case var _ when settingName == inflationFatFoldMaker:
                    slider.SetValue(_infConfig.inflationFatFold);
                    continue;

                case var _ when settingName == inflationRoundnessMaker:
                    slider.SetValue(_infConfig.inflationRoundness);
                    continue;

                default:
                    continue;
                    #endregion
                }
            }
        }
        /// <summary>
        /// Calculate the waist mesaurements that are used to set the default belly size
        /// </summary>
        /// <returns>Boolean if all measurements are valid</returns>
        internal bool MeasureWaist(ChaControl chaControl, Vector3 charScale, Vector3 nHeightScale,
                                   out float waistToRibDist, out float waistToBackThickness, out float waistWidth, out float bellyToBreastDist)
        {
            //Bone names
            #if KK
            var breastRoot  = "cf_d_bust00";
            var ribName     = "cf_s_spine02";
            var waistName   = "cf_s_waist02";
            var thighLName  = "cf_j_thigh00_L";
            var thighRName  = "cf_j_thigh00_R";
            var backName    = "a_n_back";
            var bellyButton = "cf_j_waist01";
            #elif HS2 || AI
            var breastRoot  = "cf_J_Mune00";
            var ribName     = "cf_J_Spine02_s";
            var waistName   = "cf_J_Kosi02_s";
            var thighLName  = "cf_J_LegUp00_L";
            var thighRName  = "cf_J_LegUp00_R";
            var backName    = "N_Back";
            var bellyButton = "cf_J_Kosi01";
            #endif

            //Init out params
            waistToRibDist       = 0;
            waistToBackThickness = 0;
            waistWidth           = 0;
            bellyToBreastDist    = 0;

            //Get the characters Y bones to measure from
            var ribBone   = PregnancyPlusHelper.GetBone(ChaControl, ribName);
            var waistBone = PregnancyPlusHelper.GetBone(ChaControl, waistName);
            if (ribBone == null || waistBone == null)
            {
                return(waistWidth > 0 && waistToBackThickness > 0 && waistToRibDist > 0);
            }

            //Measures from the wasist to the bottom of the ribs
            waistToRibDist = Vector3.Distance(waistBone.transform.InverseTransformPoint(waistBone.position), waistBone.transform.InverseTransformPoint(ribBone.position));


            //Get the characters z waist thickness
            var backBone   = PregnancyPlusHelper.GetBone(ChaControl, backName);
            var breastBone = PregnancyPlusHelper.GetBone(ChaControl, breastRoot);
            if (ribBone == null || breastBone == null)
            {
                return(waistWidth > 0 && waistToBackThickness > 0 && waistToRibDist > 0);
            }

            //Measures from breast root to the back spine distance
            waistToBackThickness = Math.Abs(breastBone.transform.InverseTransformPoint(backBone.position).z);

            //Get the characters X bones to measure from, in localspace to ignore n_height scale
            var thighLBone = PregnancyPlusHelper.GetBone(ChaControl, thighLName);
            var thighRBone = PregnancyPlusHelper.GetBone(ChaControl, thighRName);
            if (thighLBone == null || thighRBone == null)
            {
                return(waistWidth > 0 && waistToBackThickness > 0 && waistToRibDist > 0);
            }

            //Measures Left to right hip bone distance
            waistWidth = Vector3.Distance(thighLBone.transform.InverseTransformPoint(thighLBone.position), thighLBone.transform.InverseTransformPoint(thighRBone.position));

            //Verts above this position are not allowed to move
            var bellyButtonBone = PregnancyPlusHelper.GetBone(ChaControl, bellyButton);
            //Distance from waist to breast root
            bellyToBreastDist = Math.Abs(bellyButtonBone.transform.InverseTransformPoint(ribBone.position).y) + Math.Abs(ribBone.transform.InverseTransformPoint(breastBone.position).y);

            if (PregnancyPlusPlugin.DebugLog.Value)
            {
                PregnancyPlusPlugin.Logger.LogInfo($" MeasureWaist Recalc ");
            }
            return(waistWidth > 0 && waistToBackThickness > 0 && waistToRibDist > 0);
        }
        /// <summary>
        /// Get the characters waist width and calculate the appropriate belly sphere radius from it
        ///     Smaller characters have smaller bellies, wider characters have wider bellies etc...
        /// </summary>
        /// <param name="chaControl">The character to measure</param>
        /// <param name="forceRecalc">For debuggin, will recalculate from scratch each time when true</param>
        /// <returns>Boolean if all measurements are valid</returns>
        internal bool MeasureWaistAndSphere(ChaControl chaControl, bool forceRecalc = false)
        {
            var bodyTopScale     = PregnancyPlusHelper.GetBodyTopScale(ChaControl);
            var nHeightScale     = PregnancyPlusHelper.GetN_HeightScale(ChaControl);
            var charScale        = ChaControl.transform.localScale;
            var totalScale       = new Vector3(bodyTopScale.x * charScale.x, bodyTopScale.y * charScale.y, bodyTopScale.z * charScale.z);
            var needsWaistRecalc = bellyInfo != null?bellyInfo.NeedsBoneDistanceRecalc(bodyTopScale, nHeightScale, charScale) : true;

            var needsSphereRecalc = bellyInfo != null?bellyInfo.NeedsSphereRecalc(infConfig, GetInflationMultiplier()) : true;

            //We should reuse existing measurements when we can, because characters waise bone distance chan change with animation, which affects belly size.
            if (bellyInfo != null)
            {
                if (!forceRecalc && needsSphereRecalc && !needsWaistRecalc)//Sphere radius calc needed
                {
                    var _valid = MeasureSphere(chaControl, bodyTopScale, nHeightScale, totalScale);
                    if (PregnancyPlusPlugin.DebugLog.Value)
                    {
                        PregnancyPlusPlugin.Logger.LogInfo(bellyInfo.Log());
                    }
                    return(_valid);
                }
                else if (!forceRecalc && needsWaistRecalc && !needsSphereRecalc)//Measurements needed which also requires sphere recalc
                {
                    var _valid = MeasureWaist(chaControl, charScale, nHeightScale,
                                              out float _waistToRibDist, out float _waistToBackThickness, out float _waistWidth, out float _bellyToBreastDist);
                    MeasureSphere(chaControl, bodyTopScale, nHeightScale, totalScale);

                    //Store all these values for reuse later
                    bellyInfo = new BellyInfo(_waistWidth, _waistToRibDist, bellyInfo.SphereRadius, bellyInfo.OriginalSphereRadius, bodyTopScale,
                                              GetInflationMultiplier(), _waistToBackThickness, nHeightScale, _bellyToBreastDist,
                                              charScale, bellyInfo.MeshRootDidMove);

                    if (PregnancyPlusPlugin.DebugLog.Value)
                    {
                        PregnancyPlusPlugin.Logger.LogInfo(bellyInfo.Log());
                    }
                    return(_valid);
                }
                else if (!forceRecalc && !needsSphereRecalc && !needsWaistRecalc)//No changed needed
                {
                    //Just return the original measurements and sphere radius when no updates needed
                    if (PregnancyPlusPlugin.DebugLog.Value)
                    {
                        PregnancyPlusPlugin.Logger.LogInfo(bellyInfo.Log());
                    }

                    //Measeurements are fine and can be reused if above 0
                    return(bellyInfo.WaistWidth > 0 && bellyInfo.SphereRadius > 0 && bellyInfo.WaistThick > 0);
                }
            }

            //Measeurements need to be recalculated from scratch
            if (PregnancyPlusPlugin.DebugLog.Value)
            {
                PregnancyPlusPlugin.Logger.LogInfo($" MeasureWaistAndSphere init ");
            }

            //Get waist measurements from bone distances
            var valid = MeasureWaist(chaControl, charScale, nHeightScale,
                                     out float waistToRibDist, out float waistToBackThickness, out float waistWidth, out float bellyToBreastDist);

            //Check for bad values
            if (!valid)
            {
                return(false);
            }

            //Calculate sphere radius based on distance from waist to ribs (seems big, but lerping later will trim much of it), added Math.Min for skinny waists
            var sphereRadius           = GetSphereRadius(waistToRibDist, waistWidth, totalScale);
            var sphereRadiusMultiplied = sphereRadius * (GetInflationMultiplier() + 1);

            //Store all these values for reuse later
            bellyInfo = new BellyInfo(waistWidth, waistToRibDist, sphereRadiusMultiplied, sphereRadius, bodyTopScale,
                                      GetInflationMultiplier(), waistToBackThickness, nHeightScale, bellyToBreastDist,
                                      charScale);

            if (PregnancyPlusPlugin.DebugLog.Value)
            {
                PregnancyPlusPlugin.Logger.LogInfo(bellyInfo.Log());
            }

            return(waistWidth > 0 && sphereRadiusMultiplied > 0 && waistToBackThickness > 0 && bellyToBreastDist > 0);
        }
        /// <summary>
        /// Triggers belly mesh inflation for the current ChaControl for any active meshs (not hidden clothes)
        /// It will check the inflationSize dictionary for a valid value (last set via config slider or MeshInflate(value))
        /// If size 0 is used it will clear all active mesh inflations
        /// This will not run twice for the same parameters, a change of config value is required
        /// </summary>
        /// <param name="checkForNewMesh">Lets you force bypass the check for values changed to check for new meshes</param>
        /// <param name="freshStart">Will recalculate verts like a first time run</param>
        /// <param name="pluginConfigSliderChanged">Will treat as if some slider values changed, which they did in global plugin config</param>
        /// <returns>Will return True if the mesh was altered and False if not</returns>
        public bool MeshInflate(bool checkForNewMesh = false, bool freshStart = false, bool pluginConfigSliderChanged = false)
        {
            if (ChaControl.objBodyBone == null)
            {
                return(false);                               //Make sure chatacter objs exists first
            }
            if (!PregnancyPlusPlugin.AllowMale.Value && ChaControl.sex == 0)
            {
                return(false);                                                            // Only female characters, unless plugin config says otherwise
            }
            var sliderHaveChanged = NeedsMeshUpdate(pluginConfigSliderChanged);

            //Only continue if one of the config values changed
            if (!sliderHaveChanged)
            {
                //Only stop here, if no recalculation needed
                if (!freshStart && !checkForNewMesh)
                {
                    return(false);
                }
            }
            ResetInflation();

            if (!AllowedToInflate())
            {
                return(false);                    //if outside studio/maker, make sure StoryMode is enabled first
            }
            if (!infConfig.GameplayEnabled)
            {
                return(false);                           //Only if gameplay enabled
            }
            //Resets all stored vert values, so the script will have to recalculate all from base body
            if (freshStart)
            {
                CleanSlate();
            }

            //Only continue when size above 0
            if (infConfig.inflationSize <= 0)
            {
                infConfigHistory.inflationSize = 0;
                return(false);
            }

            if (PregnancyPlusPlugin.DebugLog.Value)
            {
                PregnancyPlusPlugin.Logger.LogInfo($" ---------- ");
            }
            if (PregnancyPlusPlugin.DebugLog.Value)
            {
                PregnancyPlusPlugin.Logger.LogInfo($" inflationSize > {infConfig.inflationSize} for {charaFileName} ");
            }

            //Get the measurements that determine the base belly size
            var hasMeasuerments = MeasureWaistAndSphere(ChaControl);

            if (!hasMeasuerments)
            {
                PregnancyPlusPlugin.errorCodeCtrl.LogErrorCode(ChaControl.chaID, ErrorCode.PregPlus_BadMeasurement,
                                                               $"Could not get belly measurements from character");
                return(false);
            }

            var anyMeshChanges = false;

            //Get and apply all clothes render mesh changes
            var clothRenderers = PregnancyPlusHelper.GetMeshRenderers(ChaControl.objClothes);

            anyMeshChanges = LoopAndApplyMeshChanges(clothRenderers, sliderHaveChanged, anyMeshChanges, true);

            //do the same for body meshs
            var bodyRenderers = PregnancyPlusHelper.GetMeshRenderers(ChaControl.objBody, true);

            anyMeshChanges = LoopAndApplyMeshChanges(bodyRenderers, sliderHaveChanged, anyMeshChanges);

            //If any changes were applied, updated the last used shape for the Restore GUI button
            if (infConfig.HasAnyValue())
            {
                PregnancyPlusPlugin.lastBellyState = (PregnancyPlusData)infConfig.Clone();//CLone so we don't accidently overwright the lastState later
            }

            //Update config history when mesh changes were made
            if (anyMeshChanges)
            {
                infConfigHistory = (PregnancyPlusData)infConfig.Clone();
            }

            return(anyMeshChanges);
        }