using System; using System.Collections.Generic; using System.Reflection; using Rukhanka.Toolbox; using Unity.Collections; using Unity.Entities; using Unity.Mathematics; using Unity.Rendering; using UnityEngine; using Unity.Assertions; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; using Unity.Transforms; using FixedStringName = Unity.Collections.FixedString512Bytes; using Hash128 = Unity.Entities.Hash128; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// namespace Rukhanka.Hybrid { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// [TemporaryBakingType] public struct SkinnedMeshRendererRootBoneEntity: IComponentData { public Entity value; } [TemporaryBakingType] public struct SkinnedMeshSplitSubmeshEntities: IComponentData { } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public partial class SkinnedMeshBaker: Baker { public override unsafe void Bake(SkinnedMeshRenderer a) { if (a.sharedMesh == null) return; var smrHash = CalculateSkinnedMeshHash(a.sharedMesh); var isSMSBlobExists = TryGetBlobAssetReference(smrHash, out var smrBlobAsset); if (!isSMSBlobExists) { smrBlobAsset = CreateSkinnedMeshBlob(a, smrHash); AddBlobAssetWithCustomHash(ref smrBlobAsset, smrHash); } var rbe = new SkinnedMeshRendererRootBoneEntity() { value = _State.BakedEntityData->GetEntity(a.rootBone) }; var smrc = new SkinnedMeshRendererComponent() { smrInfoBlob = smrBlobAsset, animatedRigEntity = GetEntity(a.gameObject.GetComponentInParent(true), TransformUsageFlags.Dynamic), rootBoneIndexInRig = -1, nameHash = a.name.CalculateHash32() }; var e = GetEntity(a, TransformUsageFlags.Renderable); var splitOnSubmeshes = a.GetComponent(); // One or many render entities if (!splitOnSubmeshes || a.sharedMaterials.Length == 0) { AddEntityComponents(e, a, smrc, rbe, -1); } else { // In case of submesh splitting requested create one entity per material in source skinned mesh renderer and configure it as ordinary // skinned mesh entity var additionalEntities = new NativeArray(a.sharedMaterials.Length - 1, Allocator.Temp); CreateAdditionalEntities(additionalEntities, TransformUsageFlags.ManualOverride); var parent = new Parent() { Value = GetEntity(a.transform.parent, TransformUsageFlags.Dynamic) }; var lt = new LocalTransform() { Position = a.transform.localPosition, Rotation = a.transform.localRotation, Scale = a.transform.localScale.x }; // Original SMR with first material AddEntityComponents(e, a, smrc, rbe, 0); for (var i = 0; i < additionalEntities.Length; ++i) { var materialIndex = i + 1; var renderEntity = additionalEntities[i]; if (parent.Value != Entity.Null) AddComponent(renderEntity, parent); AddComponent(renderEntity, lt); AddComponent(renderEntity); AddEntityComponents(renderEntity, a, smrc, rbe, materialIndex); } } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void AddEntityComponents(Entity e, SkinnedMeshRenderer a, in SkinnedMeshRendererComponent smrc, SkinnedMeshRendererRootBoneEntity smrrbe, int materialIndex) { AddComponent(e, smrrbe); AddComponent(e, smrc); if (a.updateWhenOffscreen) { AddComponent(e); } CheckMaterialCompatibility(a); CreateRenderComponents(e, a, materialIndex); CreateSkinMatricesBuffer(e, a); CreateBlendShapeWeightsBuffer(e, a); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// Hash128 CalculateSkinnedMeshHash(Mesh m) { var rv = new Hash128(); #if UNITY_EDITOR var assetId = BakingUtils.GetAssetID(m); rv = new Hash128(assetId.x, assetId.y, 0, 0); #endif return rv; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CreateSkinnedMeshBonesBlob(ref BlobBuilder bb, ref SkinnedMeshInfoBlob smrBlob, SkinnedMeshRenderer r) { var bonesArr = bb.Allocate(ref smrBlob.bones, r.bones.Length); for (int j = 0; j < bonesArr.Length; ++j) { var b = r.bones[j]; ref var boneBlob = ref bonesArr[j]; if (b != null) { #if RUKHANKA_DEBUG_INFO bb.AllocateString(ref boneBlob.name, b.name); #endif var bn = new FixedStringName(b.name); boneBlob.hash = bn.CalculateHash32(); boneBlob.bindPose = r.sharedMesh.bindposes[j]; } } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CreateBlendShapesBlob(ref BlobBuilder bb, ref SkinnedMeshInfoBlob smrBlob, SkinnedMeshRenderer r) { var blendShapeHashesArr = bb.Allocate(ref smrBlob.blendShapes, r.sharedMesh.blendShapeCount); for (var j = 0; j < blendShapeHashesArr.Length; ++j) { ref var bs = ref blendShapeHashesArr[j]; var bsName = "blendShape." + r.sharedMesh.GetBlendShapeName(j); bs.hash = bsName.CalculateHash32(); #if RUKHANKA_DEBUG_INFO if (bsName.Length > 0) bb.AllocateString(ref bs.name, bsName); #endif } smrBlob.meshBlendShapesCount = r.sharedMesh.blendShapeCount; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CreateBoneWeightsDataBlob(ref BlobBuilder bb, ref SkinnedMeshInfoBlob smrBlob, SkinnedMeshRenderer r) { CreateBoneWeightsIndicesBlob(ref bb, ref smrBlob, r); smrBlob.meshBoneWeightsCount = r.sharedMesh.GetAllBoneWeights().Length; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CreateBoneWeightsIndicesBlob(ref BlobBuilder bb, ref SkinnedMeshInfoBlob smrBlob, SkinnedMeshRenderer r) { var mesh = r.sharedMesh; var allBoneWeights = mesh.GetBonesPerVertex(); using var outArr = new NativeArray(allBoneWeights.Length, Allocator.TempJob); var computeAbsoluteOffsetsJob = new ComputeAbsoluteBoneWeightsIndicesOffsetsJob() { bonesPerVertex = allBoneWeights, outIndicesArr = outArr }; computeAbsoluteOffsetsJob.Run(); var ba = bb.Allocate(ref smrBlob.boneWeightsIndices, allBoneWeights.Length); //UnsafeUtility.MemCpy(ba.GetUnsafePtr(), allBoneWeights.GetUnsafeReadOnlyPtr(), allBoneWeights.Length * UnsafeUtility.SizeOf()); for (var i = 0; i < ba.Length; ++i) { ba[i] = outArr[i]; } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// BlobAssetReference CreateSkinnedMeshBlob(SkinnedMeshRenderer r, Hash128 smrHash) { var bb = new BlobBuilder(Allocator.Temp); ref var smrBlob = ref bb.ConstructRoot(); smrBlob.hash = smrHash; #if RUKHANKA_DEBUG_INFO if (r.name.Length > 0) bb.AllocateString(ref smrBlob.skeletonName, r.name); var startTimeMarker = Time.realtimeSinceStartup; #endif CreateSkinnedMeshBonesBlob(ref bb, ref smrBlob, r); CreateBlendShapesBlob(ref bb, ref smrBlob, r); CreateBoneWeightsDataBlob(ref bb, ref smrBlob, r); smrBlob.meshVerticesCount = r.sharedMesh.vertexCount; #if RUKHANKA_DEBUG_INFO var dt = Time.realtimeSinceStartupAsDouble - startTimeMarker; smrBlob.bakingTime = (float)dt; #endif var rv = bb.CreateBlobAssetReference(Allocator.Persistent); return rv; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CreateRenderComponents(Entity renderEntity, SkinnedMeshRenderer a, int materialIndex) { var mmIndices = new MaterialMeshIndex[a.sharedMaterials.Length]; for (var i = 0; i < mmIndices.Length; ++i) { mmIndices[i] = new () { MeshIndex = 0, MaterialIndex = i, SubMeshIndex = i }; } var rma = new RenderMeshArray(a.sharedMaterials, new [] { a.sharedMesh }, mmIndices); var rmd = new RenderMeshDescription(a); var materialBaseIndex = math.select(0, materialIndex, materialIndex >= 0); var materialsCount = math.select(a.sharedMaterials.Length, 1, materialIndex >= 0); var mmi = MaterialMeshInfo.FromMaterialMeshIndexRange(materialBaseIndex, materialsCount); var materialList = new ReadOnlySpan(rma.GetMaterials(mmi).ToArray()); var componentFlags = RenderMeshUtility.EntitiesGraphicsComponentFlags.UseRenderMeshArray; componentFlags.AppendMotionAndProbeFlags(rmd, false); componentFlags.AppendPerVertexMotionPassFlag(materialList); componentFlags.AppendDepthSortedFlag(rma.GetMaterials(mmi)); AddComponent(renderEntity, RenderMeshUtility.ComputeComponentTypes(componentFlags)); GetLayer(a); SetSharedComponent(renderEntity, rmd.FilterSettings); SetSharedComponentManaged(renderEntity, rma); SetComponent(renderEntity, mmi); AddComponent(renderEntity); AddMeshRendererBakingData(renderEntity, a); AddLODComponents(renderEntity, a); var rbb = a.localBounds.ToAABB(); if (a.rootBone != null) { var boundsTransform = math.mul(a.transform.worldToLocalMatrix, a.rootBone.localToWorldMatrix); rbb = AABB.Transform(boundsTransform, rbb); } SetComponent(renderEntity, new RenderBounds { Value = rbb }); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void AddLODComponents(Entity renderEntity, SkinnedMeshRenderer a) { var lodGroup = a.GetComponentInParent(true); var lge = GetEntity(lodGroup, TransformUsageFlags.Renderable); var lodMask = GetLODMask(lodGroup, a); if (lge == Entity.Null || lodMask == -1) return; var mlc = new MeshLODComponent () { Group = lge, LODMask = lodMask }; AddComponent(renderEntity, mlc); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// int GetLODMask(LODGroup lodGroup, SkinnedMeshRenderer a) { if (lodGroup == null) return -1; var lods = lodGroup.GetLODs(); int lodGroupMask = 0; // Find the renderer inside the LODGroup for (int i = 0; i < lods.Length; ++i) { foreach (var renderer in lods[i].renderers) { if (renderer == a) { lodGroupMask |= (1 << i); } } } return lodGroupMask > 0 ? lodGroupMask : -1; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// unsafe void AddMeshRendererBakingData(Entity renderEntity, SkinnedMeshRenderer a) { var entitiesGraphicsSystemType = typeof(EntitiesGraphicsSystem); var meshRendererBakingDataTypeName = "Unity.Rendering.MeshRendererBakingData"; var meshRendererBakingDataType = entitiesGraphicsSystemType.Assembly.GetType(meshRendererBakingDataTypeName); var mrbdTypeIndex = TypeManager.GetTypeIndex(meshRendererBakingDataType); var typeInfo = TypeManager.GetTypeInfo(mrbdTypeIndex); var untypedComponentData = UnsafeUtility.Malloc(typeInfo.TypeSize, typeInfo.AlignmentInBytes, Allocator.Temp); UnityObjectRef meshRenderer = a; UnsafeUtility.MemCpy(untypedComponentData, &meshRenderer, typeInfo.TypeSize); UnsafeAddComponent(renderEntity, mrbdTypeIndex, typeInfo.TypeSize, untypedComponentData); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CreateSkinMatricesBuffer(Entity e, SkinnedMeshRenderer a) { DependsOn(a.transform); var mesh = a.sharedMesh; var boneWeights = mesh.GetAllBoneWeights(); var bindPoses = mesh.GetBindposes(); var bones = a.bones; Assert.IsTrue(bones.Length == bindPoses.Length); var hasSkinning = boneWeights.Length > 0 && bindPoses.Length > 0; if (!hasSkinning) return; var ownInverseTransform = a.worldToLocalMatrix; var skinMatrixBuf = AddBuffer(e); skinMatrixBuf.Resize(bindPoses.Length, NativeArrayOptions.UninitializedMemory); for (var i = 0; i < bones.Length; ++i) { var b = bones[i]; if (b == null) continue; DependsOn(b); var bp = bindPoses[i]; var boneMatRootSpace = math.mul(ownInverseTransform, b.localToWorldMatrix); var skinMatRootSpace = math.mul(boneMatRootSpace, bp); var sm = new Rukhanka.SkinMatrix() { Value = new float3x4(skinMatRootSpace.c0.xyz, skinMatRootSpace.c1.xyz, skinMatRootSpace.c2.xyz, skinMatRootSpace.c3.xyz) }; skinMatrixBuf[i] = sm; } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CreateBlendShapeWeightsBuffer(Entity e, SkinnedMeshRenderer a) { var mesh = a.sharedMesh; if (mesh.blendShapeCount == 0) return; var bswb = AddBuffer(e); for (var i = 0; i < mesh.blendShapeCount; ++i) { var srcBlendShapeWeight = a.GetBlendShapeWeight(i); var bsw = new Rukhanka.BlendShapeWeight() { Value = srcBlendShapeWeight }; bswb.Add(bsw); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void CheckMaterialCompatibility(SkinnedMeshRenderer a) { var materials = new List(); a.GetSharedMaterials(materials); for (var i = 0; i < materials.Count; ++i) { var m = materials[i]; if (m == null) continue; #if RUKHANKA_ENABLE_DEFORMATION_MOTION_VECTORS var deformationCompatibleShader = m.HasProperty("_DeformationParamsForMotionVectors"); #else var deformationCompatibleShader = m.HasProperty("_DeformedMeshIndex"); #endif if (!deformationCompatibleShader) { var s = $"Shader [{m.shader.name}] on [{a.name}] does not support skinning. This can result in incorrect rendering." + " Please see the documentation" + " for information about making a deformation-compatible shader"; Debug.LogWarning(s, a); } } } } }