using System; using System.Collections.Generic; using RebootKit.Engine.Foundation; using RebootKit.Engine.Main; using TriInspector; using Unity.Burst; using Unity.Collections; using Unity.Jobs; using Unity.Mathematics; using UnityEngine; namespace RebootReality.jelycho.Ropes { public class GutsRenderer : MonoBehaviour { [SerializeField] RopesManager m_RopesManager; [SerializeField] float m_RenderDistance = 100.0f; [SerializeField] Material m_Material; [SerializeField] int m_Layer; [SerializeField] GutMeshGenerationConfig m_GutMeshGenerationConfig = new GutMeshGenerationConfig { radius = 0.2f, resolution = 16 }; readonly List m_Meshes = new List(); [ShowInInspector, TriInspector.ReadOnly] int m_RenderCount = 0; void OnDestroy() { foreach (Mesh mesh in m_Meshes) { Destroy(mesh); } m_Meshes.Clear(); } void LateUpdate() { m_RenderCount = 0; for (int ropeIndex = 0; ropeIndex < m_RopesManager.RopesCount; ++ropeIndex) { NativeArray ropePositions = m_RopesManager.PeekRopePositions(ropeIndex); if (ropePositions.Length < 2) { continue; } float distanceSquared = math.distancesq(m_RopesManager.GetRopeBounds(ropeIndex).center, RR.MainCamera.transform.position); if (distanceSquared > m_RenderDistance * m_RenderDistance) { continue; } if (!m_RopesManager.IsRopeBoundsInFrustum(ropeIndex, RR.MainCamera)) { continue; } int vertexCount = m_GutMeshGenerationConfig.resolution * ropePositions.Length; int indexCount = m_GutMeshGenerationConfig.resolution * (ropePositions.Length - 1) * 6; GenerateGutVertexDataJob job = new GenerateGutVertexDataJob { Config = m_GutMeshGenerationConfig, RopePositions = ropePositions, Positions = new NativeArray(vertexCount, Allocator.TempJob), Normals = new NativeArray(vertexCount, Allocator.TempJob), UVs0 = new NativeArray(vertexCount, Allocator.TempJob), UVs1 = new NativeArray(vertexCount, Allocator.TempJob), Indices = new NativeArray(indexCount, Allocator.TempJob) }; job.Schedule().Complete(); Mesh mesh = GetOrCreateMesh(ropeIndex); mesh.Clear(); mesh.SetVertices(job.Positions); mesh.SetNormals(job.Normals); mesh.SetUVs(0, job.UVs0); mesh.SetUVs(1, job.UVs1); mesh.SetIndices(job.Indices, MeshTopology.Triangles, 0, false); job.Positions.Dispose(); job.Normals.Dispose(); job.UVs0.Dispose(); job.UVs1.Dispose(); job.Indices.Dispose(); Graphics.DrawMesh(mesh, transform.localToWorldMatrix, m_Material, m_Layer, null, 0, null, UnityEngine.Rendering.ShadowCastingMode.On, true); m_RenderCount += 1; } } Mesh GetOrCreateMesh(int ropeIndex) { if (ropeIndex < m_Meshes.Count) { return m_Meshes[ropeIndex]; } Mesh mesh = new Mesh { name = $"GutMesh_{ropeIndex}", indexFormat = UnityEngine.Rendering.IndexFormat.UInt32 }; mesh.MarkDynamic(); m_Meshes.Add(mesh); return mesh; } [BurstCompile] struct GenerateGutVertexDataJob : IJob { [Unity.Collections.ReadOnly] public GutMeshGenerationConfig Config; [Unity.Collections.ReadOnly] public NativeArray RopePositions; [WriteOnly] public NativeArray Positions; [WriteOnly] public NativeArray Normals; [WriteOnly] public NativeArray UVs0; public NativeArray UVs1; [WriteOnly] public NativeArray Indices; public void Execute() { float ropeLength = 0f; for (int ropePositionIndex = 0; ropePositionIndex < RopePositions.Length; ++ropePositionIndex) { bool isLast = ropePositionIndex >= RopePositions.Length - 1; float3 start = RopePositions[ropePositionIndex]; float3 end = isLast ? RopePositions[ropePositionIndex - 1] : RopePositions[ropePositionIndex + 1]; float3 forward = math.normalize(end - start); if (isLast) { forward = -forward; } float3 right = math.normalize(math.cross(forward, math.abs(forward.x) > 0.99f ? new float3(0, 1, 0) : new float3(1, 0, 0))); float3 up = math.cross(forward, right); for (int i = 0; i < Config.resolution; ++i) { float angle = (float) i / Config.resolution * math.PI * 2f; float3 offset = (math.cos(angle) * right + math.sin(angle) * up) * Config.radius; int vertexIndex = ropePositionIndex * Config.resolution + (i % Config.resolution); Positions[vertexIndex] = start + offset; Normals[vertexIndex] = math.normalize(offset); UVs0[vertexIndex] = new float2((float) i / (Config.resolution - 1), ropePositionIndex % 2 == 0 ? 0 : 1); UVs1[vertexIndex] = new float2(ropeLength, ropeLength); } ropeLength += math.distance(start, end); } for (int ropePositionIndex = 0; ropePositionIndex < RopePositions.Length; ++ropePositionIndex) { for (int i = 0; i < Config.resolution; ++i) { int vertexIndex = ropePositionIndex * Config.resolution + (i % Config.resolution); UVs1[vertexIndex] = new float2(UVs1[vertexIndex].x, ropeLength); } } int index = 0; for (int ropePositionIndex = 0; ropePositionIndex < RopePositions.Length - 1; ++ropePositionIndex) { for (int i = 0; i < Config.resolution; ++i) { int current = ropePositionIndex * Config.resolution + i; int next = ropePositionIndex * Config.resolution + (i + 1) % Config.resolution; int currentNextRow = (ropePositionIndex + 1) * Config.resolution + i; int nextNextRow = (ropePositionIndex + 1) * Config.resolution + (i + 1) % Config.resolution; Indices[index++] = current; Indices[index++] = next; Indices[index++] = currentNextRow; Indices[index++] = currentNextRow; Indices[index++] = next; Indices[index++] = nextNextRow; } } } } [Serializable] struct GutMeshGenerationConfig { [Min(0.01f)] public float radius; [Range(3, 64)] public int resolution; } } }