// 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 EdgeCollisionConstraint : PhysicsManagerConstraint { /// /// 拘束データ /// todo:共有化可能 /// [System.Serializable] public struct EdgeCollisionData { /// /// エッジ形成パーティクルインデックス /// public ushort vindex0; public ushort vindex1; /// /// 書き込みバッファインデックス /// public int writeIndex0; public int writeIndex1; /// /// データが有効か判定する /// /// public bool IsValid() { return vindex0 > 0 && vindex1 > 0; } } FixedChunkNativeArray dataList; /// /// データごとのグループインデックス /// FixedChunkNativeArray groupIndexList; /// /// 内部パーティクルインデックスごとの書き込みバッファ参照 /// FixedChunkNativeArray refDataList; /// /// 頂点計算結果書き込みバッファ /// FixedChunkNativeArray writeBuffer; /// /// グループごとの拘束データ /// public struct GroupData { public int teamId; public int active; public float edgeRadius; /// /// データチャンク /// public ChunkData dataChunk; /// /// グループデータチャンク /// public ChunkData groupIndexChunk; /// /// 内部インデックス用チャンク /// public ChunkData refDataChunk; /// /// 頂点計算結果書き込み用チャンク /// public ChunkData writeDataChunk; } FixedNativeList groupList; //========================================================================================= public override void Create() { dataList = new FixedChunkNativeArray(); groupIndexList = new FixedChunkNativeArray(); refDataList = new FixedChunkNativeArray(); writeBuffer = new FixedChunkNativeArray(); groupList = new FixedNativeList(); } public override void Release() { dataList.Dispose(); groupIndexList.Dispose(); refDataList.Dispose(); writeBuffer.Dispose(); groupList.Dispose(); } //========================================================================================= public int AddGroup(int teamId, bool active, float edgeRadius, EdgeCollisionData[] dataArray, ReferenceDataIndex[] refDataArray, int writeBufferCount) { if (dataArray == null || dataArray.Length == 0 || refDataArray == null || refDataArray.Length == 0 || writeBufferCount == 0) return -1; var teamData = MagicaPhysicsManager.Instance.Team.teamDataList[teamId]; // グループデータ作成 var gdata = new GroupData(); gdata.teamId = teamId; gdata.active = active ? 1 : 0; gdata.edgeRadius = edgeRadius; //gdata.stiffness.Setup(stiffness); gdata.dataChunk = dataList.AddChunk(dataArray.Length); gdata.groupIndexChunk = groupIndexList.AddChunk(dataArray.Length); gdata.refDataChunk = refDataList.AddChunk(refDataArray.Length); gdata.writeDataChunk = writeBuffer.AddChunk(writeBufferCount); // チャンクデータコピー dataList.ToJobArray().CopyFromFast(gdata.dataChunk.startIndex, dataArray); refDataList.ToJobArray().CopyFromFast(gdata.refDataChunk.startIndex, refDataArray); int group = groupList.Add(gdata); // データごとのグループインデックス groupIndexList.Fill(gdata.groupIndexChunk, (short)group); return group; } public override void RemoveTeam(int teamId) { var teamData = MagicaPhysicsManager.Instance.Team.teamDataList[teamId]; int group = teamData.edgeCollisionGroupIndex; if (group < 0) return; var cdata = groupList[group]; // チャンクデータ削除 dataList.RemoveChunk(cdata.dataChunk); refDataList.RemoveChunk(cdata.refDataChunk); writeBuffer.RemoveChunk(cdata.writeDataChunk); groupIndexList.RemoveChunk(cdata.groupIndexChunk); // データ削除 groupList.Remove(group); } public void ChangeParam(int teamId, bool active, float edgeRadius) { var teamData = MagicaPhysicsManager.Instance.Team.teamDataList[teamId]; int group = teamData.edgeCollisionGroupIndex; if (group < 0) return; var gdata = groupList[group]; gdata.active = active ? 1 : 0; gdata.edgeRadius = edgeRadius; //gdata.stiffness.Setup(stiffness); groupList[group] = gdata; } //public int ActiveCount //{ // get // { // int cnt = 0; // for (int i = 0; i < groupList.Length; i++) // if (groupList[i].active == 1) // cnt++; // return cnt; // } //} //========================================================================================= /// /// 拘束の解決 /// /// /// /// public override JobHandle SolverConstraint(int runCount, float dtime, float updatePower, int iteration, JobHandle jobHandle) { if (groupList.Count == 0) return jobHandle; // ステップ1:コリジョンの計算 var job = new EdgeCollisionCalcJob() { updatePower = updatePower, runCount = runCount, groupDataList = groupList.ToJobArray(), dataList = dataList.ToJobArray(), groupIndexList = groupIndexList.ToJobArray(), //colliderMap = Manager.Team.colliderMap.Map, colliderList = Manager.Team.colliderList.ToJobArray(), teamDataList = Manager.Team.teamDataList.ToJobArray(), flagList = Manager.Particle.flagList.ToJobArray(), radiusList = Manager.Particle.radiusList.ToJobArray(), posList = Manager.Particle.posList.ToJobArray(), rotList = Manager.Particle.rotList.ToJobArray(), nextPosList = Manager.Particle.InNextPosList.ToJobArray(), nextRotList = Manager.Particle.InNextRotList.ToJobArray(), localPosList = Manager.Particle.localPosList.ToJobArray(), //oldPosList = Manager.Particle.oldPosList.ToJobArray(), transformIndexList = Manager.Particle.transformIndexList.ToJobArray(), boneSclList = Manager.Bone.boneSclList.ToJobArray(), writeBuffer = writeBuffer.ToJobArray(), }; jobHandle = job.Schedule(dataList.Length, 64, jobHandle); // ステップ2:コリジョン結果の集計 var job2 = new EdgeCollisionSumJob() { runCount = runCount, groupDataList = groupList.ToJobArray(), refDataList = refDataList.ToJobArray(), writeBuffer = writeBuffer.ToJobArray(), teamDataList = Manager.Team.teamDataList.ToJobArray(), teamIdList = Manager.Particle.teamIdList.ToJobArray(), flagList = Manager.Particle.flagList.ToJobArray(), inoutNextPosList = Manager.Particle.InNextPosList.ToJobArray(), frictionList = Manager.Particle.frictionList.ToJobArray(), }; jobHandle = job2.Schedule(Manager.Particle.Length, 64, jobHandle); return jobHandle; } [BurstCompile] struct EdgeCollisionCalcJob : IJobParallelFor { public float updatePower; public int runCount; [Unity.Collections.ReadOnly] public NativeArray groupDataList; [Unity.Collections.ReadOnly] public NativeArray dataList; [Unity.Collections.ReadOnly] public NativeArray groupIndexList; //[Unity.Collections.ReadOnly] //public NativeMultiHashMap colliderMap; [Unity.Collections.ReadOnly] public NativeArray colliderList; [Unity.Collections.ReadOnly] public NativeArray teamDataList; [Unity.Collections.ReadOnly] public NativeArray flagList; [Unity.Collections.ReadOnly] public NativeArray radiusList; [Unity.Collections.ReadOnly] public NativeArray posList; [Unity.Collections.ReadOnly] public NativeArray rotList; [Unity.Collections.ReadOnly] public NativeArray nextPosList; [Unity.Collections.ReadOnly] public NativeArray nextRotList; [Unity.Collections.ReadOnly] public NativeArray localPosList; //[Unity.Collections.ReadOnly] //public NativeArray oldPosList; [Unity.Collections.ReadOnly] public NativeArray transformIndexList; [Unity.Collections.ReadOnly] public NativeArray boneSclList; [Unity.Collections.WriteOnly] [NativeDisableParallelForRestriction] public NativeArray writeBuffer; // エッジデータごと public void Execute(int index) { var data = dataList[index]; if (data.IsValid() == false) return; int gindex = groupIndexList[index]; var gdata = groupDataList[gindex]; if (gdata.teamId == 0 || gdata.active == 0) return; var tdata = teamDataList[gdata.teamId]; if (tdata.IsActive() == false) return; // 更新確認 if (tdata.IsUpdate(runCount) == false) return; int pstart = tdata.particleChunk.startIndex; float3 corr0 = 0; float3 corr1 = 0; int pindex0 = data.vindex0 + pstart; int pindex1 = data.vindex1 + pstart; float3 nextpos0 = nextPosList[pindex0]; float3 nextpos1 = nextPosList[pindex1]; //float3 oldpos0 = oldPosList[pindex0]; //float3 oldpos1 = oldPosList[pindex1]; // エッジの太さ float radius = gdata.edgeRadius; // 計算結果の移動値をcorrに格納 // チームごとに判定[グローバル(0)]->[自身のチーム(team)] int colliderTeam = 0; bool hitresult = false; 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; bool hit = false; if (cflag.IsFlag(PhysicsManagerParticleData.Flag_Plane)) { // 平面コライダー判定 //hit = PlaneColliderDetection(ref nextpos, radius, cindex); } else if (cflag.IsFlag(PhysicsManagerParticleData.Flag_CapsuleX)) { // カプセルコライダー判定 hit = CapsuleColliderDetection(nextpos0, nextpos1, ref corr0, ref corr1, radius, cindex, new float3(1, 0, 0)); //hit = CapsuleColliderDetection(nextpos0, nextpos1, oldpos0, oldpos1, ref corr0, ref corr1, radius, cindex, new float3(1, 0, 0)); } else if (cflag.IsFlag(PhysicsManagerParticleData.Flag_CapsuleY)) { // カプセルコライダー判定 hit = CapsuleColliderDetection(nextpos0, nextpos1, ref corr0, ref corr1, radius, cindex, new float3(0, 1, 0)); //hit = CapsuleColliderDetection(nextpos0, nextpos1, oldpos0, oldpos1, ref corr0, ref corr1, radius, cindex, new float3(0, 1, 0)); } else if (cflag.IsFlag(PhysicsManagerParticleData.Flag_CapsuleZ)) { // カプセルコライダー判定 hit = CapsuleColliderDetection(nextpos0, nextpos1, ref corr0, ref corr1, radius, cindex, new float3(0, 0, 1)); //hit = CapsuleColliderDetection(nextpos0, nextpos1, oldpos0, oldpos1, ref corr0, ref corr1, radius, cindex, new float3(0, 0, 1)); } else if (cflag.IsFlag(PhysicsManagerParticleData.Flag_Box)) { // ボックスコライダー判定 // ★まだ未実装 } else { // 球コライダー判定 hit = SphereColliderDetection(nextpos0, nextpos1, ref corr0, ref corr1, radius, cindex); //hit = SphereColliderDetection(nextpos0, nextpos1, oldpos0, oldpos1, ref corr0, ref corr1, radius, cindex); } hitresult = hit ? true : hitresult; //if (hit) //{ // // 衝突あり! // // 摩擦設定 // //frictionList[index] = math.max(frictionList[index], teamData.friction); //} } // 自身のチームに切り替え colliderTeam = gdata.teamId; } // 摩擦係数? // 作業バッファへ格納 int wstart = gdata.writeDataChunk.startIndex; int windex0 = data.writeIndex0 + wstart; int windex1 = data.writeIndex1 + wstart; writeBuffer[windex0] = corr0; writeBuffer[windex1] = corr1; } /// /// 球衝突判定 /// /// エッジの始点 /// エッジの終点 /// /// /// /// /// bool SphereColliderDetection(float3 nextpos0, float3 nextpos1, ref float3 corr0, ref float3 corr1, float radius, int cindex) //bool SphereColliderDetection(float3 nextpos0, float3 nextpos1, float3 oldpos0, float3 oldpos1, ref float3 corr0, ref float3 corr1, float radius, int cindex) { var cpos = nextPosList[cindex]; var coldpos = posList[cindex]; var cradius = radiusList[cindex]; // スケール var tindex = transformIndexList[cindex]; var cscl = boneSclList[tindex]; cradius *= cscl.x; // X軸のみを見る // コライダー球とエッジの最接近点を求める float3 d = MathUtility.ClosestPtPointSegment(coldpos, nextpos0, nextpos1); //float3 d = MathUtility.ClosestPtPointSegment(coldpos, oldpos0, oldpos1); float3 n = math.normalize(d - coldpos); float3 c = cpos + n * (cradius.x + radius); // c = 平面位置 // n = 平面方向 // 平面衝突判定と押し出し float3 outpos0, outpos1; bool ret0 = MathUtility.IntersectPointPlane(c, n, nextpos0, out outpos0); bool ret1 = MathUtility.IntersectPointPlane(c, n, nextpos1, out outpos1); if (ret0) corr0 += outpos0 - nextpos0; if (ret1) corr1 += outpos1 - nextpos1; return ret0 || ret1; } /// /// カプセル衝突判定 /// /// エッジの始点 /// エッジの終点 /// /// /// /// /// /// bool CapsuleColliderDetection(float3 nextpos0, float3 nextpos1, ref float3 corr0, ref float3 corr1, float radius, int cindex, float3 dir) //bool CapsuleColliderDetection(float3 nextpos0, float3 nextpos1, float3 oldpos0, float3 oldpos1, ref float3 corr0, ref float3 corr1, float radius, int cindex, float3 dir) { var cpos = nextPosList[cindex]; var crot = nextRotList[cindex]; var coldpos = posList[cindex]; var coldrot = rotList[cindex]; // x = 長さ(片側) // y = 始点半径 // z = 終点半径 //var lpos = localPosList[cindex]; var cradius = radiusList[cindex]; // スケール var tindex = transformIndexList[cindex]; var cscl = boneSclList[tindex]; float scl = math.dot(cscl, dir); // dirの軸のスケールを使用する cradius *= scl; // 移動前のコライダーに対するエッジの最近接点を求める float3 oldl = math.mul(coldrot, dir * cradius.x); float3 soldpos = coldpos - oldl; float3 eoldpos = coldpos + oldl; float3 c1, c2; float s, t; MathUtility.ClosestPtSegmentSegment(soldpos, eoldpos, nextpos0, nextpos1, out s, out t, out c1, out c2); //MathUtility.ClosestPtSegmentSegment(soldpos, eoldpos, oldpos0, oldpos1, out s, out t, out c1, out c2); float3 v = c2 - c1; // 現在のカプセル始点と終点 float3 l = math.mul(crot, dir * cradius.x); float3 spos = cpos - l; float3 epos = cpos + l; float sr = cradius.y; float er = cradius.z; // 移動後のコライダーのベクトルに変換する var iq = math.inverse(coldrot); float3 lv = math.mul(iq, v); v = math.mul(crot, lv); // コライダーの半径 float r = math.lerp(sr, er, s); // 平面方程式 float3 n = math.normalize(v); float3 q = math.lerp(spos, epos, s); float3 c = q + n * (r + radius); // c = 平面位置 // n = 平面方向 // 平面衝突判定と押し出し float3 outpos0, outpos1; bool ret0 = MathUtility.IntersectPointPlane(c, n, nextpos0, out outpos0); bool ret1 = MathUtility.IntersectPointPlane(c, n, nextpos1, out outpos1); if (ret0) corr0 += outpos0 - nextpos0; if (ret1) corr1 += outpos1 - nextpos1; return ret0 || ret1; } } [BurstCompile] struct EdgeCollisionSumJob : IJobParallelFor { public int runCount; [Unity.Collections.ReadOnly] public NativeArray groupDataList; [Unity.Collections.ReadOnly] public NativeArray refDataList; [Unity.Collections.ReadOnly] public NativeArray writeBuffer; // チーム [Unity.Collections.ReadOnly] public NativeArray teamDataList; [Unity.Collections.ReadOnly] public NativeArray teamIdList; [Unity.Collections.ReadOnly] public NativeArray flagList; public NativeArray inoutNextPosList; public NativeArray frictionList; // パーティクルごと public void Execute(int pindex) { var flag = flagList[pindex]; if (flag.IsValid() == false || flag.IsFixed()) return; // チーム var team = teamDataList[teamIdList[pindex]]; if (team.IsActive() == false) return; if (team.edgeCollisionGroupIndex < 0) return; // 更新確認 if (team.IsUpdate(runCount) == false) return; // グループデータ var gdata = groupDataList[team.edgeCollisionGroupIndex]; if (gdata.active == 0) return; // 集計 int start = team.particleChunk.startIndex; int index = pindex - start; var refdata = refDataList[gdata.refDataChunk.startIndex + index]; if (refdata.count > 0) { float3 corr = 0; var bindex = gdata.writeDataChunk.startIndex + refdata.startIndex; for (int i = 0; i < refdata.count; i++) { corr += writeBuffer[bindex]; bindex++; } corr /= refdata.count; // 加算 inoutNextPosList[pindex] = inoutNextPosList[pindex] + corr; // 摩擦 //if (math.lengthsq(corr) > 0.00001f) //if (math.lengthsq(corr) > 0.0f) { // 摩擦設定 //frictionList[pindex] = math.max(frictionList[pindex], team.friction); } } } } } }