This repository has been archived on 2025-08-02. You can view files and clone it, but cannot push or open issues or pull requests.
Files
Cryville.EEW.Unity/Assets/Cryville.EEW.Unity/Map/LineRenderer.cs

248 lines
7.6 KiB
C#

using Cryville.EEW.Core.Map;
using Cryville.EEW.Unity.Map.Element;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using UnityEngine;
using Color = UnityEngine.Color;
namespace Cryville.EEW.Unity.Map {
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
class LineRenderer : MonoBehaviour {
Material _sharedMaterial;
public Material Material {
get => _meshRenderer.sharedMaterial;
set {
if (value == _sharedMaterial) return;
if (_sharedMaterial != null) Destroy(_meshRenderer.sharedMaterial);
_sharedMaterial = value;
if (_sharedMaterial == null) return;
_meshRenderer.sharedMaterial = Instantiate(_sharedMaterial);
_meshRenderer.sharedMaterial.color = m_color;
}
}
[SerializeField]
Color m_color = Color.white;
public Color Color {
get => m_color;
set {
if (m_color == value) return;
m_color = value;
_meshRenderer.sharedMaterial.color = value;
}
}
[SerializeField]
float m_width = 1;
public float Width {
get => m_width;
set {
if (m_width == value) return;
m_width = value;
Invalidate();
}
}
[SerializeField]
[Range(Vector2.kEpsilon, 1 - Vector2.kEpsilon)]
float m_flatCornerThreshold = 1 - Vector2.kEpsilon;
public float FlatCornerThreshold {
get => m_flatCornerThreshold;
set {
if (m_flatCornerThreshold == value) return;
m_flatCornerThreshold = Mathf.Clamp(value, Vector2.kEpsilon, 1 - Vector2.kEpsilon);
Invalidate();
}
}
[SerializeField]
[Range(Vector2.kEpsilon, 1 - Vector2.kEpsilon)]
float m_sharpCornerThreshold = 0.5f;
public float SharpCornerThreshold {
get => m_sharpCornerThreshold;
set {
if (m_sharpCornerThreshold == value) return;
m_sharpCornerThreshold = Mathf.Clamp(value, Vector2.kEpsilon, 1 - Vector2.kEpsilon);
Invalidate();
}
}
[SerializeField]
float m_tilingScale = 1;
public float TilingScale {
get => m_tilingScale;
set {
if (m_tilingScale == value) return;
m_tilingScale = value;
Invalidate();
}
}
int _positionCount;
Vector2[] _positions;
public void SetPositions(Vector2[] positions) => SetPositions(positions, 0, positions.Length);
public void SetPositions(Vector2[] positions, int index, int length) {
_positionCount = length;
if (_positions is not null)
ArrayPool<Vector2>.Shared.Return(_positions);
_positions = ArrayPool<Vector2>.Shared.Rent(length);
Array.Copy(positions, index, _positions, 0, length);
Invalidate();
}
public static LineRenderer Create(
IEnumerable<PointF> line, float z,
LineRenderer prefab, Material material, Color color, float width, MapElement parent,
out RectangleF? aabb
) {
var tileLine = line.Select(p => MapTileUtils.WorldToTilePos(p)).ToArray();
if (tileLine.Length == 0) {
aabb = null;
return null;
}
var lineRenderer = Instantiate(prefab);
lineRenderer.SetPositions(line.Select(p => MapTileUtils.WorldToTilePos(p).ToVector2()).ToArray());
lineRenderer.Material = material;
lineRenderer.Color = color;
lineRenderer.Width = width * parent.ComputedScale / 512;
lineRenderer.transform.SetParent(parent.transform, false);
lineRenderer.transform.localPosition = new(0, 0, z);
aabb = tileLine.Select(p => new RectangleF(p, SizeF.Empty)).Aggregate((a, b) => MapTileUtils.UnionTileAABBs(a, b));
return lineRenderer;
}
bool _valid;
void Invalidate() {
_valid = false;
}
Mesh _mesh;
MeshFilter _meshFilter;
MeshRenderer _meshRenderer;
void Awake() {
_meshFilter = GetComponent<MeshFilter>();
_meshRenderer = GetComponent<MeshRenderer>();
if (!_meshFilter.mesh) {
_meshFilter.mesh = new();
}
_mesh = _meshFilter.mesh;
if (_sharedMaterial == null && _meshRenderer.sharedMaterial != null) {
_sharedMaterial = _meshRenderer.sharedMaterial;
_meshRenderer.sharedMaterial = Instantiate(_sharedMaterial);
_meshRenderer.sharedMaterial.color = m_color;
}
}
void OnDestroy() {
if (_positions is not null)
ArrayPool<Vector2>.Shared.Return(_positions);
Destroy(_mesh);
Destroy(Material);
}
void LateUpdate() {
if (_valid) return;
_valid = true;
_mesh.Clear();
if (_positions == null) return;
if (_positionCount <= 1) return;
float hw = m_width / 2;
int i, vi = 0, ii = 0, li = 0, ri = 1;
float uvScale = 1 / (m_tilingScale * m_width);
Vector2 p0 = _positions[0], p1 = default;
for (i = 1; i < _positionCount; i++) {
if ((p1 = _positions[i]) != p0) break;
}
if (i >= _positionCount) return;
int maxVertexCount = 4 * (_positionCount - 1);
var vbuf = ArrayPool<Vector3>.Shared.Rent(maxVertexCount);
var ubuf = ArrayPool<Vector2>.Shared.Rent(maxVertexCount);
var ibuf = ArrayPool<int>.Shared.Rent(3 * (2 + 4 * (_positionCount - 2)));
Vector2 dp0 = NormalizeSmallVector(p1 - p0), np0 = GetNormal(dp0 * hw);
vbuf[vi] = p0 - np0; ubuf[vi++] = new(0, 0);
vbuf[vi] = p0 + np0; ubuf[vi++] = new(0, 1);
float dist = (p1 - p0).magnitude * uvScale;
p0 = p1;
for (i++; i < _positionCount; i++) {
p1 = _positions[i];
if (p1 == p0) continue;
Vector2 dp1 = NormalizeSmallVector(p1 - p0), np1 = GetNormal(dp1 * hw);
Vector2 dpm = NormalizeSmallVector(dp1 - dp0);
float cc = dp1 == dp0 ? 1 : Mathf.Abs(Vector2.Dot(dpm, NormalizeSmallVector(np0)));
if (cc > m_flatCornerThreshold) {
vbuf[vi] = p0 - np0; ubuf[vi++] = new(dist, 0);
vbuf[vi] = p0 + np0; ubuf[vi++] = new(dist, 0);
ibuf[ii++] = li; ibuf[ii++] = vi - 2; ibuf[ii++] = ri;
ibuf[ii++] = ri; ibuf[ii++] = vi - 2; ibuf[ii++] = vi - 1;
li = vi - 2; ri = vi - 1;
}
else {
if (cc < m_sharpCornerThreshold) {
cc = m_sharpCornerThreshold;
}
float chw = hw / cc;
float cl = Mathf.Sqrt(chw * chw - hw * hw);
float cluv = cl * uvScale;
Vector2 sp0 = p0 - dp0 * cl, sp1 = p0 + dp1 * cl;
bool isRight = Vector3.Cross(dp0, dp1).z < 0;
if (isRight) {
vbuf[vi] = sp0 - np0; ubuf[vi++] = new(dist - cluv, 0);
vbuf[vi] = p0 + chw * dpm; ubuf[vi++] = new(dist, 1);
}
else {
vbuf[vi] = p0 + chw * dpm; ubuf[vi++] = new(dist, 0);
vbuf[vi] = sp0 + np0; ubuf[vi++] = new(dist - cluv, 1);
}
ibuf[ii++] = li; ibuf[ii++] = vi - 2; ibuf[ii++] = ri;
ibuf[ii++] = ri; ibuf[ii++] = vi - 2; ibuf[ii++] = vi - 1;
vbuf[vi] = p0 - chw * dpm; ubuf[vi++] = new(dist, isRight ? 0 : 1);
ibuf[ii++] = vi - 3; ibuf[ii++] = vi - 1; ibuf[ii++] = vi - 2;
if (isRight) {
li = vi; ri = vi - 2;
vbuf[vi] = sp1 - np1; ubuf[vi++] = new(dist + cluv, 0);
}
else {
li = vi - 3; ri = vi;
vbuf[vi] = sp1 + np1; ubuf[vi++] = new(dist + cluv, 1);
}
ibuf[ii++] = vi - 2; ibuf[ii++] = li; ibuf[ii++] = ri;
}
dist += (p1 - p0).magnitude * uvScale;
p0 = p1; dp0 = dp1; np0 = np1;
}
vbuf[vi] = p0 - np0; ubuf[vi++] = new(dist, 0);
vbuf[vi] = p0 + np0; ubuf[vi++] = new(dist, 1);
ibuf[ii++] = li; ibuf[ii++] = vi - 2; ibuf[ii++] = ri;
ibuf[ii++] = ri; ibuf[ii++] = vi - 2; ibuf[ii++] = vi - 1;
_mesh.SetVertices(vbuf, 0, vi);
_mesh.SetUVs(0, ubuf, 0, vi);
_mesh.SetTriangles(ibuf, 0, ii, 0);
_mesh.RecalculateNormals();
_mesh.RecalculateBounds();
ArrayPool<int>.Shared.Return(ibuf);
ArrayPool<Vector2>.Shared.Return(ubuf);
ArrayPool<Vector3>.Shared.Return(vbuf);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static Vector2 NormalizeSmallVector(Vector2 np0) => np0 / np0.magnitude;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static Vector2 GetNormal(Vector2 dp) => new(dp.y, -dp.x);
}
}