using System; using System.Collections.Generic; using UnityEngine; using Unity.Profiling; namespace Obi { [AddComponentMenu("Physics/Obi/Obi Rope Mesh Renderer", 886)] [ExecuteInEditMode] [RequireComponent(typeof(MeshRenderer))] [RequireComponent(typeof(MeshFilter))] [RequireComponent(typeof(ObiPathSmoother))] public class ObiRopeMeshRenderer : MonoBehaviour { static ProfilerMarker m_UpdateMeshRopeRendererChunksPerfMarker = new ProfilerMarker("UpdateMeshRopeRenderer"); [SerializeProperty("SourceMesh")] [SerializeField] private Mesh mesh; [SerializeProperty("SweepAxis")] [SerializeField] private ObiPathFrame.Axis axis; public float volumeScaling = 0; public bool stretchWithRope = true; public bool spanEntireLength = true; [SerializeProperty("Instances")] [SerializeField] private int instances = 1; [SerializeProperty("InstanceSpacing")] [SerializeField] private float instanceSpacing = 1; public float offset = 0; public Vector3 scale = Vector3.one; [HideInInspector] [SerializeField] private float meshSizeAlongAxis = 1; private Vector3[] inputVertices; private Vector3[] inputNormals; private Vector4[] inputTangents; private Vector3[] vertices; private Vector3[] normals; private Vector4[] tangents; private int[] orderedVertices = new int[0]; private ObiPathSmoother smoother; public Mesh SourceMesh { set { mesh = value; PreprocessInputMesh(); } get { return mesh; } } public ObiPathFrame.Axis SweepAxis { set { axis = value; PreprocessInputMesh(); } get { return axis; } } public int Instances { set { instances = value; PreprocessInputMesh(); } get { return instances; } } public float InstanceSpacing { set { instanceSpacing = value; PreprocessInputMesh(); } get { return instanceSpacing; } } [HideInInspector] [NonSerialized] public Mesh deformedMesh; void OnEnable() { smoother = GetComponent(); smoother.OnCurveGenerated += UpdateRenderer; PreprocessInputMesh(); } void OnDisable() { smoother.OnCurveGenerated -= UpdateRenderer; GameObject.DestroyImmediate(deformedMesh); } private void PreprocessInputMesh() { if (deformedMesh == null) { deformedMesh = new Mesh(); deformedMesh.name = "deformedMesh"; deformedMesh.MarkDynamic(); GetComponent().mesh = deformedMesh; } deformedMesh.Clear(); if (mesh == null) { orderedVertices = new int[0]; return; } // Clamp instance count to a positive value. instances = Mathf.Max(0, instances); // combine all mesh instances into a single mesh: Mesh combinedMesh = new Mesh(); CombineInstance[] meshInstances = new CombineInstance[instances]; Vector3 pos = Vector3.zero; // initial offset for the combined mesh is half the size of its bounding box in the swept axis: pos[(int)axis] = mesh.bounds.extents[(int)axis]; for (int i = 0; i < instances; ++i) { meshInstances[i].mesh = mesh; meshInstances[i].transform = Matrix4x4.TRS(pos, Quaternion.identity, Vector3.one); pos[(int)axis] = mesh.bounds.extents[(int)axis] + (i + 1) * mesh.bounds.size[(int)axis] * instanceSpacing; } combinedMesh.CombineMeshes(meshInstances, true, true); // get combined mesh data: inputVertices = combinedMesh.vertices; inputNormals = combinedMesh.normals; inputTangents = combinedMesh.tangents; // sort vertices along curve axis: float[] keys = new float[inputVertices.Length]; orderedVertices = new int[inputVertices.Length]; for (int i = 0; i < keys.Length; ++i) { keys[i] = inputVertices[i][(int)axis]; orderedVertices[i] = i; } Array.Sort(keys, orderedVertices); // Copy the combined mesh data to deform it: deformedMesh.vertices = combinedMesh.vertices; deformedMesh.normals = combinedMesh.normals; deformedMesh.tangents = combinedMesh.tangents; deformedMesh.uv = combinedMesh.uv; deformedMesh.uv2 = combinedMesh.uv2; deformedMesh.uv3 = combinedMesh.uv3; deformedMesh.uv4 = combinedMesh.uv4; deformedMesh.colors = combinedMesh.colors; deformedMesh.triangles = combinedMesh.triangles; vertices = deformedMesh.vertices; normals = deformedMesh.normals; tangents = deformedMesh.tangents; // Calculate scale along swept axis so that the mesh spans the entire lenght of the rope if required. meshSizeAlongAxis = combinedMesh.bounds.size[(int)axis]; // destroy combined mesh: GameObject.DestroyImmediate(combinedMesh); } public void UpdateRenderer(ObiActor actor) { using (m_UpdateMeshRopeRendererChunksPerfMarker.Auto()) { if (mesh == null) return; if (smoother.smoothChunks.Count == 0) return; ObiList curve = smoother.smoothChunks[0]; if (curve.Count < 2) return; var rope = actor as ObiRopeBase; float actualToRestLengthRatio = stretchWithRope ? smoother.SmoothLength / rope.restLength : 1; // squashing factor, makes mesh thinner when stretched and thicker when compresssed. float squashing = Mathf.Clamp(1 + volumeScaling * (1 / Mathf.Max(actualToRestLengthRatio, 0.01f) - 1), 0.01f, 2); // Calculate scale along swept axis so that the mesh spans the entire lenght of the rope if required. Vector3 actualScale = scale; if (spanEntireLength) actualScale[(int)axis] = rope.restLength / meshSizeAlongAxis; float previousVertexValue = 0; float meshLength = 0; int index = 0; int nextIndex = 1; int prevIndex = 0; float sectionMagnitude = Vector3.Distance(curve[index].position, curve[nextIndex].position); // basis matrix for deforming the mesh: Matrix4x4 basis = curve[0].ToMatrix(axis); for (int i = 0; i < orderedVertices.Length; ++i) { int vIndex = orderedVertices[i]; float vertexValue = inputVertices[vIndex][(int)axis] * actualScale[(int)axis] + offset; // Calculate how much we've advanced in the sort axis since the last vertex: meshLength += (vertexValue - previousVertexValue) * actualToRestLengthRatio; previousVertexValue = vertexValue; // If we have advanced to the next section of the curve: while (meshLength > sectionMagnitude && sectionMagnitude > Mathf.Epsilon) { meshLength -= sectionMagnitude; index = Mathf.Min(index + 1, curve.Count - 1); // Calculate previous and next curve indices: nextIndex = Mathf.Min(index + 1, curve.Count - 1); prevIndex = Mathf.Max(index - 1, 0); // Calculate current tangent as the vector between previous and next curve points: sectionMagnitude = Vector3.Distance(curve[index].position, curve[nextIndex].position); // Update basis matrix: basis = curve[index].ToMatrix(axis); } float sectionThickness = curve[index].thickness; // calculate deformed vertex position: Vector3 offsetFromCurve = Vector3.Scale(inputVertices[vIndex], actualScale * sectionThickness * squashing); offsetFromCurve[(int)axis] = meshLength; vertices[vIndex] = curve[index].position + basis.MultiplyVector(offsetFromCurve); normals[vIndex] = basis.MultiplyVector(inputNormals[vIndex]); tangents[vIndex] = basis * inputTangents[vIndex]; // avoids expensive implicit conversion from Vector4 to Vector3. tangents[vIndex].w = inputTangents[vIndex].w; } CommitMeshData(); } } private void CommitMeshData() { deformedMesh.vertices = vertices; deformedMesh.normals = normals; deformedMesh.tangents = tangents; deformedMesh.RecalculateBounds(); } } }