#if UNITY_EDITOR using Unity.Burst; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; using UnityEditor.Animations; using UnityEngine; using Rukhanka.Toolbox; using Unity.Mathematics; using Hash128 = Unity.Entities.Hash128; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// namespace Rukhanka.Hybrid { [BurstCompile] public partial class AnimatorControllerBaker { uint motionCounter; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// static void AddTransitionBlob(RTP.Transition t, UnsafeList allStates, UnsafeList allParams, ref BlobBuilder bb, ref TransitionBlob tb) { #if RUKHANKA_DEBUG_INFO bb.AllocateString(ref tb.name, ref t.name); #endif var bbc = bb.Allocate(ref tb.conditions, t.conditions.Length); for (int ci = 0; ci < t.conditions.Length; ++ci) { ref var cb = ref bbc[ci]; var src = t.conditions[ci]; cb.conditionMode = src.conditionMode; cb.paramIdx = allParams.IndexOf(src.paramName); cb.threshold = src.threshold; #if RUKHANKA_DEBUG_INFO bb.AllocateString(ref cb.name, ref src.name); #endif } tb.hash = t.name.CalculateHash32(); tb.duration = t.duration; tb.exitTime = t.exitTime; tb.hasExitTime = t.hasExitTime; tb.offset = t.offset; tb.hasFixedDuration = t.hasFixedDuration; tb.targetStateId = allStates.IndexOf(t.targetStateHash); tb.interruptionSource = t.interruptionSource; tb.orderedInterruption = t.orderedInterruption; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// static void AddChildMotionBlob(RTP.ChildMotion cm, ref BlobBuilder bb, ref ChildMotionBlob cmb, in UnsafeList allParams) { cmb.threshold = cm.threshold; cmb.timeScale = cm.timeScale; cmb.mirror = cm.mirror; cmb.position2D = cm.position2D; cmb.directBlendParameterIndex = allParams.IndexOf(cm.directBlendParameterName); AddMotionBlob(cm.motion, ref bb, ref cmb.motion, allParams); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// static void AddMotionBlob(RTP.Motion m, ref BlobBuilder bb, ref MotionBlob mb, in UnsafeList allParams) { #if RUKHANKA_DEBUG_INFO bb.AllocateString(ref mb.name, ref m.name); #endif mb.type = m.type; mb.hash = (uint)m.name.GetHashCode(); mb.animationIndex = m.animationIndex; if (m.type != MotionBlob.Type.None && m.type != MotionBlob.Type.AnimationClip) { ref var bt = ref mb.blendTree; var bbm = bb.Allocate(ref bt.motions, m.blendTree.motions.Length); for (int i = 0; i < bbm.Length; ++i) { AddChildMotionBlob(m.blendTree.motions[i], ref bb, ref bbm[i], allParams); } bt.blendParameterIndex = allParams.IndexOf(m.blendTree.blendParameterName); bt.blendParameterYIndex = allParams.IndexOf(m.blendTree.blendParameterYName); bt.normalizeBlendValues = m.blendTree.normalizeBlendValues; #if RUKHANKA_DEBUG_INFO bb.AllocateString(ref bt.name, ref m.blendTree.name); #endif } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// static void AddStateBlob ( RTP.State s, ref BlobBuilder bb, ref StateBlob sb, UnsafeList anyStateTransitions, UnsafeList allStates, UnsafeList allParams ) { #if RUKHANKA_DEBUG_INFO bb.AllocateString(ref sb.name, ref s.name); #endif sb.hash = s.name.CalculateHash32(); sb.speed = s.speed; sb.speedMultiplierParameterIndex = allParams.IndexOf(s.speedMultiplierParameter); sb.timeParameterIndex = allParams.IndexOf(s.timeParameter); sb.cycleOffset = s.cycleOffset; sb.cycleOffsetParameterIndex = allParams.IndexOf(s.cycleOffsetParameter); if (!s.tag.IsEmpty) { #if RUKHANKA_DEBUG_INFO bb.AllocateString(ref sb.tag, ref s.tag); #endif sb.tagHash = s.tag.CalculateHash32(); } var bbt = bb.Allocate(ref sb.transitions, s.transitions.Length + anyStateTransitions.Length); // Any state transitions are first priority for (int ti = 0; ti < anyStateTransitions.Length; ++ti) { var ast = anyStateTransitions[ti]; // Do not add transitions to self according to flag if (ast.canTransitionToSelf || ast.targetStateHash != s.hashCode) AddTransitionBlob(ast, allStates, allParams, ref bb, ref bbt[ti]); } for (int ti = 0; ti < s.transitions.Length; ++ti) { var src = s.transitions[ti]; AddTransitionBlob(src, allStates, allParams, ref bb, ref bbt[ti + anyStateTransitions.Length]); } // Add motion AddMotionBlob(s.motion, ref bb, ref sb.motion, allParams); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// static void AddAllLayers(BlobBuilder bb, ref ControllerBlob c, RTP.Controller data) { var bbl = bb.Allocate(ref c.layers, data.layers.Length); // Clear synced timing value first for (int li = 0; li < data.layers.Length; ++li) { ref var l = ref bbl[li]; l.syncedTiming = -1; } for (int li = 0; li < data.layers.Length; ++li) { var src = data.layers[li]; ref var l = ref bbl[li]; #if RUKHANKA_DEBUG_INFO bb.AllocateString(ref l.name, ref src.name); #endif l.defaultStateIndex = src.defaultStateIndex; l.initialWeight = src.weight; l.blendingMode = src.blendMode; // States var bbs = bb.Allocate(ref l.states, src.states.Length); for (int si = 0; si < src.states.Length; ++si) { var s = src.states[si]; AddStateBlob(s, ref bb, ref bbs[si], src.anyStateTransitions, src.states, data.parameters); } l.avatarMaskBlobHash = src.avatarMaskBlobHash; l.syncedLayerIndex = src.syncedLayerIndex; if (src.syncedTiming) { ref var bl = ref bbl[l.syncedLayerIndex]; // Set synced timing index for base layer bl.syncedTiming = li; l.syncedTiming = 1; } } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// static void AddParameters(BlobBuilder bb, ref ControllerBlob c, in RTP.Controller data) { // Now place parameters in its original places as in authoring animator var bba = bb.Allocate(ref c.parameters, data.parameters.Length); for (int pi = 0; pi < data.parameters.Length; ++pi) { var src = data.parameters[pi]; ref var p = ref bba[pi]; p.defaultValue = src.defaultValue; #if RUKHANKA_DEBUG_INFO bb.AllocateString(ref p.name, ref src.name); #endif p.hash = data.parameters[pi].name.CalculateHash32(); p.type = src.type; } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public static unsafe BlobAssetReference CreateParametersPerfectHashTableBlobInternal(in NativeArray hashesArr) { if (!Perfect2HashTable.Build(hashesArr, out var pht, out var seed)) return default; using var bb = new BlobBuilder(Allocator.Temp); ref var ppb = ref bb.ConstructRoot(); ppb.seed = seed; var bbh = bb.Allocate(ref ppb.pht, pht.Length); UnsafeUtility.MemCpy(bbh.GetUnsafePtr(), pht.GetUnsafeReadOnlyPtr(), pht.Length * UnsafeUtility.SizeOf()); pht.Dispose(); var rv = bb.CreateBlobAssetReference(Allocator.Persistent); return rv; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// unsafe BlobAssetReference CreateParametersPerfectHashTableBlob(BlobAssetReference cb) { ref var parameters = ref cb.Value.parameters; // Create blob asset for perfect hash table, but only if number of parameters is big enough if (parameters.Length <= 0) return default; var hashesArr = new NativeArray(parameters.Length, Allocator.Temp); for (int l = 0; l < parameters.Length; ++l) { hashesArr[l] = parameters[l].hash; } var hasher = new xxHash3.StreamingState(); hasher.Update(hashesArr.GetUnsafeReadOnlyPtr(), hashesArr.Length * sizeof(uint)); var phtBlobHash = new Hash128(hasher.DigestHash128()); var blobAlreadyExists = TryGetBlobAssetReference(phtBlobHash, out var phtBlob); if (blobAlreadyExists) return phtBlob; var rv = CreateParametersPerfectHashTableBlobInternal(hashesArr); AddBlobAssetWithCustomHash(ref rv, phtBlobHash); return rv; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// [BurstCompile] static void BuildControllerBlobInternal(ref RTP.Controller controllerData, ref BlobBuilder bb, ref ControllerBlob c, in Hash128 hash) { c.hash = hash; #if RUKHANKA_DEBUG_INFO bb.AllocateString(ref c.name, ref controllerData.name); #endif AddParameters(bb, ref c, controllerData); AddAllLayers(bb, ref c, controllerData); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// BlobAssetReference BuildControllerBlob(AnimatorController controller, RigDefinitionAuthoring rd) { var controllerHash = BakingUtils.ComputeControllerHash(controller); // Try blob asset store first var isControllerAlreadyBaked = TryGetBlobAssetReference(controllerHash, out var controllerBlob); if (isControllerAlreadyBaked) return controllerBlob; // Try file cache controllerBlob = BlobCache.LoadBakedControllerFromCache(controller); if (controllerBlob != BlobAssetReference.Null) { // Don't forget to add loaded controller to blob asset store AddBlobAssetWithCustomHash(ref controllerBlob, controllerHash); return controllerBlob; } #if RUKHANKA_DEBUG_INFO var startTimeMarker = Time.realtimeSinceStartupAsDouble; #endif // Only after failed caches build controller from scratch var controllerDataCollector = new AnimatorControllerDataCollector(controller, rd); var controllerData = controllerDataCollector.Collect(); var bb = new BlobBuilder(Allocator.Temp); ref var c = ref bb.ConstructRoot(); BuildControllerBlobInternal(ref controllerData, ref bb, ref c, controllerHash); #if RUKHANKA_DEBUG_INFO var dt = Time.realtimeSinceStartupAsDouble - startTimeMarker; c.bakingTime = (float)dt; #endif var rv = bb.CreateBlobAssetReference(Allocator.Persistent); AddBlobAssetWithCustomHash(ref rv, controllerData.hash); // Save created controller to cache BlobCache.SaveBakedControllerToCache(controller, rv); return rv; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// BlobAssetReference BuildControllerAnimationHashesBlob(AnimatorController ac, Avatar avatar) { var bb = new BlobBuilder(Allocator.Temp); ref var cab = ref bb.ConstructRoot(); var animsBlobArr = bb.Allocate(ref cab.animations, ac.animationClips.Length); for (var i = 0; i < animsBlobArr.Length; ++i) { var anim = ac.animationClips[i]; animsBlobArr[i] = BakingUtils.ComputeAnimationHash(anim, avatar); } var rv = bb.CreateBlobAssetReference(Allocator.Persistent); AddBlobAsset(ref rv, out _); return rv; } } } #endif