// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections.Generic; using UnityEngine; namespace Microsoft.MixedReality.GraphicsTools.Samples.MeshInstancing { /// /// Places instances randomly on the triangles of a mesh. /// [RequireComponent(typeof(MeshFilter))] public class InstancingPlaceOnMesh : MonoBehaviour { [SerializeField] private MeshInstancer instancer = null; [SerializeField, Min(1)] private int instanceCount = 20000; [SerializeField] private float instanceScale = 1.0f; private class StateData { public float launchTime; public Vector3 acceleration; public Vector3 velocity; } /// /// Re-spawn instances when a property changes. /// private void OnValidate() { if (instancer != null && instancer.InstanceCount != 0) { CreateInstances(); } } /// /// Create instances on enable. /// private void OnEnable() { CreateInstances(); } /// /// Queries a mesh for its triangles and weights them based on area. /// private void CreateInstances() { // Clear any existing instances. instancer = (instancer == null) ? GetComponent() : instancer; instancer.Clear(); // Get the verticies and indicies from the current mesh filter. MeshFilter meshFilter = GetComponent(); if (meshFilter == null) { return; } UnityEngine.Mesh mesh = meshFilter.sharedMesh; if (mesh == null) { return; } Vector3[] verticies = mesh.vertices; int[] indicies = mesh.triangles; // Turn the verticies and indicies into triangles and areas. int triangleCount = indicies.Length / 3; Vector3[,] triangles = new Vector3[triangleCount, 3]; float[] areas = new float[triangleCount]; Vector3[] normals = new Vector3[triangleCount]; for (int i = 0; i < indicies.Length; i += 3) { int index = i / 3; triangles[index, 0] = verticies[indicies[i + 0]]; triangles[index, 1] = verticies[indicies[i + 1]]; triangles[index, 2] = verticies[indicies[i + 2]]; // Area of a triangle is half of the cross product's magnitude. Vector3 a = triangles[index, 1] - triangles[index, 0]; Vector3 b = triangles[index, 2] - triangles[index, 1]; Vector3 cross = Vector3.Cross(a, b); areas[index] = cross.magnitude * 0.5f; normals[index] = cross.normalized; } // Accumulate the area of the mesh. float meshArea = 0.0f; for (int i = 0; i < areas.Length; ++i) { meshArea += areas[i]; } // The mesh has no area. if (meshArea <= 0.0f) { return; } int colorID = Shader.PropertyToID("_Color"); for (int i = 0; i < instanceCount; ++i) { int index; Vector3[] triangle = PickRandomTriangle(triangles, areas, meshArea, out index); Vector3 nomal = normals[index]; float bias = Random.Range(0.01f, 0.02f); Vector3 position = PickRandomPointOnTriangle(triangle) + (nomal * bias); var instance = instancer.Instantiate(position, Quaternion.LookRotation(nomal), Vector3.one * instanceScale); instance.SetVector(colorID, Random.ColorHSV()); // Set the data to use during update. instance.UserData = new StateData() { launchTime = Random.Range(2.0f, 20.0f), acceleration = nomal * 0.001f, velocity = nomal * 0.01f }; instance.SetParallelUpdate(ParallelUpdate); } } /// /// Picks a random triangle weighted by area. /// private static Vector3[] PickRandomTriangle(Vector3[,] triangles, float[] areas, float meshArea, out int index) { var random = Random.Range(0.0f, meshArea); int triangleCount = triangles.GetLength(0); for (int i = 0; i < triangleCount; ++i) { if (random < areas[i]) { index = i; return new Vector3[] { triangles[index, 0], triangles[index, 1], triangles[index, 2] }; } random -= areas[i]; } index = triangleCount - 1; return new Vector3[] { triangles[index, 0], triangles[index, 1], triangles[index, 2] }; } /// /// Picks a random 3D point on a triangle. /// private static Vector3 PickRandomPointOnTriangle(Vector3[] triangle) { float r1 = Mathf.Sqrt(Random.Range(0.0f, 1.0f)); float r2 = Random.Range(0.0f, 1.0f); float m1 = 1 - r1; float m2 = r1 * (1.0f - r2); float m3 = r2 * r1; Vector3 a = triangle[0]; Vector3 b = triangle[1]; Vector3 c = triangle[2]; return (m1 * a) + (m2 * b) + (m3 * c); } /// /// Method called potentially concurrently across many threads. Make sure all function calls are thread safe. /// private void ParallelUpdate(float deltaTime, MeshInstancer.Instance instance) { // Cast the user data to our data type. StateData data = (StateData)instance.UserData; data.launchTime -= deltaTime; if (data.launchTime <= 0.0f) { // Euler integration. data.velocity += data.acceleration * deltaTime; instance.LocalPosition += data.velocity * deltaTime; // Update the user data state. instance.UserData = data; } else { // Update the user data state. instance.UserData = data; } } } }