BaiduFMX
336 строк · 7.1 Кб
1// ***************************************************************************
2//
3// A Firemonkey Bezier Animation Component
4//
5// Copyright 2017 л¶Ù (zhaoyipeng@hotmail.com)
6//
7// https://github.com/zhaoyipeng/FMXComponents
8//
9// ***************************************************************************
10// version history
11// 2017-12-04, v0.1.0.0 :
12// first release version
13//
14unit FMX.BezierAnimation;
15
16interface
17
18uses
19System.Classes,
20System.Rtti,
21FMX.Types,
22FMX.Utils,
23FMX.Ani,
24FMX.ComponentsCommon;
25
26type
27TBezier = class
28public const
29epsilon = 1.0E-5;
30private
31ax, bx, cx, ay, by, cy: Double;
32x1, y1, x2, y2: Double;
33public
34constructor Create(p1x, p1y, p2x, p2y: Double);
35procedure SetData(p1x, p1y, p2x, p2y: Double);
36function SampleCurveX(t: Double): Double;
37function SampleCurveY(t: Double): Double;
38function SampleCurveDerivativeX(t: Double): Double;
39function SolveCurveX(x, epsilon: Double): Double;
40function Solve(x, epsilon: Double): Double;
41class function GetLinear: TBezier;
42class function GetEase: TBezier;
43class function GetEaseIn: TBezier;
44class function GetEaseOut: TBezier;
45class function GetEaseInOut: TBezier;
46end;
47
48[ComponentPlatformsAttribute(TFMXPlatforms)]
49TFMXBezierAnimation = class(TFloatAnimation)
50private
51FBezier: TBezier;
52FP2X: Double;
53FP2Y: Double;
54FP1X: Double;
55FP1Y: Double;
56procedure SetP1X(const Value: Double);
57procedure SetP1Y(const Value: Double);
58procedure SetP2X(const Value: Double);
59procedure SetP2Y(const Value: Double);
60procedure UpdateBezier;
61protected
62procedure ProcessAnimation; override;
63public
64constructor Create(AOwner: TComponent); override;
65destructor Destroy; override;
66procedure SetData(p1x, p1y, p2x, p2y: Double);
67procedure SetBezier(bezier: TBezier);
68function BezierTime: Single;
69published
70property P1X: Double read FP1X write SetP1X;
71property P1Y: Double read FP1Y write SetP1Y;
72property P2X: Double read FP2X write SetP2X;
73property P2Y: Double read FP2Y write SetP2Y;
74end;
75
76implementation
77
78var
79Linear: TBezier = nil;
80Ease: TBezier = nil;
81EaseIn: TBezier = nil;
82EaseOut: TBezier = nil;
83EaseInOut: TBezier = nil;
84
85type
86TAnimationHelper = class helper for TAnimation
87public
88function GetDelayTime: Single;
89end;
90
91{ TBezier }
92
93constructor TBezier.Create(p1x, p1y, p2x, p2y: Double);
94begin
95SetData(p1x, p1y, p2x, p2y);
96end;
97
98class function TBezier.GetEase: TBezier;
99begin
100if not Assigned(Ease) then
101Ease := TBezier.Create(0.25,0.1,0.25,1);
102Result := Ease;
103end;
104
105class function TBezier.GetEaseIn: TBezier;
106begin
107if not Assigned(EaseInOut) then
108EaseInOut := TBezier.Create(0.42,0,1,1);
109Result := EaseIn;
110end;
111
112
113class function TBezier.GetEaseInOut: TBezier;
114begin
115if not Assigned(EaseInOut) then
116EaseInOut := TBezier.Create(0.42,0,0.58,1);
117Result := EaseInOut;
118end;
119
120
121class function TBezier.GetEaseOut: TBezier;
122begin
123if not Assigned(EaseInOut) then
124EaseInOut := TBezier.Create(0,0,0.58,1);
125Result := EaseOut;
126end;
127
128
129class function TBezier.GetLinear: TBezier;
130begin
131if not Assigned(Linear) then
132Linear := TBezier.Create(0, 0, 1, 1);
133Result := Linear;
134end;
135
136function TBezier.SampleCurveDerivativeX(t: Double): Double;
137begin
138Result := (3.0 * ax * t + 2.0 * bx) * t + cx;
139end;
140
141function TBezier.SampleCurveX(t: Double): Double;
142begin
143// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
144Result := ((ax * t + bx) * t + cx) * t;
145end;
146
147function TBezier.SampleCurveY(t: Double): Double;
148begin
149Result := ((ay * t + by) * t + cy) * t;
150end;
151
152procedure TBezier.SetData(p1x, p1y, p2x, p2y: Double);
153begin
154x1 := p1x;
155y1 := p1y;
156x2 := p2x;
157y2 := p2y;
158// Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
159cx := 3.0 * p1x;
160bx := 3.0 * (p2x - p1x) - cx;
161ax := 1.0 - cx -bx;
162
163cy := 3.0 * p1y;
164by := 3.0 * (p2y - p1y) - cy;
165ay := 1.0 - cy - by;
166end;
167
168// Given an x value, find a parametric value it came from.
169function TBezier.Solve(x, epsilon: Double): Double;
170begin
171Result := SampleCurveY(SolveCurveX(x, epsilon));
172end;
173
174function TBezier.SolveCurveX(x, epsilon: Double): Double;
175var
176t0, t1, t2, x2, d2: Double;
177i: Integer;
178begin
179// First try a few iterations of Newton's method -- normally very fast.
180t2 := x;
181for i := 0 to 7 do
182begin
183x2 := sampleCurveX(t2) - x;
184if (Abs(x2) < epsilon) then
185Exit(t2);
186d2 := SampleCurveDerivativeX(t2);
187if (Abs(d2) < 1e-6) then
188break;
189t2 := t2 - x2 / d2;
190end;
191
192// Fall back to the bisection method for reliability.
193t0 := 0.0;
194t1 := 1.0;
195t2 := x;
196
197if (t2 < t0) then
198Exit(t0);
199if (t2 > t1) then
200Exit(t1);
201
202while (t0 < t1) do
203begin
204x2 := SampleCurveX(t2);
205if (Abs(x2 - x) < epsilon) then
206Exit(t2);
207if (x > x2) then
208t0 := t2
209else
210t1 := t2;
211t2 := (t1 - t0) * 0.5 + t0;
212end;
213
214// Failure.
215Exit(t2);
216end;
217
218{ TBezierAnimation }
219
220function TFMXBezierAnimation.BezierTime: Single;
221begin
222Result := 0;
223if (Duration > 0) and (GetDelayTime <= 0) then
224begin
225Result := FBezier.Solve(InterpolateLinear(CurrentTime, 0, 1, Duration), TBezier.epsilon);
226end;
227end;
228
229constructor TFMXBezierAnimation.Create(AOwner: TComponent);
230begin
231inherited;
232FP1X := 0;
233FP1Y := 0;
234FP2X := 1;
235FP2Y := 1;
236FBezier := TBezier.Create(0,0,1,1);
237end;
238
239destructor TFMXBezierAnimation.Destroy;
240begin
241FBezier.Free;
242inherited;
243end;
244
245procedure TFMXBezierAnimation.ProcessAnimation;
246var
247T: TRttiType;
248P: TRttiProperty;
249begin
250if FInstance <> nil then
251begin
252T := SharedContext.GetType(FInstance.ClassInfo);
253if T <> nil then
254begin
255P := T.GetProperty(FPath);
256if (P <> nil) and (P.PropertyType.TypeKind = tkFloat) then
257P.SetValue(FInstance, InterpolateSingle(StartValue, StopValue, BezierTime));
258end;
259end;
260end;
261
262procedure TFMXBezierAnimation.SetBezier(bezier: TBezier);
263begin
264FP1X := bezier.x1;
265FP1Y := bezier.y1;
266FP2X := bezier.x2;
267FP2Y := bezier.y2;
268FBezier.SetData(bezier.x1, bezier.y1, bezier.x2, bezier.y2);
269end;
270
271procedure TFMXBezierAnimation.SetData(p1x, p1y, p2x, p2y: Double);
272begin
273FP1X := p1x;
274FP1Y := p1y;
275FP2X := p2x;
276FP2Y := p2y;
277FBezier.SetData(p1x, p1y, p2x, p2y);
278end;
279
280procedure TFMXBezierAnimation.SetP1X(const Value: Double);
281begin
282if FP1X <> Value then
283begin
284FP1X := Value;
285UpdateBezier;
286end;
287end;
288
289procedure TFMXBezierAnimation.SetP1Y(const Value: Double);
290begin
291if FP1Y <> Value then
292begin
293FP1Y := Value;
294UpdateBezier;
295end;
296end;
297
298procedure TFMXBezierAnimation.SetP2X(const Value: Double);
299begin
300if FP2X <> Value then
301begin
302FP2X := Value;
303UpdateBezier;
304end;
305end;
306
307procedure TFMXBezierAnimation.SetP2Y(const Value: Double);
308begin
309if FP2Y <> Value then
310begin
311FP2Y := Value;
312UpdateBezier;
313end;
314end;
315
316procedure TFMXBezierAnimation.UpdateBezier;
317begin
318FBezier.SetData(FP1X, FP1Y, FP2X, FP2Y);
319end;
320
321{ TAnimationHelper }
322
323function TAnimationHelper.GetDelayTime: Single;
324begin
325with Self do
326Result := FDelayTime;
327end;
328
329initialization
330finalization
331Linear.Free;
332Ease.Free;
333EaseIn.Free;
334EaseOut.Free;
335EaseInOut.Free;
336end.
337