private static bool AssignedWeightsHook(AssignedAnotherWeights __instance, GameObject obj, string delTopName, Transform rootBone)
            {
#if DEBUG
                Console.WriteLine("AssignedWeightsHook");
#endif
                return(AssignWeightsAndImplantBones(__instance, obj, delTopName, default(Bounds), rootBone));
            }
Beispiel #2
0
        /// <summary>
        /// LoadCharaFbxDataAsyncTranspilerで注入されるメソッド。主にボーン参照の移し替えを実行する
        /// </summary>
        /// <param name="aaw">ボーン共有用の情報が含まれるインスタンス</param>
        /// <param name="obj">読み込まれたモデルの最上位のGameObject</param>
        /// <param name="delTopName">削除されるツリーの最上位のGameObject</param>
        /// <param name="bounds">境界情報のインスタンス</param>
        /// <param name="rootBone">SkinnedMeshRendererのrootBoneに設定するTransform</param>
        /// <param name="cc">モデルを読み込んだキャラのChaControlコンポーネント</param>
        public static void ExecuteRefTransfer(AssignedAnotherWeights aaw, GameObject obj, string delTopName, Bounds bounds, Transform rootBone, ChaControl cc)
        {
            // BoneImplantProcessが無ければオリジナルの処理を実行して終了
            var cim  = cc.gameObject.GetComponent <ChaImplantManager>();
            var bips = obj.GetComponentsInChildren <BoneImplantProcess>(true);

            if (cim == null || bips == null || bips.Length < 1)
            {
                aaw.AssignedWeightsAndSetBounds(obj, delTopName, bounds, rootBone);
                return;
            }

            // SkinnedMeshRendererのボーン参照を移し替える
            cim.TransferBoneReference(obj, delTopName, bounds, rootBone, aaw.dictBone);
        }
            /// <summary>
            /// The full functionality of the plugin pretty much.
            /// Replaces the AssignedWeights* methods from AssignedAnotherWeights.
            /// If no BoneImplantProcess instances are found then it returns true and lets the stock method run instead to improve compatibility and safety.
            /// </summary>
            private static bool AssignWeightsAndImplantBones(AssignedAnotherWeights aaw, GameObject obj, string delTopName, Bounds bounds, Transform rootBone)
            {
                var dictBone = aaw.dictBone;

                var info = TryImplantBones(obj, dictBone);

                if (info == null)
                {
                    return(true);
                }

                var implantedBones = info.ImplantedBones;
                var dbColliders    = info.ImplantedColliders;

                var renderers = obj.GetComponentsInChildren <SkinnedMeshRenderer>();

                if (renderers.Length == 0)
                {
                    // This should never happen unless the mod is broken
                    Logger.LogWarning("Found some instances of BoneImplantProcess, but no SkinnedMeshRenderers were found so nothing will be done.\n" +
                                      $"Object: {obj.GetFullPath()}");
                    // Clean up since they won't be used
                    foreach (var implantedBone in implantedBones)
                    {
                        Destroy(implantedBone);
                    }
                    return(true);
                }

                // Remove implanted bones once all renderers that use them are destroyed
                var renderersAlive = renderers.ToList();

                void CleanupImplantedBones(SkinnedMeshRenderer renderer)
                {
                    renderersAlive.Remove(renderer);
                    if (renderersAlive.Count == 0)
                    {
                        Logger.LogDebug($"Removing {implantedBones.Count} no longer used implanted bones");
                        foreach (var implantedBone in implantedBones)
                        {
                            if (implantedBone != null)
                            {
                                Destroy(implantedBone.gameObject);
                            }
                        }
                    }
                }

                // Replacement of AssignedAnotherWeights functionality that we override (need to override it because the implanted bones would be replaced with nulls)
                var replaceBounds = bounds != default(Bounds);

                foreach (var meshRenderer in renderers)
                {
                    var boneCount         = meshRenderer.bones.Length;
                    var reassignedBoneArr = new Transform[boneCount];
                    for (var i = 0; i < boneCount; i++)
                    {
                        var rendererBone = meshRenderer.bones[i];
                        if (rendererBone == null) // Extra safety check, should never happen
                        {
                            Logger.LogWarning($"Renderer has a null bone! BoneIndex: {i}  Renderer: {meshRenderer.GetFullPath()}");
                        }
                        else if (implantedBones.Contains(rendererBone)) // Copy implanted bones as they are
                        {
                            reassignedBoneArr[i] = rendererBone;
                        }
                        else if (dictBone.TryGetValue(rendererBone.name, out var baseBone)) // Use the equivalent bone from the body skeleton if found
                        {
                            reassignedBoneArr[i] = baseBone.transform;
                        }
                        else
                        {
                            Logger.LogWarning(
                                "Renderer is using a bone that is not in the base skeleton and is not implanted. It will be set to null. You need to add a BoneImplantProcess component to your object.\n" +
                                $"Renderer: {meshRenderer.GetFullPath()}\n" +
                                $"Bone: {rendererBone.GetFullPath()}\n" +
                                $"BoneIndex: {i}");
                        }
                    }

                    meshRenderer.bones = reassignedBoneArr;
                    if (replaceBounds)
                    {
                        meshRenderer.localBounds = bounds;
                    }

                    // Same as unedited version
                    var clothCmp = meshRenderer.gameObject.GetComponent <Cloth>();
                    if (rootBone != null && clothCmp == null)
                    {
                        meshRenderer.rootBone = rootBone;
                    }
                    else if (meshRenderer.rootBone != null && dictBone.TryGetValue(meshRenderer.rootBone.name, out var baseBone))
                    {
                        meshRenderer.rootBone = baseBone.transform;
                    }

                    // Keep track of which renderers got destroyed to know when to remove the implanted bones
                    meshRenderer.OnDestroyAsObservable().Subscribe(_ => CleanupImplantedBones(meshRenderer));
                }

                // Base game code will in most cases clear the bone collider list and replace it with a fresh list of all colliders on the base body
                // so the custom colliders on the implanted bones need to be readded separately
                if (dbColliders.Count > 0)
                {
                    var dynamicBones = obj.GetComponentsInChildren <DynamicBone>(true);
                    foreach (var db in dynamicBones)
                    {
                        db.m_Colliders.AddRange(dbColliders.Except(db.m_Colliders));
                    }

                    Logger.LogDebug($"Found {dbColliders.Count} DynamicBoneColliders on the implanted bones. They were added to {dynamicBones.Length} DynamicBones (alongside colliders attached to the base body).");
                }

                obj.transform.FindLoop(delTopName).FancyDestroy(false, true);

                return(false);
            }