2025-10-23 19:20:44 +08:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System;
|
|
|
|
|
|
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
|
using UnityEditor;
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
2025-10-24 15:45:51 +08:00
|
|
|
|
namespace GameWrapper.Curve
|
2025-10-23 19:20:44 +08:00
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// B样条曲线
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public sealed class BSplineCurve : MonoBehaviour
|
|
|
|
|
|
{
|
|
|
|
|
|
[Header("是否本地坐标")]
|
2025-10-23 19:53:12 +08:00
|
|
|
|
public bool isLocalPos = true;
|
2025-10-23 19:20:44 +08:00
|
|
|
|
|
2025-10-24 15:45:51 +08:00
|
|
|
|
[Header("点位索引")]
|
|
|
|
|
|
public int Index;
|
|
|
|
|
|
[Header("下一个目标点位")]
|
|
|
|
|
|
public BSplineCurve NextTarget;
|
|
|
|
|
|
|
2025-10-23 19:20:44 +08:00
|
|
|
|
[Header("控制点设置")]
|
|
|
|
|
|
public List<Vector3> listControlPoint = new();
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 实际点位
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private List<Vector3> curvePoints = new();
|
|
|
|
|
|
|
|
|
|
|
|
[Range(2, 7)]
|
|
|
|
|
|
private int degree = 2; // 曲线阶数(次数+1)
|
|
|
|
|
|
[Range(0.001f, 0.1f)]
|
|
|
|
|
|
private float stepSize = 0.01f;
|
|
|
|
|
|
|
|
|
|
|
|
#region API
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取曲线上特定参数值的点
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public Vector3 GetPointOnCurve(float t)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (listControlPoint.Count <= degree || curvePoints.Count == 0)
|
|
|
|
|
|
return Vector3.zero;
|
|
|
|
|
|
|
|
|
|
|
|
int n = listControlPoint.Count - 1;
|
|
|
|
|
|
float normalizedT = Mathf.Lerp(degree, n + 1, Mathf.Clamp01(t));
|
|
|
|
|
|
|
|
|
|
|
|
Vector3 point = Vector3.zero;
|
|
|
|
|
|
float[] knots = GenerateKnotVector();
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i <= n; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
float basis = BasisFunction(i, degree, normalizedT, knots);
|
|
|
|
|
|
point += GetPos(i) * basis;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return point;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 生成B样条曲线
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void GenerateCurve()
|
|
|
|
|
|
{
|
|
|
|
|
|
curvePoints.Clear();
|
|
|
|
|
|
|
2025-10-24 15:45:51 +08:00
|
|
|
|
if (null != NextTarget)
|
|
|
|
|
|
{
|
|
|
|
|
|
Vector3 startPos = gameObject.transform.position;
|
|
|
|
|
|
Vector3 endPos = NextTarget.gameObject.transform.position;
|
|
|
|
|
|
|
|
|
|
|
|
if (isLocalPos)
|
|
|
|
|
|
{
|
|
|
|
|
|
startPos -= gameObject.transform.position;
|
|
|
|
|
|
endPos -= gameObject.transform.position;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (listControlPoint.Count <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
listControlPoint.Add(startPos);
|
|
|
|
|
|
listControlPoint.Add(endPos);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (listControlPoint.Count <= 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
listControlPoint[0] = startPos;
|
|
|
|
|
|
listControlPoint.Add(endPos);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
listControlPoint[0] = startPos;
|
|
|
|
|
|
listControlPoint[^1] = endPos;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-23 19:20:44 +08:00
|
|
|
|
if (listControlPoint.Count < 2)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
int n = listControlPoint.Count - 1; // 控制点索引最大值
|
|
|
|
|
|
int m = n + degree + 1; // 节点向量最大值
|
|
|
|
|
|
|
|
|
|
|
|
// 生成均匀节点向量
|
|
|
|
|
|
float[] knots = new float[m + 1];
|
|
|
|
|
|
for (int i = 0; i <= m; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
knots[i] = i;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
curvePoints.Add(GetPos(0));
|
|
|
|
|
|
// 计算曲线点
|
|
|
|
|
|
for (float t = degree; t <= n + 1; t += stepSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
Vector3 point = Vector3.zero;
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i <= n; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
float basis = BasisFunction(i, degree, t, knots);
|
|
|
|
|
|
point += GetPos(i) * basis;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
curvePoints.Add(point);
|
|
|
|
|
|
}
|
|
|
|
|
|
curvePoints.Add(GetPos(listControlPoint.Count - 1));
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// B样条基函数计算(De Boor递归算法)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private float BasisFunction(int i, int k, float t, float[] knots)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (0 == k)
|
|
|
|
|
|
return (t >= knots[i] && t < knots[i + 1]) ? 1f : 0f;
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
float denominator1 = knots[i + k] - knots[i];
|
|
|
|
|
|
float denominator2 = knots[i + k + 1] - knots[i + 1];
|
|
|
|
|
|
|
|
|
|
|
|
float term1 = 0f;
|
|
|
|
|
|
float term2 = 0f;
|
|
|
|
|
|
|
|
|
|
|
|
if (0 != denominator1)
|
|
|
|
|
|
term1 = (t - knots[i]) / denominator1 * BasisFunction(i, k - 1, t, knots);
|
|
|
|
|
|
|
|
|
|
|
|
if (0 != denominator2)
|
|
|
|
|
|
term2 = (knots[i + k + 1] - t) / denominator2 * BasisFunction(i + 1, k - 1, t, knots);
|
|
|
|
|
|
|
|
|
|
|
|
return term1 + term2;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 生成节点向量
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private float[] GenerateKnotVector()
|
|
|
|
|
|
{
|
|
|
|
|
|
int n = listControlPoint.Count - 1;
|
|
|
|
|
|
int m = n + degree + 1;
|
|
|
|
|
|
float[] knots = new float[m + 1];
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i <= m; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
knots[i] = i;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return knots;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Vector3 GetPos(int index)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (isLocalPos)
|
|
|
|
|
|
return listControlPoint[index] + gameObject.transform.position;
|
|
|
|
|
|
else
|
|
|
|
|
|
return listControlPoint[index];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
|
[Header("是否显示控制点")]
|
|
|
|
|
|
public bool isShowControlPoints = true;
|
|
|
|
|
|
[Header("是否显示曲线")]
|
|
|
|
|
|
public bool isShowCurve = true;
|
|
|
|
|
|
[Header("控制点颜色")]
|
|
|
|
|
|
public Color controlPointColor = new(1f, 0f, 0f, 0.8f);
|
|
|
|
|
|
[Header("曲线颜色")]
|
|
|
|
|
|
public Color curveColor = Color.blue;
|
|
|
|
|
|
|
|
|
|
|
|
void OnDrawGizmos()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Selection.activeGameObject != gameObject)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
GenerateCurve();
|
|
|
|
|
|
|
|
|
|
|
|
// 绘制曲线
|
|
|
|
|
|
if (isShowCurve && curvePoints.Count > 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
Gizmos.color = curveColor;
|
|
|
|
|
|
for (int i = 0; i < curvePoints.Count - 1; ++i)
|
|
|
|
|
|
{
|
|
|
|
|
|
Gizmos.DrawLine(curvePoints[i], curvePoints[i + 1]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 绘制控制点
|
|
|
|
|
|
if (isShowControlPoints)
|
|
|
|
|
|
{
|
|
|
|
|
|
Gizmos.color = controlPointColor;
|
|
|
|
|
|
for (int i = 0; i < listControlPoint.Count; ++i)
|
|
|
|
|
|
{
|
|
|
|
|
|
Vector3 point = GetPos(i);
|
|
|
|
|
|
Gizmos.DrawSphere(point, 0.2f);
|
|
|
|
|
|
Gizmos.DrawIcon(point, i.ToString());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 绘制控制多边形
|
|
|
|
|
|
for (int i = 0; i < listControlPoint.Count - 1; ++i)
|
|
|
|
|
|
{
|
|
|
|
|
|
Gizmos.DrawLine(GetPos(i), GetPos(i + 1));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|