leafthrough 0.1.5 → 0.1.7
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/README.md +95 -10
- package/dist/LeafThroughObject.d.ts +10 -2
- package/dist/{chunk-GYSQO7QZ.js → chunk-KCIOFDSW.js} +259 -87
- package/dist/chunk-KCIOFDSW.js.map +1 -0
- package/dist/element.js +1 -1
- package/dist/engine.d.ts +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +49 -7
- package/dist/index.js.map +1 -1
- package/dist/options.d.ts +32 -1
- package/dist/paper/paperMaterial.d.ts +7 -5
- package/dist/paper/paperPattern.d.ts +6 -0
- package/dist/react.js +1 -1
- package/dist/shared.d.ts +19 -0
- package/dist/single/SingleEngine.d.ts +4 -0
- package/dist/single/singleSheet.d.ts +8 -0
- package/dist/spread/SpreadEngine.d.ts +2 -0
- package/dist/spread/spreadSheet.d.ts +8 -0
- package/dist/svelte.js +1 -1
- package/dist/textures.d.ts +5 -0
- package/dist/vue.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-GYSQO7QZ.js.map +0 -1
package/README.md
CHANGED
|
@@ -145,7 +145,9 @@ draws with your renderer and takes pointer input through your camera, so it turn
|
|
|
145
145
|
it lies, however it's moved, turned or scaled. Every option, method, property and event of
|
|
146
146
|
`LeafThrough` works the same, apart from those about its own canvas and DOM: the options
|
|
147
147
|
`bleed`, `maxPixelRatio`, `plain`, `label` and `formatPageLabel`, and the properties
|
|
148
|
-
`plain` and `element`.
|
|
148
|
+
`plain` and `element`. It has one option of its own, `deform`
|
|
149
|
+
([Reshaping the paper](#reshaping-the-paper)), and its sheets stand apart by default
|
|
150
|
+
([Depth](#depth)).
|
|
149
151
|
|
|
150
152
|
```ts
|
|
151
153
|
import { LeafThroughObject } from 'leafthrough'
|
|
@@ -183,7 +185,15 @@ renderer.setAnimationLoop((now) => {
|
|
|
183
185
|
sheets cast and receive shadows when your renderer draws them, and `sun.position`
|
|
184
186
|
should point towards your shadow-casting directional light, in the magazine's own
|
|
185
187
|
frame (only the direction counts). It places the shadow a turning sheet casts right by
|
|
186
|
-
the spine, where the shadow map can't.
|
|
188
|
+
the spine, where the shadow map can't. `update({ lighting: { shadows } })` switches the
|
|
189
|
+
sheets' shadows without a rebuild. Use it rather than setting `castShadow` or
|
|
190
|
+
`receiveShadow` on the sheets, which the magazine sets itself.
|
|
191
|
+
- **Depth.** See [Depth](#depth): the sheets stand a little apart, which needs enough depth
|
|
192
|
+
precision from your camera.
|
|
193
|
+
- **Several magazines.** Magazines drawn by the same renderer share the built-in grain's
|
|
194
|
+
texture and the sheet geometry of each layout and page aspect: made for the first, freed
|
|
195
|
+
with the last. The grain itself is drawn once per page, and the wrinkles are sampled once
|
|
196
|
+
per aspect.
|
|
187
197
|
- **Rendering on demand.** If you only draw when something changes, listen for `change`:
|
|
188
198
|
it fires when input, loading or a method call needs a frame. Then keep drawing while
|
|
189
199
|
`tick()` returns true.
|
|
@@ -197,11 +207,72 @@ magazine.covers(e.clientX, e.clientY) // whether the magazine takes a press t
|
|
|
197
207
|
magazine.bounds // { pages, reach }, in the magazine's own units
|
|
198
208
|
magazine.camera = otherCamera // cast input from another camera
|
|
199
209
|
magazine.enabled = false // cancel active gestures and stop taking pointer/key input
|
|
210
|
+
magazine.update({ lighting: { shadows: false } }) // sheets stop casting and receiving, no rebuild
|
|
200
211
|
magazine.addEventListener('change', () => requestRender())
|
|
201
212
|
magazine.addEventListener('pagechange', (e) => e.detail) // also ready, pageload, error
|
|
202
213
|
magazine.dispose() // free everything and leave the scene
|
|
203
214
|
```
|
|
204
215
|
|
|
216
|
+
### Depth
|
|
217
|
+
|
|
218
|
+
In a `LeafThroughObject` the sheets stand apart, `paper.sheetGap` `0.0005` page widths
|
|
219
|
+
(0.1 mm paper on a 210 mm page), and sort by their real depth, so they stay in order from the front, from
|
|
220
|
+
behind and at any angle. Your camera has to resolve that gap. A 24-bit depth buffer tells
|
|
221
|
+
apart points roughly `distance² / (near × 16,777,216)` apart, in your scene's units, and that
|
|
222
|
+
must be well under the gap in the same units. Take a booklet 120 mm wide in a scene in
|
|
223
|
+
metres, seen from 4.4 m: its gap is 0.06 mm. With the camera's `near` at 0.1 the buffer
|
|
224
|
+
resolves about 0.012 mm, which is enough; at 0.01 it's about 0.12 mm, and hidden pages
|
|
225
|
+
flicker through. So keep `near` as large as your scene allows; `far` matters much less.
|
|
226
|
+
The magazine never changes your camera. With a coarse depth buffer you can widen the gap.
|
|
227
|
+
|
|
228
|
+
The standalone magazine instead draws sheets that touch at the spine and lets
|
|
229
|
+
`paper.depthBias` sort them: a polygon offset brings each sheet nearer the camera than
|
|
230
|
+
those under it. You can turn that on here too (`paper: { sheetGap: 0, depthBias: true }`),
|
|
231
|
+
but it has two limits in a scene of your own. It assumes the magazine is seen from the
|
|
232
|
+
front, so from behind it brings the wrong sheets forward. And it moves sheets by steps of
|
|
233
|
+
the depth buffer, so how far depends on `near`, `far` and distance: with a small `near`
|
|
234
|
+
it can pull pages through whatever lies just in front of the magazine.
|
|
235
|
+
|
|
236
|
+
### Reshaping the paper
|
|
237
|
+
|
|
238
|
+
Experimental: this may change in a patch release. `deform` reshapes the paper with your
|
|
239
|
+
own GLSL, after the magazine has shaped it: bend a booklet coming out of its case, squeeze
|
|
240
|
+
it, roll it. It applies wherever the paper is drawn, in colour and in the shadow passes,
|
|
241
|
+
and lighting follows, because normals come from the deformed shape.
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
const bend = { value: 0 }
|
|
245
|
+
const magazine = new LeafThroughObject({ renderer, camera }, {
|
|
246
|
+
pages,
|
|
247
|
+
deform: {
|
|
248
|
+
uniforms: { uBend: bend },
|
|
249
|
+
glsl: /* glsl */ `
|
|
250
|
+
uniform float uBend;
|
|
251
|
+
vec3 deformSheet(vec3 p, vec2 uv) {
|
|
252
|
+
// curl the pages up along x, around a cylinder of radius 1 / uBend
|
|
253
|
+
if (uBend < 1e-4) return p;
|
|
254
|
+
float a = p.x * uBend;
|
|
255
|
+
float r = 1.0 / uBend - p.z;
|
|
256
|
+
return vec3(r * sin(a), p.y, 1.0 / uBend - r * cos(a));
|
|
257
|
+
}`,
|
|
258
|
+
},
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
bend.value = 1.2 // read on the next frame: no rebuild
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
- `deformSheet` gets `p`, a point of a sheet in the magazine's own units (see Units
|
|
265
|
+
above), and `uv`, where it is on that sheet: x from the spine (0) to the outer edge (1),
|
|
266
|
+
y from bottom to top. It returns where to draw that point, in the same units.
|
|
267
|
+
- three.js's common GLSL (`PI`, `saturate` and so on) is available. Name your uniforms
|
|
268
|
+
with a prefix of your own, as the magazine's start with `u`.
|
|
269
|
+
- Uniforms are read every frame: set `value` on your own objects, or pass new objects
|
|
270
|
+
under the same names through `update()`. Changing the GLSL, or which uniforms there are,
|
|
271
|
+
rebuilds the pages.
|
|
272
|
+
- It changes only what's drawn. Pointer input, `covers()`, `bounds` and the shadow a
|
|
273
|
+
turning sheet casts by the spine still work from the undeformed pages, so turn input off
|
|
274
|
+
(`enabled = false`) while the paper is far from its own shape.
|
|
275
|
+
|
|
205
276
|
The [three.js demo](https://dmtrbrl.github.io/leafthrough/three.html) takes a magazine
|
|
206
277
|
out of a portrait paperboard mailer on a table; its source is
|
|
207
278
|
[`demo/main-three.ts`](https://github.com/dmtrbrl/leafthrough/blob/main/demo/main-three.ts),
|
|
@@ -244,14 +315,14 @@ The magazine fills the element you give it and keeps its proportions, centred.
|
|
|
244
315
|
| `formatPageLabel` | `(detail) => string` | `"Pages 14–15 of 28"` | What screen readers hear when pages change. |
|
|
245
316
|
| `onPageChange` | `(detail) => void` | | Same as the `pagechange` event. |
|
|
246
317
|
| `onReady` | `() => void` | | The first pages are loaded and on screen. |
|
|
247
|
-
| `onPageLoad` | `(page) => void` | | A page's image loaded. Same as the `pageload` event.
|
|
318
|
+
| `onPageLoad` | `(page) => void` | | A page's image is loaded and on the GPU. Same as the `pageload` event. |
|
|
248
319
|
| `onError` | `(error, page?) => void` | | A page failed to load. It stays blank paper until `retry()`. |
|
|
249
320
|
|
|
250
321
|
**`paper`**
|
|
251
322
|
|
|
252
323
|
| | Default | |
|
|
253
324
|
| ------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
254
|
-
| `grain` | `true` | `false` turns it off. You can also pass a tiling greyscale image or its URL; only its brightest specks show. |
|
|
325
|
+
| `grain` | `true` | `false` turns it off. You can also pass a tiling greyscale image or its URL; only its brightest specks show. The built-in one is drawn once per page, however many magazines there are. |
|
|
255
326
|
| `grainScale` | `1.46` | Tiles across a page. |
|
|
256
327
|
| `grainStrength` | `0.17` | How much the grain darkens. |
|
|
257
328
|
| `showThrough` | spread `0.04`, single `0.3` | How much of the other side shows through the paper. |
|
|
@@ -261,6 +332,8 @@ The magazine fills the element you give it and keeps its proportions, centred.
|
|
|
261
332
|
| `wrinkles` | `1` | Waviness of resting sheets; `0` lays them flat. |
|
|
262
333
|
| `bulge` | `1` | How much pages rise off the spine. |
|
|
263
334
|
| `cutout` | `false` | Cuts each sheet to the shape of its front page: transparent areas are no paper at all, shadow included. |
|
|
335
|
+
| `sheetGap` | `0` (`0.0005` in a scene) | Space between stacked sheets, in page widths. 0.1 mm paper on a 120 mm page is `0.1 / 120`. |
|
|
336
|
+
| `depthBias` | `true` (`false` in a scene) | Draws each sheet nearer the camera than those under it, so sheets touching at the spine don't flicker. See [Depth](#depth). |
|
|
264
337
|
|
|
265
338
|
**`lighting`**: `ambient: { sky, ground, intensity }`, `sun: { color, intensity, position }`,
|
|
266
339
|
`shadows` (default `true`), `shadowMapSize` (default `2048`), and `floorShadow`. That last
|
|
@@ -311,7 +384,8 @@ magazine.addEventListener('error', (e) => e.detail) // { error, page }
|
|
|
311
384
|
|
|
312
385
|
Both compare options by value. Changing callbacks or the label is free. Changing `pages`,
|
|
313
386
|
`layout`, paper, lighting, motion or interaction rebuilds the scene (a few milliseconds)
|
|
314
|
-
and keeps the reader on the same page.
|
|
387
|
+
and keeps the reader on the same page. In a `LeafThroughObject`, `lighting.shadows` and
|
|
388
|
+
new values for `deform`'s uniforms apply without a rebuild. `startPage` only applies at creation; use `goTo()`
|
|
315
389
|
after that. The React, Vue and Svelte bindings and the element use `setOptions()`, so
|
|
316
390
|
removing a prop or attribute restores its default. Options are copied on every call, so an
|
|
317
391
|
object or array changed in place and passed again (as Vue's reactive props are) counts as
|
|
@@ -341,11 +415,17 @@ pages: [
|
|
|
341
415
|
front page's outline (draw the back page as its mirror image).
|
|
342
416
|
- **Cross-origin URLs.** URLs on another origin need CORS headers, because the images are
|
|
343
417
|
uploaded to WebGL.
|
|
344
|
-
- **Textures.** A three.js `Texture` you pass in is never disposed,
|
|
345
|
-
`colorSpace`, `premultiplyAlpha` and `anisotropy
|
|
346
|
-
|
|
418
|
+
- **Textures.** A three.js `Texture` you pass in is taken as it is: never disposed, and its
|
|
419
|
+
`colorSpace`, `premultiplyAlpha` and `anisotropy` are left alone, so one that's on the
|
|
420
|
+
GPU already, or that your own material uses too, isn't uploaded again. Pages the magazine
|
|
421
|
+
loads itself are sRGB and premultiplied; for yours to look the same, set `colorSpace` to
|
|
422
|
+
`THREE.SRGBColorSpace`, and `premultiplyAlpha` to `true` if it has transparent parts (the
|
|
423
|
+
shader lays it over the paper as premultiplied colour).
|
|
347
424
|
- **Load order.** Pages load nearest the reader first, four at a time, and the order follows
|
|
348
|
-
the reader as they turn or jump. Pages that haven't arrived show blank paper.
|
|
425
|
+
the reader as they turn or jump. Pages that haven't arrived show blank paper. Once decoded,
|
|
426
|
+
the pages in view and next to them go to the GPU at once; the rest go one per frame, or
|
|
427
|
+
more in idle time, so a dozen arriving together don't drop a frame. `pageload` fires for
|
|
428
|
+
each as it's on the GPU.
|
|
349
429
|
- **Memory.** Pages within `preload` of the visible ones load; pages more than twice that
|
|
350
430
|
far away unload, freeing their GPU memory, and load again when the reader comes back.
|
|
351
431
|
Visible means anything on screen: pages still turning in a quick riffle, and in `single`
|
|
@@ -402,7 +482,10 @@ How it works:
|
|
|
402
482
|
- `content.ts` keeps the visible pages' content as DOM: hidden for screen readers in 3D,
|
|
403
483
|
the pages themselves in the plain view.
|
|
404
484
|
- `textures.ts` loads, prioritises and unloads page textures.
|
|
405
|
-
- `src/paper` holds the shared paper material and the procedural grain.
|
|
485
|
+
- `src/paper` holds the shared paper material and the procedural grain. Sheets keep their
|
|
486
|
+
order by real depth, each lifted by `paper.sheetGap` for the sheets under it, or by a
|
|
487
|
+
polygon offset (`layerSheet`) where they touch. `deform` wraps the layout's sheet shape
|
|
488
|
+
in every pass.
|
|
406
489
|
- `LeafThrough.ts` handles the DOM, sizing, loading, layout switching, keyboard and
|
|
407
490
|
events. `LeafThroughObject.ts` does the same inside someone else's scene, without the DOM.
|
|
408
491
|
- Engines take input through `EngineInput` (`engine.ts`): an element to listen on, a ray
|
|
@@ -410,6 +493,8 @@ How it works:
|
|
|
410
493
|
their own floor plane, so the same code serves the magazine's own camera and any scene's.
|
|
411
494
|
- `navigation.ts` maps pages to engine positions, riffles an animated `goTo()` and binds
|
|
412
495
|
the keyboard, for both.
|
|
496
|
+
- `test/depth.test.ts` prints the pages that should be hidden pure red and checks that
|
|
497
|
+
none shows, across layouts, views, paper shapes, turns and camera depth ranges.
|
|
413
498
|
|
|
414
499
|
## Credits
|
|
415
500
|
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
2
|
import { type Rect } from './framing.js';
|
|
3
3
|
import type { GoToOptions } from './LeafThrough.js';
|
|
4
|
-
import { type LeafThroughOptions, type Layout, type PageChangeDetail } from './options.js';
|
|
4
|
+
import { type LeafThroughOptions, type Layout, type PageChangeDetail, type PaperDeform } from './options.js';
|
|
5
5
|
/**
|
|
6
6
|
* The magazine's options, less those about its own canvas and DOM. Of `lighting`, only
|
|
7
7
|
* `shadows` (whether sheets cast and receive them) and `sun.position` apply: the lights are
|
|
8
8
|
* your scene's. Point `sun.position` towards your shadow-casting directional light, in the
|
|
9
9
|
* magazine's own frame; it places the shadow a turning sheet casts right by the spine.
|
|
10
|
+
* `lighting.shadows` changes without a rebuild.
|
|
11
|
+
*
|
|
12
|
+
* Its sheets stand apart by default (`paper.sheetGap` `0.0005`, about 0.1 mm paper on a
|
|
13
|
+
* 210 mm page) and `paper.depthBias` is off, so they sort by real depth from any side.
|
|
10
14
|
*/
|
|
11
|
-
export type LeafThroughObjectOptions = Omit<LeafThroughOptions, 'bleed' | 'maxPixelRatio' | 'plain' | 'label' | 'formatPageLabel'
|
|
15
|
+
export type LeafThroughObjectOptions = Omit<LeafThroughOptions, 'bleed' | 'maxPixelRatio' | 'plain' | 'label' | 'formatPageLabel'> & {
|
|
16
|
+
/** Reshapes the paper in your own GLSL. Experimental. */
|
|
17
|
+
deform?: PaperDeform;
|
|
18
|
+
};
|
|
12
19
|
/** What the magazine draws with, from your scene. */
|
|
13
20
|
export interface LeafThroughObjectContext {
|
|
14
21
|
/** The renderer drawing your scene. Pages load into it. */
|
|
@@ -138,6 +145,7 @@ export declare class LeafThroughObject extends THREE.Group<LeafThroughObjectEven
|
|
|
138
145
|
private pickLayout;
|
|
139
146
|
private watchSpreadQuery;
|
|
140
147
|
private remount;
|
|
148
|
+
private resolve;
|
|
141
149
|
private readonly axis;
|
|
142
150
|
private pageAxis;
|
|
143
151
|
private dragWidth;
|