// 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 PenetrationConstraint : PhysicsManagerConstraint { /// /// 浸透制限データ /// todo:共有可能 /// [System.Serializable] public struct PenetrationData { /// /// 計算頂点インデックス /// public short vertexIndex; /// /// コライダー配列インデックス /// public short colliderIndex; /// /// コライダーローカル座標(中心軸) /// public float3 localPos; /// /// 押し出しローカル方向(単位ベクトル) /// public float3 localDir; /// /// パーティクルへの距離(オリジナル位置) /// public float distance; public bool IsValid() { return vertexIndex >= 0; } } FixedChunkNativeArray dataList; /// /// ローカルパーティクルインデックスごとのデータ参照情報 /// FixedChunkNativeArray refDataList; /// /// BonePenetration用データ /// 頂点に対するローカル浸透方向 /// FixedChunkNativeArray bonePenetrationDataList; /// /// グループごとの拘束データ /// public struct GroupData { public int teamId; public int active; /// /// (0=Surface, 1=Collider, 2=Bone) /// public int mode; public float maxDepth; public CurveParam radius; public CurveParam distance; public ChunkData dataChunk; public ChunkData refDataChunk; public ChunkData bonePenetrationDataChunk; } public FixedNativeList groupList; //========================================================================================= public override void Create() { groupList = new FixedNativeList(); dataList = new FixedChunkNativeArray(); refDataList = new FixedChunkNativeArray(); bonePenetrationDataList = new FixedChunkNativeArray(); } public override void Release() { groupList.Dispose(); dataList.Dispose(); refDataList.Dispose(); bonePenetrationDataList.Dispose(); } public int AddGroup( int teamId, bool active, ClothParams.PenetrationMode mode, BezierParam distance, BezierParam radius, float maxDepth, PenetrationData[] moveLimitDataList, ReferenceDataIndex[] refDataArray, float3[] bonePenetrationDataArray ) { //var teamData = MagicaPhysicsManager.Instance.Team.teamDataList[teamId]; var gdata = new GroupData(); gdata.teamId = teamId; gdata.active = active ? 1 : 0; gdata.mode = (int)mode; gdata.distance.Setup(distance); gdata.radius.Setup(radius); gdata.maxDepth = maxDepth; if (moveLimitDataList != null && moveLimitDataList.Length > 0) { gdata.dataChunk = dataList.AddChunk(moveLimitDataList.Length); gdata.refDataChunk = refDataList.AddChunk(refDataArray.Length); // チャンクデータコピー dataList.ToJobArray().CopyFromFast(gdata.dataChunk.startIndex, moveLimitDataList); refDataList.ToJobArray().CopyFromFast(gdata.refDataChunk.startIndex, refDataArray); } if (bonePenetrationDataArray != null && bonePenetrationDataArray.Length > 0) { gdata.bonePenetrationDataChunk = bonePenetrationDataList.AddChunk(bonePenetrationDataArray.Length); // チャンクデータコピー bonePenetrationDataList.ToJobArray().CopyFromFast(gdata.bonePenetrationDataChunk.startIndex, bonePenetrationDataArray); } int group = groupList.Add(gdata); return group; } public override void RemoveTeam(int teamId) { var teamData = MagicaPhysicsManager.Instance.Team.teamDataList[teamId]; int group = teamData.penetrationGroupIndex; if (group < 0) return; var gdata = groupList[group]; // チャンクデータ削除 dataList.RemoveChunk(gdata.dataChunk); refDataList.RemoveChunk(gdata.refDataChunk); bonePenetrationDataList.RemoveChunk(gdata.bonePenetrationDataChunk); // データ削除 groupList.Remove(group); } public void ChangeParam(int teamId, bool active, BezierParam distance, BezierParam radius, float maxDepth) { var teamData = Manager.Team.teamDataList[teamId]; int group = teamData.penetrationGroupIndex; if (group < 0) return; var gdata = groupList[group]; gdata.active = active ? 1 : 0; gdata.distance.Setup(distance); gdata.radius.Setup(radius); gdata.maxDepth = maxDepth; groupList[group] = gdata; } //========================================================================================= public override JobHandle SolverConstraint(int runCount, float dtime, float updatePower, int iteration, JobHandle jobHandle) { if (groupList.Count == 0) return jobHandle; // 移動制限拘束 var job1 = new PenetrationJob() { runCount = runCount, groupList = groupList.ToJobArray(), dataList = dataList.ToJobArray(), refDataList = refDataList.ToJobArray(), bonePenetrationDataList = bonePenetrationDataList.ToJobArray(), flagList = Manager.Particle.flagList.ToJobArray(), teamIdList = Manager.Particle.teamIdList.ToJobArray(), nextPosList = Manager.Particle.InNextPosList.ToJobArray(), nextRotList = Manager.Particle.InNextRotList.ToJobArray(), transformIndexList = Manager.Particle.transformIndexList.ToJobArray(), depthList = Manager.Particle.depthList.ToJobArray(), basePosList = Manager.Particle.basePosList.ToJobArray(), baseRotList = Manager.Particle.baseRotList.ToJobArray(), colliderList = Manager.Team.colliderList.ToJobArray(), bonePosList = Manager.Bone.bonePosList.ToJobArray(), boneRotList = Manager.Bone.boneRotList.ToJobArray(), boneSclList = Manager.Bone.boneSclList.ToJobArray(), teamDataList = Manager.Team.teamDataList.ToJobArray(), skinningBoneList = Manager.Team.skinningBoneList.ToJobArray(), outNextPosList = Manager.Particle.OutNextPosList.ToJobArray(), posList = Manager.Particle.posList.ToJobArray(), //frictionList = Manager.Particle.frictionList.ToJobArray(), }; jobHandle = job1.Schedule(Manager.Particle.Length, 64, jobHandle); Manager.Particle.SwitchingNextPosList(); return jobHandle; } //========================================================================================= /// /// 浸透制限拘束ジョブ /// パーティクルごとに計算 /// [BurstCompile] struct PenetrationJob : IJobParallelFor { public int runCount; [Unity.Collections.ReadOnly] public NativeArray groupList; [Unity.Collections.ReadOnly] public NativeArray dataList; [Unity.Collections.ReadOnly] public NativeArray refDataList; [Unity.Collections.ReadOnly] public NativeArray bonePenetrationDataList; [Unity.Collections.ReadOnly] public NativeArray flagList; [Unity.Collections.ReadOnly] public NativeArray teamIdList; [Unity.Collections.ReadOnly] public NativeArray nextPosList; [Unity.Collections.ReadOnly] public NativeArray nextRotList; [Unity.Collections.ReadOnly] public NativeArray transformIndexList; [Unity.Collections.ReadOnly] public NativeArray depthList; [Unity.Collections.ReadOnly] public NativeArray basePosList; [Unity.Collections.ReadOnly] public NativeArray baseRotList; [Unity.Collections.ReadOnly] public NativeArray colliderList; // bone [Unity.Collections.ReadOnly] public NativeArray bonePosList; [Unity.Collections.ReadOnly] public NativeArray boneRotList; [Unity.Collections.ReadOnly] public NativeArray boneSclList; // team [Unity.Collections.ReadOnly] public NativeArray teamDataList; [Unity.Collections.ReadOnly] public NativeArray skinningBoneList; [Unity.Collections.WriteOnly] public NativeArray outNextPosList; public NativeArray posList; //public NativeArray frictionList; // パーティクルごと public void Execute(int index) { // 初期化コピー float3 nextpos = nextPosList[index]; outNextPosList[index] = nextpos; 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.penetrationGroupIndex < 0) return; // 更新確認 if (teamData.IsUpdate(runCount) == false) return; // グループデータ var gdata = groupList[teamData.penetrationGroupIndex]; if (gdata.active == 0) return; int vindex = index - teamData.particleChunk.startIndex; var oldpos = nextpos; // depth var depth = depthList[index]; // move radius var moveradius = gdata.radius.Evaluate(depth); // 浸透距離 float distance = gdata.distance.Evaluate(depth); // チームスケール倍率 float3 scaleDirection = teamData.scaleDirection; float teamScale = teamData.scaleRatio; distance *= teamScale; moveradius *= teamScale; //Debug.Log(teamScale); // モード別処理 if (gdata.mode == 0) { // Surface Penetration // データ参照情報 var refdata = refDataList[gdata.refDataChunk.startIndex + vindex]; if (refdata.count > 0) { // ベース位置から算出する var bpos = basePosList[index]; var brot = baseRotList[index]; int dindex = refdata.startIndex; var data = dataList[gdata.dataChunk.startIndex + dindex]; if (data.IsValid()) { //float3 n = math.mul(brot, data.localDir); float3 n = math.mul(brot, data.localDir * scaleDirection); // マイナススケール対応 // 球の位置 var c = bpos + n * (distance - moveradius); // 球内部制限 var v = nextpos - c; var len = math.length(v); if (len > moveradius) { v *= (moveradius / len); nextpos = c + v; } } } } else if (gdata.mode == 1) { // Collider Penetration // データ参照情報 var refdata = refDataList[gdata.refDataChunk.startIndex + vindex]; if (refdata.count > 0) { // 球内制限 float3 c = 0; int ccnt = 0; int dindex = refdata.startIndex; for (int i = 0; i < refdata.count; i++, dindex++) { var data = dataList[gdata.dataChunk.startIndex + dindex]; if (data.IsValid()) { int cindex = colliderList[teamData.colliderChunk.startIndex + data.colliderIndex]; var cflag = flagList[cindex]; if (cflag.IsValid() == false) continue; // 球内部制限 c += InverseSpherePosition(ref data, teamScale, scaleDirection, distance, cindex, moveradius); ccnt++; } } if (ccnt > 0) { c /= ccnt; var opos = InverseSpherePenetration(c, moveradius, nextpos); var addv = (opos - nextpos); // stiffness test //addv *= 0.25f; // 摩擦を入れてみる //float friction = math.length(addv) * 10.0f; //frictionList[index] = math.max(friction, frictionList[index]); // 大きい方 nextpos += addv; } } } else if (gdata.mode == 2) { // Bone Penetration if (depth <= gdata.maxDepth) { float3 basePos = basePosList[index]; quaternion baseRot = baseRotList[index]; float3 ln = bonePenetrationDataList[gdata.bonePenetrationDataChunk.startIndex + vindex]; float3 n = math.mul(baseRot, ln); #if true // 球の位置 var c = basePos + n * (moveradius - math.min(distance, moveradius)); //var c = basePos + n * (-distance + moveradius); // 球内部制限 var v = nextpos - c; var len = math.length(v); if (len > moveradius) { v *= (moveradius / len); nextpos = c + v; } #endif #if false // 平面押し出し var c = basePos - n * (distance); MathUtility.IntersectPointPlane(c, n, nextpos, out nextpos); #endif #if false // 逆バンク // 球の位置 var c = basePos - n * (distance + moveradius); // 球外部制限 var v = nextpos - c; var len = math.length(v); if (len < moveradius) { v *= (moveradius / len); nextpos = c + v; } #endif // test //nextpos = basePos; // 速度影響 const float velocityInfluence = (1.0f - 0.3f); // 0.2f? posList[index] += (nextpos - oldpos) * velocityInfluence; } } // 書き戻し outNextPosList[index] = nextpos; } //===================================================================================== /// /// 内球制限 /// /// /// /// /// /// /// /// /*private bool InverseSpherePenetration(ref PenetrationData data, float teamScale, float distance, int cindex, float cr, float3 nextpos, out float3 outpos) { var cpos = nextPosList[cindex]; var crot = nextRotList[cindex]; // スケール var tindex = transformIndexList[cindex]; var cscl = boneSclList[tindex]; // 中心軸 var d = math.mul(crot, data.localPos * cscl) + cpos; // 方向 var n = math.mul(crot, data.localDir); // 球の位置 var c = d + n * (data.distance * teamScale - distance + cr); // 球内部制限 var v = nextpos - c; var len = math.length(v); if (len > cr) { v *= (cr / len); outpos = c + v; return true; } else { outpos = nextpos; return false; } }*/ /// /// 内球制限の中心位置を求める /// /// /// /// チームスケール済み /// /// チームスケール済み /// private float3 InverseSpherePosition(ref PenetrationData data, float teamScale, float3 scaleDirection, float distance, int cindex, float cr) { var cpos = nextPosList[cindex]; var crot = nextRotList[cindex]; // スケール var tindex = transformIndexList[cindex]; var cscl = boneSclList[tindex]; // 中心軸 var d = math.mul(crot, data.localPos * cscl) + cpos; // 方向 //var n = math.mul(crot, data.localDir); var n = math.mul(crot, data.localDir * scaleDirection); // マイナススケール対応 // 球の位置 var c = d + n * (data.distance * teamScale - distance + cr); return c; } /// /// 内球移動制限をかける /// /// /// /// /// private float3 InverseSpherePenetration(float3 c, float cr, float3 nextpos) { // 球内部制限 var v = nextpos - c; var len = math.length(v); if (len > cr) { v *= (cr / len); return c + v; } else { return nextpos; } } #if false /// /// 平面制限 /// /// /// /// /// /// private bool PlanePenetration(ref PenetrationData data, float teamScale, float distance, int cindex, float3 nextpos, out float3 outpos) { var cpos = nextPosList[cindex]; var crot = nextRotList[cindex]; // スケール var tindex = transformIndexList[cindex]; var cscl = boneSclList[tindex]; // 中心軸 var d = math.mul(crot, data.localPos * cscl) + cpos; // 方向 var n = math.mul(crot, data.localDir); // 押し出し平面を求める var c = d + n * (data.distance * teamScale - distance); // c = 平面位置 // n = 平面方向 // 平面衝突判定と押し出し return MathUtility.IntersectPointPlane(c, n, nextpos, out outpos); } private void InversePlanePosition(ref PenetrationData data, float teamScale, float distance, int cindex, out float3 center, out float3 dir) { var cpos = nextPosList[cindex]; var crot = nextRotList[cindex]; // スケール var tindex = transformIndexList[cindex]; var cscl = boneSclList[tindex]; // 中心軸 var d = math.mul(crot, data.localPos * cscl) + cpos; // 方向 var n = math.mul(crot, data.localDir); // プレーン位置 var c = d + n * (data.distance * teamScale - distance); center = c; dir = n; } #endif #if false /// /// 角度制限 /// /// /// /// /// /// /// private bool AnglePenetration(ref PenetrationData data, int cindex, float3 nextpos, out float3 outpos, float ang) { var cpos = nextPosList[cindex]; var crot = nextRotList[cindex]; // スケール var tindex = transformIndexList[cindex]; var cscl = boneSclList[tindex]; //float scl = cscl.x; // X軸を採用(基本的には均等スケールのみを想定) // 押し出し平面を求める var c = math.mul(crot, data.localPos * cscl) + cpos; var n = math.mul(crot, data.localDir); var v = nextpos - c; float3 v2; if (MathUtility.ClampAngle(v, n, ang, out v2)) { outpos = c + v2; return true; } outpos = nextpos; return false; } #endif } } }