rev |
line source |
nuclear@0
|
1 #include <cmath>
|
nuclear@0
|
2 #include "curves.h"
|
nuclear@0
|
3
|
nuclear@0
|
4 Curve::Curve() {
|
nuclear@0
|
5 ArcParametrize = false;
|
nuclear@0
|
6 ease_curve = 0;
|
nuclear@0
|
7 Samples = 0;
|
nuclear@0
|
8
|
nuclear@0
|
9 SetEaseSampleCount(100);
|
nuclear@0
|
10 }
|
nuclear@0
|
11
|
nuclear@0
|
12 Curve::~Curve() {
|
nuclear@0
|
13 delete [] Samples;
|
nuclear@0
|
14
|
nuclear@0
|
15 }
|
nuclear@0
|
16
|
nuclear@0
|
17 void Curve::SetArcParametrization(bool state) {
|
nuclear@0
|
18 ArcParametrize = state;
|
nuclear@0
|
19 }
|
nuclear@0
|
20
|
nuclear@0
|
21 #define Param 0
|
nuclear@0
|
22 #define ArcLen 1
|
nuclear@0
|
23
|
nuclear@0
|
24 void Curve::SampleArcLengths() {
|
nuclear@0
|
25 const int SamplesPerSegment = 30;
|
nuclear@0
|
26 SampleCount = GetSegmentCount() * SamplesPerSegment;
|
nuclear@0
|
27
|
nuclear@0
|
28 ArcParametrize = false; // to be able to interpolate with the original values
|
nuclear@0
|
29
|
nuclear@0
|
30 Samples = new Vector2[SampleCount];
|
nuclear@0
|
31 Vector3 prevpos;
|
nuclear@0
|
32 float step = 1.0f / (float)(SampleCount-1);
|
nuclear@0
|
33 for(int i=0; i<SampleCount; i++) {
|
nuclear@0
|
34 float t = step * (float)i;
|
nuclear@0
|
35 Vector3 pos = Interpolate(t);
|
nuclear@0
|
36 Samples[i][Param] = t;
|
nuclear@0
|
37 if(!i) {
|
nuclear@0
|
38 Samples[i][ArcLen] = 0.0f;
|
nuclear@0
|
39 } else {
|
nuclear@0
|
40 Samples[i][ArcLen] = (pos - prevpos).Length() + Samples[i-1][ArcLen];
|
nuclear@0
|
41 }
|
nuclear@0
|
42 prevpos = pos;
|
nuclear@0
|
43 }
|
nuclear@0
|
44
|
nuclear@0
|
45 // normalize arc lenghts
|
nuclear@0
|
46 float maxlen = Samples[SampleCount-1][ArcLen];
|
nuclear@0
|
47 for(int i=0; i<SampleCount; i++) {
|
nuclear@0
|
48 Samples[i][ArcLen] /= maxlen;
|
nuclear@0
|
49 }
|
nuclear@0
|
50
|
nuclear@0
|
51 ArcParametrize = true;
|
nuclear@0
|
52 }
|
nuclear@0
|
53
|
nuclear@0
|
54 int BinarySearch(Vector2 *array, float key, int begin, int end) {
|
nuclear@0
|
55 int middle = begin + ((end - begin)>>1);
|
nuclear@0
|
56
|
nuclear@0
|
57 if(array[middle][ArcLen] == key) return middle;
|
nuclear@0
|
58 if(end == begin) return middle;
|
nuclear@0
|
59
|
nuclear@0
|
60 if(key < array[middle][ArcLen]) return BinarySearch(array, key, begin, middle);
|
nuclear@0
|
61 if(key > array[middle][ArcLen]) return BinarySearch(array, key, middle+1, end);
|
nuclear@0
|
62 return -1; // just to make the compiler shut the fuck up
|
nuclear@0
|
63 }
|
nuclear@0
|
64
|
nuclear@0
|
65 float Curve::Parametrize(float t) {
|
nuclear@0
|
66 if(!Samples) SampleArcLengths();
|
nuclear@0
|
67
|
nuclear@0
|
68 int samplepos = BinarySearch(Samples, t, 0, SampleCount);
|
nuclear@0
|
69 float par = Samples[samplepos][Param];
|
nuclear@0
|
70 float len = Samples[samplepos][ArcLen];
|
nuclear@0
|
71 if((len - t) < XSmallNumber) return par;
|
nuclear@0
|
72
|
nuclear@0
|
73 if(len < t) {
|
nuclear@0
|
74 if(!samplepos) return par;
|
nuclear@0
|
75 float prevlen = Samples[samplepos-1][ArcLen];
|
nuclear@0
|
76 float prevpar = Samples[samplepos-1][Param];
|
nuclear@0
|
77 float p = (t - prevlen) / (len - prevlen);
|
nuclear@0
|
78 return prevpar + (par - prevpar) * p;
|
nuclear@0
|
79 } else {
|
nuclear@0
|
80 if(samplepos >= SampleCount) return par;
|
nuclear@0
|
81 float nextlen = Samples[samplepos+1][ArcLen];
|
nuclear@0
|
82 float nextpar = Samples[samplepos+1][Param];
|
nuclear@0
|
83 float p = (t - len) / (nextlen - len);
|
nuclear@0
|
84 return par + (nextpar - par) * p;
|
nuclear@0
|
85 }
|
nuclear@0
|
86
|
nuclear@0
|
87 return par; // not gonna happen
|
nuclear@0
|
88 }
|
nuclear@0
|
89
|
nuclear@0
|
90
|
nuclear@0
|
91 #define MIN(a, b) ((a) < (b) ? (a) : (b))
|
nuclear@0
|
92 #define MAX(a, b) ((a) > (b) ? (a) : (b))
|
nuclear@0
|
93
|
nuclear@0
|
94 float Curve::Ease(float t) {
|
nuclear@0
|
95 if(!ease_curve) return t;
|
nuclear@0
|
96
|
nuclear@0
|
97 ease_curve->SetArcParametrization(true);
|
nuclear@0
|
98 float et = ease_curve->Interpolate(t).y;
|
nuclear@0
|
99
|
nuclear@0
|
100 return MIN(MAX(et, 0.0f), 1.0f);
|
nuclear@0
|
101 }
|
nuclear@0
|
102
|
nuclear@0
|
103
|
nuclear@0
|
104 void Curve::AddControlPoint(const Vector3 &cp) {
|
nuclear@0
|
105 ControlPoints.PushBack(cp);
|
nuclear@0
|
106 delete [] Samples;
|
nuclear@0
|
107 Samples = 0;
|
nuclear@0
|
108 }
|
nuclear@0
|
109
|
nuclear@0
|
110 void Curve::SetEaseCurve(Curve *curve) {
|
nuclear@0
|
111 ease_curve = curve;
|
nuclear@0
|
112 }
|
nuclear@0
|
113
|
nuclear@0
|
114 void Curve::SetEaseSampleCount(int count) {
|
nuclear@0
|
115 ease_sample_count = count;
|
nuclear@0
|
116 ease_step = 1.0f / ease_sample_count;
|
nuclear@0
|
117 }
|
nuclear@0
|
118
|
nuclear@0
|
119
|
nuclear@0
|
120 ///////////////// B-Spline implementation ////////////////////
|
nuclear@0
|
121
|
nuclear@0
|
122 int BSpline::GetSegmentCount() const {
|
nuclear@0
|
123 return ControlPoints.Size() - 3;
|
nuclear@0
|
124 }
|
nuclear@0
|
125
|
nuclear@0
|
126 Vector3 BSpline::Interpolate(float t) {
|
nuclear@0
|
127
|
nuclear@0
|
128 if(ControlPoints.Size() < 4) return Vector3(0, 0, 0);
|
nuclear@0
|
129
|
nuclear@0
|
130 if(ArcParametrize) {
|
nuclear@0
|
131 t = Ease(Parametrize(t));
|
nuclear@0
|
132 }
|
nuclear@0
|
133
|
nuclear@0
|
134 // find the appropriate segment of the spline that t lies and calculate the piecewise parameter
|
nuclear@0
|
135 t = (float)(ControlPoints.Size() - 3) * t;
|
nuclear@0
|
136 int seg = (int)t;
|
nuclear@0
|
137 t -= (float)floor(t);
|
nuclear@0
|
138 if(seg >= GetSegmentCount()) {
|
nuclear@0
|
139 seg = GetSegmentCount() - 1;
|
nuclear@0
|
140 t = 1.0f;
|
nuclear@0
|
141 }
|
nuclear@0
|
142
|
nuclear@0
|
143 ListNode<Vector3> *iter = ControlPoints.Begin();
|
nuclear@0
|
144 for(int i=0; i<seg; i++) iter = iter->next;
|
nuclear@0
|
145
|
nuclear@0
|
146 Vector3 Cp[4];
|
nuclear@0
|
147 for(int i=0; i<4; i++) {
|
nuclear@0
|
148 Cp[i] = iter->data;
|
nuclear@0
|
149 iter = iter->next;
|
nuclear@0
|
150 }
|
nuclear@0
|
151
|
nuclear@0
|
152 Matrix4x4 BSplineMat(-1, 3, -3, 1, 3, -6, 3, 0, -3, 0, 3, 0, 1, 4, 1, 0);
|
nuclear@0
|
153 BSplineMat.Transpose();
|
nuclear@0
|
154 Vector4 Params(t*t*t, t*t, t, 1);
|
nuclear@0
|
155 Vector4 CpX(Cp[0].x, Cp[1].x, Cp[2].x, Cp[3].x);
|
nuclear@0
|
156 Vector4 CpY(Cp[0].y, Cp[1].y, Cp[2].y, Cp[3].y);
|
nuclear@0
|
157 Vector4 CpZ(Cp[0].z, Cp[1].z, Cp[2].z, Cp[3].z);
|
nuclear@0
|
158
|
nuclear@0
|
159 CpX.Transform(BSplineMat);
|
nuclear@0
|
160 CpY.Transform(BSplineMat);
|
nuclear@0
|
161 CpZ.Transform(BSplineMat);
|
nuclear@0
|
162
|
nuclear@0
|
163 CpX /= 6.0f;
|
nuclear@0
|
164 CpY /= 6.0f;
|
nuclear@0
|
165 CpZ /= 6.0f;
|
nuclear@0
|
166
|
nuclear@0
|
167 Vector3 res;
|
nuclear@0
|
168
|
nuclear@0
|
169 res.x = Params.DotProduct(CpX);
|
nuclear@0
|
170 res.y = Params.DotProduct(CpY);
|
nuclear@0
|
171 res.z = Params.DotProduct(CpZ);
|
nuclear@0
|
172
|
nuclear@0
|
173 return res;
|
nuclear@0
|
174 }
|
nuclear@0
|
175
|
nuclear@0
|
176 //////////////// Catmull-Rom Spline implementation //////////////////
|
nuclear@0
|
177
|
nuclear@0
|
178 int CatmullRomSpline::GetSegmentCount() const {
|
nuclear@0
|
179 return ControlPoints.Size() - 1;
|
nuclear@0
|
180 }
|
nuclear@0
|
181
|
nuclear@0
|
182 Vector3 CatmullRomSpline::Interpolate(float t) {
|
nuclear@0
|
183
|
nuclear@0
|
184 if(ControlPoints.Size() < 2) return Vector3(0, 0, 0);
|
nuclear@0
|
185
|
nuclear@0
|
186 if(ArcParametrize) {
|
nuclear@0
|
187 t = Ease(Parametrize(t));
|
nuclear@0
|
188 }
|
nuclear@0
|
189
|
nuclear@0
|
190 // find the appropriate segment of the spline that t lies and calculate the piecewise parameter
|
nuclear@0
|
191 t = (float)(ControlPoints.Size() - 1) * t;
|
nuclear@0
|
192 int seg = (int)t;
|
nuclear@0
|
193 t -= (float)floor(t);
|
nuclear@0
|
194 if(seg >= GetSegmentCount()) {
|
nuclear@0
|
195 seg = GetSegmentCount() - 1;
|
nuclear@0
|
196 t = 1.0f;
|
nuclear@0
|
197 }
|
nuclear@0
|
198
|
nuclear@0
|
199 Vector3 Cp[4];
|
nuclear@0
|
200 ListNode<Vector3> *iter = ControlPoints.Begin();
|
nuclear@0
|
201 for(int i=0; i<seg; i++) iter = iter->next;
|
nuclear@0
|
202
|
nuclear@0
|
203 Cp[1] = iter->data;
|
nuclear@0
|
204 Cp[2] = iter->next->data;
|
nuclear@0
|
205
|
nuclear@0
|
206 if(!seg) {
|
nuclear@0
|
207 Cp[0] = Cp[1];
|
nuclear@0
|
208 } else {
|
nuclear@0
|
209 Cp[0] = iter->prev->data;
|
nuclear@0
|
210 }
|
nuclear@0
|
211
|
nuclear@0
|
212 if(seg == ControlPoints.Size() - 2) {
|
nuclear@0
|
213 Cp[3] = Cp[2];
|
nuclear@0
|
214 } else {
|
nuclear@0
|
215 Cp[3] = iter->next->next->data;
|
nuclear@0
|
216 }
|
nuclear@0
|
217
|
nuclear@0
|
218 Matrix4x4 BSplineMat(-1, 3, -3, 1, 2, -5, 4, -1, -1, 0, 1, 0, 0, 2, 0, 0);
|
nuclear@0
|
219 BSplineMat.Transpose();
|
nuclear@0
|
220 Vector4 Params(t*t*t, t*t, t, 1);
|
nuclear@0
|
221 Vector4 CpX(Cp[0].x, Cp[1].x, Cp[2].x, Cp[3].x);
|
nuclear@0
|
222 Vector4 CpY(Cp[0].y, Cp[1].y, Cp[2].y, Cp[3].y);
|
nuclear@0
|
223 Vector4 CpZ(Cp[0].z, Cp[1].z, Cp[2].z, Cp[3].z);
|
nuclear@0
|
224
|
nuclear@0
|
225 CpX.Transform(BSplineMat);
|
nuclear@0
|
226 CpY.Transform(BSplineMat);
|
nuclear@0
|
227 CpZ.Transform(BSplineMat);
|
nuclear@0
|
228
|
nuclear@0
|
229 CpX /= 2.0f;
|
nuclear@0
|
230 CpY /= 2.0f;
|
nuclear@0
|
231 CpZ /= 2.0f;
|
nuclear@0
|
232
|
nuclear@0
|
233 Vector3 res;
|
nuclear@0
|
234
|
nuclear@0
|
235 res.x = Params.DotProduct(CpX);
|
nuclear@0
|
236 res.y = Params.DotProduct(CpY);
|
nuclear@0
|
237 res.z = Params.DotProduct(CpZ);
|
nuclear@0
|
238
|
nuclear@0
|
239 return res;
|
nuclear@0
|
240 } |