using System.Collections.Generic; using UnityEngine; /// /// 单 Mesh 线缆渲染(移植自 Assets/Prototype/2D线缆物理交互 原型的 drawCablePath): /// 把平滑后的折线扩成多层 ribbon(阴影/描边/主体/中间调/高光),顶点色分带, /// 中间调与高光垂直上移形成顶光效果,阴影下移形成落影。 /// 带序由三角形提交顺序决定(后画的盖前画的),对外只有一个 sortingOrder, /// 自遮挡时把尾段几何追加在最后,等效原型的 tail-over-head 重绘。 /// [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public class CableMeshRenderer : MonoBehaviour { [System.Serializable] public struct Band { public Color color; public float width; // 世界单位 public float lift; // 垂直偏移,+向上 / -向下(对应原型的屏幕空间 translate) public bool skipInTail; // 尾段重绘时跳过(原型尾段不重画阴影) public Band(Color color, float width, float lift, bool skipInTail = false) { this.color = color; this.width = width; this.lift = lift; this.skipInTail = skipInTail; } } private MeshFilter _filter; private MeshRenderer _renderer; private Mesh _mesh; private Band[] _bands = System.Array.Empty(); private const int CapSegments = 10; // 圆头端帽半圆细分 private float _tipTaperFraction; // 末端收窄段占全长比例(0 关闭) private float _tipTaperScale = 1f; private Color _environmentTint = Color.white; private readonly List _vertices = new(1024); private readonly List _colors = new(1024); private readonly List _triangles = new(4096); private void Awake() { _filter = GetComponent(); _renderer = GetComponent(); _mesh = new Mesh { name = "Cable Mesh" }; _mesh.MarkDynamic(); _filter.sharedMesh = _mesh; _renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; _renderer.receiveShadows = false; _renderer.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off; _renderer.reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off; } public void Configure(Material material, string sortingLayerName, int sortingOrder) { if (_renderer == null) _renderer = GetComponent(); _renderer.sharedMaterial = material; _renderer.sortingLayerName = sortingLayerName; _renderer.sortingOrder = sortingOrder; } public void SetBands(Band[] bands) { _bands = bands ?? System.Array.Empty(); } /// /// 设置动态网格的环境光色调。RGB 会调制各色带,Alpha 保持原值。 /// public void SetEnvironmentTint(Color tint) { _environmentTint = tint; } /// 末端宽度收窄:最后 fraction 段从全宽线性收到 scale 倍(线头略细于孔圈)。 public void SetTipTaper(float fraction, float scale) { _tipTaperFraction = Mathf.Clamp01(fraction); _tipTaperScale = Mathf.Clamp(scale, 0.1f, 1f); } public void SetVisible(bool visible) { if (_renderer == null) _renderer = GetComponent(); _renderer.enabled = visible; } /// /// 用世界坐标折线重建网格。顶点直接使用世界坐标,本物体的世界变换须保持 identity /// (由创建方负责摆到世界原点)。tailStart >= 0 时,从该下标起把非阴影带重绘一遍盖在最上。 /// roundEndCap 时线缆末端用圆头端帽(半圆,同旧 LineRenderer 圆头),平头改圆头。 /// public void UpdateMesh(Vector3[] points, int count, int tailStart, bool roundEndCap = false) { if (_mesh == null || points == null || count < 2) return; // 顶点是世界坐标,本物体世界变换必须保持 identity(父级面板移动时会被带偏,持续归零) if (transform.position != Vector3.zero) transform.position = Vector3.zero; if (transform.rotation != Quaternion.identity) transform.rotation = Quaternion.identity; _vertices.Clear(); _colors.Clear(); _triangles.Clear(); for (int b = 0; b < _bands.Length; b++) { AppendBand(points, 0, count, in _bands[b], roundEndCap); } if (tailStart >= 0 && tailStart < count - 1) { for (int b = 0; b < _bands.Length; b++) { if (_bands[b].skipInTail) continue; AppendBand(points, tailStart, count, in _bands[b], roundEndCap); } } _mesh.Clear(); _mesh.SetVertices(_vertices); _mesh.SetColors(_colors); _mesh.SetTriangles(_triangles, 0); _mesh.RecalculateBounds(); } private void AppendBand(Vector3[] points, int start, int count, in Band band, bool roundEndCap = false) { int baseIndex = _vertices.Count; float half = band.width * 0.5f; Color32 color = new Color( band.color.r * _environmentTint.r, band.color.g * _environmentTint.g, band.color.b * _environmentTint.b, band.color.a); for (int i = start; i < count; i++) { Vector3 prev = points[Mathf.Max(start, i - 1)]; Vector3 next = points[Mathf.Min(count - 1, i + 1)]; Vector3 dir = next - prev; dir.z = 0f; float mag = dir.magnitude; Vector3 n = mag > 1e-6f ? new Vector3(-dir.y / mag, dir.x / mag, 0f) : Vector3.right; // 偏移量取"世界上方向在截面上的投影":水平段全额偏移(同原型的屏幕空间平移), // 竖直段自然衰减为 0——偏移始终垂直于线身,不会在出线口/末端沿线方向戳出残留 Vector3 liftDir = Vector3.up - (mag > 1e-6f ? dir / mag * (dir.y / mag) : Vector3.zero); Vector3 p = points[i] + liftDir * band.lift; float taperedHalf = half * TipTaper(i, count); _vertices.Add(p + n * taperedHalf); _vertices.Add(p - n * taperedHalf); _colors.Add(color); _colors.Add(color); } int rows = count - start; for (int r = 0; r < rows - 1; r++) { int i0 = baseIndex + r * 2; _triangles.Add(i0); _triangles.Add(i0 + 2); _triangles.Add(i0 + 1); _triangles.Add(i0 + 1); _triangles.Add(i0 + 2); _triangles.Add(i0 + 3); } if (roundEndCap && count - start >= 2) { AppendRoundCap(points, count, in band, color, half * _tipTaperScale); } } /// 末端宽度收窄系数:最后 _tipTaperFraction 段从 1 线性降到 _tipTaperScale。 private float TipTaper(int index, int count) { if (_tipTaperFraction <= 0f || count < 2) return 1f; float f = index / (float)(count - 1); float taperStart = 1f - _tipTaperFraction; if (f <= taperStart) return 1f; return Mathf.Lerp(1f, _tipTaperScale, (f - taperStart) / _tipTaperFraction); } /// 末端半圆端帽:沿末段方向向前鼓出,半径即带宽一半——线缆自身的圆头,非附加物。 private void AppendRoundCap(Vector3[] points, int count, in Band band, Color32 color, float half) { Vector3 dir = points[count - 1] - points[count - 2]; dir.z = 0f; float mag = dir.magnitude; if (mag < 1e-6f) return; dir /= mag; Vector3 n = new(-dir.y, dir.x, 0f); Vector3 liftDir = Vector3.up - dir * dir.y; Vector3 center = points[count - 1] + liftDir * band.lift; int baseIndex = _vertices.Count; _vertices.Add(center); _colors.Add(color); for (int i = 0; i <= CapSegments; i++) { // φ 从 -π/2(−n 侧)扫到 +π/2(+n 侧),中点朝向 dir:正好补上平头切面 float phi = -Mathf.PI * 0.5f + i * (Mathf.PI / CapSegments); _vertices.Add(center + (n * Mathf.Sin(phi) + dir * Mathf.Cos(phi)) * half); _colors.Add(color); } for (int i = 0; i < CapSegments; i++) { _triangles.Add(baseIndex); _triangles.Add(baseIndex + 1 + i + 1); _triangles.Add(baseIndex + 1 + i); } } }