// 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 TriangleBendConstraint : PhysicsManagerConstraint { /// /// 拘束データ /// todo:共有化可能 /// [System.Serializable] public struct TriangleBendData { /// /// トライアングル形成パーティクルインデックスx4 /// 2つの三角形、p2-p3が共通辺で、p0/p1が端の独立点 /// p2 + /// /|\ /// p0 + | + p1 /// \|/ /// p3 + /// /// A(p0-p2-p3) B(p1-p3-p2) /// /// ただしvindex3が-1の場合は次のように単純なトライアングルの固定位置復元となる /// v0 + /// / \ /// v1 +---+ v2 /// /// v3 = -1 public int vindex0; public int vindex1; public int vindex2; public int vindex3; /// /// 復元角度(ラジアン) /// public float restAngle; /// /// 初期方向性 [Algorithm 2] /// public float direction; /// /// ベンド影響を取得するデプス値(0.0-1.0) /// public float depth; /// /// 書き込みバッファインデックス /// public int writeIndex0; public int writeIndex1; public int writeIndex2; public int writeIndex3; /// /// データが有効か判定する /// /// public bool IsValid() { return vindex0 > 0 && vindex1 > 0; } /// /// データが固定位置復元かどうか /// /// public bool IsPositionBend() { return vindex3 < 0; } /// /// データが厳密版か判定する /// /// //public bool IsStrict() //{ // return math.abs(direction) > 1e-06f; //} } FixedChunkNativeArray dataList; /// /// データごとのグループインデックス /// FixedChunkNativeArray groupIndexList; /// /// 内部パーティクルインデックスごとの書き込みバッファ参照 /// FixedChunkNativeArray refDataList; /// /// 頂点計算結果書き込みバッファ /// FixedChunkNativeArray writeBuffer; /// /// グループごとの拘束データ /// public struct TriangleBendGroupData { public int teamId; public int active; /// /// アルゴリズム(ClothParams.Algorithmを参照) /// public int algorithm; /// /// 固定点を含むトライアングルの計算の有無 /// //public int useIncludeFixed; /// /// 曲げの戻り効果量(0.0-1.0) /// public CurveParam stiffness; /// /// データチャンク /// 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, ClothParams.Algorithm algorithm, BezierParam stiffness, //bool useIncludeFixed, TriangleBendData[] 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 TriangleBendGroupData(); gdata.teamId = teamId; gdata.active = active ? 1 : 0; gdata.algorithm = (int)algorithm; //gdata.useIncludeFixed = useIncludeFixed ? 1 : 0; 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.triangleBendGroupIndex; 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, BezierParam stiffness/*, bool useIncludeFixed*/) { var teamData = MagicaPhysicsManager.Instance.Team.teamDataList[teamId]; int group = teamData.triangleBendGroupIndex; if (group < 0) return; var gdata = groupList[group]; gdata.active = active ? 1 : 0; //gdata.useIncludeFixed = useIncludeFixed ? 1 : 0; gdata.stiffness.Setup(stiffness); groupList[group] = gdata; } //========================================================================================= /// /// 拘束の解決 /// /// /// /// public override JobHandle SolverConstraint(int runCount, float dtime, float updatePower, int iteration, JobHandle jobHandle) { if (groupList.Count == 0) return jobHandle; // ステップ1:ベンドの計算 var job = new TriangleBendCalcJob() { updatePower = updatePower, runCount = runCount, triangleBendGroupDataList = groupList.ToJobArray(), triangleBendList = dataList.ToJobArray(), groupIndexList = groupIndexList.ToJobArray(), teamDataList = Manager.Team.teamDataList.ToJobArray(), nextPosList = Manager.Particle.InNextPosList.ToJobArray(), basePosList = Manager.Particle.basePosList.ToJobArray(), writeBuffer = writeBuffer.ToJobArray(), }; jobHandle = job.Schedule(dataList.Length, 64, jobHandle); // ステップ2:ベンド結果の集計 var job2 = new TriangleBendSumJob() { runCount = runCount, triangleBendGroupDataList = 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(), posList = Manager.Particle.posList.ToJobArray(), }; jobHandle = job2.Schedule(Manager.Particle.Length, 64, jobHandle); return jobHandle; } [BurstCompile] struct TriangleBendCalcJob : IJobParallelFor { public float updatePower; public int runCount; [Unity.Collections.ReadOnly] public NativeArray triangleBendGroupDataList; [Unity.Collections.ReadOnly] public NativeArray triangleBendList; [Unity.Collections.ReadOnly] public NativeArray groupIndexList; [Unity.Collections.ReadOnly] public NativeArray teamDataList; [Unity.Collections.ReadOnly] public NativeArray nextPosList; [Unity.Collections.ReadOnly] public NativeArray basePosList; [Unity.Collections.WriteOnly] [NativeDisableParallelForRestriction] public NativeArray writeBuffer; // ベンドデータごと public void Execute(int index) { var data = triangleBendList[index]; if (data.IsValid() == false) return; int gindex = groupIndexList[index]; var gdata = triangleBendGroupDataList[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; float3 corr2 = 0; float3 corr3 = 0; int pindex0 = data.vindex0 + pstart; int pindex1 = data.vindex1 + pstart; int pindex2 = data.vindex2 + pstart; int pindex3 = data.IsPositionBend() ? -1 : (data.vindex3 + pstart); float3 nextpos0 = nextPosList[pindex0]; float3 nextpos1 = nextPosList[pindex1]; float3 nextpos2 = nextPosList[pindex2]; float3 nextpos3 = data.IsPositionBend() ? 0 : nextPosList[pindex3]; // 厳密正 //bool isStrict = data.IsStrict(); bool isStrict = gdata.algorithm == 1; // 復元率 float stiffness = gdata.stiffness.Evaluate(data.depth); stiffness = (1.0f - math.pow(1.0f - stiffness, updatePower)); // 復元方法判定 if (data.IsPositionBend() == false) { // トライアングルベンド float3 e = nextpos3 - nextpos2; float elen = math.length(e); if (elen > 1e-06f) { float invElen = 1.0f / elen; float3 n1 = math.cross(nextpos2 - nextpos0, nextpos3 - nextpos0); float n1_lsq = math.lengthsq(n1); float3 n2 = math.cross(nextpos3 - nextpos1, nextpos2 - nextpos1); float n2_lsq = math.lengthsq(n2); if (n1_lsq > 0 && n2_lsq > 0) // v1.12.0 { n1 /= n1_lsq; n2 /= n2_lsq; float3 d0 = elen * n1; float3 d1 = elen * n2; float3 d2 = math.dot(nextpos0 - nextpos3, e) * invElen * n1 + math.dot(nextpos1 - nextpos3, e) * invElen * n2; float3 d3 = math.dot(nextpos2 - nextpos0, e) * invElen * n1 + math.dot(nextpos2 - nextpos1, e) * invElen * n2; n1 = math.normalize(n1); n2 = math.normalize(n2); float dot = math.dot(n1, n2); dot = math.clamp(dot, -1.0f, 1.0f); float phi = math.acos(dot); float lambda = math.lengthsq(d0) + math.lengthsq(d1) + math.lengthsq(d2) + math.lengthsq(d3); // 方向性 float direction = math.dot(math.cross(n1, n2), e); // Strictでは方向を考慮した角度で計算 phi = math.select(phi, phi * math.sign(direction), isStrict); // 安定性 float rest = math.abs(phi - data.restAngle); //if (stiffness > 0.5f && rest > 1.5f) // stiffness = 0.5f; // 角度による安定化 if (isStrict) { // 復元角度が大きいほどstiffnessが強くなる float ratio = math.max(math.pow(math.saturate(rest / math.PI), 0.5f), 0.1f); stiffness *= ratio; } lambda = (phi - data.restAngle) / lambda * stiffness; // 旧来処理 lambda = math.select(lambda * math.sign(direction), lambda, isStrict); corr0 = lambda * d0; corr1 = lambda * d1; corr2 = lambda * d2; corr3 = lambda * d3; } } } // v1.11.0では一旦オミット //else //{ // // 固定頂点を含む特殊復元 // if (gdata.useIncludeFixed == 1) // { // // 単純にベース位置に復元させる // stiffness *= 0.2f; // 調整 // float3 basepos0 = basePosList[pindex0]; // float3 basepos1 = basePosList[pindex1]; // float3 basepos2 = basePosList[pindex2]; // corr0 = (basepos0 - nextpos0) * stiffness; // corr1 = (basepos1 - nextpos1) * stiffness; // corr2 = (basepos2 - nextpos2) * stiffness; // // todo:Interlockに切り替わった場合はここで処理を抜ける // } //} // 作業バッファへ格納 int wstart = gdata.writeDataChunk.startIndex; int windex0 = data.writeIndex0 + wstart; int windex1 = data.writeIndex1 + wstart; int windex2 = data.writeIndex2 + wstart; writeBuffer[windex0] = corr0; writeBuffer[windex1] = corr1; writeBuffer[windex2] = corr2; if (data.IsPositionBend() == false) { int windex3 = data.writeIndex3 + wstart; writeBuffer[windex3] = corr3; } } } [BurstCompile] struct TriangleBendSumJob : IJobParallelFor { public int runCount; [Unity.Collections.ReadOnly] public NativeArray triangleBendGroupDataList; [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 posList; // パーティクルごと 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.triangleBendGroupIndex < 0) return; // 更新確認 if (team.IsUpdate(runCount) == false) return; // グループデータ var gdata = triangleBendGroupDataList[team.triangleBendGroupIndex]; 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; // 速度影響(Strictに合わせる) var av = corr * (1.0f - Define.Compute.TriangleBendVelocityInfluence); // 実験結果より(0.5) posList[pindex] = posList[pindex] + av; } } } } }