2026-06-20 뉴페어리

This commit is contained in:
2026-06-20 16:46:29 +09:00
parent 833dd50fa9
commit 295e284dd0
849 changed files with 116860 additions and 2 deletions

View File

@@ -0,0 +1,77 @@
// Magica Cloth 2.
// Copyright (c) 2023 MagicaSoft.
// https://magicasoft.jp
using Unity.Mathematics;
using UnityEngine;
namespace MagicaCloth2
{
/// <summary>
/// Configuration data for reduction.
/// リダクション用の設定データ
/// </summary>
[System.Serializable]
public class ReductionSettings : IDataValidate
{
/// <summary>
/// Simple distance reduction (% of AABB maximum distance) (0.0 ~ 1.0).
/// 単純な距離による削減(AABB最大距離の%)(0.0 ~ 1.0)
/// [NG] Runtime changes.
/// [NG] Export/Import with Presets
/// </summary>
[Range(0.0f, 0.2f)]
public float simpleDistance = 0.0f;
/// <summary>
/// Reduction by distance considering geometry (% of AABB maximum distance) (0.0 ~ 1.0).
/// 形状を考慮した距離による削減(AABB最大距離の%)(0.0 ~ 1.0)
/// [NG] Runtime changes.
/// [NG] Export/Import with Presets
/// </summary>
[Range(0.0f, 0.2f)]
public float shapeDistance = 0.0f;
//=========================================================================================
public bool IsEnabled => Define.System.ReductionEnable;
public float GetMaxConnectionDistance()
{
return math.max(math.max(Define.System.ReductionSameDistance, simpleDistance), shapeDistance);
}
public ReductionSettings Clone()
{
return new ReductionSettings()
{
simpleDistance = simpleDistance,
shapeDistance = shapeDistance,
};
}
public void DataValidate()
{
simpleDistance = Mathf.Clamp(simpleDistance, 0.0f, 0.2f);
shapeDistance = Mathf.Clamp(shapeDistance, 0.0f, 0.2f);
}
/// <summary>
/// エディタメッシュの更新を判定するためのハッシュコード
/// (このハッシュは実行時には利用されない編集用のもの)
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
int hash = 0;
hash += simpleDistance.GetHashCode();
hash += shapeDistance.GetHashCode();
return hash;
}
public override string ToString()
{
return $"ReductionSettings. sameDist:{Define.System.ReductionSameDistance}, simpleDist:{simpleDistance}, shapeDist:{shapeDistance} maxStep:{Define.System.ReductionMaxStep}";
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 143de5f6b100ea74a87486d4d796dd38
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 242307
packageName: Magica Cloth 2
packageVersion: 2.18.1
assetPath: Assets/MagicaCloth2/Scripts/Core/Reduction/ReductionSettings.cs
uploadId: 893596

View File

@@ -0,0 +1,141 @@
// Magica Cloth 2.
// Copyright (c) 2023 MagicaSoft.
// https://magicasoft.jp
using System;
using Unity.Collections;
using Unity.Mathematics;
namespace MagicaCloth2
{
public class ReductionWorkData : IDisposable
{
public VirtualMesh vmesh;
//=========================================================================================
// リダクション作業データ
//=========================================================================================
/// <summary>
/// 頂点の結合先インデックス(-1=生存している, 0以上=削除されている)
/// </summary>
public NativeArray<int> vertexJoinIndices;
/// <summary>
/// 頂点ごとの接続頂点インデックスリスト
/// </summary>
public NativeParallelMultiHashMap<ushort, ushort> vertexToVertexMap;
//=========================================================================================
// メッシュ最適化作業データ
//=========================================================================================
/// <summary>
/// 古い頂点インデックスから新しい頂点インデックスへの変換リスト
/// </summary>
public NativeArray<int> vertexRemapIndices;
/// <summary>
/// スキニングボーンのインデックス変換辞書
/// キー:最適化前のボーンインデックス、データ:最適化後のボーンインデックス
/// </summary>
public NativeParallelHashMap<int, int> useSkinBoneMap;
/// <summary>
/// 新しい頂点の頂点接続リスト
/// </summary>
public NativeParallelMultiHashMap<ushort, ushort> newVertexToVertexMap;
public NativeParallelHashSet<int2> edgeSet;
public NativeParallelHashSet<int3> triangleSet;
//=========================================================================================
// 最終的なメッシュデータ
//=========================================================================================
public int oldVertexCount;
public int newVertexCount;
public int removeVertexCount;
public ExSimpleNativeArray<VertexAttribute> newAttributes;
public ExSimpleNativeArray<float3> newLocalPositions;
public ExSimpleNativeArray<float3> newLocalNormals;
public ExSimpleNativeArray<float3> newLocalTangents;
public ExSimpleNativeArray<float2> newUv;
public ExSimpleNativeArray<VirtualMeshBoneWeight> newBoneWeights;
public NativeReference<int> newSkinBoneCount;
public NativeList<int> newSkinBoneTransformIndices;
public NativeList<float4x4> newSkinBoneBindPoseList;
public NativeList<int2> newLineList;
public NativeList<int3> newTriangleList;
//=========================================================================================
public ReductionWorkData(VirtualMesh vmesh)
{
this.vmesh = vmesh;
}
public void Dispose()
{
if (vertexJoinIndices.IsCreated)
vertexJoinIndices.Dispose();
if (vertexToVertexMap.IsCreated)
vertexToVertexMap.Dispose();
if (vertexRemapIndices.IsCreated)
vertexRemapIndices.Dispose();
if (useSkinBoneMap.IsCreated)
useSkinBoneMap.Dispose();
if (newVertexToVertexMap.IsCreated)
newVertexToVertexMap.Dispose();
if (edgeSet.IsCreated)
edgeSet.Dispose();
if (triangleSet.IsCreated)
triangleSet.Dispose();
// 最終メッシュデータ
newAttributes?.Dispose();
newLocalPositions?.Dispose();
newLocalNormals?.Dispose();
newLocalTangents?.Dispose();
newUv?.Dispose();
newBoneWeights?.Dispose();
if (newSkinBoneCount.IsCreated)
newSkinBoneCount.Dispose();
if (newSkinBoneTransformIndices.IsCreated)
newSkinBoneTransformIndices.Dispose();
if (newSkinBoneBindPoseList.IsCreated)
newSkinBoneBindPoseList.Dispose();
if (newLineList.IsCreated)
newLineList.Dispose();
if (newTriangleList.IsCreated)
newTriangleList.Dispose();
}
#if false
public void DebugVerify()
{
// vertexToVertexMap
//using var keyArray = vertexToVertexMap.GetKeyArray(Allocator.TempJob);
//foreach (var key in keyArray)
//{
// int cnt = vertexToVertexMap.CountValuesForKey(key);
// if (cnt == 0)
// {
// Develop.DebugLogWarning($"vertexToVertexMap. no data! :{key}");
// }
//}
}
public void DebugInfo()
{
// vertexToVertexMap
//using var keyArray = vertexToVertexMap.GetKeyArray(Allocator.TempJob);
//foreach (var key in keyArray)
//{
// int cnt = vertexToVertexMap.CountValuesForKey(key);
// Develop.DebugLog($"vertexToVertexMap [{key}] cnt:{cnt}");
//}
}
#endif
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 96acdb752b9af6746a6eb571598ca2dd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 242307
packageName: Magica Cloth 2
packageVersion: 2.18.1
assetPath: Assets/MagicaCloth2/Scripts/Core/Reduction/ReductionWorkData.cs
uploadId: 893596

View File

@@ -0,0 +1,560 @@
// Magica Cloth 2.
// Copyright (c) 2023 MagicaSoft.
// https://magicasoft.jp
using System;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
#if MAGICACLOTH2_REDUCTION_DEBUG
using UnityEngine;
#endif
namespace MagicaCloth2
{
/// <summary>
/// 距離内のすべての頂点を一度に結合させる
/// </summary>
public class SameDistanceReduction : IDisposable
{
string name = string.Empty;
VirtualMesh vmesh;
ReductionWorkData workData;
ResultCode result;
float mergeLength;
//=========================================================================================
GridMap<int> gridMap;
//NativeParallelHashSet<int2> joinPairSet;
NativeParallelMultiHashMap<ushort, ushort> joinPairMap;
NativeReference<int> resultRef;
//=========================================================================================
public SameDistanceReduction() { }
public SameDistanceReduction(
string name,
VirtualMesh mesh,
ReductionWorkData workingData,
float mergeLength
)
{
this.name = name;
this.vmesh = mesh;
this.workData = workingData;
this.result = ResultCode.None;
this.mergeLength = math.max(mergeLength, 1e-09f);
}
public virtual void Dispose()
{
//if (joinPairSet.IsCreated)
// joinPairSet.Dispose();
if (joinPairMap.IsCreated)
joinPairMap.Dispose();
if (resultRef.IsCreated)
resultRef.Dispose();
gridMap?.Dispose();
}
public ResultCode Result => result;
//=========================================================================================
/// <summary>
/// リダクション実行(スレッド可)
/// </summary>
/// <returns></returns>
public ResultCode Reduction()
{
//bool success = false;
result.Clear();
try
{
// グリッドマップ
gridMap = new GridMap<int>(vmesh.VertexCount);
// 最適なグリッドサイズを割り出す(mergeLengthは>0が保証されている)
float gridSize = mergeLength * 2.0f;
// 頂点ごとの接続マップ
// インデックスが若い方が記録する
//joinPairSet = new NativeParallelHashSet<int2>(vmesh.VertexCount / 4, Allocator.Persistent);
joinPairMap = new NativeParallelMultiHashMap<ushort, ushort>(vmesh.VertexCount, Allocator.Persistent);
resultRef = new NativeReference<int>(Allocator.Persistent);
// ポイントをグリッドに登録
var initGridJob = new InitGridJob()
{
vcnt = vmesh.VertexCount,
gridSize = gridSize,
localPositions = vmesh.localPositions.GetNativeArray(),
joinIndices = workData.vertexJoinIndices,
gridMap = gridMap.GetMultiHashMap(),
};
initGridJob.Run();
// 近傍頂点を検索、結合マップに登録する
var searchJoinJob = new SearchJoinJob()
{
vcnt = vmesh.VertexCount,
gridSize = gridSize,
radius = mergeLength,
localPositions = vmesh.localPositions.GetNativeArray(),
joinIndices = workData.vertexJoinIndices,
gridMap = gridMap.GetMultiHashMap(),
//joinPairSet = joinPairSet,
joinPairMap = joinPairMap,
};
searchJoinJob.Run();
// 結合する
#if false
var joinJob = new JoinJob()
{
vertexCount = vmesh.VertexCount,
//joinPairSet = joinPairSet,
joinPairMap = joinPairMap,
joinIndices = workData.vertexJoinIndices,
vertexToVertexMap = workData.vertexToVertexMap,
boneWeights = vmesh.boneWeights.GetNativeArray(),
attributes = vmesh.attributes.GetNativeArray(),
result = resultRef,
};
joinJob.Run();
#endif
#if true
// 高速化および詳細メッシュがある場合に1つの頂点に大量に結合しオーバーフローが発生する問題を回避したもの
// 以前はA->B->C結合時にA->C間が接続距離以上でもA->Cが結合されてしまったが、この改良版ではそれがおこならない
// そのため以前とは結果がことなることに注意!
using var tempList = new NativeList<ushort>(2048, Allocator.Persistent);
var joinJob2 = new JoinJob2()
{
vertexCount = vmesh.VertexCount,
joinPairMap = joinPairMap,
joinIndices = workData.vertexJoinIndices,
vertexToVertexMap = workData.vertexToVertexMap,
boneWeights = vmesh.boneWeights.GetNativeArray(),
attributes = vmesh.attributes.GetNativeArray(),
result = resultRef,
tempList = tempList,
};
joinJob2.Run();
#endif
// 頂点の接続状態を最新に更新する。すべて最新の生存ポイントを指すように変更する
UpdateJoinAndLink();
// 頂点情報を整理する
// 法線単位化
// ボーンウエイトを1に平均化
UpdateReductionResultJob();
// 削除頂点数集計
int removeVertexCount = resultRef.Value;
workData.removeVertexCount += removeVertexCount;
//Debug.Log($"[SameDistanceReduction [{name}]] mergeLength:{mergeLength} RemoveVertex:{removeVertexCount}");
// 完了
//success = true;
result.SetSuccess();
}
catch (Exception exception)
{
Debug.LogError(exception);
result.SetError(Define.Result.Reduction_SameDistanceException);
}
finally
{
// 作業バッファを解放する(重要)
// ★仮にタスクが例外やキャンセルされたとしてもこれで作成したバッファは正しくDispose()される
//MagicaManager.Discard.Add();
// 登録したジョブを解除する(重要)
// ★仮にタスクが例外やキャンセルされたとしてもこれで発行したJobは正しくComplete()される
//MagicaManager.Thread.DisposeJob(int);
}
return result;
}
//=========================================================================================
[BurstCompile]
struct InitGridJob : IJob
{
public int vcnt;
public float gridSize;
[Unity.Collections.ReadOnly]
public NativeArray<float3> localPositions;
[Unity.Collections.ReadOnly]
public NativeArray<int> joinIndices;
public NativeParallelMultiHashMap<int3, int> gridMap;
// 頂点ごと
public void Execute()
{
for (int vindex = 0; vindex < vcnt; vindex++)
{
if (joinIndices[vindex] >= 0)
continue; // isDelete
GridMap<int>.AddGrid(localPositions[vindex], vindex, gridMap, gridSize);
}
}
}
[BurstCompile]
struct SearchJoinJob : IJob
{
public int vcnt;
public float gridSize;
public float radius;
[Unity.Collections.ReadOnly]
public NativeArray<float3> localPositions;
[Unity.Collections.ReadOnly]
public NativeArray<int> joinIndices;
[Unity.Collections.ReadOnly]
public NativeParallelMultiHashMap<int3, int> gridMap;
//public NativeParallelHashSet<int2> joinPairSet;
public NativeParallelMultiHashMap<ushort, ushort> joinPairMap;
public void Execute()
{
for (int vindex = 0; vindex < vcnt; vindex++)
{
if (joinIndices[vindex] >= 0)
continue; // isDelete
float3 pos = localPositions[vindex];
// 範囲グリッド走査
foreach (int3 grid in GridMap<int>.GetArea(pos, radius, gridMap, gridSize))
{
if (gridMap.ContainsKey(grid) == false)
continue;
// このグリッドを検索する
foreach (int tvindex in gridMap.GetValuesForKey(grid))
{
// 自身は弾く
if (tvindex == vindex)
continue;
// 距離判定
float3 tpos = localPositions[tvindex];
float dist = math.distance(pos, tpos);
if (dist > radius)
continue;
// 結合登録
//int2 ehash = DataUtility.PackInt2(vindex, tvindex);
//joinPairSet.Add(ehash);
joinPairMap.Add((ushort)vindex, (ushort)tvindex);
}
}
}
}
}
[BurstCompile]
struct JoinJob2 : IJob
{
public int vertexCount;
[Unity.Collections.ReadOnly]
public NativeParallelMultiHashMap<ushort, ushort> joinPairMap;
public NativeArray<int> joinIndices;
public NativeParallelMultiHashMap<ushort, ushort> vertexToVertexMap;
public NativeArray<VirtualMeshBoneWeight> boneWeights;
public NativeArray<VertexAttribute> attributes;
public NativeReference<int> result;
public NativeList<ushort> tempList;
public void Execute()
{
int cnt = 0;
for (ushort vindexLive = 0; vindexLive < vertexCount; vindexLive++)
{
// すでに結合済みならスキップ
if (joinIndices[vindexLive] >= 0)
continue;
// 対象ループ
foreach (ushort vindexDead in joinPairMap.GetValuesForKey(vindexLive))
{
// 対象がすでに結合ずみならスキップ
if (joinIndices[vindexDead] >= 0)
continue;
// 結合(vertexDead -> vertexLive)
joinIndices[vindexDead] = vindexLive;
cnt++;
vertexToVertexMap.MC2RemoveValue(vindexLive, vindexDead);
tempList.Clear();
foreach (ushort i in vertexToVertexMap.GetValuesForKey(vindexDead))
{
tempList.Add(i);
}
foreach (ushort i in tempList)
{
if (joinIndices[i] >= 0)
continue;
if (i == vindexLive || i == vindexDead)
continue;
vertexToVertexMap.MC2RemoveValue(i, vindexDead);
vertexToVertexMap.MC2UniqueAdd(vindexLive, i);
vertexToVertexMap.MC2UniqueAdd(i, vindexLive);
// p2にBoneWeightを結合
var bw = boneWeights[vindexLive];
bw.AddWeight(boneWeights[vindexDead]);
boneWeights[vindexLive] = bw;
// 属性
var attr1 = attributes[vindexDead];
var attr2 = attributes[vindexLive];
attributes[vindexLive] = VertexAttribute.JoinAttribute(attr1, attr2);
attributes[vindexDead] = VertexAttribute.Invalid; // 削除頂点は無効にする
}
}
}
// 削除頂点数記録
result.Value = cnt;
}
}
#if false // old
[BurstCompile]
struct JoinJob : IJob
{
[Unity.Collections.ReadOnly]
public NativeParallelHashSet<int2> joinPairSet;
public NativeArray<int> joinIndices;
public NativeParallelMultiHashMap<ushort, ushort> vertexToVertexMap;
public NativeArray<VirtualMeshBoneWeight> boneWeights;
public NativeArray<VertexAttribute> attributes;
public NativeReference<int> result;
public void Execute()
{
var workSet = new FixedList512Bytes<ushort>();
int cnt = 0;
foreach (var ehash in joinPairSet)
{
int vindexLive = ehash[0]; // 生存側
int vindexDead = ehash[1]; // 削除側
while (joinIndices[vindexDead] >= 0)
{
vindexDead = joinIndices[vindexDead];
}
while (joinIndices[vindexLive] >= 0)
{
vindexLive = joinIndices[vindexLive];
}
if (vindexDead == vindexLive)
continue;
// 結合(vertex1 -> vertex2)
joinIndices[vindexDead] = vindexLive;
cnt++;
// 接続数を結合する(重複は弾かれる)
workSet.Clear();
foreach (ushort i in vertexToVertexMap.GetValuesForKey((ushort)vindexDead))
{
int index = i;
// 生存インデックス
while (joinIndices[index] >= 0)
{
index = joinIndices[index];
}
if (index != vindexDead && index != vindexLive)
workSet.MC2Set((ushort)index);
}
foreach (ushort i in vertexToVertexMap.GetValuesForKey((ushort)vindexLive))
{
int index = i;
// 生存インデックス
while (joinIndices[index] >= 0)
{
index = joinIndices[index];
}
if (index != vindexDead && index != vindexLive)
workSet.MC2Set((ushort)index);
}
vertexToVertexMap.Remove((ushort)vindexLive);
for (int i = 0; i < workSet.Length; i++)
{
vertexToVertexMap.Add((ushort)vindexLive, workSet[i]);
}
//Debug.Assert(workSet.Length > 0);
// p2にBoneWeightを結合
var bw = boneWeights[vindexLive];
bw.AddWeight(boneWeights[vindexDead]);
boneWeights[vindexLive] = bw;
// 属性
var attr1 = attributes[vindexDead];
var attr2 = attributes[vindexLive];
attributes[vindexLive] = VertexAttribute.JoinAttribute(attr1, attr2);
attributes[vindexDead] = VertexAttribute.Invalid; // 削除頂点は無効にする
}
// 削除頂点数記録
result.Value = cnt;
}
}
#endif
//=========================================================================================
/// <summary>
/// 接続状態を最新に更新するジョブを発行する
/// </summary>
/// <param name="jobHandle"></param>
/// <returns></returns>
void UpdateJoinAndLink()
{
// JoinIndexの状態を更新する。現在の最新の生存ポイントを指すように変更する
var updateJoinIndexJob = new UpdateJoinIndexJob()
{
joinIndices = workData.vertexJoinIndices,
};
updateJoinIndexJob.Run(vmesh.VertexCount);
// 頂点の接続頂点リストを最新に更新する。すべて最新の生存ポイントを指すように変更する
var updateLinkIndexJob = new UpdateLinkIndexJob()
{
joinIndices = workData.vertexJoinIndices,
vertexToVertexMap = workData.vertexToVertexMap,
};
updateLinkIndexJob.Run(vmesh.VertexCount);
}
[BurstCompile]
struct UpdateJoinIndexJob : IJobParallelFor
{
[NativeDisableParallelForRestriction]
public NativeArray<int> joinIndices;
public void Execute(int vindex)
{
int join = joinIndices[vindex];
if (join >= 0)
{
// 削除されている
// 最終的な生存ポイントに連結させる
while (joinIndices[join] >= 0)
{
join = joinIndices[join];
}
joinIndices[vindex] = join;
}
}
}
[BurstCompile]
struct UpdateLinkIndexJob : IJobParallelFor
{
[NativeDisableParallelForRestriction]
public NativeArray<int> joinIndices;
public NativeParallelMultiHashMap<ushort, ushort> vertexToVertexMap;
public void Execute(int vindex)
{
int join = joinIndices[vindex];
// 自身が削除されている場合は無視
if (join >= 0)
return;
// 自身が生存している
// 現在の接続インデックスから削除されたものを生存インデックスに入れ替える
var newLinkSet = new FixedList512Bytes<ushort>();
foreach (ushort i in vertexToVertexMap.GetValuesForKey((ushort)vindex))
{
int tvindex = i;
int tjoin = joinIndices[tvindex];
if (tjoin >= 0)
{
// 削除されている
tvindex = tjoin;
Debug.Assert(joinIndices[tvindex] < 0);
}
// 自身は弾く
if (tvindex == vindex)
continue;
newLinkSet.MC2Set((ushort)tvindex);
}
// 生存のみの新しいセットに入れ替え
vertexToVertexMap.Remove((ushort)vindex);
for (int i = 0; i < newLinkSet.Length; i++)
{
vertexToVertexMap.Add((ushort)vindex, newLinkSet[i]);
}
//Debug.Assert(newLinkSet.Length > 0);
}
}
//=========================================================================================
/// <summary>
/// リダクション後のデータを整える
/// </summary>
void UpdateReductionResultJob()
{
// 頂点法線の単位化、およびボーンウエイトを1に整える
var finalVertexJob = new FinalMergeVertexJob()
{
joinIndices = workData.vertexJoinIndices,
localNormals = vmesh.localNormals.GetNativeArray(),
boneWeights = vmesh.boneWeights.GetNativeArray(),
};
finalVertexJob.Run(vmesh.VertexCount);
}
[BurstCompile]
struct FinalMergeVertexJob : IJobParallelFor
{
[Unity.Collections.ReadOnly]
public NativeArray<int> joinIndices;
public NativeArray<float3> localNormals;
public NativeArray<VirtualMeshBoneWeight> boneWeights;
// 頂点ごと
public void Execute(int vindex)
{
int join = joinIndices[vindex];
if (join >= 0)
{
// 削除されている
return;
}
// 法線単位化
localNormals[vindex] = math.normalize(localNormals[vindex]);
// ボーンウエイトを平均化
var bw = boneWeights[vindex];
bw.AdjustWeight();
boneWeights[vindex] = bw;
}
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 9a8f11c168bca19409d6e7cec1de9fc8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 242307
packageName: Magica Cloth 2
packageVersion: 2.18.1
assetPath: Assets/MagicaCloth2/Scripts/Core/Reduction/SameDistanceReduction.cs
uploadId: 893596

View File

@@ -0,0 +1,145 @@
// Magica Cloth 2.
// Copyright (c) 2023 MagicaSoft.
// https://magicasoft.jp
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
namespace MagicaCloth2
{
/// <summary>
/// 形状に合わせた頂点リダクション
/// このリダクションは頂点の接続状態に沿ってリダクションを行う
/// </summary>
public class ShapeDistanceReduction : StepReductionBase
{
//=========================================================================================
public ShapeDistanceReduction(
string name,
VirtualMesh mesh,
ReductionWorkData workingData,
float startMergeLength,
float endMergeLength,
int maxStep,
bool dontMakeLine,
float joinPositionAdjustment
)
: base($"ShapeReduction [{name}]", mesh, workingData, startMergeLength, endMergeLength, maxStep, dontMakeLine, joinPositionAdjustment)
{
}
public override void Dispose()
{
base.Dispose();
}
protected override void StepInitialize()
{
base.StepInitialize();
}
protected override void CustomReductionStep()
{
// 近傍ペアを検索、結合エッジリストに登録
var searchJob = new SearchJoinEdgeJob()
{
vcnt = vmesh.VertexCount,
radius = nowMergeLength,
dontMakeLine = dontMakeLine,
localPositions = vmesh.localPositions.GetNativeArray(),
joinIndices = workData.vertexJoinIndices,
//vertexToVertexArray = workData.vertexToVertexArray,
vertexToVertexMap = workData.vertexToVertexMap,
joinEdgeList = joinEdgeList,
};
searchJob.Run();
}
[BurstCompile]
struct SearchJoinEdgeJob : IJob
{
public int vcnt;
public float radius;
public bool dontMakeLine;
[Unity.Collections.ReadOnly]
public NativeArray<float3> localPositions;
[Unity.Collections.ReadOnly]
public NativeArray<int> joinIndices;
[Unity.Collections.ReadOnly]
public NativeParallelMultiHashMap<ushort, ushort> vertexToVertexMap;
//public NativeArray<ExFixedSet128Bytes<ushort>> vertexToVertexArray;
public NativeList<JoinEdge> joinEdgeList;
public void Execute()
{
for (int vindex = 0; vindex < vcnt; vindex++)
{
if (joinIndices[vindex] >= 0)
continue; // isDelete
// 接続頂点リスト
int vcnt = vertexToVertexMap.CountValuesForKey((ushort)vindex);
if (vcnt == 0)
continue; // 接続なし
//var vlist = vertexToVertexArray[vindex];
//if (vlist.Count == 0)
// continue; // 接続なし
float3 pos = localPositions[vindex];
// 自身の接続頂点数
//float linkCount = math.max(vlist.Count - 1, 1);
float linkCount = math.max(vcnt - 1, 1);
// 範囲内で最もコストが低い接続ペアを登録する
float minCost = float.MaxValue;
int minVindex = -1;
//for (int i = 0; i < vlist.Count; i++)
foreach (ushort tvindex in vertexToVertexMap.GetValuesForKey((ushort)vindex))
{
//int tvindex = vlist.Get(i);
// 距離判定
float3 tpos = localPositions[tvindex];
float dist = math.distance(pos, tpos);
if (dist > radius)
continue;
// 相手の接続頂点数
float tlinkCount = math.max(vertexToVertexMap.CountValuesForKey(tvindex) - 1, 1);
//var tvlist = vertexToVertexArray[tvindex];
//float tlinkCount = math.max(tvlist.Count - 1, 1);
// この頂点を結合して問題がないか調べる
//if (CheckJoin(vertexToVertexArray, vindex, tvindex, vlist, tvlist, dontMakeLine) == false)
if (CheckJoin2(vertexToVertexMap, vindex, tvindex, dontMakeLine) == false)
continue;
// コスト計算
float cost = dist * (1.0f + (linkCount + tlinkCount) / 2.0f); // とりあえず
// 最小コスト判定
if (cost < minCost)
{
minCost = cost;
minVindex = tvindex;
}
}
if (minVindex >= 0)
{
// 登録
var pair = new JoinEdge()
{
vertexPair = new int2(vindex, minVindex),
cost = minCost,
};
joinEdgeList.Add(pair);
}
}
}
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 2b8b32d329a0b2541afe170d131c5f76
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 242307
packageName: Magica Cloth 2
packageVersion: 2.18.1
assetPath: Assets/MagicaCloth2/Scripts/Core/Reduction/ShapeDistanceReduction.cs
uploadId: 893596

View File

@@ -0,0 +1,182 @@
// Magica Cloth 2.
// Copyright (c) 2023 MagicaSoft.
// https://magicasoft.jp
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
namespace MagicaCloth2
{
/// <summary>
/// 単純な頂点間の距離によるリダクション
/// このリダクションは頂点の接続状態を無視する
/// </summary>
public class SimpleDistanceReduction : StepReductionBase
{
/// <summary>
/// グリッドマップ
/// </summary>
private GridMap<int> gridMap;
//=========================================================================================
public SimpleDistanceReduction(
string name,
VirtualMesh mesh,
ReductionWorkData workingData,
float startMergeLength,
float endMergeLength,
int maxStep,
bool dontMakeLine,
float joinPositionAdjustment
)
: base($"SimpleDistanceReduction [{name}]", mesh, workingData, startMergeLength, endMergeLength, maxStep, dontMakeLine, joinPositionAdjustment)
{
}
public override void Dispose()
{
base.Dispose();
gridMap.Dispose();
}
protected override void StepInitialize()
{
base.StepInitialize();
gridMap = new GridMap<int>(vmesh.VertexCount);
}
protected override void CustomReductionStep()
{
// 最適なグリッドサイズを割り出す(nowMergeLengthは>0が保証されている)
float gridSize = nowMergeLength * 2.0f; // 1.5?
// 作業用バッファクリア
gridMap.GetMultiHashMap().Clear();
// ポイントをグリッドに登録
var initGridJob = new InitGridJob()
{
vcnt = vmesh.VertexCount,
gridSize = gridSize,
localPositions = vmesh.localPositions.GetNativeArray(),
joinIndices = workData.vertexJoinIndices,
gridMap = gridMap.GetMultiHashMap(),
};
initGridJob.Run();
// 近傍ポイントを検索、結合エッジリストに登録
var searchJob = new SearchJoinEdgeJob()
{
vcnt = vmesh.VertexCount,
gridSize = gridSize,
radius = nowMergeLength,
localPositions = vmesh.localPositions.GetNativeArray(),
joinIndices = workData.vertexJoinIndices,
vertexToVertexMap = workData.vertexToVertexMap, // cost用
gridMap = gridMap.GetMultiHashMap(),
joinEdgeList = joinEdgeList,
};
searchJob.Run();
}
[BurstCompile]
struct InitGridJob : IJob
{
public int vcnt;
public float gridSize;
[Unity.Collections.ReadOnly]
public NativeArray<float3> localPositions;
[Unity.Collections.ReadOnly]
public NativeArray<int> joinIndices;
public NativeParallelMultiHashMap<int3, int> gridMap;
// 頂点ごと
public void Execute()
{
for (int vindex = 0; vindex < vcnt; vindex++)
{
if (joinIndices[vindex] >= 0)
continue; // isDelete
GridMap<int>.AddGrid(localPositions[vindex], vindex, gridMap, gridSize);
}
}
}
[BurstCompile]
struct SearchJoinEdgeJob : IJob
{
public int vcnt;
public float gridSize;
public float radius;
[Unity.Collections.ReadOnly]
public NativeArray<float3> localPositions;
[Unity.Collections.ReadOnly]
public NativeArray<int> joinIndices;
[Unity.Collections.ReadOnly]
public NativeParallelMultiHashMap<ushort, ushort> vertexToVertexMap;
[Unity.Collections.ReadOnly]
public NativeParallelMultiHashMap<int3, int> gridMap;
[Unity.Collections.WriteOnly]
public NativeList<JoinEdge> joinEdgeList;
// 頂点ごと
public void Execute()
{
for (int vindex = 0; vindex < vcnt; vindex++)
{
if (joinIndices[vindex] >= 0)
continue; // isDelete
float3 pos = localPositions[vindex];
// 自身の接続頂点数
float linkCount = math.max(vertexToVertexMap.CountValuesForKey((ushort)vindex) - 1, 1);
// 範囲グリッド走査
foreach (int3 grid in GridMap<int>.GetArea(pos, radius, gridMap, gridSize))
{
if (gridMap.ContainsKey(grid) == false)
continue;
// このグリッドを検索する
foreach (int tindex in gridMap.GetValuesForKey(grid))
{
// 自身は弾く
if (tindex == vindex)
continue;
// 距離判定
float3 tpos = localPositions[tindex];
float dist = math.distance(pos, tpos);
if (dist > radius)
continue;
// 相手の接続頂点数
float tlinkCount = math.max(vertexToVertexMap.CountValuesForKey((ushort)tindex) - 1, 1);
// コスト計算
//float cost = dist / (linkCount + tlinkCount); // todo:とりあえずテスト
//float cost = dist * (1.0f / (linkCount + tlinkCount));
float cost = dist * (1.0f + (linkCount + tlinkCount) / 2.0f); // 12
// 全部登録
var pair = new JoinEdge()
{
vertexPair = new int2(vindex, tindex),
cost = cost,
};
joinEdgeList.Add(pair);
}
}
}
}
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: b244c982dfa4761478be41f4a264d73e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 242307
packageName: Magica Cloth 2
packageVersion: 2.18.1
assetPath: Assets/MagicaCloth2/Scripts/Core/Reduction/SimpleDistanceReduction.cs
uploadId: 893596

View File

@@ -0,0 +1,773 @@
// Magica Cloth 2.
// Copyright (c) 2023 MagicaSoft.
// https://magicasoft.jp
using System;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
#if MAGICACLOTH2_REDUCTION_DEBUG
using UnityEngine;
#endif
namespace MagicaCloth2
{
/// <summary>
/// 結合距離を拡大させながら段階的にリダクションする方式のベースクラス
/// 高速化のため結合候補のペアのうち頂点が被らないものを1回のステップですべて結合させる。
/// このため結合距離内にもかかわらず結合されないペアが発生することになる。
/// この問題は手順を複数可実行することで徐々に収束させて解決する。
/// </summary>
public abstract class StepReductionBase : IDisposable
{
protected string name = string.Empty;
protected VirtualMesh vmesh;
protected ReductionWorkData workData;
protected ResultCode result;
protected float startMergeLength;
protected float endMergeLength;
protected int maxStep;
protected bool dontMakeLine;
protected float joinPositionAdjustment;
protected int nowStepIndex;
protected float nowMergeLength;
protected float nowStepScale;
//=========================================================================================
/// <summary>
/// 結合エッジ情報
/// </summary>
public struct JoinEdge : IComparable<JoinEdge>
{
public int2 vertexPair;
public float cost;
public bool Contains(in int2 pair)
{
if (vertexPair.x == pair.x || vertexPair.x == pair.y || vertexPair.y == pair.x || vertexPair.y == pair.y)
return true;
else
return false;
}
public int CompareTo(JoinEdge other)
{
// コストの昇順
if (cost != other.cost)
return cost < other.cost ? -1 : 1;
else
return 0;
}
}
protected NativeList<JoinEdge> joinEdgeList;
// すでに結合された頂点セット
private NativeParallelHashSet<int> completeVertexSet;
// 結合された頂点ペア(x->yへ結合)
private NativeList<int2> removePairList;
// ステップごとの削減頂点数
private NativeArray<int> resultArray;
//=========================================================================================
public StepReductionBase() { }
public StepReductionBase(
string name,
VirtualMesh mesh,
ReductionWorkData workingData,
float startMergeLength,
float endMergeLength,
int maxStep,
bool dontMakeLine,
float joinPositionAdjustment
)
{
this.name = name;
this.vmesh = mesh;
this.workData = workingData;
this.result = ResultCode.None;
this.startMergeLength = math.max(startMergeLength, 1e-09f);
this.endMergeLength = math.max(endMergeLength, 1e-09f);
this.maxStep = math.min(maxStep, 100);
this.dontMakeLine = dontMakeLine;
this.joinPositionAdjustment = joinPositionAdjustment;
}
public virtual void Dispose()
{
if (joinEdgeList.IsCreated)
joinEdgeList.Dispose();
if (completeVertexSet.IsCreated)
completeVertexSet.Dispose();
if (removePairList.IsCreated)
removePairList.Dispose();
if (resultArray.IsCreated)
resultArray.Dispose();
}
public ResultCode Result => result;
//=========================================================================================
/// <summary>
/// リダクション実行(スレッド可)
/// </summary>
/// <returns></returns>
public ResultCode Reduction()
{
//bool success = false;
result.Clear();
try
{
// ステップ前初期化
StepInitialize();
// 開始範囲から終了範囲までを半径を拡大にしながら実行する
InitStep();
while (nowStepIndex < maxStep)
{
// リダクションステップ実行
ReductionStep();
nowStepIndex++;
if (IsEndStep())
break;
NextStep();
}
// 最後に追加で数回実行する(残り物処理)
if (nowStepIndex < maxStep)
{
// リダクションステップ実行
ReductionStep();
nowStepIndex++;
}
//Debug.Log(nowStepIndex);
// 頂点情報を整理する
// 法線単位化
// ボーンウエイトを1に平均化
UpdateReductionResultJob();
// 削除頂点数集計
int removeVertexCount = 0;
for (int i = 0; i < nowStepIndex; i++)
{
removeVertexCount += resultArray[i];
//Debug.Log($"[Step:{i + 1}] => {resultArray[i]}");
}
workData.removeVertexCount += removeVertexCount;
// 完了
//success = true;
result.SetSuccess();
}
catch (Exception exception)
{
Debug.LogError(exception);
if (result.IsError() == false)
{
if (this is SimpleDistanceReduction)
result.SetError(Define.Result.Reduction_SimpleDistanceException);
else if (this is ShapeDistanceReduction)
result.SetError(Define.Result.Reduction_ShapeDistanceException);
else
result.SetError(Define.Result.Reduction_Exception);
}
}
finally
{
// 作業バッファを解放する(重要)
// ★仮にタスクが例外やキャンセルされたとしてもこれで作成したバッファは正しくDispose()される
//MagicaManager.Discard.Add();
// 登録したジョブを解除する(重要)
// ★仮にタスクが例外やキャンセルされたとしてもこれで発行したJobは正しくComplete()される
//MagicaManager.Thread.DisposeJob(int);
}
return result;
}
void InitStep()
{
nowStepIndex = 0;
nowMergeLength = startMergeLength;
nowStepScale = 2.0f;
}
bool IsEndStep()
{
return nowMergeLength == endMergeLength;
}
void NextStep()
{
nowStepScale = math.max(nowStepScale * 0.93f, 1.1f);
nowMergeLength = math.min(nowMergeLength * nowStepScale, endMergeLength);
}
/// <summary>
/// リダクションステップ処理
/// </summary>
/// <param name="jobHandle"></param>
/// <param name="mergeLength"></param>
/// <param name="vertexCount"></param>
/// <param name="stepIndex"></param>
/// <returns></returns>
void ReductionStep()
{
//Debug.Log($"nowMergeLength:{nowMergeLength}");
// ステップ前処理
// 結合ペアリストのクリア
PreReductionStep();
// 結合ペアの追加
CustomReductionStep();
// ステップ後処理
// 結合ペアリストから頂点が被らないペアを摘出し結合を実行する
// 結合後に頂点の接続状態を最新に更新する
PostReductionStep();
}
//=========================================================================================
/// <summary>
/// ステップ処理前初期化
/// この関数をオーバーライドし必要なステップ前初期化を追加する
/// </summary>
protected virtual void StepInitialize()
{
int vcnt = vmesh.VertexCount;
joinEdgeList = new NativeList<JoinEdge>(vcnt / 4, Allocator.Persistent);
completeVertexSet = new NativeParallelHashSet<int>(vcnt, Allocator.Persistent);
removePairList = new NativeList<int2>(vcnt, Allocator.Persistent);
resultArray = new NativeArray<int>(maxStep, Allocator.Persistent);
}
/// <summary>
/// この関数をオーバーライドしjoinEdgeListに削除候補のペアを追加する処理を記述する
/// </summary>
/// <param name="jobHandle"></param>
/// <returns></returns>
protected virtual void CustomReductionStep()
{
}
//=========================================================================================
/// <summary>
/// リダクションステップ前処理
/// </summary>
/// <param name="jobHandle"></param>
/// <returns></returns>
void PreReductionStep()
{
// 作業用バッファクリア
joinEdgeList.Clear();
}
//=========================================================================================
/// <summary>
/// リダクションステップ後処理
/// </summary>
/// <param name="jobHandle"></param>
/// <returns></returns>
void PostReductionStep()
{
// 結合候補をソートする
SortJoinEdge();
// 結合リストを作成する
DetermineJoinEdge();
// 結合を実行する
RunJoinEdge();
// 頂点の接続状態を最新に更新する。すべて最新の生存ポイントを指すように変更する
UpdateJoinAndLink();
//Debug.Log($"[{name}] Step:{nowStepIndex} mergeLength:{nowMergeLength} RemoveVertex:{resultArray[nowStepIndex]}");
}
//=========================================================================================
/// <summary>
/// 結合候補のペアリストをコストの昇順でソートするジョブを発行する
/// </summary>
/// <param name="jobHandle"></param>
/// <returns></returns>
void SortJoinEdge()
{
// コストの昇順でソートする
joinEdgeList.Sort();
}
//=========================================================================================
/// <summary>
/// 結合候補から距離順位に頂点が被らないように結合ペアを選択するジョブを発行する
/// 頂点が被らないのでこれらのペアは並列に結合処理を行っても問題なくなる
/// </summary>
/// <param name="jobHandle"></param>
/// <returns></returns>
void DetermineJoinEdge()
{
var reductionJob = new DeterminJoinEdgeJob()
{
stepIndex = nowStepIndex,
mergeLength = nowMergeLength,
joinEdgeList = joinEdgeList,
completeVertexSet = completeVertexSet,
removePairList = removePairList,
resultArray = resultArray,
};
reductionJob.Run();
//Debug.Log($"Step:{nowStepIndex} mergeLength:{nowMergeLength} RemoveVertex:{resultArray[nowStepIndex]}");
}
[BurstCompile]
struct DeterminJoinEdgeJob : IJob
{
public int stepIndex;
public float mergeLength;
[Unity.Collections.ReadOnly]
public NativeList<JoinEdge> joinEdgeList;
public NativeParallelHashSet<int> completeVertexSet;
public NativeList<int2> removePairList;
public NativeArray<int> resultArray;
public void Execute()
{
// すでに結合された頂点セット
completeVertexSet.Clear();
removePairList.Clear();
int cnt = 0;
for (int i = 0; i < joinEdgeList.Length; i++)
{
var joinEdge = joinEdgeList[i];
var vertexPair = joinEdge.vertexPair;
// 頂点がすでに結合されているならばスキップする
if (completeVertexSet.Contains(vertexPair.x) || completeVertexSet.Contains(vertexPair.y))
continue;
// このエッジを結合する
// 結合リストに追加する
removePairList.Add(vertexPair.xy);
// 処理済みマップに追加
completeVertexSet.Add(vertexPair.x);
completeVertexSet.Add(vertexPair.y);
cnt++;
}
// 削減頂点数を保存
resultArray[stepIndex] = cnt;
}
}
//=========================================================================================
/// <summary>
/// 結合ペアを実際に結合するジョブを発行する
/// </summary>
/// <param name="jobHandle"></param>
/// <returns></returns>
void RunJoinEdge()
{
var organizeJoinEdgeJob = new JoinPairJob()
{
joinPositionAdjustment = joinPositionAdjustment,
removePairList = removePairList,
localPositions = vmesh.localPositions.GetNativeArray(),
localNormals = vmesh.localNormals.GetNativeArray(),
joinIndices = workData.vertexJoinIndices,
vertexToVertexMap = workData.vertexToVertexMap,
boneWeights = vmesh.boneWeights.GetNativeArray(),
attributes = vmesh.attributes.GetNativeArray(),
};
organizeJoinEdgeJob.Run();
}
[BurstCompile]
struct JoinPairJob : IJob
{
public float joinPositionAdjustment;
[Unity.Collections.ReadOnly]
public NativeList<int2> removePairList;
public NativeArray<float3> localPositions;
public NativeArray<float3> localNormals;
public NativeParallelMultiHashMap<ushort, ushort> vertexToVertexMap;
public NativeArray<VirtualMeshBoneWeight> boneWeights;
public NativeArray<VertexAttribute> attributes;
public NativeArray<int> joinIndices;
public void Execute()
{
for (int index = 0; index < removePairList.Length; index++)
{
int2 pair = removePairList[index];
// pair.x -> pair.yに結合する
int vindex1 = pair.x;
int vindex2 = pair.y;
float3 pos1 = localPositions[vindex1];
float3 pos2 = localPositions[vindex2];
float3 nor1 = localNormals[vindex1];
float3 nor2 = localNormals[vindex2];
// vertex2にvertex1を結合し、vertex1は削除としてマークする
// 結合(vertex1 -> vertex2)
joinIndices[vindex1] = vindex2;
// vertex2の新しい座標
// 各頂点の接続数に応じて結合位置のウエイトを変える(接続数が多いほど動かない)
float linkCnt1 = math.max(vertexToVertexMap.CountValuesForKey((ushort)vindex1) - 1, 1); // 接続トライアングルを想定して1を引く
float linkCnt2 = math.max(vertexToVertexMap.CountValuesForKey((ushort)vindex2) - 1, 1); // 接続トライアングルを想定して1を引く
float ratio = linkCnt2 / (linkCnt1 + linkCnt2);
// 頂点接合位置のユーザー調整(1.0の場合は平均位置になる)
ratio = math.lerp(ratio, 0.5f, joinPositionAdjustment);
// p2の座標を変更
float3 newpos = math.lerp(pos2, pos1, ratio);
localPositions[vindex2] = newpos;
localNormals[vindex2] = (nor1 + nor2);
// 接続数を結合する(重複は弾かれる)
var newLink = new FixedList512Bytes<ushort>();
foreach (ushort nindex in vertexToVertexMap.GetValuesForKey((ushort)vindex1))
{
if (nindex != vindex1 && nindex != vindex2)
newLink.MC2Set(nindex);
}
foreach (ushort nindex in vertexToVertexMap.GetValuesForKey((ushort)vindex2))
{
if (nindex != vindex1 && nindex != vindex2)
newLink.MC2Set(nindex);
}
vertexToVertexMap.Remove((ushort)vindex2);
for (int i = 0; i < newLink.Length; i++)
{
vertexToVertexMap.Add((ushort)vindex2, newLink[i]);
}
//Debug.Assert(newLink.Length > 0);
// p2にBoneWeightを結合
var bw = boneWeights[vindex2];
bw.AddWeight(boneWeights[vindex1]);
boneWeights[vindex2] = bw;
// 頂点属性
var attr1 = attributes[vindex1];
var attr2 = attributes[vindex2];
attributes[vindex2] = VertexAttribute.JoinAttribute(attr1, attr2);
attributes[vindex1] = VertexAttribute.Invalid; // 削除頂点は無効にする
}
}
}
//=========================================================================================
/// <summary>
/// 接続状態を最新に更新するジョブを発行する
/// </summary>
/// <param name="jobHandle"></param>
/// <returns></returns>
void UpdateJoinAndLink()
{
// JoinIndexの状態を更新する。現在の最新の生存ポイントを指すように変更する
var updateJoinIndexJob = new UpdateJoinIndexJob()
{
joinIndices = workData.vertexJoinIndices,
};
updateJoinIndexJob.Run(vmesh.VertexCount);
// 頂点の接続頂点リストを最新に更新する。すべて最新の生存ポイントを指すように変更する
var updateLinkIndexJob = new UpdateLinkIndexJob()
{
joinIndices = workData.vertexJoinIndices,
vertexToVertexMap = workData.vertexToVertexMap,
};
updateLinkIndexJob.Run(vmesh.VertexCount);
}
[BurstCompile]
struct UpdateJoinIndexJob : IJobParallelFor
{
[NativeDisableParallelForRestriction]
public NativeArray<int> joinIndices;
public void Execute(int vindex)
{
int join = joinIndices[vindex];
if (join >= 0)
{
// 削除されている
// 最終的な生存ポイントに連結させる
while (joinIndices[join] >= 0)
{
join = joinIndices[join];
}
joinIndices[vindex] = join;
}
}
}
[BurstCompile]
struct UpdateLinkIndexJob : IJobParallelFor
{
[NativeDisableParallelForRestriction]
public NativeArray<int> joinIndices;
public NativeParallelMultiHashMap<ushort, ushort> vertexToVertexMap;
public void Execute(int vindex)
{
int join = joinIndices[vindex];
// 自身が削除されている場合は無視
if (join >= 0)
return;
// 自身が生存している
// 現在の接続インデックスから削除されたものを生存インデックスに入れ替える
var newLinkSet = new FixedList512Bytes<ushort>();
foreach (ushort i in vertexToVertexMap.GetValuesForKey((ushort)vindex))
{
int tvindex = i;
int tjoin = joinIndices[tvindex];
if (tjoin >= 0)
{
// 削除されている
tvindex = tjoin;
Debug.Assert(joinIndices[tvindex] < 0);
}
// 自身は弾く
if (tvindex == vindex)
continue;
newLinkSet.MC2Set((ushort)tvindex);
}
// 生存のみの新しいセットに入れ替え
vertexToVertexMap.Remove((ushort)vindex);
for (int i = 0; i < newLinkSet.Length; i++)
{
vertexToVertexMap.Add((ushort)vindex, newLinkSet[i]);
}
//Debug.Assert(newLinkSet.Length > 0);
}
}
//=========================================================================================
/// <summary>
/// リダクション後のデータを整える
/// </summary>
void UpdateReductionResultJob()
{
// 頂点法線の単位化、およびボーンウエイトを1に整える
var finalVertexJob = new FinalMergeVertexJob()
{
joinIndices = workData.vertexJoinIndices,
localNormals = vmesh.localNormals.GetNativeArray(),
boneWeights = vmesh.boneWeights.GetNativeArray(),
};
finalVertexJob.Run(vmesh.VertexCount);
}
[BurstCompile]
struct FinalMergeVertexJob : IJobParallelFor
{
[Unity.Collections.ReadOnly]
public NativeArray<int> joinIndices;
public NativeArray<float3> localNormals;
public NativeArray<VirtualMeshBoneWeight> boneWeights;
public void Execute(int vindex)
{
int join = joinIndices[vindex];
if (join >= 0)
{
// 削除されている
return;
}
// 法線単位化
localNormals[vindex] = math.normalize(localNormals[vindex]);
// ボーンウエイトを平均化
var bw = boneWeights[vindex];
bw.AdjustWeight();
boneWeights[vindex] = bw;
}
}
//=========================================================================================
// Job Utility
//=========================================================================================
/// <summary>
/// 頂点を結合して問題がないか調べる
/// </summary>
/// <param name="vertexToVertexArray"></param>
/// <param name="vindex"></param>
/// <param name="tvindex"></param>
/// <param name="vlist"></param>
/// <param name="tvlist"></param>
/// <param name="dontMakeLine"></param>
/// <returns></returns>
protected static bool CheckJoin2(
in NativeParallelMultiHashMap<ushort, ushort> vertexToVertexMap,
int vindex,
int tvindex,
bool dontMakeLine
)
{
// 結合後の接続リストを仮作成する
var joinVlink = new FixedList512Bytes<ushort>();
foreach (ushort index in vertexToVertexMap.GetValuesForKey((ushort)vindex))
{
if (index != vindex && index != tvindex)
joinVlink.MC2Set(index);
}
foreach (ushort index in vertexToVertexMap.GetValuesForKey((ushort)tvindex))
{
if (index != vindex && index != tvindex)
joinVlink.MC2Set(index);
}
// 点になるのはNG
// 結合後に接続がになる場合はNG
if (joinVlink.Length == 0)
{
//Debug.LogWarning("joinVlink.Count = 0!");
return false;
}
// 可能な限りラインを作らない(オプション)
if (dontMakeLine)
{
// 結合後のトライアングルの外周をひと筆書きし、すべての外周頂点が使われているならOK!
// 外周頂点が一筆書きできない場合はつ以上のグループに分かれいるX型になる
var stack = new FixedList512Bytes<ushort>();
stack.MC2Push(joinVlink[0]);
while (stack.Length > 0)
{
ushort index = stack.MC2Pop();
if (joinVlink.Contains(index) == false)
continue;
joinVlink.MC2RemoveItemAtSwapBack(index);
foreach (ushort nindex in vertexToVertexMap.GetValuesForKey((ushort)index))
{
if (joinVlink.Contains(nindex))
{
// next
stack.MC2Push(nindex);
}
}
}
if (joinVlink.Length > 0)
{
// 外周を一筆書きしたあとでもまだ頂点が残っている!
// これは頂点がX型になるのでNG
return false;
}
}
// 大丈夫
return true;
}
/// <summary>
/// 頂点を結合して問題がないか調べる
/// </summary>
/// <param name="vertexToVertexArray"></param>
/// <param name="vindex"></param>
/// <param name="tvindex"></param>
/// <param name="vlist"></param>
/// <param name="tvlist"></param>
/// <param name="dontMakeLine"></param>
/// <returns></returns>
protected static bool CheckJoin(
in NativeArray<FixedList128Bytes<ushort>> vertexToVertexArray,
int vindex,
int tvindex,
in FixedList128Bytes<ushort> vlist,
in FixedList128Bytes<ushort> tvlist,
bool dontMakeLine
)
{
// 結合後の接続リストを仮作成する
var joinVlink = new FixedList128Bytes<ushort>();
for (int i = 0; i < vlist.Length; i++)
{
int index = vlist[i];
if (index != vindex && index != tvindex)
joinVlink.MC2SetLimit((ushort)index);
}
for (int i = 0; i < tvlist.Length; i++)
{
int index = tvlist[i];
if (index != vindex && index != tvindex)
joinVlink.MC2SetLimit((ushort)index);
}
// 点になるのはNG
// 結合後に接続がになる場合はNG
if (joinVlink.Length == 0)
{
//Debug.LogWarning("joinVlink.Count = 0!");
return false;
}
// 可能な限りラインを作らない(オプション)
if (dontMakeLine)
{
// 結合後のトライアングルの外周をひと筆書きし、すべての外周頂点が使われているならOK!
// 外周頂点が一筆書きできない場合はつ以上のグループに分かれいるX型になる
var stack = new FixedList512Bytes<ushort>();
stack.MC2Push(joinVlink[0]);
while (stack.Length > 0)
{
ushort index = stack.MC2Pop();
if (joinVlink.Contains(index) == false)
continue;
joinVlink.MC2RemoveItemAtSwapBack(index);
var link = vertexToVertexArray[index];
for (int i = 0; i < link.Length; i++)
{
ushort nindex = link[i];
if (joinVlink.Contains(nindex))
{
// next
stack.MC2Push(nindex);
}
}
}
if (joinVlink.Length > 0)
{
// 外周を一筆書きしたあとでもまだ頂点が残っている!
// これは頂点がX型になるのでNG
return false;
}
}
// 大丈夫
return true;
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: facec8b9837c5ca4d8d05ccc7f1ca6a9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 242307
packageName: Magica Cloth 2
packageVersion: 2.18.1
assetPath: Assets/MagicaCloth2/Scripts/Core/Reduction/StepReductionBase.cs
uploadId: 893596