@effect-motion/three 0.5.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/Interop.d.ts +28 -0
- package/dist/Interop.js +34 -0
- package/dist/Line2.d.ts +42 -0
- package/dist/Line2.js +39 -0
- package/dist/Object3D.d.ts +13 -0
- package/dist/Object3D.js +1 -0
- package/dist/PostProcessing.d.ts +112 -0
- package/dist/PostProcessing.js +102 -0
- package/dist/RenderTarget.d.ts +86 -0
- package/dist/RenderTarget.js +97 -0
- package/dist/Renderer.d.ts +204 -0
- package/dist/Renderer.js +241 -0
- package/dist/Scene.d.ts +105 -0
- package/dist/Scene.js +118 -0
- package/dist/ThreeException.d.ts +24 -0
- package/dist/ThreeException.js +16 -0
- package/dist/Tsl.d.ts +18 -0
- package/dist/Tsl.js +18 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +76 -0
- package/dist/node.d.ts +47 -0
- package/dist/node.js +157 -0
- package/dist/usageDemo.d.ts +16 -0
- package/dist/usageDemo.js +35 -0
- package/package.json +57 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Data } from "effect";
|
|
2
|
+
/**
|
|
3
|
+
* A three.js operation failed.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Every fallible call in this package reports through this one error type,
|
|
7
|
+
* so a caller catches by tag rather than guarding each three API
|
|
8
|
+
* separately. `operation` names which call failed — `"WebGPURenderer.init"`,
|
|
9
|
+
* `"readRenderTargetPixelsAsync"` — and `cause` carries whatever three
|
|
10
|
+
* threw or rejected with.
|
|
11
|
+
*
|
|
12
|
+
* Typical causes are environmental rather than logical: no WebGPU adapter,
|
|
13
|
+
* a lost device, a shader that failed to compile.
|
|
14
|
+
*/
|
|
15
|
+
export class ThreeException extends Data.TaggedError("ThreeException") {
|
|
16
|
+
}
|
package/dist/Tsl.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TSL — three's shading language, for building node materials and shader
|
|
3
|
+
* graphs.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Re-exported so downstream code has a single import root for the subset
|
|
7
|
+
* this project uses, rather than reaching into `three/tsl` from a dozen
|
|
8
|
+
* places.
|
|
9
|
+
*
|
|
10
|
+
* These are pure description: building a node graph runs no GPU work and
|
|
11
|
+
* cannot fail, so nothing here is an Effect. Effect enters when a graph is
|
|
12
|
+
* eventually rendered.
|
|
13
|
+
*
|
|
14
|
+
* Note that node types are deliberately not re-exported — three's published
|
|
15
|
+
* node types expand into unions large enough to stall a type check, so
|
|
16
|
+
* consumers declare the minimal shape they use. See `PostProcessing`.
|
|
17
|
+
*/
|
|
18
|
+
export { attribute, float, fwidth, interleavedGradientNoise, mix, perspectiveDepthToViewZ, positionGeometry, screenCoordinate, screenUV, smoothstep, texture, uniform, vec2, vec3, vec4, } from "three/tsl";
|
package/dist/Tsl.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TSL — three's shading language, for building node materials and shader
|
|
3
|
+
* graphs.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Re-exported so downstream code has a single import root for the subset
|
|
7
|
+
* this project uses, rather than reaching into `three/tsl` from a dozen
|
|
8
|
+
* places.
|
|
9
|
+
*
|
|
10
|
+
* These are pure description: building a node graph runs no GPU work and
|
|
11
|
+
* cannot fail, so nothing here is an Effect. Effect enters when a graph is
|
|
12
|
+
* eventually rendered.
|
|
13
|
+
*
|
|
14
|
+
* Note that node types are deliberately not re-exported — three's published
|
|
15
|
+
* node types expand into unions large enough to stall a type check, so
|
|
16
|
+
* consumers declare the minimal shape they use. See `PostProcessing`.
|
|
17
|
+
*/
|
|
18
|
+
export { attribute, float, fwidth, interleavedGradientNoise, mix, perspectiveDepthToViewZ, positionGeometry, screenCoordinate, screenUV, smoothstep, texture, uniform, vec2, vec3, vec4, } from "three/tsl";
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@effect-motion/three` — an Effect wrapper over three.js.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Bindings only. This package knows nothing about effect-motion — no
|
|
6
|
+
* frames, no entities, no projection — and exists to give three.js the two
|
|
7
|
+
* things Effect code needs from it: resources that clean themselves up, and
|
|
8
|
+
* failures in a typed channel.
|
|
9
|
+
*
|
|
10
|
+
* The organizing rule is **Effect at the seams, raw three in between**:
|
|
11
|
+
*
|
|
12
|
+
* - **Construction and teardown are Effects.** {@link Renderer.make},
|
|
13
|
+
* {@link Scene.make}, and {@link RenderTarget.make} are scoped, so a GPU
|
|
14
|
+
* device, a scene's children, and a render target's memory are released
|
|
15
|
+
* when the scope closes rather than by hand.
|
|
16
|
+
* - **Anything that can fail or is async is an Effect** — rendering,
|
|
17
|
+
* readback, shader compilation — typed as {@link ThreeException}.
|
|
18
|
+
* - **Infallible mutation stays synchronous and chains.** Adding objects,
|
|
19
|
+
* setting a background, resizing: these cannot fail, so wrapping them in
|
|
20
|
+
* Effects would buy ceremony and nothing else. They return their handle
|
|
21
|
+
* and compose with `.pipe`.
|
|
22
|
+
*
|
|
23
|
+
* Handles are branded wrappers around the three object, which stays
|
|
24
|
+
* reachable — `ThreeRaw` re-exports three itself for the leaf value types
|
|
25
|
+
* (geometries, materials, `Vector3`) and the per-frame mutation in a hot
|
|
26
|
+
* path. That escape hatch is deliberate: reaching past a wrapper should be
|
|
27
|
+
* a visible, greppable import rather than the path of least resistance.
|
|
28
|
+
*
|
|
29
|
+
* For headless rendering on a real GPU, import
|
|
30
|
+
* `@effect-motion/three/node` — it lives behind its own subpath so
|
|
31
|
+
* Node-only code never reaches a browser bundle.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* Scoped construction, sync chaining, Effects only where GPU work happens.
|
|
35
|
+
* ```typescript
|
|
36
|
+
* import { Renderer, RenderTarget, Scene } from "@effect-motion/three";
|
|
37
|
+
* import { Mesh, PerspectiveCamera } from "three/webgpu";
|
|
38
|
+
* import { Effect } from "effect";
|
|
39
|
+
*
|
|
40
|
+
* const program = Effect.gen(function* () {
|
|
41
|
+
* const scene = yield* Scene.make();
|
|
42
|
+
* const renderer = yield* Renderer.make({ width: 640, height: 360 });
|
|
43
|
+
* const target = yield* RenderTarget.make(640, 360);
|
|
44
|
+
*
|
|
45
|
+
* scene.pipe(Scene.add([new Mesh()]), Scene.setBackground(null));
|
|
46
|
+
*
|
|
47
|
+
* const camera = new PerspectiveCamera(50, 16 / 9, 1, 1000);
|
|
48
|
+
* Renderer.setRenderTarget(renderer, target);
|
|
49
|
+
* yield* Renderer.render(renderer, scene, camera);
|
|
50
|
+
* return yield* Renderer.readRenderTarget(renderer, target, 640, 360);
|
|
51
|
+
* }).pipe(Effect.scoped);
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @packageDocumentation
|
|
55
|
+
*/
|
|
56
|
+
export * as ThreeRaw from "three/webgpu";
|
|
57
|
+
export * as Interop from "./Interop.js";
|
|
58
|
+
export * as Line2 from "./Line2.js";
|
|
59
|
+
export * as Object3D from "./Object3D.js";
|
|
60
|
+
export * as PostProcessing from "./PostProcessing.js";
|
|
61
|
+
export * as Renderer from "./Renderer.js";
|
|
62
|
+
export * as RenderTarget from "./RenderTarget.js";
|
|
63
|
+
export * as Scene from "./Scene.js";
|
|
64
|
+
export { ThreeException } from "./ThreeException.js";
|
|
65
|
+
export * as Tsl from "./Tsl.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@effect-motion/three` — an Effect wrapper over three.js.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Bindings only. This package knows nothing about effect-motion — no
|
|
6
|
+
* frames, no entities, no projection — and exists to give three.js the two
|
|
7
|
+
* things Effect code needs from it: resources that clean themselves up, and
|
|
8
|
+
* failures in a typed channel.
|
|
9
|
+
*
|
|
10
|
+
* The organizing rule is **Effect at the seams, raw three in between**:
|
|
11
|
+
*
|
|
12
|
+
* - **Construction and teardown are Effects.** {@link Renderer.make},
|
|
13
|
+
* {@link Scene.make}, and {@link RenderTarget.make} are scoped, so a GPU
|
|
14
|
+
* device, a scene's children, and a render target's memory are released
|
|
15
|
+
* when the scope closes rather than by hand.
|
|
16
|
+
* - **Anything that can fail or is async is an Effect** — rendering,
|
|
17
|
+
* readback, shader compilation — typed as {@link ThreeException}.
|
|
18
|
+
* - **Infallible mutation stays synchronous and chains.** Adding objects,
|
|
19
|
+
* setting a background, resizing: these cannot fail, so wrapping them in
|
|
20
|
+
* Effects would buy ceremony and nothing else. They return their handle
|
|
21
|
+
* and compose with `.pipe`.
|
|
22
|
+
*
|
|
23
|
+
* Handles are branded wrappers around the three object, which stays
|
|
24
|
+
* reachable — `ThreeRaw` re-exports three itself for the leaf value types
|
|
25
|
+
* (geometries, materials, `Vector3`) and the per-frame mutation in a hot
|
|
26
|
+
* path. That escape hatch is deliberate: reaching past a wrapper should be
|
|
27
|
+
* a visible, greppable import rather than the path of least resistance.
|
|
28
|
+
*
|
|
29
|
+
* For headless rendering on a real GPU, import
|
|
30
|
+
* `@effect-motion/three/node` — it lives behind its own subpath so
|
|
31
|
+
* Node-only code never reaches a browser bundle.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* Scoped construction, sync chaining, Effects only where GPU work happens.
|
|
35
|
+
* ```typescript
|
|
36
|
+
* import { Renderer, RenderTarget, Scene } from "@effect-motion/three";
|
|
37
|
+
* import { Mesh, PerspectiveCamera } from "three/webgpu";
|
|
38
|
+
* import { Effect } from "effect";
|
|
39
|
+
*
|
|
40
|
+
* const program = Effect.gen(function* () {
|
|
41
|
+
* const scene = yield* Scene.make();
|
|
42
|
+
* const renderer = yield* Renderer.make({ width: 640, height: 360 });
|
|
43
|
+
* const target = yield* RenderTarget.make(640, 360);
|
|
44
|
+
*
|
|
45
|
+
* scene.pipe(Scene.add([new Mesh()]), Scene.setBackground(null));
|
|
46
|
+
*
|
|
47
|
+
* const camera = new PerspectiveCamera(50, 16 / 9, 1, 1000);
|
|
48
|
+
* Renderer.setRenderTarget(renderer, target);
|
|
49
|
+
* yield* Renderer.render(renderer, scene, camera);
|
|
50
|
+
* return yield* Renderer.readRenderTarget(renderer, target, 640, 360);
|
|
51
|
+
* }).pipe(Effect.scoped);
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @packageDocumentation
|
|
55
|
+
*/
|
|
56
|
+
// Browser-safe, bindings-only surface over three.js: branded handles with
|
|
57
|
+
// Effect at the seams (lifecycle, async boundaries, failures), sync
|
|
58
|
+
// chaining for infallible mutation. Knows nothing about effect-motion —
|
|
59
|
+
// no frames, entities, or projection. The Node entry (Dawn device +
|
|
60
|
+
// environment shims) lives at "@effect-motion/three/node" so node-only
|
|
61
|
+
// code never reaches a browser bundle.
|
|
62
|
+
//
|
|
63
|
+
// `ThreeRaw` is the deliberate escape hatch, not the front door: reaching
|
|
64
|
+
// past a wrapper (three's leaf value types, the per-frame object mutation
|
|
65
|
+
// in the renderer's hot path) is a visible, greppable import rather than
|
|
66
|
+
// the path of least resistance. New code should prefer the actors below.
|
|
67
|
+
export * as ThreeRaw from "three/webgpu";
|
|
68
|
+
export * as Interop from "./Interop.js";
|
|
69
|
+
export * as Line2 from "./Line2.js";
|
|
70
|
+
export * as Object3D from "./Object3D.js";
|
|
71
|
+
export * as PostProcessing from "./PostProcessing.js";
|
|
72
|
+
export * as Renderer from "./Renderer.js";
|
|
73
|
+
export * as RenderTarget from "./RenderTarget.js";
|
|
74
|
+
export * as Scene from "./Scene.js";
|
|
75
|
+
export { ThreeException } from "./ThreeException.js";
|
|
76
|
+
export * as Tsl from "./Tsl.js";
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Effect } from "effect";
|
|
2
|
+
import type { ThreeException } from "./ThreeException.js";
|
|
3
|
+
/**
|
|
4
|
+
* Acquire a GPU device from Dawn.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Pass the result to `Renderer.make` rather than letting three request its
|
|
8
|
+
* own. three asks for a "compatibility" feature level, which Chrome quietly
|
|
9
|
+
* upgrades but Dawn honors literally — costing MSAA and other core features,
|
|
10
|
+
* so headless output would be visibly worse than the browser's for the same
|
|
11
|
+
* scene. This requests a core device with every feature the adapter offers.
|
|
12
|
+
*
|
|
13
|
+
* Fails with a `ThreeException` when no adapter is available.
|
|
14
|
+
*/
|
|
15
|
+
export declare const makeDevice: () => Effect.Effect<GPUDevice, ThreeException>;
|
|
16
|
+
/**
|
|
17
|
+
* A canvas-shaped stand-in for headless rendering.
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* three's renderer requires a canvas and a context even when nothing is
|
|
21
|
+
* displayed. These satisfy that without a DOM. The renderer must draw only
|
|
22
|
+
* into render targets: asking this context for the default framebuffer
|
|
23
|
+
* throws, deliberately, rather than silently rendering nowhere.
|
|
24
|
+
*
|
|
25
|
+
* @param width - Buffer width in device pixels.
|
|
26
|
+
* @param height - Buffer height in device pixels.
|
|
27
|
+
*/
|
|
28
|
+
export declare const stubCanvas: (width: number, height: number) => {
|
|
29
|
+
canvas: {
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
style: {};
|
|
33
|
+
addEventListener(): void;
|
|
34
|
+
removeEventListener(): void;
|
|
35
|
+
dispatchEvent(): void;
|
|
36
|
+
getContext: () => {
|
|
37
|
+
configure(): void;
|
|
38
|
+
unconfigure(): void;
|
|
39
|
+
getCurrentTexture(): never;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
context: {
|
|
43
|
+
configure(): void;
|
|
44
|
+
unconfigure(): void;
|
|
45
|
+
getCurrentTexture(): never;
|
|
46
|
+
};
|
|
47
|
+
};
|
package/dist/node.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { create, globals } from "webgpu";
|
|
2
|
+
import { wrapPromise } from "./Interop.js";
|
|
3
|
+
/**
|
|
4
|
+
* Running three.js on a real GPU in Node, with no browser.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* WebGPU comes from Dawn (Chrome's implementation, via the `webgpu` npm
|
|
8
|
+
* bindings). Importing this module installs the browser environment three
|
|
9
|
+
* expects as a TOP-LEVEL SIDE EFFECT: `navigator.gpu`, the WebGPU
|
|
10
|
+
* constants, `self`, a `requestAnimationFrame` shim, and a minimal
|
|
11
|
+
* `XMLHttpRequest` that font loaders need. Import it before creating a
|
|
12
|
+
* renderer.
|
|
13
|
+
*
|
|
14
|
+
* Node-only. Never import it from the browser-safe root entry.
|
|
15
|
+
*
|
|
16
|
+
* Two gotchas worth knowing before your first script:
|
|
17
|
+
*
|
|
18
|
+
* - **Your script will hang on exit.** three's animation loop keeps
|
|
19
|
+
* rescheduling the rAF shim, so Node's event loop never drains. End a
|
|
20
|
+
* standalone script with `process.exit(0)`, or run it under
|
|
21
|
+
* `NodeRuntime.runMain`, which exits explicitly. Test runners and the
|
|
22
|
+
* export pipeline already handle this.
|
|
23
|
+
* - **There is no canvas**, so render into a `RenderTarget` and read it
|
|
24
|
+
* back. {@link stubCanvas} supplies the canvas-shaped object the renderer
|
|
25
|
+
* constructor insists on.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* The headless setup, start to finish.
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import * as NodeThree from "@effect-motion/three/node";
|
|
31
|
+
* import { Renderer, RenderTarget, Scene } from "@effect-motion/three";
|
|
32
|
+
* import { Effect } from "effect";
|
|
33
|
+
*
|
|
34
|
+
* const program = Effect.gen(function* () {
|
|
35
|
+
* const device = yield* NodeThree.makeDevice();
|
|
36
|
+
* const { canvas, context } = NodeThree.stubCanvas(640, 360);
|
|
37
|
+
* const renderer = yield* Renderer.make({
|
|
38
|
+
* canvas: canvas as unknown as HTMLCanvasElement,
|
|
39
|
+
* context: context as never,
|
|
40
|
+
* device,
|
|
41
|
+
* width: 640,
|
|
42
|
+
* height: 360,
|
|
43
|
+
* });
|
|
44
|
+
* const target = yield* RenderTarget.make(640, 360);
|
|
45
|
+
* Renderer.setRenderTarget(renderer, target);
|
|
46
|
+
* yield* Renderer.render(renderer, scene, camera);
|
|
47
|
+
* return yield* Renderer.readRenderTarget(renderer, target, 640, 360);
|
|
48
|
+
* }).pipe(Effect.scoped);
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
// WebGPU constants (GPUBufferUsage etc.) that browser code assumes global
|
|
52
|
+
Object.assign(globalThis, globals);
|
|
53
|
+
// Node ≥ 24 defines `navigator` as a getter-only global — defineProperty
|
|
54
|
+
// over it. Idempotent: skip when a gpu-bearing navigator already exists.
|
|
55
|
+
if (typeof navigator === "undefined" ||
|
|
56
|
+
navigator.gpu === undefined) {
|
|
57
|
+
Object.defineProperty(globalThis, "navigator", {
|
|
58
|
+
value: { gpu: create([]) },
|
|
59
|
+
configurable: true,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
// three's internal Animation loop wants requestAnimationFrame on `self`
|
|
63
|
+
const g = globalThis;
|
|
64
|
+
if (g.self === undefined) {
|
|
65
|
+
g.self = globalThis;
|
|
66
|
+
}
|
|
67
|
+
if (g.requestAnimationFrame === undefined) {
|
|
68
|
+
g.requestAnimationFrame = (cb) => setTimeout(() => cb(performance.now()), 16);
|
|
69
|
+
g.cancelAnimationFrame = (id) => clearTimeout(id);
|
|
70
|
+
}
|
|
71
|
+
// Minimal XMLHttpRequest over fetch: arraybuffer GETs only — what font
|
|
72
|
+
// loaders (troika's Typr path) need. Supports data:/http(s) URIs.
|
|
73
|
+
if (g.XMLHttpRequest === undefined) {
|
|
74
|
+
class NodeXHR {
|
|
75
|
+
responseType = "";
|
|
76
|
+
response = null;
|
|
77
|
+
status = 0;
|
|
78
|
+
statusText = "";
|
|
79
|
+
onload = null;
|
|
80
|
+
onerror = null;
|
|
81
|
+
url = "";
|
|
82
|
+
open(_method, url) {
|
|
83
|
+
this.url = url;
|
|
84
|
+
}
|
|
85
|
+
send() {
|
|
86
|
+
fetch(this.url)
|
|
87
|
+
.then(async (res) => {
|
|
88
|
+
this.status = res.status;
|
|
89
|
+
this.statusText = res.statusText;
|
|
90
|
+
this.response =
|
|
91
|
+
this.responseType === "arraybuffer"
|
|
92
|
+
? await res.arrayBuffer()
|
|
93
|
+
: await res.text();
|
|
94
|
+
this.onload?.();
|
|
95
|
+
})
|
|
96
|
+
.catch((err) => {
|
|
97
|
+
this.onerror?.(err);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
g.XMLHttpRequest = NodeXHR;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Acquire a GPU device from Dawn.
|
|
105
|
+
*
|
|
106
|
+
* @remarks
|
|
107
|
+
* Pass the result to `Renderer.make` rather than letting three request its
|
|
108
|
+
* own. three asks for a "compatibility" feature level, which Chrome quietly
|
|
109
|
+
* upgrades but Dawn honors literally — costing MSAA and other core features,
|
|
110
|
+
* so headless output would be visibly worse than the browser's for the same
|
|
111
|
+
* scene. This requests a core device with every feature the adapter offers.
|
|
112
|
+
*
|
|
113
|
+
* Fails with a `ThreeException` when no adapter is available.
|
|
114
|
+
*/
|
|
115
|
+
export const makeDevice = () => wrapPromise("requestAdapter", async () => {
|
|
116
|
+
const gpu = navigator.gpu;
|
|
117
|
+
const adapter = await gpu.requestAdapter({
|
|
118
|
+
featureLevel: "core",
|
|
119
|
+
});
|
|
120
|
+
if (adapter === null) {
|
|
121
|
+
throw new Error("no WebGPU adapter available (Dawn)");
|
|
122
|
+
}
|
|
123
|
+
return adapter.requestDevice({
|
|
124
|
+
requiredFeatures: [...adapter.features],
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
/**
|
|
128
|
+
* A canvas-shaped stand-in for headless rendering.
|
|
129
|
+
*
|
|
130
|
+
* @remarks
|
|
131
|
+
* three's renderer requires a canvas and a context even when nothing is
|
|
132
|
+
* displayed. These satisfy that without a DOM. The renderer must draw only
|
|
133
|
+
* into render targets: asking this context for the default framebuffer
|
|
134
|
+
* throws, deliberately, rather than silently rendering nowhere.
|
|
135
|
+
*
|
|
136
|
+
* @param width - Buffer width in device pixels.
|
|
137
|
+
* @param height - Buffer height in device pixels.
|
|
138
|
+
*/
|
|
139
|
+
export const stubCanvas = (width, height) => {
|
|
140
|
+
const context = {
|
|
141
|
+
configure() { },
|
|
142
|
+
unconfigure() { },
|
|
143
|
+
getCurrentTexture() {
|
|
144
|
+
throw new Error("default framebuffer used in headless mode");
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
const canvas = {
|
|
148
|
+
width,
|
|
149
|
+
height,
|
|
150
|
+
style: {},
|
|
151
|
+
addEventListener() { },
|
|
152
|
+
removeEventListener() { },
|
|
153
|
+
dispatchEvent() { },
|
|
154
|
+
getContext: () => context,
|
|
155
|
+
};
|
|
156
|
+
return { canvas, context };
|
|
157
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
/**
|
|
3
|
+
* A worked example of this package's conventions, kept compiling so it
|
|
4
|
+
* cannot drift.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Demonstrates the three rules the wrapper is built on: scoped
|
|
8
|
+
* construction that cleans itself up, synchronous chaining for mutation
|
|
9
|
+
* that cannot fail, and Effects only where a call is fallible or async.
|
|
10
|
+
*
|
|
11
|
+
* Internal — a reference to read, not part of the public surface. It is
|
|
12
|
+
* written for the browser and kept compiling rather than kept running:
|
|
13
|
+
* executing it in Node fails at renderer init, since it takes no canvas or
|
|
14
|
+
* device. See `@effect-motion/three/node` for the headless equivalent.
|
|
15
|
+
*/
|
|
16
|
+
export declare const program: Effect.Effect<number, import("./ThreeException.js").ThreeException, never>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { Mesh, PerspectiveCamera } from "three/webgpu";
|
|
3
|
+
import * as Renderer from "./Renderer.js";
|
|
4
|
+
import * as RenderTarget from "./RenderTarget.js";
|
|
5
|
+
import * as Scene from "./Scene.js";
|
|
6
|
+
/**
|
|
7
|
+
* A worked example of this package's conventions, kept compiling so it
|
|
8
|
+
* cannot drift.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* Demonstrates the three rules the wrapper is built on: scoped
|
|
12
|
+
* construction that cleans itself up, synchronous chaining for mutation
|
|
13
|
+
* that cannot fail, and Effects only where a call is fallible or async.
|
|
14
|
+
*
|
|
15
|
+
* Internal — a reference to read, not part of the public surface. It is
|
|
16
|
+
* written for the browser and kept compiling rather than kept running:
|
|
17
|
+
* executing it in Node fails at renderer init, since it takes no canvas or
|
|
18
|
+
* device. See `@effect-motion/three/node` for the headless equivalent.
|
|
19
|
+
*/
|
|
20
|
+
export const program = Effect.gen(function* () {
|
|
21
|
+
// scoped construction: the scene detaches its children on close, the
|
|
22
|
+
// renderer drains and disposes, the target frees its GPU allocation
|
|
23
|
+
const scene = yield* Scene.make();
|
|
24
|
+
const renderer = yield* Renderer.make({ width: 640, height: 360 });
|
|
25
|
+
const target = yield* RenderTarget.make(640, 360);
|
|
26
|
+
// infallible mutation: sync, chains through pipe, allocates no Effect
|
|
27
|
+
scene.pipe(Scene.add([new Mesh()]), Scene.setBackground(null));
|
|
28
|
+
Renderer.setPixelRatio(renderer, 2);
|
|
29
|
+
// GPU work can fail: Effects, typed as ThreeException
|
|
30
|
+
const camera = new PerspectiveCamera(50, 16 / 9, 1, 1000);
|
|
31
|
+
Renderer.setRenderTarget(renderer, target);
|
|
32
|
+
yield* Renderer.render(renderer, scene, camera);
|
|
33
|
+
const pixels = yield* Renderer.readRenderTarget(renderer, target, 640, 360);
|
|
34
|
+
return pixels.byteLength;
|
|
35
|
+
}).pipe(Effect.scoped);
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
}
|