222 lines
8.5 KiB
C#
222 lines
8.5 KiB
C#
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);
|
||
}
|
||
}
|
||
}
|