react-native-smooth-clip-view 0.2.9 → 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 -349
- package/android/build.gradle +8 -4
- package/android/proguard-rules.pro +3 -3
- package/android/src/main/cpp/SmoothClipAndroid.h +13 -6
- package/android/src/main/cpp/SmoothClipBindings.cpp +605 -123
- package/android/src/main/cpp/SmoothClipRegistry.cpp +975 -209
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +192 -118
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +22 -4
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +81 -56
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +449 -240
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +150 -38
- package/cpp/SmoothClipAnimationCurve.h +317 -397
- package/cpp/SmoothClipRegistry.h +84 -20
- package/cpp/SmoothClipSharedGeometry.h +131 -22
- package/cpp/SmoothClipVelocityTracker.h +16 -12
- package/ios/SmoothClipGeometry.h +188 -21
- package/ios/SmoothClipRegistry.mm +869 -368
- package/ios/SmoothClipRegistrySnapshot.h +13 -0
- package/ios/SmoothClipTurboModule.cpp +513 -156
- package/ios/SmoothClipTurboModule.h +123 -56
- package/ios/SmoothClipView.mm +1074 -449
- package/ios/SmoothClipViewRegistry.h +3 -7
- package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/lib/module/NativeSmoothClipModule.js.map +1 -1
- package/lib/module/SmoothClipView.ios.js +94 -11
- package/lib/module/SmoothClipView.ios.js.map +1 -1
- package/lib/module/SmoothClipView.js +1 -46
- package/lib/module/SmoothClipView.js.map +1 -1
- package/lib/module/SmoothClipViewNativeComponent.ts +32 -12
- package/lib/module/capabilities.js +4 -0
- package/lib/module/capabilities.js.map +1 -0
- package/lib/module/capabilities.native.js +18 -0
- package/lib/module/capabilities.native.js.map +1 -0
- package/lib/module/capabilityTypes.js +2 -0
- package/lib/module/capabilityTypes.js.map +1 -0
- 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 +180 -30
- package/lib/module/geometry.js.map +1 -1
- package/lib/module/groupTypes.js +4 -0
- package/lib/module/groupTypes.js.map +1 -0
- 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 +4 -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 +20 -4
- package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.d.ts +2 -10
- package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.ios.d.ts +139 -4
- package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +16 -3
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/capabilities.d.ts +3 -0
- package/lib/typescript/src/capabilities.d.ts.map +1 -0
- package/lib/typescript/src/capabilities.native.d.ts +3 -0
- package/lib/typescript/src/capabilities.native.d.ts.map +1 -0
- package/lib/typescript/src/capabilityTypes.d.ts +4 -0
- package/lib/typescript/src/capabilityTypes.d.ts.map +1 -0
- 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 +49 -5
- 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 +8 -5
- 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 +17 -13
- package/src/NativeSmoothClipModule.ts +80 -56
- package/src/SmoothClipView.ios.tsx +142 -16
- package/src/SmoothClipView.tsx +6 -76
- package/src/SmoothClipViewNativeComponent.ts +32 -12
- package/src/capabilities.native.ts +19 -0
- package/src/capabilities.ts +2 -0
- package/src/capabilityTypes.ts +3 -0
- 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 +293 -45
- 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 +32 -12
- package/src/nativeCompletion.ts +67 -32
- package/src/presentationCodec.ts +134 -0
- package/lib/module/SmoothClipView.web.js +0 -54
- package/lib/module/SmoothClipView.web.js.map +0 -1
- package/lib/module/driverState.js +0 -110
- 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 -284
- package/lib/module/drivers.js.map +0 -1
- package/lib/module/drivers.native.js +0 -556
- package/lib/module/drivers.native.js.map +0 -1
- package/lib/module/index.web.js +0 -8
- package/lib/module/index.web.js.map +0 -1
- package/lib/module/reactRequests.js +0 -56
- package/lib/module/reactRequests.js.map +0 -1
- package/lib/module/webClipPath.js +0 -25
- package/lib/module/webClipPath.js.map +0 -1
- package/lib/typescript/src/SmoothClipView.web.d.ts +0 -10
- package/lib/typescript/src/SmoothClipView.web.d.ts.map +0 -1
- package/lib/typescript/src/driverState.d.ts +0 -31
- package/lib/typescript/src/driverState.d.ts.map +0 -1
- package/lib/typescript/src/driverTypes.d.ts +0 -107
- 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 -5
- 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/index.web.d.ts +0 -9
- package/lib/typescript/src/index.web.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/lib/typescript/src/webClipPath.d.ts +0 -7
- package/lib/typescript/src/webClipPath.d.ts.map +0 -1
- package/src/SmoothClipView.web.tsx +0 -66
- package/src/driverState.ts +0 -158
- package/src/driverTypes.ts +0 -140
- package/src/drivers.android.ts +0 -8
- package/src/drivers.ios.ts +0 -8
- package/src/drivers.native.ts +0 -899
- package/src/drivers.ts +0 -427
- package/src/index.web.ts +0 -23
- package/src/reactRequests.ts +0 -76
- package/src/webClipPath.ts +0 -30
|
@@ -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,420 +128,353 @@ inline double cubicBezier(
|
|
|
141
128
|
3 * inverse * t * t * y2 + t * t * t;
|
|
142
129
|
}
|
|
143
130
|
|
|
144
|
-
|
|
145
|
-
TimingAnimation animation;
|
|
146
|
-
double easedProgress;
|
|
147
|
-
bool representable;
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
struct TimingContinuation {
|
|
151
|
-
Presentation start;
|
|
152
|
-
TimingAnimation animation;
|
|
153
|
-
};
|
|
131
|
+
// --- Presentation channels ------------------------------------------------
|
|
154
132
|
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
}
|
|
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;
|
|
137
|
+
using Channels = std::array<double, kChannelCount>;
|
|
174
138
|
|
|
175
|
-
|
|
176
|
-
|
|
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};
|
|
139
|
+
inline double resolvedRadius(double overrideValue, double shorthand) {
|
|
140
|
+
return std::isfinite(overrideValue) ? overrideValue : shorthand;
|
|
209
141
|
}
|
|
210
142
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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};
|
|
143
|
+
inline bool radiiAreUniform(
|
|
144
|
+
double topLeft,
|
|
145
|
+
double topRight,
|
|
146
|
+
double bottomRight,
|
|
147
|
+
double bottomLeft) {
|
|
148
|
+
return topLeft == topRight && topLeft == bottomRight &&
|
|
149
|
+
topLeft == bottomLeft;
|
|
273
150
|
}
|
|
274
151
|
|
|
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
|
-
// --- Presentation channels ------------------------------------------------
|
|
291
|
-
|
|
292
|
-
constexpr std::size_t kChannelCount = 7;
|
|
293
|
-
using Channels = std::array<double, kChannelCount>;
|
|
294
|
-
|
|
295
152
|
inline Channels toChannels(const Presentation &presentation) {
|
|
153
|
+
const double topLeft = resolvedRadius(
|
|
154
|
+
presentation.clip.topLeftRadius, presentation.clip.radius);
|
|
155
|
+
const double topRight = resolvedRadius(
|
|
156
|
+
presentation.clip.topRightRadius, presentation.clip.radius);
|
|
157
|
+
const double bottomRight = resolvedRadius(
|
|
158
|
+
presentation.clip.bottomRightRadius, presentation.clip.radius);
|
|
159
|
+
const double bottomLeft = resolvedRadius(
|
|
160
|
+
presentation.clip.bottomLeftRadius, presentation.clip.radius);
|
|
296
161
|
return {presentation.clip.x,
|
|
297
162
|
presentation.clip.y,
|
|
298
163
|
presentation.clip.width,
|
|
299
164
|
presentation.clip.height,
|
|
300
|
-
|
|
165
|
+
topLeft,
|
|
166
|
+
topRight,
|
|
167
|
+
bottomRight,
|
|
168
|
+
bottomLeft,
|
|
301
169
|
presentation.contentTranslateX,
|
|
302
|
-
presentation.contentTranslateY
|
|
170
|
+
presentation.contentTranslateY,
|
|
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};
|
|
303
180
|
}
|
|
304
181
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
182
|
+
// --- Animation validation ---------------------------------------------------
|
|
183
|
+
//
|
|
184
|
+
// Keep the invariants at the native ownership boundary as well as in JS. OTA
|
|
185
|
+
// callers and direct host-function users can bypass the TypeScript preflight;
|
|
186
|
+
// rejecting here prevents an invalid request from dissolving an existing
|
|
187
|
+
// animation group before the registry notices it cannot install the new plan.
|
|
188
|
+
|
|
189
|
+
inline bool isSupportedCurve(ClipCurve curve) {
|
|
190
|
+
return curve == ClipCurve::Circular || curve == ClipCurve::Continuous;
|
|
310
191
|
}
|
|
311
192
|
|
|
312
|
-
inline
|
|
313
|
-
const Presentation &
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
mix(from.contentTranslateX, to.contentTranslateX),
|
|
326
|
-
mix(from.contentTranslateY, to.contentTranslateY)};
|
|
193
|
+
inline bool isFinitePresentation(
|
|
194
|
+
const Presentation &presentation) {
|
|
195
|
+
const Channels channels = toChannels(presentation);
|
|
196
|
+
return std::all_of(
|
|
197
|
+
channels.begin(), channels.end(),
|
|
198
|
+
[](double value) { return std::isfinite(value); }) &&
|
|
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;
|
|
327
206
|
}
|
|
328
207
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const Presentation &target,
|
|
337
|
-
double rawProgress) {
|
|
338
|
-
const TimingRemainder remainder = timingRemainder(original, rawProgress);
|
|
339
|
-
return {
|
|
340
|
-
interpolate(start, target, remainder.easedProgress),
|
|
341
|
-
remainder.animation};
|
|
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;
|
|
342
215
|
}
|
|
343
216
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const Presentation &start,
|
|
351
|
-
const Presentation &target,
|
|
352
|
-
double lastRenderedS,
|
|
353
|
-
double startedAtS,
|
|
354
|
-
double durationS) {
|
|
355
|
-
return timingContinuation(
|
|
356
|
-
original,
|
|
357
|
-
start,
|
|
358
|
-
target,
|
|
359
|
-
timingFraction(lastRenderedS, startedAtS, durationS));
|
|
217
|
+
inline bool presentationsEqual(
|
|
218
|
+
const Presentation &first,
|
|
219
|
+
const Presentation &second) {
|
|
220
|
+
return first.clip.curve == second.clip.curve &&
|
|
221
|
+
first.shadow.enabled == second.shadow.enabled &&
|
|
222
|
+
toChannels(first) == toChannels(second);
|
|
360
223
|
}
|
|
361
224
|
|
|
362
|
-
|
|
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
|
+
}
|
|
363
229
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
//
|
|
374
|
-
// Monotone rather than plain Catmull-Rom because the alternative can overshoot,
|
|
375
|
-
// and these channels do not tolerate it: width, height and radius must never
|
|
376
|
-
// dip below zero or bulge past the values the consumer asked for. The
|
|
377
|
-
// Fritsch-Carlson tangent clamp keeps every segment inside the range of its own
|
|
378
|
-
// endpoints. Two keyframes degenerate to the old straight line (both tangents
|
|
379
|
-
// equal the secant) — equal in real arithmetic, within one ulp (~1e-13 of the
|
|
380
|
-
// travel) in doubles because the Hermite blend orders its operations
|
|
381
|
-
// differently than the lerp it replaced. The jfloat delivery cast erases the
|
|
382
|
-
// difference long before a pixel could see it.
|
|
383
|
-
//
|
|
384
|
-
// PARITY NOTE: iOS builds a CAKeyframeAnimation with `kCAAnimationLinear`
|
|
385
|
-
// (SmoothClipView.mm) and cannot run this evaluator — CoreAnimation interpolates
|
|
386
|
-
// off-thread. The platforms therefore differ mid-segment by the linearization
|
|
387
|
-
// error this removes, sub-pixel at any realistic keyframe density and zero at
|
|
388
|
-
// every keyframe. Closing it needs either `kCAAnimationCubic` (a different
|
|
389
|
-
// spline that CAN overshoot, so no) or resampling the keyframes densely through
|
|
390
|
-
// this curve before handing them to CA.
|
|
391
|
-
class KeyframeCurve {
|
|
392
|
-
public:
|
|
393
|
-
void reset(std::vector<Keyframe> frames) {
|
|
394
|
-
frames_ = std::move(frames);
|
|
395
|
-
cursor_ = 1;
|
|
396
|
-
tangents_.assign(frames_.size(), Channels{});
|
|
397
|
-
const std::size_t count = frames_.size();
|
|
398
|
-
if (count < 2) return;
|
|
399
|
-
|
|
400
|
-
// Secant slope of every segment, per channel. A non-increasing offset pair
|
|
401
|
-
// (the re-latch remap can produce one) contributes no slope rather than
|
|
402
|
-
// dividing by zero; evaluate() short-circuits those segments too.
|
|
403
|
-
std::vector<Channels> secants(count - 1);
|
|
404
|
-
for (std::size_t index = 0; index + 1 < count; index += 1) {
|
|
405
|
-
const double span = frames_[index + 1].offset - frames_[index].offset;
|
|
406
|
-
const Channels lower = toChannels(frames_[index].presentation);
|
|
407
|
-
const Channels upper = toChannels(frames_[index + 1].presentation);
|
|
408
|
-
for (std::size_t channel = 0; channel < kChannelCount; channel += 1) {
|
|
409
|
-
secants[index][channel] =
|
|
410
|
-
span > 0 ? (upper[channel] - lower[channel]) / span : 0.0;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
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
|
+
}
|
|
413
239
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
+
}
|
|
423
263
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
const double secant = secants[index][channel];
|
|
428
|
-
if (secant == 0.0) {
|
|
429
|
-
// Flat segment: pin both ends flat so the curve cannot bulge off a
|
|
430
|
-
// pair of equal keyframes.
|
|
431
|
-
tangents_[index][channel] = 0.0;
|
|
432
|
-
tangents_[index + 1][channel] = 0.0;
|
|
433
|
-
continue;
|
|
434
|
-
}
|
|
435
|
-
double alpha = tangents_[index][channel] / secant;
|
|
436
|
-
double beta = tangents_[index + 1][channel] / secant;
|
|
437
|
-
if (alpha < 0.0) {
|
|
438
|
-
tangents_[index][channel] = 0.0;
|
|
439
|
-
alpha = 0.0;
|
|
440
|
-
}
|
|
441
|
-
if (beta < 0.0) {
|
|
442
|
-
tangents_[index + 1][channel] = 0.0;
|
|
443
|
-
beta = 0.0;
|
|
444
|
-
}
|
|
445
|
-
const double magnitude = alpha * alpha + beta * beta;
|
|
446
|
-
if (magnitude > 9.0) {
|
|
447
|
-
const double scale = 3.0 / std::sqrt(magnitude);
|
|
448
|
-
tangents_[index][channel] = scale * alpha * secant;
|
|
449
|
-
tangents_[index + 1][channel] = scale * beta * secant;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
264
|
+
inline bool isValidReduceMotionCode(int32_t value) {
|
|
265
|
+
return value >= 0 && value <= 2;
|
|
266
|
+
}
|
|
454
267
|
|
|
455
|
-
|
|
456
|
-
std::
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
// Progress is monotonic within an animation, so this is O(1) amortized
|
|
466
|
-
// where the old restart-every-frame scan was O(keyframes) per frame; the
|
|
467
|
-
// backward walk keeps it correct if progress ever moves the other way.
|
|
468
|
-
std::size_t upper = std::min(std::max<std::size_t>(cursor_, 1), count - 1);
|
|
469
|
-
while (upper > 1 && progress < frames_[upper - 1].offset) upper -= 1;
|
|
470
|
-
while (upper < count - 1 && progress > frames_[upper].offset) upper += 1;
|
|
471
|
-
cursor_ = upper;
|
|
472
|
-
|
|
473
|
-
const Keyframe &lower = frames_[upper - 1];
|
|
474
|
-
const Keyframe &higher = frames_[upper];
|
|
475
|
-
const double span = higher.offset - lower.offset;
|
|
476
|
-
if (span <= 0) return higher.presentation;
|
|
477
|
-
|
|
478
|
-
const double t = clamp01((progress - lower.offset) / span);
|
|
479
|
-
const double t2 = t * t;
|
|
480
|
-
const double t3 = t2 * t;
|
|
481
|
-
// Cubic Hermite basis.
|
|
482
|
-
const double h00 = 2 * t3 - 3 * t2 + 1;
|
|
483
|
-
const double h10 = t3 - 2 * t2 + t;
|
|
484
|
-
const double h01 = -2 * t3 + 3 * t2;
|
|
485
|
-
const double h11 = t3 - t2;
|
|
486
|
-
|
|
487
|
-
const Channels from = toChannels(lower.presentation);
|
|
488
|
-
const Channels to = toChannels(higher.presentation);
|
|
489
|
-
const Channels &fromSlope = tangents_[upper - 1];
|
|
490
|
-
const Channels &toSlope = tangents_[upper];
|
|
491
|
-
Channels blended{};
|
|
492
|
-
for (std::size_t channel = 0; channel < kChannelCount; channel += 1) {
|
|
493
|
-
blended[channel] = h00 * from[channel] +
|
|
494
|
-
h10 * span * fromSlope[channel] + h01 * to[channel] +
|
|
495
|
-
h11 * span * toSlope[channel];
|
|
496
|
-
}
|
|
497
|
-
return fromChannels(blended);
|
|
498
|
-
}
|
|
268
|
+
inline bool isValidTiming(const TimingAnimation &animation) {
|
|
269
|
+
return std::isfinite(animation.durationMs) && animation.durationMs >= 0 &&
|
|
270
|
+
std::isfinite(animation.controlPoint1X) &&
|
|
271
|
+
std::isfinite(animation.controlPoint1Y) &&
|
|
272
|
+
std::isfinite(animation.controlPoint2X) &&
|
|
273
|
+
std::isfinite(animation.controlPoint2Y) &&
|
|
274
|
+
animation.controlPoint1X >= 0 && animation.controlPoint1X <= 1 &&
|
|
275
|
+
animation.controlPoint2X >= 0 && animation.controlPoint2X <= 1 &&
|
|
276
|
+
isValidReduceMotionCode(animation.reduceMotion);
|
|
277
|
+
}
|
|
499
278
|
|
|
500
|
-
|
|
501
|
-
std::
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
279
|
+
inline bool isValidSpring(const SpringAnimation &animation) {
|
|
280
|
+
return std::isfinite(animation.mass) && animation.mass > 0 &&
|
|
281
|
+
std::isfinite(animation.stiffness) && animation.stiffness > 0 &&
|
|
282
|
+
std::isfinite(animation.damping) && animation.damping >= 0 &&
|
|
283
|
+
std::isfinite(animation.initialVelocity) &&
|
|
284
|
+
std::isfinite(animation.energyThreshold) &&
|
|
285
|
+
animation.energyThreshold > 0 &&
|
|
286
|
+
isValidReduceMotionCode(animation.reduceMotion);
|
|
287
|
+
}
|
|
505
288
|
|
|
506
|
-
struct
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
double durationMs;
|
|
289
|
+
struct ScalarSpringState {
|
|
290
|
+
double position;
|
|
291
|
+
double velocity;
|
|
510
292
|
};
|
|
511
293
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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,
|
|
333
|
+
const SpringAnimation &animation) {
|
|
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;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
inline bool springScaleStaysPositive(
|
|
345
|
+
const Presentation &start,
|
|
520
346
|
const Presentation &target,
|
|
521
|
-
|
|
522
|
-
double
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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;
|
|
533
389
|
}
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
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);
|
|
534
401
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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;
|
|
541
413
|
}
|
|
542
|
-
|
|
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);
|
|
543
420
|
}
|
|
544
421
|
|
|
545
|
-
inline
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
422
|
+
inline Presentation fromChannels(
|
|
423
|
+
const Channels &channels,
|
|
424
|
+
ClipCurve curve = ClipCurve::Circular,
|
|
425
|
+
bool shadowEnabled = false) {
|
|
426
|
+
Geometry geometry{
|
|
427
|
+
channels[0], channels[1], channels[2], channels[3], 0.0};
|
|
428
|
+
geometry.topLeftRadius = channels[4];
|
|
429
|
+
geometry.topRightRadius = channels[5];
|
|
430
|
+
geometry.bottomRightRadius = channels[6];
|
|
431
|
+
geometry.bottomLeftRadius = channels[7];
|
|
432
|
+
geometry.radius = radiiAreUniform(
|
|
433
|
+
channels[4], channels[5], channels[6], channels[7])
|
|
434
|
+
? channels[4]
|
|
435
|
+
: 0.0;
|
|
436
|
+
geometry.curve = curve;
|
|
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};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
inline Presentation interpolate(
|
|
452
|
+
const Presentation &from,
|
|
453
|
+
const Presentation &to,
|
|
454
|
+
double progress) {
|
|
455
|
+
const auto mix = [progress](double start, double end) {
|
|
456
|
+
return start + (end - start) * progress;
|
|
457
|
+
};
|
|
458
|
+
Channels blended{};
|
|
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) {
|
|
466
|
+
blended[index] = mix(fromChannelsValue[index], toChannelsValue[index]);
|
|
467
|
+
}
|
|
468
|
+
if (!rendersShadow) {
|
|
469
|
+
std::copy(
|
|
470
|
+
fromChannelsValue.begin() + kBaseChannelCount,
|
|
471
|
+
fromChannelsValue.end(),
|
|
472
|
+
blended.begin() + kBaseChannelCount);
|
|
473
|
+
}
|
|
474
|
+
const bool shadowEnabled = progress >= 1
|
|
475
|
+
? to.shadow.enabled
|
|
476
|
+
: rendersShadow;
|
|
477
|
+
return fromChannels(blended, from.clip.curve, shadowEnabled);
|
|
558
478
|
}
|
|
559
479
|
|
|
560
480
|
} // namespace smoothclip
|