// Magica Cloth.
// Copyright (c) MagicaSoft, 2020-2022.
// https://magicasoft.jp
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
namespace MagicaCloth
{
///
/// コライダーコリジョン拘束
///
public class ColliderCollisionConstraint : PhysicsManagerConstraint
{
public override void Create()
{
}
public override void RemoveTeam(int teamId)
{
}
public void ChangeParam(int teamId, bool useCollision)
{
Manager.Team.SetFlag(teamId, PhysicsManagerTeamData.Flag_Collision, useCollision);
}
public override void Release()
{
}
//=========================================================================================
public override JobHandle SolverConstraint(int runCount, float dtime, float updatePower, int iteration, JobHandle jobHandle)
{
if (Manager.Particle.ColliderCount <= 0)
return jobHandle;
// コリジョン拘束
var job1 = new CollisionJob()
{
runCount = runCount,
flagList = Manager.Particle.flagList.ToJobArray(),
teamIdList = Manager.Particle.teamIdList.ToJobArray(),
radiusList = Manager.Particle.radiusList.ToJobArray(),
nextPosList = Manager.Particle.InNextPosList.ToJobArray(),
nextRotList = Manager.Particle.InNextRotList.ToJobArray(),
posList = Manager.Particle.posList.ToJobArray(),
rotList = Manager.Particle.rotList.ToJobArray(),
localPosList = Manager.Particle.localPosList.ToJobArray(),
basePosList = Manager.Particle.basePosList.ToJobArray(),
baseRotList = Manager.Particle.baseRotList.ToJobArray(),
transformIndexList = Manager.Particle.transformIndexList.ToJobArray(),
colliderList = Manager.Team.colliderList.ToJobArray(),
boneSclList = Manager.Bone.boneSclList.ToJobArray(),
teamDataList = Manager.Team.teamDataList.ToJobArray(),
frictionList = Manager.Particle.frictionList.ToJobArray(),
collisionLinkIdList = Manager.Particle.collisionLinkIdList.ToJobArray(),
collisionNormalList = Manager.Particle.collisionNormalList.ToJobArray(),
};
jobHandle = job1.Schedule(Manager.Particle.Length, 64, jobHandle);
return jobHandle;
}
//=========================================================================================
///
/// コリジョン拘束ジョブ
/// 移動パーティクルごとに計算
///
[BurstCompile]
struct CollisionJob : IJobParallelFor
{
public int runCount;
[Unity.Collections.ReadOnly]
public NativeArray flagList;
[Unity.Collections.ReadOnly]
public NativeArray teamIdList;
[Unity.Collections.ReadOnly]
public NativeArray radiusList;
[NativeDisableParallelForRestriction]
public NativeArray nextPosList;
[Unity.Collections.ReadOnly]
public NativeArray nextRotList;
[Unity.Collections.ReadOnly]
public NativeArray posList;
[Unity.Collections.ReadOnly]
public NativeArray rotList;
[Unity.Collections.ReadOnly]
public NativeArray localPosList;
[Unity.Collections.ReadOnly]
public NativeArray basePosList;
[Unity.Collections.ReadOnly]
public NativeArray baseRotList;
[Unity.Collections.ReadOnly]
public NativeArray transformIndexList;
[Unity.Collections.ReadOnly]
public NativeArray colliderList;
[Unity.Collections.ReadOnly]
public NativeArray boneSclList;
[Unity.Collections.ReadOnly]
public NativeArray teamDataList;
public NativeArray frictionList;
[Unity.Collections.WriteOnly]
public NativeArray collisionLinkIdList;
[Unity.Collections.WriteOnly]
public NativeArray collisionNormalList;
// パーティクルごと
public void Execute(int index)
{
var flag = flagList[index];
if (flag.IsValid() == false || flag.IsFixed() || flag.IsCollider())
return;
// チーム
var team = teamIdList[index];
var teamData = teamDataList[team];
if (teamData.IsActive() == false)
return;
if (teamData.IsFlag(PhysicsManagerTeamData.Flag_Collision) == false)
return;
// 更新確認
if (teamData.IsUpdate(runCount) == false)
return;
float3 nextpos = nextPosList[index];
var radius = radiusList[index].x;
//var basepos = basePosList[index];
// チームスケール倍率
radius *= teamData.scaleRatio;
// チームごとに判定[グローバル(0)]->[自身のチーム(team)]
int colliderTeam = 0;
// コライダーとの距離
float mindist = 100.0f;
// 接触コライダー情報
int collisionColliderId = 0;
float3 collisionNormal = 0;
float3 n = 0;
for (int i = 0; i < 2; i++)
{
// チーム内のコライダーをループ
var c = teamDataList[colliderTeam].colliderChunk;
int dataIndex = c.startIndex;
for (int j = 0; j < c.useLength; j++, dataIndex++)
{
int cindex = colliderList[dataIndex];
var cflag = flagList[cindex];
if (cflag.IsValid() == false)
continue;
float dist = 100.0f;
if (cflag.IsFlag(PhysicsManagerParticleData.Flag_Plane))
{
// 平面コライダー判定
dist = PlaneColliderDetection(ref nextpos, radius, cindex, out n);
}
else if (cflag.IsFlag(PhysicsManagerParticleData.Flag_CapsuleX))
{
// カプセルコライダー判定
dist = CapsuleColliderDetection(ref nextpos, radius, cindex, new float3(1, 0, 0), out n);
}
else if (cflag.IsFlag(PhysicsManagerParticleData.Flag_CapsuleY))
{
// カプセルコライダー判定
dist = CapsuleColliderDetection(ref nextpos, radius, cindex, new float3(0, 1, 0), out n);
}
else if (cflag.IsFlag(PhysicsManagerParticleData.Flag_CapsuleZ))
{
// カプセルコライダー判定
dist = CapsuleColliderDetection(ref nextpos, radius, cindex, new float3(0, 0, 1), out n);
}
else if (cflag.IsFlag(PhysicsManagerParticleData.Flag_Box))
{
// ボックスコライダー判定
// ★まだ未実装
}
else
{
// 球コライダー判定
dist = SphereColliderDetection(ref nextpos, radius, cindex, out n);
}
// 押し出し(接触)あり
if (dist < mindist && dist <= Define.Compute.CollisionFrictionRange)
{
collisionColliderId = cindex;
collisionNormal = n;
mindist = dist;
}
}
// 自身のチームに切り替え
if (team > 0)
colliderTeam = team;
else
break;
}
// 摩擦係数(friction)計算
if (collisionColliderId > 0)
{
// コライダーから一定距離内にいる
// 摩擦係数計算(コライダーからの距離により変化.0.0~接地面1.0~深くめり込む場合は1.0を超える)
//var friction = math.max(1.0f - mindist / Define.Compute.CollisionFrictionRange, 0.0f); // ★この実装では振動が酷くなるので却下!
// コライダーからの距離により変化(0.0~接地面1.0)
var friction = 1.0f - math.saturate(mindist / Define.Compute.CollisionFrictionRange);
frictionList[index] = math.max(friction, frictionList[index]); // 大きい方
}
collisionLinkIdList[index] = collisionColliderId;
collisionNormalList[index] = collisionNormal;
// 書き戻し
nextPosList[index] = nextpos;
// コリジョンの速度影響は100%にしておく
// コリジョン衝突による速度影響は非常に重要!
// 速度影響を抑えると容易に突き抜けるようになってしまう
}
//=====================================================================================
///
/// 球衝突判定
///
///
///
///
///
///
///
float SphereColliderDetection(ref float3 nextpos, float radius, int cindex, out float3 normal)
{
var cpos = nextPosList[cindex];
var cradius = radiusList[cindex];
// スケール
var tindex = transformIndexList[cindex];
var cscl = boneSclList[tindex];
cradius *= math.abs(cscl.x); // X軸のみを見る
// 移動前のコライダーに対するローカル位置から移動後コライダーの押し出し平面を求める
float3 c = 0, n = 0, v = 0;
var coldpos = posList[cindex];
v = nextpos - coldpos;
n = math.normalize(v);
c = cpos + n * (cradius.x + radius);
// 衝突法線
normal = n;
// c = 平面位置
// n = 平面方向
// 平面衝突判定と押し出し
return MathUtility.IntersectPointPlaneDist(c, n, nextpos, out nextpos);
}
///
/// カプセル衝突判定
///
///
///
///
///
///
///
///
float CapsuleColliderDetection(ref float3 nextpos, float radius, int cindex, float3 dir, out float3 normal)
{
var cpos = nextPosList[cindex];
var crot = nextRotList[cindex];
// x = 長さ(片側)
// y = 始点半径
// z = 終点半径
var cradius = radiusList[cindex];
// スケール
var tindex = transformIndexList[cindex];
var cscl = boneSclList[tindex];
float scl = math.dot(math.abs(cscl), dir); // dirの軸のスケールを使用する
cradius *= scl;
float3 c = 0, n = 0;
var coldpos = posList[cindex];
var coldrot = rotList[cindex];
// カプセル始点と終点
float3 l = math.mul(coldrot, dir * cradius.x);
float3 spos = coldpos - l;
float3 epos = coldpos + l;
float sr = cradius.y;
float er = cradius.z;
// 移動前のコライダー位置から押し出し平面を割り出す
float t = MathUtility.ClosestPtPointSegmentRatio(nextpos, spos, epos);
float r = math.lerp(sr, er, t);
float3 d = math.lerp(spos, epos, t);
float3 v = nextpos - d;
// 移動前コライダーのローカルベクトル
var iq = math.inverse(coldrot);
float3 lv = math.mul(iq, v);
// 移動後コライダーに変換
l = math.mul(crot, dir * cradius.x);
spos = cpos - l;
epos = cpos + l;
d = math.lerp(spos, epos, t);
v = math.mul(crot, lv);
n = math.normalize(v);
c = d + n * (r + radius);
// 衝突法線
normal = n;
// c = 平面位置
// n = 平面方向
// 平面衝突判定と押し出し
return MathUtility.IntersectPointPlaneDist(c, n, nextpos, out nextpos);
}
///
/// 平面衝突判定
///
///
///
///
float PlaneColliderDetection(ref float3 nextpos, float radius, int cindex, out float3 normal)
{
// 平面姿勢
var cpos = nextPosList[cindex];
var crot = nextRotList[cindex];
// 平面法線
float3 n = math.mul(crot, math.up());
// パーティクル半径分オフセット
cpos += n * radius;
// 衝突法線
normal = n;
// c = 平面位置
// n = 平面方向
// 平面衝突判定と押し出し
// 平面との距離を返す(押し出しの場合は0.0)
return MathUtility.IntersectPointPlaneDist(cpos, n, nextpos, out nextpos);
}
}
}
}