// Magica Cloth.
// Copyright (c) MagicaSoft, 2020-2022.
// https://magicasoft.jp
// どうも入れないほうが良さげ
//#define normalizeStretch
//#define normalizeShear // どうも安定しない
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
namespace MagicaCloth
{
///
/// ボリューム拘束
///
public class VolumeConstraint : PhysicsManagerConstraint
{
///
/// 拘束データ
/// todo:共有化可能
///
[System.Serializable]
public struct VolumeData
{
///
/// ボリューム形成パーティクルインデックスx4
///
public int vindex0;
public int vindex1;
public int vindex2;
public int vindex3;
///
/// ボリューム行列
///
public float3x3 ivMat;
///
/// ベンド影響を取得するデプス値(0.0-1.0)
///
public float depth;
///
/// 方向性拘束(0-1-2)トライアングルに対しての(3)の方向
/// (0=拘束なし,1=正方向,-1=負方向)
///
public int direction;
///
/// 書き込みバッファインデックス
///
public int writeIndex0;
public int writeIndex1;
public int writeIndex2;
public int writeIndex3;
///
/// データが有効か判定する
///
///
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;
///
/// 伸縮率(0.0-1.0)
///
public CurveParam stretchStiffness;
///
/// せん断率(0.0-1.0)
///
public CurveParam shearStiffness;
///
/// データチャンク
///
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, BezierParam stretchStiffness, BezierParam shearStiffness, VolumeData[] 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.stretchStiffness.Setup(stretchStiffness);
gdata.shearStiffness.Setup(shearStiffness);
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.volumeGroupIndex;
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 stretchStiffness, BezierParam shearStiffness)
{
var teamData = MagicaPhysicsManager.Instance.Team.teamDataList[teamId];
int group = teamData.volumeGroupIndex;
if (group < 0)
return;
var gdata = groupList[group];
gdata.active = active ? 1 : 0;
gdata.stretchStiffness.Setup(stretchStiffness);
gdata.shearStiffness.Setup(shearStiffness);
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 int GetIterationCount()
{
return base.GetIterationCount();
}
///
/// 拘束の解決
///
///
///
///
public override JobHandle SolverConstraint(int runCount, float dtime, float updatePower, int iteration, JobHandle jobHandle)
{
if (groupList.Count == 0)
return jobHandle;
// ステップ1:ベンドの計算
var job = new VolumeCalcJob()
{
runCount = runCount,
updatePower = updatePower,
groupDataList = groupList.ToJobArray(),
dataList = dataList.ToJobArray(),
groupIndexList = groupIndexList.ToJobArray(),
teamDataList = Manager.Team.teamDataList.ToJobArray(),
//flagList = Manager.Particle.flagList.ToJobArray(),
nextPosList = Manager.Particle.InNextPosList.ToJobArray(),
writeBuffer = writeBuffer.ToJobArray(),
};
jobHandle = job.Schedule(dataList.Length, 64, jobHandle);
// ステップ2:ベンド結果の集計
var job2 = new VolumeSumJob()
{
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(),
};
jobHandle = job2.Schedule(Manager.Particle.Length, 64, jobHandle);
return jobHandle;
}
[BurstCompile]
struct VolumeCalcJob : 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 NativeArray teamDataList;
//[Unity.Collections.ReadOnly]
//public NativeArray flagList;
[Unity.Collections.ReadOnly]
public NativeArray nextPosList;
[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;
float3 corr2 = 0;
float3 corr3 = 0;
int pindex0 = data.vindex0 + pstart;
int pindex1 = data.vindex1 + pstart;
int pindex2 = data.vindex2 + pstart;
int pindex3 = data.vindex3 + pstart;
float3 nextpos0 = nextPosList[pindex0];
float3 nextpos1 = nextPosList[pindex1];
float3 nextpos2 = nextPosList[pindex2];
float3 nextpos3 = nextPosList[pindex3];
// 復元率
float stretchStiffness = (1.0f - math.pow(1.0f - gdata.stretchStiffness.Evaluate(data.depth), updatePower));
float shearStiffness = (1.0f - math.pow(1.0f - gdata.shearStiffness.Evaluate(data.depth), updatePower));
// 方向性拘束
float3 add3 = 0;
#if false
if (data.direction != 0)
{
var v1 = nextpos1 - nextpos0;
var v2 = nextpos2 - nextpos0;
//var v3 = nextpos3 - nextpos0;
var n = math.normalize(math.cross(v1, v2));
n *= data.direction; // 方向
var n0 = nextpos0 + n * 0.005f; // 厚み
//var n0 = nextpos0 + n * 0.001f; // 厚み
var v3 = nextpos3 - n0;
float d = math.dot(n, v3);
if (d < 0.0f)
{
//float3 add = n * -d;
float3 add = n * (-d * stretchStiffness);
//add3 = add;
//nextpos3 += add3;
//corr3 += add;
add *= 0.5f;
corr0 -= add;
corr1 -= add;
corr2 -= add;
corr3 += add;
}
}
#endif
// ボリューム拘束
float3x3 ivMat = data.ivMat;
//float3[] c = new float3[3];
float3x3 c = 0;
c[0] = ivMat.c0;
c[1] = ivMat.c1;
c[2] = ivMat.c2;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j <= i; j++)
{
float3x3 P;
P.c0 = (nextpos1 + corr1) - (nextpos0 + corr0); // Gauss - Seidel
P.c1 = (nextpos2 + corr2) - (nextpos0 + corr0);
P.c2 = (nextpos3 + corr3) - (nextpos0 + corr0);
float3 fi = math.mul(P, c[i]);
float3 fj = math.mul(P, c[j]);
//float3 fi = math.mul(P, ivMat[i]);
//float3 fj = math.mul(P, ivMat[j]);
float Sij = math.dot(fi, fj);
#if normalizeShear
float wi = 0, wj = 0, s1 = 0, s3 = 0;
//if (normalizeShear && i != j)
if (i != j)
{
wi = math.length(fi);
wj = math.length(fj);
s1 = 1.0f / (wi * wj);
s3 = s1 * s1 * s1;
}
#endif
//float3[] d = new float3[4];
//d[0] = 0;
//for (int k = 0; k < 3; k++)
//{
// d[k + 1] = fj * ivMat[i][k] + fi * ivMat[j][k];
// //if (normalizeShear && i != j)
// if (i != j)
// {
// d[k + 1] = s1 * d[k + 1] - Sij * s3 * (wj * wj * fi * ivMat[i][k] + wi * wi * fj * ivMat[j][k]);
// }
// d[0] -= d[k + 1];
//}
float3x4 d = 0;
d[0] = 0;
for (int k = 0; k < 3; k++)
{
d[k + 1] = fj * ivMat[i][k] + fi * ivMat[j][k];
#if normalizeShear
//if (normalizeShear && i != j)
if (i != j)
{
d[k + 1] = s1 * d[k + 1] - Sij * s3 * (wj * wj * fi * ivMat[i][k] + wi * wi * fj * ivMat[j][k]);
}
#endif
d[0] -= d[k + 1];
}
#if normalizeShear
//if (normalizeShear && i != j)
if (i != j)
Sij *= s1;
#endif
//float lambda =
// invMass0 * math.lengthsq(d[0]) +
// invMass1 * math.lengthsq(d[1]) +
// invMass2 * math.lengthsq(d[2]) +
// invMass3 * math.lengthsq(d[3]);
float lambda =
math.lengthsq(d[0]) +
math.lengthsq(d[1]) +
math.lengthsq(d[2]) +
math.lengthsq(d[3]);
if (math.abs(lambda) < 1e-6f) // foo: threshold should be scale dependent
continue;
if (i == j)
{ // diagonal, stretch
#if normalizeStretch
float s = math.sqrt(Sij);
//lambda = 2.0f * s * (s - 1.0f) / lambda * stretchStiffness[i];
lambda = 2.0f * s * (s - 1.0f) / lambda * stretchStiffness;
#else
//lambda = (Sij - 1.0f) / lambda * stretchStiffness[i];
lambda = (Sij - 1.0f) / lambda * stretchStiffness;
#endif
}
else
{ // off diagonal, shear
//lambda = Sij / lambda * shearStiffness[i + j - 1];
lambda = Sij / lambda * shearStiffness;
}
//corr0 -= lambda * invMass0 * d[0];
//corr1 -= lambda * invMass1 * d[1];
//corr2 -= lambda * invMass2 * d[2];
//corr3 -= lambda * invMass3 * d[3];
corr0 -= lambda * d[0];
corr1 -= lambda * d[1];
corr2 -= lambda * d[2];
corr3 -= lambda * d[3];
}
}
// 作業バッファへ格納
int wstart = gdata.writeDataChunk.startIndex;
int windex0 = data.writeIndex0 + wstart;
int windex1 = data.writeIndex1 + wstart;
int windex2 = data.writeIndex2 + wstart;
int windex3 = data.writeIndex3 + wstart;
writeBuffer[windex0] = corr0;
writeBuffer[windex1] = corr1;
writeBuffer[windex2] = corr2;
writeBuffer[windex3] = corr3 + add3;
}
}
[BurstCompile]
struct VolumeSumJob : 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 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.volumeGroupIndex < 0)
return;
// 更新確認
if (team.IsUpdate(runCount) == false)
return;
// グループデータ
var gdata = groupDataList[team.volumeGroupIndex];
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;
}
}
}
}
}