using System.Collections.Generic; using System.Reflection; using GLTF.Schema; using Needle.Engine.Serialization; using Needle.Engine.Utils; using Newtonsoft.Json.Linq; using UnityEngine; namespace Needle.Engine.Gltf.UnityGltf { public class UnityGltfPersistentAssetExtension : IExtension, IAssetExtension { public const string EXTENSION_NAME = "NEEDLE_persistent_assets"; private readonly GltfExportContext context; private readonly List assets = new List(); private readonly List failedToSerialize = new List(); // AnimatorControllerValueResolver needs to store the model until the whole scene was parsed // for cases when the controller is used on multiple objects for example // because we need to register the pointers per animator using the same controller private readonly List models = new List(); private readonly List currentlySerializing = new List(); private readonly List serialized = new List(); public bool CanAdd(object owner, Object asset) { // var go = asset as GameObject; // if (asset is Transform t) go = t.gameObject; // if (go) // { // if(!go.TryGetComponent(out _)) // return false; // if (ReferenceEquals(asset, owner)) // return false; // if (owner is Component comp && comp.transform == asset) // return false; // } return true; } public object GetPathOrAdd(Object asset, object owner, MemberInfo member) { if (failedToSerialize.Contains(asset)) return null; var index = this.assets.IndexOf(asset); if (index < 0) { var i = this.assets.Count; this.assets.Add(asset); try { currentlySerializing.Add(asset); this.models.Add(null); this.serialized.Add(null); // currently just used for playable asset object obj = asset; foreach (var valueResolver in context.ValueResolvers) { if (valueResolver.TryGetValue(context, owner, member, ref obj)) break; } if (obj is ISerializablePersistentAssetModel ser) { this.models[i] = ser; } else { if (obj == null) { // if an object can not be serialized we dont want to add it to the extension at all // we also have to remove the entries in the list to make sure the pointer indices are correct failedToSerialize.Add(asset); var assetIndex = this.assets.IndexOf(asset); this.assets.RemoveAt(assetIndex); this.models.RemoveAt(assetIndex); this.serialized.RemoveAt(assetIndex); return null; } else this.serialized[i] = Serialize(obj); } } finally { currentlySerializing.RemoveAt(currentlySerializing.Count - 1); } return EXTENSION_NAME.AsExtensionPointer(i); } // if the asset was previously seen or is currently being serialized if (currentlySerializing.Contains(asset) || models[index] != null || serialized[index] != null) { if (models[index] != null) { var existing = models[index]; if (existing != null) { existing.OnNewObjectDiscovered(asset, owner, member, context); } } return EXTENSION_NAME.AsExtensionPointer(index); } return asset; } public void AddExtension(IGltfBridge bridge) { if (assets.Count > 0) { bridge.AddExtension(EXTENSION_NAME, this); for (var index = 0; index < models.Count; index++) { var val = models[index]; if (val == null) continue; var ser = Serialize(val); if (ser == null) continue; serialized[index] = ser; } } } public UnityGltfPersistentAssetExtension(GltfExportContext context) { this.context = context; } public JProperty Serialize() { var obj = new JObject(); var arr = new JArray(); obj.Add("assets", arr); for (var index = 0; index < serialized.Count; index++) { var ser = serialized[index]; if (ser != null) { arr.Add(ser); var asset = assets[index]; if (asset && ser is JObject) { ser["__type"] = asset.GetType().GetTypeInformation(); if (ser["guid"] == null) ser["guid"] = asset.GetId(); } } } return new JProperty(EXTENSION_NAME, obj); } public IExtension Clone(GLTFRoot root) { var ext = new UnityGltfPersistentAssetExtension(context); ext.assets.AddRange(this.assets); ext.models.AddRange(this.models); return ext; } private JToken Serialize(object obj) { var ser = context.Serializer.Serialize(obj); var res = JToken.Parse(ser); return res; } } }