@effect-motion/three 0.5.0 → 0.6.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,58 @@
1
+ import type { Node } from "three/webgpu";
2
+ import type { Pass } from "./PostProcessing.js";
3
+ /** A float parameter: a plain number or a `uniform()` node. */
4
+ export type FloatParam = number | (Node & {
5
+ value: number;
6
+ });
7
+ export interface DepthAwareDofOptions {
8
+ focusDistance: FloatParam;
9
+ aperture: FloatParam;
10
+ maxBlurPx?: FloatParam;
11
+ /**
12
+ * Gather taps per pixel for the near and far fields (default 64 / 48). Fewer
13
+ * taps cost proportionally less GPU time for more sample noise; the blur
14
+ * shape and edges are unchanged. Fixed when the node is built.
15
+ */
16
+ taps?: {
17
+ readonly near: number;
18
+ readonly far: number;
19
+ };
20
+ }
21
+ /** The output node, with its parameters as uniforms (set `.value`). */
22
+ export type DepthAwareDofNode = Node & {
23
+ focusDistance: Node & {
24
+ value: number;
25
+ };
26
+ aperture: Node & {
27
+ value: number;
28
+ };
29
+ maxBlurPx: Node & {
30
+ value: number;
31
+ };
32
+ };
33
+ /** Screen px per world unit on the focus plane (perspective camera). */
34
+ export declare function pxPerUnitAtFocus(camera: {
35
+ fov: number;
36
+ zoom: number;
37
+ }, focusDistance: number, height: number): number;
38
+ /**
39
+ * Physical thin-lens CoC radius (px) at view distance d = −viewZ: a lens of
40
+ * radius `aperture` focused at `focusDistance` images the point as a disc of
41
+ * world radius aperture · |d − focus| / d on the focus plane, projected by
42
+ * `pxPerUnit` ({@link pxPerUnitAtFocus}). Near blur grows without bound as
43
+ * d → 0; far blur saturates at aperture · pxPerUnit.
44
+ */
45
+ export declare function cocRadiusPx(d: number, focusDistance: number, aperture: number, pxPerUnit: number): number;
46
+ /**
47
+ * Depth-aware gather DOF on a continuous per-pixel signed CoC s (px, < 0 near).
48
+ *
49
+ * - Near field: scatter-as-gather over a disc sized by the dilated near-CoC
50
+ * tile max, so blurred foreground spreads over sharp pixels behind it. Each
51
+ * near tap covers R²/N px and spreads over π c², giving coverage α.
52
+ * - Far field: disc of the pixel's own CoC; a tap reaches with min(s_q, s_p),
53
+ * so sharp (s ≈ 0) neighbours mask themselves out and the rest is
54
+ * renormalized — the hidden background is filled from its visible part.
55
+ * - Hidden surfaces come from a depth-peeled second layer: behind near pixels,
56
+ * and behind a missed front tap across a depth jump.
57
+ */
58
+ export declare function depthAwareDof(pass: Pass, { focusDistance, aperture, maxBlurPx, taps: { near: nearTaps, far: farTaps }, }: DepthAwareDofOptions): DepthAwareDofNode;
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Depth-aware depth of field for three.js WebGPU: a drop-in alternative to
3
+ * three's `dof()` with a physical thin-lens circle of confusion (CoC).
4
+ *
5
+ * @remarks
6
+ * Ported verbatim (algorithm and constants) from the standalone
7
+ * `DepthAwareDofNode.ts` prototype; only typing and lint conventions differ,
8
+ * plus one fix: the near gather reaches nearR + 0.5 and the centre tap's
9
+ * coverage no longer double-counts, so opaque near surfaces at a few px of
10
+ * CoC stay opaque.
11
+ * Exposed as `PostProcessing.depthAwareDof`.
12
+ *
13
+ * ```ts
14
+ * const scenePass = PostProcessing.pass(scene, camera);
15
+ * const dofNode = PostProcessing.depthAwareDof(scenePass, {
16
+ * focusDistance: 500, // world units along the view axis
17
+ * aperture: 12.5, // lens radius, world units
18
+ * });
19
+ * const pipeline = PostProcessing.makePipeline(renderer, dofNode);
20
+ * dofNode.focusDistance.value = 300; // parameters are uniforms: change any time
21
+ * ```
22
+ *
23
+ * Parameters (each a number or a float `uniform()`; exposed as uniforms on the
24
+ * returned node):
25
+ * - `focusDistance`: distance of the sharp plane, world units.
26
+ * - `aperture`: lens radius, world units. A point at distance d blurs to a
27
+ * disc of radius aperture · |d − focus| / d · H / (2 · focus · tan(fov / 2))
28
+ * px (H = render height in px), see {@link cocRadiusPx}.
29
+ * - `maxBlurPx` (default 30): CoC radius cap in render-target px. Keep it
30
+ * ≤ (REACH − 1) · TILE = 48, the reach of the near-field tile dilation.
31
+ * The uncapped CoC scales with H, so it looks the same at any pixel ratio;
32
+ * the cap does not (at DPR 2, 30 caps at 15 CSS px). It is not scaled by
33
+ * DPR internally because 30 · 2 would pass the 48 px limit.
34
+ * - `taps` (default near 64 / far 48, plain numbers fixed at build): the gather
35
+ * cost is about linear in them; halving trades GPU time for sample noise.
36
+ *
37
+ * Requirements: WebGPURenderer + RenderPipeline, a PerspectiveCamera, opaque
38
+ * geometry. Resize, pixel ratio and camera near/far/fov/zoom changes are
39
+ * picked up every frame; no rebuild is needed. The node takes over the given
40
+ * pass's rendering (it renders under the node's context), so give it a pass
41
+ * of its own.
42
+ *
43
+ * Known limits:
44
+ * - Transparent surfaces are not handled (the CoC comes from the depth buffer).
45
+ * - The scene is rendered twice per frame: the second, depth-peeled pass finds
46
+ * surfaces hidden behind near / sharp objects (≈ 20–50% of the effect cost).
47
+ * Both passes share their render objects, so the second costs draw calls,
48
+ * not a per-object rebuild.
49
+ * - A nearer blurred surface still composites with a hard step over a surface
50
+ * whose own CoC is > 1 px (a depth-gap split would fix it).
51
+ */
52
+ import * as TSL from "three/tsl";
53
+ import { FloatType, HalfFloatType, NearestFilter, NodeUpdateType, Vector3, } from "three/webgpu";
54
+ // Shallow signatures: full three/tsl overloads hang tsc.
55
+ const t = TSL;
56
+ /** Near-CoC tile size (px) and dilation reach (tiles): covers ≥ (REACH − 1) · TILE px. */
57
+ const TILE = 16;
58
+ const REACH = 4;
59
+ /** A source never spreads over less than ~one pixel (π·0.5² ≈ 0.8 px²). */
60
+ const MIN_AREA = 0.25;
61
+ /** Relative view-depth gap a fragment needs to count as behind the first layer. */
62
+ const PEEL_EPS = 1e-3;
63
+ const GOLDEN = Math.PI * (3 - Math.sqrt(5));
64
+ /** Screen px per world unit on the focus plane (perspective camera). */
65
+ export function pxPerUnitAtFocus(camera, focusDistance, height) {
66
+ return (height /
67
+ (2 * focusDistance * (Math.tan((camera.fov * Math.PI) / 360) / camera.zoom)));
68
+ }
69
+ /**
70
+ * Physical thin-lens CoC radius (px) at view distance d = −viewZ: a lens of
71
+ * radius `aperture` focused at `focusDistance` images the point as a disc of
72
+ * world radius aperture · |d − focus| / d on the focus plane, projected by
73
+ * `pxPerUnit` ({@link pxPerUnitAtFocus}). Near blur grows without bound as
74
+ * d → 0; far blur saturates at aperture · pxPerUnit.
75
+ */
76
+ export function cocRadiusPx(d, focusDistance, aperture, pxPerUnit) {
77
+ return ((aperture * Math.abs(d - focusDistance)) / d) * pxPerUnit;
78
+ }
79
+ const asUniform = (p) => (typeof p === "number" ? t.uniform(p) : p);
80
+ function packedTarget(node, scale = 1, type = HalfFloatType) {
81
+ const tex = t.rtt(node, null, null, { type });
82
+ tex.setResolutionScale(scale);
83
+ tex.updateBeforeType = NodeUpdateType.FRAME; // once per frame, not per consumer
84
+ tex.renderTarget.texture.minFilter = NearestFilter;
85
+ tex.renderTarget.texture.magFilter = NearestFilter;
86
+ return tex;
87
+ }
88
+ /** Vogel disc tap i of n at radius R: [screen offset px, radius px]. */
89
+ function vogel(i, n, radius, rot) {
90
+ // Unit-disc taps precomputed (x, y, r): no per-tap sqrt / sincos.
91
+ const taps = Array.from({ length: n }, (_, k) => {
92
+ const r = Math.sqrt((k + 0.5) / n);
93
+ return new Vector3(r * Math.cos(k * GOLDEN), r * Math.sin(k * GOLDEN), r);
94
+ });
95
+ const tap = t.uniformArray(taps, "vec3").element(i);
96
+ const off = t.vec2(tap.x.mul(rot.x).sub(tap.y.mul(rot.y)), tap.x.mul(rot.y).add(tap.y.mul(rot.x)));
97
+ return [off.mul(radius), tap.z.mul(radius)];
98
+ }
99
+ /**
100
+ * Depth-aware gather DOF on a continuous per-pixel signed CoC s (px, < 0 near).
101
+ *
102
+ * - Near field: scatter-as-gather over a disc sized by the dilated near-CoC
103
+ * tile max, so blurred foreground spreads over sharp pixels behind it. Each
104
+ * near tap covers R²/N px and spreads over π c², giving coverage α.
105
+ * - Far field: disc of the pixel's own CoC; a tap reaches with min(s_q, s_p),
106
+ * so sharp (s ≈ 0) neighbours mask themselves out and the rest is
107
+ * renormalized — the hidden background is filled from its visible part.
108
+ * - Hidden surfaces come from a depth-peeled second layer: behind near pixels,
109
+ * and behind a missed front tap across a depth jump.
110
+ */
111
+ export function depthAwareDof(pass, { focusDistance, aperture, maxBlurPx = 30, taps: { near: nearTaps, far: farTaps } = { near: 64, far: 48 }, }) {
112
+ const scenePass = pass["~three.pass"];
113
+ const focus = asUniform(focusDistance);
114
+ const lensRadius = asUniform(aperture);
115
+ const maxBlur = asUniform(maxBlurPx);
116
+ const camera = scenePass.camera;
117
+ // CoC px per unit of |d − focus| / d, as a fraction of maxBlur. Per render:
118
+ // follows resize / pixel ratio (the pass target) and camera fov / zoom.
119
+ const cocScale = t
120
+ .uniform(0)
121
+ .onRenderUpdate(() => (lensRadius.value *
122
+ pxPerUnitAtFocus(camera, focus.value, scenePass.renderTarget.height)) /
123
+ maxBlur.value);
124
+ const side = (d, far) => t.min(t
125
+ .max(far ? d.sub(focus) : focus.sub(d), 0)
126
+ .div(d)
127
+ .mul(cocScale), 1);
128
+ const beauty = scenePass.getTextureNode();
129
+ const distance = t.negate(scenePass.getViewZNode());
130
+ const signedCoc = (d) => side(d, true).sub(side(d, false)).mul(maxBlur);
131
+ const signed = signedCoc(distance);
132
+ const packed = packedTarget(t.vec4(beauty.rgb, signed));
133
+ // Second depth layer: re-render, discarding fragments at or in front of the
134
+ // first layer, so hidden surfaces behind near / sharp objects are known.
135
+ // Fragments within 1 px CoC of the first layer are the same surface (e.g. a
136
+ // Line2's overlapping segment caps) and are peeled too, else it counts twice.
137
+ // The front pass can't sample its own depth attachment, so the peel reads a
138
+ // float copy (exact: same size, nearest).
139
+ const frontDepth = packedTarget(scenePass.getTextureNode("depth").sample(t.screenUV), 1, FloatType);
140
+ // Front pass and peel render with ONE context node, the peel test switched
141
+ // by a uniform: three keys render objects by context node, so two nodes
142
+ // would rebuild every scene object's render object twice a frame (CPU-bound
143
+ // with a few hundred entities). Giving the peel render objects of its own
144
+ // instead made output depend on earlier frames (see the render-history test
145
+ // in packages/renderer); shared ones are the front pass's, rendered anyway.
146
+ const back = t.pass(scenePass.scene, scenePass.camera);
147
+ const peeling = t.uniform(0);
148
+ const getOutput = (output) => {
149
+ // A plain texture node, and a new one per material: the rtt node would
150
+ // trigger the copy nested inside the front render, and three caches a
151
+ // texture node's binding across materials, so after a resize only the
152
+ // first render object sharing it would rebind (the rest keep sampling
153
+ // the destroyed texture).
154
+ const frontZ = t.perspectiveDepthToViewZ(t.texture(frontDepth.renderTarget.texture).sample(t.screenUV).x, t.cameraNear, t.cameraFar);
155
+ // Rasterized depth, not positionView: Line2's positionView is its unit
156
+ // quad. fragCoord is declared by the screenUV lookup above.
157
+ const fragZ = t.perspectiveDepthToViewZ(t.builtin("fragCoord.z"), t.cameraNear, t.cameraFar);
158
+ t.Discard(peeling
159
+ .greaterThan(0.5)
160
+ .and(fragZ
161
+ .greaterThan(frontZ.mul(1 + PEEL_EPS))
162
+ .or(t
163
+ .abs(signedCoc(t.negate(fragZ)).sub(signedCoc(t.negate(frontZ))))
164
+ .lessThan(1))));
165
+ return output;
166
+ };
167
+ // Same merge as PassNode's own contextNode: the renderer's flow data first.
168
+ // ponytail: three 0.185 skips a render object's uniform / texture updates
169
+ // unless its MATERIAL holds nodes (NodeMaterialObserver.containsNode), and
170
+ // nodes injected through getOutput don't count, so plain materials would
171
+ // keep a stale `peeling` and the depth copy's destroyed texture (what broke
172
+ // the earlier separate-peel attempt). A context `modelViewMatrix` counts; set
173
+ // to three's own default it changes no shader. Drop it once three checks
174
+ // context nodes too.
175
+ let shared;
176
+ const renderWith = (p, peel) => {
177
+ const updateBefore = p.updateBefore.bind(p);
178
+ p.updateBefore = (frame) => {
179
+ const renderer = frame.renderer;
180
+ const base = renderer.contextNode;
181
+ if (shared?.base !== base || shared.version !== base.version) {
182
+ const node = t.context({
183
+ modelViewMatrix: t.mediumpModelViewMatrix,
184
+ ...base.getFlowContextData(),
185
+ getOutput,
186
+ });
187
+ shared = { base, version: base.version, node };
188
+ }
189
+ renderer.contextNode = shared.node;
190
+ peeling.value = peel;
191
+ const result = updateBefore(frame);
192
+ peeling.value = 0;
193
+ renderer.contextNode = base;
194
+ return result;
195
+ };
196
+ };
197
+ renderWith(scenePass, 0);
198
+ renderWith(back, 1);
199
+ // `packed` and the depth copy first so the front pass renders (once per
200
+ // frame) and is copied before the peel; otherwise the peel would trigger
201
+ // them nested, inside the peel context.
202
+ const backPacked = packedTarget(t.vec4(packed
203
+ .sample(t.uv())
204
+ .a.add(frontDepth.sample(t.uv()).x)
205
+ .mul(0)
206
+ .add(back.getTextureNode().rgb), signedCoc(t.negate(back.getViewZNode()))));
207
+ const texel = t.vec2(1).div(t.vec2(t.textureSize(packed)));
208
+ // Near CoC max per TILE² block (two 4×4 max reductions: short serial loops
209
+ // keep the low-res passes from being latency-bound), then dilated below.
210
+ const maxDown = (src, value, scale) => {
211
+ const srcTexel = t.vec2(1).div(t.vec2(t.textureSize(src)));
212
+ return packedTarget(t.Fn(() => {
213
+ const m = t.float(0).toVar();
214
+ t.Loop(4, 4, ({ i, j }) => {
215
+ const off = t.vec2(t.float(i), t.float(j)).sub(1.5);
216
+ m.assign(t.max(m, value(src.sample(t.uv().add(off.mul(srcTexel))))));
217
+ });
218
+ return m;
219
+ })(), scale);
220
+ };
221
+ const tileMax = maxDown(maxDown(packed, (q) => t.negate(q.a), 1 / 4), (q) => q.x, 1 / TILE);
222
+ const tileTexel = t.vec2(1).div(t.vec2(t.textureSize(tileMax)));
223
+ const nearTile = packedTarget(t.Fn(() => {
224
+ const m = t.float(0).toVar();
225
+ const n = 2 * REACH + 1;
226
+ t.Loop(n, n, ({ i, j }) => {
227
+ const off = t.vec2(t.float(i), t.float(j)).sub(REACH);
228
+ m.assign(t.max(m, tileMax.sample(t.uv().add(off.mul(tileTexel))).x));
229
+ });
230
+ return m;
231
+ })(), 1 / TILE);
232
+ const output = t
233
+ .Fn(() => {
234
+ const uv = t.uv();
235
+ const center = packed.sample(uv);
236
+ // Per-pixel pattern rotation (interleaved gradient noise): trades the
237
+ // tap pattern's banding for fine noise.
238
+ const px = uv.mul(t.vec2(t.textureSize(packed)));
239
+ const ign = t.fract(t.fract(px.x.mul(0.06711056).add(px.y.mul(0.00583715))).mul(52.9829189));
240
+ const angle = ign.mul(2 * Math.PI);
241
+ const rot = t.vec2(t.cos(angle), t.sin(angle));
242
+ const sP = center.a;
243
+ // Near field
244
+ const nearR = nearTile.sample(uv).x;
245
+ // A tap's reach ramps out at r = c + 0.5, so gather that far: cut at
246
+ // nearR, the ramp's outer half is lost and α ≈ 1 − 0.25 / c inside an
247
+ // opaque near surface (see-through at a few px of CoC).
248
+ const gatherR = nearR.add(0.5);
249
+ const tapArea = gatherR.mul(gatherR).div(nearTaps);
250
+ const spread = (c) => t.max(t.max(c.mul(c), tapArea), MIN_AREA);
251
+ // Centre tap: a near pixel's own colour counts in its near average.
252
+ // Only a surface blurred by more than 1 px counts as near: a barely-near
253
+ // one is in focus (CoC < 1 px), so nearer blur composites over it via the
254
+ // far path instead of being normalized together with its centre tap.
255
+ // ponytail: a hard 1 px cut; a [0.5, 1.5] ramp scored slightly worse.
256
+ const nearP = sP.lessThan(-1);
257
+ const cP = t.max(t.negate(sP), 0);
258
+ const w0 = t.select(nearP, t.float(1).div(spread(cP)), 0);
259
+ const nearSum = center.rgb.mul(w0).toVar();
260
+ const nearW = w0.toVar();
261
+ // The centre tap's coverage is only what the disc taps under-sample:
262
+ // spaced for gatherR, they resolve p's own disc (area ∝ cP²) when
263
+ // gatherR ≈ cP but miss it when a bigger nearby CoC dilated the tile.
264
+ // A flat extra term would double-count and bulge α at edges.
265
+ const alphaSum = w0
266
+ .mul(t.max(tapArea.sub(cP.mul(cP).div(nearTaps)), 0))
267
+ .toVar();
268
+ t.If(nearR.greaterThan(0.5), () => {
269
+ t.Loop(nearTaps, ({ i }) => {
270
+ const [off, r] = vogel(i, nearTaps, gatherR, rot);
271
+ const q = packed.sample(uv.add(off.mul(texel)));
272
+ const c = t.negate(q.a);
273
+ const isNear = c.greaterThan(0);
274
+ const reach = t.clamp(c.sub(r).add(0.5), 0, 1);
275
+ const w = t.select(isNear, reach.div(spread(c)), 0);
276
+ nearSum.addAssign(q.rgb.mul(w));
277
+ nearW.addAssign(w);
278
+ alphaSum.addAssign(w.mul(tapArea));
279
+ });
280
+ });
281
+ const alpha = t.min(alphaSum, 1);
282
+ const nearColor = nearSum.div(t.max(nearW, 1e-6));
283
+ // Far / focus field of the scene with near surfaces peeled away: per
284
+ // tap, the front layer if it reaches p, else the layer hidden behind it.
285
+ // Layer 2 can itself be near (a line behind a nearer disc): it is then
286
+ // blurred by its own CoC |sB| too, near layer-2 taps included.
287
+ const bP = t.select(nearP, backPacked.sample(uv), center);
288
+ const sB = bP.a;
289
+ const cB = t.abs(sB);
290
+ const farColor = bP.rgb.toVar();
291
+ t.If(cB.greaterThan(0.5), () => {
292
+ const farArea = t.max(cB.mul(cB).div(farTaps), MIN_AREA);
293
+ const spreadF = (c) => t.max(c.mul(c), farArea);
294
+ const sum = bP.rgb.div(spreadF(cB)).toVar();
295
+ const wSum = t.float(1).div(spreadF(cB)).toVar();
296
+ t.Loop(farTaps, ({ i }) => {
297
+ const [off, r] = vogel(i, farTaps, cB, rot);
298
+ const quv = uv.add(off.mul(texel));
299
+ const q1 = packed.sample(quv);
300
+ const c1 = t.min(t.abs(q1.a), cB);
301
+ // In front of p's hidden surface (left to the near field): any near
302
+ // tap, or when that surface is itself near, a clearly nearer one.
303
+ const inFront = q1.a.lessThan(t.select(sB.lessThan(0), sB.sub(1), 0));
304
+ const reach1 = t.select(inFront, 0, t.clamp(c1.sub(r).add(0.5), 0, 1));
305
+ const w1 = reach1.div(spreadF(c1));
306
+ sum.addAssign(q1.rgb.mul(w1));
307
+ wSum.addAssign(w1);
308
+ // A missed front tap only reveals layer 2 across a depth jump; on a
309
+ // continuous surface (s changes slowly with r) the ray hits it anyway.
310
+ const jump = t.select(inFront, 1, t.clamp(cB.sub(c1).div(t.max(r, 1)).sub(0.25).mul(4), 0, 1));
311
+ const miss = t.float(1).sub(reach1).mul(jump);
312
+ t.If(miss.greaterThan(0), () => {
313
+ const q2 = backPacked.sample(quv);
314
+ const c2 = t.min(t.abs(q2.a), cB);
315
+ const reach2 = t.select(q2.a.lessThan(0).and(sB.greaterThan(0)), 0, t.clamp(c2.sub(r).add(0.5), 0, 1));
316
+ const w2 = miss.mul(reach2).div(spreadF(c2));
317
+ sum.addAssign(q2.rgb.mul(w2));
318
+ wSum.addAssign(w2);
319
+ });
320
+ });
321
+ farColor.assign(sum.div(t.max(wSum, 1e-6)));
322
+ });
323
+ return t.vec4(t.mix(farColor, nearColor, alpha), 1);
324
+ })()
325
+ .toInspector("Composite");
326
+ return Object.assign(output, {
327
+ focusDistance: focus,
328
+ aperture: lensRadius,
329
+ maxBlurPx: maxBlur,
330
+ });
331
+ }
@@ -109,4 +109,5 @@ export declare const makePipeline: (renderer: Renderer.Renderer, outputNode: unk
109
109
  * renders the passes the graph depends on and applies the output transform.
110
110
  */
111
111
  export declare const render: (self: RenderPipeline) => Effect.Effect<void, ThreeException>;
112
+ export { cocRadiusPx, type DepthAwareDofNode, type DepthAwareDofOptions, depthAwareDof, type FloatParam, pxPerUnitAtFocus, } from "./DepthAwareDof.js";
112
113
  export { uniform };
@@ -99,4 +99,5 @@ export const makePipeline = (renderer, outputNode) => {
99
99
  * renders the passes the graph depends on and applies the output transform.
100
100
  */
101
101
  export const render = (self) => wrap("RenderPipeline.render", () => self["~three.renderPipeline"].render());
102
+ export { cocRadiusPx, depthAwareDof, pxPerUnitAtFocus, } from "./DepthAwareDof.js";
102
103
  export { uniform };
package/package.json CHANGED
@@ -1,57 +1,57 @@
1
1
  {
2
- "name": "@effect-motion/three",
3
- "version": "0.5.0",
4
- "description": "Bindings-only Effect wrapper over three.js for effect-motion",
5
- "type": "module",
6
- "license": "MIT",
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/julia-script/effect-motion.git",
10
- "directory": "packages/three"
11
- },
12
- "homepage": "https://github.com/julia-script/effect-motion#readme",
13
- "bugs": "https://github.com/julia-script/effect-motion/issues",
14
- "keywords": [
15
- "effect",
16
- "motion",
17
- "three",
18
- "webgpu",
19
- "motion-graphics"
20
- ],
21
- "exports": {
22
- ".": {
23
- "types": "./dist/index.d.ts",
24
- "default": "./dist/index.js"
25
- },
26
- "./*": {
27
- "types": "./dist/*.d.ts",
28
- "default": "./dist/*.js"
29
- }
30
- },
31
- "files": [
32
- "dist"
33
- ],
34
- "publishConfig": {
35
- "access": "public"
36
- },
37
- "dependencies": {
38
- "@types/three": "^0.185.1",
39
- "three": "^0.185.1",
40
- "webgpu": "^0.4.0"
41
- },
42
- "peerDependencies": {
43
- "effect": ">=4.0.0-beta.98"
44
- },
45
- "devDependencies": {
46
- "@types/node": "^26.1.1",
47
- "effect": "4.0.0-beta.98",
48
- "typescript": "^7.0.2",
49
- "vitest": "^4.1.10"
50
- },
51
- "scripts": {
52
- "build": "tsc -p tsconfig.build.json",
53
- "dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput",
54
- "test": "vitest run --passWithNoTests",
55
- "check": "tsc --noEmit"
56
- }
57
- }
2
+ "name": "@effect-motion/three",
3
+ "version": "0.6.0",
4
+ "description": "Bindings-only Effect wrapper over three.js for effect-motion",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/julia-script/effect-motion.git",
10
+ "directory": "packages/three"
11
+ },
12
+ "homepage": "https://github.com/julia-script/effect-motion#readme",
13
+ "bugs": "https://github.com/julia-script/effect-motion/issues",
14
+ "keywords": [
15
+ "effect",
16
+ "motion",
17
+ "three",
18
+ "webgpu",
19
+ "motion-graphics"
20
+ ],
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./*": {
27
+ "types": "./dist/*.d.ts",
28
+ "default": "./dist/*.js"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "scripts": {
35
+ "build": "tsc -p tsconfig.build.json",
36
+ "dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput",
37
+ "test": "vitest run --passWithNoTests",
38
+ "check": "tsc --noEmit"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "dependencies": {
44
+ "@types/three": "^0.185.1",
45
+ "three": "^0.185.1",
46
+ "webgpu": "^0.4.0"
47
+ },
48
+ "peerDependencies": {
49
+ "effect": ">=4.0.0-rc.115"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^26.1.1",
53
+ "effect": "4.0.0-rc.115",
54
+ "typescript": "^7.0.2",
55
+ "vitest": "^4.1.10"
56
+ }
57
+ }