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.
- package/dist/cjs/three.js +192 -0
- package/dist/cjs/vgpu.js +248 -0
- package/dist/es/three.mjs +188 -0
- package/dist/es/vgpu.mjs +244 -0
- package/dist/motion.dev.js +179 -158
- package/dist/motion.js +1 -1
- package/dist/three.d.ts +29 -0
- package/dist/vgpu.d.ts +25 -0
- package/package.json +16 -3
package/dist/es/vgpu.mjs
ADDED
|
@@ -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 };
|