using UnityEngine; using System.Collections.Generic; using System; #if UNITY_EDITOR using UnityEditor; #endif namespace GameWrapper.Curve { /// /// B样条曲线 /// public sealed class BSplineCurve : MonoBehaviour { [Header("是否本地坐标")] public bool isLocalPos = true; [Header("点位索引")] public int Index; [Header("下一个目标点位")] public BSplineCurve NextTarget; [Header("控制点设置")] public List listControlPoint = new(); /// /// 实际点位 /// private List curvePoints = new(); [Range(2, 7)] private int degree = 2; // 曲线阶数(次数+1) [Range(0.001f, 0.1f)] private float stepSize = 0.01f; #region API /// /// 获取曲线上特定参数值的点 /// 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; } /// /// 生成B样条曲线 /// public void GenerateCurve() { curvePoints.Clear(); 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; } } 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 /// /// B样条基函数计算(De Boor递归算法) /// 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; } } /// /// 生成节点向量 /// 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 } }