@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,204 @@
|
|
|
1
|
+
import type { Scope } from "effect";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import * as Pipeable from "effect/Pipeable";
|
|
4
|
+
import * as THREE from "three/webgpu";
|
|
5
|
+
import type * as RenderTarget from "./RenderTarget.js";
|
|
6
|
+
import type * as Scene from "./Scene.js";
|
|
7
|
+
import type { ThreeException } from "./ThreeException.js";
|
|
8
|
+
/**
|
|
9
|
+
* The GPU renderer — a scoped handle over three's `WebGPURenderer`.
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* {@link make} hands back a renderer whose async initialization has already
|
|
13
|
+
* completed, so nothing downstream has to await a device that might not be
|
|
14
|
+
* ready. On scope close it drains the GPU queue before disposing, which is
|
|
15
|
+
* what prevents the "destroyed texture used in a submit" errors that
|
|
16
|
+
* disposing mid-flight would otherwise produce.
|
|
17
|
+
*
|
|
18
|
+
* Work that touches the GPU — {@link render}, {@link readRenderTarget},
|
|
19
|
+
* {@link compile} — is an Effect, typed as `ThreeException`. Sizing and
|
|
20
|
+
* output-target bookkeeping cannot fail, so those stay synchronous and
|
|
21
|
+
* chain through `.pipe`.
|
|
22
|
+
*/
|
|
23
|
+
export declare const TypeId: "~three/Renderer";
|
|
24
|
+
/**
|
|
25
|
+
* A handle to an initialized GPU renderer.
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* The three renderer stays reachable through `~three.renderer` for anything
|
|
29
|
+
* this wrapper does not cover — a deliberate escape hatch, not the front
|
|
30
|
+
* door.
|
|
31
|
+
*/
|
|
32
|
+
export interface Renderer extends Pipeable.Pipeable {
|
|
33
|
+
readonly [TypeId]: typeof TypeId;
|
|
34
|
+
readonly "~three.renderer": THREE.WebGPURenderer;
|
|
35
|
+
}
|
|
36
|
+
/** Whether `u` is a {@link Renderer} handle. */
|
|
37
|
+
export declare const isRenderer: (u: unknown) => u is Renderer;
|
|
38
|
+
type WebGPURendererParameters = NonNullable<ConstructorParameters<typeof THREE.WebGPURenderer>[0]>;
|
|
39
|
+
/**
|
|
40
|
+
* Everything three's `WebGPURenderer` accepts, plus initial sizing applied
|
|
41
|
+
* before initialization.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* Sizing here rather than after `make` avoids an initial render at the
|
|
45
|
+
* wrong size. Canvas CSS is never touched — callers own the element's
|
|
46
|
+
* styling.
|
|
47
|
+
*/
|
|
48
|
+
export interface MakeOptions extends WebGPURendererParameters {
|
|
49
|
+
readonly width?: number;
|
|
50
|
+
readonly height?: number;
|
|
51
|
+
readonly pixelRatio?: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Acquire a renderer, initialization already awaited.
|
|
55
|
+
*
|
|
56
|
+
* @remarks
|
|
57
|
+
* Scoped: on close the renderer waits for in-flight GPU work to land,
|
|
58
|
+
* drains the queue, and disposes. A failure during that teardown is logged
|
|
59
|
+
* rather than raised, so it never masks the scope's real outcome.
|
|
60
|
+
*
|
|
61
|
+
* Without a `canvas`, three creates one. For headless use, pass the canvas
|
|
62
|
+
* and device from `@effect-motion/three/node`.
|
|
63
|
+
*
|
|
64
|
+
* @param options - Renderer parameters plus optional initial sizing.
|
|
65
|
+
* @returns A renderer, valid for the current scope.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* const renderer = yield* Renderer.make({ width: 640, height: 360 });
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare const make: (options?: MakeOptions) => Effect.Effect<Renderer, ThreeException, Scope.Scope>;
|
|
73
|
+
/**
|
|
74
|
+
* Draw a scene through a camera.
|
|
75
|
+
*
|
|
76
|
+
* @remarks
|
|
77
|
+
* Output goes wherever {@link setRenderTarget} last pointed — the canvas by
|
|
78
|
+
* default, or an offscreen target. Safe to call immediately after
|
|
79
|
+
* {@link make}, since initialization is already complete by then.
|
|
80
|
+
*/
|
|
81
|
+
export declare const render: (self: Renderer, scene: Scene.Scene, camera: THREE.Camera) => Effect.Effect<void, ThreeException>;
|
|
82
|
+
/**
|
|
83
|
+
* Read rendered pixels back off the GPU as RGBA bytes.
|
|
84
|
+
*
|
|
85
|
+
* @remarks
|
|
86
|
+
* The result is exactly `width * height * 4` bytes, top-down and tightly
|
|
87
|
+
* packed — ready to hand to an image encoder. WebGPU itself pads each row
|
|
88
|
+
* to a 256-byte boundary; that padding is stripped here so callers never
|
|
89
|
+
* deal with stride.
|
|
90
|
+
*
|
|
91
|
+
* Colors come back LINEAR. Render through a
|
|
92
|
+
* {@link PostProcessing.RenderPipeline} first if you want the sRGB output
|
|
93
|
+
* transform applied — reading a raw render target and encoding it directly
|
|
94
|
+
* produces a visibly dark image.
|
|
95
|
+
*
|
|
96
|
+
* @param target - The target to read.
|
|
97
|
+
* @param width - Region width in device pixels.
|
|
98
|
+
* @param height - Region height in device pixels.
|
|
99
|
+
* @returns `width * height * 4` bytes of RGBA.
|
|
100
|
+
*/
|
|
101
|
+
export declare const readRenderTarget: (self: Renderer, target: RenderTarget.RenderTarget, width: number, height: number) => Effect.Effect<Uint8Array, ThreeException>;
|
|
102
|
+
/**
|
|
103
|
+
* Compile the shader pipelines a scene needs, ahead of drawing it.
|
|
104
|
+
*
|
|
105
|
+
* @remarks
|
|
106
|
+
* WebGPU compiles a pipeline the first time it is used, which lands as a
|
|
107
|
+
* visible hitch (roughly 40–80ms) on the first frame. Calling this after
|
|
108
|
+
* the scene is populated but before anything is shown moves that cost into
|
|
109
|
+
* startup.
|
|
110
|
+
*/
|
|
111
|
+
export declare const compile: (self: Renderer, scene: Scene.Scene, camera: THREE.Camera) => Effect.Effect<void, ThreeException>;
|
|
112
|
+
/**
|
|
113
|
+
* Point the renderer's output at an offscreen target, or `null` to draw to
|
|
114
|
+
* the canvas.
|
|
115
|
+
*
|
|
116
|
+
* @remarks
|
|
117
|
+
* Rendering to a target is how a result becomes something to sample —
|
|
118
|
+
* reading pixels back, or feeding a texture into another pass. Save and
|
|
119
|
+
* restore the previous target around nested renders; {@link getRenderTarget}
|
|
120
|
+
* is there for exactly that.
|
|
121
|
+
*/
|
|
122
|
+
export declare const setRenderTarget: {
|
|
123
|
+
(target: RenderTarget.RenderTarget | null): (self: Renderer) => Renderer;
|
|
124
|
+
(self: Renderer, target: RenderTarget.RenderTarget | null): Renderer;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* The current output target, or `null` when drawing to the canvas.
|
|
128
|
+
*
|
|
129
|
+
* @remarks
|
|
130
|
+
* Read it before redirecting output so you can restore it afterwards.
|
|
131
|
+
*/
|
|
132
|
+
export declare const getRenderTarget: (self: Renderer) => RenderTarget.RenderTarget | null;
|
|
133
|
+
/**
|
|
134
|
+
* Whether the renderer clears the canvas before each render.
|
|
135
|
+
*
|
|
136
|
+
* @remarks
|
|
137
|
+
* Turning it off is how a second pass draws ON TOP of what is already
|
|
138
|
+
* there — an overlay or HUD tier. Turn it back on afterwards, or the next
|
|
139
|
+
* frame will accumulate over this one.
|
|
140
|
+
*/
|
|
141
|
+
export declare const setAutoClear: {
|
|
142
|
+
(autoClear: boolean): (self: Renderer) => Renderer;
|
|
143
|
+
(self: Renderer, autoClear: boolean): Renderer;
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Clear the depth buffer.
|
|
147
|
+
*
|
|
148
|
+
* @remarks
|
|
149
|
+
* Paired with `setAutoClear(false)` for an overlay pass: without it, the
|
|
150
|
+
* overlay's geometry would be depth-tested against the world it is meant to
|
|
151
|
+
* sit above and could be hidden by it.
|
|
152
|
+
*/
|
|
153
|
+
export declare const clearDepth: (self: Renderer) => Renderer;
|
|
154
|
+
/**
|
|
155
|
+
* Advance three's internal frame counter by one.
|
|
156
|
+
*
|
|
157
|
+
* @remarks
|
|
158
|
+
* Only needed when driving renders yourself rather than through three's
|
|
159
|
+
* animation loop — a headless export, above all.
|
|
160
|
+
*
|
|
161
|
+
* Nodes that dedupe their work per frame (the scene pass especially) decide
|
|
162
|
+
* whether to recompute by comparing against this counter. three only ticks
|
|
163
|
+
* it inside its own rAF loop, which an export outruns, so consecutive
|
|
164
|
+
* exported frames would sample a stale texture and come out
|
|
165
|
+
* pairwise-duplicated. Calling this once per exported frame makes one
|
|
166
|
+
* exported frame mean one three frame.
|
|
167
|
+
*/
|
|
168
|
+
export declare const advanceFrame: (self: Renderer) => Renderer;
|
|
169
|
+
/**
|
|
170
|
+
* Set the renderer's logical size in CSS pixels.
|
|
171
|
+
*
|
|
172
|
+
* @remarks
|
|
173
|
+
* The actual drawing buffer is this multiplied by the pixel ratio. Canvas
|
|
174
|
+
* CSS is left alone unless `updateStyle` is true, since callers usually own
|
|
175
|
+
* the element's layout.
|
|
176
|
+
*
|
|
177
|
+
* @defaultValue `updateStyle` — `false`
|
|
178
|
+
*/
|
|
179
|
+
export declare const setSize: {
|
|
180
|
+
(width: number, height: number, updateStyle?: boolean): (self: Renderer) => Renderer;
|
|
181
|
+
(self: Renderer, width: number, height: number, updateStyle?: boolean): Renderer;
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* Set device pixels per logical pixel.
|
|
185
|
+
*
|
|
186
|
+
* @remarks
|
|
187
|
+
* Pass `window.devicePixelRatio` for a sharp result on a high-DPI display,
|
|
188
|
+
* or a fixed value above 1 to supersample an export for cleaner edges.
|
|
189
|
+
*/
|
|
190
|
+
export declare const setPixelRatio: {
|
|
191
|
+
(pixelRatio: number): (self: Renderer) => Renderer;
|
|
192
|
+
(self: Renderer, pixelRatio: number): Renderer;
|
|
193
|
+
};
|
|
194
|
+
/** The current device-pixels-per-logical-pixel ratio. */
|
|
195
|
+
export declare const getPixelRatio: (self: Renderer) => number;
|
|
196
|
+
/**
|
|
197
|
+
* The drawing buffer's size in device pixels — the logical size multiplied
|
|
198
|
+
* by the pixel ratio, and therefore the dimensions to read pixels back at.
|
|
199
|
+
*/
|
|
200
|
+
export declare const getDrawingBufferSize: (self: Renderer) => {
|
|
201
|
+
readonly width: number;
|
|
202
|
+
readonly height: number;
|
|
203
|
+
};
|
|
204
|
+
export {};
|
package/dist/Renderer.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { Effect, Predicate } from "effect";
|
|
2
|
+
import { dual } from "effect/Function";
|
|
3
|
+
import * as Pipeable from "effect/Pipeable";
|
|
4
|
+
import * as THREE from "three/webgpu";
|
|
5
|
+
import { wrap, wrapPromise } from "./Interop.js";
|
|
6
|
+
import * as RenderTargetModule from "./RenderTarget.js";
|
|
7
|
+
/**
|
|
8
|
+
* The GPU renderer — a scoped handle over three's `WebGPURenderer`.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* {@link make} hands back a renderer whose async initialization has already
|
|
12
|
+
* completed, so nothing downstream has to await a device that might not be
|
|
13
|
+
* ready. On scope close it drains the GPU queue before disposing, which is
|
|
14
|
+
* what prevents the "destroyed texture used in a submit" errors that
|
|
15
|
+
* disposing mid-flight would otherwise produce.
|
|
16
|
+
*
|
|
17
|
+
* Work that touches the GPU — {@link render}, {@link readRenderTarget},
|
|
18
|
+
* {@link compile} — is an Effect, typed as `ThreeException`. Sizing and
|
|
19
|
+
* output-target bookkeeping cannot fail, so those stay synchronous and
|
|
20
|
+
* chain through `.pipe`.
|
|
21
|
+
*/
|
|
22
|
+
export const TypeId = "~three/Renderer";
|
|
23
|
+
/** Whether `u` is a {@link Renderer} handle. */
|
|
24
|
+
export const isRenderer = (u) => Predicate.hasProperty(u, TypeId);
|
|
25
|
+
/**
|
|
26
|
+
* `dual`'s predicate receives the whole `arguments` object, not the first
|
|
27
|
+
* argument — dispatch on `args[0]`. Guard-based, never arity (AGENTS.md).
|
|
28
|
+
*/
|
|
29
|
+
const firstArgIsRenderer = (args) => isRenderer(args[0]);
|
|
30
|
+
const brand = (renderer) => {
|
|
31
|
+
const self = {
|
|
32
|
+
[TypeId]: TypeId,
|
|
33
|
+
"~three.renderer": renderer,
|
|
34
|
+
// see Scene.ts on the array-like cast
|
|
35
|
+
pipe(...fns) {
|
|
36
|
+
return Pipeable.pipeArguments(self, fns);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
return self;
|
|
40
|
+
};
|
|
41
|
+
const acquire = Effect.fnUntraced(function* (options) {
|
|
42
|
+
const { width, height, pixelRatio, ...parameters } = options;
|
|
43
|
+
const renderer = yield* wrap("WebGPURenderer", () => new THREE.WebGPURenderer(parameters));
|
|
44
|
+
if (pixelRatio !== undefined) {
|
|
45
|
+
renderer.setPixelRatio(pixelRatio);
|
|
46
|
+
}
|
|
47
|
+
if (width !== undefined && height !== undefined) {
|
|
48
|
+
renderer.setSize(width, height, false);
|
|
49
|
+
}
|
|
50
|
+
yield* wrapPromise("WebGPURenderer.init", () => renderer.init());
|
|
51
|
+
return brand(renderer);
|
|
52
|
+
});
|
|
53
|
+
// disposing destroys GPU textures immediately, but the backend's in-flight
|
|
54
|
+
// async chains (deferred submits, per-render resolves) can still submit
|
|
55
|
+
// afterwards — "Destroyed texture used in a submit" validation spam. Let
|
|
56
|
+
// pending work land while resources are alive, drain the queue, then
|
|
57
|
+
// dispose. A release failure is logged, never thrown — teardown must not
|
|
58
|
+
// mask the scope's real outcome.
|
|
59
|
+
const release = (self) => {
|
|
60
|
+
const renderer = self["~three.renderer"];
|
|
61
|
+
return Effect.sleep("50 millis").pipe(Effect.andThen(wrapPromise("WebGPURenderer queue drain", async () => {
|
|
62
|
+
const device = renderer.backend.device;
|
|
63
|
+
if (device !== undefined) {
|
|
64
|
+
await device.queue.onSubmittedWorkDone();
|
|
65
|
+
}
|
|
66
|
+
})), Effect.andThen(wrap("WebGPURenderer.dispose", () => renderer.dispose())), Effect.catchCause((cause) => Effect.logWarning("WebGPURenderer dispose failed", cause)));
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Acquire a renderer, initialization already awaited.
|
|
70
|
+
*
|
|
71
|
+
* @remarks
|
|
72
|
+
* Scoped: on close the renderer waits for in-flight GPU work to land,
|
|
73
|
+
* drains the queue, and disposes. A failure during that teardown is logged
|
|
74
|
+
* rather than raised, so it never masks the scope's real outcome.
|
|
75
|
+
*
|
|
76
|
+
* Without a `canvas`, three creates one. For headless use, pass the canvas
|
|
77
|
+
* and device from `@effect-motion/three/node`.
|
|
78
|
+
*
|
|
79
|
+
* @param options - Renderer parameters plus optional initial sizing.
|
|
80
|
+
* @returns A renderer, valid for the current scope.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* const renderer = yield* Renderer.make({ width: 640, height: 360 });
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export const make = (options = {}) => Effect.acquireRelease(acquire(options), release);
|
|
88
|
+
/**
|
|
89
|
+
* Draw a scene through a camera.
|
|
90
|
+
*
|
|
91
|
+
* @remarks
|
|
92
|
+
* Output goes wherever {@link setRenderTarget} last pointed — the canvas by
|
|
93
|
+
* default, or an offscreen target. Safe to call immediately after
|
|
94
|
+
* {@link make}, since initialization is already complete by then.
|
|
95
|
+
*/
|
|
96
|
+
export const render = (self, scene, camera) => wrap("WebGPURenderer.render", () => self["~three.renderer"].render(scene["~three.scene"], camera));
|
|
97
|
+
/**
|
|
98
|
+
* Read rendered pixels back off the GPU as RGBA bytes.
|
|
99
|
+
*
|
|
100
|
+
* @remarks
|
|
101
|
+
* The result is exactly `width * height * 4` bytes, top-down and tightly
|
|
102
|
+
* packed — ready to hand to an image encoder. WebGPU itself pads each row
|
|
103
|
+
* to a 256-byte boundary; that padding is stripped here so callers never
|
|
104
|
+
* deal with stride.
|
|
105
|
+
*
|
|
106
|
+
* Colors come back LINEAR. Render through a
|
|
107
|
+
* {@link PostProcessing.RenderPipeline} first if you want the sRGB output
|
|
108
|
+
* transform applied — reading a raw render target and encoding it directly
|
|
109
|
+
* produces a visibly dark image.
|
|
110
|
+
*
|
|
111
|
+
* @param target - The target to read.
|
|
112
|
+
* @param width - Region width in device pixels.
|
|
113
|
+
* @param height - Region height in device pixels.
|
|
114
|
+
* @returns `width * height * 4` bytes of RGBA.
|
|
115
|
+
*/
|
|
116
|
+
export const readRenderTarget = (self, target, width, height) => wrapPromise("readRenderTargetPixelsAsync", () => self["~three.renderer"].readRenderTargetPixelsAsync(target["~three.renderTarget"], 0, 0, width, height)).pipe(Effect.map((pixels) => {
|
|
117
|
+
const padded = new Uint8Array(pixels.buffer, pixels.byteOffset, pixels.byteLength);
|
|
118
|
+
const tightRow = width * 4;
|
|
119
|
+
const paddedRow = Math.ceil(tightRow / 256) * 256;
|
|
120
|
+
if (padded.length === tightRow * height) {
|
|
121
|
+
return padded;
|
|
122
|
+
}
|
|
123
|
+
const rgba = new Uint8Array(tightRow * height);
|
|
124
|
+
for (let y = 0; y < height; y++) {
|
|
125
|
+
rgba.set(padded.subarray(y * paddedRow, y * paddedRow + tightRow), y * tightRow);
|
|
126
|
+
}
|
|
127
|
+
return rgba;
|
|
128
|
+
}));
|
|
129
|
+
/**
|
|
130
|
+
* Compile the shader pipelines a scene needs, ahead of drawing it.
|
|
131
|
+
*
|
|
132
|
+
* @remarks
|
|
133
|
+
* WebGPU compiles a pipeline the first time it is used, which lands as a
|
|
134
|
+
* visible hitch (roughly 40–80ms) on the first frame. Calling this after
|
|
135
|
+
* the scene is populated but before anything is shown moves that cost into
|
|
136
|
+
* startup.
|
|
137
|
+
*/
|
|
138
|
+
export const compile = (self, scene, camera) => wrapPromise("WebGPURenderer.compileAsync", () => self["~three.renderer"]
|
|
139
|
+
.compileAsync(scene["~three.scene"], camera)
|
|
140
|
+
.then(() => undefined));
|
|
141
|
+
/**
|
|
142
|
+
* Point the renderer's output at an offscreen target, or `null` to draw to
|
|
143
|
+
* the canvas.
|
|
144
|
+
*
|
|
145
|
+
* @remarks
|
|
146
|
+
* Rendering to a target is how a result becomes something to sample —
|
|
147
|
+
* reading pixels back, or feeding a texture into another pass. Save and
|
|
148
|
+
* restore the previous target around nested renders; {@link getRenderTarget}
|
|
149
|
+
* is there for exactly that.
|
|
150
|
+
*/
|
|
151
|
+
export const setRenderTarget = dual(firstArgIsRenderer, (self, target) => {
|
|
152
|
+
self["~three.renderer"].setRenderTarget(target === null ? null : target["~three.renderTarget"]);
|
|
153
|
+
return self;
|
|
154
|
+
});
|
|
155
|
+
/**
|
|
156
|
+
* The current output target, or `null` when drawing to the canvas.
|
|
157
|
+
*
|
|
158
|
+
* @remarks
|
|
159
|
+
* Read it before redirecting output so you can restore it afterwards.
|
|
160
|
+
*/
|
|
161
|
+
export const getRenderTarget = (self) => {
|
|
162
|
+
const target = self["~three.renderer"].getRenderTarget();
|
|
163
|
+
return target === null ? null : RenderTargetModule.fromRaw(target);
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Whether the renderer clears the canvas before each render.
|
|
167
|
+
*
|
|
168
|
+
* @remarks
|
|
169
|
+
* Turning it off is how a second pass draws ON TOP of what is already
|
|
170
|
+
* there — an overlay or HUD tier. Turn it back on afterwards, or the next
|
|
171
|
+
* frame will accumulate over this one.
|
|
172
|
+
*/
|
|
173
|
+
export const setAutoClear = dual(firstArgIsRenderer, (self, autoClear) => {
|
|
174
|
+
self["~three.renderer"].autoClear = autoClear;
|
|
175
|
+
return self;
|
|
176
|
+
});
|
|
177
|
+
/**
|
|
178
|
+
* Clear the depth buffer.
|
|
179
|
+
*
|
|
180
|
+
* @remarks
|
|
181
|
+
* Paired with `setAutoClear(false)` for an overlay pass: without it, the
|
|
182
|
+
* overlay's geometry would be depth-tested against the world it is meant to
|
|
183
|
+
* sit above and could be hidden by it.
|
|
184
|
+
*/
|
|
185
|
+
export const clearDepth = (self) => {
|
|
186
|
+
self["~three.renderer"].clearDepth();
|
|
187
|
+
return self;
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Advance three's internal frame counter by one.
|
|
191
|
+
*
|
|
192
|
+
* @remarks
|
|
193
|
+
* Only needed when driving renders yourself rather than through three's
|
|
194
|
+
* animation loop — a headless export, above all.
|
|
195
|
+
*
|
|
196
|
+
* Nodes that dedupe their work per frame (the scene pass especially) decide
|
|
197
|
+
* whether to recompute by comparing against this counter. three only ticks
|
|
198
|
+
* it inside its own rAF loop, which an export outruns, so consecutive
|
|
199
|
+
* exported frames would sample a stale texture and come out
|
|
200
|
+
* pairwise-duplicated. Calling this once per exported frame makes one
|
|
201
|
+
* exported frame mean one three frame.
|
|
202
|
+
*/
|
|
203
|
+
export const advanceFrame = (self) => {
|
|
204
|
+
self["~three.renderer"]._nodes.nodeFrame.update();
|
|
205
|
+
return self;
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Set the renderer's logical size in CSS pixels.
|
|
209
|
+
*
|
|
210
|
+
* @remarks
|
|
211
|
+
* The actual drawing buffer is this multiplied by the pixel ratio. Canvas
|
|
212
|
+
* CSS is left alone unless `updateStyle` is true, since callers usually own
|
|
213
|
+
* the element's layout.
|
|
214
|
+
*
|
|
215
|
+
* @defaultValue `updateStyle` — `false`
|
|
216
|
+
*/
|
|
217
|
+
export const setSize = dual(firstArgIsRenderer, (self, width, height, updateStyle = false) => {
|
|
218
|
+
self["~three.renderer"].setSize(width, height, updateStyle);
|
|
219
|
+
return self;
|
|
220
|
+
});
|
|
221
|
+
/**
|
|
222
|
+
* Set device pixels per logical pixel.
|
|
223
|
+
*
|
|
224
|
+
* @remarks
|
|
225
|
+
* Pass `window.devicePixelRatio` for a sharp result on a high-DPI display,
|
|
226
|
+
* or a fixed value above 1 to supersample an export for cleaner edges.
|
|
227
|
+
*/
|
|
228
|
+
export const setPixelRatio = dual(firstArgIsRenderer, (self, pixelRatio) => {
|
|
229
|
+
self["~three.renderer"].setPixelRatio(pixelRatio);
|
|
230
|
+
return self;
|
|
231
|
+
});
|
|
232
|
+
/** The current device-pixels-per-logical-pixel ratio. */
|
|
233
|
+
export const getPixelRatio = (self) => self["~three.renderer"].getPixelRatio();
|
|
234
|
+
/**
|
|
235
|
+
* The drawing buffer's size in device pixels — the logical size multiplied
|
|
236
|
+
* by the pixel ratio, and therefore the dimensions to read pixels back at.
|
|
237
|
+
*/
|
|
238
|
+
export const getDrawingBufferSize = (self) => {
|
|
239
|
+
const size = self["~three.renderer"].getDrawingBufferSize(new THREE.Vector2());
|
|
240
|
+
return { width: size.x, height: size.y };
|
|
241
|
+
};
|
package/dist/Scene.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Scope } from "effect";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import * as Pipeable from "effect/Pipeable";
|
|
4
|
+
import * as THREE from "three/webgpu";
|
|
5
|
+
import type * as Object3D from "./Object3D.js";
|
|
6
|
+
/**
|
|
7
|
+
* The scene graph root — a scoped handle over three's `Scene`.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* three is already shaped the way this wrapper wants, so this is a
|
|
11
|
+
* branding-and-lifecycle layer rather than a redesign. Everything here —
|
|
12
|
+
* {@link add}, {@link remove}, {@link clear}, {@link setBackground} — is
|
|
13
|
+
* infallible bookkeeping on an object already in hand, so it stays
|
|
14
|
+
* synchronous and chains through `.pipe`. Effect enters only at
|
|
15
|
+
* {@link make}, which registers teardown.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const scene = yield* Scene.make();
|
|
20
|
+
* scene.pipe(Scene.add([mesh]), Scene.setBackground(new Color(0x16161d)));
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare const TypeId: "~three/Scene";
|
|
24
|
+
/**
|
|
25
|
+
* A handle to a three scene.
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* The underlying scene stays reachable through `~three.scene` for anything
|
|
29
|
+
* this wrapper does not cover.
|
|
30
|
+
*/
|
|
31
|
+
export interface Scene extends Pipeable.Pipeable {
|
|
32
|
+
readonly [TypeId]: typeof TypeId;
|
|
33
|
+
readonly "~three.scene": THREE.Scene;
|
|
34
|
+
}
|
|
35
|
+
/** Whether `u` is a {@link Scene} handle. */
|
|
36
|
+
export declare const isScene: (u: unknown) => u is Scene;
|
|
37
|
+
/**
|
|
38
|
+
* Wrap an existing three scene WITHOUT registering teardown.
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* For a scene whose lifetime something else already owns and will clean up.
|
|
42
|
+
* Prefer {@link make}, which ties teardown to a scope; reach for this only
|
|
43
|
+
* when a longer-lived owner is genuinely in charge.
|
|
44
|
+
*/
|
|
45
|
+
export declare const makeUnsafe: (scene: THREE.Scene) => Scene;
|
|
46
|
+
/**
|
|
47
|
+
* A scoped scene that detaches its children when the scope closes.
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* Detaching is NOT disposal. A scene does not own the geometries,
|
|
51
|
+
* materials, and textures hanging off its objects — those are routinely
|
|
52
|
+
* shared between objects and outlive any one graph — so whoever created
|
|
53
|
+
* them is responsible for freeing them.
|
|
54
|
+
*/
|
|
55
|
+
export declare const make: () => Effect.Effect<Scene, never, Scope.Scope>;
|
|
56
|
+
/**
|
|
57
|
+
* Detach every child from the scene root.
|
|
58
|
+
*
|
|
59
|
+
* @remarks
|
|
60
|
+
* Unparents only — see {@link make} on why this does not dispose anything.
|
|
61
|
+
*/
|
|
62
|
+
export declare const clear: (self: Scene) => Scene;
|
|
63
|
+
/** The scene root's direct children (not a deep traversal). */
|
|
64
|
+
export declare const children: (self: Scene) => ReadonlyArray<Object3D.Object3D>;
|
|
65
|
+
/**
|
|
66
|
+
* Whether the scene root has no children.
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* The "is there anything to draw" check — worth making before an optional
|
|
70
|
+
* pass, so an empty overlay tier costs nothing.
|
|
71
|
+
*/
|
|
72
|
+
export declare const isEmpty: (self: Scene) => boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Attach objects to the scene root.
|
|
75
|
+
*
|
|
76
|
+
* @remarks
|
|
77
|
+
* Re-adding an object that is already parented moves it, as in three
|
|
78
|
+
* itself — an object has exactly one parent.
|
|
79
|
+
*/
|
|
80
|
+
export declare const add: {
|
|
81
|
+
(objects: ReadonlyArray<Object3D.Object3D>): (self: Scene) => Scene;
|
|
82
|
+
(self: Scene, objects: ReadonlyArray<Object3D.Object3D>): Scene;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Detach objects from the scene root.
|
|
86
|
+
*
|
|
87
|
+
* @remarks
|
|
88
|
+
* A no-op for objects that are not attached, and it does not dispose them —
|
|
89
|
+
* a removed object can be added back.
|
|
90
|
+
*/
|
|
91
|
+
export declare const remove: {
|
|
92
|
+
(objects: ReadonlyArray<Object3D.Object3D>): (self: Scene) => Scene;
|
|
93
|
+
(self: Scene, objects: ReadonlyArray<Object3D.Object3D>): Scene;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Set the background color or texture, or `null` for transparency.
|
|
97
|
+
*
|
|
98
|
+
* @remarks
|
|
99
|
+
* `null` is what an overlay tier wants: with nothing painted behind it,
|
|
100
|
+
* whatever was rendered first shows through.
|
|
101
|
+
*/
|
|
102
|
+
export declare const setBackground: {
|
|
103
|
+
(background: THREE.Color | THREE.Texture | null): (self: Scene) => Scene;
|
|
104
|
+
(self: Scene, background: THREE.Color | THREE.Texture | null): Scene;
|
|
105
|
+
};
|
package/dist/Scene.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Effect, Predicate } from "effect";
|
|
2
|
+
import { dual } from "effect/Function";
|
|
3
|
+
import * as Pipeable from "effect/Pipeable";
|
|
4
|
+
import * as THREE from "three/webgpu";
|
|
5
|
+
/**
|
|
6
|
+
* The scene graph root — a scoped handle over three's `Scene`.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* three is already shaped the way this wrapper wants, so this is a
|
|
10
|
+
* branding-and-lifecycle layer rather than a redesign. Everything here —
|
|
11
|
+
* {@link add}, {@link remove}, {@link clear}, {@link setBackground} — is
|
|
12
|
+
* infallible bookkeeping on an object already in hand, so it stays
|
|
13
|
+
* synchronous and chains through `.pipe`. Effect enters only at
|
|
14
|
+
* {@link make}, which registers teardown.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const scene = yield* Scene.make();
|
|
19
|
+
* scene.pipe(Scene.add([mesh]), Scene.setBackground(new Color(0x16161d)));
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export const TypeId = "~three/Scene";
|
|
23
|
+
/** Whether `u` is a {@link Scene} handle. */
|
|
24
|
+
export const isScene = (u) => Predicate.hasProperty(u, TypeId);
|
|
25
|
+
/**
|
|
26
|
+
* `dual`'s predicate receives the whole `arguments` object, not the first
|
|
27
|
+
* argument — dispatch on `args[0]`, as the motion package's animators do.
|
|
28
|
+
* Guard-based, never arity (AGENTS.md).
|
|
29
|
+
*/
|
|
30
|
+
const firstArgIsScene = (args) => isScene(args[0]);
|
|
31
|
+
/**
|
|
32
|
+
* Wrap an existing three scene WITHOUT registering teardown.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* For a scene whose lifetime something else already owns and will clean up.
|
|
36
|
+
* Prefer {@link make}, which ties teardown to a scope; reach for this only
|
|
37
|
+
* when a longer-lived owner is genuinely in charge.
|
|
38
|
+
*/
|
|
39
|
+
export const makeUnsafe = (scene) => {
|
|
40
|
+
const self = {
|
|
41
|
+
[TypeId]: TypeId,
|
|
42
|
+
"~three.scene": scene,
|
|
43
|
+
// pipeArguments reads its second parameter as an array-like; a rest
|
|
44
|
+
// array satisfies that at runtime, and the cast avoids both the
|
|
45
|
+
// `arguments` object and a lint suppression
|
|
46
|
+
pipe(...fns) {
|
|
47
|
+
return Pipeable.pipeArguments(self, fns);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
return self;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* A scoped scene that detaches its children when the scope closes.
|
|
54
|
+
*
|
|
55
|
+
* @remarks
|
|
56
|
+
* Detaching is NOT disposal. A scene does not own the geometries,
|
|
57
|
+
* materials, and textures hanging off its objects — those are routinely
|
|
58
|
+
* shared between objects and outlive any one graph — so whoever created
|
|
59
|
+
* them is responsible for freeing them.
|
|
60
|
+
*/
|
|
61
|
+
export const make = Effect.fnUntraced(function* () {
|
|
62
|
+
const scene = new THREE.Scene();
|
|
63
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => scene.clear()));
|
|
64
|
+
return makeUnsafe(scene);
|
|
65
|
+
});
|
|
66
|
+
/**
|
|
67
|
+
* Detach every child from the scene root.
|
|
68
|
+
*
|
|
69
|
+
* @remarks
|
|
70
|
+
* Unparents only — see {@link make} on why this does not dispose anything.
|
|
71
|
+
*/
|
|
72
|
+
export const clear = (self) => {
|
|
73
|
+
self["~three.scene"].clear();
|
|
74
|
+
return self;
|
|
75
|
+
};
|
|
76
|
+
/** The scene root's direct children (not a deep traversal). */
|
|
77
|
+
export const children = (self) => self["~three.scene"].children;
|
|
78
|
+
/**
|
|
79
|
+
* Whether the scene root has no children.
|
|
80
|
+
*
|
|
81
|
+
* @remarks
|
|
82
|
+
* The "is there anything to draw" check — worth making before an optional
|
|
83
|
+
* pass, so an empty overlay tier costs nothing.
|
|
84
|
+
*/
|
|
85
|
+
export const isEmpty = (self) => self["~three.scene"].children.length === 0;
|
|
86
|
+
/**
|
|
87
|
+
* Attach objects to the scene root.
|
|
88
|
+
*
|
|
89
|
+
* @remarks
|
|
90
|
+
* Re-adding an object that is already parented moves it, as in three
|
|
91
|
+
* itself — an object has exactly one parent.
|
|
92
|
+
*/
|
|
93
|
+
export const add = dual(firstArgIsScene, (self, objects) => {
|
|
94
|
+
self["~three.scene"].add(...objects);
|
|
95
|
+
return self;
|
|
96
|
+
});
|
|
97
|
+
/**
|
|
98
|
+
* Detach objects from the scene root.
|
|
99
|
+
*
|
|
100
|
+
* @remarks
|
|
101
|
+
* A no-op for objects that are not attached, and it does not dispose them —
|
|
102
|
+
* a removed object can be added back.
|
|
103
|
+
*/
|
|
104
|
+
export const remove = dual(firstArgIsScene, (self, objects) => {
|
|
105
|
+
self["~three.scene"].remove(...objects);
|
|
106
|
+
return self;
|
|
107
|
+
});
|
|
108
|
+
/**
|
|
109
|
+
* Set the background color or texture, or `null` for transparency.
|
|
110
|
+
*
|
|
111
|
+
* @remarks
|
|
112
|
+
* `null` is what an overlay tier wants: with nothing painted behind it,
|
|
113
|
+
* whatever was rendered first shows through.
|
|
114
|
+
*/
|
|
115
|
+
export const setBackground = dual(firstArgIsScene, (self, background) => {
|
|
116
|
+
self["~three.scene"].background = background;
|
|
117
|
+
return self;
|
|
118
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
declare const ThreeException_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
2
|
+
readonly _tag: "ThreeException";
|
|
3
|
+
} & Readonly<A>;
|
|
4
|
+
/**
|
|
5
|
+
* A three.js operation failed.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* Every fallible call in this package reports through this one error type,
|
|
9
|
+
* so a caller catches by tag rather than guarding each three API
|
|
10
|
+
* separately. `operation` names which call failed — `"WebGPURenderer.init"`,
|
|
11
|
+
* `"readRenderTargetPixelsAsync"` — and `cause` carries whatever three
|
|
12
|
+
* threw or rejected with.
|
|
13
|
+
*
|
|
14
|
+
* Typical causes are environmental rather than logical: no WebGPU adapter,
|
|
15
|
+
* a lost device, a shader that failed to compile.
|
|
16
|
+
*/
|
|
17
|
+
export declare class ThreeException extends ThreeException_base<{
|
|
18
|
+
/** The underlying error three threw or rejected with. */
|
|
19
|
+
cause?: unknown;
|
|
20
|
+
/** Name of the three.js operation that failed. */
|
|
21
|
+
operation?: string;
|
|
22
|
+
}> {
|
|
23
|
+
}
|
|
24
|
+
export {};
|