using System.Collections.Generic; using System.Linq; using UnityEngine; namespace AibisDream { public class CutLine : MonoBehaviour { private LineRenderer _lineRenderer; private Vector2[] _linePoints; private float[] _normalizedLinePoints; private Bounds _bounds; private List _recordedRanges = new(); #region 初始化 public void Init(List shapePoints, float clickRadius) { _lineRenderer = GetComponent(); // 按照新点位重绘线条 RedrawLine(shapePoints); // 生成归一化点 NormalizePoints(shapePoints); // 生成基本的包围盒 GeneBoundingBox(clickRadius); ClearRecordedRanges(); } private void RedrawLine(List shapePoints) { var linePoints = shapePoints.Select(item => new Vector3(item.x, item.y, 0)).ToArray(); _lineRenderer.SetPositions(linePoints); } private void NormalizePoints(List shapePoints) { // 先生成首尾相连的点,默认Loop shapePoints.Add(shapePoints[0]); _linePoints = shapePoints.ToArray(); float totalLength = 0f; var segmentLengths = new List(); // 计算总长度和各段长度 for (var i = 1; i < _linePoints.Length; i++) { var segmentLength = Vector2.Distance(_linePoints[i - 1], _linePoints[i]); segmentLengths.Add(segmentLength); totalLength += segmentLength; } // 计算每个点的归一化位置 var normalizedLinePoints = new float[segmentLengths.Count + 1]; normalizedLinePoints[0] = 0f; var accumulatedLength = 0f; for (var i = 0; i < segmentLengths.Count; i++) { accumulatedLength += segmentLengths[i]; normalizedLinePoints[i + 1] = accumulatedLength / totalLength; } } private void GeneBoundingBox(float clickRadius) { _bounds = new Bounds(); foreach (var point in _linePoints) { _bounds.Encapsulate(point); } _bounds.Expand(clickRadius * 2); } #endregion #region 切割检测 public void DetectLineClick(Vector2 clickPosition, float clickRadius) { // 先进行包围盒检测,不在包围盒直接返回 if (!_bounds.Contains(clickPosition)) { return; } List newRanges = new List(); // 检查每条线段 for (int i = 1; i < _linePoints.Length; i++) { Vector2 start = _linePoints[i - 1]; Vector2 end = _linePoints[i]; // 计算点到线段的距离 float distance = DistanceToSegment(clickPosition, start, end); if (distance <= clickRadius) { // 计算点击影响的范围 float normalizedStart = _normalizedLinePoints[i - 1]; float normalizedEnd = _normalizedLinePoints[i]; // 计算实际影响的范围(考虑点击半径) float segmentLength = Vector2.Distance(start, end); float radiusRatio = clickRadius / segmentLength; float rangeStart = Mathf.Clamp01(normalizedStart - radiusRatio); float rangeEnd = Mathf.Clamp01(normalizedEnd + radiusRatio); newRanges.Add(new Vector2(rangeStart, rangeEnd)); } } // 合并新检测到的区间 foreach (Vector2 range in newRanges) { _recordedRanges.Add(range); } // 合并所有重叠区间 MergeRanges(); // 打印当前所有区间(调试用) Debug.Log("Current recorded ranges:"); foreach (Vector2 range in _recordedRanges) { Debug.Log($"Range: {range.x} - {range.y}"); } } private float DistanceToSegment(Vector2 point, Vector2 segmentStart, Vector2 segmentEnd) { Vector2 segment = segmentEnd - segmentStart; Vector2 pointToStart = point - segmentStart; float dotProduct = Vector2.Dot(pointToStart, segment); // 检查点是否在线段的投影范围内 if (dotProduct <= 0) { return Vector2.Distance(point, segmentStart); } float segmentLengthSquared = segment.sqrMagnitude; if (dotProduct >= segmentLengthSquared) { return Vector2.Distance(point, segmentEnd); } // 计算垂直距离 return Mathf.Abs(segmentStart.y * segmentEnd.x - segmentStart.x * segmentEnd.y + point.x * (segmentStart.y - segmentEnd.y) + point.y * (segmentEnd.x - segmentStart.x)) / Mathf.Sqrt(segmentLengthSquared); } private void MergeRanges() { if (_recordedRanges.Count <= 1) return; // 先按起始位置排序 _recordedRanges.Sort((a, b) => a.x.CompareTo(b.x)); List mergedRanges = new List(); Vector2 currentRange = _recordedRanges[0]; for (int i = 1; i < _recordedRanges.Count; i++) { Vector2 nextRange = _recordedRanges[i]; // 检查是否有重叠或相邻 if (nextRange.x <= currentRange.y) { // 合并区间 currentRange.y = Mathf.Max(currentRange.y, nextRange.y); } else { mergedRanges.Add(currentRange); currentRange = nextRange; } } mergedRanges.Add(currentRange); _recordedRanges = mergedRanges; } #endregion // 获取当前所有记录的范围(0-1之间的值) public List GetRecordedRanges() { return new List(_recordedRanges); } // 清除所有记录的范围 public void ClearRecordedRanges() { _recordedRanges.Clear(); } } }