// Magica Cloth. // Copyright (c) MagicaSoft, 2020-2022. // https://magicasoft.jp using System.Collections.Generic; using Unity.Burst; using Unity.Collections; using Unity.Jobs; using Unity.Mathematics; using UnityEngine.Jobs; using UnityEngine.Profiling; namespace MagicaCloth { /// /// 計算処理 /// public class PhysicsManagerCompute : PhysicsManagerAccess { /// /// 拘束判定繰り返し回数 /// //[Header("拘束全体の反復回数")] //[Range(1, 8)] //public int solverIteration = 2; private int solverIteration = 1; /// /// 拘束条件 /// List constraints = new List(); public ClampPositionConstraint ClampPosition { get; private set; } public ClampDistanceConstraint ClampDistance { get; private set; } //public ClampDistance2Constraint ClampDistance2 { get; private set; } public ClampRotationConstraint ClampRotation { get; private set; } public SpringConstraint Spring { get; private set; } public RestoreDistanceConstraint RestoreDistance { get; private set; } public RestoreRotationConstraint RestoreRotation { get; private set; } public TriangleBendConstraint TriangleBend { get; private set; } public ColliderCollisionConstraint Collision { get; private set; } public PenetrationConstraint Penetration { get; private set; } public ColliderExtrusionConstraint ColliderExtrusion { get; private set; } public TwistConstraint Twist { get; private set; } public CompositeRotationConstraint CompositeRotation { get; private set; } //public ColliderAfterCollisionConstraint AfterCollision { get; private set; } //public EdgeCollisionConstraint EdgeCollision { get; private set; } //public VolumeConstraint Volume { get; private set; } /// /// ワーカーリスト /// List workers = new List(); public RenderMeshWorker RenderMeshWorker { get; private set; } public VirtualMeshWorker VirtualMeshWorker { get; private set; } public MeshParticleWorker MeshParticleWorker { get; private set; } public SpringMeshWorker SpringMeshWorker { get; private set; } public AdjustRotationWorker AdjustRotationWorker { get; private set; } public LineWorker LineWorker { get; private set; } public TriangleWorker TriangleWorker { get; private set; } public BaseSkinningWorker BaseSkinningWorker { get; private set; } /// /// マスタージョブハンドル /// すべてのジョブはこのハンドルに連結される /// JobHandle jobHandle; private bool runMasterJob = false; private int swapIndex = 0; /// /// プロファイラ用 /// public CustomSampler SamplerCalcMesh { get; set; } public CustomSampler SamplerWriteMesh { get; set; } //========================================================================================= /// /// 初期設定 /// public override void Create() { // 拘束の作成 // ※この並び順が実行順番となります。 // コリジョン ColliderExtrusion = new ColliderExtrusionConstraint(); constraints.Add(ColliderExtrusion); Penetration = new PenetrationConstraint(); constraints.Add(Penetration); Collision = new ColliderCollisionConstraint(); constraints.Add(Collision); // 移動制限 ClampDistance = new ClampDistanceConstraint(); constraints.Add(ClampDistance); // 主なクロスシミュレーション Spring = new SpringConstraint(); constraints.Add(Spring); Twist = new TwistConstraint(); constraints.Add(Twist); RestoreDistance = new RestoreDistanceConstraint(); constraints.Add(RestoreDistance); RestoreRotation = new RestoreRotationConstraint(); constraints.Add(RestoreRotation); CompositeRotation = new CompositeRotationConstraint(); constraints.Add(CompositeRotation); // 形状維持 TriangleBend = new TriangleBendConstraint(); constraints.Add(TriangleBend); //Volume = new VolumeConstraint(); //constraints.Add(Volume); // 移動制限2 ClampPosition = new ClampPositionConstraint(); constraints.Add(ClampPosition); ClampRotation = new ClampRotationConstraint(); constraints.Add(ClampRotation); foreach (var con in constraints) con.Init(manager); // ワーカーの作成 // ※この並び順は変更してはいけません。 RenderMeshWorker = new RenderMeshWorker(); workers.Add(RenderMeshWorker); VirtualMeshWorker = new VirtualMeshWorker(); workers.Add(VirtualMeshWorker); MeshParticleWorker = new MeshParticleWorker(); workers.Add(MeshParticleWorker); SpringMeshWorker = new SpringMeshWorker(); workers.Add(SpringMeshWorker); AdjustRotationWorker = new AdjustRotationWorker(); workers.Add(AdjustRotationWorker); LineWorker = new LineWorker(); workers.Add(LineWorker); TriangleWorker = new TriangleWorker(); workers.Add(TriangleWorker); BaseSkinningWorker = new BaseSkinningWorker(); workers.Add(BaseSkinningWorker); foreach (var worker in workers) worker.Init(manager); // プロファイラ用 SamplerCalcMesh = CustomSampler.Create("CalcMesh"); SamplerWriteMesh = CustomSampler.Create("WriteMesh"); } /// /// 破棄 /// public override void Dispose() { if (constraints != null) { foreach (var con in constraints) con.Release(); } if (workers != null) { foreach (var worker in workers) worker.Release(); } } /// /// 各コンストレイント/ワーカーから指定グループのデータを削除する /// /// public void RemoveTeam(int teamId) { if (MagicaPhysicsManager.Instance.Team.IsValidData(teamId) == false) return; if (constraints != null) { foreach (var con in constraints) con.RemoveTeam(teamId); } if (workers != null) { foreach (var worker in workers) worker.RemoveGroup(teamId); } } //========================================================================================= /// /// ボーン姿勢を元の位置に復元する /// internal void UpdateRestoreBone(PhysicsTeam.TeamUpdateMode updateMode) { // 活動チームが1つ以上ある場合のみ更新 if (Team.ActiveTeamCount > 0) { // トランスフォーム姿勢のリセット Bone.ResetBoneFromTransform(updateMode == PhysicsTeam.TeamUpdateMode.UnityPhysics); } } /// /// ボーン姿勢を読み込む /// internal void UpdateReadBone() { // 活動チームが1つ以上ある場合のみ更新 if (Team.ActiveTeamCount > 0) { // トランスフォーム姿勢の読み込み Bone.ReadBoneFromTransform(); } } /// /// メインスレッドで行うチームデータ更新処理 /// internal void UpdateTeamAlways() { // 常に実行するチームデータ更新 Team.PreUpdateTeamAlways(); } /// /// クロスシミュレーション計算開始 /// /// internal void UpdateStartSimulation(UpdateTimeManager update) { // マネージャ非アクティブ時にはシミュレーション計算を完全に停止させる if (MagicaPhysicsManager.Instance.IsActive == false) return; // 時間 float deltaTime = update.DeltaTime; float physicsDeltaTime = update.PhysicsDeltaTime; float updatePower = update.UpdatePower; float updateDeltaTime = update.UpdateIntervalTime; int ups = update.UpdatePerSecond; // 活動チームが1つ以上ある場合のみ更新 if (Team.ActiveTeamCount > 0) { // 今回フレームの更新回数 int updateCount = Team.CalcMaxUpdateCount(ups, deltaTime, physicsDeltaTime, updateDeltaTime); //Debug.Log($"updateCount:{updateCount} dtime:{deltaTime} pdtime:{physicsDeltaTime} fixedCount:{update.FixedUpdateCount}"); // 風更新 //Wind.UpdateWind(); // チームデータ更新、更新回数確定、ワールド移動影響、テレポート Team.PreUpdateTeamData(deltaTime, physicsDeltaTime, updateDeltaTime, ups, updateCount); // ワーカー処理 WarmupWorker(); // ボーン姿勢をパーティクルにコピーする Particle.UpdateBoneToParticle(); // 物理更新前ワーカー処理 //MasterJob = RenderMeshWorker.PreUpdate(MasterJob); // 何もなし MasterJob = VirtualMeshWorker.PreUpdate(MasterJob); // 仮想メッシュをスキニングしワールド姿勢を求める MasterJob = MeshParticleWorker.PreUpdate(MasterJob); // 仮想メッシュ頂点姿勢を連動パーティクルにコピーする //MasterJob = SpringMeshWorker.PreUpdate(MasterJob); // 何もなし //MasterJob = AdjustRotationWorker.PreUpdate(MasterJob); // 何もなし //MasterJob = LineWorker.PreUpdate(MasterJob); // 何もなし MasterJob = BaseSkinningWorker.PreUpdate(MasterJob); // ベーススキニングによりbasePos/baseRotをスキニング // パーティクルのリセット判定 Particle.UpdateResetParticle(); // 物理更新 for (int i = 0, cnt = updateCount; i < cnt; i++) { UpdatePhysics(updateCount, i, updatePower, updateDeltaTime); } // 物理演算後処理 PostUpdatePhysics(updateDeltaTime); // 物理更新後ワーカー処理 MasterJob = TriangleWorker.PostUpdate(MasterJob); // トライアングル回転調整 MasterJob = LineWorker.PostUpdate(MasterJob); // ラインの回転調整 MasterJob = AdjustRotationWorker.PostUpdate(MasterJob); // パーティクル回転調整(Adjust Rotation) Particle.UpdateParticleToBone(); // パーティクル姿勢をボーン姿勢に書き戻す(ここに挟まないと駄目) MasterJob = SpringMeshWorker.PostUpdate(MasterJob); // メッシュスプリング MasterJob = MeshParticleWorker.PostUpdate(MasterJob); // パーティクル姿勢を仮想メッシュに書き出す MasterJob = VirtualMeshWorker.PostUpdate(MasterJob); // 仮想メッシュ座標書き込み(仮想メッシュトライアングル法線計算) MasterJob = RenderMeshWorker.PostUpdate(MasterJob); // レンダーメッシュ座標書き込み(仮想メッシュからレンダーメッシュ座標計算) // 書き込みボーン姿勢をローカル姿勢に変換する Bone.ConvertWorldToLocal(); // チームデータ後処理 Team.PostUpdateTeamData(); } } /// /// クロスシミュレーション完了待ち /// internal void UpdateCompleteSimulation() { // マスタージョブ完了待機 CompleteJob(); runMasterJob = true; #if UNITY_2021_2_OR_NEWER // 高速書き込みバッファの作業終了 Mesh.FinishVertexBuffer(); #endif //Debug.Log($"runMasterJob = true! F:{Time.frameCount}"); } /// /// ボーン姿勢をトランスフォームに書き込む /// internal void UpdateWriteBone() { // ボーン姿勢をトランスフォームに書き出す Bone.WriteBoneToTransform(manager.IsDelay ? 1 : 0); } /// /// メッシュ書き込みの事前判定 /// internal void MeshCalculation() { // プロファイラ計測開始 SamplerCalcMesh.Begin(); Mesh.ClearWritingList(); if (Mesh.VirtualMeshCount > 0 && runMasterJob) { Mesh.MeshCalculation(manager.IsDelay ? 1 : 0); } // プロファイラ計測終了 SamplerCalcMesh.End(); } /// /// メッシュ姿勢をメッシュに書き込む /// internal void NormalWritingMesh() { // プロファイラ計測開始 SamplerWriteMesh.Begin(); // メッシュへの頂点書き戻し if (Mesh.VirtualMeshCount > 0 && runMasterJob) { Mesh.NormalWriting(manager.IsDelay ? 1 : 0); #if UNITY_2021_2_OR_NEWER Mesh.FasterWriting(manager.IsDelay ? 1 : 0); #endif } // プロファイラ計測終了 SamplerWriteMesh.End(); } /// /// 遅延実行時のボーン読み込みと前回のボーン結果の書き込み /// internal void UpdateReadWriteBone() { // 活動チームが1つ以上ある場合のみ更新 if (Team.ActiveTeamCount > 0) { // トランスフォーム姿勢の読み込み Bone.ReadBoneFromTransform(); if (runMasterJob) { // ボーン姿勢をトランスフォームに書き出す Bone.WriteBoneToTransform(manager.IsDelay ? 1 : 0); } } } /// /// 遅延実行時のみボーンの計算結果を書き込みバッファにコピーする /// internal void UpdateSyncBuffer() { Bone.writeBoneIndexList.SyncBuffer(); Bone.writeBonePosList.SyncBuffer(); Bone.writeBoneRotList.SyncBuffer(); Bone.boneFlagList.SyncBuffer(); InitJob(); Bone.CopyBoneBuffer(); CompleteJob(); } /// /// 遅延実行時のみメッシュの計算結果をスワップする /// internal void UpdateSwapBuffer() { Mesh.renderPosList.SwapBuffer(); Mesh.renderNormalList.SwapBuffer(); Mesh.renderTangentList.SwapBuffer(); Mesh.renderBoneWeightList.SwapBuffer(); #if UNITY_2021_2_OR_NEWER // 高速書き込み用コンピュートバッファをスワップ Mesh.renderPosBuffer.Swap(); Mesh.renderNormalBuffer.Swap(); #endif swapIndex ^= 1; // 遅延実行計算済みフラグを立てる Mesh.SetDelayedCalculatedFlag(); } //========================================================================================= public JobHandle MasterJob { get { return jobHandle; } set { jobHandle = value; } } /// /// マスタージョブハンドル初期化 /// public void InitJob() { jobHandle = default(JobHandle); } public void ScheduleJob() { JobHandle.ScheduleBatchedJobs(); } /// /// マスタージョブハンドル完了待機 /// public void CompleteJob() { jobHandle.Complete(); jobHandle = default(JobHandle); } /// /// 遅延実行時のダブルバッファのフロントインデックス /// public int SwapIndex { get { return swapIndex; } } //========================================================================================= /// /// 物理エンジン更新ループ処理 /// これは1フレームにステップ回数分呼び出される /// 場合によっては1回も呼ばれないフレームも発生するので注意! /// /// /// /// void UpdatePhysics(int updateCount, int runCount, float updatePower, float updateDeltaTime) { if (Particle.Count == 0) return; // フォース影響+速度更新 var job1 = new ForceAndVelocityJob() { updateDeltaTime = updateDeltaTime, updatePower = updatePower, runCount = runCount, teamDataList = Team.teamDataList.ToJobArray(), teamMassList = Team.teamMassList.ToJobArray(), teamGravityList = Team.teamGravityList.ToJobArray(), teamDragList = Team.teamDragList.ToJobArray(), teamDepthInfluenceList = Team.teamDepthInfluenceList.ToJobArray(), teamWindInfoList = Team.teamWindInfoList.ToJobArray(), //teamMaxVelocityList = Team.teamMaxVelocityList.ToJobArray(), //teamDirectionalDampingList = Team.teamDirectionalDampingList.ToJobArray(), flagList = Particle.flagList.ToJobArray(), teamIdList = Particle.teamIdList.ToJobArray(), depthList = Particle.depthList.ToJobArray(), snapBasePosList = Particle.snapBasePosList.ToJobArray(), snapBaseRotList = Particle.snapBaseRotList.ToJobArray(), basePosList = Particle.basePosList.ToJobArray(), baseRotList = Particle.baseRotList.ToJobArray(), oldBasePosList = Particle.oldBasePosList.ToJobArray(), oldBaseRotList = Particle.oldBaseRotList.ToJobArray(), nextPosList = Particle.InNextPosList.ToJobArray(), nextRotList = Particle.InNextRotList.ToJobArray(), oldPosList = Particle.oldPosList.ToJobArray(), oldRotList = Particle.oldRotList.ToJobArray(), frictionList = Particle.frictionList.ToJobArray(), //oldSlowPosList = Particle.oldSlowPosList.ToJobArray(), posList = Particle.posList.ToJobArray(), rotList = Particle.rotList.ToJobArray(), velocityList = Particle.velocityList.ToJobArray(), //boneRotList = Bone.boneRotList.ToJobArray(), // wind windDataList = Wind.windDataList.ToJobArray(), // bone bonePosList = Bone.bonePosList.ToJobArray(), boneRotList = Bone.boneRotList.ToJobArray(), }; jobHandle = job1.Schedule(Particle.Length, 64, jobHandle); // 拘束条件解決 if (constraints != null) { // 拘束解決反復数分ループ for (int i = 0; i < solverIteration; i++) { foreach (var con in constraints) { if (con != null /*&& con.enabled*/) { // 拘束ごとの反復回数 for (int j = 0; j < con.GetIterationCount(); j++) { jobHandle = con.SolverConstraint(runCount, updateDeltaTime, updatePower, j, jobHandle); } } } } } // 座標確定 var job2 = new FixPositionJob() { updatePower = updatePower, updateDeltaTime = updateDeltaTime, runCount = runCount, teamDataList = Team.teamDataList.ToJobArray(), teamMaxVelocityList = Team.teamMaxVelocityList.ToJobArray(), flagList = Particle.flagList.ToJobArray(), teamIdList = Particle.teamIdList.ToJobArray(), depthList = Particle.depthList.ToJobArray(), nextPosList = Particle.InNextPosList.ToJobArray(), nextRotList = Particle.InNextRotList.ToJobArray(), //basePosList = Particle.basePosList.ToJobArray(), //baseRotList = Particle.baseRotList.ToJobArray(), oldPosList = Particle.oldPosList.ToJobArray(), oldRotList = Particle.oldRotList.ToJobArray(), frictionList = Particle.frictionList.ToJobArray(), velocityList = Particle.velocityList.ToJobArray(), rotList = Particle.rotList.ToJobArray(), posList = Particle.posList.ToJobArray(), localPosList = Particle.localPosList.ToJobArray(), collisionNormalList = Particle.collisionNormalList.ToJobArray(), staticFrictionList = Particle.staticFrictionList.ToJobArray(), }; jobHandle = job2.Schedule(Particle.Length, 64, jobHandle); } [BurstCompile] struct ForceAndVelocityJob : IJobParallelFor { public float updateDeltaTime; public float updatePower; public int runCount; // team [Unity.Collections.ReadOnly] public NativeArray teamDataList; [Unity.Collections.ReadOnly] public NativeArray teamMassList; [Unity.Collections.ReadOnly] public NativeArray teamGravityList; [Unity.Collections.ReadOnly] public NativeArray teamDragList; [Unity.Collections.ReadOnly] public NativeArray teamDepthInfluenceList; [Unity.Collections.ReadOnly] public NativeArray teamWindInfoList; //[Unity.Collections.ReadOnly] //public NativeArray teamMaxVelocityList; //[Unity.Collections.ReadOnly] //public NativeArray teamDirectionalDampingList; // particle public NativeArray flagList; [Unity.Collections.ReadOnly] public NativeArray teamIdList; [Unity.Collections.ReadOnly] public NativeArray depthList; [Unity.Collections.ReadOnly] public NativeArray snapBasePosList; [Unity.Collections.ReadOnly] public NativeArray snapBaseRotList; [Unity.Collections.WriteOnly] public NativeArray basePosList; [Unity.Collections.WriteOnly] public NativeArray baseRotList; [Unity.Collections.ReadOnly] public NativeArray oldBasePosList; [Unity.Collections.ReadOnly] public NativeArray oldBaseRotList; public NativeArray nextPosList; public NativeArray nextRotList; public NativeArray frictionList; [Unity.Collections.WriteOnly] public NativeArray posList; [Unity.Collections.WriteOnly] public NativeArray rotList; [Unity.Collections.ReadOnly] public NativeArray oldPosList; [Unity.Collections.ReadOnly] public NativeArray oldRotList; [Unity.Collections.ReadOnly] public NativeArray velocityList; // wind [Unity.Collections.ReadOnly] public NativeArray windDataList; // bone [Unity.Collections.ReadOnly] public NativeArray bonePosList; [Unity.Collections.ReadOnly] public NativeArray boneRotList; // パーティクルごと public void Execute(int index) { var flag = flagList[index]; if (flag.IsValid() == false) return; // チームデータ int teamId = teamIdList[index]; var teamData = teamDataList[teamId]; // ここからは更新がある場合のみ実行(グローバルチームは除く) if (teamId != 0 && teamData.IsUpdate(runCount) == false) return; var oldpos = oldPosList[index]; var oldrot = oldRotList[index]; float3 nextPos = oldpos; quaternion nextRot = oldrot; var friction = frictionList[index]; // 基準姿勢のステップ補間(v1.11.1) var oldBasePos = oldBasePosList[index]; var oldBaseRot = oldBaseRotList[index]; var snapBasePos = snapBasePosList[index]; var snapBaseRot = snapBaseRotList[index]; float stime = teamData.startTime + updateDeltaTime * runCount; float oldtime = teamData.startTime - updateDeltaTime; float interval = teamData.time - oldtime; float step = interval >= 1e-06f ? math.saturate((stime - oldtime) / interval) : 0.0f; float3 basePos = math.lerp(oldBasePos, snapBasePos, step); quaternion baseRot = math.slerp(oldBaseRot, snapBaseRot, step); baseRot = math.normalize(baseRot); // 必要 basePosList[index] = basePos; baseRotList[index] = baseRot; if (flag.IsFixed()) { // キネマティックパーティクル nextPos = basePos; nextRot = baseRot; // nextPos/nextRotが1ステップ前の姿勢 var oldNextPos = nextPosList[index]; var oldNextRot = nextRotList[index]; // 前回の姿勢をoldpos/rotとしてposList/rotListに格納する if (flag.IsCollider() && teamId == 0) { // グローバルコライダー // 移動量と回転量に制限をかける(1.7.5) // 制限をかけないと高速移動/回転時に遠く離れたパーティクルが押し出されてしまう問題が発生する。 oldpos = MathUtility.ClampDistance(nextPos, oldNextPos, Define.Compute.GlobalColliderMaxMoveDistance); oldrot = MathUtility.ClampAngle(nextRot, oldNextRot, math.radians(Define.Compute.GlobalColliderMaxRotationAngle)); } else { oldpos = oldNextPos; oldrot = oldNextRot; } #if false // nextPos/nextRotが1ステップ前の姿勢 var oldNextPos = nextPosList[index]; var oldNextRot = nextRotList[index]; // oldpos/rotが前フレームの最終計算姿勢 // oldpos/rot から BasePos/Rot に step で補間して現在姿勢とする float stime = teamData.startTime + updateDeltaTime * runCount; float oldtime = teamData.startTime - updateDeltaTime; float interval = teamData.time - oldtime; float step = interval >= 1e-06f ? math.saturate((stime - oldtime) / interval) : 0.0f; nextPos = math.lerp(oldpos, basePosList[index], step); nextRot = math.slerp(oldrot, baseRotList[index], step); nextRot = math.normalize(nextRot); // 前回の姿勢をoldpos/rotとしてposList/rotListに格納する if (flag.IsCollider() && teamId == 0) { // グローバルコライダー // 移動量と回転量に制限をかける(1.7.5) // 制限をかけないと高速移動/回転時に遠く離れたパーティクルが押し出されてしまう問題が発生する。 oldpos = MathUtility.ClampDistance(nextPos, oldNextPos, Define.Compute.GlobalColliderMaxMoveDistance); oldrot = MathUtility.ClampAngle(nextRot, oldNextRot, math.radians(Define.Compute.GlobalColliderMaxRotationAngle)); } else { oldpos = oldNextPos; oldrot = oldNextRot; } #endif // debug //nextPos = basePosList[index]; //nextRot = baseRotList[index]; } else { // 動的パーティクル var depth = depthList[index]; //var maxVelocity = teamMaxVelocityList[teamId].Evaluate(depth); var drag = teamDragList[teamId].Evaluate(depth); var gravity = teamGravityList[teamId].Evaluate(depth); var gravityDirection = teamData.gravityDirection; var mass = teamMassList[teamId].Evaluate(depth); var depthInfluence = teamDepthInfluenceList[teamId].Evaluate(depth); var velocity = velocityList[index]; // チームスケール倍率 //maxVelocity *= teamData.scaleRatio; // massは主に伸縮を中心に調整されるので、フォース適用時は少し調整する //mass = (mass - 1.0f) * teamData.forceMassInfluence + 1.0f; // 安定化用の速度ウエイト velocity *= teamData.velocityWeight; // 最大速度 //velocity = MathUtility.ClampVector(velocity, 0.0f, maxVelocity); // 空気抵抗(90ups基準) // 重力に影響させたくないので先に計算する(※通常はforce適用後に行うのが一般的) velocity *= math.pow(1.0f - drag, updatePower); // フォース // フォースは空気抵抗を無視して加算する float3 force = 0; // 重力(質量に関係なく一定) // (最後に質量で割るためここでは質量をかける) force += gravityDirection * (gravity * mass); // 外部フォース if (runCount == 0) { float3 exForce = 0; switch (teamData.forceMode) { case PhysicsManagerTeamData.ForceMode.VelocityAdd: exForce += teamData.impactForce; break; case PhysicsManagerTeamData.ForceMode.VelocityAddWithoutMass: exForce += teamData.impactForce * mass; break; case PhysicsManagerTeamData.ForceMode.VelocityChange: exForce += teamData.impactForce; velocity = 0; break; case PhysicsManagerTeamData.ForceMode.VelocityChangeWithoutMass: exForce += teamData.impactForce * mass; velocity = 0; break; } // 外力 exForce += teamData.externalForce; // 風(重量に関係なく一定) if (teamData.IsFlag(PhysicsManagerTeamData.Flag_Wind)) exForce += Wind(teamId, teamData, snapBasePos) * mass; // 外力深さ影響率 exForce *= depthInfluence; force += exForce; } // 外力チームスケール倍率 force *= teamData.scaleRatio; // 速度計算(質量で割る) velocity += (force / mass) * updateDeltaTime; // 速度を理想位置に反映させる nextPos = oldpos + velocity * updateDeltaTime; } // 予定座標更新 ============================================================== // 摩擦減衰 friction = friction * Define.Compute.FrictionDampingRate; frictionList[index] = friction; //frictionList[index] = 0; // 移動前の姿勢 posList[index] = oldpos; rotList[index] = oldrot; // 予測位置 nextPosList[index] = nextPos; nextRotList[index] = nextRot; } /// /// 風の計算 /// /// /// /// /// float3 Wind(int teamId, in PhysicsManagerTeamData.TeamData teamData, in float3 pos) { var windInfo = teamWindInfoList[teamId]; // ノイズ起点 // ここをずらすと他のパーティクルと非同期になっていく float sync = math.lerp(3.0f, 0.1f, teamData.forceWindSynchronization); var noiseBasePos = new float2(pos.x, pos.z) * sync; float3 externalForce = 0; for (int i = 0; i < 4; i++) { int windId = windInfo.windDataIndexList[i]; if (windId < 0) continue; var windData = windDataList[windId]; float3 windForce = PhysicsManagerWindData.CalcWindForce( teamData.time, noiseBasePos, windInfo.windDirectionList[i], windInfo.windMainList[i], windData.turbulence, windData.frequency, teamData.forceWindRandomScale ); externalForce += windForce; } // チームの風の影響率 externalForce *= teamData.forceWindInfluence; return externalForce; } } [BurstCompile] struct FixPositionJob : IJobParallelFor { public float updatePower; public float updateDeltaTime; public int runCount; // チーム [Unity.Collections.ReadOnly] public NativeArray teamDataList; [Unity.Collections.ReadOnly] public NativeArray teamMaxVelocityList; // パーティクルごと [Unity.Collections.ReadOnly] public NativeArray flagList; [Unity.Collections.ReadOnly] public NativeArray teamIdList; [Unity.Collections.ReadOnly] public NativeArray depthList; [Unity.Collections.ReadOnly] public NativeArray nextPosList; [Unity.Collections.ReadOnly] public NativeArray nextRotList; [Unity.Collections.ReadOnly] public NativeArray frictionList; //[Unity.Collections.ReadOnly] //public NativeArray basePosList; //[Unity.Collections.ReadOnly] //public NativeArray baseRotList; public NativeArray velocityList; [Unity.Collections.WriteOnly] public NativeArray rotList; public NativeArray oldPosList; public NativeArray oldRotList; public NativeArray posList; [Unity.Collections.WriteOnly] public NativeArray localPosList; [Unity.Collections.ReadOnly] public NativeArray collisionNormalList; public NativeArray staticFrictionList; // パーティクルごと public void Execute(int index) { var flag = flagList[index]; if (flag.IsValid() == false) return; // チームデータ int teamId = teamIdList[index]; var teamData = teamDataList[teamId]; // ここからは更新がある場合のみ実行 if (teamData.IsUpdate(runCount) == false) return; // 速度更新(m/s) if (flag.IsFixed() == false) { // 移動パーティクルのみ var nextPos = nextPosList[index]; var nextRot = nextRotList[index]; nextRot = math.normalize(nextRot); // 回転蓄積で精度が落ちていくので正規化しておく float3 velocity = 0; // posListには移動影響を考慮した最終座標が入っている var pos = posList[index]; var oldpos = oldPosList[index]; // コライダー接触情報 float friction = frictionList[index]; var cn = collisionNormalList[index]; bool isCollision = math.lengthsq(cn) > Define.Compute.Epsilon; // 接触の有無 #if true // 静止摩擦 float staticFriction = staticFrictionList[index]; if (isCollision && friction > 0.0f) { // 接線方向の移動速度から計算する var v = nextPos - oldpos; v = v - MathUtility.Project(v, cn); float tangentVelocity = math.length(v) / updateDeltaTime; // 接線方向の移動速度 float stopVelocity = teamData.staticFriction * teamData.scaleRatio; // 静止速度 if (tangentVelocity < stopVelocity) { staticFriction = math.saturate(staticFriction + 0.02f * updatePower); // 係数増加 } else { // 接線速度に応じて係数を減少 var vel = tangentVelocity - stopVelocity; var value = math.max(vel / 0.2f, 0.05f) * updatePower; staticFriction = math.saturate(staticFriction - value); } // 現在の静止摩擦係数を使い接線方向の移動にブレーキをかける v *= staticFriction; nextPos -= v; pos -= v; } else { staticFriction = math.saturate(staticFriction - 0.05f * updatePower); // 係数減少 } staticFrictionList[index] = staticFriction; #endif // 速度更新(m/s) velocity = (nextPos - pos) / updateDeltaTime; velocity *= teamData.velocityWeight; // 安定化用の速度ウエイト #if true // 動摩擦による速度減衰(衝突面との角度が大きいほど減衰が強くなる) if (friction > Define.Compute.Epsilon && isCollision && math.lengthsq(velocity) >= Define.Compute.Epsilon) { var dot = math.dot(cn, math.normalize(velocity)); dot = 0.5f + 0.5f * dot; // 1.0(front) - 0.5(side) - 0.0(back) dot *= dot; // サイドを強めに dot = 1.0f - dot; // 0.0(front) - 0.75(side) - 1.0(back) velocity -= velocity * (dot * math.saturate(friction * teamData.dynamicFriction * 1.5f)); // 以前と同程度になるように補正 } #else // 摩擦による速度減衰(旧) friction *= teamData.friction; // チームごとの摩擦係数 velocity *= math.pow(1.0f - math.saturate(friction), updatePower); #endif // 最大速度 var depth = depthList[index]; var maxVelocity = teamMaxVelocityList[teamId].Evaluate(depth); maxVelocity *= teamData.scaleRatio; // チームスケール velocity = MathUtility.ClampVector(velocity, 0.0f, maxVelocity); // 実際の移動速度(localPosに格納) var realVelocity = (nextPos - oldpos) / updateDeltaTime; realVelocity = MathUtility.ClampVector(realVelocity, 0.0f, maxVelocity); // 最大速度は考慮する localPosList[index] = realVelocity; // 書き戻し velocityList[index] = velocity; oldPosList[index] = nextPos; oldRotList[index] = nextRot; } } } //========================================================================================= /// /// 物理演算後処理 /// /// void PostUpdatePhysics(float updateDeltaTime) { if (Particle.Count == 0) return; var job = new PostUpdatePhysicsJob() { updateDeltaTime = updateDeltaTime, teamDataList = Team.teamDataList.ToJobArray(), flagList = Particle.flagList.ToJobArray(), teamIdList = Particle.teamIdList.ToJobArray(), snapBasePosList = Particle.snapBasePosList.ToJobArray(), snapBaseRotList = Particle.snapBaseRotList.ToJobArray(), basePosList = Particle.basePosList.ToJobArray(), baseRotList = Particle.baseRotList.ToJobArray(), oldBasePosList = Particle.oldBasePosList.ToJobArray(), oldBaseRotList = Particle.oldBaseRotList.ToJobArray(), oldPosList = Particle.oldPosList.ToJobArray(), oldRotList = Particle.oldRotList.ToJobArray(), velocityList = Particle.velocityList.ToJobArray(), localPosList = Particle.localPosList.ToJobArray(), posList = Particle.posList.ToJobArray(), rotList = Particle.rotList.ToJobArray(), nextPosList = Particle.InNextPosList.ToJobArray(), nextRotList = Particle.InNextRotList.ToJobArray(), oldSlowPosList = Particle.oldSlowPosList.ToJobArray(), }; jobHandle = job.Schedule(Particle.Length, 64, jobHandle); } [BurstCompile] struct PostUpdatePhysicsJob : IJobParallelFor { public float updateDeltaTime; // チーム [Unity.Collections.ReadOnly] public NativeArray teamDataList; // パーティクルごと [Unity.Collections.ReadOnly] public NativeArray flagList; [Unity.Collections.ReadOnly] public NativeArray teamIdList; // パーティクルごと [Unity.Collections.ReadOnly] public NativeArray snapBasePosList; [Unity.Collections.ReadOnly] public NativeArray snapBaseRotList; [Unity.Collections.ReadOnly] public NativeArray basePosList; [Unity.Collections.ReadOnly] public NativeArray baseRotList; [Unity.Collections.WriteOnly] public NativeArray oldBasePosList; [Unity.Collections.WriteOnly] public NativeArray oldBaseRotList; [Unity.Collections.ReadOnly] public NativeArray velocityList; [Unity.Collections.ReadOnly] public NativeArray localPosList; public NativeArray oldPosList; public NativeArray oldRotList; [Unity.Collections.WriteOnly] public NativeArray posList; [Unity.Collections.WriteOnly] public NativeArray rotList; [Unity.Collections.ReadOnly] public NativeArray nextPosList; [Unity.Collections.ReadOnly] public NativeArray nextRotList; public NativeArray oldSlowPosList; // パーティクルごと public void Execute(int index) { var flag = flagList[index]; if (flag.IsValid() == false) return; // チームデータ int teamId = teamIdList[index]; var teamData = teamDataList[teamId]; float3 viewPos = 0; quaternion viewRot = quaternion.identity; //var basePos = basePosList[index]; //var baseRot = baseRotList[index]; var snapBasePos = snapBasePosList[index]; var snapBaseRot = snapBaseRotList[index]; if (flag.IsFixed() == false) { // 未来予測 // 1フレーム前の表示位置と将来の予測位置を、現在のフレーム位置で線形補間する //var velocity = velocityList[index]; // 従来 //var velocity = posList[index]; // 実際の速度(どうもこっちだとカクつき?があるぞ) var velocity = localPosList[index]; // 実際の速度 var futurePos = oldPosList[index] + velocity * updateDeltaTime; var oldViewPos = oldSlowPosList[index]; float addTime = teamData.addTime; float oldTime = teamData.time - addTime; float futureTime = teamData.time + (updateDeltaTime - teamData.nowTime); float interval = futureTime - oldTime; //Debug.Log($"addTime:{teamData.addTime} interval:{interval}"); if (addTime > 1e-06f && interval > 1e-06f) { float ratio = teamData.addTime / interval; viewPos = math.lerp(oldViewPos, futurePos, ratio); } else { viewPos = oldViewPos; } viewRot = oldRotList[index]; viewRot = math.normalize(viewRot); // 回転蓄積で精度が落ちていくので正規化しておく #if false // 未来予測を切る futurePos = oldPosList[index]; viewPos = futurePos; #endif oldSlowPosList[index] = viewPos; } else { // 固定パーティクルの表示位置は常にベース位置 //viewPos = basePos; //viewRot = baseRot; viewPos = snapBasePos; viewRot = snapBaseRot; // 固定パーティクルは今回のbasePosを記録する(更新時のみ) if (teamData.IsRunning()) { // 最終計算位置を格納する oldPosList[index] = nextPosList[index]; oldRotList[index] = nextRotList[index]; } } // ブレンド if (teamData.blendRatio < 0.99f) { //viewPos = math.lerp(basePos, viewPos, teamData.blendRatio); //viewRot = math.slerp(baseRot, viewRot, teamData.blendRatio); viewPos = math.lerp(snapBasePos, viewPos, teamData.blendRatio); viewRot = math.slerp(snapBaseRot, viewRot, teamData.blendRatio); viewRot = math.normalize(viewRot); // 回転蓄積で精度が落ちていくので正規化しておく } // test //viewPos = snapBasePos; //viewRot = snapBaseRot; // 表示位置 posList[index] = viewPos; rotList[index] = viewRot; // 1つ前の基準位置を記録 if (teamData.IsRunning()) { oldBasePosList[index] = basePosList[index]; oldBaseRotList[index] = baseRotList[index]; } } } //========================================================================================= /// /// ワーカーウォームアップ処理実行 /// void WarmupWorker() { if (workers == null || workers.Count == 0) return; for (int i = 0; i < workers.Count; i++) { var worker = workers[i]; worker.Warmup(); } } } }