softbox 4.0.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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 LEMing
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,270 @@
1
+ # softbox
2
+
3
+ [![npm version](https://img.shields.io/npm/v/softbox)](https://www.npmjs.com/package/softbox)
4
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/softbox)](https://bundlephobia.com/package/softbox)
5
+ [![CI](https://github.com/LEMing/softbox/actions/workflows/ci.yml/badge.svg)](https://github.com/LEMing/softbox/actions/workflows/ci.yml)
6
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/LEMing/softbox/blob/main/LICENSE)
7
+
8
+ **The batteries-included React 3D viewer — any GLB looks studio-shot in one line.**
9
+
10
+ > Formerly published as [`threedviewer`](https://www.npmjs.com/package/threedviewer) — renamed in 4.0.0, same API. See [MIGRATION_GUIDE.md](MIGRATION_GUIDE.md).
11
+
12
+ ```tsx
13
+ <SimpleViewer object="/model.glb" />
14
+ ```
15
+
16
+ Balanced studio lighting, a glossy glass floor with a contact shadow, auto-framing, compressed-asset decoders — all on by default, zero configuration, zero external CDN requests. The core is ~105 kB gzip; the path tracer and the decoders live in lazy chunks that load only when actually used. Every release is gated by 470+ unit tests plus a Playwright suite that asserts **real WebGL pixels** in CI.
17
+
18
+ [![softbox playground](https://raw.githubusercontent.com/LEMing/softbox/main/public/og-image.png)](https://leming.github.io/softbox/)
19
+
20
+ **▶ [Live playground](https://leming.github.io/softbox/)** — drag & drop your own `.glb`, switch presets live, click the model to pin hotspots, download a still.
21
+
22
+ ## What you get
23
+
24
+ - **[Visual presets](#visual-presets)** — `studio · product · neutral · dark · outdoor`, one word, switched live
25
+ - **[Turntable](#turntable)** — one-word showcase auto-rotate that pauses while the user drags
26
+ - **[Animations](#animations)** — GLTF clips autoplay with one word; play/pause/clip-picker API
27
+ - **[Photoreal mode](#photoreal-mode--stills)** — progressive path tracing + `captureStill()` PNG export
28
+ - **[Video capture](#video-capture)** — `captureVideo()` records the canvas to WebM/MP4, zero dependencies
29
+ - **[Hotspots & click selection](#hotspot-annotations--click-selection)** — DOM pins on world-space points, BVH-accelerated picking
30
+ - **Compressed assets** — DRACO, KTX2/Basis and Meshopt decoders wired in, fetched lazily
31
+ - **Built-in chrome** — loading overlay, preset picker, viewport gizmo — all optional, all off-or-subtle by default
32
+ - **Typed end to end** — every option, event payload and handle method ships TypeScript types
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ npm install softbox
38
+ ```
39
+
40
+ Peer dependencies: `react` / `react-dom` `>=18 <20`, `three` `>=0.177 <0.186`.
41
+
42
+ ## Quick start
43
+
44
+ ```tsx
45
+ import { SimpleViewer } from 'softbox';
46
+
47
+ function App() {
48
+ return (
49
+ <div style={{ width: '100%', height: '400px' }}>
50
+ <SimpleViewer object="https://modelviewer.dev/shared-assets/models/RobotExpressive.glb" />
51
+ </div>
52
+ );
53
+ }
54
+ ```
55
+
56
+ A `THREE.Object3D` works too:
57
+
58
+ ```tsx
59
+ <SimpleViewer object={new THREE.Mesh(geometry, material)} />
60
+ ```
61
+
62
+ ## Visual presets
63
+
64
+ One word sets a cohesive look — background, tone mapping, environment intensity. Presets switch **live**: no rebuild, no model reload.
65
+
66
+ ```tsx
67
+ <SimpleViewer object={url} preset="product" />
68
+ // studio | product | neutral | dark | outdoor
69
+ ```
70
+
71
+ Let your users switch presets with the built-in picker (off by default):
72
+
73
+ ```tsx
74
+ <SimpleViewer object={url} options={{ ui: { presets: true } }} />
75
+ ```
76
+
77
+ ## Turntable
78
+
79
+ One word puts the model on a slowly rotating showcase turntable. It pauses while the user drags and resumes on release; toggling is live, like presets.
80
+
81
+ ```tsx
82
+ <SimpleViewer object={url} turntable />
83
+ // speed: options.controls.autoRotateSpeed (2 ≈ one orbit / 30 s)
84
+ ```
85
+
86
+ ## Animations
87
+
88
+ One word plays everything the GLTF carries — all clips, looped, from the moment the model loads.
89
+
90
+ ```tsx
91
+ <SimpleViewer object={url} animations />
92
+ ```
93
+
94
+ Pick one clip, tune the speed, or build your own play/pause UI via the handle:
95
+
96
+ ```tsx
97
+ <SimpleViewer object={url} options={{ animations: { autoplay: 'Walk', speed: 1.5 } }} />
98
+
99
+ viewerRef.current.getAnimationNames(); // ['Walk', 'Idle', ...]
100
+ viewerRef.current.pauseAnimations();
101
+ viewerRef.current.playAnimations('Idle');
102
+ ```
103
+
104
+ Turntable and animations compose — a spinning, walking robot is two words.
105
+
106
+ ## Photoreal mode & stills
107
+
108
+ ```tsx
109
+ <SimpleViewer object={url} pathTraced />
110
+ ```
111
+
112
+ `pathTraced` turns on progressive path tracing (a construction-time render mode). Capture a PNG programmatically — in raster mode at any resolution, in path-traced mode once the accumulation completes:
113
+
114
+ ```tsx
115
+ const handle = viewerRef.current;
116
+ const dataUrl = await handle.captureStill({ width: 1920 }); // PNG data URL
117
+ ```
118
+
119
+ ## Video capture
120
+
121
+ Record the live canvas into a clip — no dependencies, straight through `MediaRecorder` (WebM in Chromium/Firefox, MP4 in Safari). Motion is captured as it happens, so `turntable` + `captureVideo` is a ready-made product-card orbit:
122
+
123
+ ```tsx
124
+ <SimpleViewer ref={viewerRef} object={url} turntable />
125
+
126
+ const blob = await viewerRef.current.captureVideo({ duration: 5 }); // Blob
127
+ // also: fps, mimeType, videoBitsPerSecond
128
+ ```
129
+
130
+ ## Hotspot annotations & click selection
131
+
132
+ Pin DOM content to a world-space point — it tracks orbiting, zooming and resizes:
133
+
134
+ ```tsx
135
+ import { SimpleViewer, Hotspot } from 'softbox';
136
+
137
+ <SimpleViewer object="/model.glb">
138
+ <Hotspot position={[0, 1.2, 0]}>
139
+ <div className="pin">Engine</div>
140
+ </Hotspot>
141
+ <Hotspot position={[0.4, 0.2, 0.8]} occlude /> {/* hidden when the model covers it */}
142
+ </SimpleViewer>
143
+ ```
144
+
145
+ Without children a built-in dot pin is rendered. `occlude` hides the hotspot while the model blocks the line of sight to its anchor.
146
+
147
+ A click on the model (drags and pinches are ignored) emits `object:selected` with the hit object and world-space point — feed it straight into a `<Hotspot>`:
148
+
149
+ ```tsx
150
+ handle.events.on('object:selected', ({ object, point }) => {
151
+ addPin([point.x, point.y, point.z]);
152
+ });
153
+ ```
154
+
155
+ Raycasts are BVH-accelerated per loaded model (logarithmic on high-poly meshes); opt out with `options.selection = { bvh: false }`.
156
+
157
+ ## Events
158
+
159
+ Subscribe through the imperative handle:
160
+
161
+ ```tsx
162
+ const { events } = viewerRef.current;
163
+ events.on('model:loaded', ({ model, loadTime }) => console.log('loaded in', loadTime, 'ms'));
164
+ ```
165
+
166
+ | Event | Fires |
167
+ |---|---|
168
+ | `model:loading` / `model:loaded` / `model:error` | around every model load |
169
+ | `render:complete` | after each rendered frame |
170
+ | `pathtracing:complete` | once the sample cap is reached |
171
+ | `controls:change` | when the camera controls move |
172
+ | `object:selected` | when a click hits the loaded model (`{ object, point }`) |
173
+ | `error` | any viewer error |
174
+
175
+ ## Imperative handle
176
+
177
+ ```tsx
178
+ const viewerRef = useRef<SimpleViewerHandle>(null);
179
+ <SimpleViewer ref={viewerRef} object={url} />
180
+ ```
181
+
182
+ The handle exposes `scene`, `camera`, `renderer`, `controls`, `events`, `loadModel(source)`, `captureStill(options?)`, `captureVideo(options?)`, `getAnimationNames()`, `playAnimations(name?)`, `pauseAnimations()` and `dispose()`.
183
+
184
+ ## Configuration
185
+
186
+ Everything is optional — the defaults are the point. Pass `options` to override any part:
187
+
188
+ ```ts
189
+ import { ControlType, defaultOptions } from 'softbox';
190
+
191
+ const options: SimpleViewerOptions = {
192
+ preset: 'studio', // one-word look; explicit options below win over it
193
+ backgroundColor: '#f0f0f7',
194
+ staticScene: false, // stop the render loop when nothing moves
195
+
196
+ camera: { position: [60, 60, 60], fov: 45, autoFitToObject: true },
197
+
198
+ // The procedural studio environment lights the scene by default (zero network
199
+ // requests). Point environment.url at an HDR/EXR/image to use your own.
200
+ environment: { environmentIntensity: 0.7 },
201
+
202
+ renderer: { antialias: true, toneMappingExposure: 1.1 },
203
+
204
+ controls: { type: ControlType.OrbitControls, enableDamping: true, autoRotate: false },
205
+
206
+ animations: { autoplay: true, speed: 1 }, // GLTF clip playback
207
+
208
+ helpers: {
209
+ grid: { type: 'hexagonal_glass' }, // glossy glass floor; also: hexagonal_wire, square_wire, stone_tiles
210
+ gizmo: false, // optional viewport orientation gizmo
211
+ studioEnvironment: true,
212
+ },
213
+
214
+ pathTracing: { enabled: false, maxSamples: 16 },
215
+
216
+ ui: { presets: false }, // built-in preset picker chips
217
+ selection: { bvh: true }, // BVH-accelerated raycasts for picking/occlusion
218
+ loadingIndicator: true, // built-in loading overlay (object form customizes it)
219
+ loaders: {}, // DRACO/KTX2/Meshopt decoder config (self-host paths, toggles)
220
+ };
221
+ ```
222
+
223
+ See [`defaultOptions`](https://github.com/LEMing/softbox/blob/main/src/defaultOptions.ts) for the full annotated set and the typed option interfaces exported from the package root.
224
+
225
+ ### Loading indicator
226
+
227
+ A built-in overlay shows while a model loads (and an error state if it fails) — the scene is never blank. Customize or disable it:
228
+
229
+ ```tsx
230
+ <SimpleViewer object={url} options={{ loadingIndicator: { label: 'Loading…' } }} />
231
+ <SimpleViewer object={url} options={{ loadingIndicator: false }} /> // drive your own via events
232
+ ```
233
+
234
+ ### Viewport gizmo
235
+
236
+ ```ts
237
+ helpers: { gizmo: { placement: 'top-right', size: 128 } }
238
+ ```
239
+
240
+ Interactive orientation cube: click to snap the camera to axis views, synchronized with the controls.
241
+
242
+ ### Path tracing options
243
+
244
+ `pathTracing`: `enabled`, `maxSamples` (completion cap), `bounces`, `transmissiveBounces`, `renderScale`, `lowResScale`, `dynamicLowRes`. With `replaceWithScreenshotOnComplete: true` (default) the finished frame replaces the live canvas until the user interacts.
245
+
246
+ ## Next.js / SSR
247
+
248
+ The viewer renders into a WebGL canvas, so it is client-only. In Next.js, load it dynamically:
249
+
250
+ ```tsx
251
+ 'use client';
252
+ import dynamic from 'next/dynamic';
253
+
254
+ const SimpleViewer = dynamic(
255
+ () => import('softbox').then((m) => m.SimpleViewer),
256
+ { ssr: false }
257
+ );
258
+ ```
259
+
260
+ ## Upgrading
261
+
262
+ - **3.x → 4.0**: the package was renamed — `threedviewer` is now **`softbox`**. Swap the dependency and the import specifier; the API is unchanged. See [MIGRATION_GUIDE.md](MIGRATION_GUIDE.md).
263
+ - **3.0 → 3.x**: additive all the way — presets, `ui`, `pathTraced`, `captureStill`, `Hotspot`, `object:selected`, `selection`, `turntable`, `animations` are new surface on top of 3.0; existing options keep working. React peer is `>=18 <20`.
264
+ - **2.x → 3.0**: breaking release (removed no-op APIs, ESM+CJS packaging). See [MIGRATION_GUIDE.md](https://github.com/LEMing/softbox/blob/main/MIGRATION_GUIDE.md).
265
+
266
+ Full history: [CHANGELOG.md](https://github.com/LEMing/softbox/blob/main/CHANGELOG.md)
267
+
268
+ ## License
269
+
270
+ MIT