357 lines
13 KiB
C#

using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using System.Threading.Tasks;
using UnityEngine.Profiling;
using VRMShaders;
namespace UniGLTF
{
/// <summary>
/// GLTF importer
/// </summary>
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<SubAssetKey, UnityEngine.Object> ExternalObjectMap;
/// <summary>
/// UnityObject の 生成(LoadAsync) と 破棄(Dispose) を行う。
/// LoadAsync が成功した場合、返り値(RuntimeGltfInstance) に破棄する責務を移動させる。
/// </summary>
/// <param name="data">Jsonからデシリアライズされた GLTF 情報など</param>
/// <param name="externalObjectMap">外部オブジェクトのリスト(主にScriptedImporterのRemapで使う)</param>
/// <param name="textureDeserializer">Textureロードをカスタマイズする</param>
/// <param name="materialGenerator">Materialロードをカスタマイズする(URP向け)</param>
public ImporterContext(
GltfData data,
IReadOnlyDictionary<SubAssetKey, UnityEngine.Object> externalObjectMap = null,
ITextureDeserializer textureDeserializer = null,
IMaterialDescriptorGenerator materialGenerator = null)
{
Data = data;
TextureDescriptorGenerator = new GltfTextureDescriptorGenerator(Data);
MaterialDescriptorGenerator = materialGenerator ?? new GltfMaterialDescriptorGenerator();
ExternalObjectMap = externalObjectMap ?? new Dictionary<SubAssetKey, UnityEngine.Object>();
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
/// <summary>
/// GLTF から Unity に変換するときに反転させる軸
/// </summary>
public Axes InvertAxis = Axes.Z;
public static List<string> UnsupportedExtensions = new List<string>
{
// 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<RuntimeGltfInstance> LoadAsync(IAwaitCaller awaitCaller, Func<string, IDisposable> MeasureTime = null)
{
if (awaitCaller == null)
{
throw new ArgumentNullException();
}
if (MeasureTime == null)
{
MeasureTime = new ImporterContextSpeedLog().MeasureTime;
}
if (GLTF.extensionsRequired != null)
{
var sb = new List<string>();
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();
}
}
/// <summary>
/// AnimationClips を AnimationComponent に載せる
/// </summary>
protected virtual async Task SetupAnimationsAsync(IAwaitCaller awaitCaller)
{
if (AnimationClipFactory.LoadedClipKeys.Count == 0) return;
var animation = Root.AddComponent<Animation>();
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<string, IDisposable> 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<NodeImporter.TransformWithSkin>();
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<string, IDisposable> MeasureTime)
{
// do nothing
return Task.FromResult<object>(null);
}
async Task<MeshWithMaterials> BuildMeshAsync(IAwaitCaller awaitCaller, Func<string, IDisposable> MeasureTime, MeshContext x, int i)
{
using (MeasureTime("BuildMesh"))
{
var meshWithMaterials = await MeshImporter.BuildMeshAsync(awaitCaller, MaterialFactory.GetMaterial, x);
var mesh = meshWithMaterials.Mesh;
// mesh name
if (string.IsNullOrEmpty(mesh.name))
{
mesh.name = string.Format("UniGLTF import#{0}", i);
}
var originalName = mesh.name;
for (int j = 1; Meshes.Any(y => y.Mesh.name == mesh.name); ++j)
{
mesh.name = string.Format("{0}({1})", originalName, j);
}
return meshWithMaterials;
}
}
#endregion
#region Imported
protected GameObject Root;
public List<Transform> Nodes = new List<Transform>();
public List<MeshWithMaterials> Meshes = new List<MeshWithMaterials>();
#endregion
/// <summary>
/// ImporterContextが所有する UnityEngine.Object を破棄する
/// </summary>
public virtual void Dispose()
{
foreach (var x in Meshes)
{
UnityObjectDestoyer.DestroyRuntimeOrEditor(x.Mesh);
}
Meshes.Clear();
AnimationClipFactory?.Dispose();
MaterialFactory?.Dispose();
TextureFactory?.Dispose();
}
/// <summary>
/// Root ヒエラルキーで使っているリソース
/// </summary>
/// <returns></returns>
public virtual void TransferOwnership(TakeResponsibilityForDestroyObjectFunc take)
{
foreach (var mesh in Meshes.ToArray())
{
take(SubAssetKey.Create(mesh.Mesh), mesh.Mesh);
Meshes.Remove(mesh);
}
AnimationClipFactory.TransferOwnership(take);
TextureFactory.TransferOwnership(take);
MaterialFactory.TransferOwnership(take);
}
}
}