2 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali/public-api/geometry/spline.h>
22 #include <dali/public-api/common/constants.h>
23 #include <dali/public-api/common/dali-common.h>
24 #include <dali/public-api/math/math-utils.h>
28 const int MAXIMUM_ITERATIONS=1000;
35 * These basis matrices arise from the cubic polynomial equations for each
36 * spline type. See Collada 1.4.1 specification for more information
38 const float Spline::BezierBasisA[] = {-1.0f, 3.0f, -3.0f, 1.0f,
39 3.0f, -6.0f, 3.0f, 0.0f,
40 -3.0f, 3.0f, 0.0f, 0.0f,
41 1.0f, 0.0f, 0.0f, 0.0f };
45 : mBasis(BezierBasisA)
53 void Spline::AddKnot(Vector3 knot)
55 mKnots.push_back(knot);
56 mInTangents.push_back(Vector3());
57 mOutTangents.push_back(Vector3());
61 void Spline::SetInTangent(size_t knotIndex, Vector3 inTangent)
63 DALI_ASSERT_ALWAYS( knotIndex < mInTangents.size() && "knotIndex out of bounds" );
64 mInTangents[knotIndex] = inTangent;
67 void Spline::SetOutTangent(size_t knotIndex, Vector3 outTangent)
69 DALI_ASSERT_ALWAYS( knotIndex < mOutTangents.size() && "knotIndex out of bounds" );
70 mOutTangents[knotIndex] = outTangent;
73 Vector3 Spline::GetKnot(size_t knotIndex)
75 DALI_ASSERT_ALWAYS( knotIndex < mKnots.size() && "knotIndex out of bounds" );
76 return mKnots[knotIndex];
79 Vector3 Spline::GetInTangent(size_t knotIndex)
81 DALI_ASSERT_ALWAYS( knotIndex < mInTangents.size() && "knotIndex out of bounds" );
82 return mInTangents[knotIndex];
85 Vector3 Spline::GetOutTangent(size_t knotIndex)
87 DALI_ASSERT_ALWAYS( knotIndex < mOutTangents.size() && "knotIndex out of bounds" );
88 return mOutTangents[knotIndex];
91 void Spline::GenerateControlPoints(float curvature)
93 size_t numKnots = mKnots.size();
94 DALI_ASSERT_ALWAYS( numKnots > 1 && "Need at least 1 segment to generate control points" ); // need at least 1 segment
96 for(size_t knotIndex = 0; knotIndex < numKnots; knotIndex++)
98 Vector3 curPoint = mKnots[knotIndex];
99 Vector3 prevPoint, nextPoint;
102 // create a dummy point
103 Vector3 nextPoint = mKnots[1];
104 prevPoint = curPoint - (nextPoint - curPoint)/8.0f;
108 prevPoint = mKnots[knotIndex-1];
111 if(knotIndex == numKnots-1)
113 // create a dummy point
114 nextPoint = curPoint - (prevPoint - curPoint)/8.0f;
118 nextPoint = mKnots[knotIndex+1];
121 Vector3 a = curPoint - prevPoint;
122 Vector3 b = nextPoint - curPoint;
123 float aLength = a.Length();
124 float bLength = b.Length();
126 Vector3 tangent = (a*bLength + b*aLength)/2.0f;
129 aLength *= curvature;
130 bLength *= curvature;
131 mInTangents[knotIndex] = curPoint - tangent*aLength;
132 mOutTangents[knotIndex] = curPoint + tangent*bLength;
136 unsigned int Spline::GetNumberOfSegments() const
138 return (mKnots.size()>1)?mKnots.size()-1:0;
142 const Vector3 Spline::GetPoint(float alpha) const
145 unsigned int numSegs = GetNumberOfSegments();
148 unsigned int segment = alpha * numSegs;
149 float segLength = 1.0f / numSegs;
150 float segStart = (float)segment * segLength;
151 float progress = (alpha - segStart) * numSegs;
152 if(segment >= numSegs)
154 segment = numSegs-1.0f;
157 current = GetPoint(segment, progress);
163 // Given a parameter s, return the point on the given spline segment.
164 // Checks that the segment index is valid (e.g. for 10 points, there
165 // are only 9 segments)
166 const Vector3 Spline::GetPoint(unsigned int segmentIndex, float s) const
168 DALI_ASSERT_ALWAYS( segmentIndex+1 < mKnots.size() && segmentIndex < mKnots.size() && "segmentIndex out of bounds" );
169 DALI_ASSERT_ALWAYS( mOutTangents.size() == mKnots.size() && "Spline not fully initialized" );
170 DALI_ASSERT_ALWAYS( mInTangents.size() == mKnots.size() && "Spline not fully initialized" );
174 if(s < 0.0f || s > 1.0f)
180 else if(s < Math::MACHINE_EPSILON_1)
182 bPoint = mKnots[segmentIndex];
184 else if( (1.0 - s) < Math::MACHINE_EPSILON_1)
186 bPoint = mKnots[segmentIndex+1];
190 Vector4 sVect(s*s*s, s*s, s, 1);
193 cVect.x = mKnots[segmentIndex].x;
194 cVect.y = mOutTangents[segmentIndex].x;
195 cVect.z = mInTangents[segmentIndex+1].x;
196 cVect.w = mKnots[segmentIndex+1].x;
197 bPoint.x = sVect.Dot4(mBasis * cVect);
199 cVect.x = mKnots[segmentIndex].y;
200 cVect.y = mOutTangents[segmentIndex].y;
201 cVect.z = mInTangents[segmentIndex+1].y;
202 cVect.w = mKnots[segmentIndex+1].y;
203 bPoint.y = sVect.Dot4(mBasis * cVect);
205 cVect.x = mKnots[segmentIndex].z;
206 cVect.y = mOutTangents[segmentIndex].z;
207 cVect.z = mInTangents[segmentIndex+1].z;
208 cVect.w = mKnots[segmentIndex+1].z;
209 bPoint.z = sVect.Dot4(mBasis * cVect);
215 // Given a parameter s (NOT x), return the Y value. Checks that the
216 // segment index is valid. For bezier splines, the last segment is
217 // only used to specify the end point, so is not valid.
218 const float Spline::GetY(unsigned int segmentIndex, float s) const
220 DALI_ASSERT_ALWAYS( segmentIndex+1 < mKnots.size() && segmentIndex < mKnots.size() && "segmentIndex out of bounds");
221 DALI_ASSERT_ALWAYS( mOutTangents.size() == mKnots.size() && "Spline not fully initialized" );
222 DALI_ASSERT_ALWAYS( mInTangents.size() == mKnots.size() && "Spline not fully initialized" );
226 if(s < 0.0f || s > 1.0f)
230 else if(s < Math::MACHINE_EPSILON_1)
232 yValue = mKnots[segmentIndex].y;
234 else if( (1.0 - s) < Math::MACHINE_EPSILON_1)
236 yValue = mKnots[segmentIndex+1].y;
240 Vector4 sVect(s*s*s, s*s, s, 1);
243 cVect.x = mKnots[segmentIndex].y;
244 cVect.y = mOutTangents[segmentIndex].y;
245 cVect.z = mInTangents[segmentIndex+1].y;
246 cVect.w = mKnots[segmentIndex+1].y;
247 yValue = sVect.Dot4(mBasis * cVect);
252 float ClampToZeroOne(float v)
254 return (v<0.0f)?(0.0f):((v>1.0f)?1.0f:v);
257 // Use de Casteljau subdivision to approximate the parameter required to find x
258 // on a Bezier spline
259 // note, atX is already determined to be >= P0_X, <P1_X
260 const float Spline::ApproximateCubicBezierParameter (
261 float atX, float P0_X, float C0_X, float C1_X, float P1_X ) const
263 if (fabs(atX - P0_X) < GetRangedEpsilon(atX, P0_X) )
268 if (fabs(P1_X - atX) < GetRangedEpsilon(atX, P1_X) )
273 int iterationStep = 0;
275 float u = 0.0f; float v = 1.0f;
277 // iteratively apply subdivision to approach value atX
278 while (iterationStep < MAXIMUM_ITERATIONS)
280 float a = (P0_X + C0_X)*0.5f;
281 float b = (C0_X + C1_X)*0.5f;
282 float c = (C1_X + P1_X)*0.5f;
283 float d = (a + b)*0.5f;
284 float e = (b + c)*0.5f;
285 float f = (d + e)*0.5f; // must be on curve - a Bezier spline is 2nd order diff continuous
287 // The curve point is close enough to required value.
288 if (fabsf(f - atX) < GetRangedEpsilon(f, atX) )
311 return ClampToZeroOne((u + v)*0.5f);
315 const bool Spline::FindSegment(float x, /*out*/ int& segmentIndex) const
318 unsigned int index=0;
319 unsigned int prevIndex=0;
321 while(index < mKnots.size() && mKnots[index].x <= x)
327 if(index == mKnots.size())
333 if( (mKnots[prevIndex].x <= x) && ( x < mKnots[index].x ) )
336 segmentIndex = prevIndex;
341 const float Spline::GetYFromMonotonicX(float x) const
346 if(FindSegment(x, segmentIndex))
348 float s = ApproximateCubicBezierParameter (x, mKnots[segmentIndex].x,
349 mOutTangents[segmentIndex].x,
350 mInTangents[segmentIndex+1].x,
351 mKnots[segmentIndex+1].x);
352 yValue = GetY(segmentIndex, s);
356 if(mKnots.size() > 0)
358 Vector3 lastPoint = mKnots[mKnots.size()-1];
359 if(fabsf(lastPoint.x - x) < GetRangedEpsilon(lastPoint.x, x))
361 yValue = lastPoint.y;