scenic-prism-standalone 0.1.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/LICENSE ADDED
@@ -0,0 +1,133 @@
1
+ # PolyForm Noncommercial License 1.0.0
2
+
3
+ <https://polyformproject.org/licenses/noncommercial/1.0.0>
4
+
5
+ Required Notice: Copyright James Porter (https://scenic-prism.pages.dev/license)
6
+
7
+ ## Acceptance
8
+
9
+ In order to get any license under these terms, you must agree
10
+ to them as both strict obligations and conditions to all
11
+ your licenses.
12
+
13
+ ## Copyright License
14
+
15
+ The licensor grants you a copyright license for the
16
+ software to do everything you might do with the software
17
+ that would otherwise infringe the licensor's copyright
18
+ in it for any permitted purpose. However, you may
19
+ only distribute the software according to [Distribution
20
+ License](#distribution-license) and make changes or new works
21
+ based on the software according to [Changes and New Works
22
+ License](#changes-and-new-works-license).
23
+
24
+ ## Distribution License
25
+
26
+ The licensor grants you an additional copyright license
27
+ to distribute copies of the software. Your license
28
+ to distribute covers distributing the software with
29
+ changes and new works permitted by [Changes and New Works
30
+ License](#changes-and-new-works-license).
31
+
32
+ ## Notices
33
+
34
+ You must ensure that anyone who gets a copy of any part of
35
+ the software from you also gets a copy of these terms or the
36
+ URL for them above, as well as copies of any plain-text lines
37
+ beginning with `Required Notice:` that the licensor provided
38
+ with the software. For example:
39
+
40
+ > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
41
+
42
+ ## Changes and New Works License
43
+
44
+ The licensor grants you an additional copyright license to
45
+ make changes and new works based on the software for any
46
+ permitted purpose.
47
+
48
+ ## Patent License
49
+
50
+ The licensor grants you a patent license for the software that
51
+ covers patent claims the licensor can license, or becomes able
52
+ to license, that you would infringe by using the software.
53
+
54
+ ## Noncommercial Purposes
55
+
56
+ Any noncommercial purpose is a permitted purpose.
57
+
58
+ ## Personal Uses
59
+
60
+ Personal use for research, experiment, and testing for
61
+ the benefit of public knowledge, personal study, private
62
+ entertainment, hobby projects, amateur pursuits, or religious
63
+ observance, without any anticipated commercial application,
64
+ is use for a permitted purpose.
65
+
66
+ ## Noncommercial Organizations
67
+
68
+ Use by any charitable organization, educational institution,
69
+ public research organization, public safety or health
70
+ organization, environmental protection organization,
71
+ or government institution is use for a permitted purpose
72
+ regardless of the source of funding or obligations resulting
73
+ from the funding.
74
+
75
+ ## Fair Use
76
+
77
+ You may have "fair use" rights for the software under the
78
+ law. These terms do not limit them.
79
+
80
+ ## No Other Rights
81
+
82
+ These terms do not allow you to sublicense or transfer any of
83
+ your licenses to anyone else, or prevent the licensor from
84
+ granting licenses to anyone else. These terms do not imply
85
+ any other licenses.
86
+
87
+ ## Patent Defense
88
+
89
+ If you make any written claim that the software infringes or
90
+ contributes to infringement of any patent, your patent license
91
+ for the software granted under these terms ends immediately. If
92
+ your company makes such a claim, your patent license ends
93
+ immediately for work on behalf of your company.
94
+
95
+ ## Violations
96
+
97
+ The first time you are notified in writing that you have
98
+ violated any of these terms, or done anything with the software
99
+ not covered by your licenses, your licenses can nonetheless
100
+ continue if you come into full compliance with these terms,
101
+ and take practical steps to correct past violations, within
102
+ 32 days of receiving notice. Otherwise, all your licenses
103
+ end immediately.
104
+
105
+ ## No Liability
106
+
107
+ **_As far as the law allows, the software comes as is, without
108
+ any warranty or condition, and the licensor will not be liable
109
+ to you for any damages arising out of these terms or the use
110
+ or nature of the software, under any kind of legal claim._**
111
+
112
+ ## Definitions
113
+
114
+ The **licensor** is the individual or entity offering these
115
+ terms, and the **software** is the software the licensor makes
116
+ available under these terms.
117
+
118
+ **You** refers to the individual or entity agreeing to these
119
+ terms.
120
+
121
+ **Your company** is any legal entity, sole proprietorship,
122
+ or other kind of organization that you work for, plus all
123
+ organizations that have control over, are under the control of,
124
+ or are under common control with that organization. **Control**
125
+ means ownership of substantially all the assets of an entity,
126
+ or the power to direct its management and policies by vote,
127
+ contract, or otherwise. Control can be direct or indirect.
128
+
129
+ **Your licenses** are all the licenses granted to you for the
130
+ software under these terms.
131
+
132
+ **Use** means anything you do with the software requiring one
133
+ of your licenses.
package/README.md ADDED
@@ -0,0 +1,202 @@
1
+ # scenic-prism-standalone
2
+
3
+ A [`scenic-prism`](https://www.npmjs.com/package/scenic-prism) scene, as one
4
+ self-contained file: an HTML page that needs nothing, or a React component that
5
+ needs only React. Inside each is the scene's WGSL and a WebGPU loop.
6
+
7
+ ```ts
8
+ import { backgrounds, buildStandaloneHtml, materials, plane, sphere } from 'scenic-prism-standalone';
9
+ import { writeFileSync } from 'node:fs';
10
+
11
+ const SCENE = {
12
+ scene: sphere(1).paint(materials.chrome).union(plane([0, 1, 0], -1)),
13
+ background: backgrounds.dusk,
14
+ };
15
+
16
+ writeFileSync('chrome.html', buildStandaloneHtml(SCENE, { size: 900, seed: 7, min: true }));
17
+ ```
18
+
19
+ The generated HTML contains the compiled shaders, a canvas and a render loop.
20
+ It needs no external scripts or assets and works offline in a browser with
21
+ WebGPU.
22
+
23
+ ## Install
24
+
25
+ ```sh
26
+ npm install scenic-prism-standalone
27
+ # or: pnpm add scenic-prism-standalone
28
+ ```
29
+
30
+ The package includes `scenic-prism` and `scenic-prism-fluent` at exact versions
31
+ and re-exports their main APIs. It ships ESM with TypeScript declarations.
32
+ You can generate files in Node or in a browser.
33
+
34
+ ## `buildStandaloneHtml(spec, options?)`
35
+
36
+ Compiles `spec` and returns the document as a string. Where that string goes —
37
+ a file, an HTTP response, an `<iframe srcdoc>` — is yours.
38
+
39
+ | Option | | |
40
+ | --- | --- | --- |
41
+ | `size` | `number` | Square backing resolution in pixels (default 1024). |
42
+ | `width` `height` | `number` | The same, when the canvas is not square. |
43
+ | `maxFrames` | `number` | Samples per pixel to accumulate before stopping (default 1200). |
44
+ | `bounces` | `number` | Light-bounce budget, baked into the shader (default 6). |
45
+ | `tone` | `'aces' \| 'reinhard' \| 'linear'` | The curve that brings the traced light into display range (default `'aces'`). |
46
+ | `exposure` | `number` | Exposure in stops, before that curve: `-1` is half as bright, `+2` four times (default 0). The page has no controls, so this is the only chance to set it. |
47
+ | `colorSpace` | `'srgb' \| 'display-p3'` | Which colour space the render is shown in (default `'srgb'`). `'display-p3'` asks the canvas for the wider gamut of a modern screen; a browser without one stays in sRGB. |
48
+ | `alpha` | `boolean` | Trace onto a transparent background; the page's own backdrop becomes a chequer (default false). |
49
+ | `seed` | `number` | Fix the sample sequence, so every visitor sees the same noise. |
50
+ | `min` | `boolean` | Minify the markup, the stylesheet, the script and the shader. |
51
+ | `title` | `string` | The document's `<title>` (default `scenic-prism`). |
52
+
53
+ `width` and `height` are the canvas's **backing store**, and so what the shader
54
+ is compiled for. How large the page draws it is the stylesheet's business: it is
55
+ centred and fitted to the window, so a 2048-pixel render is still a 2048-pixel
56
+ render on a phone.
57
+
58
+ ## `buildStandaloneTsx(spec, options?)`
59
+
60
+ Returns the scene as a `.tsx` file for a React project. The generated component
61
+ imports only `react`; the shaders and render loop are included in the file.
62
+
63
+ ```ts
64
+ import { backgrounds, buildStandaloneTsx, materials, sphere } from 'scenic-prism-standalone';
65
+ import { writeFileSync } from 'node:fs';
66
+
67
+ writeFileSync(
68
+ 'src/ChromeSphere.tsx',
69
+ buildStandaloneTsx(SCENE, { componentName: 'ChromeSphere', size: 900, seed: 7 }),
70
+ );
71
+ ```
72
+
73
+ ```tsx
74
+ import { ChromeSphere } from './ChromeSphere';
75
+
76
+ <ChromeSphere size={720} className="rounded-xl" />;
77
+ ```
78
+
79
+ The component renders a single `<canvas>` and forwards canvas props to it.
80
+ Set its display size and appearance with CSS.
81
+
82
+ | Option | | |
83
+ | --- | --- | --- |
84
+ | `componentName` | `string` | The component's name, and so the name to import it by (default `Scene`). Its props interface is exported alongside it as this plus `Props`. |
85
+ | `size` | `number` | Backing resolution the `size` prop *defaults* to (default 1024). |
86
+ | `width` `height` | `number` | The same, when the canvas is not square. |
87
+ | `maxFrames` | `number` | Samples per pixel the `maxFrames` prop defaults to (default 1200). |
88
+ | `bounces` | `number` | Light-bounce budget, baked into the shader (default 6). |
89
+ | `tone` | `'aces' \| 'reinhard' \| 'linear'` | The curve that brings the traced light into display range (default `'aces'`). |
90
+ | `exposure` | `number` | Exposure in stops, before that curve (default 0). |
91
+ | `colorSpace` | `'srgb' \| 'display-p3'` | Which colour space the render is shown in (default `'srgb'`). `'display-p3'` asks the canvas for the wider gamut of a modern screen; a browser without one stays in sRGB. |
92
+ | `alpha` | `boolean` | Trace onto a transparent background, so whatever the canvas sits on shows through (default false). |
93
+ | `seed` | `number` | The seed the `seed` prop defaults to. Without one, a fresh sample sequence on every mount. |
94
+ | `min` | `boolean` | Minify the two shaders while leaving the component code readable. |
95
+ | `useClient` | `boolean` | Prepend `'use client'`, for a framework that renders on the server first (default false). |
96
+ | `exportDefault` | `boolean` | Also export the component as the module's default (default false). The named export is always there. |
97
+
98
+ The first five of those are only *defaults*, because the component takes them as
99
+ props too:
100
+
101
+ | Prop | | |
102
+ | --- | --- | --- |
103
+ | `size` | `number` | Square backing resolution in pixels; `width`/`height` override it. |
104
+ | `width` `height` | `number` | The backing store, not the CSS box. |
105
+ | `maxFrames` | `number` | Stop accumulating after this many samples per pixel. |
106
+ | `seed` | `number \| null` | Fix the sample sequence, or `null` for a fresh one. |
107
+ | `onProgress` | `(frames, total) => void` | Called after every accumulated frame. |
108
+ | `onDone` | `(frames) => void` | Called once the accumulation finishes. |
109
+ | `onError` | `(error) => void` | Called if rendering cannot start — no WebGPU, or a hand-written WGSL function that does not compile, in the compiler's own words. Use it to show your own fallback. |
110
+
111
+ …plus everything a `<canvas>` accepts. The resolution is a uniform rather than a
112
+ literal in the shader, which is what lets the size be a prop at all; `bounces`,
113
+ `tone`, `exposure`, `colorSpace` and `alpha` are baked in when the file is written, where
114
+ there is still a spec to compile, and so are not.
115
+
116
+ Changing the size restarts the accumulation. Each mount requests a GPU device of
117
+ its own, and unmounting destroys it — and every buffer and pipeline on it with
118
+ it. The generated file uses TypeScript's DOM WebGPU types, which ship with
119
+ TypeScript 6 and later.
120
+
121
+ ## `downloadStandaloneHtml(spec, options?)`
122
+
123
+ The same page, saved by the browser it is called in — an export button, in one
124
+ call. Takes everything `buildStandaloneHtml` does plus `filename` (default
125
+ `scene.html`; `.html` is appended if it is missing), and returns the same HTML
126
+ string, so a caller that also wants to show or measure what it saved need not
127
+ build it twice.
128
+
129
+ ```tsx
130
+ <button onClick={() => downloadStandaloneHtml(SCENE, { size: 900, min: true })}>
131
+ Download this scene
132
+ </button>
133
+ ```
134
+
135
+ ## `min`
136
+
137
+ `min` strips the comments and squeezes the whitespace out of all four languages
138
+ in the page. It **renames nothing and reorders nothing** — an identifier in a
139
+ minified page is the identifier this package wrote, and the shader is the same
140
+ shader — so the two modes differ in size and in nothing else. Roughly a third
141
+ comes off a typical page; the shader is most of what is left, and most of that
142
+ is the scene.
143
+
144
+ In a `.tsx` file it squeezes the two shaders and stops there: the component
145
+ around them is yours to read and edit, and whatever bundles it will minify it
146
+ anyway.
147
+
148
+ Leave it off while you are working — the readable file is the best description
149
+ of what the library actually does at runtime — and turn it on for what you ship.
150
+
151
+ ## What is in the page
152
+
153
+ ```
154
+ <canvas> the backing store, at the size you asked for
155
+ <style> centre it, fit it to the window, nothing else
156
+ <script>
157
+ WIDTH … SEED the constants you passed
158
+ TRACE your scene, compiled to a WGSL compute shader
159
+ DISPLAY exposure, tonemap and gamma, accumulation buffer to canvas
160
+ the loop one float storage buffer, one sample per pixel per frame
161
+ ```
162
+
163
+ The loop is `scenic-prism`'s own renderer with everything a *library* needs
164
+ taken out — no handle, no shared device, no teardown, no progress callback —
165
+ because a page that draws one picture and stops has nobody to hand any of that
166
+ to. A browser without WebGPU gets a line of text saying so instead of a black
167
+ rectangle, and a scene whose hand-written WGSL does not compile gets the
168
+ compiler's reason under it.
169
+
170
+ The `.tsx` file is the same two shaders and the same loop, put back inside a
171
+ React effect, which is what gives it the teardown and the callbacks a page has
172
+ nobody to hand: it is `scenic-prism-react`'s component with the library taken
173
+ out rather than the renderer with the library taken out.
174
+
175
+ ## The companion packages
176
+
177
+ | Package | |
178
+ | --- | --- |
179
+ | [`scenic-prism`](https://www.npmjs.com/package/scenic-prism) | The library: scenes, the compiler, the renderer. |
180
+ | [`scenic-prism-fluent`](https://www.npmjs.com/package/scenic-prism-fluent) | The same vocabulary, chained. |
181
+ | [`scenic-prism-react`](https://www.npmjs.com/package/scenic-prism-react) | The renderer as a React component — the library kept, the scene still a scene. |
182
+ | [`scenic-prism-playwright`](https://www.npmjs.com/package/scenic-prism-playwright) | Scenes to image files, from Node. |
183
+
184
+ All of them are released together and carry the same version.
185
+
186
+ ## Licence
187
+
188
+ [PolyForm Noncommercial 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0).
189
+
190
+ Free for any **noncommercial** purpose — personal projects, study, experiments,
191
+ hobby and amateur work, teaching, and use by charities, schools, universities,
192
+ public research bodies, public health and safety organisations and government,
193
+ whoever is funding them. Change it, build on it and redistribute it on those
194
+ same terms, passing on the licence and its `Required Notice:` line with any copy.
195
+
196
+ **Commercial use needs a separate licence.** If you want to use `scenic-prism`
197
+ in or around a commercial product, ask via
198
+ [the repository](https://github.com/jamesporter/Scenic-Draft).
199
+
200
+ The terms in full are in the `LICENSE` file shipped inside this package, and
201
+ explained in plain language at
202
+ [scenic-prism.pages.dev/license](https://scenic-prism.pages.dev/license).
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The TypeScript that goes in the `.tsx` file.
3
+ *
4
+ * This is `scenic-prism-react`'s `SceneRenderer` with the library taken out, in
5
+ * the same way `runtime.ts` is the core renderer with the library taken out.
6
+ * The scene has already become a shader by the time this runs, so what is left
7
+ * is one component holding one canvas: ask for a GPU, build two pipelines,
8
+ * allocate one float buffer, and add one sample per pixel per animation frame
9
+ * until the budget is spent — inside an effect, so it starts when the component
10
+ * mounts, restarts when the size changes and gives its GPU back when it
11
+ * unmounts.
12
+ *
13
+ * Everything here is emitted verbatim into a file somebody else's build will
14
+ * compile, so two rules hold. It must type-check under `strict` against a
15
+ * consumer's own React and DOM library — which is why the WebGPU flags are
16
+ * written as numbers, since TypeScript's DOM library declares the device API
17
+ * but not the `GPUBufferUsage` namespace. And it may import nothing but
18
+ * `react`: the point of the file is that there is nothing to add to the
19
+ * project it is pasted into.
20
+ */
21
+ import type { ColorSpace } from 'scenic-prism';
22
+ /** The values `buildStandaloneTsx` bakes into the top of the file. */
23
+ export interface ComponentConfig {
24
+ /** The component's name; its props interface is this plus `Props`. */
25
+ name: string;
26
+ /** Default backing resolution, which the `size`/`width`/`height` props override. */
27
+ width: number;
28
+ height: number;
29
+ /** Default sample budget, which the `maxFrames` prop overrides. */
30
+ maxFrames: number;
31
+ /** A fixed base seed, or `null` for a fresh sample sequence on every mount. */
32
+ seed: number | null;
33
+ /** The tracer's workgroup, which the image is divided by to dispatch it. */
34
+ workgroup: readonly [number, number];
35
+ /** The two shader sources: the scene's compute pass, and the tonemap. */
36
+ trace: string;
37
+ display: string;
38
+ /** Which colour space the component shows the render in. */
39
+ colorSpace: ColorSpace;
40
+ /** Whether the render has a transparent background, and so composites. */
41
+ alpha: boolean;
42
+ /** Prepend `'use client'`, for a framework that renders on the server first. */
43
+ useClient: boolean;
44
+ /** Add `export default <name>` under the component. */
45
+ exportDefault: boolean;
46
+ }
47
+ /**
48
+ * The whole `.tsx` file for one scene: the baked constants, the props, then the
49
+ * component that reads them. `min` squeezes the two shaders — which are most of
50
+ * the file — and leaves the component alone, since that half is meant to be
51
+ * read, and whatever bundles it will minify it anyway.
52
+ */
53
+ export declare function componentSource(config: ComponentConfig, min?: boolean): string;