using System; using Unity.Burst; using Unity.Collections; using Unity.Mathematics; using Unity.Collections.LowLevel.Unsafe; using UnityEngine.U2D.Common; namespace UnityEngine.U2D.Animation { internal enum SpriteSkinValidationResult { SpriteNotFound, SpriteHasNoSkinningInformation, SpriteHasNoWeights, RootTransformNotFound, InvalidTransformArray, InvalidTransformArrayLength, TransformArrayContainsNull, RootNotFoundInTransformArray, InvalidBoneWeights, Ready } internal class NativeByteArray { public int Length => array.Length; public bool IsCreated => array.IsCreated; public byte this[int index] => array[index]; public NativeArray array { get; } public NativeByteArray(NativeArray array) { this.array = array; } public void Dispose() => array.Dispose(); } internal static class SpriteSkinUtility { internal static SpriteSkinValidationResult Validate(this SpriteSkin spriteSkin) { if (spriteSkin.spriteRenderer.sprite == null) return SpriteSkinValidationResult.SpriteNotFound; var bindPoses = spriteSkin.spriteRenderer.sprite.GetBindPoses(); var bindPoseCount = bindPoses.Length; if (bindPoseCount == 0) return SpriteSkinValidationResult.SpriteHasNoSkinningInformation; if (spriteSkin.rootBone == null) return SpriteSkinValidationResult.RootTransformNotFound; if (spriteSkin.boneTransforms == null) return SpriteSkinValidationResult.InvalidTransformArray; if (bindPoseCount != spriteSkin.boneTransforms.Length) return SpriteSkinValidationResult.InvalidTransformArrayLength; var rootFound = false; foreach (var boneTransform in spriteSkin.boneTransforms) { if (boneTransform == null) return SpriteSkinValidationResult.TransformArrayContainsNull; if (boneTransform == spriteSkin.rootBone) rootFound = true; } if (!rootFound) return SpriteSkinValidationResult.RootNotFoundInTransformArray; var boneWeights = spriteSkin.sprite.GetVertexAttribute(UnityEngine.Rendering.VertexAttribute.BlendWeight); if (!BurstedSpriteSkinUtilities.ValidateBoneWeights(in boneWeights, bindPoseCount)) return SpriteSkinValidationResult.InvalidBoneWeights; return SpriteSkinValidationResult.Ready; } internal static void CreateBoneHierarchy(this SpriteSkin spriteSkin) { if (spriteSkin.spriteRenderer.sprite == null) throw new InvalidOperationException("SpriteRenderer has no Sprite set"); var spriteBones = spriteSkin.spriteRenderer.sprite.GetBones(); var transforms = new Transform[spriteBones.Length]; Transform root = null; for (int i = 0; i < spriteBones.Length; ++i) { CreateGameObject(i, spriteBones, transforms, spriteSkin.transform); if (spriteBones[i].parentId < 0 && root == null) root = transforms[i]; } spriteSkin.rootBone = root; spriteSkin.boneTransforms = transforms; } internal static int GetVertexStreamSize(this Sprite sprite) { int vertexStreamSize = 12; if (sprite.HasVertexAttribute(Rendering.VertexAttribute.Normal)) vertexStreamSize = vertexStreamSize + 12; if (sprite.HasVertexAttribute(Rendering.VertexAttribute.Tangent)) vertexStreamSize = vertexStreamSize + 16; return vertexStreamSize; } internal static int GetVertexStreamOffset(this Sprite sprite, Rendering.VertexAttribute channel ) { bool hasPosition = sprite.HasVertexAttribute(Rendering.VertexAttribute.Position); bool hasNormals = sprite.HasVertexAttribute(Rendering.VertexAttribute.Normal); bool hasTangents = sprite.HasVertexAttribute(Rendering.VertexAttribute.Tangent); switch(channel) { case Rendering.VertexAttribute.Position: return hasPosition ? 0 : -1; case Rendering.VertexAttribute.Normal: return hasNormals ? 12 : -1; case Rendering.VertexAttribute.Tangent: return hasTangents ? (hasNormals ? 24 : 12) : -1; } return -1; } private static void CreateGameObject(int index, SpriteBone[] spriteBones, Transform[] transforms, Transform root) { if (transforms[index] == null) { var spriteBone = spriteBones[index]; if (spriteBone.parentId >= 0) CreateGameObject(spriteBone.parentId, spriteBones, transforms, root); var go = new GameObject(spriteBone.name); var transform = go.transform; if (spriteBone.parentId >= 0) transform.SetParent(transforms[spriteBone.parentId]); else transform.SetParent(root); transform.localPosition = spriteBone.position; transform.localRotation = spriteBone.rotation; transform.localScale = Vector3.one; transforms[index] = transform; } } internal static void ResetBindPose(this SpriteSkin spriteSkin) { if (!spriteSkin.isValid) throw new InvalidOperationException("SpriteSkin is not valid"); var spriteBones = spriteSkin.spriteRenderer.sprite.GetBones(); var boneTransforms = spriteSkin.boneTransforms; for (int i = 0; i < boneTransforms.Length; ++i) { var boneTransform = boneTransforms[i]; var spriteBone = spriteBones[i]; if (spriteBone.parentId != -1) { boneTransform.localPosition = spriteBone.position; boneTransform.localRotation = spriteBone.rotation; boneTransform.localScale = Vector3.one; } } } private static int GetHash(Matrix4x4 matrix) { unsafe { uint* b = (uint*)&matrix; { var c = (char*)b; return (int)math.hash(c, 16 * sizeof(float)); } } } internal static int CalculateTransformHash(this SpriteSkin spriteSkin) { int bits = 0; int boneTransformHash = GetHash(spriteSkin.transform.localToWorldMatrix) >> bits; bits++; foreach (var transform in spriteSkin.boneTransforms) { boneTransformHash ^= GetHash(transform.localToWorldMatrix) >> bits; bits = (bits + 1) % 8; } return boneTransformHash; } internal unsafe static void Deform(Sprite sprite, Matrix4x4 rootInv, NativeSlice vertices, NativeSlice tangents, NativeSlice boneWeights, NativeArray boneTransforms, NativeSlice bindPoses, NativeArray deformableVertices) { var verticesFloat3 = vertices.SliceWithStride(); var tangentsFloat4 = tangents.SliceWithStride(); var bindPosesFloat4x4 = bindPoses.SliceWithStride(); var spriteVertexCount = sprite.GetVertexCount(); var spriteVertexStreamSize = sprite.GetVertexStreamSize(); var boneTransformsFloat4x4 = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(boneTransforms.GetUnsafePtr(), boneTransforms.Length, Allocator.None); byte* deformedPosOffset = (byte*)NativeArrayUnsafeUtility.GetUnsafePtr(deformableVertices); NativeSlice deformableVerticesFloat3 = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice(deformedPosOffset, spriteVertexStreamSize, spriteVertexCount); NativeSlice deformableTangentsFloat4 = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice(deformedPosOffset, spriteVertexStreamSize, 1); // Just Dummy. if (sprite.HasVertexAttribute(Rendering.VertexAttribute.Tangent)) { byte* deformedTanOffset = deformedPosOffset + sprite.GetVertexStreamOffset(Rendering.VertexAttribute.Tangent); deformableTangentsFloat4 = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice(deformedTanOffset, spriteVertexStreamSize, spriteVertexCount); } #if ENABLE_UNITY_COLLECTIONS_CHECKS var handle1 = CreateSafetyChecks(ref boneTransformsFloat4x4); var handle2 = CreateSafetyChecks(ref deformableVerticesFloat3); var handle3 = CreateSafetyChecks(ref deformableTangentsFloat4); #endif if (sprite.HasVertexAttribute(Rendering.VertexAttribute.Tangent)) Deform(rootInv, verticesFloat3, tangentsFloat4, boneWeights, boneTransformsFloat4x4, bindPosesFloat4x4, deformableVerticesFloat3, deformableTangentsFloat4); else Deform(rootInv, verticesFloat3, boneWeights, boneTransformsFloat4x4, bindPosesFloat4x4, deformableVerticesFloat3); #if ENABLE_UNITY_COLLECTIONS_CHECKS DisposeSafetyChecks(handle1); DisposeSafetyChecks(handle2); DisposeSafetyChecks(handle3); #endif } internal static void Deform(float4x4 rootInv, NativeSlice vertices, NativeSlice boneWeights, NativeArray boneTransforms, NativeSlice bindPoses, NativeSlice deformed) { if (boneTransforms.Length == 0) return; for (var i = 0; i < boneTransforms.Length; i++) { var bindPoseMat = bindPoses[i]; var boneTransformMat = boneTransforms[i]; boneTransforms[i] = math.mul(rootInv, math.mul(boneTransformMat, bindPoseMat)); } for (var i = 0; i < vertices.Length; i++) { var bone0 = boneWeights[i].boneIndex0; var bone1 = boneWeights[i].boneIndex1; var bone2 = boneWeights[i].boneIndex2; var bone3 = boneWeights[i].boneIndex3; var vertex = vertices[i]; deformed[i] = math.transform(boneTransforms[bone0], vertex) * boneWeights[i].weight0 + math.transform(boneTransforms[bone1], vertex) * boneWeights[i].weight1 + math.transform(boneTransforms[bone2], vertex) * boneWeights[i].weight2 + math.transform(boneTransforms[bone3], vertex) * boneWeights[i].weight3; } } internal static void Deform(float4x4 rootInv, NativeSlice vertices, NativeSlice tangents, NativeSlice boneWeights, NativeArray boneTransforms, NativeSlice bindPoses, NativeSlice deformed, NativeSlice deformedTangents) { if(boneTransforms.Length == 0) return; for (var i = 0; i < boneTransforms.Length; i++) { var bindPoseMat = bindPoses[i]; var boneTransformMat = boneTransforms[i]; boneTransforms[i] = math.mul(rootInv, math.mul(boneTransformMat, bindPoseMat)); } for (var i = 0; i < vertices.Length; i++) { var bone0 = boneWeights[i].boneIndex0; var bone1 = boneWeights[i].boneIndex1; var bone2 = boneWeights[i].boneIndex2; var bone3 = boneWeights[i].boneIndex3; var vertex = vertices[i]; deformed[i] = math.transform(boneTransforms[bone0], vertex) * boneWeights[i].weight0 + math.transform(boneTransforms[bone1], vertex) * boneWeights[i].weight1 + math.transform(boneTransforms[bone2], vertex) * boneWeights[i].weight2 + math.transform(boneTransforms[bone3], vertex) * boneWeights[i].weight3; var tangent = new float4(tangents[i].xyz, 0.0f); tangent = math.mul(boneTransforms[bone0], tangent) * boneWeights[i].weight0 + math.mul(boneTransforms[bone1], tangent) * boneWeights[i].weight1 + math.mul(boneTransforms[bone2], tangent) * boneWeights[i].weight2 + math.mul(boneTransforms[bone3], tangent) * boneWeights[i].weight3; deformedTangents[i] = new float4(math.normalize(tangent.xyz), tangents[i].w); } } internal unsafe static void Deform(Sprite sprite, Matrix4x4 invRoot, Transform[] boneTransformsArray, NativeArray deformVertexData) { Debug.Assert(sprite != null); Debug.Assert(sprite.GetVertexCount() == (deformVertexData.Length / sprite.GetVertexStreamSize())); var vertices = sprite.GetVertexAttribute(UnityEngine.Rendering.VertexAttribute.Position); var tangents = sprite.GetVertexAttribute(UnityEngine.Rendering.VertexAttribute.Tangent); var boneWeights = sprite.GetVertexAttribute(UnityEngine.Rendering.VertexAttribute.BlendWeight); var bindPoses = sprite.GetBindPoses(); Debug.Assert(bindPoses.Length == boneTransformsArray.Length); Debug.Assert(boneWeights.Length == sprite.GetVertexCount()); var boneTransforms = new NativeArray(boneTransformsArray.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory); for (var i = 0; i < boneTransformsArray.Length; ++i) boneTransforms[i] = boneTransformsArray[i].localToWorldMatrix; Deform(sprite, invRoot, vertices, tangents, boneWeights, boneTransforms, bindPoses, deformVertexData); boneTransforms.Dispose(); } #if ENABLE_UNITY_COLLECTIONS_CHECKS private static AtomicSafetyHandle CreateSafetyChecks(ref NativeArray array) where T : struct { var handle = AtomicSafetyHandle.Create(); AtomicSafetyHandle.SetAllowSecondaryVersionWriting(handle, true); AtomicSafetyHandle.UseSecondaryVersion(ref handle); NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array, handle); return handle; } private static AtomicSafetyHandle CreateSafetyChecks(ref NativeSlice array) where T : struct { var handle = AtomicSafetyHandle.Create(); AtomicSafetyHandle.SetAllowSecondaryVersionWriting(handle, true); AtomicSafetyHandle.UseSecondaryVersion(ref handle); NativeSliceUnsafeUtility.SetAtomicSafetyHandle(ref array, handle); return handle; } private static void DisposeSafetyChecks(AtomicSafetyHandle handle) { AtomicSafetyHandle.Release(handle); } #endif internal static void Bake(this SpriteSkin spriteSkin, NativeArray deformVertexData) { if (!spriteSkin.isValid) throw new Exception("Bake error: invalid SpriteSkin"); var sprite = spriteSkin.spriteRenderer.sprite; var boneTransformsArray = spriteSkin.boneTransforms; Deform(sprite, Matrix4x4.identity, boneTransformsArray, deformVertexData); } internal unsafe static void CalculateBounds(this SpriteSkin spriteSkin) { Debug.Assert(spriteSkin.isValid); var sprite = spriteSkin.sprite; var deformVertexData = new NativeArray(sprite.GetVertexStreamSize() * sprite.GetVertexCount(), Allocator.Temp, NativeArrayOptions.UninitializedMemory); void* dataPtr = NativeArrayUnsafeUtility.GetUnsafePtr(deformVertexData); var deformedPosSlice = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice(dataPtr, sprite.GetVertexStreamSize(), sprite.GetVertexCount()); #if ENABLE_UNITY_COLLECTIONS_CHECKS NativeSliceUnsafeUtility.SetAtomicSafetyHandle(ref deformedPosSlice, NativeArrayUnsafeUtility.GetAtomicSafetyHandle(deformVertexData)); #endif spriteSkin.Bake(deformVertexData); UpdateBounds(spriteSkin, deformVertexData); deformVertexData.Dispose(); } internal static Bounds CalculateSpriteSkinBounds(NativeSlice deformablePositions) { float3 min = deformablePositions[0]; float3 max = deformablePositions[0]; for (int j = 1; j < deformablePositions.Length; ++j) { min = math.min(min, deformablePositions[j]); max = math.max(max, deformablePositions[j]); } float3 ext = (max - min) * 0.5F; float3 ctr = min + ext; Bounds bounds = new Bounds(); bounds.center = ctr; bounds.extents = ext; return bounds; } internal static unsafe void UpdateBounds(this SpriteSkin spriteSkin, NativeArray deformedVertices) { byte* deformedPosOffset = (byte*)NativeArrayUnsafeUtility.GetUnsafePtr(deformedVertices); var spriteVertexCount = spriteSkin.sprite.GetVertexCount(); var spriteVertexStreamSize = spriteSkin.sprite.GetVertexStreamSize(); NativeSlice deformedPositions = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice(deformedPosOffset, spriteVertexStreamSize, spriteVertexCount); #if ENABLE_UNITY_COLLECTIONS_CHECKS var handle = CreateSafetyChecks(ref deformedPositions); #endif spriteSkin.bounds = CalculateSpriteSkinBounds(deformedPositions); #if ENABLE_UNITY_COLLECTIONS_CHECKS DisposeSafetyChecks(handle); #endif InternalEngineBridge.SetLocalAABB(spriteSkin.spriteRenderer, spriteSkin.bounds); } } [BurstCompile] internal static class BurstedSpriteSkinUtilities { [BurstCompile] internal static bool ValidateBoneWeights(in NativeSlice boneWeights, int bindPoseCount) { var boneWeightCount = boneWeights.Length; for (var i = 0; i < boneWeightCount; ++i) { var boneWeight = boneWeights[i]; var idx0 = boneWeight.boneIndex0; var idx1 = boneWeight.boneIndex1; var idx2 = boneWeight.boneIndex2; var idx3 = boneWeight.boneIndex3; if ((idx0 < 0 || idx0 >= bindPoseCount) || (idx1 < 0 || idx1 >= bindPoseCount) || (idx2 < 0 || idx2 >= bindPoseCount) || (idx3 < 0 || idx3 >= bindPoseCount)) return false; } return true; } } }