Tanoda
BSpline.cs
Go to the documentation of this file.
1/******************************************************************************
2 * Copyright (C) Ultraleap, Inc. 2011-2020. *
3 * *
4 * Use subject to the terms of the Apache License 2.0 available at *
5 * http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
6 * between Ultraleap and you, your company or other organization. *
7 ******************************************************************************/
8
9using System.Collections.Generic;
10using System.Text;
11using UnityEngine;
12
14
15 [ExecuteInEditMode]
16 public class BSpline : MonoBehaviour {
17
18 public Transform pointsParent = null;
19
20 [Range(5, 120)]
21 public int numDivisions = 5;
22
23 [Tooltip("Must have at least one more control point than this value.")]
24 public int polyDegree = 3;
25
26 public List<float> _weightsBuffer = new List<float>();
27
28 public bool isUniform = true;
29
30 [Range(0f, 1f)]
31 public float renderFraction = 1f;
32 public bool reverseRenderOrder = false;
33
34 private List<Vector3> _pointsBuffer = new List<Vector3>();
35 private List<float> _knotVectorBuffer = new List<float>();
36
37 private List<Vector3> _outputBuffer = new List<Vector3>();
38
39 private void Update() {
40 _pointsBuffer.Clear();
41 if (pointsParent != null) {
42 foreach (var child in pointsParent.GetChildren()) {
43 _pointsBuffer.Add(child.position);
44 }
45 }
46
47 if (_weightsBuffer == null) {
48 _weightsBuffer = new List<float>();
49 }
50 while (_weightsBuffer.Count < _pointsBuffer.Count) {
51 _weightsBuffer.Add(1f);
52 }
53 while (_weightsBuffer.Count > _pointsBuffer.Count) {
54 _weightsBuffer.RemoveLast();
55 }
56 for (int i = 0; i < _weightsBuffer.Count; i++) {
57 if (_weightsBuffer[i] < 0.01f) {
58 _weightsBuffer[i] = 0.01f;
59 }
60 }
61
62 if (_pointsBuffer.Count > 1) {
63 //var minPolyDegree = 3;
64 //while (_pointsBuffer.Count < 4) {
65 // _pointsBuffer.Add(_pointsBuffer[0]);
66 //}
67 var usePolyDegree = Mathf.Min(polyDegree, _pointsBuffer.Count - 1);
68
69 _knotVectorBuffer.Clear();
70 calculateKnotVector(usePolyDegree, _pointsBuffer.Count, isUniform: true,
71 fillKnotVector: _knotVectorBuffer);
72
73 var step = 1f / numDivisions;
74 _outputBuffer.Clear();
75 for (float t = 0; t < 1f; t += step) {
76 _outputBuffer.Add(interpolateRationalBSpline(_pointsBuffer,
77 _weightsBuffer, usePolyDegree, _knotVectorBuffer, t));
78 }
79 }
80 }
81
82 protected void OnDrawGizmos() {
83 if (_pointsBuffer.Count > 1) {
84 var currFraction = 0f;
85 var fractionStep = 1f / (_outputBuffer.Count - 1);
86 var start = 0;
87 var end = _outputBuffer.Count - 1;
88 var dir = 1;
90 start = _outputBuffer.Count - 1;
91 end = 0;
92 dir = -1;
93 }
94 for (int i = start; i != end; i += dir) {
95 var curr = _outputBuffer[i];
96 var next = _outputBuffer[i + dir];
97 Drawer.UnityGizmoDrawer.Line(curr, next);
98 currFraction += fractionStep;
99 if (currFraction > renderFraction) { break; }
100 }
101 }
102 }
103
104 public void SetRenderFraction(float t) {
105 renderFraction = t;
106 }
107
108 // B-Spline code from:
109 // https://www.codeproject.com/Articles/1095142/Generate-and-understand-NURBS-curves
110
111 private void calculateKnotVector(int polyDegree, int numControlPoints,
112 bool isUniform, List<float> fillKnotVector = null)
113 {
114 if (polyDegree + 1 > numControlPoints || numControlPoints == 0) {
115 return;
116 }
117
118 StringBuilder outText = new StringBuilder();
119
120 int n = numControlPoints;
121 int m = n + polyDegree + 1;
122
123 if (isUniform) {
124 int divisor = m - 1;
125 fillKnotVector.Add(0);
126 outText.Append("0");
127 for (int i = 1; i < m; i++) {
128 if (i >= m - 1) {
129 fillKnotVector.Add(1);
130 outText.Append(", 1");
131 }
132 else {
133 fillKnotVector.Add(i / (float)divisor);
134 outText.Append(", " + i.ToString() + "/" + divisor.ToString());
135 }
136 }
137 }
138 else {
139 int divisor = m - 1 - 2 * polyDegree;
140 outText.Append("0");
141 for (int i = 0; i < m; i++) {
142 if (i < polyDegree) {
143 fillKnotVector.Add(0);
144 outText.Append(", 0");
145 }
146 else if (i >= m - polyDegree - 1) {
147 fillKnotVector.Add(1);
148 outText.Append(", 1");
149 }
150 else {
151 int numerator = i - polyDegree;
152 fillKnotVector.Add(numerator / (float)divisor);
153 outText.Append(", " + numerator.ToString() + "/" +
154 divisor.ToString());
155 }
156 }
157 }
158 }
159
160 private Vector3 interpolateBSpline(List<Vector3> controlPoints,
161 int polyDegree, List<float> knotVector, float t)
162 {
163 float x = 0, y = 0, z = 0;
164 for (int i = 0; i < controlPoints.Count; i++) {
165 var knotWeight = calcKnotWeight(i, polyDegree, knotVector, t);
166 x += controlPoints[i].x * knotWeight;
167 y += controlPoints[i].y * knotWeight;
168 z += controlPoints[i].z * knotWeight;
169 }
170 return new Vector3(x, y, z);
171 }
172
173 private Vector3 interpolateRationalBSpline(List<Vector3> controlPoints,
174 List<float> controlPointWeights, int polyDegree, List<float> knotVector,
175 float t)
176 {
177 float x = 0, y = 0, z = 0;
178 float totalWeight = 0f;
179
180 for (int i = 0; i < controlPoints.Count; i++) {
181 var knotWeight = calcKnotWeight(i, polyDegree, knotVector, t) *
182 controlPointWeights[i];
183 totalWeight += knotWeight;
184 }
185
186 for (int i = 0; i < controlPoints.Count; i++) {
187 var knotWeight = calcKnotWeight(i, polyDegree, knotVector, t);
188 x += controlPoints[i].x * controlPointWeights[i] * knotWeight /
189 totalWeight;
190 y += controlPoints[i].y * controlPointWeights[i] * knotWeight /
191 totalWeight;
192 z += controlPoints[i].z * controlPointWeights[i] * knotWeight /
193 totalWeight;
194 }
195
196 return new Vector3(x, y, z);
197 }
198
199 private float[] N = new float[128];
200 private float calcKnotWeight(int i, int polyDegree, List<float> knotVector,
201 float t)
202 {
203 //var lenN = polyDegree + 1;
204 float saved, temp;
205
206 int maxKnotVectorIndex = knotVector.Count - 1;
207 if ((i == 0 && t == knotVector[0]) ||
208 (i == (maxKnotVectorIndex - polyDegree - 1) &&
209 t == knotVector[maxKnotVectorIndex]))
210 {
211 return 1;
212 }
213 if (t < knotVector[i] || t >= knotVector[i + polyDegree + 1]) {
214 return 0;
215 }
216
217 for (int j = 0; j <= polyDegree; j++) {
218 if (t >= knotVector[i + j] && t < knotVector[i + j + 1]) {
219 N[j] = 1;
220 }
221 else {
222 N[j] = 0;
223 }
224 }
225
226 for (int k = 1; k <= polyDegree; k++) {
227 if (N[0] == 0) {
228 saved = 0;
229 }
230 else {
231 saved = ((t - knotVector[i]) * N[0]) /
232 (knotVector[i + k] - knotVector[i]);
233 }
234
235 for (int j = 0; j < polyDegree - k + 1; j++) {
236 float knotVectorLeft = knotVector[i + j + 1];
237 float knotVectorRight = knotVector[i + j + k + 1];
238
239 if (N[j + 1] == 0) {
240 N[j] = saved;
241 saved = 0;
242 }
243 else {
244 temp = N[j + 1] / (knotVectorRight - knotVectorLeft);
245 N[j] = saved + (knotVectorRight - t) * temp;
246 saved = (t - knotVectorLeft) * temp;
247 }
248 }
249 }
250 return N[0];
251 }
252
253 }
254
255}
Simple drawing interface abstraction (intended for debug drawing, not production!) with statically-ac...
Definition: Drawer.cs:19
static Drawer UnityGizmoDrawer
For use in OnDrawGizmos and OnDrawGizmosSelected.
Definition: Drawer.cs:87
void Line(Vector3 a, Vector3 b)
Definition: Drawer.cs:140
void SetRenderFraction(float t)
Definition: BSpline.cs:104
List< float > _weightsBuffer
Definition: BSpline.cs:26