248 lines
7.6 KiB
C#
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);
|
|
}
|
|
}
|