-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathBezierCurve.cs
More file actions
151 lines (128 loc) · 4.02 KB
/
BezierCurve.cs
File metadata and controls
151 lines (128 loc) · 4.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
using System;
using System.Linq;
using MessagePack;
using Newtonsoft.Json;
using Unity.Mathematics;
using static Unity.Mathematics.math;
[MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class BezierCurve
{
[JsonProperty("keys"), Key(0)] public float4[] Keys;
[IgnoreMember] private int2 _cachedIndices;
[IgnoreMember] private const int STEPS = 64;
[IgnoreMember] private float? _maximum;
[IgnoreMember]
public float Maximum
{
get
{
if (_maximum != null) return _maximum ?? 0;
var samples = Enumerable.Range(0, STEPS).Select(i => (float) i / STEPS).ToArray();
float max = 0;
_maximum = 0;
foreach (var f in samples)
{
var p = Evaluate(f);
if (p > max)
{
max = p;
_maximum = f;
}
}
return _maximum??0;
}
}
// Integrate area under curve between start and end time
public float IntegrateCurve(float startTime, float endTime, int steps)
{
return Integrate(Evaluate, startTime, endTime, steps);
}
// Integrate function f(x) using the trapezoidal rule between x=x_low..x_high
public static float Integrate(Func<float, float> f, float x_low, float x_high, int N_steps)
{
float h = (x_high - x_low) / N_steps;
float res = (f(x_low) + f(x_high)) / 2;
for (int i = 1; i < N_steps; i++)
{
res += f(x_low + i * h);
}
return h * res;
}
// public static float Screen(float a, float b)
// {
// return 1 - (1 - a)*(1 - b);
// }
public float Evaluate(float time)
{
// Clamp time
time = clamp(time, Keys[0].x, Keys[Keys.Length - 1].x);
FindSurroundingKeyframes(time, Keys);
return HermiteInterpolate(time, Keys[_cachedIndices.x], Keys[_cachedIndices.y]);
}
void FindSurroundingKeyframes(float time, float4[] curve)
{
// Check that time is within cached keyframe time
if (time >= curve[_cachedIndices.x].x &&
time <= curve[_cachedIndices.y].x)
{
return;
}
// Fall back to using dichotomic search.
var length = curve.Length;
int half;
int middle;
int first = 0;
while (length > 0)
{
half = length >> 1;
middle = first + half;
if (time < curve[middle].x)
{
length = half;
}
else
{
first = middle + 1;
length = length - half - 1;
}
}
// If not within range, we pick the last element twice.
var indices = int2(first - 1, min(curve.Length - 1, first));
_cachedIndices = indices;
}
float HermiteInterpolate(float time, in float4 leftKeyframe, in float4 rightKeyframe)
{
// Handle stepped curve.
if (isinf(leftKeyframe.w) || isinf(rightKeyframe.z))
{
return leftKeyframe.y;
}
float dx = rightKeyframe.x - leftKeyframe.x;
float m0;
float m1;
float t;
if (dx != 0.0f)
{
t = (time - leftKeyframe.x) / dx;
m0 = leftKeyframe.w * dx;
m1 = rightKeyframe.z * dx;
}
else
{
t = 0.0f;
m0 = 0;
m1 = 0;
}
return HermiteInterpolate(t, leftKeyframe.y, m0, m1, rightKeyframe.y);
}
static float HermiteInterpolate(float t, float p0, float m0, float m1, float p1)
{
// Unrolled the equations to avoid precision issue.
// (2 * t^3 -3 * t^2 +1) * p0 + (t^3 - 2 * t^2 + t) * m0 + (-2 * t^3 + 3 * t^2) * p1 + (t^3 - t^2) * m1
var a = 2.0f * p0 + m0 - 2.0f * p1 + m1;
var b = -3.0f * p0 - 2.0f * m0 + 3.0f * p1 - m1;
var c = m0;
var d = p0;
return t * (t * (a * t + b) + c) + d;
}
}