diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/GltfUtility.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/GltfUtility.cs index 881f74b0a..dd2238c18 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/GltfUtility.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/GltfUtility.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.Threading.Tasks; using UnityEngine; @@ -14,12 +15,37 @@ namespace UniGLTF throw new FileNotFoundException(path); } - Debug.LogFormat("{0}", path); + if (awaitCaller == null) + { + Debug.LogWarning("GltfUtility.LoadAsync: awaitCaller argument is null. ImmediateCaller is used as the default fallback. When playing, we recommend RuntimeOnlyAwaitCaller."); + awaitCaller = new ImmediateCaller(); + } + using (GltfData data = new AutoGltfFileParser(path).Parse()) using (var loader = new UniGLTF.ImporterContext(data, materialGenerator: materialGenerator)) { return await loader.LoadAsync(awaitCaller); } } + + public static async Task LoadBytesAsync(string path, byte[] bytes, IAwaitCaller awaitCaller = null, IMaterialDescriptorGenerator materialGenerator = null) + { + if (bytes == null) + { + throw new ArgumentNullException("bytes"); + } + + if (awaitCaller == null) + { + Debug.LogWarning("GltfUtility.LoadAsync: awaitCaller argument is null. ImmediateCaller is used as the default fallback. When playing, we recommend RuntimeOnlyAwaitCaller."); + awaitCaller = new ImmediateCaller(); + } + + using (GltfData data = new GlbBinaryParser(bytes, path).Parse()) + using (var loader = new UniGLTF.ImporterContext(data, materialGenerator: materialGenerator)) + { + return await loader.LoadAsync(awaitCaller); + } + } } } diff --git a/Assets/VRM/Runtime/IO/VrmUtility.cs b/Assets/VRM/Runtime/IO/VrmUtility.cs index 5a7b60296..fec35b4d7 100644 --- a/Assets/VRM/Runtime/IO/VrmUtility.cs +++ b/Assets/VRM/Runtime/IO/VrmUtility.cs @@ -60,5 +60,57 @@ namespace VRM } } } + + + public static async Task LoadBytesAsync(string path, + byte[] bytes, + IAwaitCaller awaitCaller = null, + MaterialGeneratorCallback materialGeneratorCallback = null, + MetaCallback metaCallback = null, + bool loadAnimation = false + ) + { + if (bytes == null) + { + throw new ArgumentNullException("bytes"); + } + + if (awaitCaller == null) + { + Debug.LogWarning("VrmUtility.LoadAsync: awaitCaller argument is null. ImmediateCaller is used as the default fallback. When playing, we recommend RuntimeOnlyAwaitCaller."); + awaitCaller = new ImmediateCaller(); + } + + using (GltfData data = new GlbBinaryParser(bytes, path).Parse()) + { + try + { + var vrm = new VRMData(data); + IMaterialDescriptorGenerator materialGen = default; + if (materialGeneratorCallback != null) + { + materialGen = materialGeneratorCallback(vrm.VrmExtension); + } + using (var loader = new VRMImporterContext(vrm, materialGenerator: materialGen, loadAnimation: loadAnimation)) + { + if (metaCallback != null) + { + var meta = await loader.ReadMetaAsync(awaitCaller, true); + metaCallback(meta); + } + return await loader.LoadAsync(awaitCaller); + } + } + catch (NotVrm0Exception) + { + // retry + Debug.LogWarning("file extension is vrm. but not vrm ?"); + using (var loader = new UniGLTF.ImporterContext(data)) + { + return await loader.LoadAsync(awaitCaller); + } + } + } + } } } diff --git a/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs b/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs new file mode 100644 index 000000000..e3b53c9a8 --- /dev/null +++ b/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs @@ -0,0 +1,51 @@ +using System; +using System.Threading.Tasks; + +namespace VRMShaders +{ + /// + /// Runtime (Build 後と、Editor Playing) での非同期ロードを実現する AwaitCaller. + /// WebGL など Thread が無いもの向け + /// + public sealed class RuntimeOnlyNoThreadAwaitCaller : IAwaitCaller + { + private readonly NextFrameTaskScheduler _scheduler; + + public RuntimeOnlyNoThreadAwaitCaller() + { + _scheduler = new NextFrameTaskScheduler(); + } + + public Task NextFrame() + { + var tcs = new TaskCompletionSource(); + _scheduler.Enqueue(() => tcs.SetResult(default)); + return tcs.Task; + } + + public Task Run(Action action) + { + try + { + action(); + return Task.FromResult(null); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + public Task Run(Func action) + { + try + { + return Task.FromResult(action()); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + } +} \ No newline at end of file diff --git a/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs.meta b/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs.meta new file mode 100644 index 000000000..42bdc91ae --- /dev/null +++ b/Assets/VRMShaders/GLTF/IO/Runtime/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6d3630f9fecf62f4a8137907b0f7beb2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM_Samples/SimpleViewer/Plugins.meta b/Assets/VRM_Samples/SimpleViewer/Plugins.meta new file mode 100644 index 000000000..fdecce026 --- /dev/null +++ b/Assets/VRM_Samples/SimpleViewer/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ccb1849bc34bcd740ba2e17462125200 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM_Samples/SimpleViewer/Plugins/OpenFile.jslib b/Assets/VRM_Samples/SimpleViewer/Plugins/OpenFile.jslib new file mode 100644 index 000000000..48ba0ce57 --- /dev/null +++ b/Assets/VRM_Samples/SimpleViewer/Plugins/OpenFile.jslib @@ -0,0 +1,21 @@ +mergeInto(LibraryManager.library, { + WebGLFileDialog: function () { + const file_input_id = "file-input"; + var file_input = document.getElementById(file_input_id); + if (!file_input) { + file_input = document.createElement('input'); + file_input.setAttribute('type', 'file'); + file_input.setAttribute('id', file_input_id); + // file_input.setAttribute('accept', '.vrm') + file_input.style.visibility = 'hidden'; + file_input.onclick = function (event) { + event.target.value = null; + }; + file_input.onchange = function (event) { + SendMessage('Canvas', 'FileSelected', URL.createObjectURL(event.target.files[0])); + } + document.body.appendChild(file_input); + } + file_input.click(); + }, +}); \ No newline at end of file diff --git a/Assets/VRM_Samples/SimpleViewer/Plugins/OpenFile.jslib.meta b/Assets/VRM_Samples/SimpleViewer/Plugins/OpenFile.jslib.meta new file mode 100644 index 000000000..ee0cd7c4f --- /dev/null +++ b/Assets/VRM_Samples/SimpleViewer/Plugins/OpenFile.jslib.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: 2e8941ad33d65584f8ef2f8b829a05e7 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM_Samples/SimpleViewer/ViewerUI.cs b/Assets/VRM_Samples/SimpleViewer/ViewerUI.cs index d7ea46692..b9e034fdc 100644 --- a/Assets/VRM_Samples/SimpleViewer/ViewerUI.cs +++ b/Assets/VRM_Samples/SimpleViewer/ViewerUI.cs @@ -1,6 +1,8 @@ using System; +using System.Collections; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Threading.Tasks; using UniGLTF; using UniHumanoid; @@ -11,10 +13,13 @@ using VRMShaders; namespace VRM.SimpleViewer { - - public class ViewerUI : MonoBehaviour { +#if UNITY_WEBGL + [DllImport("__Internal")] + private static extern void WebGLFileDialog(); +#endif + #region UI [SerializeField] Text m_version = default; @@ -353,10 +358,29 @@ namespace VRM.SimpleViewer } } + IEnumerator LoadTexture(string url) + { + var www = new WWW(url); + yield return www; + LoadModelAsync("tmp.vrm", www.bytes); + } + + public void FileSelected(string url) + { + Debug.Log($"FileSelected: {url}"); + StartCoroutine(LoadTexture(url)); + } + void OnOpenClicked() { #if UNITY_STANDALONE_WIN var path = FileDialogForWindows.FileDialog("open VRM", "vrm", "glb", "bvh", "gltf", "zip"); +#elif UNITY_WEBGL + { + WebGLFileDialog(); + return; + } + var path = ""; #elif UNITY_EDITOR var path = UnityEditor.EditorUtility.OpenFilePanel("Open VRM", "", "vrm"); #else @@ -370,8 +394,11 @@ namespace VRM.SimpleViewer LoadModelAsync(path); } - async void LoadModelAsync(string path) + async void LoadModelAsync(string path, byte[] bytes = null) { + var size = bytes != null ? bytes.Length : 0; + Debug.Log($"LoadModelAsync: {path}: {size}bytes"); + var ext = Path.GetExtension(path).ToLower(); switch (ext) { @@ -379,7 +406,7 @@ namespace VRM.SimpleViewer case ".glb": case ".zip": { - var instance = await GltfUtility.LoadAsync(path, + var instance = await GltfUtility.LoadBytesAsync(path, bytes, GetIAwaitCaller(m_useAsync.isOn), GetGltfMaterialGenerator(m_useUrpMaterial.isOn)); break; @@ -389,7 +416,7 @@ namespace VRM.SimpleViewer { VrmUtility.MaterialGeneratorCallback materialCallback = (VRM.glTF_VRM_extensions vrm) => GetVrmMaterialGenerator(m_useUrpMaterial.isOn, vrm); VrmUtility.MetaCallback metaCallback = m_texts.UpdateMeta; - var instance = await VrmUtility.LoadAsync(path, GetIAwaitCaller(m_useAsync.isOn), materialCallback, metaCallback, loadAnimation: m_loadAnimation.isOn); + var instance = await VrmUtility.LoadBytesAsync(path, bytes, GetIAwaitCaller(m_useAsync.isOn), materialCallback, metaCallback, loadAnimation: m_loadAnimation.isOn); SetModel(instance); break; } @@ -433,7 +460,11 @@ namespace VRM.SimpleViewer { if (useAsync) { +#if UNITY_WEBGL + return new RuntimeOnlyNoThreadAwaitCaller(); +#else return new RuntimeOnlyAwaitCaller(); +#endif } else {