您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

566 行
14 KiB

using System;
using UnityEngine;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.DemoTeam.Attributes;
namespace Unity.DemoTeam.DigitalHuman
{
[ExecuteAlways]
public class SkinAttachment : MeshInstanceBehaviour
{
public enum AttachmentMode
{
BuildPoses,
LinkPosesByReference,
LinkPosesBySpecificIndex,
}
public enum AttachmentType
{
Transform,
Mesh,
MeshRoots,
}
[HideInInspector] public bool attached;
[HideInInspector] public Vector3 attachedLocalPosition;
[HideInInspector] public Quaternion attachedLocalRotation;
[HideInInspector]
public SkinAttachmentTarget targetActive;
[EditableIf("attached", false)]
public SkinAttachmentTarget target;
[EditableIf("attached", false)]
public AttachmentType attachmentType = AttachmentType.Transform;
[EditableIf("attached", false)]
public AttachmentMode attachmentMode = AttachmentMode.BuildPoses;
[EditableIf("attached", false)]
public SkinAttachment attachmentLink = null;
[EditableIf("attachmentMode", AttachmentMode.LinkPosesBySpecificIndex)]
public int attachmentIndex = -1;
[EditableIf("attachmentMode", AttachmentMode.LinkPosesBySpecificIndex)]
public int attachmentCount = 0;
[HideInInspector]
public ulong checksum0 = 0;
[HideInInspector]
public ulong checksum1 = 0;
[Header("Debug options")]
[Range(0, 6)]
public int debugIndex = 0;
public bool debugIndexEnabled = false;
public bool debugBounds = false;
[Header("Runtime options")]
public bool forceRecalculateBounds;
public bool forceRecalculateNormals;
public bool forceRecalculateTangents;
[NonSerialized] public float meshAssetRadius;
[NonSerialized] public MeshBuffers meshBuffers;
[NonSerialized] public MeshAdjacency meshAdjacency;
[NonSerialized] public MeshIslands meshIslands;
[NonSerialized] public Transform skinningBone;
[NonSerialized] public Matrix4x4 skinningBoneBindPose;
[NonSerialized] public Matrix4x4 skinningBoneBindPoseInverse;
void DiscoverSkinningBone()
{
skinningBone = null;
skinningBoneBindPose = Matrix4x4.identity;
skinningBoneBindPoseInverse = Matrix4x4.identity;
// search for skinning bone
var smr = GetComponent<SkinnedMeshRenderer>();
if (smr != null)
{
int skinningBoneIndex = -1;
unsafe
{
var boneWeights = meshAsset.GetAllBoneWeights();
var boneWeightPtr = (BoneWeight1*)boneWeights.GetUnsafeReadOnlyPtr();
for (int i = 0; i != boneWeights.Length; i++)
{
if (boneWeightPtr[i].weight > 0.0f)
{
if (skinningBoneIndex == -1)
skinningBoneIndex = boneWeightPtr[i].boneIndex;
if (skinningBoneIndex != boneWeightPtr[i].boneIndex)
{
skinningBoneIndex = -1;
break;
}
}
}
}
if (skinningBoneIndex != -1)
{
skinningBone = smr.bones[skinningBoneIndex];
skinningBoneBindPose = meshInstance.bindposes[skinningBoneIndex];
skinningBoneBindPoseInverse = skinningBoneBindPose.inverse;
//Debug.Log("discovered skinning bone for " + this.name + " : " + skinningBone.name);
}
}
}
protected override void OnMeshInstanceCreated()
{
meshAssetRadius = meshAsset.bounds.extents.magnitude;// conservative
if (meshBuffers == null)
meshBuffers = new MeshBuffers(meshInstance);
else
meshBuffers.LoadFrom(meshInstance);
if (meshAdjacency == null)
meshAdjacency = new MeshAdjacency(meshBuffers);
else
meshAdjacency.LoadFrom(meshBuffers);
if (meshIslands == null)
meshIslands = new MeshIslands(meshAdjacency);
else
meshIslands.LoadFrom(meshAdjacency);
DiscoverSkinningBone();
}
protected override void OnMeshInstanceDeleted()
{
// do nothing
}
public Hash128 Checksum()
{
return new Hash128(checksum0, checksum1);
}
public void RevertVertexData()
{
if (meshAsset != null)
{
if (meshBuffers == null)
meshBuffers = new MeshBuffers(meshAsset);
else
meshBuffers.LoadFrom(meshAsset);
}
}
public void Attach(bool storePositionRotation = true)
{
EnsureMeshInstance();
if (targetActive != null)
targetActive.RemoveSubject(this);
targetActive = target;
targetActive.AddSubject(this);
if (storePositionRotation)
{
attachedLocalPosition = transform.localPosition;
attachedLocalRotation = transform.localRotation;
}
attached = true;
}
public void Detach(bool revertPositionRotation = true)
{
RemoveMeshInstance();
if (targetActive != null)
targetActive.RemoveSubject(this);
if (revertPositionRotation)
{
transform.localPosition = attachedLocalPosition;
transform.localRotation = attachedLocalRotation;
}
attached = false;
}
void ValidateAttachedState()
{
if (attached)
{
if (targetActive != null && targetActive == target)
{
EnsureMeshInstance();
}
else
{
Detach();
}
}
else
{
RemoveMeshInstance();
}
}
void OnEnable()
{
ValidateAttachedState();
}
void Update()
{
ValidateAttachedState();
}
void LateUpdate()
{
var forceRecalculateAny = forceRecalculateBounds || forceRecalculateNormals || forceRecalculateTangents;
if (forceRecalculateAny && meshInstance != null)
{
if (forceRecalculateTangents)
meshInstance.SilentlyRecalculateTangents();
if (forceRecalculateNormals)
meshInstance.SilentlyRecalculateNormals();
if (forceRecalculateBounds)
meshInstance.SilentlyRecalculateBounds();
}
}
void OnDestroy()
{
RemoveMeshInstance();
}
#if UNITY_EDITOR
void OnDrawGizmosSelected()
{
if (isActiveAndEnabled == false)
return;
if (target == null)
return;
var targetMeshInfo = target.GetCachedMeshInfo();
if (targetMeshInfo.valid == false)
return;
//TODO get rid of duplicate code
if (attached)
{
if (attachmentType != AttachmentType.Transform && debugBounds)
{
Gizmos.matrix = this.transform.localToWorldMatrix;
Gizmos.DrawWireCube(meshInstance.bounds.center, meshInstance.bounds.extents * 2.0f);
}
return;
}
if (attachmentType == AttachmentType.Transform)
{
var targetPosition = target.transform.InverseTransformPoint(this.transform.position);
var closestDist = float.MaxValue;
var closestNode = -1;
if (targetMeshInfo.meshVertexBSP.FindNearest(ref closestDist, ref closestNode, ref targetPosition))
{
Gizmos.matrix = target.transform.localToWorldMatrix;
var r = targetPosition - target.meshBuffers.vertexPositions[closestNode];
var d = Vector3.Dot(target.meshBuffers.vertexNormals[closestNode], r);
var c = (d >= 0.0f) ? Color.cyan : Color.magenta;
Gizmos.color = Color.Lerp(Color.clear, c, 0.75f);
Gizmos.DrawSphere(targetPosition, Mathf.Sqrt(closestDist));
Gizmos.color = Color.Lerp(Color.clear, c, 0.75f);
Gizmos.DrawLine(targetPosition, target.meshBuffers.vertexPositions[closestNode]);
target.meshBuffers.DrawTriangles(targetMeshInfo.meshAdjacency.vertexTriangles[closestNode]);
}
return;
}
if (meshBuffers == null)
{
EnsureMeshInstance();
}
if (skinningBone != null)
Gizmos.matrix = skinningBone.localToWorldMatrix * skinningBoneBindPose;
else
Gizmos.matrix = this.transform.localToWorldMatrix;
if (attachmentType == AttachmentType.Mesh)
{
var colorArray = new Color[] { Color.red, Color.green, Color.blue, Color.cyan, Color.magenta, Color.yellow, Color.white };
var colorIndex = -1;
var positions = meshBuffers.vertexPositions;
for (int island = 0; island != meshIslands.islandCount; island++)
{
colorIndex++;
colorIndex %= colorArray.Length;
if (colorIndex != debugIndex && debugIndexEnabled)
continue;
Gizmos.color = Color.Lerp(Color.clear, colorArray[colorIndex], 0.3f);
foreach (var i in meshIslands.islandVertices[island])
{
foreach (var j in meshAdjacency.vertexVertices[i])
{
Gizmos.DrawLine(positions[i], positions[j]);
}
}
}
return;
}
if (attachmentType == AttachmentType.MeshRoots)
{
var colorArray = new Color[] { Color.red, Color.green, Color.blue, Color.cyan, Color.magenta, Color.yellow, Color.white };
var colorIndex = -1;
var positions = meshBuffers.vertexPositions;
var targetPositions = new Vector3[positions.Length];
Matrix4x4 targetToWorld = Matrix4x4.TRS(target.transform.position, target.transform.rotation, Vector3.one);
Matrix4x4 subjectToTarget;
Matrix4x4 targetToSubject;
{
if (skinningBone != null)
{
subjectToTarget = target.transform.worldToLocalMatrix * (skinningBone.localToWorldMatrix * skinningBoneBindPose);
targetToSubject = (skinningBoneBindPoseInverse * skinningBone.worldToLocalMatrix) * targetToWorld;
}
else
{
subjectToTarget = target.transform.worldToLocalMatrix * this.transform.localToWorldMatrix;
targetToSubject = this.transform.worldToLocalMatrix * targetToWorld;
}
}
for (int i = 0; i != positions.Length; i++)
{
targetPositions[i] = subjectToTarget.MultiplyPoint3x4(positions[i]);
}
for (int island = 0; island != meshIslands.islandCount; island++)
{
colorIndex++;
colorIndex %= colorArray.Length;
if (colorIndex != debugIndex && debugIndexEnabled)
continue;
Gizmos.color = colorArray[colorIndex];
// draw the faces
foreach (int i in meshIslands.islandVertices[island])
{
foreach (int j in meshAdjacency.vertexVertices[i])
{
Gizmos.DrawLine(positions[i], positions[j]);
}
}
// draw root-lines
unsafe
{
var rootIdx = new UnsafeArrayInt(positions.Length);
var rootDir = new UnsafeArrayVector3(positions.Length);
var rootGen = new UnsafeArrayInt(positions.Length);
var visitor = new UnsafeBFS(positions.Length);
visitor.Clear();
// find island roots
{
int rootCount = 0;
var bestDist0 = float.PositiveInfinity;
var bestNode0 = -1;
var bestVert0 = -1;
var bestDist1 = float.PositiveInfinity;
var bestNode1 = -1;
var bestVert1 = -1;
foreach (var i in meshIslands.islandVertices[island])
{
var targetDist = float.PositiveInfinity;
var targetNode = -1;
if (targetMeshInfo.meshVertexBSP.FindNearest(ref targetDist, ref targetNode, ref targetPositions[i]))
{
// found a root if one or more neighbouring vertices are below
var bestDist = float.PositiveInfinity;
var bestNode = -1;
foreach (var j in meshAdjacency.vertexVertices[i])
{
var targetDelta = targetPositions[j] - target.meshBuffers.vertexPositions[targetNode];
var targetNormalDist = Vector3.Dot(targetDelta, target.meshBuffers.vertexNormals[targetNode]);
if (targetNormalDist < 0.0f)
{
var d = Vector3.SqrMagnitude(targetDelta);
if (d < bestDist)
{
bestDist = d;
bestNode = j;
}
}
}
if (bestNode != -1)
{
visitor.Ignore(i);
rootIdx.val[i] = targetNode;
rootDir.val[i] = Vector3.Normalize(targetPositions[bestNode] - targetPositions[i]);
rootGen.val[i] = 0;
rootCount++;
}
else
{
rootIdx.val[i] = -1;
rootGen.val[i] = -1;
// see if node qualifies as second choice root
var targetDelta = targetPositions[i] - target.meshBuffers.vertexPositions[targetNode];
var targetNormalDist = Mathf.Abs(Vector3.Dot(targetDelta, target.meshBuffers.vertexNormals[targetNode]));
if (targetNormalDist < bestDist0)
{
bestDist1 = bestDist0;
bestNode1 = bestNode0;
bestVert1 = bestVert0;
bestDist0 = targetNormalDist;
bestNode0 = targetNode;
bestVert0 = i;
}
else if (targetNormalDist < bestDist1)
{
bestDist1 = targetNormalDist;
bestNode1 = targetNode;
bestVert1 = i;
}
}
}
}
if (rootCount < 2 && bestVert0 != -1)
{
visitor.Ignore(bestVert0);
rootIdx.val[bestVert0] = bestNode0;
rootDir.val[bestVert0] = Vector3.Normalize(target.meshBuffers.vertexPositions[bestNode0] - targetPositions[bestVert0]);
rootGen.val[bestVert0] = 0;
rootCount++;
if (rootCount < 2 && bestVert1 != -1)
{
visitor.Ignore(bestVert1);
rootIdx.val[bestVert1] = bestNode1;
rootDir.val[bestVert1] = Vector3.Normalize(target.meshBuffers.vertexPositions[bestNode1] - targetPositions[bestVert1]);
rootGen.val[bestVert1] = 0;
rootCount++;
}
}
}
// find boundary
foreach (var i in meshIslands.islandVertices[island])
{
if (rootIdx.val[i] != -1)
continue;
foreach (var j in meshAdjacency.vertexVertices[i])
{
if (rootIdx.val[j] != -1)
{
visitor.Insert(i);
break;
}
}
}
// propagate roots
while (visitor.MoveNext())
{
var i = visitor.position;
var bestDist = float.PositiveInfinity;
var bestNode = -1;
foreach (var j in meshAdjacency.vertexVertices[i])
{
if (rootIdx.val[j] != -1)
{
var d = -Vector3.Dot(rootDir.val[j], Vector3.Normalize(targetPositions[j] - targetPositions[i]));
if (d < bestDist)
{
bestDist = d;
bestNode = j;
}
}
else
{
visitor.Insert(j);
}
}
rootIdx.val[i] = rootIdx.val[bestNode];
rootDir.val[i] = Vector3.Normalize(targetPositions[bestNode] - targetPositions[i]);
rootGen.val[i] = rootGen.val[bestNode] + 1;
Gizmos.color = colorArray[rootGen.val[i] % colorArray.Length];
Gizmos.DrawSphere(positions[bestNode], 0.0002f);
Gizmos.DrawSphere(0.5f * (positions[i] + positions[bestNode]), 0.0001f);
}
// draw roots
foreach (var i in meshIslands.islandVertices[island])
{
var root = rootIdx.val[i];
if (root < 0)
{
Debug.Log("i " + i + " has rootIdx " + root);
Gizmos.DrawLine(positions[i], positions[i] + Vector3.up);
}
Gizmos.DrawLine(positions[i], targetToSubject.MultiplyPoint3x4(target.meshBuffers.vertexPositions[root]));
}
// dispose
visitor.Dispose();
rootGen.Dispose();
rootDir.Dispose();
rootIdx.Dispose();
}
//-------------
}
return;
}
}
#endif
}
}