using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using System.Threading.Tasks;
using UnityEngine.Profiling;
using VRMShaders;
namespace UniGLTF
{
///
/// GLTF importer
///
public class ImporterContext : IResponsibilityForDestroyObjects
{
public ITextureDescriptorGenerator TextureDescriptorGenerator { get; protected set; }
public IMaterialDescriptorGenerator MaterialDescriptorGenerator { get; protected set; }
public TextureFactory TextureFactory { get; }
public MaterialFactory MaterialFactory { get; }
public AnimationClipFactory AnimationClipFactory { get; }
public IReadOnlyDictionary ExternalObjectMap;
///
/// UnityObject の 生成(LoadAsync) と 破棄(Dispose) を行う。
/// LoadAsync が成功した場合、返り値(RuntimeGltfInstance) に破棄する責務を移動させる。
///
/// Jsonからデシリアライズされた GLTF 情報など
/// 外部オブジェクトのリスト(主にScriptedImporterのRemapで使う)
/// Textureロードをカスタマイズする
/// Materialロードをカスタマイズする(URP向け)
public ImporterContext(
GltfData data,
IReadOnlyDictionary externalObjectMap = null,
ITextureDeserializer textureDeserializer = null,
IMaterialDescriptorGenerator materialGenerator = null)
{
Data = data;
TextureDescriptorGenerator = new GltfTextureDescriptorGenerator(Data);
MaterialDescriptorGenerator = materialGenerator ?? new GltfMaterialDescriptorGenerator();
ExternalObjectMap = externalObjectMap ?? new Dictionary();
textureDeserializer = textureDeserializer ?? new UnityTextureDeserializer();
TextureFactory = new TextureFactory(textureDeserializer, ExternalObjectMap
.Where(x => x.Value is Texture)
.ToDictionary(x => x.Key, x => (Texture)x.Value),
Data.MigrationFlags.IsRoughnessTextureValueSquared);
MaterialFactory = new MaterialFactory(ExternalObjectMap
.Where(x => x.Value is Material)
.ToDictionary(x => x.Key, x => (Material)x.Value));
AnimationClipFactory = new AnimationClipFactory(ExternalObjectMap
.Where(x => x.Value is AnimationClip)
.ToDictionary(x => x.Key, x => (AnimationClip)x.Value));
}
#region Source
public GltfData Data { get; }
public String Json => Data.Json;
public glTF GLTF => Data.GLTF;
#endregion
// configuration
///
/// GLTF から Unity に変換するときに反転させる軸
///
public Axes InvertAxis = Axes.Z;
public static List UnsupportedExtensions = new List
{
// https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_draco_mesh_compression
"KHR_draco_mesh_compression",
};
#region Load. Build unity objects
public virtual async Task LoadAsync(IAwaitCaller awaitCaller, Func MeasureTime = null)
{
if (awaitCaller == null)
{
throw new ArgumentNullException();
}
if (MeasureTime == null)
{
MeasureTime = new ImporterContextSpeedLog().MeasureTime;
}
if (GLTF.extensionsRequired != null)
{
var sb = new List();
foreach (var required in GLTF.extensionsRequired)
{
if (UnsupportedExtensions.Contains(required))
{
sb.Add(required);
}
}
if (sb.Any())
{
throw new UniGLTFNotSupportedException(string.Join(", ", sb) + " is not supported");
}
}
using (MeasureTime("LoadTextures"))
{
await LoadTexturesAsync(awaitCaller);
}
using (MeasureTime("LoadMaterials"))
{
await LoadMaterialsAsync(awaitCaller);
}
await LoadGeometryAsync(awaitCaller, MeasureTime);
using (MeasureTime("AnimationImporter"))
{
await LoadAnimationAsync(awaitCaller);
await SetupAnimationsAsync(awaitCaller);
}
await OnLoadHierarchy(awaitCaller, MeasureTime);
return RuntimeGltfInstance.AttachTo(Root, this);
}
public virtual async Task LoadAnimationAsync(IAwaitCaller awaitCaller)
{
if (GLTF.animations != null && GLTF.animations.Any())
{
foreach (var (key, gltfAnimation) in Enumerable.Zip(AnimationImporterUtil.EnumerateSubAssetKeys(GLTF), GLTF.animations, (x, y) => (x, y)))
{
await AnimationClipFactory.LoadAnimationClipAsync(key, () =>
{
var clip = AnimationImporterUtil.ConvertAnimationClip(Data, gltfAnimation, InvertAxis.Create());
return Task.FromResult(clip);
});
}
await awaitCaller.NextFrame();
}
}
///
/// AnimationClips を AnimationComponent に載せる
///
protected virtual async Task SetupAnimationsAsync(IAwaitCaller awaitCaller)
{
if (AnimationClipFactory.LoadedClipKeys.Count == 0) return;
var animation = Root.AddComponent();
for (var clipIdx = 0; clipIdx < AnimationClipFactory.LoadedClipKeys.Count; ++clipIdx)
{
var key = AnimationClipFactory.LoadedClipKeys[clipIdx];
var clip = AnimationClipFactory.GetAnimationClip(key);
animation.AddClip(clip, key.Name);
if (clipIdx == 0)
{
animation.clip = clip;
}
}
await awaitCaller.NextFrame();
}
protected virtual async Task LoadGeometryAsync(IAwaitCaller awaitCaller, Func MeasureTime)
{
var inverter = InvertAxis.Create();
var meshImporter = new MeshImporter();
if (GLTF.meshes.Count > 0)
{
for (var i = 0; i < GLTF.meshes.Count; ++i)
{
var index = i;
using (MeasureTime("ReadMesh"))
{
var meshContext = await awaitCaller.Run(() => meshImporter.ReadMesh(Data, index, inverter));
var meshWithMaterials = await BuildMeshAsync(awaitCaller, MeasureTime, meshContext, index);
Meshes.Add(meshWithMaterials);
}
}
await awaitCaller.NextFrame();
}
if (GLTF.nodes.Count > 0)
{
using (MeasureTime("LoadNodes"))
{
Profiler.BeginSample("ImporterContext.LoadNodes");
for (var i = 0; i < GLTF.nodes.Count; i++)
{
Nodes.Add(NodeImporter.ImportNode(GLTF.nodes[i], i).transform);
}
Profiler.EndSample();
}
await awaitCaller.NextFrame();
}
using (MeasureTime("BuildHierarchy"))
{
var nodes = new List();
if (Nodes.Count > 0)
{
Profiler.BeginSample("NodeImporter.BuildHierarchy");
for (var i = 0; i < Nodes.Count; ++i)
{
nodes.Add(NodeImporter.BuildHierarchy(GLTF, i, Nodes, Meshes));
}
Profiler.EndSample();
await awaitCaller.NextFrame();
}
NodeImporter.FixCoordinate(GLTF, nodes, inverter);
// skinning
if (nodes.Count > 0)
{
Profiler.BeginSample("NodeImporter.SetupSkinning");
for (var i = 0; i < nodes.Count; ++i)
{
NodeImporter.SetupSkinning(Data, nodes, i, inverter);
}
Profiler.EndSample();
await awaitCaller.NextFrame();
}
if (Root == null)
{
Root = new GameObject("GLTF");
}
if (GLTF.rootnodes != null)
{
// connect root
foreach (var x in GLTF.rootnodes)
{
var t = nodes[x].Transform;
t.SetParent(Root.transform, false);
}
}
}
await awaitCaller.NextFrame();
}
public async Task LoadTexturesAsync(IAwaitCaller awaitCaller)
{
if (awaitCaller == null)
{
throw new ArgumentNullException();
}
var textures = TextureDescriptorGenerator.Get().GetEnumerable();
foreach (var param in textures)
{
var tex = await TextureFactory.GetTextureAsync(param, awaitCaller);
}
}
public async Task LoadMaterialsAsync(IAwaitCaller awaitCaller)
{
if (awaitCaller == null)
{
throw new ArgumentNullException();
}
if (Data.GLTF.materials == null || Data.GLTF.materials.Count == 0)
{
// no material. work around.
// TODO: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#default-material
var param = MaterialDescriptor.Default;
var material = await MaterialFactory.LoadAsync(param, TextureFactory.GetTextureAsync, awaitCaller);
}
else
{
for (int i = 0; i < Data.GLTF.materials.Count; ++i)
{
var param = MaterialDescriptorGenerator.Get(Data, i);
var material = await MaterialFactory.LoadAsync(param, TextureFactory.GetTextureAsync, awaitCaller);
}
}
}
protected virtual Task OnLoadHierarchy(IAwaitCaller awaitCaller, Func MeasureTime)
{
// do nothing
return Task.FromResult