Files
AR-Menu/Library/PackageCache/com.needle.engine-exporter@8c046140a1d9/Gltf/Editor/UnityGltf/UnityGltfComponentHandler.cs
2025-11-30 08:35:03 +02:00

126 lines
4.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using JetBrains.Annotations;
using Needle.Engine.Core.Emitter;
using Needle.Engine.Utils;
using UnityEngine;
using UnityGLTF;
using UnityGLTF.JsonPointer;
using Debug = UnityEngine.Debug;
namespace Needle.Engine.Gltf.UnityGltf
{
[UsedImplicitly]
public class UnityGltfComponentHandler : GltfExtensionHandlerBase
{
private readonly List<UnityGltf_NEEDLE_components_Extension> extensions = new List<UnityGltf_NEEDLE_components_Extension>();
public override void OnBeforeExport(GltfExportContext context)
{
base.OnBeforeExport(context);
extensions.Clear();
if (context.Exporter is GLTFSceneExporter exp)
exp.RegisterResolver(this.componentJsonPathResolver);
}
public override void OnAfterNodeExport(GltfExportContext context, Transform transform, int nodeId)
{
var bridge = context.Bridge;
var components = transform.gameObject.GetKnownComponents(context.TypeRegistry);
if (components?.Count > 0)
{
foreach (var comp in components)
{
var type = comp.GetType();
if (context.TypeRegistry.TryGetImportedTypeInfo(type, out var ti) && !ti.IsInstalled)
{
Debug.LogWarning("Component " + type.Name + " is not installed in current project, used on " + comp.name + ", script location at " + ti.FilePath.AsLink(), comp);
}
// ScriptEmitter.RegisterExportedInGltfExtras(comp);
}
var ext = new UnityGltf_NEEDLE_components_Extension(transform, context, context, GltfValueResolver.Default, components);
bridge.AddNodeExtension(nodeId, UnityGltf_NEEDLE_components_Extension.EXTENSION_NAME, ext);
extensions.Add(ext);
for (var i = 0; i < components.Count; i++)
{
componentJsonPathResolver.Register(components[i], i);
}
}
}
private readonly Stopwatch watch = new Stopwatch();
private readonly StringBuilder timingLog = new StringBuilder();
private readonly ComponentIndexResolver componentJsonPathResolver = new ComponentIndexResolver();
public override void OnAfterExport(GltfExportContext context)
{
base.OnAfterExport(context);
var sum = 0f;
timingLog.Clear();
var noImpact = 0;
var noImpactSum = 0L;
foreach (var ext in extensions)
{
watch.Restart();
ext.OnAfterExport();
var elapsed = watch.ElapsedMilliseconds;
sum += elapsed;
if (elapsed > .1)
{
var t = ext.transform;
var postfix = t ? t.parent?.name + "/" : "";
timingLog.AppendLine($"{elapsed:0} ms \t{postfix}{ext.name}");
}
else
{
noImpact += 1;
noImpactSum += elapsed;
}
}
if (noImpact > 0) timingLog.AppendLine($"and {noImpact} more scripts exported in {noImpactSum:0.0} ms");
var name = !string.IsNullOrWhiteSpace(context.Path) ? Path.GetFileName(context.Path) : context.Path;
Debug.Log($"<b>Components</b>: <i>{name}</i> exported {extensions.Count} components: {sum:0} ms\n" + timingLog, context.Root);
watch.Stop();
timingLog.Clear();
}
}
internal class ComponentIndexResolver : IJsonPointerResolver
{
internal readonly Dictionary<Component, int> indices = new Dictionary<Component, int>();
public void Register(Component comp, int index)
{
if (!indices.ContainsKey(comp))
indices.Add(comp, index);
else indices[comp] = index;
}
public bool TryResolve(object target, ref string path)
{
// when animating gameObject values we dont want them to remap to components
// e.g. activeSelf should be created by the runtime node/Object3D in threejs if appropriate
if (target is GameObject)
{
return true;
}
if (target is Component comp && indices.TryGetValue(comp, out var id))
{
var propertyPathIndex = path.LastIndexOf("/", StringComparison.Ordinal);
var before = path.Substring(0, propertyPathIndex);
var propertyName = path.Substring(propertyPathIndex);
var basePath = before + "/extensions/" + UnityGltf_NEEDLE_components_Extension.GetPath(id);
path = basePath + propertyName;
return true;
}
return false;
}
}
}