线条切割
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Utility;
|
||||
using Newtonsoft.Json;
|
||||
@@ -51,7 +52,7 @@ namespace AibisDream.FixSystem
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_cutting && Input.GetMouseButtonDown(0))
|
||||
if (_cutting && Input.GetMouseButton(0))
|
||||
{
|
||||
OnCut();
|
||||
}
|
||||
@@ -114,6 +115,7 @@ namespace AibisDream.FixSystem
|
||||
{
|
||||
// 打开或获取CutLine
|
||||
ShowCutLine();
|
||||
_cutLine.OnCuttingDown += OnCuttingDown;
|
||||
_cutting = true;
|
||||
}
|
||||
|
||||
@@ -123,6 +125,13 @@ namespace AibisDream.FixSystem
|
||||
_cutLine.DetectLineClick(mousePos, clickRadius);
|
||||
}
|
||||
|
||||
private void OnCuttingDown()
|
||||
{
|
||||
_cutLine.OnCuttingDown -= OnCuttingDown;
|
||||
_cutting = false;
|
||||
// TODO 播放切割动画
|
||||
}
|
||||
|
||||
private void ShowCutLine()
|
||||
{
|
||||
// 切割线不存在,就新建一个
|
||||
@@ -132,7 +141,7 @@ namespace AibisDream.FixSystem
|
||||
var cutLineObj = Instantiate(prefab, transform);
|
||||
_cutLine = cutLineObj.GetComponent<CutLine>();
|
||||
}
|
||||
|
||||
|
||||
// 对切割线进行初始化
|
||||
var shapePoints = new List<Vector2>();
|
||||
_targetSpriteRenderer.sprite.GetPhysicsShape(0, shapePoints);
|
||||
@@ -141,8 +150,13 @@ namespace AibisDream.FixSystem
|
||||
Debug.LogWarning($"{name} has no shape");
|
||||
return;
|
||||
}
|
||||
|
||||
_cutLine.Init(shapePoints, clickRadius);
|
||||
|
||||
// 本地坐标转世界坐标
|
||||
var shapePointsV3 = shapePoints
|
||||
.Select(item => _targetSpriteRenderer.transform.TransformPoint(item))
|
||||
.ToList();
|
||||
|
||||
_cutLine.Init(shapePointsV3, clickRadius);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace AibisDream.FixSystem
|
||||
private void InitComponentRefs()
|
||||
{
|
||||
// 注册数据
|
||||
StorageSystem.Instance.RegisterData(_data);
|
||||
// StorageSystem.Instance.RegisterData(_data);
|
||||
|
||||
// 处理内部索引
|
||||
_bodyModuleGroup = transform.Find("Body Module Group")?.gameObject;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -6,42 +7,97 @@ namespace AibisDream
|
||||
{
|
||||
public class CutLine : MonoBehaviour
|
||||
{
|
||||
#region Shader参数
|
||||
|
||||
private static readonly int RangeCount = Shader.PropertyToID("_RangeCount");
|
||||
|
||||
#endregion
|
||||
|
||||
private LineRenderer _lineRenderer;
|
||||
private Material _lineMaterial;
|
||||
|
||||
private Vector2[] _linePoints;
|
||||
private float[] _normalizedLinePoints;
|
||||
private float _totalLength;
|
||||
private Bounds _bounds;
|
||||
private List<Vector2> _recordedRanges = new();
|
||||
private static readonly int Ranges = Shader.PropertyToID("_Ranges");
|
||||
private static readonly int TotalLength = Shader.PropertyToID("_TotalLength");
|
||||
|
||||
public event Action OnCuttingDown;
|
||||
|
||||
#region 初始化
|
||||
|
||||
public void Init(List<Vector2> shapePoints, float clickRadius)
|
||||
public void Init(List<Vector3> shapePoints, float clickRadius)
|
||||
{
|
||||
_lineRenderer = GetComponent<LineRenderer>();
|
||||
if (!_lineRenderer)
|
||||
{
|
||||
_lineRenderer = GetComponent<LineRenderer>();
|
||||
_lineMaterial = _lineRenderer.materials[0];
|
||||
}
|
||||
|
||||
// 按照新点位重绘线条
|
||||
RedrawLine(shapePoints);
|
||||
// 清除旧数据
|
||||
ClearOldData();
|
||||
// 生成归一化点
|
||||
NormalizePoints(shapePoints);
|
||||
// 按照新点位重绘线条
|
||||
RedrawLine(shapePoints);
|
||||
// 生成基本的包围盒
|
||||
GeneBoundingBox(clickRadius);
|
||||
|
||||
|
||||
ClearRecordedRanges();
|
||||
}
|
||||
|
||||
private void RedrawLine(List<Vector2> shapePoints)
|
||||
private void ClearOldData()
|
||||
{
|
||||
var linePoints = shapePoints.Select(item => new Vector3(item.x, item.y, 0)).ToArray();
|
||||
_lineRenderer.SetPositions(linePoints);
|
||||
_recordedRanges.Clear();
|
||||
_totalLength = 0;
|
||||
}
|
||||
|
||||
private void NormalizePoints(List<Vector2> shapePoints)
|
||||
private void RedrawLine(List<Vector3> shapePoints)
|
||||
{
|
||||
// 画线
|
||||
var linePoints = shapePoints.ToArray();
|
||||
|
||||
_lineRenderer.positionCount = linePoints.Length;
|
||||
_lineRenderer.SetPositions(linePoints);
|
||||
// 设置Shader
|
||||
_lineMaterial.SetFloat(TotalLength, _totalLength);
|
||||
_lineMaterial.SetInt(RangeCount, 1);
|
||||
_lineMaterial.SetVectorArray(Ranges, new List<Vector4> {Vector4.zero} );
|
||||
}
|
||||
|
||||
private void UpdateMaterial()
|
||||
{
|
||||
if (_recordedRanges.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 将Range组装成Shader可以读取的格式
|
||||
var ranges = new List<Vector4>();
|
||||
|
||||
for (int i = 0; i < _recordedRanges.Count; i += 2)
|
||||
{
|
||||
var v1 = _recordedRanges[i];
|
||||
var v2 = i + 1 < _recordedRanges.Count ? _recordedRanges[i + 1] : Vector2.zero;
|
||||
var combined = new Vector4(v1.x, v1.y, v2.x, v2.y);
|
||||
ranges.Add(combined);
|
||||
}
|
||||
|
||||
// 更新线条
|
||||
_lineMaterial.SetInt(RangeCount, ranges.Count);
|
||||
_lineMaterial.SetVectorArray(Ranges, ranges);
|
||||
}
|
||||
|
||||
private void NormalizePoints(List<Vector3> shapePoints)
|
||||
{
|
||||
// 先生成首尾相连的点,默认Loop
|
||||
shapePoints.Add(shapePoints[0]);
|
||||
_linePoints = shapePoints.ToArray();
|
||||
_linePoints = shapePoints
|
||||
.Select(item => new Vector2(item.x, item.y))
|
||||
.ToArray();
|
||||
|
||||
float totalLength = 0f;
|
||||
var segmentLengths = new List<float>();
|
||||
|
||||
// 计算总长度和各段长度
|
||||
@@ -49,21 +105,23 @@ namespace AibisDream
|
||||
{
|
||||
var segmentLength = Vector2.Distance(_linePoints[i - 1], _linePoints[i]);
|
||||
segmentLengths.Add(segmentLength);
|
||||
totalLength += 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;
|
||||
normalizedLinePoints[i + 1] = accumulatedLength / _totalLength;
|
||||
}
|
||||
|
||||
_normalizedLinePoints = normalizedLinePoints;
|
||||
}
|
||||
|
||||
|
||||
private void GeneBoundingBox(float clickRadius)
|
||||
{
|
||||
_bounds = new Bounds();
|
||||
@@ -71,14 +129,14 @@ namespace AibisDream
|
||||
{
|
||||
_bounds.Encapsulate(point);
|
||||
}
|
||||
|
||||
|
||||
_bounds.Expand(clickRadius * 2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 切割检测
|
||||
|
||||
|
||||
public void DetectLineClick(Vector2 clickPosition, float clickRadius)
|
||||
{
|
||||
// 先进行包围盒检测,不在包围盒直接返回
|
||||
@@ -86,7 +144,7 @@ namespace AibisDream
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
List<Vector2> newRanges = new List<Vector2>();
|
||||
|
||||
// 检查每条线段
|
||||
@@ -95,24 +153,20 @@ namespace AibisDream
|
||||
Vector2 start = _linePoints[i - 1];
|
||||
Vector2 end = _linePoints[i];
|
||||
|
||||
// 计算点到线段的距离
|
||||
float distance = DistanceToSegment(clickPosition, start, end);
|
||||
|
||||
if (distance <= clickRadius)
|
||||
// 计算点覆盖线段的范围
|
||||
if (!TryGetCoverRange(start, end, clickPosition, clickRadius, out var rawRange))
|
||||
{
|
||||
// 计算点击影响的范围
|
||||
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));
|
||||
// 如果未覆盖直接计算下一段
|
||||
continue;
|
||||
}
|
||||
|
||||
// 将线段范围转成整体范围
|
||||
var normalizedStart = _normalizedLinePoints[i - 1];
|
||||
var normalizedEnd = _normalizedLinePoints[i];
|
||||
var normalizedDistance = normalizedEnd - normalizedStart;
|
||||
|
||||
var range = rawRange * normalizedDistance + new Vector2(normalizedStart, normalizedStart);
|
||||
newRanges.Add(range);
|
||||
}
|
||||
|
||||
// 合并新检测到的区间
|
||||
@@ -123,39 +177,76 @@ namespace AibisDream
|
||||
|
||||
// 合并所有重叠区间
|
||||
MergeRanges();
|
||||
|
||||
// 判断当前切割有没有割到95%
|
||||
if (CalcTotalRange() >= 0.95)
|
||||
{
|
||||
Debug.Log("cutting All");
|
||||
OnCuttingDown?.Invoke();
|
||||
}
|
||||
|
||||
// 打印当前所有区间(调试用)
|
||||
Debug.Log("Current recorded ranges:");
|
||||
Debug.Log($"rangesCount:{_recordedRanges.Count}");
|
||||
|
||||
string log = "Ranges: ";
|
||||
foreach (Vector2 range in _recordedRanges)
|
||||
{
|
||||
Debug.Log($"Range: {range.x} - {range.y}");
|
||||
log += $"{range.x:F2}-{range.y:F2}; ";
|
||||
}
|
||||
Debug.Log(log);
|
||||
|
||||
UpdateMaterial();
|
||||
}
|
||||
|
||||
private float DistanceToSegment(Vector2 point, Vector2 segmentStart, Vector2 segmentEnd)
|
||||
private bool TryGetCoverRange(Vector2 lineStart, Vector2 lineEnd, Vector2 point, float radius,
|
||||
out Vector2 rawRange)
|
||||
{
|
||||
Vector2 segment = segmentEnd - segmentStart;
|
||||
Vector2 pointToStart = point - segmentStart;
|
||||
|
||||
float dotProduct = Vector2.Dot(pointToStart, segment);
|
||||
|
||||
// 检查点是否在线段的投影范围内
|
||||
if (dotProduct <= 0)
|
||||
// 计算线段方向向量和长度
|
||||
Vector2 lineDir = lineEnd - lineStart;
|
||||
float lineLength = lineDir.magnitude;
|
||||
|
||||
// 如果线段长度为零(两个端点重合)
|
||||
if (Mathf.Approximately(lineLength, 0f))
|
||||
{
|
||||
return Vector2.Distance(point, segmentStart);
|
||||
// 检查点是否覆盖了这个点
|
||||
var distance = Vector2.Distance(lineStart, point);
|
||||
rawRange = distance <= radius ? new Vector2(0f, 1f) : default;
|
||||
return distance <= radius;
|
||||
}
|
||||
|
||||
// 计算点到线段所在直线距离
|
||||
Vector2 lineDirNormalized = lineDir / lineLength; // 归一化方向向量
|
||||
Vector2 pointToStart = point - lineStart; // 计算点到线段起点的向量
|
||||
float projection = Vector2.Dot(pointToStart, lineDirNormalized); // 计算投影长度(点在线段上的投影位置)
|
||||
float closestT = Mathf.Clamp01(projection / lineLength); // 计算实际最近点在线段上的比例位置
|
||||
Vector2 closestPoint = lineStart + closestT * lineDir; // 计算最近点坐标
|
||||
float distanceToLine = Vector2.Distance(point, closestPoint); // 计算点到最近点的距离
|
||||
|
||||
// 如果距离大于半径,完全没有覆盖
|
||||
if (distanceToLine > radius)
|
||||
{
|
||||
rawRange = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 计算覆盖范围
|
||||
float halfCoveredLength = Mathf.Sqrt(radius * radius - distanceToLine * distanceToLine); // 计算实际覆盖的线段长度(勾股定理)
|
||||
|
||||
float startCoverage = (projection - halfCoveredLength) / lineLength;
|
||||
float endCoverage = (projection + halfCoveredLength) / lineLength; // 计算覆盖的起点和终点比例
|
||||
|
||||
startCoverage = Mathf.Clamp01(startCoverage);
|
||||
endCoverage = Mathf.Clamp01(endCoverage); // 限制在0-1范围内
|
||||
|
||||
// 如果起点和终点相同,表示只覆盖了一个点
|
||||
if (Mathf.Approximately(startCoverage, endCoverage))
|
||||
{
|
||||
rawRange = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
rawRange = new Vector2(startCoverage, endCoverage);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void MergeRanges()
|
||||
@@ -173,7 +264,7 @@ namespace AibisDream
|
||||
Vector2 nextRange = _recordedRanges[i];
|
||||
|
||||
// 检查是否有重叠或相邻
|
||||
if (nextRange.x <= currentRange.y)
|
||||
if (nextRange.x - currentRange.y <= 0.05f)
|
||||
{
|
||||
// 合并区间
|
||||
currentRange.y = Mathf.Max(currentRange.y, nextRange.y);
|
||||
@@ -188,7 +279,12 @@ namespace AibisDream
|
||||
mergedRanges.Add(currentRange);
|
||||
_recordedRanges = mergedRanges;
|
||||
}
|
||||
|
||||
|
||||
private float CalcTotalRange()
|
||||
{
|
||||
return _recordedRanges.Select(item => item.y - item.x).Sum();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// 获取当前所有记录的范围(0-1之间的值)
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace AibisDream.FixSystem
|
||||
_trigger = GetComponent<EventTriggerEx>();
|
||||
|
||||
// 处理插头高亮
|
||||
_sprite.material.SetFloat(ShineFade, GlobalVariableKit.IsPlugHighLight ? 1 : 0);
|
||||
// _sprite.material.SetFloat(ShineFade, GlobalVariableKit.IsPlugHighLight ? 1 : 0);
|
||||
}
|
||||
|
||||
private void EventRegister()
|
||||
@@ -97,7 +97,7 @@ namespace AibisDream.FixSystem
|
||||
_cableSystem.CableRef.SetEndPos(_plugRootPos);
|
||||
|
||||
_cableSystem.curSocket?.PlugOut();
|
||||
AudioManager.Instance.PlaySfx("event:/FollowInput/plugout");
|
||||
// AudioManager.Instance.PlaySfx("event:/FollowInput/plugout");
|
||||
_sprite.enabled = true;
|
||||
|
||||
if (_cableSystem.curSocket is BodyModule)
|
||||
|
||||
Reference in New Issue
Block a user