private void ProcessCreatedActors(CreateActor originalMessage, IList <Actor> createdActors, Action onCompleteCallback) { var guids = new DeterministicGuids(originalMessage.Actor?.Id); var rootActor = createdActors.FirstOrDefault(); if (rootActor.transform.parent == null) { // Delete entire hierarchy as we no longer have a valid parent actor for the root of this hierarchy. It was likely // destroyed in the process of the async operation before this callback was called. foreach (var actor in createdActors) { actor.Destroy(); } createdActors.Clear(); SendCreateActorResponse( originalMessage, failureMessage: "Parent for the actor being created no longer exists. Cannot create new actor."); return; } ProcessActors(rootActor.transform, rootActor.transform.parent.GetComponent <Actor>()); rootActor?.ApplyPatch(originalMessage.Actor); Actor.ApplyVisibilityUpdate(rootActor); _actorManager.UponStable( () => SendCreateActorResponse(originalMessage, actors: createdActors, onCompleteCallback: onCompleteCallback)); void ProcessActors(Transform xfrm, Actor parent) { // Generate actors for all GameObjects, even if the loader didn't. Only loader-generated // actors are returned to the app though. We do this so library objects get enabled/disabled // correctly, even if they're not tracked by the app. var actor = xfrm.gameObject.GetComponent <Actor>() ?? xfrm.gameObject.AddComponent <Actor>(); _actorManager.AddActor(guids.Next(), actor); _ownedGameObjects.Add(actor.gameObject); actor.ParentId = parent?.Id ?? actor.ParentId; if (actor.Renderer != null) { actor.MaterialId = MREAPI.AppsAPI.AssetCache.GetId(actor.Renderer.sharedMaterial) ?? Guid.Empty; actor.MeshId = MREAPI.AppsAPI.AssetCache.GetId(actor.UnityMesh) ?? Guid.Empty; } foreach (Transform child in xfrm) { ProcessActors(child, actor); } } }
private void ProcessCreatedActors(CreateActor originalMessage, IList <Actor> createdActors, Action onCompleteCallback) { var guids = new DeterministicGuids(originalMessage.Actor?.Id); var rootActor = createdActors.FirstOrDefault(); ProcessActors(rootActor.transform, rootActor.transform.parent.GetComponent <Actor>()); rootActor?.ApplyPatch(originalMessage.Actor); Actor.ApplyVisibilityUpdate(rootActor); foreach (var actor in createdActors) { actor.AddSubscriptions(originalMessage.Subscriptions); } SendCreateActorResponse(originalMessage, actors: createdActors, onCompleteCallback: onCompleteCallback); void ProcessActors(Transform xfrm, Actor parent) { // Generate actors for all GameObjects, even if the loader didn't. Only loader-generated // actors are returned to the app though. We do this so library objects get enabled/disabled // correctly, even if they're not tracked by the app. var actor = xfrm.gameObject.GetComponent <Actor>() ?? xfrm.gameObject.AddComponent <Actor>(); _actorManager.AddActor(guids.Next(), actor); _ownedGameObjects.Add(actor.gameObject); actor.ParentId = parent?.Id ?? actor.ParentId; if (actor.Renderer != null) { actor.MaterialId = MREAPI.AppsAPI.AssetCache.GetId(actor.Renderer.sharedMaterial) ?? Guid.Empty; } foreach (Transform child in xfrm) { ProcessActors(child, actor); } } }
private async Task <IList <Asset> > LoadAssetsFromGLTF(AssetSource source, Guid containerId, ColliderType colliderType) { WebRequestLoader loader = null; Stream stream = null; source.ParsedUri = new Uri(_app.ServerAssetUri, source.ParsedUri); var rootUri = URIHelper.GetDirectoryName(source.ParsedUri.AbsoluteUri); var cachedVersion = MREAPI.AppsAPI.AssetCache.SupportsSync ? MREAPI.AppsAPI.AssetCache.GetVersionSync(source.ParsedUri) : await MREAPI.AppsAPI.AssetCache.GetVersion(source.ParsedUri); // Wait asynchronously until the load throttler lets us through. using (var scope = await AssetLoadThrottling.AcquireLoadScope()) { // set up loader loader = new WebRequestLoader(rootUri); if (!string.IsNullOrEmpty(cachedVersion)) { loader.BeforeRequestCallback += (msg) => { if (msg.RequestUri == source.ParsedUri) { msg.Headers.Add("If-None-Match", cachedVersion); } }; } // download root gltf file, check for cache hit try { stream = await loader.LoadStreamAsync(URIHelper.GetFileFromUri(source.ParsedUri)); source.Version = loader.LastResponse.Headers.ETag?.Tag ?? ""; } catch (HttpRequestException) { if (loader.LastResponse.StatusCode == System.Net.HttpStatusCode.NotModified) { source.Version = cachedVersion; } else { throw; } } } IList <Asset> assetDefs = new List <Asset>(30); DeterministicGuids guidGenerator = new DeterministicGuids(UtilMethods.StringToGuid( $"{containerId}:{source.ParsedUri.AbsoluteUri}")); IList <UnityEngine.Object> assets; // fetch assets from glTF stream or cache if (source.Version != cachedVersion) { assets = await LoadGltfFromStream(loader, stream, colliderType); MREAPI.AppsAPI.AssetCache.StoreAssets(source.ParsedUri, assets, source.Version); } else { var assetsEnum = MREAPI.AppsAPI.AssetCache.SupportsSync ? MREAPI.AppsAPI.AssetCache.LeaseAssetsSync(source.ParsedUri) : await MREAPI.AppsAPI.AssetCache.LeaseAssets(source.ParsedUri); assets = assetsEnum.ToList(); } // catalog assets int textureIndex = 0, meshIndex = 0, materialIndex = 0, prefabIndex = 0; foreach (var asset in assets) { var assetDef = GenerateAssetPatch(asset, guidGenerator.Next()); assetDef.Name = asset.name; string internalId = null; if (asset is UnityEngine.Texture) { internalId = $"texture:{textureIndex++}"; } else if (asset is UnityEngine.Mesh) { internalId = $"mesh:{meshIndex++}"; } else if (asset is UnityEngine.Material) { internalId = $"material:{materialIndex++}"; } else if (asset is GameObject) { internalId = $"scene:{prefabIndex++}"; } assetDef.Source = new AssetSource(source.ContainerType, source.ParsedUri.AbsoluteUri, internalId, source.Version); ColliderGeometry colliderGeo = null; if (asset is UnityEngine.Mesh mesh) { colliderGeo = colliderType == ColliderType.Mesh ? (ColliderGeometry) new MeshColliderGeometry() { MeshId = assetDef.Id } : (ColliderGeometry) new BoxColliderGeometry() { Size = (mesh.bounds.size * 0.8f).CreateMWVector3(), Center = mesh.bounds.center.CreateMWVector3() }; } _app.AssetManager.Set(assetDef.Id, containerId, asset, colliderGeo, assetDef.Source); assetDefs.Add(assetDef); } return(assetDefs); }
private void ProcessCreatedActors(CreateActor originalMessage, IList <Actor> createdActors, Action onCompleteCallback, string guidSeed = null) { Guid guidGenSeed; if (originalMessage != null) { guidGenSeed = originalMessage.Actor.Id; } else { guidGenSeed = UtilMethods.StringToGuid(guidSeed); } var guids = new DeterministicGuids(guidGenSeed); // find the actors with no actor parents var rootActors = GetDistinctTreeRoots( createdActors.Select(a => a.gameObject).ToArray() ).Select(go => go.GetComponent <Actor>()).ToArray(); var rootActor = createdActors.FirstOrDefault(); var createdAnims = new List <Animation.BaseAnimation>(5); if (rootActors.Length == 1 && rootActor.transform.parent == null) { // Delete entire hierarchy as we no longer have a valid parent actor for the root of this hierarchy. It was likely // destroyed in the process of the async operation before this callback was called. foreach (var actor in createdActors) { actor.Destroy(); } createdActors.Clear(); SendCreateActorResponse( originalMessage, failureMessage: "Parent for the actor being created no longer exists. Cannot create new actor."); return; } var secondPassXfrms = new List <Transform>(2); foreach (var root in rootActors) { ProcessActors(root.transform, root.transform.parent != null ? root.transform.parent.GetComponent <Actor>() : null); } // some things require the whole hierarchy to have actors on it. run those here foreach (var pass2 in secondPassXfrms) { ProcessActors2(pass2); } if (originalMessage != null && rootActors.Length == 1) { rootActor?.ApplyPatch(originalMessage.Actor); } Actor.ApplyVisibilityUpdate(rootActor); _actorManager.UponStable( () => SendCreateActorResponse(originalMessage, actors: createdActors, anims: createdAnims, onCompleteCallback: onCompleteCallback)); void ProcessActors(Transform xfrm, Actor parent) { // Generate actors for all GameObjects, even if the loader didn't. Only loader-generated // actors are returned to the app though. We do this so library objects get enabled/disabled // correctly, even if they're not tracked by the app. var actor = xfrm.gameObject.GetComponent <Actor>() ?? xfrm.gameObject.AddComponent <Actor>(); _actorManager.AddActor(guids.Next(), actor); _ownedGameObjects.Add(actor.gameObject); actor.ParentId = parent?.Id ?? actor.ParentId; if (actor.Renderer != null) { actor.MaterialId = AssetCache.GetId(actor.Renderer.sharedMaterial) ?? Guid.Empty; actor.MeshId = AssetCache.GetId(actor.UnityMesh) ?? Guid.Empty; } // native animation construction requires the whole actor hierarchy to already exist. defer to second pass var nativeAnim = xfrm.gameObject.GetComponent <UnityEngine.Animation>(); if (nativeAnim != null && createdActors.Contains(actor)) { secondPassXfrms.Add(xfrm); } foreach (Transform child in xfrm) { ProcessActors(child, actor); } } void ProcessActors2(Transform xfrm) { var actor = xfrm.gameObject.GetComponent <Actor>(); var nativeAnim = xfrm.gameObject.GetComponent <UnityEngine.Animation>(); if (nativeAnim != null && createdActors.Contains(actor)) { var animTargets = xfrm.gameObject.GetComponent <PrefabAnimationTargets>(); int stateIndex = 0; foreach (AnimationState state in nativeAnim) { var anim = new NativeAnimation(AnimationManager, guids.Next(), nativeAnim, state); anim.TargetIds = animTargets != null ? animTargets.GetTargets(xfrm, stateIndex ++, addRootToTargets : true).Select(a => a.Id).ToList() : new List <Guid>() { actor.Id }; AnimationManager.RegisterAnimation(anim); createdAnims.Add(anim); } } } }
private void ProcessCreatedActors(CreateActor originalMessage, IList <Actor> createdActors, Action onCompleteCallback, string guidSeed = null) { Guid guidGenSeed; if (originalMessage != null) { guidGenSeed = originalMessage.Actor.Id; } else { guidGenSeed = UtilMethods.StringToGuid(guidSeed); } var guids = new DeterministicGuids(guidGenSeed); // find the actors with no actor parents var rootActors = GetDistinctTreeRoots( createdActors.ToArray() ).Select(go => go as Actor).ToArray(); var rootActor = createdActors.FirstOrDefault(); var createdAnims = new List <Animation.BaseAnimation>(5); if (rootActors.Length == 1 && rootActor.GetParent() == null) { // Delete entire hierarchy as we no longer have a valid parent actor for the root of this hierarchy. It was likely // destroyed in the process of the async operation before this callback was called. foreach (var actor in createdActors) { actor.Destroy(); } createdActors.Clear(); SendCreateActorResponse( originalMessage, failureMessage: "Parent for the actor being created no longer exists. Cannot create new actor."); return; } var secondPassXfrms = new List <Spatial>(2); foreach (var root in rootActors) { ProcessActors(root.Node3D, root.GetParent() as Actor); } // some things require the whole hierarchy to have actors on it. run those here foreach (var pass2 in secondPassXfrms) { ProcessActors2(pass2); } if (originalMessage != null && rootActors.Length == 1) { rootActor?.ApplyPatch(originalMessage.Actor); } Actor.ApplyVisibilityUpdate(rootActor); _actorManager.UponStable( () => SendCreateActorResponse(originalMessage, actors: createdActors, anims: createdAnims, onCompleteCallback: onCompleteCallback)); void ProcessActors(Spatial node3D, Actor parent) { // Generate actors for all node3D, even if the loader didn't. Only loader-generated // actors are returned to the app though. We do this so library objects get enabled/disabled // correctly, even if they're not tracked by the app. Actor actor = (node3D as Actor) ?? Actor.Instantiate(node3D); _actorManager.AddActor(guids.Next(), actor); _ownedNodes.Add(actor); actor.ParentId = parent?.Id ?? actor.ParentId; if (actor.MeshInstance != null) { // only overwrite material if there's something in the cache, i.e. not a random library material if (actor.MeshInstance.MaterialOverride != null) { var matId = AssetManager.GetByObject(actor.MeshInstance.MaterialOverride)?.Id; if (matId.HasValue) { actor.MaterialId = matId.Value; } } actor.MeshId = AssetManager.GetByObject(actor.GodotMesh)?.Id ?? Guid.Empty; } // native animation construction requires the whole actor hierarchy to already exist. defer to second pass var nativeAnim = node3D.GetChild <Godot.AnimationPlayer>(); if (nativeAnim != null && createdActors.Contains(actor)) { secondPassXfrms.Add(node3D); } foreach (object node in actor.GetChildren()) { if (node is Spatial) { ProcessActors((Spatial)node, actor); } } } void ProcessActors2(Spatial node3D) { var actor = node3D as Actor; var animationPlayer = node3D.GetChild <Godot.AnimationPlayer>(); if (animationPlayer != null && createdActors.Contains(actor)) { var animTargets = node3D.GetChild <PrefabAnimationTargets>(); int animIndex = 0; foreach (string animationString in animationPlayer.GetAnimationList()) { var anim = new NativeAnimation(AnimationManager, guids.Next(), animationPlayer, animationPlayer.GetAnimation(animationString)); anim.TargetIds = animTargets != null ? animTargets.GetTargets(node3D, animIndex ++, addRootToTargets : true).Select(a => a.Id).ToList() : new List <Guid>() { actor.Id }; AnimationManager.RegisterAnimation(anim); createdAnims.Add(anim); } } } }
private async Task <IList <Asset> > LoadAssetsFromGLTF(AssetSource source, Guid containerId, ColliderType colliderType) { IList <Asset> assets = new List <Asset>(); DeterministicGuids guidGenerator = new DeterministicGuids(UtilMethods.StringToGuid( $"{containerId}:{source.ParsedUri.AbsoluteUri}")); // download file UtilMethods.GetUrlParts(source.ParsedUri.AbsoluteUri, out string rootUrl, out string filename); var loader = new WebRequestLoader(rootUrl); await loader.LoadStream(filename); // pre-parse glTF document so we can get a scene count // TODO: run this in thread GLTF.GLTFParser.ParseJson(loader.LoadedStream, out GLTF.Schema.GLTFRoot gltfRoot); GLTFSceneImporter importer = MREAPI.AppsAPI.GLTFImporterFactory.CreateImporter(gltfRoot, loader, _asyncHelper, loader.LoadedStream); importer.SceneParent = MREAPI.AppsAPI.AssetCache.CacheRootGO().transform; importer.Collider = colliderType.ToGLTFColliderType(); // load prefabs if (gltfRoot.Scenes != null) { for (var i = 0; i < gltfRoot.Scenes.Count; i++) { await importer.LoadSceneAsync(i); GameObject rootObject = importer.LastLoadedScene; rootObject.name = gltfRoot.Scenes[i].Name ?? $"scene:{i}"; MWGOTreeWalker.VisitTree(rootObject, (go) => { go.layer = UnityConstants.ActorLayerIndex; }); var def = GenerateAssetPatch(rootObject, guidGenerator.Next()); def.Name = rootObject.name; def.Source = new AssetSource(source.ContainerType, source.Uri, $"scene:{i}"); MREAPI.AppsAPI.AssetCache.CacheAsset(rootObject, def.Id, containerId, source); assets.Add(def); } } // load textures if (gltfRoot.Textures != null) { for (var i = 0; i < gltfRoot.Textures.Count; i++) { await importer.LoadTextureAsync(gltfRoot.Textures[i], i, true); var texture = importer.GetTexture(i); texture.name = gltfRoot.Textures[i].Name ?? $"texture:{i}"; var asset = GenerateAssetPatch(texture, guidGenerator.Next()); asset.Name = texture.name; asset.Source = new AssetSource(source.ContainerType, source.Uri, $"texture:{i}"); MREAPI.AppsAPI.AssetCache.CacheAsset(texture, asset.Id, containerId, source); assets.Add(asset); } } // load materials if (gltfRoot.Materials != null) { for (var i = 0; i < gltfRoot.Materials.Count; i++) { var matdef = gltfRoot.Materials[i]; var material = await importer.LoadMaterialAsync(i); material.name = matdef.Name ?? $"material:{i}"; var asset = GenerateAssetPatch(material, guidGenerator.Next()); asset.Name = material.name; asset.Source = new AssetSource(source.ContainerType, source.Uri, $"material:{i}"); MREAPI.AppsAPI.AssetCache.CacheAsset(material, asset.Id, containerId, source); assets.Add(asset); } } importer.Dispose(); return(assets); }
private async Task <IList <Asset> > LoadAssetsFromGLTF(AssetSource source, Guid containerId, ColliderType colliderType) { IList <Asset> assets = new List <Asset>(); DeterministicGuids guidGenerator = new DeterministicGuids(UtilMethods.StringToGuid( $"{containerId}:{source.ParsedUri.AbsoluteUri}")); // download file var rootUrl = URIHelper.GetDirectoryName(source.ParsedUri.AbsoluteUri); var loader = new WebRequestLoader(rootUrl); var stream = await loader.LoadStreamAsync(URIHelper.GetFileFromUri(source.ParsedUri)); // pre-parse glTF document so we can get a scene count // TODO: run this in thread GLTF.GLTFParser.ParseJson(stream, out GLTF.Schema.GLTFRoot gltfRoot); stream.Position = 0; GLTFSceneImporter importer = MREAPI.AppsAPI.GLTFImporterFactory.CreateImporter(gltfRoot, loader, _asyncHelper, stream); importer.SceneParent = MREAPI.AppsAPI.AssetCache.CacheRootGO().transform; importer.Collider = colliderType.ToGLTFColliderType(); // load textures if (gltfRoot.Textures != null) { for (var i = 0; i < gltfRoot.Textures.Count; i++) { await importer.LoadTextureAsync(gltfRoot.Textures[i], i, true); var texture = importer.GetTexture(i); texture.name = gltfRoot.Textures[i].Name ?? $"texture:{i}"; var asset = GenerateAssetPatch(texture, guidGenerator.Next()); asset.Name = texture.name; asset.Source = new AssetSource(source.ContainerType, source.Uri, $"texture:{i}"); MREAPI.AppsAPI.AssetCache.CacheAsset(texture, asset.Id, containerId, source); assets.Add(asset); } } // load meshes if (gltfRoot.Meshes != null) { var cancellationSource = new System.Threading.CancellationTokenSource(); for (var i = 0; i < gltfRoot.Meshes.Count; i++) { var mesh = await importer.LoadMeshAsync(i, cancellationSource.Token); mesh.name = gltfRoot.Meshes[i].Name ?? $"mesh:{i}"; var asset = GenerateAssetPatch(mesh, guidGenerator.Next()); asset.Name = mesh.name; asset.Source = new AssetSource(source.ContainerType, source.Uri, $"mesh:{i}"); var colliderGeo = colliderType == ColliderType.Mesh ? (ColliderGeometry) new MeshColliderGeometry() { MeshId = asset.Id } : (ColliderGeometry) new BoxColliderGeometry() { Size = (mesh.bounds.size * 0.8f).CreateMWVector3() }; MREAPI.AppsAPI.AssetCache.CacheAsset(mesh, asset.Id, containerId, source, colliderGeo); assets.Add(asset); } } // load materials if (gltfRoot.Materials != null) { for (var i = 0; i < gltfRoot.Materials.Count; i++) { var matdef = gltfRoot.Materials[i]; var material = await importer.LoadMaterialAsync(i); material.name = matdef.Name ?? $"material:{i}"; var asset = GenerateAssetPatch(material, guidGenerator.Next()); asset.Name = material.name; asset.Source = new AssetSource(source.ContainerType, source.Uri, $"material:{i}"); MREAPI.AppsAPI.AssetCache.CacheAsset(material, asset.Id, containerId, source); assets.Add(asset); } } // load prefabs if (gltfRoot.Scenes != null) { for (var i = 0; i < gltfRoot.Scenes.Count; i++) { await importer.LoadSceneAsync(i).ConfigureAwait(true); GameObject rootObject = importer.LastLoadedScene; rootObject.name = gltfRoot.Scenes[i].Name ?? $"scene:{i}"; var animation = rootObject.GetComponent <UnityEngine.Animation>(); if (animation != null) { animation.playAutomatically = false; // initialize mapping so we know which gameobjects are targeted by which animation clips var mapping = rootObject.AddComponent <PrefabAnimationTargets>(); mapping.Initialize(gltfRoot, i); } MWGOTreeWalker.VisitTree(rootObject, (go) => { go.layer = MREAPI.AppsAPI.LayerApplicator.DefaultLayer; }); var def = GenerateAssetPatch(rootObject, guidGenerator.Next()); def.Name = rootObject.name; def.Source = new AssetSource(source.ContainerType, source.Uri, $"scene:{i}"); MREAPI.AppsAPI.AssetCache.CacheAsset(rootObject, def.Id, containerId, source); assets.Add(def); } } importer.Dispose(); return(assets); }