// Magica Cloth. // Copyright (c) MagicaSoft, 2020-2022. // https://magicasoft.jp using System.Collections.Generic; using Unity.Mathematics; using UnityEngine; using System.Linq; #if UNITY_EDITOR using UnityEditor; #endif namespace MagicaCloth { /// /// クロス基本クラス /// public abstract partial class BaseCloth : PhysicsTeam { /// /// パラメータ設定 /// [SerializeField] protected ClothParams clothParams = new ClothParams(); [SerializeField] protected List clothParamDataHashList = new List(); /// /// クロスデータ /// [SerializeField] private ClothData clothData = null; [SerializeField] protected int clothDataHash; [SerializeField] protected int clothDataVersion; /// /// 頂点選択データ /// [SerializeField] private SelectionData clothSelection = null; [SerializeField] private int clothSelectionHash; [SerializeField] private int clothSelectionVersion; /// /// カリング用レンダラーリスト /// BoneCloth / BoneSpring で使用 /// [SerializeField] private List cullRendererList = new List(); /// /// ランタイムクロス設定 /// protected ClothSetup setup = new ClothSetup(); //========================================================================================= private float oldBlendRatio = -1.0f; private TeamUpdateMode oldUpdateMode = 0; private TeamCullingMode oldCullingMode = 0; private bool oldUseAnimatedDistance = false; //========================================================================================= /// /// データハッシュを求める /// /// public override int GetDataHash() { int hash = base.GetDataHash(); if (ClothData != null) hash += ClothData.GetDataHash(); if (ClothSelection != null) hash += ClothSelection.GetDataHash(); return hash; } //========================================================================================= public ClothParams Params { get { return clothParams; } } public ClothData ClothData { get { #if UNITY_EDITOR if (Application.isPlaying) return clothData; else { // unity2019.3で参照がnullとなる不具合の対処(臨時) var so = new SerializedObject(this); return so.FindProperty("clothData").objectReferenceValue as ClothData; } #else return clothData; #endif } set { clothData = value; } } public SelectionData ClothSelection { get { #if UNITY_EDITOR if (Application.isPlaying) return clothSelection; else { // unity2019.3で参照がnullとなる不具合の対処(臨時) var so = new SerializedObject(this); return so.FindProperty("clothSelection").objectReferenceValue as SelectionData; } #else return clothSelection; #endif } } public ClothSetup Setup { get { return setup; } } //========================================================================================= protected virtual void Reset() { } protected virtual void OnValidate() { if (Application.isPlaying == false) return; // クロスパラメータのラインタイム変更 setup.ChangeData(this, clothParams, clothData); } //========================================================================================= protected override void OnInit() { base.OnInit(); BaseClothInit(); } protected override void OnActive() { base.OnActive(); // パーティクル有効化 EnableParticle(UserTransform, UserTransformLocalPosition, UserTransformLocalRotation); // コライダー状態更新 TeamData.UpdateStatus(); SetUseMesh(true); ClothActive(); } protected override void OnInactive() { base.OnInactive(); // パーティクル無効化 DisableParticle(UserTransform, UserTransformLocalPosition, UserTransformLocalRotation); SetUseMesh(false); ClothInactive(); } protected override void OnDispose() { BaseClothDispose(); base.OnDispose(); } //========================================================================================= internal override void UpdateCullingMode(CoreComponent caller) { //Debug.Log($"UpdateCullingMode [{this.name}]"); // カリングモード bool isBoneCloth = GetComponentType() == ComponentType.BoneCloth || GetComponentType() == ComponentType.BoneSpring; if (CullingMode != TeamCullingMode.Off && isBoneCloth && cullRendererList.Count == 0) { // BoneCloth/BoneSpringだが参照レンダラーが設定されていないので強制的にカリングをOFFに設定する CullingMode = TeamCullingMode.Off; } // deformer CoreComponent vd = GetDeformer()?.Parent; // 表示状態 bool visible = false; if (CullingMode == TeamCullingMode.Off) { visible = true; } else if (IsActive()) // 起動中のみ { if (isBoneCloth) { // カリング用レンダラーリストから判定する if (cullRendererList.Count > 0) { foreach (var ren in cullRendererList) { if (ren && ren.isVisible) { visible = true; break; } } } else { // 未設定は念の為動作させておく visible = true; } } else { // デフォーマーの表示状態から判定する visible = vd ? vd.IsVisible : false; } } IsVisible = visible; // 計算状態 bool stopInvisible = (CullingMode != TeamCullingMode.Off); bool calc = true; if (stopInvisible) { calc = visible; } int val = calc ? 1 : 0; // コンポーネントアクティブ状態 val = Status.IsActive ? val : 0; // 最終判定 if (calculateValue != val) { calculateValue = val; OnChangeCalculation(); } // デフォーマーへ伝達 if (vd && vd != caller) GetDeformer()?.Parent?.UpdateCullingMode(this); } protected override void OnChangeCalculation() { //Debug.Log($"Cloth [{this.name}] Visible:{IsVisible} Calc:{IsCalculate} F:{Time.frameCount}"); MagicaPhysicsManager.Instance.Team.SetFlag(teamId, PhysicsManagerTeamData.Flag_Pause, !IsCalculate); if (IsCalculate) { // 一時停止再開によるリセット if (CullingMode == TeamCullingMode.Reset) { //Debug.Log($"Reset cloth! [{this.name}] F:{Time.frameCount}"); ResetCloth(ClothParams.TeleportMode.Reset); } // デフォーマの未来予測をリセットする // 遅延実行+再アクティブ時のみ //if (MagicaPhysicsManager.Instance.IsDelay && ActiveCount > 1) if (MagicaPhysicsManager.Instance.IsDelay) { GetDeformer()?.ResetFuturePrediction(); } // コライダーボーンの未来予測をリセットする // 遅延実行+再アクティブ時のみ //if (MagicaPhysicsManager.Instance.IsDelay && ActiveCount > 1) if (MagicaPhysicsManager.Instance.IsDelay) { MagicaPhysicsManager.Instance.Team.ResetFuturePredictionCollidere(TeamId); } } } public int GetCullRenderListCount() { if (cullRendererList == null) return 0; return cullRendererList.Count(x => x != null); } //========================================================================================= void BaseClothInit() { // デフォーマー初期化 if (IsRequiresDeformer()) { var deformer = GetDeformer(); if (deformer == null) { Status.SetInitError(); return; } // デフォーマーと状態を連動 var component = deformer.Parent; Status.LinkParentStatus(component.Status); // デフォーマーが親、クロスコンポーネントが子 component.Init(); if (component.Status.IsInitError) { Status.SetInitError(); return; } } if (VerifyData() != Define.Error.None) { Status.SetInitError(); return; } // クロス初期化 ClothInit(); // クロス初期化後の主にワーカーへの登録 WorkerInit(); // 頂点有効化 SetUseVertex(true); // 更新モード記録 oldUpdateMode = UpdateMode; oldCullingMode = CullingMode; oldUseAnimatedDistance = UseAnimatedPose; // UnityPhysics更新モードによる各種設定 if (UpdateMode == TeamUpdateMode.UnityPhysics) SetUseUnityPhysics(true); } void BaseClothDispose() { if (MagicaPhysicsManager.IsInstance() == false) return; // デフォーマとの状態の連動を解除 var deformer = GetDeformer(); if (deformer != null) { var component = deformer.Parent; Status.UnlinkParentStatus(component.Status); } if (Status.IsInitSuccess) { // 頂点無効化 SetUseVertex(false); // クロス破棄 // この中ですべてのコンストレイントとワーカーからチームのデータが自動削除される setup.ClothDispose(this); ClothDispose(); } } /// /// クロス初期化 /// protected virtual void ClothInit() { setup.ClothInit(this, GetMeshData(), ClothData, clothParams, UserFlag); } protected virtual void ClothActive() { setup.ClothActive(this, clothParams, ClothData); // アニメーションされた距離の使用設定 MagicaPhysicsManager.Instance.Team.SetFlag(TeamId, PhysicsManagerTeamData.Flag_AnimatedPose, UseAnimatedPose); } protected virtual void ClothInactive() { setup.ClothInactive(this); } protected virtual void ClothDispose() { } /// /// 頂点ごとのパーティクルフラグ設定(不要な場合は0) /// /// /// protected abstract uint UserFlag(int vindex); /// /// 頂点ごとの連動トランスフォーム設定(不要な場合はnull) /// /// /// protected abstract Transform UserTransform(int vindex); /// /// 頂点ごとの連動トランスフォームのLocalPositionを返す(不要な場合は0) /// /// /// protected abstract float3 UserTransformLocalPosition(int vindex); /// /// 頂点ごとの連動トランスフォームのLocalRotationを返す(不要な場合はquaternion.identity) /// /// /// protected abstract quaternion UserTransformLocalRotation(int vindex); /// /// デフォーマーが必須か返す /// /// public abstract bool IsRequiresDeformer(); /// /// デフォーマーを返す /// /// /// public abstract BaseMeshDeformer GetDeformer(); /// /// クロス初期化時に必要なMeshDataを返す(不要ならnull) /// /// protected abstract MeshData GetMeshData(); /// /// クロス初期化後の主にワーカーへの登録 /// protected abstract void WorkerInit(); //========================================================================================= /// /// 使用デフォーマー設定 /// /// void SetUseMesh(bool sw) { if (MagicaPhysicsManager.IsInstance() == false) return; if (Status.IsInitSuccess == false) return; var deformer = GetDeformer(); if (deformer != null) { if (sw) deformer.AddUseMesh(this); else deformer.RemoveUseMesh(this); } } /// /// 使用頂点設定 /// /// void SetUseVertex(bool sw) { if (MagicaPhysicsManager.IsInstance() == false) return; var deformer = GetDeformer(); if (deformer != null) { SetDeformerUseVertex(sw, deformer); } } /// /// デフォーマーの使用頂点設定 /// 使用頂点に対して AddUseVertex() / RemoveUseVertex() を実行する /// /// /// protected abstract void SetDeformerUseVertex(bool sw, BaseMeshDeformer deformer); /// /// デフォーマーに対してアクションを実行する /// /// internal void DeformerForEach(System.Action act) { var deformer = GetDeformer(); if (deformer != null) { act(deformer); } } //========================================================================================= /// /// ブレンド率更新 /// public void UpdateBlend() { if (teamId <= 0) return; // ユーザーブレンド率 float blend = UserBlendWeight; // 距離ブレンド率 blend *= setup.DistanceBlendRatio; // 変更チェック blend = Mathf.Clamp01(blend); if (blend != oldBlendRatio) { // チームデータへ反映 MagicaPhysicsManager.Instance.Team.SetBlendRatio(teamId, blend); // コンポーネント有効化判定 SetUserEnable(blend >= 1e-03f); oldBlendRatio = blend; } // カリングモード変更 if (CullingMode != oldCullingMode) { // 反映 UpdateCullingMode(this); oldCullingMode = CullingMode; } // 更新モード変更 if (UpdateMode != oldUpdateMode) { // チームデータへ反映 //Debug.Log($"Change Update Mode:{UpdateMode}"); MagicaPhysicsManager.Instance.Team.SetUpdateMode(TeamId, UpdateMode); // チームのパーティクルおよびボーンに反映 SetUseUnityPhysics(UpdateMode == TeamUpdateMode.UnityPhysics); oldUpdateMode = UpdateMode; } // アニメーションされた距離の使用 if (UseAnimatedPose != oldUseAnimatedDistance) { // チームデータへ反映 MagicaPhysicsManager.Instance.Team.SetFlag(TeamId, PhysicsManagerTeamData.Flag_AnimatedPose, UseAnimatedPose); oldUseAnimatedDistance = UseAnimatedPose; } } //========================================================================================= /// /// ボーンを置換する /// /// public override void ReplaceBone(Dictionary boneReplaceDict) { base.ReplaceBone(boneReplaceDict); // セットアップデータのボーン置換 setup.ReplaceBone(this, clothParams, boneReplaceDict); } /// /// 現在使用しているボーンを格納して返す /// /// public override HashSet GetUsedBones() { var bones = base.GetUsedBones(); // セットアップデータのボーン取得 bones.UnionWith(setup.GetUsedBones(this, clothParams)); return bones; } //========================================================================================= /// /// UnityPhyiscsでの更新の変更 /// 継承クラスは自身の使用するボーンの状態更新などを記述する /// /// protected override void ChangeUseUnityPhysics(bool sw) { if (teamId <= 0) return; setup.ChangeUseUnityPhysics(sw); MagicaPhysicsManager.Instance.Team.ChangeUseUnityPhysics(TeamId, sw); } //========================================================================================= /// /// データを検証して結果を格納する /// /// public override void CreateVerifyData() { base.CreateVerifyData(); clothDataHash = ClothData != null ? ClothData.SaveDataHash : 0; clothDataVersion = ClothData != null ? ClothData.SaveDataVersion : 0; clothSelectionHash = ClothSelection != null ? ClothSelection.SaveDataHash : 0; clothSelectionVersion = ClothSelection != null ? ClothSelection.SaveDataVersion : 0; // パラメータハッシュ clothParamDataHashList.Clear(); for (int i = 0; i < (int)ClothParams.ParamType.Max; i++) { int paramHash = clothParams.GetParamHash(this, (ClothParams.ParamType)i); clothParamDataHashList.Add(paramHash); } } /// /// 現在のデータが正常(実行できる状態)か返す /// /// public override Define.Error VerifyData() { var baseError = base.VerifyData(); if (baseError != Define.Error.None) return baseError; // clothDataはオプション if (ClothData != null) { var clothDataError = ClothData.VerifyData(); if (clothDataError != Define.Error.None) return clothDataError; if (clothDataHash != ClothData.SaveDataHash) return Define.Error.ClothDataHashMismatch; if (clothDataVersion != ClothData.SaveDataVersion) return Define.Error.ClothDataVersionMismatch; } // clothSelectionはオプション if (ClothSelection != null) { var clothSelectionError = ClothSelection.VerifyData(); if (clothSelectionError != Define.Error.None) return clothSelectionError; if (clothSelectionHash != ClothSelection.SaveDataHash) return Define.Error.ClothSelectionHashMismatch; if (clothSelectionVersion != ClothSelection.SaveDataVersion) return Define.Error.ClothSelectionVersionMismatch; } return Define.Error.None; } /// /// パラメータに重要な変更が発生したか調べる /// 重要な変更はデータを作り直す必要を指している /// /// /// public bool HasChangedParam(ClothParams.ParamType ptype) { int index = (int)ptype; if (clothParamDataHashList.Count == 0) return false; if (index >= clothParamDataHashList.Count) { return true; } int hash = clothParams.GetParamHash(this, ptype); if (hash == 0) return false; return clothParamDataHashList[index] != hash; } /// /// アルゴリズムバージョンチェック /// /// public Define.Error VerifyAlgorithmVersion() { if (clothData == null) return Define.Error.None; if (clothData.clampRotationAlgorithm != ClothParams.Algorithm.Algorithm_2) return Define.Error.OldAlgorithm; if (clothData.restoreRotationAlgorithm != ClothParams.Algorithm.Algorithm_2) return Define.Error.OldAlgorithm; if (clothData.triangleBendAlgorithm != ClothParams.Algorithm.Algorithm_2) return Define.Error.OldAlgorithm; return Define.Error.None; } /// /// データフォーマットを最新に更新する /// 主に古いパラメータを最新のパラメータに変換する /// /// true=更新あり, false=更新なし public override bool UpgradeFormat() { bool change = false; // アルゴリズム if (clothParams.AlgorithmType == ClothParams.Algorithm.Algorithm_1) { // アルゴリズム[2]へアップグレード clothParams.AlgorithmType = ClothParams.Algorithm.Algorithm_2; clothParams.ConvertToLatestAlgorithmParameter(); change = true; } return change; } //========================================================================================= /// /// 共有データオブジェクト収集 /// /// public override List GetAllShareDataObject() { var sdata = base.GetAllShareDataObject(); sdata.Add(ClothData); sdata.Add(ClothSelection); return sdata; } /// /// sourceの共有データを複製して再セットする /// 再セットした共有データを返す /// /// /// public override ShareDataObject DuplicateShareDataObject(ShareDataObject source) { if (ClothData == source) { //clothData = Instantiate(ClothData); clothData = ShareDataObject.Clone(ClothData); return clothData; } if (ClothSelection == source) { //clothSelection = Instantiate(ClothSelection); clothSelection = ShareDataObject.Clone(ClothSelection); return clothSelection; } return null; } //========================================================================================= /// /// シミュレーションリセットAPIの内部実装 /// /// /// private void ResetClothInternal(ClothParams.TeleportMode teleportMode, float resetStabilizationTime) { if (IsValid()) { switch (teleportMode) { case ClothParams.TeleportMode.Reset: MagicaPhysicsManager.Instance.Team.SetFlag(teamId, PhysicsManagerTeamData.Flag_Reset_WorldInfluence, true); MagicaPhysicsManager.Instance.Team.SetFlag(teamId, PhysicsManagerTeamData.Flag_Reset_Position, true); MagicaPhysicsManager.Instance.Team.SetFlag(teamId, PhysicsManagerTeamData.Flag_Reset_Keep, false); MagicaPhysicsManager.Instance.Team.ResetStabilizationTime(teamId, resetStabilizationTime); break; case ClothParams.TeleportMode.Keep: MagicaPhysicsManager.Instance.Team.SetFlag(teamId, PhysicsManagerTeamData.Flag_Reset_WorldInfluence, false); MagicaPhysicsManager.Instance.Team.SetFlag(teamId, PhysicsManagerTeamData.Flag_Reset_Position, false); MagicaPhysicsManager.Instance.Team.SetFlag(teamId, PhysicsManagerTeamData.Flag_Reset_Keep, true); break; } } } } }