增加B样条曲线脚本

main
Yurui 2025-10-23 19:20:44 +08:00
parent cf7d3b3b75
commit 83763d8168
3 changed files with 209 additions and 0 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5dcf5a38e38ac3a40b688e333002f841
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,190 @@
using UnityEngine;
using System.Collections.Generic;
using System;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace GameWrapper.BSplineCurve
{
/// <summary>
/// B样条曲线
/// </summary>
public sealed class BSplineCurve : MonoBehaviour
{
[Header("是否本地坐标")]
public bool isLocalPos;
[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();
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
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7da60780d216efa4f97b7bd37895bf08
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: