您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
410 行
11 KiB
410 行
11 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")]
|
|
public bool showBounds = false;
|
|
public bool showIslands = false;
|
|
public bool showRootLines = false;
|
|
private const int debugColorsSize = 7;
|
|
private static Color[] debugColors = new Color[debugColorsSize] { Color.red, Color.green, Color.blue, Color.cyan, Color.magenta, Color.yellow, Color.white };
|
|
private static SkinAttachmentData debugData;
|
|
|
|
[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;
|
|
|
|
public Matrix4x4 GetWorldToLocalSkinning()
|
|
{
|
|
if (skinningBone != null)
|
|
return (skinningBoneBindPoseInverse * skinningBone.worldToLocalMatrix);
|
|
else
|
|
return (this.transform.worldToLocalMatrix);
|
|
}
|
|
|
|
public Matrix4x4 GetLocalSkinningToWorld()
|
|
{
|
|
if (skinningBone != null)
|
|
return (skinningBone.localToWorldMatrix * skinningBoneBindPose);
|
|
else
|
|
return (this.transform.localToWorldMatrix);
|
|
}
|
|
|
|
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 (attached)
|
|
DrawGizmosAttached();
|
|
else
|
|
DrawGizmosDetached();
|
|
}
|
|
|
|
void DrawGizmosAttached()
|
|
{
|
|
if (attachmentType != AttachmentType.Transform)
|
|
{
|
|
if (meshInstance == null)
|
|
return;
|
|
|
|
Gizmos.matrix = this.transform.localToWorldMatrix;
|
|
|
|
if (showBounds)
|
|
{
|
|
Gizmos.color = Color.white;
|
|
Gizmos.DrawWireCube(meshInstance.bounds.center, meshInstance.bounds.extents * 2.0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrawGizmosDetached()
|
|
{
|
|
if (target == null)
|
|
return;
|
|
|
|
var targetMeshInfo = target.GetCachedMeshInfo();
|
|
if (targetMeshInfo.valid == false)
|
|
return;
|
|
|
|
if (attachmentType == AttachmentType.Transform)
|
|
{
|
|
// draw sphere with radius to closest vertex
|
|
var closestDist = float.MaxValue;
|
|
var closestNode = -1;
|
|
|
|
var targetLocalPos = target.transform.InverseTransformPoint(this.transform.position);
|
|
if (targetMeshInfo.meshVertexBSP.FindNearest(ref closestDist, ref closestNode, ref targetLocalPos))
|
|
{
|
|
Gizmos.matrix = target.transform.localToWorldMatrix;
|
|
|
|
var r = targetLocalPos - 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(targetLocalPos, Mathf.Sqrt(closestDist));
|
|
|
|
Gizmos.color = Color.Lerp(Color.clear, c, 0.75f);
|
|
Gizmos.DrawLine(targetLocalPos, target.meshBuffers.vertexPositions[closestNode]);
|
|
|
|
foreach (var triangle in targetMeshInfo.meshAdjacency.vertexTriangles[closestNode])
|
|
{
|
|
int _0 = triangle * 3;
|
|
int v0 = target.meshBuffers.triangles[_0];
|
|
int v1 = target.meshBuffers.triangles[_0 + 1];
|
|
int v2 = target.meshBuffers.triangles[_0 + 2];
|
|
|
|
Gizmos.DrawLine(target.meshBuffers.vertexPositions[v0], target.meshBuffers.vertexPositions[v1]);
|
|
Gizmos.DrawLine(target.meshBuffers.vertexPositions[v1], target.meshBuffers.vertexPositions[v2]);
|
|
Gizmos.DrawLine(target.meshBuffers.vertexPositions[v2], target.meshBuffers.vertexPositions[v0]);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EnsureMeshInstance();
|
|
|
|
if (meshInstance == null)
|
|
return;
|
|
|
|
var subjectPositions = meshBuffers.vertexPositions;
|
|
|
|
if (skinningBone != null)
|
|
Gizmos.matrix = skinningBone.localToWorldMatrix * skinningBoneBindPose;
|
|
else
|
|
Gizmos.matrix = this.transform.localToWorldMatrix;
|
|
|
|
if (showIslands)
|
|
{
|
|
for (int island = 0; island != meshIslands.islandCount; island++)
|
|
{
|
|
Gizmos.color = Color.Lerp(Color.clear, debugColors[island % debugColors.Length], 0.3f);
|
|
foreach (var i in meshIslands.islandVertices[island])
|
|
{
|
|
foreach (var j in meshAdjacency.vertexVertices[i])
|
|
{
|
|
Gizmos.DrawLine(subjectPositions[i], subjectPositions[j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (showRootLines)
|
|
{
|
|
if (debugData == null)
|
|
{
|
|
debugData = SkinAttachmentData.CreateInstance<SkinAttachmentData>();
|
|
debugData.hideFlags = HideFlags.HideAndDontSave;
|
|
}
|
|
else
|
|
{
|
|
debugData.Clear();
|
|
}
|
|
|
|
int dryRunPoseCount = -1;
|
|
int dryRunItemCount = -1;
|
|
|
|
SkinAttachmentDataBuilder.BuildDataAttachSubject(ref debugData, target.transform, target.GetCachedMeshInfo(), this, dryRun: true, ref dryRunPoseCount, ref dryRunItemCount);
|
|
{
|
|
ArrayUtils.ResizeCheckedIfLessThan(ref debugData.pose, dryRunPoseCount);
|
|
ArrayUtils.ResizeCheckedIfLessThan(ref debugData.item, dryRunItemCount);
|
|
}
|
|
|
|
SkinAttachmentDataBuilder.BuildDataAttachSubject(ref debugData, target.transform, target.GetCachedMeshInfo(), this, dryRun: false, ref dryRunPoseCount, ref dryRunItemCount);
|
|
|
|
Matrix4x4 targetToWorld = Matrix4x4.TRS(target.transform.position, target.transform.rotation, Vector3.one);
|
|
// NOTE: targetToWorld specifically excludes scale, since source data (BakeMesh) is already scaled
|
|
|
|
Matrix4x4 targetToSubject;
|
|
{
|
|
if (skinningBone != null)
|
|
targetToSubject = (skinningBoneBindPoseInverse * skinningBone.worldToLocalMatrix) * targetToWorld;
|
|
else
|
|
targetToSubject = this.transform.worldToLocalMatrix * targetToWorld;
|
|
}
|
|
|
|
Gizmos.color = Color.white;
|
|
|
|
for (int i = 0; i != meshBuffers.vertexCount; i++)
|
|
{
|
|
Vector3 rootOffset = targetToSubject.MultiplyVector(-debugData.item[i].targetOffset);
|
|
Gizmos.DrawRay(subjectPositions[i], rootOffset);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|