diff --git a/Assets/RGLUnityPlugin/Scripts/LowLevelWrappers/RGLMeshObject.cs b/Assets/RGLUnityPlugin/Scripts/LowLevelWrappers/RGLMeshObject.cs index 91d6a47a9..3c1866db5 100644 --- a/Assets/RGLUnityPlugin/Scripts/LowLevelWrappers/RGLMeshObject.cs +++ b/Assets/RGLUnityPlugin/Scripts/LowLevelWrappers/RGLMeshObject.cs @@ -17,34 +17,55 @@ using UnityEngine.Assertions; // Experimental is necessary for gathering GraphicsFormat of the texture. using UnityEngine.Experimental.Rendering; -using Unity.Collections; +using System.Collections.Generic; namespace RGLUnityPlugin { + + internal interface IRGLObject + { + GameObject RepresentedGO { get; } + int? CategoryId { get; } + string CategoryName { get; } + + void Update(); + + void DestroyInRGL(); + } + /// /// RGL counterpart of Unity GameObjects. /// Contains information about RGL entity and RGLMesh. /// - public class RGLObject + public abstract class RGLObject : IRGLObject { - public string Identifier; - public RGLMesh RglMesh; - public RGLTexture Texture; - public Func GetLocalToWorld; - public GameObject RepresentedGO; - public int? categoryId; - public string categoryName; - + private readonly string identifier; + private RGLTexture rglTexture; private IntPtr rglEntityPtr; - public RGLObject(string identifier, RGLMesh rglMesh, Func getLocalToWorld, GameObject representedGO) + protected RGLMesh rglMesh; + + public GameObject RepresentedGO { get; } + public int? CategoryId { get; private set; } + public string CategoryName { get; private set; } + + // There are different stratiegies for obtaining a RGLMesh so we have to also destroy it differently. + protected abstract RGLMesh GetRGLMeshFrom(T meshSource); + protected abstract void DestroyRGLMesh(); + + protected abstract Matrix4x4 GetLocalToWorld(); + + protected RGLObject(string identifier, GameObject representedGO, T meshSource) { - Identifier = identifier; - RglMesh = rglMesh; - GetLocalToWorld = getLocalToWorld; + this.identifier = identifier; RepresentedGO = representedGO; - + rglMesh = GetRGLMeshFrom(meshSource); + if (rglMesh == null) + { + throw new RGLException($"Could not create RGLMesh from gameobject '{representedGO.name}'."); + } UploadToRGL(); + SetIntensityTexture(); var semanticCategory = RepresentedGO.GetComponentInParent(); if (semanticCategory != null) @@ -56,20 +77,42 @@ public RGLObject(string identifier, RGLMesh rglMesh, Func getLocalToW ~RGLObject() { - DestroyFromRGL(); + DestroyInRGL(); } - public void DestroyFromRGL() + public virtual void DestroyInRGL() { - if (rglEntityPtr != IntPtr.Zero) + if (rglEntityPtr == IntPtr.Zero) { - RGLNativeAPI.CheckErr(RGLNativeAPI.rgl_entity_destroy(rglEntityPtr)); - rglEntityPtr = IntPtr.Zero; + return; + } + + RGLNativeAPI.CheckErr(RGLNativeAPI.rgl_entity_destroy(rglEntityPtr)); + rglEntityPtr = IntPtr.Zero; + + DestroyRGLMesh(); + rglMesh = null; + + if (rglTexture != null) + { + RGLTextureSharingManager.UnregisterRGLTextureInstance(rglTexture); + rglTexture = null; + } + } + + public void Update() + { + UpdateTransform(); + if (rglMesh is RGLSkinnedMesh rglSkinnedMesh) + { + rglSkinnedMesh.UpdateSkinnedMesh(); } } - public void UpdateTransform() + protected virtual void UpdateTransform() { + Assert.IsFalse(rglEntityPtr == IntPtr.Zero); + Matrix4x4 m = GetLocalToWorld(); float[] matrix3x4 = { @@ -89,25 +132,25 @@ public void UpdateTransform() public override int GetHashCode() { - return Identifier.GetHashCode(); + return identifier.GetHashCode(); } public override bool Equals(object obj) { - return obj is RGLObject rglObject && Identifier.Equals(rglObject.Identifier); + return obj is RGLObject rglObject && identifier.Equals(rglObject.identifier); } - protected void UploadToRGL() + private void UploadToRGL() { // Mesh should be uploaded. - Assert.IsFalse(RglMesh.rglMeshPtr == IntPtr.Zero); + Assert.IsFalse(rglMesh.RGLMeshPtr == IntPtr.Zero); unsafe { try { RGLNativeAPI.CheckErr( - RGLNativeAPI.rgl_entity_create(out rglEntityPtr, IntPtr.Zero, RglMesh.rglMeshPtr)); + RGLNativeAPI.rgl_entity_create(out rglEntityPtr, IntPtr.Zero, rglMesh.RGLMeshPtr)); } catch (RGLException) { @@ -124,32 +167,228 @@ private void UpdateSemanticCategory(SemanticCategory semanticCategory) return; } - categoryId = semanticCategory.CategoryId; - categoryName = semanticCategory.gameObject.name; - RGLNativeAPI.CheckErr(RGLNativeAPI.rgl_entity_set_id(rglEntityPtr, categoryId.Value)); + CategoryId = semanticCategory.CategoryId; + CategoryName = semanticCategory.gameObject.name; + RGLNativeAPI.CheckErr(RGLNativeAPI.rgl_entity_set_id(rglEntityPtr, CategoryId.Value)); } - public void SetIntensityTexture(RGLTexture texture) + private void SetIntensityTexture() { - unsafe + var intensityTextureComponent = RepresentedGO.GetComponent(); + + if( intensityTextureComponent == null || intensityTextureComponent.texture == null) { - try + return; + } + + rglTexture = RGLTextureSharingManager.RegisterRGLTextureInstance(intensityTextureComponent.texture); + try + { + RGLNativeAPI.CheckErr( + RGLNativeAPI.rgl_entity_set_intensity_texture(rglEntityPtr, rglTexture.RGLTexturePtr)); + } + catch (RGLException) + { + Debug.LogError($"Cannot assign texture: {rglTexture.Texture.name}, to entity: {RepresentedGO.name}"); + throw; + } + + // Mesh should be uploaded before assigning UVs. + Assert.IsFalse(rglMesh.RGLMeshPtr == IntPtr.Zero); + + rglMesh.UploadUVs(); + } + } + + public class RGLMeshRendererObject : RGLObject + { + private readonly Func getLocalToWorld; + + // By default localToWorld is taken from MeshRenderer, but developer can override it by passing overrideGetLocalToWorld. + public RGLMeshRendererObject(MeshRenderer meshRenderer, Func overrideGetLocalToWorld = null): + base( + $"{meshRenderer.gameObject.name}#{meshRenderer.gameObject.GetInstanceID()}", + meshRenderer.gameObject, + meshRenderer + ) + { + var rendererTransform = meshRenderer.transform; + getLocalToWorld = overrideGetLocalToWorld != null ? overrideGetLocalToWorld : () => rendererTransform.localToWorldMatrix; + } + + protected override RGLMesh GetRGLMeshFrom(MeshRenderer meshRenderer) + { + var meshFilter = meshRenderer.GetComponent(); + if (meshFilter.sharedMesh == null) + { + Debug.LogWarning($"Shared mesh of {meshRenderer.gameObject.name} is null, skipping"); + return null; + } + + return RGLMeshSharingManager.RegisterRGLMeshInstance(meshFilter.sharedMesh); + } + + protected override Matrix4x4 GetLocalToWorld() + { + return getLocalToWorld(); + } + + protected override void DestroyRGLMesh() + { + RGLMeshSharingManager.UnregisterRGLMeshInstance(rglMesh); + } + } + + public class RGLSkinnedMeshRendererObject : RGLObject + { + private readonly Transform skinnedMeshRendererTransform; + + public RGLSkinnedMeshRendererObject(SkinnedMeshRenderer skinnedMeshRenderer) : + base( + $"{skinnedMeshRenderer.gameObject.name}#{skinnedMeshRenderer.gameObject.GetInstanceID()}", + skinnedMeshRenderer.gameObject, + skinnedMeshRenderer + ) + { + skinnedMeshRendererTransform = skinnedMeshRenderer.transform; + } + + protected override RGLMesh GetRGLMeshFrom(SkinnedMeshRenderer skinnedMeshRenderer) + { + // Skinned meshes cannot be shared by using RGLMeshSharingManager + return new RGLSkinnedMesh(skinnedMeshRenderer.gameObject.GetInstanceID(), skinnedMeshRenderer); + } + + protected override Matrix4x4 GetLocalToWorld() + { + return skinnedMeshRendererTransform.localToWorldMatrix; + } + + protected override void DestroyRGLMesh() + { + rglMesh.DestroyInRGL(); + } + } + + public class RGLColliderObject : RGLObject + { + private readonly Collider collider; + + public RGLColliderObject(Collider collider) : + base($"{collider.gameObject.name}#{collider.gameObject.GetInstanceID()}", + collider.gameObject, + collider + ) + { + this.collider = collider; + } + + protected override RGLMesh GetRGLMeshFrom(Collider collider) + { + return RGLMeshSharingManager.RegisterRGLMeshInstance(ColliderUtilities.GetMeshForCollider(collider)); + } + + protected override Matrix4x4 GetLocalToWorld() + { + return collider.transform.localToWorldMatrix * ColliderUtilities.GetColliderTransformMatrix(collider); + } + + protected override void DestroyRGLMesh() + { + RGLMeshSharingManager.UnregisterRGLMeshInstance(rglMesh); + } + } + + public class RGLTerrainObject : RGLObject + { + private readonly List terrainSubObjects; + private readonly Transform terrainTransform; + + public RGLTerrainObject(Terrain terrain) : + base($"{terrain.gameObject.name}#{terrain.gameObject.GetInstanceID()}", terrain.gameObject, terrain) + { + terrainTransform = terrain.transform; + terrainSubObjects = new List(); + var treePrototypes = terrain.terrainData.treePrototypes; + + var treePrototypesRenderers = new List[treePrototypes.Length]; + var treePrototypeHasLODGroup = new bool[treePrototypes.Length]; + + for (var i = 0; i < treePrototypes.Length; i++) + { + treePrototypesRenderers[i] = new List(); + var treePrefab = treePrototypes[i].prefab; + if (treePrefab.TryGetComponent(out var lodGroup)) { - RGLNativeAPI.CheckErr( - RGLNativeAPI.rgl_entity_set_intensity_texture(rglEntityPtr, texture.rglTexturePtr)); - Texture = texture; + if (lodGroup.GetLODs().Length == 0) + { + Debug.LogWarning($"No LOD levels in LODGroup of tree prototype {treePrefab.name}"); + continue; + } + var lod = lodGroup.GetLODs()[0]; + foreach (var renderer in lod.renderers) + { + if (renderer.gameObject.TryGetComponent(out _)) + { + treePrototypesRenderers[i].Add(renderer); + } + } + treePrototypeHasLODGroup[i] = true; + } else if (treePrefab.TryGetComponent(out var mr) && treePrefab.TryGetComponent(out _)) + { + treePrototypesRenderers[i].Add(mr); + treePrototypeHasLODGroup[i] = false; } - catch (RGLException) + } + + for (var treeIndex = 0; treeIndex < terrain.terrainData.treeInstanceCount; treeIndex++) + { + var prototypeIndex = terrain.terrainData.GetTreeInstance(treeIndex).prototypeIndex; + foreach (var renderer in treePrototypesRenderers[prototypeIndex]) { - Debug.LogError($"Cannot assign texture: {texture.Identifier}, to entity: {Identifier}"); - throw; + var treePose = TerrainUtilities.GetTreePose(terrain, treeIndex, treePrototypeHasLODGroup[prototypeIndex]); + if (renderer is MeshRenderer mr) + { + terrainSubObjects.Add(new RGLMeshRendererObject(mr,() => + terrain.transform.localToWorldMatrix * treePose * mr.localToWorldMatrix)); + } } + } + } - // Mesh should be uploaded before assigning UVs. - Assert.IsFalse(RglMesh.rglMeshPtr == IntPtr.Zero); + protected override Matrix4x4 GetLocalToWorld() + { + return terrainTransform.localToWorldMatrix; + } + + public override void DestroyInRGL() + { + foreach (var terrainSubObject in terrainSubObjects) + { + terrainSubObject.DestroyInRGL(); + } + + base.DestroyInRGL(); + } - RglMesh.UploadUVs(); + protected override void UpdateTransform() + { + foreach (var terrainSubObject in terrainSubObjects) + { + terrainSubObject.Update(); } + + base.UpdateTransform(); + } + + protected override RGLMesh GetRGLMeshFrom(Terrain terrain) + { + return new RGLMesh(terrain.gameObject.GetInstanceID(), TerrainUtilities.GetTerrainMesh(terrain)); + } + + protected override void DestroyRGLMesh() + { + rglMesh.DestroyInRGL(); } } @@ -160,12 +399,12 @@ public void SetIntensityTexture(RGLTexture texture) /// public class RGLMesh { - public string Identifier; - public Mesh Mesh; + public int Identifier; + protected Mesh Mesh; - public IntPtr rglMeshPtr = IntPtr.Zero; + public IntPtr RGLMeshPtr = IntPtr.Zero; - public RGLMesh(string identifier, Mesh mesh) + public RGLMesh(int identifier, Mesh mesh) { Identifier = identifier; Mesh = mesh; @@ -175,15 +414,15 @@ public RGLMesh(string identifier, Mesh mesh) ~RGLMesh() { - DestroyFromRGL(); + DestroyInRGL(); } - public void DestroyFromRGL() + public void DestroyInRGL() { - if (rglMeshPtr != IntPtr.Zero) + if (RGLMeshPtr != IntPtr.Zero) { - RGLNativeAPI.CheckErr(RGLNativeAPI.rgl_mesh_destroy(rglMeshPtr)); - rglMeshPtr = IntPtr.Zero; + RGLNativeAPI.CheckErr(RGLNativeAPI.rgl_mesh_destroy(RGLMeshPtr)); + RGLMeshPtr = IntPtr.Zero; } } @@ -200,7 +439,7 @@ protected void UploadToRGL() if (!verticesOK || !indicesOK) { throw new NotSupportedException( - $"Could not get mesh data with mesh identifier {Identifier}. The mesh may be not readable or empty."); + $"Could not get mesh data from Mesh '{Mesh.name}'. The mesh may be not readable or empty."); } unsafe @@ -212,13 +451,13 @@ protected void UploadToRGL() try { RGLNativeAPI.CheckErr( - RGLNativeAPI.rgl_mesh_create(out rglMeshPtr, + RGLNativeAPI.rgl_mesh_create(out RGLMeshPtr, (IntPtr) pVertices, vertices.Length, (IntPtr) pIndices, indices.Length / 3)); } catch (RGLException) { - if (rglMeshPtr != IntPtr.Zero) RGLNativeAPI.rgl_mesh_destroy(rglMeshPtr); + if (RGLMeshPtr != IntPtr.Zero) RGLNativeAPI.rgl_mesh_destroy(RGLMeshPtr); throw; } } @@ -233,28 +472,28 @@ public void UploadUVs() if(!uvOK) { - Debug.LogWarning($"Could not assign UVs to mesh: {Identifier}. Mash has no UV, or UV are empty."); + Debug.LogWarning($"Could not assign UVs to mesh: '{Mesh.name}'. Mesh has no UV, or UV are empty."); } else { unsafe - { + { fixed(Vector2* pUVs = UVs) { try { RGLNativeAPI.CheckErr( - RGLNativeAPI.rgl_mesh_set_texture_coords(rglMeshPtr, + RGLNativeAPI.rgl_mesh_set_texture_coords(RGLMeshPtr, (IntPtr)pUVs, UVs.Length)); } catch (RGLException) { - Debug.LogWarning($"Could not assign UVs to mesh: {Identifier}."); + Debug.LogWarning($"Could not assign UVs to mesh: '{Mesh.name}'."); throw; } } - } + } } } } @@ -264,20 +503,20 @@ public void UploadUVs() /// public class RGLSkinnedMesh : RGLMesh { - public SkinnedMeshRenderer SkinnedMeshRenderer; + private readonly SkinnedMeshRenderer skinnedMeshRenderer; - public RGLSkinnedMesh(string identifier, SkinnedMeshRenderer smr) + public RGLSkinnedMesh(int identifier, SkinnedMeshRenderer smr) { Identifier = identifier; Mesh = new Mesh(); - SkinnedMeshRenderer = smr; - SkinnedMeshRenderer.BakeMesh(Mesh, true); + skinnedMeshRenderer = smr; + skinnedMeshRenderer.BakeMesh(Mesh, true); UploadToRGL(); } public void UpdateSkinnedMesh() { - SkinnedMeshRenderer.BakeMesh(Mesh, true); + skinnedMeshRenderer.BakeMesh(Mesh, true); unsafe { // Accessing .vertices perform a CPU copy! @@ -288,7 +527,7 @@ public void UpdateSkinnedMesh() fixed (Vector3* pVertices = Mesh.vertices) { RGLNativeAPI.CheckErr( - RGLNativeAPI.rgl_mesh_update_vertices(rglMeshPtr, (IntPtr) pVertices, Mesh.vertices.Length)); + RGLNativeAPI.rgl_mesh_update_vertices(RGLMeshPtr, (IntPtr) pVertices, Mesh.vertices.Length)); } } } @@ -300,9 +539,9 @@ public void UpdateSkinnedMesh() /// public class RGLTexture { - public int Identifier; - public Texture2D Texture; - public IntPtr rglTexturePtr = IntPtr.Zero; + public readonly int Identifier; + public readonly Texture2D Texture; + public IntPtr RGLTexturePtr = IntPtr.Zero; public RGLTexture(){} @@ -315,15 +554,15 @@ public RGLTexture(Texture2D texture, int identifier) ~RGLTexture() { - DestroyFromRGL(); + DestroyInRGL(); } - public void DestroyFromRGL() + public void DestroyInRGL() { - if (rglTexturePtr != IntPtr.Zero) + if (RGLTexturePtr != IntPtr.Zero) { - RGLNativeAPI.CheckErr(RGLNativeAPI.rgl_texture_destroy(rglTexturePtr)); - rglTexturePtr = IntPtr.Zero; + RGLNativeAPI.CheckErr(RGLNativeAPI.rgl_texture_destroy(RGLTexturePtr)); + RGLTexturePtr = IntPtr.Zero; } } @@ -335,13 +574,13 @@ protected void UploadToRGL() if (!resolutionOK) { throw new NotSupportedException( - $"Could not get texture data. Resolution seems to be broken."); + $"Could not get texture data from Texture '{Texture.name}'. Resolution seems to be broken."); } if (!graphicsFormatOK) { throw new NotSupportedException( - $"Could not get texture data. Texture format has to be equal to R8_UNorm."); + $"Could not get texture data from Texture '{Texture.name}'. Texture format has to be equal to R8_UNorm."); } unsafe @@ -352,22 +591,22 @@ protected void UploadToRGL() { RGLNativeAPI.CheckErr( RGLNativeAPI.rgl_texture_create( - out rglTexturePtr, + out RGLTexturePtr, (IntPtr) textureDataPtr, Texture.width, Texture.height)); } catch (RGLException) { - if (rglTexturePtr != IntPtr.Zero) + if (RGLTexturePtr != IntPtr.Zero) { - RGLNativeAPI.rgl_texture_destroy(rglTexturePtr); - rglTexturePtr = IntPtr.Zero; + RGLNativeAPI.rgl_texture_destroy(RGLTexturePtr); + RGLTexturePtr = IntPtr.Zero; } - throw; + throw; } - } - } + } + } } } } \ No newline at end of file diff --git a/Assets/RGLUnityPlugin/Scripts/LowLevelWrappers/RGLMeshSharingManager.cs b/Assets/RGLUnityPlugin/Scripts/LowLevelWrappers/RGLMeshSharingManager.cs new file mode 100644 index 000000000..136a1c596 --- /dev/null +++ b/Assets/RGLUnityPlugin/Scripts/LowLevelWrappers/RGLMeshSharingManager.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace RGLUnityPlugin +{ + public class RGLMeshSharingManager + { + private static Dictionary sharedMeshes = new Dictionary(); // + private static Dictionary sharedMeshesUsageCount = new Dictionary(); // + + public static RGLMesh RegisterRGLMeshInstance(Mesh unityMesh) + { + var meshId = unityMesh.GetInstanceID(); + if (!sharedMeshes.ContainsKey(meshId)) + { + var rglMesh = new RGLMesh(meshId, unityMesh); + sharedMeshes.Add(meshId, rglMesh); + sharedMeshesUsageCount.Add(meshId, 1); + } + else + { + sharedMeshesUsageCount[meshId]++; + } + + return sharedMeshes[meshId]; + } + + public static void UnregisterRGLMeshInstance(RGLMesh rglMesh) + { + var meshId = rglMesh.Identifier; + if (sharedMeshes[meshId] is null) + { + Debug.LogWarning($"Trying to unregister absent in RGLMeshSharingManager mesh of id: {meshId}, ignoring request"); + return; + } + + sharedMeshesUsageCount[meshId]--; + if (sharedMeshesUsageCount[meshId] == 0) + { + sharedMeshes[meshId].DestroyInRGL(); + sharedMeshes.Remove(meshId); + sharedMeshesUsageCount.Remove(meshId); + } + } + + public static void Clear() + { + foreach (var mesh in sharedMeshes) + { + mesh.Value.DestroyInRGL(); + } + } + } +} \ No newline at end of file diff --git a/Assets/RGLUnityPlugin/Scripts/LowLevelWrappers/RGLMeshSharingManager.cs.meta b/Assets/RGLUnityPlugin/Scripts/LowLevelWrappers/RGLMeshSharingManager.cs.meta new file mode 100644 index 000000000..1aa510c44 --- /dev/null +++ b/Assets/RGLUnityPlugin/Scripts/LowLevelWrappers/RGLMeshSharingManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f3925eb4ce66baa129634b42f2ff2c6f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/RGLUnityPlugin/Scripts/LowLevelWrappers/RGLTextureManager.cs b/Assets/RGLUnityPlugin/Scripts/LowLevelWrappers/RGLTextureManager.cs new file mode 100644 index 000000000..f876a1804 --- /dev/null +++ b/Assets/RGLUnityPlugin/Scripts/LowLevelWrappers/RGLTextureManager.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace RGLUnityPlugin +{ + public class RGLTextureSharingManager + { + private static Dictionary sharedTextures = new Dictionary(); // + private static Dictionary sharedTexturesUsageCount = new Dictionary(); // + + public static RGLTexture RegisterRGLTextureInstance(Texture2D texture) + { + var textureID = texture.GetInstanceID(); + + if(!sharedTextures.ContainsKey(textureID)) + { + var rglTextureToAdd = new RGLTexture(texture, textureID); + sharedTextures.Add(textureID, rglTextureToAdd); + sharedTexturesUsageCount.Add(textureID, 0); + } + + sharedTexturesUsageCount[textureID] += 1; + + return sharedTextures[textureID]; + } + + public static void UnregisterRGLTextureInstance(RGLTexture rglTexture) + { + var textureId = rglTexture.Identifier; + if (sharedTextures[textureId] is null) + { + Debug.LogWarning($"Trying to unregister absent in RGLTextureSharingManager texture of id: {textureId}, ignoring request"); + return; + } + + sharedTexturesUsageCount[textureId]--; + if (sharedTexturesUsageCount[textureId] == 0) + { + sharedTextures[textureId].DestroyInRGL(); + sharedTextures.Remove(textureId); + sharedTextures.Remove(textureId); + } + } + + public static void Clear() + { + foreach (var mesh in sharedTextures) + { + mesh.Value.DestroyInRGL(); + } + } + } +} diff --git a/Assets/RGLUnityPlugin/Scripts/LowLevelWrappers/RGLTextureManager.cs.meta b/Assets/RGLUnityPlugin/Scripts/LowLevelWrappers/RGLTextureManager.cs.meta new file mode 100644 index 000000000..2d0afa663 --- /dev/null +++ b/Assets/RGLUnityPlugin/Scripts/LowLevelWrappers/RGLTextureManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 91c9c04927411d5dcb8a4505e5e1e81b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/RGLUnityPlugin/Scripts/SceneManager.cs b/Assets/RGLUnityPlugin/Scripts/SceneManager.cs index fe4b92f65..24e697a9e 100644 --- a/Assets/RGLUnityPlugin/Scripts/SceneManager.cs +++ b/Assets/RGLUnityPlugin/Scripts/SceneManager.cs @@ -53,35 +53,29 @@ public enum MeshSource private string semanticCategoryDictionaryFile; // Getting meshes strategies - private delegate IEnumerable IntoRGLObjectsStrategy(IEnumerable gameObjects); + private delegate IEnumerable IntoRGLObjectsStrategy(IEnumerable gameObjects); private IntoRGLObjectsStrategy IntoRGLObjects; // Keeping track of the scene objects private HashSet lastFrameGameObjects = new HashSet(); - private readonly Dictionary uploadedRGLObjects = new Dictionary(); + private readonly Dictionary uploadedRGLObjects = new Dictionary(); // This dictionary keeps tracks of identifier -> instance id of objects that were removed (e.g. temporary NPCs) // This is needed to include them in the instance id dictionary yaml saved at the end of simulation. // Since categoryId can be changed in the runtime, this is filled only on object removal / simulation end. - private Dictionary semanticDict = new Dictionary(); - - private static Dictionary sharedMeshes = new Dictionary(); // - private static Dictionary sharedMeshesUsageCount = new Dictionary(); // - - private static Dictionary sharedTextures = new Dictionary(); // - private static Dictionary sharedTexturesUsageCount = new Dictionary(); // + private readonly Dictionary semanticDict = new Dictionary(); public static ITimeSource TimeSource { get; set; } = new UnityTimeSource(); private int lastUpdateFrame = -1; - void OnDisable() + private void OnDisable() { Clear(); } - void OnValidate() + private void OnValidate() { UpdateMeshSource(); } @@ -98,7 +92,7 @@ private void UpdateMeshSource() { IntoRGLObjectsStrategy UpdatedIntoRGLObjects = meshSource switch { - MeshSource.OnlyColliders => IntoRGLObjectsUsingCollider, + MeshSource.OnlyColliders => IntoRGLObjectsUsingColliders, MeshSource.RegularMeshesAndCollidersInsteadOfSkinned => IntoRGLObjectsHybrid, MeshSource.RegularMeshesAndSkinnedMeshes => IntoRGLObjectsUsingMeshes, _ => throw new ArgumentOutOfRangeException() @@ -144,101 +138,49 @@ public void DoUpdate() // Added var toAddGOs = new HashSet(thisFrameGOs); toAddGOs.ExceptWith(lastFrameGameObjects); - RGLObject[] toAdd = IntoRGLObjects(toAddGOs).ToArray(); + var toAdd = IntoRGLObjects(toAddGOs).ToArray(); + var toAddTerrain = IntoRGLTerrainObjects(toAddGOs).ToArray(); + if (toAddTerrain.Length != 0) + { + toAdd = toAdd.Concat(toAddTerrain).ToArray(); + } // Removed var toRemoveGOs = new HashSet(lastFrameGameObjects); toRemoveGOs.ExceptWith(thisFrameGOs); toRemoveGOs.IntersectWith(uploadedRGLObjects.Keys); - RGLObject[] toRemove = toRemoveGOs.Select((o) => uploadedRGLObjects[o]).ToArray(); - - // Skinned - RGLObject[] newToSkin = toAdd.Where(o => o.RglMesh is RGLSkinnedMesh).ToArray(); - RGLObject[] existingToSkin = uploadedRGLObjects.Values - .Where(o => o.RglMesh is RGLSkinnedMesh) - .Except(toRemove).ToArray(); - RGLObject[] toSkin = existingToSkin.Concat(newToSkin).ToArray(); + var toRemove = toRemoveGOs.Select((o) => uploadedRGLObjects[o]).ToArray(); lastFrameGameObjects = thisFrameGOs; Profiler.EndSample(); - Profiler.BeginSample("Add new textures"); - AddTextures(toAdd); - Profiler.EndSample(); - Profiler.BeginSample("Remove despawned objects"); foreach (var rglObject in toRemove) { - if(rglObject.Texture != null) - { - sharedTexturesUsageCount[rglObject.Texture.Identifier] -=1; - } - - if (!(rglObject.RglMesh is RGLSkinnedMesh)) - { - sharedMeshesUsageCount[rglObject.RglMesh.Identifier] -= 1; - } - - updateSemanticDict(rglObject); - rglObject.DestroyFromRGL(); + rglObject.DestroyInRGL(); uploadedRGLObjects.Remove(rglObject.RepresentedGO); } - - Profiler.EndSample(); - - // TODO: this can be parallelized and moved to Update() - Profiler.BeginSample("Update skinned meshes"); - foreach (var rglObject in toSkin) - { - var skinnedMesh = rglObject.RglMesh as RGLSkinnedMesh; - Assert.IsNotNull(skinnedMesh); - skinnedMesh.UpdateSkinnedMesh(); - } - Profiler.EndSample(); - + Profiler.BeginSample("Mark spawned objects as updated"); foreach (var rglObject in toAdd) { // Game Objects must not have duplicate representations. // Occasionally, this assertion may fail due to disabled Read/Write setting of the prefab's mesh. Assert.IsFalse(uploadedRGLObjects.ContainsKey(rglObject.RepresentedGO)); - - if (!(rglObject.RglMesh is RGLSkinnedMesh)) sharedMeshesUsageCount[rglObject.RglMesh.Identifier] += 1; + uploadedRGLObjects.Add(rglObject.RepresentedGO, rglObject); } - Profiler.EndSample(); // TODO(prybicki): This part can take up to 8ms on Shinjuku scene, two ideas to optimize it soon: // - Implement batch update in RGL // - Use Transform.hasChanged to filter out some objects - Profiler.BeginSample("Update transforms"); + Profiler.BeginSample("Update transforms and skinned meshes"); foreach (var gameRGLObject in uploadedRGLObjects) { - gameRGLObject.Value.UpdateTransform(); - } - - Profiler.EndSample(); - - Profiler.BeginSample("Destroy unused meshes"); - foreach (var meshUsageCounter in sharedMeshesUsageCount.Where(x => x.Value < 1).ToList()) - { - sharedMeshes[meshUsageCounter.Key].DestroyFromRGL(); - sharedMeshes.Remove(meshUsageCounter.Key); - sharedMeshesUsageCount.Remove(meshUsageCounter.Key); + gameRGLObject.Value.Update(); } - - Profiler.EndSample(); - - Profiler.BeginSample("Destroy unused textures"); - foreach(var textureUsageCounter in sharedTexturesUsageCount.Where(x =>x.Value < 1).ToList()) - { - sharedTextures[textureUsageCounter.Key].DestroyFromRGL(); - sharedTextures.Remove(textureUsageCounter.Key); - sharedTexturesUsageCount.Remove(textureUsageCounter.Key); - } - Profiler.EndSample(); } @@ -253,39 +195,27 @@ private void SynchronizeSceneTime() private void Clear() { - if (!lastFrameGameObjects.Any() && !uploadedRGLObjects.Any() && !sharedMeshes.Any()) return; - - foreach (var rglMesh in sharedMeshes) - { - rglMesh.Value.DestroyFromRGL(); - } + if (!lastFrameGameObjects.Any() && !uploadedRGLObjects.Any()) return; foreach (var rglObject in uploadedRGLObjects) { - rglObject.Value.DestroyFromRGL(); + rglObject.Value.DestroyInRGL(); } - - foreach ( var rglTexture in sharedTextures) - { - rglTexture.Value.DestroyFromRGL(); - } - + + RGLMeshSharingManager.Clear(); + RGLTextureSharingManager.Clear(); uploadedRGLObjects.Clear(); lastFrameGameObjects.Clear(); - sharedMeshes.Clear(); - sharedMeshesUsageCount.Clear(); - sharedTextures.Clear(); - sharedMeshesUsageCount.Clear(); Debug.Log("RGLSceneManager: cleared"); } - private void updateSemanticDict(RGLObject rglObject) + private void updateSemanticDict(IRGLObject rglObject) { - if (rglObject.categoryId.HasValue) + if (rglObject.CategoryId.HasValue) { - if (!semanticDict.ContainsKey(rglObject.categoryName)) + if (!semanticDict.ContainsKey(rglObject.CategoryName)) { - semanticDict.Add(rglObject.categoryName, rglObject.categoryId.Value); + semanticDict.Add(rglObject.CategoryName, rglObject.CategoryId.Value); } } } @@ -311,7 +241,7 @@ private void OnApplicationQuit() /// Yields a collection of RGL objects based on active colliders found in provided game objects. /// This function ignores whether gameObject is active, if filtering is needed, it should be done earlier. /// - private static IEnumerable IntoRGLObjectsUsingCollider(IEnumerable gameObjects) + private static IEnumerable IntoRGLObjectsUsingColliders(IEnumerable gameObjects) { foreach (var gameObject in gameObjects) { @@ -331,7 +261,14 @@ private static IEnumerable IntoRGLObjectsUsingCollider(IEnumerable IntoRGLObjectsUsingCollider(IEnumerable - private static IEnumerable IntoRGLObjectsHybrid(IEnumerable gameObjects) + private static IEnumerable IntoRGLObjectsHybrid(IEnumerable gameObjects) { var collidersToYield = new HashSet(); foreach (var renderer in GetUniqueRenderersInGameObjects(gameObjects)) @@ -362,19 +299,13 @@ private static IEnumerable IntoRGLObjectsHybrid(IEnumerable(); - if (mf.sharedMesh == null) - { - Debug.LogWarning($"Shared mesh of {mr.gameObject} is null, skipping"); - continue; - } - yield return MeshFilterToRGLObject(mf); + yield return new RGLMeshRendererObject(mr); } } foreach (var collider in collidersToYield) { - yield return ColliderToRGLObject(collider); + yield return new RGLColliderObject(collider); } } @@ -383,20 +314,13 @@ private static IEnumerable IntoRGLObjectsHybrid(IEnumerable - private static IEnumerable IntoRGLObjectsUsingMeshes(IEnumerable gameObjects) + private static IEnumerable IntoRGLObjectsUsingMeshes(IEnumerable gameObjects) { foreach (var renderer in GetUniqueRenderersInGameObjects(gameObjects)) { - var go = renderer.gameObject; if (renderer is MeshRenderer mr) { - var mf = mr.GetComponent(); - if (mf.sharedMesh == null) - { - Debug.LogWarning($"Shared mesh of {mr.gameObject} is null, skipping"); - continue; - } - yield return MeshFilterToRGLObject(mf); + yield return new RGLMeshRendererObject(mr); } if (renderer is SkinnedMeshRenderer smr) @@ -406,52 +330,21 @@ private static IEnumerable IntoRGLObjectsUsingMeshes(IEnumerable smr.transform.localToWorldMatrix, - go); - } - } - } - private static RGLObject ColliderToRGLObject(Collider collider) - { - var mesh = ColliderUtilities.GetMeshForCollider(collider); - string meshId = $"r#{mesh.GetInstanceID()}"; - if (!sharedMeshes.ContainsKey(meshId)) - { - RGLMesh rglMesh = new RGLMesh(meshId, mesh); - sharedMeshes.Add(meshId, rglMesh); - sharedMeshesUsageCount.Add(meshId, 0); + yield return new RGLSkinnedMeshRendererObject(smr); + } } - - var gameObject = collider.gameObject; - return new RGLObject($"{gameObject.name}#{gameObject.GetInstanceID()}", - sharedMeshes[meshId], - () => collider.transform.localToWorldMatrix * - ColliderUtilities.GetColliderTransformMatrix(collider), - gameObject); } - private static RGLObject MeshFilterToRGLObject(MeshFilter meshFilter) + private static IEnumerable IntoRGLTerrainObjects(IEnumerable gameObjects) { - var mesh = meshFilter.sharedMesh; - string meshId = $"r#{mesh.GetInstanceID()}"; - if (!sharedMeshes.ContainsKey(meshId)) + foreach (var gameObject in gameObjects) { - RGLMesh rglMesh = new RGLMesh(meshId, mesh); - sharedMeshes.Add(meshId, rglMesh); - sharedMeshesUsageCount.Add(meshId, 0); + if (gameObject.TryGetComponent(out var terrain)) + { + yield return new RGLTerrainObject(terrain); + } } - - var gameObject = meshFilter.gameObject; - return new RGLObject($"{gameObject.name}#{gameObject.GetInstanceID()}", - sharedMeshes[meshId], - () => gameObject.transform.localToWorldMatrix, - gameObject); } /// @@ -512,42 +405,6 @@ private static IEnumerable GetUniqueRenderersInGameObjects(IEnumerable foreach (var mr in mrs) yield return mr; } - /// - /// Searches through new rglObjects for ones containing IntensityTexture component. - /// If present, check if the found texture was already sent to RGL. - /// If not, send it and write its identifier to sharedTextures dictionary. - /// After that assign rglTexture to the proper rglObject. - /// - private static void AddTextures(IEnumerable rglObjects) - { - foreach (var rglObject in rglObjects) - { - var intensityTextureComponent = rglObject.RepresentedGO.GetComponent(); - - if( intensityTextureComponent == null) - { - continue; - } - - if( intensityTextureComponent.texture == null) - { - continue; - } - - int textureID = intensityTextureComponent.texture.GetInstanceID(); - - if(!sharedTextures.ContainsKey(textureID)) - { - var rglTextureToAdd = new RGLTexture(intensityTextureComponent.texture, textureID); - sharedTextures.Add(textureID, rglTextureToAdd); - sharedTexturesUsageCount.Add(textureID, 0); - } - - rglObject.SetIntensityTexture(sharedTextures[textureID]); - sharedTexturesUsageCount[textureID] += 1; - } - } - private static bool IsNotActiveOrParentHasLidar(GameObject gameObject) { return !gameObject.activeInHierarchy || gameObject.GetComponentsInParent().Length != 0; diff --git a/Assets/RGLUnityPlugin/Scripts/Utilities/TerrainUtilities.cs b/Assets/RGLUnityPlugin/Scripts/Utilities/TerrainUtilities.cs new file mode 100644 index 000000000..580653935 --- /dev/null +++ b/Assets/RGLUnityPlugin/Scripts/Utilities/TerrainUtilities.cs @@ -0,0 +1,126 @@ +using System.Collections.Generic; +using UnityEngine.Rendering; +using UnityEngine; + +namespace RGLUnityPlugin +{ + public class TerrainUtilities + { + public static Mesh GetTerrainMesh(Terrain terrain) + { + var terrainData = terrain.terrainData; + var heightmapResolution = terrainData.heightmapResolution; + var holesResolution = terrainData.holesResolution; + var correctHolesResolution = holesResolution == heightmapResolution - 1; + var heights = terrainData.GetHeights(0, 0, heightmapResolution, heightmapResolution); + var solidSurfaceTiles = terrainData.GetHoles(0, 0, holesResolution, holesResolution); + var scale = terrainData.heightmapScale; + var vertices = new Vector3[heightmapResolution * heightmapResolution]; + + if (!correctHolesResolution) + { + Debug.LogWarning($"Terrain {terrain.GetInstanceID()} holes resolution is incorrect, holes will be ignored by RGL"); + } + + for (var z = 0; z < heightmapResolution; z++) + { + for (var x = 0; x < heightmapResolution; x++) + { + vertices[x * heightmapResolution + z].x = x * scale.x; + vertices[x * heightmapResolution + z].y = heights[z, x] * scale.y; + vertices[x * heightmapResolution + z].z = z * scale.z; + } + } + + // this is the number of squares alongside each axis of the terrain + // e.g. if you have a 3x3 grid of points you can fill the space between them using a 2x2 square grid + var tileResolution = heightmapResolution - 1; + + // count solid terrain tiles (not holes) + var tileCount = 0; + foreach (var solidSurface in solidSurfaceTiles) + { + if (solidSurface) + { + tileCount++; + } + } + + if (!correctHolesResolution) + { + tileCount = tileResolution * tileResolution; + } + + // there are 2 triangles per square tile, so 6 indices + var triangles = new int[tileCount * 2 * 3]; + + var trianglesIndex = 0; + for (var z = 0; z < tileResolution; z++) + { + for (var x = 0; x < tileResolution; x++) + { + if (correctHolesResolution && !solidSurfaceTiles[z, x]) + { + continue; + } + + var sampleBase = x * heightmapResolution + z; + + // first triangle of tile + triangles[trianglesIndex++] = sampleBase; + triangles[trianglesIndex++] = sampleBase + heightmapResolution; + triangles[trianglesIndex++] = sampleBase + heightmapResolution + 1; + + // second triangle of tile + triangles[trianglesIndex++] = sampleBase; + triangles[trianglesIndex++] = sampleBase + 1; + triangles[trianglesIndex++] = sampleBase + 1 + heightmapResolution; + } + } + + var uv = new Vector2[vertices.Length]; + for (var i = 0; i < vertices.Length; i++) + { + uv[i] = new Vector2(vertices[i].x / (tileResolution * scale.x), vertices[i].z / (tileResolution * scale.z)); + } + + var heightmapMesh = new Mesh(); + heightmapMesh.indexFormat = IndexFormat.UInt32; + heightmapMesh.vertices = vertices; + heightmapMesh.triangles = triangles; + heightmapMesh.uv = uv; + + return heightmapMesh; + } + + public static Matrix4x4 GetTreePose(Terrain terrain, int treeIndex, bool applyRotationAndScale) + { + var terrainPosition = terrain.transform.position; + var terrainData = terrain.terrainData; + var resolution = terrainData.heightmapResolution; + var heightmapScale = terrainData.heightmapScale; + var treeInstance = terrainData.GetTreeInstance(treeIndex); + var treePosition = treeInstance.position; + + var translation = new Vector3(treePosition.x, 0, treePosition.z); + translation.x *= heightmapScale.x * (resolution - 1); + translation.z *= heightmapScale.z * (resolution - 1); + var samplePose = new Vector3(terrainPosition.x + translation.x, 0, terrainPosition.z + translation.z); + translation.y = terrain.SampleHeight(samplePose); + + var rotation = Quaternion.identity; + if (applyRotationAndScale) + { + rotation = Quaternion.AngleAxis(treeInstance.rotation * Mathf.Rad2Deg, Vector3.up); + } + + var scale = Vector3.one; + if (applyRotationAndScale) + { + scale = new Vector3(treeInstance.widthScale, treeInstance.heightScale, treeInstance.widthScale); + } + + return Matrix4x4.TRS(translation, rotation, scale); + } + } +} diff --git a/Assets/RGLUnityPlugin/Scripts/Utilities/TerrainUtilities.cs.meta b/Assets/RGLUnityPlugin/Scripts/Utilities/TerrainUtilities.cs.meta new file mode 100644 index 000000000..0c582e395 --- /dev/null +++ b/Assets/RGLUnityPlugin/Scripts/Utilities/TerrainUtilities.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1719548a6c3b4d6b90c6162fec245261 +timeCreated: 1690364114 \ No newline at end of file