// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; using System.Collections.Generic; using UnityEngine; // WebGL doesn't support threaded operations. #if !UNITY_WEBGL using System.Collections.Concurrent; using System.Threading.Tasks; #endif namespace Microsoft.MixedReality.GraphicsTools { /// /// The MeshInstancer is a component used for quickly using Unity's Graphics.DrawMeshInstanced API. The MeshInstancer should be /// used when you need to render and update massive amounts of objects (greater than ~1000). Note, each object rendered is not /// a typical Unity GameObject, for performance reasons, but is instead a MeshInstancer.Instance. MeshInstancer.Instances can be /// updated concurrently by specifying a ParallelUpdate delegate. /// public class MeshInstancer : MonoBehaviour { // Note: You can only draw a maximum of 1023 instances at once. // https://docs.unity3d.com/ScriptReference/Graphics.DrawMeshInstanced.html public const int UNITY_MAX_INSTANCE_COUNT = 1023; /// /// The mesh to draw via Graphics.DrawMeshInstanced. /// [Header("Visuals"), Tooltip("The mesh to draw via Graphics.DrawMeshInstanced.")] public UnityEngine.Mesh InstanceMesh = null; /// /// Which subset of the mesh to draw. This applies only to meshes that are composed of several materials. /// [Min(0), Tooltip("Which subset of the mesh to draw. This applies only to meshes that are composed of several materials.")] public int InstanceSubMeshIndex = 0; /// /// The material to use when drawing. The material must have "Enable GPU Instancing" checked on. /// [Tooltip("The material to use when drawing. The material must have \"Enable GPU Instancing\" checked on.")] public Material InstanceMaterial = null; /// /// Determines whether the Meshes should cast shadows. /// [Tooltip("Determines whether the Meshes should cast shadows.")] public UnityEngine.Rendering.ShadowCastingMode ShadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; /// /// Determines whether the Meshes should receive shadows. /// [Tooltip("Determines whether the Meshes should receive shadows.")] public bool RecieveShadows = false; [Serializable] private class FloatMaterialProperty { public string Name = null; public float DefaultValue = 0.0f; } [SerializeField, Tooltip("Name and default value of instanced float material properties. These must be set in order to update them at runtime.")] private FloatMaterialProperty[] FloatMaterialProperties = new FloatMaterialProperty[0]; [Serializable] private class VectorMaterialProperty { public string Name = null; public Vector4 DefaultValue = Vector3.zero; } [SerializeField, Tooltip("Name and default value of instanced vector material properties. These must be set in order to update them at runtime.")] private VectorMaterialProperty[] VectorMaterialProperties = new VectorMaterialProperty[0]; [Serializable] private class MatrixMaterialProperty { public string Name = null; public Matrix4x4 DefaultValue = Matrix4x4.identity; } [SerializeField, Tooltip("Name and default value of instanced matrix material properties. These must be set in order to update them at runtime.")] private MatrixMaterialProperty[] MatrixMaterialProperties = new MatrixMaterialProperty[0]; /// /// If true, the RaycastHits list is filled out each frame with instances that intersect the DeferredRayQuery. Disable this if you don't need to query instances. /// [Header("Physics"), Tooltip("If true, the RaycastHits list is filled out each frame with instances that intersect the DeferredRayQuery. Disable this if you don't need to query instances.")] public bool RaycastInstances = false; /// /// AABB of an instance. /// [Serializable] public struct Box { public Vector3 Center; public Vector3 Size; } /// /// Raycast query results when RaycastInstances is true and the DeferredRayQuery intersects an instance. /// public struct RaycastHit { /// /// The instance hit with the raycast query. /// public Instance Instance; /// /// The raycast intersection point in world space. /// public Vector3 Point; /// /// The raycast intersection normal in world space. /// public Vector3 Normal; /// /// The distance along the ray the intersection point lies at in world space. /// public float Distance; /// /// The ray used during the query. /// public Ray Ray; /// /// Restores the hit to the default state. /// public void Reset() { Instance = null; Point = Vector3.zero; Normal = Vector3.zero; Distance = float.MaxValue; Ray = new Ray(); } } /// /// The AABB local space position and extents of every instance's collider. /// public Box BoxCollider = new Box() { Center = Vector3.zero, Size = Vector3.one }; /// /// The ray to use in queries if RaycastInstances is true. /// public Ray DeferredRayQuery { get; set; } /// /// The list of all hits that DeferredRayQuery intersects with when RaycastInstances is true. /// Results are from the previous frame's LateUpdate query. /// public List DeferredRaycastHits { get; private set; } /// /// Signature of the multi-threaded update method for each instance. /// public delegate void ParallelUpdate(float deltaTime, Instance instance); /// /// The total number of instances across all buckets. /// public int InstanceCount { get; private set; } /// /// If true displays diagnostic GUI text in the bottom left of the screen displaying how long it took to update all instances. /// [Header("Diagnostics"), Tooltip("If true displays diagnostic GUI text in the bottom left of the screen displaying how long it took to update all instances.")] public bool DisplayUpdateTime = false; /// /// Forces all instance updating to happen on the main thread. Helpful when debugging multi-threaded issues. /// [Tooltip("Forces all instance updating to happen on the main thread. Helpful when debugging multi-threaded issues. ")] public bool DisableParallelUpdate = false; /// /// An object that represents a instance to be drawn. Akin to a Unity GameObject. /// public class Instance { /// /// The unique instance id within a bucket. /// NOTE: Thread safe. /// public int InstanceIndex { get; private set; } /// /// The id of the bucket the instances lives in. Instances live in buckets of UNITY_MAX_INSTANCE_COUNT. /// NOTE: Thread safe. /// public int InstanceBucketIndex { get; private set; } /// /// Pointer to any custom data the developer wishes to store on a per instance basis. /// NOTE: Thread safe. /// NOTE: This data should be a value type to avoid allocations when boxing and unboxing. /// public System.Object UserData { get; set; } /// /// The world space position of the instance. /// NOTE: Thread safe. /// public Vector3 Position { get { return Transformation.GetColumn(3); } set { Transformation = Matrix4x4.TRS(value, Rotation, LossyScale); } } /// /// Position of the instance relative to the parent MeshInstancer. /// NOTE: Thread safe. /// public Vector3 LocalPosition { get { return LocalTransformation.GetColumn(3); } set { LocalTransformation = Matrix4x4.TRS(value, LocalRotation, LocalScale); } } /// /// A Quaternion that stores the rotation of the instance in world space. /// NOTE: Thread safe. /// public Quaternion Rotation { get { Matrix4x4 matrix = Transformation; return Quaternion.LookRotation(matrix.GetColumn(2), matrix.GetColumn(1)); } set { Transformation = Matrix4x4.TRS(Position, value, LossyScale); } } /// /// The rotation of the instance relative to the instance rotation of the parent MeshInstancer. /// NOTE: Thread safe. /// public Quaternion LocalRotation { get { Matrix4x4 matrix = LocalTransformation; return Quaternion.LookRotation(matrix.GetColumn(2), matrix.GetColumn(1)); } set { LocalTransformation = Matrix4x4.TRS(LocalPosition, value, LocalScale); } } /// /// The global scale of the instance (Read Only). /// NOTE: Thread safe. /// public Vector3 LossyScale { get { Matrix4x4 matrix = Transformation; return new Vector3(matrix.GetColumn(0).magnitude, matrix.GetColumn(1).magnitude, matrix.GetColumn(2).magnitude); } } /// /// The scale of the instance relative to the parent MeshInstancer. /// NOTE: Thread safe. /// public Vector3 LocalScale { get { Matrix4x4 matrix = LocalTransformation; return new Vector3(matrix.GetColumn(0).magnitude, matrix.GetColumn(1).magnitude, matrix.GetColumn(2).magnitude); } set { LocalTransformation = Matrix4x4.TRS(LocalPosition, LocalRotation, value); } } /// /// Translation, rotation, and scale matrix in world space. /// NOTE: Thread safe. /// public Matrix4x4 Transformation { get { return meshInstancer.transform.localToWorldMatrix * LocalTransformation; } set { LocalTransformation = meshInstancer.transform.worldToLocalMatrix * value; } } /// /// Translation, rotation, and scale matrix relative to the parent MeshInstancer. /// NOTE: Thread safe. /// public Matrix4x4 LocalTransformation { get { return meshInstancer.instanceBuckets[InstanceBucketIndex].Matricies[InstanceIndex]; } set { meshInstancer.instanceBuckets[InstanceBucketIndex].Matricies[InstanceIndex] = value; } } /// /// True if this instance has been destoryed (removed) from the parent MeshInstancer. /// NOTE: Thread safe. /// public bool Destroyed { get { return (meshInstancer == null); } } private MeshInstancer meshInstancer; private Instance() { } /// /// Default constructor. /// NOTE: Thread safe. /// public Instance(int instanceIndex, int instanceBucketIndex, System.Object data, MeshInstancer instancer) { InstanceIndex = instanceIndex; InstanceBucketIndex = instanceBucketIndex; UserData = data; meshInstancer = instancer; } /// /// Call this method to destory an instance so it no longer renders or updates. /// NOTE: Not thread safe. /// public void Destroy() { if (Destroyed) { Debug.LogWarning("Attempting to double destroy a MeshInstancer instance."); return; } meshInstancer.Destroy(this); InstanceIndex = -1; InstanceBucketIndex = -1; meshInstancer = null; } /// /// Sets an instanced material float property. /// NOTE: Not thread safe. /// public void SetFloat(string name, float value) { SetFloat(Shader.PropertyToID(name), value); } /// /// Sets an instanced material float property. /// NOTE: Not thread safe. /// public void SetFloat(int nameID, float value) { if (Destroyed) { Debug.LogWarning("Attempting to SetFloat on a destroyed MeshInstancer instance."); return; } meshInstancer.SetFloat(this, nameID, value); } /// /// Gets an instanced material float property. /// NOTE: Not thread safe. /// public float GetFloat(string name) { return GetFloat(Shader.PropertyToID(name)); } /// /// Gets an instanced material float property. /// NOTE: Not thread safe. /// public float GetFloat(int nameID) { if (Destroyed) { Debug.LogWarning("Attempting to GetFloat on a destroyed MeshInstancer instance."); return 0.0f; } return meshInstancer.GetFloat(this, nameID); } /// /// Sets an instanced material vector property. /// NOTE: Not thread safe. /// public void SetVector(string name, Vector4 value) { SetVector(Shader.PropertyToID(name), value); } /// /// Sets an instanced material vector property. /// NOTE: Not thread safe. /// public void SetVector(int nameID, Vector4 value) { if (Destroyed) { Debug.LogWarning("Attempting to SetVector on a destroyed MeshInstancer instance."); return; } meshInstancer.SetVector(this, nameID, value); } /// /// Gets an instanced material vector property. /// NOTE: Not thread safe. /// public Vector4 GetVector(string name) { return GetVector(Shader.PropertyToID(name)); } /// /// Gets an instanced material vector property. /// NOTE: Not thread safe. /// public Vector4 GetVector(int nameID) { if (Destroyed) { Debug.LogWarning("Attempting to GetVector on a destroyed MeshInstancer instance."); return Vector4.zero; } return meshInstancer.GetVector(this, nameID); } /// /// Sets an instanced material matrix property. /// NOTE: Not thread safe. /// public void SetMatrix(string name, Matrix4x4 value) { SetMatrix(Shader.PropertyToID(name), value); } /// /// Sets an instanced material matrix property. /// NOTE: Not thread safe. /// public void SetMatrix(int nameID, Matrix4x4 value) { if (Destroyed) { Debug.LogWarning("Attempting to SetMatrix on a destroyed MeshInstancer instance."); return; } meshInstancer.SetMatrix(this, nameID, value); } /// /// Gets an instanced material matrix property. /// NOTE: Not thread safe. /// public Matrix4x4 GetMatrix(string name) { return GetMatrix(Shader.PropertyToID(name)); } /// /// Gets an instanced material matrix property. /// NOTE: Not thread safe. /// public Matrix4x4 GetMatrix(int nameID) { if (Destroyed) { Debug.LogWarning("Attempting to v on a destroyed MeshInstancer instance."); return Matrix4x4.identity; } return meshInstancer.GetMatrix(this, nameID); } /// /// Sets the delegate update method to call each frame. Make sure all function within this method are thread safe. /// NOTE: Not thread safe. /// public void SetParallelUpdate(ParallelUpdate parallelUpdate) { if (Destroyed) { Debug.LogWarning("Attempting to set the ParallelUpdate method on a destroyed MeshInstancer instance."); return; } meshInstancer.instanceBuckets[InstanceBucketIndex].ParallelUpdates[InstanceIndex] = parallelUpdate; } } private class InstanceBucket { public int InstanceCount = 0; public Instance[] Instances = new Instance[UNITY_MAX_INSTANCE_COUNT]; public Matrix4x4[] Matricies = new Matrix4x4[UNITY_MAX_INSTANCE_COUNT]; public MaterialPropertyBlock Properties = new MaterialPropertyBlock(); public ParallelUpdate[] ParallelUpdates = new ParallelUpdate[UNITY_MAX_INSTANCE_COUNT]; #if UNITY_WEBGL public List RaycastHits = new List(); #else public ConcurrentBag RaycastHits = new ConcurrentBag(); #endif private Matrix4x4[] matrixScratchBuffer = new Matrix4x4[UNITY_MAX_INSTANCE_COUNT]; private InstanceBucket() { } public InstanceBucket(List> floatMaterialProperties, List> vectorMaterialProperties, List> matrixMaterialProperties) { RegisterMaterialPropertiesFloat(floatMaterialProperties); RegisterMaterialPropertiesVector(vectorMaterialProperties); RegisterMaterialPropertiesMatrix(matrixMaterialProperties); } public void RegisterMaterialPropertiesFloat(List> materialProperties) { foreach (var property in materialProperties) { Properties.SetFloatArray(property.Key, Repeat(property.Value, UNITY_MAX_INSTANCE_COUNT)); } } public void RegisterMaterialPropertiesVector(List> materialProperties) { foreach (var property in materialProperties) { Properties.SetVectorArray(property.Key, Repeat(property.Value, UNITY_MAX_INSTANCE_COUNT)); } } public void RegisterMaterialPropertiesMatrix(List> materialProperties) { foreach (var property in materialProperties) { Properties.SetMatrixArray(property.Key, Repeat(property.Value, UNITY_MAX_INSTANCE_COUNT)); } } public void UpdateJob(float deltaTime, Matrix4x4 localToWorld, int firstIndex, int lastIndex) { for (int i = firstIndex; i < lastIndex; ++i) { // Apply any per instance logic. ParallelUpdates[i]?.Invoke(deltaTime, Instances[i]); // Calculate the final transformation matrix. matrixScratchBuffer[i] = localToWorld * Matricies[i]; } } public void UpdateJobRaycast(float deltaTime, Matrix4x4 localToWorld, Box box, Ray ray, int firstIndex, int lastIndex) { // Pre calculate collider info. Vector3 boxHalfSize = box.Size * 0.5f; Vector3 boxMin = box.Center - boxHalfSize; Vector3 boxMax = box.Center + boxHalfSize; for (int i = firstIndex; i < lastIndex; ++i) { // Apply any per instance logic. ParallelUpdates[i]?.Invoke(deltaTime, Instances[i]); // Calculate the final transformation matrix. matrixScratchBuffer[i] = localToWorld * Matricies[i]; // Perform a ray cast against the current instance. First do a coarse test against a sphere then a fine test against the OOBB. // TODO - [Cameron-Micka] accelerate this with spatial partitioning? float radius = Vector3.Scale(boxHalfSize, matrixScratchBuffer[i].lossyScale).magnitude; if (RaycastSphere(ray, matrixScratchBuffer[i].GetColumn(3), radius)) { RaycastHit hitInfo; if (RaycastOOBB(ray, matrixScratchBuffer[i], boxMin, boxMax, out hitInfo)) { hitInfo.Instance = Instances[i]; RaycastHits.Add(hitInfo); } } } } public void Draw(UnityEngine.Mesh mesh, int submeshIndex, Material material, UnityEngine.Rendering.ShadowCastingMode shadowCastingMode, bool recieveShadows) { Graphics.DrawMeshInstanced(mesh, submeshIndex, material, matrixScratchBuffer, InstanceCount, Properties, shadowCastingMode, recieveShadows); } private static T[] Repeat(T element, int count) { var output = new T[count]; for (int i = 0; i < count; ++i) { output[i] = element; } return output; } private static bool RaycastSphere(Ray ray, Vector3 center, float radius) { // 5.3.2 Intersecting Ray or Segment Against Sphere // http://realtimecollisiondetection.net/ Vector3 m = ray.origin - center; float b = Vector3.Dot(m, ray.direction); float c = Vector3.Dot(m, m) - (radius * radius); // Exit if ray’s origin outside sphere (c > 0) and ray pointing away from sphere (b > 0). if (c > 0.0f && b > 0.0f) { return false; } // A negative discriminant corresponds to ray missing sphere. float discriminant = b * b - c; return (discriminant >= 0.0f); } private static bool Approximately(float a, float b, float epsilon = 0.0001f) { return (Mathf.Abs(a - b) < epsilon); } private static bool RaycastOOBB(Ray ray, Matrix4x4 localToWorld, Vector3 boxMin, Vector3 boxMax, out RaycastHit hitInfo) { // Transform the ray into the instance's local space. Matrix4x4 localToWorldInverse = localToWorld.inverse; Ray localRay = new Ray(localToWorldInverse.MultiplyPoint3x4(ray.origin), localToWorldInverse.MultiplyVector(ray.direction)); if (RaycastAABB(localRay, boxMin, boxMax, out hitInfo)) { // Transform back to world space. Vector3 localPoint = localRay.origin + (localRay.direction * hitInfo.Distance); hitInfo.Point = localToWorld.MultiplyPoint3x4(localPoint); hitInfo.Normal = new Vector3(Approximately(Mathf.Abs(localPoint.x), 0.5f) ? localPoint.x * 2.0f : 0.0f, Approximately(Mathf.Abs(localPoint.y), 0.5f) ? localPoint.y * 2.0f : 0.0f, Approximately(Mathf.Abs(localPoint.z), 0.5f) ? localPoint.z * 2.0f : 0.0f); hitInfo.Normal = localToWorld.MultiplyVector(hitInfo.Normal).normalized; hitInfo.Distance = (hitInfo.Point - ray.origin).magnitude; hitInfo.Ray = ray; return true; } return false; } private static bool RaycastAABB(Ray ray, Vector3 boxMin, Vector3 boxMax, out RaycastHit hitInfo) { // Fast and Robust Ray/OBB Intersection Using the Lorentz Transformation // https://www.realtimerendering.com/raytracinggems/rtg2/index.html Vector3 rayDirectionInverse = new Vector3(1.0f / ray.direction.x, 1.0f / ray.direction.y, 1.0f / ray.direction.z); Vector3 tMin = Vector3.Scale(boxMin - ray.origin, rayDirectionInverse); Vector3 tMax = Vector3.Scale(boxMax - ray.origin, rayDirectionInverse); Vector3 tMins = Vector3.Min(tMin, tMax); Vector3 tMaxes = Vector3.Max(tMin, tMax); float tBoxMin = Mathf.Max(Mathf.Max(tMins.x, tMins.y), tMins.z); float tBoxMax = Mathf.Min(Mathf.Min(tMaxes.x, tMaxes.y), tMaxes.z); hitInfo = new RaycastHit(); if (tBoxMin <= tBoxMax) { hitInfo.Distance = tBoxMin < 0.0f ? tBoxMax : tBoxMin; return true; } return false; } } private bool isInitialized = false; private List instanceBuckets = new List(); private List> floatMaterialProperties = new List>(); private List> vectorMaterialProperties = new List>(); private List> matrixMaterialProperties = new List>(); private System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); private long[] stopwatchSamples = new long[120]; private int stopwatchSampleIndex = 0; private float averageElapsedMilliseconds = 0.0f; private void Awake() { Initialize(); } private void OnDestroy() { Clear(); } private void LateUpdate() { DeferredRaycastHits.Clear(); UpdateBuckets(); foreach (var bucket in instanceBuckets) { // Draw each instance bucket. bucket.Draw(InstanceMesh, InstanceSubMeshIndex, InstanceMaterial, ShadowCastingMode, RecieveShadows); // Collect the aggregate raycast hits. if (RaycastInstances) { DeferredRaycastHits.AddRange(bucket.RaycastHits); } } } private void OnGUI() { if (DisplayUpdateTime) { if (stopwatchSampleIndex == stopwatchSamples.Length) { long sum = 0; for (int i = 0; i < stopwatchSampleIndex; ++i) { sum += stopwatchSamples[i]; } averageElapsedMilliseconds = (float)sum / stopwatchSampleIndex; stopwatchSampleIndex = 0; } else { stopwatchSamples[stopwatchSampleIndex] = stopwatch.ElapsedMilliseconds; ++stopwatchSampleIndex; } string label = string.Format("MeshInstancer Update: {0} instances @ {1:f2} ms", InstanceCount, averageElapsedMilliseconds); GUI.Label(new Rect(10.0f, Screen.height - 24.0f, Screen.height, 128.0f), label); } } private void Initialize() { if (isInitialized) { return; } // Register all properties specified in the component properties. foreach (FloatMaterialProperty property in FloatMaterialProperties) { RegisterMaterialPropertyCommonFloat(Shader.PropertyToID(property.Name), property.DefaultValue, false); } foreach (VectorMaterialProperty property in VectorMaterialProperties) { RegisterMaterialPropertyCommonVector(Shader.PropertyToID(property.Name), property.DefaultValue, false); } foreach (MatrixMaterialProperty property in MatrixMaterialProperties) { RegisterMaterialPropertyCommonMatrix(Shader.PropertyToID(property.Name), property.DefaultValue, false); } DeferredRaycastHits = new List(); isInitialized = true; } private void UpdateBuckets() { // Nothing to process. if (instanceBuckets.Count == 0) { return; } if (DisplayUpdateTime) { stopwatch.Restart(); } float deltaTime = Time.deltaTime; Matrix4x4 localToWorld = transform.localToWorldMatrix; int processorCount = Environment.ProcessorCount; // WebGL doesn't support threaded operations. #if UNITY_WEBGL foreach (InstanceBucket bucket in instanceBuckets) { if (RaycastInstances) { bucket.RaycastHits = new List(); bucket.UpdateJobRaycast(deltaTime, localToWorld, BoxCollider, DeferredRayQuery, 0, bucket.InstanceCount); } else { bucket.UpdateJob(deltaTime, localToWorld, 0, bucket.InstanceCount); } }; #else // We are on a single processor machine, so best to not multi-thread. if (processorCount == 1 || DisableParallelUpdate) { foreach (InstanceBucket bucket in instanceBuckets) { if (RaycastInstances) { bucket.RaycastHits = new ConcurrentBag(); bucket.UpdateJobRaycast(deltaTime, localToWorld, BoxCollider, DeferredRayQuery, 0, bucket.InstanceCount); } else { bucket.UpdateJob(deltaTime, localToWorld, 0, bucket.InstanceCount); } }; } else if (processorCount > instanceBuckets.Count) // More processors than buckets so split up the work within buckets. { int processorsPerBucket = Mathf.CeilToInt((float)processorCount / instanceBuckets.Count); int count = UNITY_MAX_INSTANCE_COUNT / processorsPerBucket; // Clear all bucket raycast hits before splicing the bucket. if (RaycastInstances) { foreach (InstanceBucket bucket in instanceBuckets) { bucket.RaycastHits = new ConcurrentBag(); } } Parallel.For(0, instanceBuckets.Count * processorsPerBucket, (i, state) => { int bucketIndex = i / processorsPerBucket; InstanceBucket bucket = instanceBuckets[bucketIndex]; int iteration = i % processorsPerBucket; int firstIndex = iteration * count; if (firstIndex < bucket.InstanceCount) { int lastIndex = firstIndex + count; // Ensure the whole bucket is processed for the last iteration. if (iteration == (processorsPerBucket - 1) || lastIndex > bucket.InstanceCount) { lastIndex = bucket.InstanceCount; } if (RaycastInstances) { bucket.UpdateJobRaycast(deltaTime, localToWorld, BoxCollider, DeferredRayQuery, firstIndex, lastIndex); } else { bucket.UpdateJob(deltaTime, localToWorld, firstIndex, lastIndex); } } }); } else // More buckets than processors, so each processor gets (at least) a bucket to chew though. { // Spin up an update job for each instance bucket. Parallel.ForEach(instanceBuckets, (bucket) => { if (RaycastInstances) { bucket.RaycastHits = new ConcurrentBag(); bucket.UpdateJobRaycast(deltaTime, localToWorld, BoxCollider, DeferredRayQuery, 0, bucket.InstanceCount); } else { bucket.UpdateJob(deltaTime, localToWorld, 0, bucket.InstanceCount); } }); } #endif // UNITY_WEBGL if (DisplayUpdateTime) { stopwatch.Stop(); } } private void OnDrawGizmos() { if (Application.isPlaying) { return; } if (InstanceMesh) { Gizmos.matrix = transform.localToWorldMatrix; if (InstanceMesh.normals.Length != 0) { Gizmos.DrawMesh(InstanceMesh); } else if (InstanceMaterial) { // Using Graphics.DrawMeshInstanced instead of Gizmos.DrawMesh because Gizmos.DrawMesh requires that a mesh has normals. Graphics.DrawMeshInstanced(InstanceMesh, InstanceSubMeshIndex, InstanceMaterial, new Matrix4x4[1] { Gizmos.matrix }, 1, null, ShadowCastingMode, RecieveShadows); } if (RaycastInstances) { Gizmos.color = Color.green; Gizmos.DrawWireCube(BoxCollider.Center, BoxCollider.Size); } } } /// /// Creates an instance at a position. /// public Instance Instantiate(Vector3 position, bool instantiateInWorldSpace = false) { return Instantiate(Matrix4x4.TRS(position, Quaternion.identity, Vector3.one), instantiateInWorldSpace); } /// /// Creates an instance at a position and rotation. /// public Instance Instantiate(Vector3 position, Quaternion rotation, bool instantiateInWorldSpace = false) { return Instantiate(Matrix4x4.TRS(position, rotation, Vector3.one), instantiateInWorldSpace); } /// /// Creates an instance at a position, rotation, and scale. /// public Instance Instantiate(Vector3 position, Quaternion rotation, Vector3 scale, bool instantiateInWorldSpace = false) { return Instantiate(Matrix4x4.TRS(position, rotation, scale), instantiateInWorldSpace); } /// /// Creates an instance at TRS matrix. /// public Instance Instantiate(Matrix4x4 transformation, bool instantiateInWorldSpace = false) { Initialize(); int instanceBucketIndex = AllocateInstanceBucketIndex(); int instanceIndex = AllocateInstanceIndex(instanceBucketIndex); InstanceBucket bucket = instanceBuckets[instanceBucketIndex]; bucket.Instances[instanceIndex] = new Instance(instanceIndex, instanceBucketIndex, null, this); bucket.Matricies[instanceIndex] = (instantiateInWorldSpace) ? transform.worldToLocalMatrix * transformation : transformation; ++InstanceCount; return bucket.Instances[instanceIndex]; } /// /// Removes all instances from the MeshInstancer. /// public void Clear() { // Destroy in reverse order to avoid array compacting. for (int i = (instanceBuckets.Count - 1); i >= 0; --i) { for (int j = (instanceBuckets[i].InstanceCount - 1); j >= 0; --j) { instanceBuckets[i].Instances[j].Destroy(); } } instanceBuckets.Clear(); } /// /// Registers a float material property that can be updated at runtime per instance. /// public bool RegisterMaterialProperty(string name, float defaultValue) { return RegisterMaterialPropertyCommonFloat(Shader.PropertyToID(name), defaultValue); } /// /// Registers a float material property that can be updated at runtime per instance. /// public bool RegisterMaterialProperty(int nameID, float defaultValue) { return RegisterMaterialPropertyCommonFloat(nameID, defaultValue); } /// /// Registers a vector material property that can be updated at runtime per instance. /// public bool RegisterMaterialProperty(string name, Vector4 defaultValue) { return RegisterMaterialPropertyCommonVector(Shader.PropertyToID(name), defaultValue); } /// /// Registers a vector material property that can be updated at runtime per instance. /// public bool RegisterMaterialProperty(int nameID, Vector4 defaultValue) { return RegisterMaterialPropertyCommonVector(nameID, defaultValue); } /// /// Registers a float matrix property that can be updated at runtime per instance. /// public bool RegisterMaterialProperty(string name, Matrix4x4 defaultValue) { return RegisterMaterialPropertyCommonMatrix(Shader.PropertyToID(name), defaultValue); } /// /// Registers a matrix material property that can be updated at runtime per instance. /// public bool RegisterMaterialProperty(int nameID, Matrix4x4 defaultValue) { return RegisterMaterialPropertyCommonMatrix(nameID, defaultValue); } /// /// Returns the hit thats the smallest distance away from the DeferredRayQuery origin. /// public bool GetClosestRaycastHit(ref RaycastHit hit) { Initialize(); hit.Reset(); foreach (RaycastHit h in DeferredRaycastHits) { if (h.Distance < hit.Distance) { hit = h; } } return (hit.Instance != null); } private bool RegisterMaterialPropertyCommonFloat(int nameID, float defaultValue, bool initialize = true) { if (initialize) { Initialize(); } if (floatMaterialProperties.Exists(element => (element.Key == nameID))) { Debug.LogWarningFormat("RegisterMaterialProperty failed because {0} has already been registered.", nameID); return false; } floatMaterialProperties.Add(new KeyValuePair(nameID, defaultValue)); foreach (var bucket in instanceBuckets) { bucket.RegisterMaterialPropertiesFloat(floatMaterialProperties); } return true; } private bool RegisterMaterialPropertyCommonVector(int nameID, Vector4 defaultValue, bool initialize = true) { if (initialize) { Initialize(); } if (vectorMaterialProperties.Exists(element => (element.Key == nameID))) { Debug.LogWarningFormat("RegisterMaterialProperty failed because {0} has already been registered.", nameID); return false; } vectorMaterialProperties.Add(new KeyValuePair(nameID, defaultValue)); foreach (var bucket in instanceBuckets) { bucket.RegisterMaterialPropertiesVector(vectorMaterialProperties); } return true; } private bool RegisterMaterialPropertyCommonMatrix(int nameID, Matrix4x4 defaultValue, bool initialize = true) { if (initialize) { Initialize(); } if (matrixMaterialProperties.Exists(element => (element.Key == nameID))) { Debug.LogWarningFormat("RegisterMaterialProperty failed because {0} has already been registered.", nameID); return false; } matrixMaterialProperties.Add(new KeyValuePair(nameID, defaultValue)); foreach (var bucket in instanceBuckets) { bucket.RegisterMaterialPropertiesMatrix(matrixMaterialProperties); } return true; } private void SetFloat(Instance instance, int nameID, float value) { Initialize(); if (!floatMaterialProperties.Exists(element => (element.Key == nameID))) { Debug.LogWarningFormat("SetFloat failed because {0} is not a registered material property on the MeshInstancer.", nameID); return; } InstanceBucket bucket = instanceBuckets[instance.InstanceBucketIndex]; float[] values = bucket.Properties.GetFloatArray(nameID); values[instance.InstanceIndex] = value; bucket.Properties.SetFloatArray(nameID, values); } private float GetFloat(Instance instance, int nameID) { Initialize(); if (!floatMaterialProperties.Exists(element => (element.Key == nameID))) { Debug.LogWarningFormat("GetFloat failed because {0} is not a registered material property on the MeshInstancer.", nameID); return 0.0f; } InstanceBucket bucket = instanceBuckets[instance.InstanceBucketIndex]; float[] values = bucket.Properties.GetFloatArray(nameID); return values[instance.InstanceIndex]; } private void SetVector(Instance instance, int nameID, Vector4 value) { Initialize(); if (!vectorMaterialProperties.Exists(element => (element.Key == nameID))) { Debug.LogWarningFormat("SetVector failed because {0} is not a registered material property on the MeshInstancer.", nameID); return; } InstanceBucket bucket = instanceBuckets[instance.InstanceBucketIndex]; Vector4[] values = bucket.Properties.GetVectorArray(nameID); values[instance.InstanceIndex] = value; bucket.Properties.SetVectorArray(nameID, values); } private Vector4 GetVector(Instance instance, int nameID) { Initialize(); if (!vectorMaterialProperties.Exists(element => (element.Key == nameID))) { Debug.LogWarningFormat("GetVector failed because {0} is not a registered material property on the MeshInstancer.", nameID); return Vector4.zero; } InstanceBucket bucket = instanceBuckets[instance.InstanceBucketIndex]; Vector4[] values = bucket.Properties.GetVectorArray(nameID); return values[instance.InstanceIndex]; } private void SetMatrix(Instance instance, int nameID, Matrix4x4 value) { Initialize(); if (!matrixMaterialProperties.Exists(element => (element.Key == nameID))) { Debug.LogWarningFormat("SetMatrix failed because {0} is not a registered material property on the MeshInstancer.", nameID); return; } InstanceBucket bucket = instanceBuckets[instance.InstanceBucketIndex]; Matrix4x4[] values = bucket.Properties.GetMatrixArray(nameID); values[instance.InstanceIndex] = value; bucket.Properties.SetMatrixArray(nameID, values); } private Matrix4x4 GetMatrix(Instance instance, int nameID) { Initialize(); if (!matrixMaterialProperties.Exists(element => (element.Key == nameID))) { Debug.LogWarningFormat("GetMatrix failed because {0} is not a registered material property on the MeshInstancer.", nameID); return Matrix4x4.identity; } InstanceBucket bucket = instanceBuckets[instance.InstanceBucketIndex]; Matrix4x4[] values = bucket.Properties.GetMatrixArray(nameID); return values[instance.InstanceIndex]; } private void Destroy(Instance instance) { // TODO - [Cameron-Micka] Defragment InstanceBuckets when instance bucket count becomes low across the list? InstanceBucket bucket = instanceBuckets[instance.InstanceBucketIndex]; int newInstanceCount = bucket.InstanceCount - 1; // If this is the last instance of the date, remove the whole instance bucket. if (newInstanceCount == 0) { instanceBuckets.RemoveAt(instance.InstanceBucketIndex); --InstanceCount; return; } // If this is not the last instance, move the last instance to the place of the recently destroyed instance. if (newInstanceCount != instance.InstanceIndex) { // Update the transformation and instance reference. bucket.Matricies[instance.InstanceIndex] = bucket.Matricies[newInstanceCount]; bucket.Instances[instance.InstanceIndex] = new Instance(instance.InstanceIndex, bucket.Instances[newInstanceCount].InstanceBucketIndex, bucket.Instances[newInstanceCount].UserData, this); bucket.Instances[newInstanceCount] = null; // Update material properties. foreach (var property in floatMaterialProperties) { float[] values = bucket.Properties.GetFloatArray(property.Key); values[instance.InstanceIndex] = values[newInstanceCount]; bucket.Properties.SetFloatArray(property.Key, values); } foreach (var property in vectorMaterialProperties) { Vector4[] values = bucket.Properties.GetVectorArray(property.Key); values[instance.InstanceIndex] = values[newInstanceCount]; bucket.Properties.SetVectorArray(property.Key, values); } foreach (var property in matrixMaterialProperties) { Matrix4x4[] values = bucket.Properties.GetMatrixArray(property.Key); values[instance.InstanceIndex] = values[newInstanceCount]; bucket.Properties.SetMatrixArray(property.Key, values); } // Update the parallel update delegate. bucket.ParallelUpdates[instance.InstanceIndex] = bucket.ParallelUpdates[newInstanceCount]; } bucket.InstanceCount = newInstanceCount; --InstanceCount; } private int AllocateInstanceBucketIndex() { for (int i = 0; i < instanceBuckets.Count; ++i) { if (instanceBuckets[i].InstanceCount < instanceBuckets[i].Matricies.Length) { return i; } } instanceBuckets.Add(new InstanceBucket(floatMaterialProperties, vectorMaterialProperties, matrixMaterialProperties)); return (instanceBuckets.Count - 1); } private int AllocateInstanceIndex(int instanceBucketIndex) { int instanceIndex = instanceBuckets[instanceBucketIndex].InstanceCount; ++instanceBuckets[instanceBucketIndex].InstanceCount; return instanceIndex; } } }