motion 13.1.1-alpha.0 → 13.2.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.
@@ -0,0 +1,244 @@
1
+ import { createEffect, frame, rgba, color, clamp, hslaToRgba } from 'framer-motion/dom';
2
+
3
+ const axes = "xyzw";
4
+ const radiansPerDegree = Math.PI / 180;
5
+ const transforms = {
6
+ x: ["position", 0],
7
+ y: ["position", 1],
8
+ z: ["position", 2],
9
+ rotateX: ["rotation", 0, true],
10
+ rotateY: ["rotation", 1, true],
11
+ rotateZ: ["rotation", 2, true],
12
+ scaleX: ["scale", 0],
13
+ scaleY: ["scale", 1],
14
+ scaleZ: ["scale", 2],
15
+ };
16
+ /**
17
+ * Last vector written to each path. vgpu subjects like Effect and
18
+ * SharedUniforms have no getters, and SceneNode stores rotation as a
19
+ * quaternion, so this is how single components find their siblings.
20
+ */
21
+ const shadows = new WeakMap();
22
+ /**
23
+ * Values changed this frame, flushed into one set() per subject.
24
+ */
25
+ const pending = new Map();
26
+ function flush() {
27
+ pending.forEach(applyValues);
28
+ pending.clear();
29
+ }
30
+ /**
31
+ * Binds motion values to vgpu shared uniforms, Effect/Draw/Compute bindings
32
+ * ("params.time"), scene nodes (x, rotateY, scale), cameras, lights,
33
+ * materials, orbit controls and target clear colors.
34
+ *
35
+ * Register with `animate.addEffect(vgpuEffect)` so `animate()` can target
36
+ * these subjects directly, or call it yourself to wire up existing motion
37
+ * values:
38
+ *
39
+ * ```ts
40
+ * vgpuEffect(wave, { "params.time": time })
41
+ * vgpuEffect(cube, { x, rotateY })
42
+ * ```
43
+ *
44
+ * Changed values are batched into a single set() per subject per frame in
45
+ * `frame.preRender`, ahead of render loops scheduled with `frame.render`.
46
+ */
47
+ const vgpuEffect = createEffect((subject, state, key, value) => state.set(key, value, () => {
48
+ const bag = pending.get(subject) ?? {};
49
+ bag[key] = state.latest[key];
50
+ pending.set(subject, bag);
51
+ frame.preRender(flush, false, true);
52
+ }, undefined, false), {
53
+ test: isVGPUSubject,
54
+ read: readInitial,
55
+ step: frame.preRender,
56
+ });
57
+ /**
58
+ * Claims vgpu shader units (Effect, Draw, Compute), shared uniforms, scene
59
+ * nodes, materials, orbit controls and targets. Everything else, including
60
+ * Three.js vectors and plain objects, is left alone.
61
+ */
62
+ function isVGPUSubject(subject) {
63
+ if (!subject || typeof subject !== "object")
64
+ return false;
65
+ const s = subject;
66
+ return typeof s.set === "function"
67
+ ? typeof s.kind === "string" ||
68
+ "gpu" in s ||
69
+ "reflection" in s ||
70
+ typeof s.yaw === "number"
71
+ : "clearColor" in s && "gpu" in s;
72
+ }
73
+ function isNode(subject) {
74
+ return "quaternion" in subject;
75
+ }
76
+ function isVector(value) {
77
+ return Array.isArray(value) || ArrayBuffer.isView(value);
78
+ }
79
+ function resolveKey(subject, key) {
80
+ const transform = isNode(subject) && transforms[key];
81
+ if (transform) {
82
+ return {
83
+ path: [transform[0]],
84
+ index: transform[1],
85
+ degrees: transform[2],
86
+ };
87
+ }
88
+ const path = key.split(".");
89
+ const last = path[path.length - 1];
90
+ const index = axes.indexOf(last.slice(-1).toLowerCase());
91
+ if (last.length > 1 && index !== -1) {
92
+ const base = [...path.slice(0, -1), last.slice(0, -1)];
93
+ if (getVector(subject, base))
94
+ return { path: base, index };
95
+ }
96
+ return { path };
97
+ }
98
+ function readPath(subject, path) {
99
+ return path.reduce((value, key) => value?.[key], subject);
100
+ }
101
+ /**
102
+ * Reads a subject property, falling back to ShaderMaterial's `values` record.
103
+ */
104
+ function readValue(subject, path) {
105
+ return (readPath(subject, path) ??
106
+ (subject.values && readPath(subject.values, path)));
107
+ }
108
+ function getVector(subject, path) {
109
+ const value = readValue(subject, path);
110
+ if (isVector(value))
111
+ return Array.from(value);
112
+ const shadow = shadows.get(subject)?.[path.join(".")];
113
+ if (shadow)
114
+ return shadow;
115
+ return path[0] === "rotation" && isNode(subject)
116
+ ? eulerFromQuaternion(subject.quaternion)
117
+ : undefined;
118
+ }
119
+ function setShadow(subject, path, vector) {
120
+ const shadow = shadows.get(subject) ?? {};
121
+ shadow[path.join(".")] = vector;
122
+ shadows.set(subject, shadow);
123
+ }
124
+ function setPath(target, path, value) {
125
+ var _a;
126
+ const last = path.length - 1;
127
+ let current = target;
128
+ for (let i = 0; i < last; i++) {
129
+ current = current[_a = path[i]] ?? (current[_a] = {});
130
+ }
131
+ current[path[last]] = value;
132
+ }
133
+ function isColorTarget(target) {
134
+ const sample = Array.isArray(target)
135
+ ? target.find((value) => typeof value === "string")
136
+ : target;
137
+ return typeof sample === "string" && color.test(sample);
138
+ }
139
+ function readInitial(subject, key, target) {
140
+ const { path, index, degrees } = resolveKey(subject, key);
141
+ if (index !== undefined) {
142
+ const value = getVector(subject, path)?.[index];
143
+ return degrees && value !== undefined ? value / radiansPerDegree : value;
144
+ }
145
+ const value = readValue(subject, path);
146
+ if (typeof value === "number")
147
+ return value;
148
+ if (!isVector(value))
149
+ return undefined;
150
+ if (isColorTarget(target))
151
+ return toColorString(value);
152
+ const sample = Array.isArray(target)
153
+ ? target.find((keyframe) => keyframe !== null)
154
+ : target;
155
+ // A numeric target on a vector reads the first component (uniform scale)
156
+ return typeof sample === "string" ? Array.from(value).join(" ") : value[0];
157
+ }
158
+ function applyValues(values, subject) {
159
+ const bag = {};
160
+ const vectors = new Map();
161
+ for (const key in values) {
162
+ const target = resolveKey(subject, key);
163
+ const { path, index, degrees } = target;
164
+ const value = values[key];
165
+ if (index === undefined) {
166
+ const parsed = parseValue(value, getVector(subject, path));
167
+ Array.isArray(parsed) && setShadow(subject, path, parsed);
168
+ setPath(bag, path, parsed);
169
+ continue;
170
+ }
171
+ const id = path.join(".");
172
+ const entry = vectors.get(id) ?? {
173
+ ...target,
174
+ vector: getVector(subject, path) ?? [],
175
+ };
176
+ vectors.set(id, entry);
177
+ entry.vector[index] = degrees
178
+ ? value * radiansPerDegree
179
+ : value;
180
+ }
181
+ vectors.forEach(({ path, vector }) => {
182
+ setShadow(subject, path, vector);
183
+ setPath(bag, path, vector);
184
+ });
185
+ typeof subject.set === "function"
186
+ ? subject.set(bag)
187
+ : Object.assign(subject, bag);
188
+ }
189
+ /**
190
+ * Converts animated strings into vgpu values: CSS colors become linear RGB
191
+ * (matching vgpu/scene's srgb()), "1 0 0" becomes [1, 0, 0].
192
+ */
193
+ function parseValue(value, current) {
194
+ if (typeof value !== "string")
195
+ return value;
196
+ if (color.test(value)) {
197
+ const parsed = color.parse(value);
198
+ const { red, green, blue, alpha = 1, } = "hue" in parsed ? hslaToRgba(parsed) : parsed;
199
+ const linear = [red, green, blue].map((channel) => toLinear(channel / 255));
200
+ return current?.length === 4 ? [...linear, alpha] : linear;
201
+ }
202
+ const parts = value
203
+ .split(",")
204
+ .join(" ")
205
+ .split(" ")
206
+ .filter(Boolean)
207
+ .map(Number);
208
+ return parts.length > 1 && parts.every((part) => !isNaN(part))
209
+ ? parts
210
+ : value;
211
+ }
212
+ function toLinear(channel) {
213
+ return channel <= 0.04045
214
+ ? channel / 12.92
215
+ : ((channel + 0.055) / 1.055) ** 2.4;
216
+ }
217
+ function toSRGB(channel) {
218
+ return channel <= 0.0031308
219
+ ? channel * 12.92
220
+ : 1.055 * channel ** (1 / 2.4) - 0.055;
221
+ }
222
+ function toColorString(linear) {
223
+ const [red, green, blue, alpha = 1] = Array.from(linear).map((channel, i) => i < 3 ? toSRGB(channel) * 255 : channel);
224
+ return rgba.transform({ red, green, blue, alpha });
225
+ }
226
+ /**
227
+ * Inverse of vgpu's quatFromEuler (intrinsic XYZ).
228
+ */
229
+ function eulerFromQuaternion(quaternion) {
230
+ const [x, y, z, w] = Array.from(quaternion);
231
+ const m13 = 2 * (x * z + w * y);
232
+ const singular = Math.abs(m13) > 0.9999999;
233
+ return [
234
+ singular
235
+ ? Math.atan2(2 * (y * z + w * x), 1 - 2 * (x * x + z * z))
236
+ : Math.atan2(-2 * (y * z - w * x), 1 - 2 * (x * x + y * y)),
237
+ Math.asin(clamp(-1, 1, m13)),
238
+ singular
239
+ ? 0
240
+ : Math.atan2(-2 * (x * y - w * z), 1 - 2 * (y * y + z * z)),
241
+ ];
242
+ }
243
+
244
+ export { vgpuEffect };