react-native-smooth-clip-view 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -388
- package/android/build.gradle +3 -3
- package/android/proguard-rules.pro +2 -2
- package/android/src/main/cpp/SmoothClipAndroid.h +9 -12
- package/android/src/main/cpp/SmoothClipBindings.cpp +300 -704
- package/android/src/main/cpp/SmoothClipRegistry.cpp +348 -594
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +36 -227
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +16 -19
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +36 -294
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +344 -488
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +75 -77
- package/cpp/SmoothClipAnimationCurve.h +237 -346
- package/cpp/SmoothClipRegistry.h +24 -52
- package/cpp/SmoothClipSharedGeometry.h +64 -94
- package/cpp/SmoothClipVelocityTracker.h +5 -5
- package/ios/SmoothClipGeometry.h +23 -34
- package/ios/SmoothClipRegistry.mm +206 -711
- package/ios/SmoothClipTurboModule.cpp +217 -715
- package/ios/SmoothClipTurboModule.h +54 -297
- package/ios/SmoothClipView.mm +599 -505
- package/ios/SmoothClipViewRegistry.h +2 -7
- package/lib/module/NativeSmoothClipModule.js.map +1 -1
- package/lib/module/SmoothClipView.ios.js +84 -17
- package/lib/module/SmoothClipView.ios.js.map +1 -1
- package/lib/module/SmoothClipView.js +1 -1
- package/lib/module/SmoothClipView.js.map +1 -1
- package/lib/module/SmoothClipViewNativeComponent.ts +20 -26
- package/lib/module/capabilities.native.js +1 -19
- package/lib/module/capabilities.native.js.map +1 -1
- package/lib/module/capabilityTypes.js +0 -9
- package/lib/module/capabilityTypes.js.map +1 -1
- package/lib/module/controllerInternals.js +28 -0
- package/lib/module/controllerInternals.js.map +1 -0
- package/lib/module/controllerLifecycle.js +9 -0
- package/lib/module/controllerLifecycle.js.map +1 -0
- package/lib/module/controllerTypes.js +4 -0
- package/lib/module/controllerTypes.js.map +1 -0
- package/lib/module/controllers.android.js +4 -0
- package/lib/module/controllers.android.js.map +1 -0
- package/lib/module/controllers.ios.js +4 -0
- package/lib/module/controllers.ios.js.map +1 -0
- package/lib/module/controllers.js +4 -0
- package/lib/module/controllers.js.map +1 -0
- package/lib/module/controllers.native.js +89 -0
- package/lib/module/controllers.native.js.map +1 -0
- package/lib/module/geometry.js +71 -51
- package/lib/module/geometry.js.map +1 -1
- package/lib/module/groupTypes.js +4 -0
- package/lib/module/{groupDriverTypes.js.map → groupTypes.js.map} +1 -1
- package/lib/module/groups.android.js +4 -0
- package/lib/module/groups.android.js.map +1 -0
- package/lib/module/groups.ios.js +4 -0
- package/lib/module/groups.ios.js.map +1 -0
- package/lib/module/groups.js +4 -0
- package/lib/module/groups.js.map +1 -0
- package/lib/module/groups.native.js +399 -0
- package/lib/module/groups.native.js.map +1 -0
- package/lib/module/ids.js +8 -0
- package/lib/module/ids.js.map +1 -0
- package/lib/module/index.js +3 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/nativeCompletion.js +37 -16
- package/lib/module/nativeCompletion.js.map +1 -1
- package/lib/module/presentationCodec.js +81 -0
- package/lib/module/presentationCodec.js.map +1 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts +14 -23
- package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.d.ts +1 -1
- package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.ios.d.ts +18 -14
- package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +10 -5
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/capabilities.native.d.ts +1 -1
- package/lib/typescript/src/capabilities.native.d.ts.map +1 -1
- package/lib/typescript/src/capabilityTypes.d.ts +0 -6
- package/lib/typescript/src/capabilityTypes.d.ts.map +1 -1
- package/lib/typescript/src/controllerInternals.d.ts +14 -0
- package/lib/typescript/src/controllerInternals.d.ts.map +1 -0
- package/lib/typescript/src/controllerLifecycle.d.ts +3 -0
- package/lib/typescript/src/controllerLifecycle.d.ts.map +1 -0
- package/lib/typescript/src/controllerTypes.d.ts +62 -0
- package/lib/typescript/src/controllerTypes.d.ts.map +1 -0
- package/lib/typescript/src/controllers.android.d.ts +2 -0
- package/lib/typescript/src/controllers.android.d.ts.map +1 -0
- package/lib/typescript/src/controllers.d.ts +2 -0
- package/lib/typescript/src/controllers.d.ts.map +1 -0
- package/lib/typescript/src/controllers.ios.d.ts +2 -0
- package/lib/typescript/src/controllers.ios.d.ts.map +1 -0
- package/lib/typescript/src/controllers.native.d.ts +3 -0
- package/lib/typescript/src/controllers.native.d.ts.map +1 -0
- package/lib/typescript/src/geometry.d.ts +22 -12
- package/lib/typescript/src/geometry.d.ts.map +1 -1
- package/lib/typescript/src/groupTypes.d.ts +31 -0
- package/lib/typescript/src/groupTypes.d.ts.map +1 -0
- package/lib/typescript/src/groups.android.d.ts +2 -0
- package/lib/typescript/src/groups.android.d.ts.map +1 -0
- package/lib/typescript/src/groups.d.ts +2 -0
- package/lib/typescript/src/groups.d.ts.map +1 -0
- package/lib/typescript/src/groups.ios.d.ts +2 -0
- package/lib/typescript/src/groups.ios.d.ts.map +1 -0
- package/lib/typescript/src/groups.native.d.ts +3 -0
- package/lib/typescript/src/groups.native.d.ts.map +1 -0
- package/lib/typescript/src/ids.d.ts +2 -0
- package/lib/typescript/src/ids.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +6 -6
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/nativeCompletion.d.ts +16 -6
- package/lib/typescript/src/nativeCompletion.d.ts.map +1 -1
- package/lib/typescript/src/presentationCodec.d.ts +10 -0
- package/lib/typescript/src/presentationCodec.d.ts.map +1 -0
- package/package.json +8 -3
- package/src/NativeSmoothClipModule.ts +41 -292
- package/src/SmoothClipView.ios.tsx +130 -23
- package/src/SmoothClipView.tsx +5 -1
- package/src/SmoothClipViewNativeComponent.ts +20 -26
- package/src/capabilities.native.ts +2 -52
- package/src/capabilityTypes.ts +0 -14
- package/src/controllerInternals.ts +52 -0
- package/src/controllerLifecycle.ts +6 -0
- package/src/controllerTypes.ts +87 -0
- package/src/controllers.android.ts +1 -0
- package/src/controllers.ios.ts +1 -0
- package/src/controllers.native.ts +104 -0
- package/src/controllers.ts +1 -0
- package/src/geometry.ts +118 -74
- package/src/groupTypes.ts +53 -0
- package/src/groups.android.ts +1 -0
- package/src/groups.ios.ts +1 -0
- package/src/groups.native.ts +604 -0
- package/src/groups.ts +1 -0
- package/src/ids.ts +6 -0
- package/src/index.ts +20 -28
- package/src/nativeCompletion.ts +67 -32
- package/src/presentationCodec.ts +134 -0
- package/lib/module/driverState.js +0 -82
- package/lib/module/driverState.js.map +0 -1
- package/lib/module/driverTypes.js +0 -4
- package/lib/module/driverTypes.js.map +0 -1
- package/lib/module/drivers.android.js +0 -6
- package/lib/module/drivers.android.js.map +0 -1
- package/lib/module/drivers.ios.js +0 -6
- package/lib/module/drivers.ios.js.map +0 -1
- package/lib/module/drivers.js +0 -4
- package/lib/module/drivers.js.map +0 -1
- package/lib/module/drivers.native.js +0 -756
- package/lib/module/drivers.native.js.map +0 -1
- package/lib/module/groupDriverTypes.js +0 -4
- package/lib/module/groupDrivers.android.js +0 -4
- package/lib/module/groupDrivers.android.js.map +0 -1
- package/lib/module/groupDrivers.ios.js +0 -4
- package/lib/module/groupDrivers.ios.js.map +0 -1
- package/lib/module/groupDrivers.js +0 -4
- package/lib/module/groupDrivers.js.map +0 -1
- package/lib/module/groupDrivers.native.js +0 -799
- package/lib/module/groupDrivers.native.js.map +0 -1
- package/lib/module/presentationProtocol.js +0 -15
- package/lib/module/presentationProtocol.js.map +0 -1
- package/lib/module/reactRequests.js +0 -56
- package/lib/module/reactRequests.js.map +0 -1
- package/lib/typescript/src/driverState.d.ts +0 -26
- package/lib/typescript/src/driverState.d.ts.map +0 -1
- package/lib/typescript/src/driverTypes.d.ts +0 -121
- package/lib/typescript/src/driverTypes.d.ts.map +0 -1
- package/lib/typescript/src/drivers.android.d.ts +0 -3
- package/lib/typescript/src/drivers.android.d.ts.map +0 -1
- package/lib/typescript/src/drivers.d.ts +0 -3
- package/lib/typescript/src/drivers.d.ts.map +0 -1
- package/lib/typescript/src/drivers.ios.d.ts +0 -3
- package/lib/typescript/src/drivers.ios.d.ts.map +0 -1
- package/lib/typescript/src/drivers.native.d.ts +0 -5
- package/lib/typescript/src/drivers.native.d.ts.map +0 -1
- package/lib/typescript/src/groupDriverTypes.d.ts +0 -66
- package/lib/typescript/src/groupDriverTypes.d.ts.map +0 -1
- package/lib/typescript/src/groupDrivers.android.d.ts +0 -2
- package/lib/typescript/src/groupDrivers.android.d.ts.map +0 -1
- package/lib/typescript/src/groupDrivers.d.ts +0 -2
- package/lib/typescript/src/groupDrivers.d.ts.map +0 -1
- package/lib/typescript/src/groupDrivers.ios.d.ts +0 -2
- package/lib/typescript/src/groupDrivers.ios.d.ts.map +0 -1
- package/lib/typescript/src/groupDrivers.native.d.ts +0 -4
- package/lib/typescript/src/groupDrivers.native.d.ts.map +0 -1
- package/lib/typescript/src/presentationProtocol.d.ts +0 -4
- package/lib/typescript/src/presentationProtocol.d.ts.map +0 -1
- package/lib/typescript/src/reactRequests.d.ts +0 -9
- package/lib/typescript/src/reactRequests.d.ts.map +0 -1
- package/src/driverState.ts +0 -118
- package/src/driverTypes.ts +0 -171
- package/src/drivers.android.ts +0 -8
- package/src/drivers.ios.ts +0 -8
- package/src/drivers.native.ts +0 -1599
- package/src/drivers.ts +0 -6
- package/src/groupDriverTypes.ts +0 -117
- package/src/groupDrivers.android.ts +0 -1
- package/src/groupDrivers.ios.ts +0 -1
- package/src/groupDrivers.native.ts +0 -1189
- package/src/groupDrivers.ts +0 -1
- package/src/presentationProtocol.ts +0 -28
- package/src/reactRequests.ts +0 -76
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
#include "SmoothClipRegistry.h"
|
|
4
|
+
#include "SmoothClipSharedGeometry.h"
|
|
4
5
|
|
|
5
6
|
#include <algorithm>
|
|
6
7
|
#include <array>
|
|
@@ -9,9 +10,8 @@
|
|
|
9
10
|
#include <utility>
|
|
10
11
|
#include <vector>
|
|
11
12
|
|
|
12
|
-
// Shared animation math for the Android integrator
|
|
13
|
-
//
|
|
14
|
-
// continuation, presentation blending and keyframes. Keeping the pure math out
|
|
13
|
+
// Shared animation math for the Android integrator: frame-clock anchoring,
|
|
14
|
+
// timing curves, spring validation, and presentation blending. Keeping the pure math out
|
|
15
15
|
// of the registry translation units also lets the XCTest binary pin every rule
|
|
16
16
|
// below — see ios/tests/SmoothClipAnimationCurveTests.mm.
|
|
17
17
|
namespace smoothclip {
|
|
@@ -50,7 +50,7 @@ inline double clamp01(double value) {
|
|
|
50
50
|
// parallel Reanimated animation by the callback's intra-frame offset for the
|
|
51
51
|
// whole duration. Starts that carry the JS-captured stamp resolve through
|
|
52
52
|
// resolveStartStamp() below and skip this anchor entirely; min() remains for
|
|
53
|
-
//
|
|
53
|
+
// pre-ready starts and unstamped callers, where its two branches are exact.
|
|
54
54
|
inline double anchorStartTime(double startedAtS, double frameNowS) {
|
|
55
55
|
return std::min(startedAtS, frameNowS);
|
|
56
56
|
}
|
|
@@ -81,7 +81,7 @@ inline StartStamp resolveStartStamp(double jsStampS, double wallNowS) {
|
|
|
81
81
|
return {wallNowS, false};
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
// Elapsed fraction of a timing
|
|
84
|
+
// Elapsed fraction of a timing animation. A non-positive duration is
|
|
85
85
|
// complete on its first frame rather than dividing by zero.
|
|
86
86
|
inline double timingFraction(
|
|
87
87
|
double nowS,
|
|
@@ -91,20 +91,7 @@ inline double timingFraction(
|
|
|
91
91
|
return clamp01((nowS - startedAtS) / durationS);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
// --- Timing curve
|
|
95
|
-
|
|
96
|
-
struct CubicBezierPoint {
|
|
97
|
-
double x;
|
|
98
|
-
double y;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
inline CubicBezierPoint mixBezierPoint(
|
|
102
|
-
CubicBezierPoint from,
|
|
103
|
-
CubicBezierPoint to,
|
|
104
|
-
double progress) {
|
|
105
|
-
return {from.x + (to.x - from.x) * progress,
|
|
106
|
-
from.y + (to.y - from.y) * progress};
|
|
107
|
-
}
|
|
94
|
+
// --- Timing curve --------------------------------------------------------
|
|
108
95
|
|
|
109
96
|
inline double cubicBezierParameterForX(
|
|
110
97
|
double x1,
|
|
@@ -141,157 +128,12 @@ inline double cubicBezier(
|
|
|
141
128
|
3 * inverse * t * t * y2 + t * t * t;
|
|
142
129
|
}
|
|
143
130
|
|
|
144
|
-
struct TimingRemainder {
|
|
145
|
-
TimingAnimation animation;
|
|
146
|
-
double easedProgress;
|
|
147
|
-
bool representable;
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
struct TimingContinuation {
|
|
151
|
-
Presentation start;
|
|
152
|
-
TimingAnimation animation;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
// Exact right-hand segment of a CSS/CoreAnimation cubic Bezier after a raw
|
|
156
|
-
// time cutoff. Restarting the original controls from a frozen presentation
|
|
157
|
-
// resets its slope and creates a visible velocity cusp. De Casteljau splitting
|
|
158
|
-
// preserves the original curve; normalizing the right segment to (0,0)->(1,1)
|
|
159
|
-
// makes it directly reusable with a shorter BasicAnimation/integrator run.
|
|
160
|
-
inline TimingRemainder timingRemainder(
|
|
161
|
-
const TimingAnimation &original,
|
|
162
|
-
double rawProgress) {
|
|
163
|
-
const double progress = clamp01(rawProgress);
|
|
164
|
-
if (progress <= 0) return {original, 0, true};
|
|
165
|
-
if (progress >= 1) {
|
|
166
|
-
TimingAnimation complete = original;
|
|
167
|
-
complete.durationMs = 0;
|
|
168
|
-
complete.controlPoint1X = 0;
|
|
169
|
-
complete.controlPoint1Y = 0;
|
|
170
|
-
complete.controlPoint2X = 1;
|
|
171
|
-
complete.controlPoint2Y = 1;
|
|
172
|
-
return {complete, 1, true};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const double t = cubicBezierParameterForX(
|
|
176
|
-
original.controlPoint1X, original.controlPoint2X, progress, 30);
|
|
177
|
-
const CubicBezierPoint p0{0, 0};
|
|
178
|
-
const CubicBezierPoint p1{
|
|
179
|
-
original.controlPoint1X, original.controlPoint1Y};
|
|
180
|
-
const CubicBezierPoint p2{
|
|
181
|
-
original.controlPoint2X, original.controlPoint2Y};
|
|
182
|
-
const CubicBezierPoint p3{1, 1};
|
|
183
|
-
const CubicBezierPoint a = mixBezierPoint(p0, p1, t);
|
|
184
|
-
const CubicBezierPoint b = mixBezierPoint(p1, p2, t);
|
|
185
|
-
const CubicBezierPoint c = mixBezierPoint(p2, p3, t);
|
|
186
|
-
const CubicBezierPoint d = mixBezierPoint(a, b, t);
|
|
187
|
-
const CubicBezierPoint e = mixBezierPoint(b, c, t);
|
|
188
|
-
const CubicBezierPoint split = mixBezierPoint(d, e, t);
|
|
189
|
-
|
|
190
|
-
TimingAnimation remainder = original;
|
|
191
|
-
remainder.durationMs = original.durationMs * (1 - progress);
|
|
192
|
-
const double remainingX = 1 - split.x;
|
|
193
|
-
const double remainingY = 1 - split.y;
|
|
194
|
-
// A curve whose y reaches exactly 1 before its time endpoint and later
|
|
195
|
-
// departs cannot be represented as one endpoint-normalized BasicAnimation.
|
|
196
|
-
// Keep the result finite; ordinary monotone easings never take this branch.
|
|
197
|
-
if (std::abs(remainingX) <= 1e-12 || std::abs(remainingY) <= 1e-12) {
|
|
198
|
-
remainder.controlPoint1X = 0;
|
|
199
|
-
remainder.controlPoint1Y = 0;
|
|
200
|
-
remainder.controlPoint2X = 1;
|
|
201
|
-
remainder.controlPoint2Y = 1;
|
|
202
|
-
return {remainder, split.y, false};
|
|
203
|
-
}
|
|
204
|
-
remainder.controlPoint1X = (e.x - split.x) / remainingX;
|
|
205
|
-
remainder.controlPoint1Y = (e.y - split.y) / remainingY;
|
|
206
|
-
remainder.controlPoint2X = (c.x - split.x) / remainingX;
|
|
207
|
-
remainder.controlPoint2Y = (c.y - split.y) / remainingY;
|
|
208
|
-
return {remainder, split.y, true};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// --- Spring continuation --------------------------------------------------
|
|
212
|
-
|
|
213
|
-
struct NormalizedSpringState {
|
|
214
|
-
// Remaining fraction of the original from->target displacement.
|
|
215
|
-
double remaining;
|
|
216
|
-
// Derivative of `remaining` in units per second.
|
|
217
|
-
double velocity;
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
// Closed-form state of the same mass/spring/damper equation CoreAnimation
|
|
221
|
-
// evaluates. If p is animation progress, remaining=1-p starts at 1 and its
|
|
222
|
-
// derivative starts at -initialVelocity.
|
|
223
|
-
inline NormalizedSpringState normalizedSpringState(
|
|
224
|
-
const SpringAnimation &spring,
|
|
225
|
-
double elapsedS) {
|
|
226
|
-
if (!std::isfinite(elapsedS) || elapsedS <= 0) {
|
|
227
|
-
return {1, -spring.initialVelocity};
|
|
228
|
-
}
|
|
229
|
-
const double mass = spring.mass;
|
|
230
|
-
const double stiffness = spring.stiffness;
|
|
231
|
-
const double damping = spring.damping;
|
|
232
|
-
const double initialVelocity = spring.initialVelocity;
|
|
233
|
-
if (!std::isfinite(mass) || !std::isfinite(stiffness) ||
|
|
234
|
-
!std::isfinite(damping) || !std::isfinite(initialVelocity) ||
|
|
235
|
-
mass <= 0 || stiffness <= 0 || damping < 0) {
|
|
236
|
-
return {1, 0};
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const double alpha = damping / (2 * mass);
|
|
240
|
-
const double omegaSquared = stiffness / mass;
|
|
241
|
-
const double discriminant = alpha * alpha - omegaSquared;
|
|
242
|
-
const double criticalScale = std::max(1.0, omegaSquared);
|
|
243
|
-
if (std::abs(discriminant) <= 1e-12 * criticalScale) {
|
|
244
|
-
const double coefficient = alpha - initialVelocity;
|
|
245
|
-
const double decay = std::exp(-alpha * elapsedS);
|
|
246
|
-
const double remaining = decay * (1 + coefficient * elapsedS);
|
|
247
|
-
const double velocity = decay *
|
|
248
|
-
(coefficient - alpha * (1 + coefficient * elapsedS));
|
|
249
|
-
return {remaining, velocity};
|
|
250
|
-
}
|
|
251
|
-
if (discriminant < 0) {
|
|
252
|
-
const double omega = std::sqrt(-discriminant);
|
|
253
|
-
const double coefficient = (alpha - initialVelocity) / omega;
|
|
254
|
-
const double angle = omega * elapsedS;
|
|
255
|
-
const double cosine = std::cos(angle);
|
|
256
|
-
const double sine = std::sin(angle);
|
|
257
|
-
const double basis = cosine + coefficient * sine;
|
|
258
|
-
const double basisVelocity =
|
|
259
|
-
-omega * sine + coefficient * omega * cosine;
|
|
260
|
-
const double decay = std::exp(-alpha * elapsedS);
|
|
261
|
-
return {decay * basis, decay * (basisVelocity - alpha * basis)};
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const double root = std::sqrt(discriminant);
|
|
265
|
-
const double firstRate = -alpha + root;
|
|
266
|
-
const double secondRate = -alpha - root;
|
|
267
|
-
const double firstCoefficient =
|
|
268
|
-
(-initialVelocity - secondRate) / (firstRate - secondRate);
|
|
269
|
-
const double secondCoefficient = 1 - firstCoefficient;
|
|
270
|
-
const double first = firstCoefficient * std::exp(firstRate * elapsedS);
|
|
271
|
-
const double second = secondCoefficient * std::exp(secondRate * elapsedS);
|
|
272
|
-
return {first + second, firstRate * first + secondRate * second};
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// CASpringAnimation.initialVelocity is normalized by the current remaining
|
|
276
|
-
// distance. Re-expressing the old physical velocity against the frozen
|
|
277
|
-
// current->target displacement preserves the derivative at a re-latch seam.
|
|
278
|
-
inline double springContinuationVelocity(
|
|
279
|
-
const SpringAnimation &spring,
|
|
280
|
-
double elapsedS) {
|
|
281
|
-
const NormalizedSpringState state = normalizedSpringState(spring, elapsedS);
|
|
282
|
-
if (!std::isfinite(state.remaining) || !std::isfinite(state.velocity) ||
|
|
283
|
-
std::abs(state.remaining) <= 1e-9) {
|
|
284
|
-
return 0;
|
|
285
|
-
}
|
|
286
|
-
const double result = -state.velocity / state.remaining;
|
|
287
|
-
return std::isfinite(result) ? result : 0;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
131
|
// --- Presentation channels ------------------------------------------------
|
|
291
132
|
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
constexpr std::size_t
|
|
133
|
+
// The first eleven channels are geometry/content; the final eight are shadow.
|
|
134
|
+
// Curve and shadow presence are categorical and stay outside the scalar array.
|
|
135
|
+
constexpr std::size_t kBaseChannelCount = 11;
|
|
136
|
+
constexpr std::size_t kChannelCount = 19;
|
|
295
137
|
using Channels = std::array<double, kChannelCount>;
|
|
296
138
|
|
|
297
139
|
inline double resolvedRadius(double overrideValue, double shorthand) {
|
|
@@ -326,42 +168,104 @@ inline Channels toChannels(const Presentation &presentation) {
|
|
|
326
168
|
bottomLeft,
|
|
327
169
|
presentation.contentTranslateX,
|
|
328
170
|
presentation.contentTranslateY,
|
|
329
|
-
presentation.contentScale
|
|
171
|
+
presentation.contentScale,
|
|
172
|
+
presentation.shadow.red,
|
|
173
|
+
presentation.shadow.green,
|
|
174
|
+
presentation.shadow.blue,
|
|
175
|
+
presentation.shadow.alpha,
|
|
176
|
+
presentation.shadow.offsetX,
|
|
177
|
+
presentation.shadow.offsetY,
|
|
178
|
+
presentation.shadow.blurRadius,
|
|
179
|
+
presentation.shadow.spreadDistance};
|
|
330
180
|
}
|
|
331
181
|
|
|
332
|
-
// ---
|
|
182
|
+
// --- Animation validation ---------------------------------------------------
|
|
333
183
|
//
|
|
334
184
|
// Keep the invariants at the native ownership boundary as well as in JS. OTA
|
|
335
185
|
// callers and direct host-function users can bypass the TypeScript preflight;
|
|
336
186
|
// rejecting here prevents an invalid request from dissolving an existing
|
|
337
187
|
// animation group before the registry notices it cannot install the new plan.
|
|
338
188
|
|
|
339
|
-
inline bool
|
|
189
|
+
inline bool isSupportedCurve(ClipCurve curve) {
|
|
340
190
|
return curve == ClipCurve::Circular || curve == ClipCurve::Continuous;
|
|
341
191
|
}
|
|
342
192
|
|
|
343
|
-
inline bool
|
|
193
|
+
inline bool isFinitePresentation(
|
|
344
194
|
const Presentation &presentation) {
|
|
345
195
|
const Channels channels = toChannels(presentation);
|
|
346
196
|
return std::all_of(
|
|
347
197
|
channels.begin(), channels.end(),
|
|
348
198
|
[](double value) { return std::isfinite(value); }) &&
|
|
349
|
-
|
|
350
|
-
presentation.contentScale > 0
|
|
199
|
+
isSupportedCurve(presentation.clip.curve) &&
|
|
200
|
+
presentation.contentScale > 0 &&
|
|
201
|
+
presentation.shadow.red >= 0 && presentation.shadow.red <= 1 &&
|
|
202
|
+
presentation.shadow.green >= 0 && presentation.shadow.green <= 1 &&
|
|
203
|
+
presentation.shadow.blue >= 0 && presentation.shadow.blue <= 1 &&
|
|
204
|
+
presentation.shadow.alpha >= 0 && presentation.shadow.alpha <= 1 &&
|
|
205
|
+
presentation.shadow.blurRadius >= 0;
|
|
351
206
|
}
|
|
352
207
|
|
|
353
|
-
|
|
208
|
+
/** Canonicalizes registry geometry without consulting any attached host. */
|
|
209
|
+
inline bool canonicalizePresentation(Presentation &presentation) {
|
|
210
|
+
if (!isFinitePresentation(presentation)) return false;
|
|
211
|
+
CanonicalClip clip;
|
|
212
|
+
if (!SmoothClipCanonicalize(presentation.clip, clip)) return false;
|
|
213
|
+
presentation.clip = SmoothClipGeometry(clip);
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
inline bool presentationsEqual(
|
|
354
218
|
const Presentation &first,
|
|
355
219
|
const Presentation &second) {
|
|
356
220
|
return first.clip.curve == second.clip.curve &&
|
|
221
|
+
first.shadow.enabled == second.shadow.enabled &&
|
|
357
222
|
toChannels(first) == toChannels(second);
|
|
358
223
|
}
|
|
359
224
|
|
|
225
|
+
inline bool hasVisibleShadow(const Presentation &presentation) {
|
|
226
|
+
return presentation.shadow.enabled && presentation.shadow.alpha > 0 &&
|
|
227
|
+
presentation.clip.width > 0 && presentation.clip.height > 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
inline bool isAutonomousUniformCircular(
|
|
231
|
+
const Presentation &presentation) {
|
|
232
|
+
const Geometry &clip = presentation.clip;
|
|
233
|
+
return clip.curve == ClipCurve::Circular && radiiAreUniform(
|
|
234
|
+
resolvedRadius(clip.topLeftRadius, clip.radius),
|
|
235
|
+
resolvedRadius(clip.topRightRadius, clip.radius),
|
|
236
|
+
resolvedRadius(clip.bottomRightRadius, clip.radius),
|
|
237
|
+
resolvedRadius(clip.bottomLeftRadius, clip.radius));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Makes shadow appearance/disappearance a pure alpha fade. A disabled or
|
|
242
|
+
* transparent endpoint borrows the visible endpoint's style while retaining
|
|
243
|
+
* its own geometry, so path, blur, spread, and offset do not collapse during
|
|
244
|
+
* the fade. The public target remains unchanged in the registry; these copies
|
|
245
|
+
* are animation inputs only.
|
|
246
|
+
*/
|
|
247
|
+
inline std::pair<Presentation, Presentation> normalizeShadowEndpoints(
|
|
248
|
+
Presentation from,
|
|
249
|
+
Presentation to) {
|
|
250
|
+
const bool fromVisible = hasVisibleShadow(from);
|
|
251
|
+
const bool toVisible = hasVisibleShadow(to);
|
|
252
|
+
if (!fromVisible) from.shadow.alpha = 0;
|
|
253
|
+
if (!toVisible) to.shadow.alpha = 0;
|
|
254
|
+
if (fromVisible == toVisible) return {from, to};
|
|
255
|
+
|
|
256
|
+
Shadow &invisible = fromVisible ? to.shadow : from.shadow;
|
|
257
|
+
const Shadow &visible = fromVisible ? from.shadow : to.shadow;
|
|
258
|
+
invisible = visible;
|
|
259
|
+
invisible.enabled = true;
|
|
260
|
+
invisible.alpha = 0;
|
|
261
|
+
return {from, to};
|
|
262
|
+
}
|
|
263
|
+
|
|
360
264
|
inline bool isValidReduceMotionCode(int32_t value) {
|
|
361
265
|
return value >= 0 && value <= 2;
|
|
362
266
|
}
|
|
363
267
|
|
|
364
|
-
inline bool
|
|
268
|
+
inline bool isValidTiming(const TimingAnimation &animation) {
|
|
365
269
|
return std::isfinite(animation.durationMs) && animation.durationMs >= 0 &&
|
|
366
270
|
std::isfinite(animation.controlPoint1X) &&
|
|
367
271
|
std::isfinite(animation.controlPoint1Y) &&
|
|
@@ -372,59 +276,153 @@ inline bool isValidProtocolV2Timing(const TimingAnimation &animation) {
|
|
|
372
276
|
isValidReduceMotionCode(animation.reduceMotion);
|
|
373
277
|
}
|
|
374
278
|
|
|
375
|
-
inline bool
|
|
279
|
+
inline bool isValidSpring(const SpringAnimation &animation) {
|
|
376
280
|
return std::isfinite(animation.mass) && animation.mass > 0 &&
|
|
377
281
|
std::isfinite(animation.stiffness) && animation.stiffness > 0 &&
|
|
378
282
|
std::isfinite(animation.damping) && animation.damping >= 0 &&
|
|
379
283
|
std::isfinite(animation.initialVelocity) &&
|
|
284
|
+
std::isfinite(animation.energyThreshold) &&
|
|
285
|
+
animation.energyThreshold > 0 &&
|
|
380
286
|
isValidReduceMotionCode(animation.reduceMotion);
|
|
381
287
|
}
|
|
382
288
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
289
|
+
struct ScalarSpringState {
|
|
290
|
+
double position;
|
|
291
|
+
double velocity;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/** One analytic Reanimated-compatible spring step for normalized progress. */
|
|
295
|
+
inline ScalarSpringState advanceScalarSpring(
|
|
296
|
+
ScalarSpringState state,
|
|
297
|
+
const SpringAnimation &animation,
|
|
298
|
+
double deltaTimeS) {
|
|
299
|
+
const double dt = std::clamp(deltaTimeS, 0.0, 0.064);
|
|
300
|
+
if (dt == 0) return state;
|
|
301
|
+
const double omega0 = std::sqrt(animation.stiffness / animation.mass);
|
|
302
|
+
const double zeta = animation.damping /
|
|
303
|
+
(2.0 * std::sqrt(animation.stiffness * animation.mass));
|
|
304
|
+
const double displacement = state.position - 1.0;
|
|
305
|
+
const double velocity = state.velocity;
|
|
306
|
+
if (zeta < 1.0) {
|
|
307
|
+
const double omega1 = omega0 * std::sqrt(1.0 - zeta * zeta);
|
|
308
|
+
const double envelope = std::exp(-zeta * omega0 * dt);
|
|
309
|
+
const double sinTerm = std::sin(omega1 * dt);
|
|
310
|
+
const double cosTerm = std::cos(omega1 * dt);
|
|
311
|
+
const double coefficient =
|
|
312
|
+
(velocity + zeta * omega0 * displacement) / omega1;
|
|
313
|
+
const double nextDisplacement =
|
|
314
|
+
envelope * (displacement * cosTerm + coefficient * sinTerm);
|
|
315
|
+
const double nextVelocity = envelope *
|
|
316
|
+
(velocity * (cosTerm - zeta * omega0 / omega1 * sinTerm) -
|
|
317
|
+
displacement * (omega0 * omega0 / omega1) * sinTerm);
|
|
318
|
+
return {1.0 + nextDisplacement, nextVelocity};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Reanimated deliberately uses its critical branch for zeta >= 1.
|
|
322
|
+
const double envelope = std::exp(-omega0 * dt);
|
|
323
|
+
const double coefficient = velocity + omega0 * displacement;
|
|
324
|
+
const double nextDisplacement =
|
|
325
|
+
envelope * (displacement + coefficient * dt);
|
|
326
|
+
const double nextVelocity =
|
|
327
|
+
envelope * (velocity - omega0 * coefficient * dt);
|
|
328
|
+
return {1.0 + nextDisplacement, nextVelocity};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
inline double relativeSpringEnergy(
|
|
332
|
+
ScalarSpringState state,
|
|
386
333
|
const SpringAnimation &animation) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
animation.
|
|
394
|
-
|
|
334
|
+
const double displacement = state.position - 1.0;
|
|
335
|
+
const double currentEnergy =
|
|
336
|
+
animation.mass * state.velocity * state.velocity +
|
|
337
|
+
animation.stiffness * displacement * displacement;
|
|
338
|
+
const double initialEnergy =
|
|
339
|
+
animation.mass * animation.initialVelocity * animation.initialVelocity +
|
|
340
|
+
animation.stiffness;
|
|
341
|
+
return currentEnergy / initialEnergy;
|
|
395
342
|
}
|
|
396
343
|
|
|
397
|
-
inline bool
|
|
398
|
-
const
|
|
399
|
-
const Presentation &resolvedStart,
|
|
344
|
+
inline bool springScaleStaysPositive(
|
|
345
|
+
const Presentation &start,
|
|
400
346
|
const Presentation &target,
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
347
|
+
const SpringAnimation &animation,
|
|
348
|
+
double initialVelocity) {
|
|
349
|
+
if (start.contentScale == target.contentScale) return true;
|
|
350
|
+
if (!std::isfinite(initialVelocity)) return false;
|
|
351
|
+
|
|
352
|
+
const double startScale = start.contentScale;
|
|
353
|
+
const double targetScale = target.contentScale;
|
|
354
|
+
const double delta = targetScale - startScale;
|
|
355
|
+
const auto scaleForDisplacement = [&](double displacement) {
|
|
356
|
+
// displacement is q = progress - 1, so scale = target + delta*q.
|
|
357
|
+
return targetScale + delta * displacement;
|
|
358
|
+
};
|
|
359
|
+
if (startScale <= 0 || targetScale <= 0) return false;
|
|
360
|
+
|
|
361
|
+
const double naturalFrequency =
|
|
362
|
+
std::sqrt(animation.stiffness / animation.mass);
|
|
363
|
+
const double decay = animation.damping / (2 * animation.mass);
|
|
364
|
+
const double discriminant = decay * decay -
|
|
365
|
+
naturalFrequency * naturalFrequency;
|
|
366
|
+
constexpr double epsilon = 1e-12;
|
|
367
|
+
constexpr double pi = 3.14159265358979323846;
|
|
368
|
+
|
|
369
|
+
const auto isPositiveAt = [&](double time, double displacement) {
|
|
370
|
+
return time <= epsilon || scaleForDisplacement(displacement) > 0;
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
if (discriminant < -epsilon) {
|
|
374
|
+
const double frequency = std::sqrt(-discriminant);
|
|
375
|
+
const double a = -1;
|
|
376
|
+
const double b = (initialVelocity - decay) / frequency;
|
|
377
|
+
const double derivativeCos = initialVelocity;
|
|
378
|
+
const double derivativeSin = frequency - decay * b;
|
|
379
|
+
double phase = std::atan2(-derivativeCos, derivativeSin);
|
|
380
|
+
while (phase <= epsilon) phase += pi;
|
|
381
|
+
// Extrema alternate and their envelope only decays, so the first two
|
|
382
|
+
// positive extrema cover both signs and therefore the global minimum.
|
|
383
|
+
for (int index = 0; index < 2; index += 1) {
|
|
384
|
+
const double time = phase / frequency;
|
|
385
|
+
const double displacement = std::exp(-decay * time) *
|
|
386
|
+
(a * std::cos(phase) + b * std::sin(phase));
|
|
387
|
+
if (!isPositiveAt(time, displacement)) return false;
|
|
388
|
+
phase += pi;
|
|
389
|
+
}
|
|
390
|
+
return true;
|
|
406
391
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
392
|
+
|
|
393
|
+
if (std::abs(discriminant) <= epsilon) {
|
|
394
|
+
const double b = initialVelocity - decay;
|
|
395
|
+
const double denominator = decay * b;
|
|
396
|
+
if (std::abs(denominator) <= epsilon) return true;
|
|
397
|
+
const double time = initialVelocity / denominator;
|
|
398
|
+
if (time <= epsilon) return true;
|
|
399
|
+
const double displacement = std::exp(-decay * time) * (-1 + b * time);
|
|
400
|
+
return isPositiveAt(time, displacement);
|
|
411
401
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
402
|
+
|
|
403
|
+
const double root = std::sqrt(discriminant);
|
|
404
|
+
const double slow = -decay + root;
|
|
405
|
+
const double fast = -decay - root;
|
|
406
|
+
const double slowCoefficient =
|
|
407
|
+
(initialVelocity + fast) / (slow - fast);
|
|
408
|
+
const double fastCoefficient = -1 - slowCoefficient;
|
|
409
|
+
const double numerator = -fastCoefficient * fast;
|
|
410
|
+
const double denominator = slowCoefficient * slow;
|
|
411
|
+
if (std::abs(denominator) <= epsilon || numerator / denominator <= 0) {
|
|
412
|
+
return true;
|
|
421
413
|
}
|
|
422
|
-
|
|
414
|
+
const double time =
|
|
415
|
+
std::log(numerator / denominator) / (slow - fast);
|
|
416
|
+
if (time <= epsilon) return true;
|
|
417
|
+
const double displacement = slowCoefficient * std::exp(slow * time) +
|
|
418
|
+
fastCoefficient * std::exp(fast * time);
|
|
419
|
+
return isPositiveAt(time, displacement);
|
|
423
420
|
}
|
|
424
421
|
|
|
425
422
|
inline Presentation fromChannels(
|
|
426
423
|
const Channels &channels,
|
|
427
|
-
ClipCurve curve = ClipCurve::Circular
|
|
424
|
+
ClipCurve curve = ClipCurve::Circular,
|
|
425
|
+
bool shadowEnabled = false) {
|
|
428
426
|
Geometry geometry{
|
|
429
427
|
channels[0], channels[1], channels[2], channels[3], 0.0};
|
|
430
428
|
geometry.topLeftRadius = channels[4];
|
|
@@ -436,7 +434,18 @@ inline Presentation fromChannels(
|
|
|
436
434
|
? channels[4]
|
|
437
435
|
: 0.0;
|
|
438
436
|
geometry.curve = curve;
|
|
439
|
-
|
|
437
|
+
Shadow shadow{
|
|
438
|
+
shadowEnabled,
|
|
439
|
+
std::clamp(channels[11], 0.0, 1.0),
|
|
440
|
+
std::clamp(channels[12], 0.0, 1.0),
|
|
441
|
+
std::clamp(channels[13], 0.0, 1.0),
|
|
442
|
+
std::clamp(channels[14], 0.0, 1.0),
|
|
443
|
+
channels[15],
|
|
444
|
+
channels[16],
|
|
445
|
+
std::max(0.0, channels[17]),
|
|
446
|
+
channels[18]};
|
|
447
|
+
return Presentation{
|
|
448
|
+
geometry, channels[8], channels[9], channels[10], shadow};
|
|
440
449
|
}
|
|
441
450
|
|
|
442
451
|
inline Presentation interpolate(
|
|
@@ -447,143 +456,25 @@ inline Presentation interpolate(
|
|
|
447
456
|
return start + (end - start) * progress;
|
|
448
457
|
};
|
|
449
458
|
Channels blended{};
|
|
450
|
-
const
|
|
451
|
-
const Channels
|
|
452
|
-
|
|
459
|
+
const auto normalized = normalizeShadowEndpoints(from, to);
|
|
460
|
+
const Channels fromChannelsValue = toChannels(normalized.first);
|
|
461
|
+
const Channels toChannelsValue = toChannels(normalized.second);
|
|
462
|
+
const bool rendersShadow = hasVisibleShadow(from) || hasVisibleShadow(to);
|
|
463
|
+
const std::size_t channelCount =
|
|
464
|
+
rendersShadow ? kChannelCount : kBaseChannelCount;
|
|
465
|
+
for (std::size_t index = 0; index < channelCount; index += 1) {
|
|
453
466
|
blended[index] = mix(fromChannelsValue[index], toChannelsValue[index]);
|
|
454
467
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
// residual curve in one helper prevents lifecycle code from sampling one
|
|
461
|
-
// timestamp for the geometry and another for the duration.
|
|
462
|
-
inline TimingContinuation timingContinuation(
|
|
463
|
-
const TimingAnimation &original,
|
|
464
|
-
const Presentation &start,
|
|
465
|
-
const Presentation &target,
|
|
466
|
-
double rawProgress) {
|
|
467
|
-
const TimingRemainder remainder = timingRemainder(original, rawProgress);
|
|
468
|
-
return {
|
|
469
|
-
interpolate(start, target, remainder.easedProgress),
|
|
470
|
-
remainder.animation};
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Android lifecycle callbacks can arrive long after the frame whose
|
|
474
|
-
// presentation was actually delivered. Keep the clock inputs at this shared
|
|
475
|
-
// boundary so callers cannot accidentally trim from callback wall time while
|
|
476
|
-
// calculating geometry from the last rendered frame.
|
|
477
|
-
inline TimingContinuation timingContinuationAtFrame(
|
|
478
|
-
const TimingAnimation &original,
|
|
479
|
-
const Presentation &start,
|
|
480
|
-
const Presentation &target,
|
|
481
|
-
double lastRenderedS,
|
|
482
|
-
double startedAtS,
|
|
483
|
-
double durationS) {
|
|
484
|
-
return timingContinuation(
|
|
485
|
-
original,
|
|
486
|
-
start,
|
|
487
|
-
target,
|
|
488
|
-
timingFraction(lastRenderedS, startedAtS, durationS));
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// --- Keyframe curve -------------------------------------------------------
|
|
492
|
-
|
|
493
|
-
// Keyframes interpolate segment-wise linearly on every platform. This is a
|
|
494
|
-
// public V2 contract: consumers compile unsupported springs/easings into exact
|
|
495
|
-
// scalar samples, and native must not reinterpret those samples as a spline.
|
|
496
|
-
class KeyframeCurve {
|
|
497
|
-
public:
|
|
498
|
-
void reset(std::vector<Keyframe> frames) {
|
|
499
|
-
frames_ = std::move(frames);
|
|
500
|
-
cursor_ = 1;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
const std::vector<Keyframe> &frames() const { return frames_; }
|
|
504
|
-
std::size_t size() const { return frames_.size(); }
|
|
505
|
-
bool usable() const { return frames_.size() >= 2; }
|
|
506
|
-
|
|
507
|
-
Presentation evaluate(double progress) {
|
|
508
|
-
const std::size_t count = frames_.size();
|
|
509
|
-
if (count == 0) return Presentation{{0, 0, 0, 0, 0}, 0, 0, 1};
|
|
510
|
-
if (count == 1) return frames_[0].presentation;
|
|
511
|
-
|
|
512
|
-
// The scan resumes from the previous segment instead of restarting at 1.
|
|
513
|
-
// Progress is monotonic within an animation, so this is O(1) amortized
|
|
514
|
-
// where the old restart-every-frame scan was O(keyframes) per frame; the
|
|
515
|
-
// backward walk keeps it correct if progress ever moves the other way.
|
|
516
|
-
std::size_t upper = std::min(std::max<std::size_t>(cursor_, 1), count - 1);
|
|
517
|
-
while (upper > 1 && progress < frames_[upper - 1].offset) upper -= 1;
|
|
518
|
-
while (upper < count - 1 && progress > frames_[upper].offset) upper += 1;
|
|
519
|
-
cursor_ = upper;
|
|
520
|
-
|
|
521
|
-
const Keyframe &lower = frames_[upper - 1];
|
|
522
|
-
const Keyframe &higher = frames_[upper];
|
|
523
|
-
const double span = higher.offset - lower.offset;
|
|
524
|
-
if (span <= 0) return higher.presentation;
|
|
525
|
-
|
|
526
|
-
const double t = clamp01((progress - lower.offset) / span);
|
|
527
|
-
return interpolate(lower.presentation, higher.presentation, t);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
private:
|
|
531
|
-
std::vector<Keyframe> frames_;
|
|
532
|
-
std::size_t cursor_ = 1;
|
|
533
|
-
};
|
|
534
|
-
|
|
535
|
-
struct KeyframeContinuation {
|
|
536
|
-
Presentation start;
|
|
537
|
-
std::vector<Keyframe> frames;
|
|
538
|
-
double durationMs;
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
// Trims keyframes at the same raw-time phase that produced `frozen`. The new
|
|
542
|
-
// zero frame is the rendered presentation, not an analytically re-sampled
|
|
543
|
-
// neighbor, so a pause/re-latch cannot jump at the seam. Strictly increasing
|
|
544
|
-
// remapped offsets avoid duplicate/division-by-zero segments after repeated
|
|
545
|
-
// trims or floating-point collisions near the cutoff.
|
|
546
|
-
inline KeyframeContinuation keyframeContinuation(
|
|
547
|
-
const std::vector<Keyframe> &frames,
|
|
548
|
-
const Presentation &frozen,
|
|
549
|
-
const Presentation &target,
|
|
550
|
-
double durationMs,
|
|
551
|
-
double rawProgress) {
|
|
552
|
-
const double progress = clamp01(rawProgress);
|
|
553
|
-
KeyframeContinuation result{
|
|
554
|
-
frozen, {{0.0, frozen}}, std::max(0.0, durationMs * (1 - progress))};
|
|
555
|
-
if (progress < 1.0) {
|
|
556
|
-
for (const Keyframe &frame : frames) {
|
|
557
|
-
if (frame.offset <= progress) continue;
|
|
558
|
-
const double offset = clamp01(
|
|
559
|
-
(frame.offset - progress) / (1 - progress));
|
|
560
|
-
if (offset <= result.frames.back().offset + 1e-12) continue;
|
|
561
|
-
result.frames.push_back({offset, frame.presentation});
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
if (result.frames.size() == 1 ||
|
|
565
|
-
result.frames.back().offset < 1.0 - 1e-12) {
|
|
566
|
-
result.frames.push_back({1.0, target});
|
|
567
|
-
} else {
|
|
568
|
-
result.frames.back().offset = 1.0;
|
|
569
|
-
result.frames.back().presentation = target;
|
|
468
|
+
if (!rendersShadow) {
|
|
469
|
+
std::copy(
|
|
470
|
+
fromChannelsValue.begin() + kBaseChannelCount,
|
|
471
|
+
fromChannelsValue.end(),
|
|
472
|
+
blended.begin() + kBaseChannelCount);
|
|
570
473
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
const std::vector<Keyframe> &frames,
|
|
576
|
-
const Presentation &frozen,
|
|
577
|
-
const Presentation &target,
|
|
578
|
-
double lastRenderedS,
|
|
579
|
-
double startedAtS,
|
|
580
|
-
double durationS) {
|
|
581
|
-
return keyframeContinuation(
|
|
582
|
-
frames,
|
|
583
|
-
frozen,
|
|
584
|
-
target,
|
|
585
|
-
std::max(0.0, durationS * 1000.0),
|
|
586
|
-
timingFraction(lastRenderedS, startedAtS, durationS));
|
|
474
|
+
const bool shadowEnabled = progress >= 1
|
|
475
|
+
? to.shadow.enabled
|
|
476
|
+
: rendersShadow;
|
|
477
|
+
return fromChannels(blended, from.clip.curve, shadowEnabled);
|
|
587
478
|
}
|
|
588
479
|
|
|
589
480
|
} // namespace smoothclip
|