using System.Collections.Generic;
using JetBrains.Annotations;
using Needle.Engine.Gltf;
using Needle.Engine.Settings;
using Needle.Engine.Utils;
using UnityEngine;
using UnityEngine.Playables;
namespace Needle.Engine.Timeline
{
internal struct TimelineAnimationKey
{
public object owner;
public AnimationClip clip;
}
// Timeline serialization must run before components so that the clips have been added
[UsedImplicitly, Priority(100)]
public class PlayableDirectorExtension : GltfExtensionHandlerBase
{
///
/// Map to only export an timeline animationclip once, the key contains the information used to check if the key was exported with the specific info
///
private readonly Dictionary exportedAnimations = new Dictionary();
private readonly List foundTimelines = new List();
public override void OnBeforeExport(GltfExportContext context)
{
base.OnBeforeExport(context);
exportedAnimations.Clear();
foundTimelines.Clear();
}
public override void OnAfterNodeExport(GltfExportContext context, Transform transform, int nodeId)
{
base.OnAfterNodeExport(context, transform, nodeId);
if (!transform.TryGetComponent(out PlayableDirector dir)) return;
if (!dir.playableAsset) return;
// create export context
var directorExport = new PlayableDirectorExportContext(dir, context);
context.RegisterValueResolver(new TimelineValueResolver(directorExport));
foundTimelines.Add(directorExport);
// var ext = new TimelineAssetExtension(directorExport);
// context.Bridge.AddNodeExtension(nodeId, TimelineAssetExtension.EXTENSION_NAME, ext);
}
public override void OnAfterExport(GltfExportContext context)
{
base.OnAfterExport(context);
foreach (var directorExport in foundTimelines)
{
var dir = directorExport.Director;
if (ExporterProjectSettings.instance.debugMode)
Debug.Log($"Export Timeline: {dir.name}", dir.gameObject);
// first export all animations in timeline (make sure to only export clips once)
foreach (var info in TimelineUtils.EnumerateAnimationClips(dir))
{
var clip = info.TimelineClip;
var owner = info.Owner;
if (EditorUtils.IsEditorOnly(owner, context.Root))
continue;
var animationClip = info.AnimationClip;
// we need to get the animation index when building the timeline model
// and we only want to export either each clip once of each infinite track once
object clipMapKey = info.IsInfiniteClip ? info.AnimationClip : (object)info.TimelineClip;
if (clip != null && directorExport.ClipMap.ContainsKey(clipMapKey)) continue;
var key = new TimelineAnimationKey()
{
owner = owner,
clip = animationClip,
};
// check if this exact animation has been exported before, if so just reuse the index
if (!exportedAnimations.TryGetValue(key, out var index) && animationClip)
{
try
{
TimelinePreview.DisableTimelinePreview();
if (ExporterProjectSettings.instance.debugMode)
Debug.Log($"Add timeline animation: {owner.name}/{animationClip.name}", animationClip);
index = context.Bridge.AddAnimationClip(animationClip, owner.transform, 1);
if (index < 0)
{
Debug.LogError(
$"Could not export animation clip: {owner.name}/{animationClip.name}. The object might be disabled/missing or it might require KHR_animation_pointer support.",
owner);
continue;
}
exportedAnimations.Add(key, index);
}
finally
{
TimelinePreview.ResetState();
}
}
directorExport.ClipMap.Add(clipMapKey, index);
}
}
}
}
}