using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using UniGLTF; using UnityEngine; using VRMShaders; namespace UniVRM10 { /// /// High-level VRM-1.0 loading API. /// public static class Vrm10 { /// /// You can receive the thumbnail texture and the meta information. /// `vrm10Meta` will be available if the model was vrm-1.0. /// `vrm0Meta` will be available if the model was vrm-0.x. /// public delegate void VrmMetaInformationCallback(Texture2D thumbnail, UniGLTF.Extensions.VRMC_vrm.Meta vrm10Meta, Migration.Vrm0Meta vrm0Meta); /// /// Load the VRM file from the path. /// /// You should call this on Unity main thread. /// This will throw Exceptions (include OperationCanceledException). /// /// vrm file path /// if true, this loader can load the vrm-0.x model as vrm-1.0 model with migration. /// the flag of generating the control rig provides bone manipulation unified between models. /// if true, show meshes when loaded. /// this loader use specified await strategy. /// this loader use specified texture deserialization strategy. /// this loader use specified material generation strategy. /// return callback that notify meta information before loading. /// CancellationToken /// vrm-1.0 instance. Maybe return null if unexpected error was raised. public static async Task LoadPathAsync( string path, bool canLoadVrm0X = true, ControlRigGenerationOption controlRigGenerationOption = ControlRigGenerationOption.Generate, bool showMeshes = true, IAwaitCaller awaitCaller = null, ITextureDeserializer textureDeserializer = null, IMaterialDescriptorGenerator materialGenerator = null, VrmMetaInformationCallback vrmMetaInformationCallback = null, CancellationToken ct = default) { if (awaitCaller == null) { awaitCaller = Application.isPlaying ? (IAwaitCaller)new RuntimeOnlyAwaitCaller() : (IAwaitCaller)new ImmediateCaller(); } return await LoadAsync( path, System.IO.File.ReadAllBytes(path), canLoadVrm0X, controlRigGenerationOption, showMeshes, awaitCaller, textureDeserializer, materialGenerator, vrmMetaInformationCallback, ct); } /// /// Load the VRM file from the binary. /// /// You should call this on Unity main thread. /// This will throw Exceptions (include OperationCanceledException). /// /// vrm file data /// if true, this loader can load the vrm-0.x model as vrm-1.0 model with migration. /// the flag of generating the control rig provides bone manipulation unified between models. /// if true, show meshes when loaded. /// this loader use specified await strategy. /// this loader use specified texture deserialization strategy. /// this loader use specified material generation strategy. /// return callback that notify meta information before loading. /// CancellationToken /// vrm-1.0 instance. Maybe return null if unexpected error was raised. public static async Task LoadBytesAsync( byte[] bytes, bool canLoadVrm0X = true, ControlRigGenerationOption controlRigGenerationOption = ControlRigGenerationOption.Generate, bool showMeshes = true, IAwaitCaller awaitCaller = null, ITextureDeserializer textureDeserializer = null, IMaterialDescriptorGenerator materialGenerator = null, VrmMetaInformationCallback vrmMetaInformationCallback = null, CancellationToken ct = default) { if (awaitCaller == null) { awaitCaller = Application.isPlaying ? (IAwaitCaller)new RuntimeOnlyAwaitCaller() : (IAwaitCaller)new ImmediateCaller(); } return await LoadAsync( string.Empty, bytes, canLoadVrm0X, controlRigGenerationOption, showMeshes, awaitCaller, textureDeserializer, materialGenerator, vrmMetaInformationCallback, ct); } private static async Task LoadAsync( string name, byte[] bytes, bool canLoadVrm0X, ControlRigGenerationOption controlRigGenerationOption, bool showMeshes, IAwaitCaller awaitCaller, ITextureDeserializer textureDeserializer, IMaterialDescriptorGenerator materialGenerator, VrmMetaInformationCallback vrmMetaInformationCallback, CancellationToken ct) { ct.ThrowIfCancellationRequested(); if (awaitCaller == null) { throw new ArgumentNullException(); } using (var gltfData = new GlbLowLevelParser(name, bytes).Parse()) { // 1. Try loading as vrm-1.0 var instance = await TryLoadingAsVrm10Async( gltfData, controlRigGenerationOption, showMeshes, awaitCaller, textureDeserializer, materialGenerator, vrmMetaInformationCallback, ct); if (instance != null) { if (ct.IsCancellationRequested) { UnityObjectDestroyer.DestroyRuntimeOrEditor(instance.gameObject); ct.ThrowIfCancellationRequested(); } return instance; } // 2. Stop loading if not allowed migration. if (!canLoadVrm0X) { throw new Exception($"Failed to load as VRM 1.0: {name}"); } // 3. Try migration from vrm-0.x into vrm-1.0 var migratedInstance = await TryMigratingFromVrm0XAsync( gltfData, controlRigGenerationOption, showMeshes, awaitCaller, textureDeserializer, materialGenerator, vrmMetaInformationCallback, ct); if (migratedInstance != null) { if (ct.IsCancellationRequested) { UnityObjectDestroyer.DestroyRuntimeOrEditor(migratedInstance.gameObject); ct.ThrowIfCancellationRequested(); } return migratedInstance; } // 4. Failed loading. throw new Exception($"Failed to load: {name}"); } } private static async Task TryLoadingAsVrm10Async( GltfData gltfData, ControlRigGenerationOption controlRigGenerationOption, bool showMeshes, IAwaitCaller awaitCaller, ITextureDeserializer textureDeserializer, IMaterialDescriptorGenerator materialGenerator, VrmMetaInformationCallback vrmMetaInformationCallback, CancellationToken ct) { ct.ThrowIfCancellationRequested(); if (awaitCaller == null) { throw new ArgumentNullException(); } var vrm10Data = await awaitCaller.Run(() => Vrm10Data.Parse(gltfData)); ct.ThrowIfCancellationRequested(); if (vrm10Data == null) { // NOTE: Failed to parse as VRM 1.0. return null; } return await LoadVrm10DataAsync( vrm10Data, null, controlRigGenerationOption, showMeshes, awaitCaller, textureDeserializer, materialGenerator, vrmMetaInformationCallback, ct); } private static async Task TryMigratingFromVrm0XAsync( GltfData gltfData, ControlRigGenerationOption controlRigGenerationOption, bool showMeshes, IAwaitCaller awaitCaller, ITextureDeserializer textureDeserializer, IMaterialDescriptorGenerator materialGenerator, VrmMetaInformationCallback vrmMetaInformationCallback, CancellationToken ct) { ct.ThrowIfCancellationRequested(); if (awaitCaller == null) { throw new ArgumentNullException(); } Vrm10Data migratedVrm10Data = default; MigrationData migrationData = default; using (var migratedGltfData = await awaitCaller.Run(() => Vrm10Data.Migrate(gltfData, out migratedVrm10Data, out migrationData))) { ct.ThrowIfCancellationRequested(); if (migratedVrm10Data == null) { throw new Exception(migrationData?.Message ?? "Failed to migrate."); } var migratedVrm10Instance = await LoadVrm10DataAsync( migratedVrm10Data, migrationData, controlRigGenerationOption, showMeshes, awaitCaller, textureDeserializer, materialGenerator, vrmMetaInformationCallback, ct); if (migratedVrm10Instance == null) { throw new Exception(migrationData?.Message ?? "Failed to load migrated."); } return migratedVrm10Instance; } } private static async Task LoadVrm10DataAsync( Vrm10Data vrm10Data, MigrationData migrationData, ControlRigGenerationOption controlRigGenerationOption, bool showMeshes, IAwaitCaller awaitCaller, ITextureDeserializer textureDeserializer, IMaterialDescriptorGenerator materialGenerator, VrmMetaInformationCallback vrmMetaInformationCallback, CancellationToken ct) { ct.ThrowIfCancellationRequested(); if (awaitCaller == null) { throw new ArgumentNullException(); } if (vrm10Data == null) { throw new ArgumentNullException(nameof(vrm10Data)); } using (var loader = new Vrm10Importer( vrm10Data, textureDeserializer: textureDeserializer, materialGenerator: materialGenerator, useControlRig: controlRigGenerationOption != ControlRigGenerationOption.None)) { // 1. Load meta information if callback was available. if (vrmMetaInformationCallback != null) { var thumbnail = await loader.LoadVrmThumbnailAsync(); if (migrationData != null) { vrmMetaInformationCallback(thumbnail, default, migrationData.OriginalMetaBeforeMigration); } else { vrmMetaInformationCallback(thumbnail, vrm10Data.VrmExtension.Meta, default); } } // 2. Load // NOTE: Current Vrm10Importer.LoadAsync implementation CAN'T ABORT. var gltfInstance = await loader.LoadAsync(awaitCaller); if (gltfInstance == null) { throw new Exception("Failed to load by unknown reason."); } var vrm10Instance = gltfInstance.GetComponent(); if (vrm10Instance == null) { gltfInstance.Dispose(); throw new Exception("Failed to load as VRM by unknown reason."); } if (ct.IsCancellationRequested) { // NOTE: Destroy before showing meshes if canceled. gltfInstance.Dispose(); ct.ThrowIfCancellationRequested(); } if (showMeshes) { gltfInstance.ShowMeshes(); } return vrm10Instance; } } } }