using System.Runtime.InteropServices; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using Unity.Profiling; namespace Obi { [StructLayout(LayoutKind.Sequential)] public struct BurstExtrudedMeshData { public int sectionVertexCount; public float thicknessScale; public float uvAnchor; public uint normalizeV; public Vector2 uvScale; public BurstExtrudedMeshData(ObiRopeExtrudedRenderer renderer) { sectionVertexCount = renderer.section.vertices.Count; uvAnchor = renderer.uvAnchor; thicknessScale = renderer.thicknessScale; uvScale = renderer.uvScale; normalizeV = (uint)(renderer.normalizeV ? 1 : 0); } } [StructLayout(LayoutKind.Sequential)] public struct ProceduralRopeVertex { public Vector3 pos; public Vector3 normal; public Vector4 tangent; public Vector4 color; public Vector2 uv; } public abstract class ObiExtrudedRopeRenderSystem : RenderSystem { public Oni.RenderingSystemType typeEnum { get => Oni.RenderingSystemType.ExtrudedRope; } public RendererSet renderers { get; } = new RendererSet(); protected List sortedRenderers = new List(); /**< temp list used to store renderers sorted by batch.*/ // specify vertex count and layout protected VertexAttributeDescriptor[] layout = { new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3), new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3), new VertexAttributeDescriptor(VertexAttribute.Tangent, VertexAttributeFormat.Float32, 4), new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.Float32, 4), new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2), }; static protected ProfilerMarker m_SetupRenderMarker = new ProfilerMarker("SetupExtrudedRopeRendering"); static protected ProfilerMarker m_RenderMarker = new ProfilerMarker("ExtrudedRopeRendering"); protected ObiSolver m_Solver; protected SubMeshDescriptor subMeshDescriptor = new SubMeshDescriptor(0, 0); protected List> batchList = new List>(); protected ObiNativeList rendererData; /**< for each renderer, data about smoother.*/ protected ObiNativeList pathSmootherIndices; /**< renderer indices, sorted by batch */ protected Dictionary sectionToIndex = new Dictionary(); protected ObiNativeVector2List sectionData; protected ObiNativeList sectionOffsets; /**< for each section, offset of its first entry in the sectionData array.*/ protected ObiNativeList sectionIndices; /**< for each renderer, index of the section used.*/ protected ObiNativeList vertexOffsets; /**< for each renderer, vertex offset in its batch mesh data.*/ protected ObiNativeList triangleOffsets; /**< for each renderer, triangle offset in its batch mesh data.*/ protected ObiNativeList vertexCounts; /**< for each renderer, vertex count.*/ protected ObiNativeList triangleCounts; /**< for each renderer, triangle count.*/ protected ObiPathSmootherRenderSystem pathSmootherSystem; public ObiExtrudedRopeRenderSystem(ObiSolver solver) { m_Solver = solver; rendererData = new ObiNativeList(); pathSmootherIndices = new ObiNativeList(); sectionData = new ObiNativeVector2List(); sectionOffsets = new ObiNativeList(); sectionIndices = new ObiNativeList(); vertexOffsets = new ObiNativeList(); triangleOffsets = new ObiNativeList(); vertexCounts = new ObiNativeList(); triangleCounts = new ObiNativeList(); } public void Dispose() { for (int i = 0; i < batchList.Count; ++i) batchList[i].Dispose(); batchList.Clear(); if (rendererData != null) rendererData.Dispose(); if (pathSmootherIndices != null) pathSmootherIndices.Dispose(); if (sectionData != null) sectionData.Dispose(); if (sectionOffsets != null) sectionOffsets.Dispose(); if (sectionIndices != null) sectionIndices.Dispose(); if (vertexOffsets != null) vertexOffsets.Dispose(); if (triangleOffsets != null) triangleOffsets.Dispose(); if (vertexCounts != null) vertexCounts.Dispose(); if (triangleCounts != null) triangleCounts.Dispose(); } private void Clear() { rendererData.Clear(); pathSmootherIndices.Clear(); sectionData.Clear(); sectionToIndex.Clear(); sectionOffsets.Clear(); vertexOffsets.Clear(); triangleOffsets.Clear(); vertexCounts.Clear(); triangleCounts.Clear(); for (int i = 0; i < batchList.Count; ++i) batchList[i].Dispose(); batchList.Clear(); } private void CreateBatches() { // generate batches: sortedRenderers.Clear(); for (int i = 0; i < renderers.Count; ++i) { if (renderers[i].TryGetComponent(out ObiPathSmoother smoother) && smoother.enabled) { renderers[i].renderParameters.layer = renderers[i].gameObject.layer; batchList.Add(new ProceduralRenderBatch(i, renderers[i].material, renderers[i].renderParameters)); sortedRenderers.Add(renderers[i]); } } vertexOffsets.ResizeUninitialized(sortedRenderers.Count); triangleOffsets.ResizeUninitialized(sortedRenderers.Count); // sort batches: batchList.Sort(); // reorder renderers based on sorted batches: sortedRenderers.Clear(); for (int i = 0; i < batchList.Count; ++i) { var batch = batchList[i]; sortedRenderers.Add(renderers[batch.firstRenderer]); batch.firstRenderer = i; int pathIndex = sortedRenderers[i].GetComponent().indexInSystem; pathSmootherIndices.Add(pathIndex); // get or create extruded section: if (!sectionToIndex.TryGetValue(sortedRenderers[i].section, out int sectionIndex)) { sectionIndex = sectionOffsets.count; sectionToIndex[sortedRenderers[i].section] = sectionIndex; sectionOffsets.Add(sectionData.count); sectionData.AddRange(sortedRenderers[i].section.vertices); } sectionIndices.Add(sectionIndex); // calculate vertex and triangle counts for each renderer: int chunkStart = pathSmootherSystem.chunkOffsets[pathIndex]; int chunkAmount = pathSmootherSystem.chunkOffsets[pathIndex + 1] - chunkStart; for (int k = chunkStart; k < chunkStart + chunkAmount; ++k) { int frameCount = pathSmootherSystem.smoothFrameCounts[k]; batch.vertexCount += frameCount * sortedRenderers[i].section.vertices.Count; batch.triangleCount += (frameCount - 1) * (sortedRenderers[i].section.vertices.Count - 1) * 2; } vertexCounts.Add(batch.vertexCount); triangleCounts.Add(batch.triangleCount); rendererData.Add(new BurstExtrudedMeshData(sortedRenderers[i])); } // add last entry to section offsets: sectionOffsets.Add(sectionData.count); } private void CalculateMeshOffsets() { for (int i = 0; i < batchList.Count; ++i) { var batch = batchList[i]; int vtxCount = 0; int triCount = 0; // Calculate vertex and triangle offsets for each renderer in the batch: for (int j = 0; j < batch.rendererCount; ++j) { int r = batch.firstRenderer + j; vertexOffsets[r] = vtxCount; triangleOffsets[r] = triCount; vtxCount += vertexCounts[r]; triCount += triangleCounts[r]; } } } public virtual void Setup() { pathSmootherSystem = m_Solver.GetRenderSystem() as ObiPathSmootherRenderSystem; if (pathSmootherSystem == null) return; using (m_SetupRenderMarker.Auto()) { Clear(); CreateBatches(); ObiUtils.MergeBatches(batchList); CalculateMeshOffsets(); } } public abstract void Render(); public void Step() { } public void BakeMesh(ObiRopeExtrudedRenderer renderer, ref Mesh mesh, bool transformToActorLocalSpace = false) { int index = sortedRenderers.IndexOf(renderer); for (int i = 0; i < batchList.Count; ++i) { var batch = batchList[i]; if (index >= batch.firstRenderer && index < batch.firstRenderer + batch.rendererCount) { batch.BakeMesh(vertexOffsets[index], vertexCounts[index], triangleOffsets[index], triangleCounts[index], renderer.actor.actorSolverToLocalMatrix, ref mesh, transformToActorLocalSpace); return; } } } } }