Files

222 lines
8.5 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 单 Mesh 线缆渲染(移植自 Assets/Prototype/2D线缆物理交互 原型的 drawCablePath):
/// 把平滑后的折线扩成多层 ribbon(阴影/描边/主体/中间调/高光),顶点色分带,
/// 中间调与高光垂直上移形成顶光效果,阴影下移形成落影。
/// 带序由三角形提交顺序决定(后画的盖前画的),对外只有一个 sortingOrder,
/// 自遮挡时把尾段几何追加在最后,等效原型的 tail-over-head 重绘。
/// </summary>
[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<Band>();
private const int CapSegments = 10; // 圆头端帽半圆细分
private float _tipTaperFraction; // 末端收窄段占全长比例(0 关闭)
private float _tipTaperScale = 1f;
private Color _environmentTint = Color.white;
private readonly List<Vector3> _vertices = new(1024);
private readonly List<Color32> _colors = new(1024);
private readonly List<int> _triangles = new(4096);
private void Awake()
{
_filter = GetComponent<MeshFilter>();
_renderer = GetComponent<MeshRenderer>();
_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<MeshRenderer>();
_renderer.sharedMaterial = material;
_renderer.sortingLayerName = sortingLayerName;
_renderer.sortingOrder = sortingOrder;
}
public void SetBands(Band[] bands)
{
_bands = bands ?? System.Array.Empty<Band>();
}
/// <summary>
/// 设置动态网格的环境光色调。RGB 会调制各色带,Alpha 保持原值。
/// </summary>
public void SetEnvironmentTint(Color tint)
{
_environmentTint = tint;
}
/// <summary>末端宽度收窄:最后 fraction 段从全宽线性收到 scale 倍(线头略细于孔圈)。</summary>
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<MeshRenderer>();
_renderer.enabled = visible;
}
/// <summary>
/// 用世界坐标折线重建网格。顶点直接使用世界坐标,本物体的世界变换须保持 identity
/// (由创建方负责摆到世界原点)。tailStart >= 0 时,从该下标起把非阴影带重绘一遍盖在最上。
/// roundEndCap 时线缆末端用圆头端帽(半圆,同旧 LineRenderer 圆头),平头改圆头。
/// </summary>
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);
}
}
/// <summary>末端宽度收窄系数:最后 _tipTaperFraction 段从 1 线性降到 _tipTaperScale。</summary>
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);
}
/// <summary>末端半圆端帽:沿末段方向向前鼓出,半径即带宽一半——线缆自身的圆头,非附加物。</summary>
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);
}
}
}