pixi-effects 0.48.2 → 0.50.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/CHANGELOG.md +17 -0
- package/README.md +53 -8
- package/ai/chat-template.html +8 -4
- package/ai/tools/check.mjs +6 -6
- package/dist/3d.cjs +967 -0
- package/dist/3d.cjs.map +1 -0
- package/dist/3d.d.cts +305 -0
- package/dist/3d.d.ts +305 -0
- package/dist/3d.js +960 -0
- package/dist/3d.js.map +1 -0
- package/dist/Canvas-BfZkAFJm.d.cts +12 -0
- package/dist/Canvas-BfZkAFJm.d.ts +12 -0
- package/dist/Composition-47U5U637.cjs +16 -0
- package/dist/{Composition-TJ5D7Z63.cjs.map → Composition-47U5U637.cjs.map} +1 -1
- package/dist/Composition-GVWWLFDL.js +7 -0
- package/dist/{Composition-5FKV2IBP.js.map → Composition-GVWWLFDL.js.map} +1 -1
- package/dist/Controller.d.cts +2 -2
- package/dist/Controller.d.ts +2 -2
- package/dist/{Movie-svZQF-9Z.d.cts → Movie-DogzDBuW.d.cts} +36 -5
- package/dist/{Movie--qSYKgAQ.d.ts → Movie-Y7iHg2V8.d.ts} +36 -5
- package/dist/{Spans-6A3VLHRH.js → Spans-3SEZSTQV.js} +6 -6
- package/dist/{Spans-6A3VLHRH.js.map → Spans-3SEZSTQV.js.map} +1 -1
- package/dist/{Spans-5ENZ3XGS.cjs → Spans-R426NVY3.cjs} +11 -11
- package/dist/{Spans-5ENZ3XGS.cjs.map → Spans-R426NVY3.cjs.map} +1 -1
- package/dist/{blendModes-V5XHC7U4.cjs → blendModes-2IDILRN6.cjs} +5 -5
- package/dist/{blendModes-V5XHC7U4.cjs.map → blendModes-2IDILRN6.cjs.map} +1 -1
- package/dist/{blendModes-535EMYGK.js → blendModes-4QH44GOL.js} +4 -4
- package/dist/{blendModes-535EMYGK.js.map → blendModes-4QH44GOL.js.map} +1 -1
- package/dist/{chunk-NGJXVJB7.cjs → chunk-3FCDRLW3.cjs} +5 -3
- package/dist/chunk-3FCDRLW3.cjs.map +1 -0
- package/dist/{chunk-JBDBICS2.js → chunk-C2HL5236.js} +4 -4
- package/dist/{chunk-JBDBICS2.js.map → chunk-C2HL5236.js.map} +1 -1
- package/dist/{chunk-WYR2AU4W.cjs → chunk-GXGIISFS.cjs} +5 -5
- package/dist/{chunk-WYR2AU4W.cjs.map → chunk-GXGIISFS.cjs.map} +1 -1
- package/dist/{chunk-BFRWSV3R.js → chunk-J4ZP76IL.js} +5 -4
- package/dist/chunk-J4ZP76IL.js.map +1 -0
- package/dist/{chunk-CCPJD76D.cjs → chunk-O2MD3L4J.cjs} +93 -93
- package/dist/chunk-O2MD3L4J.cjs.map +1 -0
- package/dist/{chunk-IVKI6CPH.js → chunk-SSVHAF3Q.js} +483 -20
- package/dist/chunk-SSVHAF3Q.js.map +1 -0
- package/dist/{chunk-6D4W2KZQ.js → chunk-TXNQLMRT.js} +6 -6
- package/dist/chunk-TXNQLMRT.js.map +1 -0
- package/dist/{chunk-RCKLUCB7.js → chunk-UTUUOQ47.js} +46 -20
- package/dist/chunk-UTUUOQ47.js.map +1 -0
- package/dist/{chunk-Z3DM4ETD.cjs → chunk-VVUSZVP7.cjs} +507 -42
- package/dist/chunk-VVUSZVP7.cjs.map +1 -0
- package/dist/{chunk-45ALIMAF.cjs → chunk-YOKRPAKA.cjs} +89 -63
- package/dist/chunk-YOKRPAKA.cjs.map +1 -0
- package/dist/index.cjs +35 -35
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +5 -5
- package/dist/loader.css +1 -1
- package/dist/{music-P4JRT73P.js → music-ATX22VAT.js} +3 -3
- package/dist/{music-P4JRT73P.js.map → music-ATX22VAT.js.map} +1 -1
- package/dist/{music-XQEPBIDT.cjs → music-OFVM3PYH.cjs} +3 -3
- package/dist/{music-XQEPBIDT.cjs.map → music-OFVM3PYH.cjs.map} +1 -1
- package/dist/present.cjs +5 -5
- package/dist/present.d.cts +2 -2
- package/dist/present.d.ts +2 -2
- package/dist/present.js +5 -5
- package/dist/three.cjs +13 -10
- package/dist/three.cjs.map +1 -1
- package/dist/three.d.cts +4 -12
- package/dist/three.d.ts +4 -12
- package/dist/three.js +8 -5
- package/dist/three.js.map +1 -1
- package/dist/{types-CTSrPvY3.d.cts → types-BZngm4nT.d.cts} +48 -5
- package/dist/{types-CTSrPvY3.d.ts → types-BZngm4nT.d.ts} +48 -5
- package/docs/api.md +45 -2
- package/docs/dsl.md +115 -1
- package/llms-full.txt +307 -6
- package/package.json +19 -3
- package/skills/pixi-effects/SKILL.md +4 -2
- package/skills/pixi-effects/reference/cheatsheet.md +4 -0
- package/skills/pixi-effects/reference/pitfalls.md +20 -0
- package/skills/pixi-effects/reference/recipes.md +119 -1
- package/skills/pixi-effects/template.html +14 -8
- package/dist/Composition-5FKV2IBP.js +0 -7
- package/dist/Composition-TJ5D7Z63.cjs +0 -16
- package/dist/chunk-45ALIMAF.cjs.map +0 -1
- package/dist/chunk-6D4W2KZQ.js.map +0 -1
- package/dist/chunk-BFRWSV3R.js.map +0 -1
- package/dist/chunk-CCPJD76D.cjs.map +0 -1
- package/dist/chunk-IVKI6CPH.js.map +0 -1
- package/dist/chunk-NGJXVJB7.cjs.map +0 -1
- package/dist/chunk-RCKLUCB7.js.map +0 -1
- package/dist/chunk-Z3DM4ETD.cjs.map +0 -1
|
@@ -147,3 +147,23 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
|
|
|
147
147
|
117. **A sprite or mask made in `setup` with `document.createElement('canvas')` can be drawn differently the first time.** A GPU canvas used as the source of `drawImage`, under `destination-in` after many anti-aliased strokes, drew up to 128 of 255 differently over 40 000 pixels of a 1280×720 layer the first time and the same every time after. `info.canvas(w, h)` makes a canvas that does not do this; `check` throws away one draw of each moment before it compares, so it does not blame the layer, but an export that draws a frame for the first time is still safer with `info.canvas`. A mask is also easier to get right as: the words first, then `globalCompositeOperation = 'source-atop'` and the texture (recipe `canvas-text-mask`); two `fillText` under `destination-in` intersect.
|
|
148
148
|
118. **`check` does not see text drawn in a canvas.** The layout test (cut-off, off-canvas, overlap) reads the text layers; a label or a caption you draw with `fillText` can run off the edge or collide with another without a word, and "no cut-off text" only speaks for the text layers. Look at the sheet and at `--at … --crop`, and keep a margin.
|
|
149
149
|
119. **`bitrate` is a ceiling, not a target: a quiet or mostly black picture is written far below it, and no setting changes that.** Asking for `'150M'` gave 105 to 151 Mbps on grainy dark pictures and 13 to 18 Mbps on clean ones (a platform that wants at least 35 Mbps for 4K then rejects the file). The encoder spends bits on what changes: a constant bitrate mode reaches the encoder (it was measured, with H.264, HEVC and VP9) and still writes 0.1 Mbps for a black frame with a circle, because no encoder pads. So read the real rate of the file (`render` prints it and says when it is far under what you asked; `npx pixi-effects probe file.mp4` too) and, when a minimum is needed, raise it by what the picture really has: fine film grain (a seeded noise of about ±2/255 in the darks, which also hides banding in dark gradients) costs bits and looks right. Do not stretch it with an invisible pattern.
|
|
150
|
+
|
|
151
|
+
## 3D scenes (`scene3d`, PixiJS 3D)
|
|
152
|
+
|
|
153
|
+
120. **Import `pixi-effects/3d` before `movie.init` and call `register3D()`.** PixiJS loads the 3D drawing code while its renderer starts; a scene3d layer built on a renderer that started before `@pixi/3d` was imported stops the build with `PixiJS 3D was loaded after the renderer started`. The import map needs `pixi-effects/3d`, `@pixi/3d` and `@pixi/3d/extras`, and `pixi.js` 8.23.0 or later. *(warns)*
|
|
154
|
+
121. **`update(t)` sets everything from `t`.** `angle += 0.02` per call, a velocity carried from the last call, `Math.random()` or a clock draw a different picture in playback, a seek and an export; `check` draws the layer in two orders and names it. A model's clip: `model.player.getState(name).gotoAndStop(t % clip.animation.duration)` (a clip left playing with `autoPlay` is played on the layer's time by the layer). *(check catches it)*
|
|
155
|
+
122. **Particle systems (`ParticleSystem3D`) and a `MotionBlurEffect` are not a function of the time.** Particle births add up from frame to frame (a seek does not reproduce them) and motion blur reads the camera of the frame drawn before. Keep particles in 2D (`particles()`) or drive a `ParticleContainer3D` from `t`; leave motion blur out of a scene3d layer. *(check catches it)*
|
|
156
|
+
123. **An inline shape mask needs a `fillColor`.** A circle or rect with no fill masks out everything: the layer disappears without a word. Give the mask shape `initial: { fillColor: '#fff' }`. (Any layer, not only scene3d.)
|
|
157
|
+
124. **`tint` does nothing on a scene3d layer** (PixiJS 3D draws its materials' colours): colour the materials, or put a filter on the layer. `alpha` and `blendMode` do work (the layer applies them). *(warns)*
|
|
158
|
+
125. **PixiJS 3D's units are not pixels (unless the layer has `followCamera`).** The scene has its own units (a glTF is in metres: a fox is about 1.3 units long) and its own camera (`fov` in degrees, default 60, the camera looks down its `-z`); `width` / `height` only size the layer's rectangle. Place the camera with `camera.position.set(…)` and `camera.lookAt({ x, y, z })`; read a model's size with `model.getLocalBounds()` before scaling it.
|
|
159
|
+
126. **A light is aimed by its rotation, a plane lies in XY.** `DirectionalLight` / `SpotLight` look down their local `-z` (`sun.rotation.set(-0.9, 0.6, 0)`, or `lookAt`); `PlaneGeometry` faces `+z`, so a floor is `rotation: { x: -Math.PI / 2, y: 0, z: 0 }`. A view's `toneMapping` is `'none'` by default: strong lights clip, use `view: { toneMapping: 'aces' }`.
|
|
160
|
+
127. **Under `followCamera` the scene is in composition PIXELS, y flipped.** A composition point (x, y, z) is `(x, -y, z)` in the scene: put a model at `position.set(640, -400, 0)` for (640, 400), and size it in pixels (`model.fit(480)`), not in metres. Give the layer the composition's size (another shape warns).
|
|
161
|
+
128. **`followLights` matches where light falls, not how bright the shade is.** PixiJS 3D lights in linear light, the composition's cards shade the sRGB values: a 3D surface under ambient 0.3 is 0.58 × its colour, a card 0.3 ×. A point light casts no shadow in PixiJS 3D (use a spot or a parallel light); `shadowDarkness` / `shadowDiffusion` are not mirrored. *(warns)*
|
|
162
|
+
129. **A 2D layer for `layerTexture` is a `composition` listed BEFORE the scene3d layer**, in the same composition. It is no longer drawn on the stage (it shows only on the mesh). *(warns with the names it found)*
|
|
163
|
+
130. **After a rebuild (`setVars`) the scene is the same but the view is a new one.** Its settings (background, environment, exposure, tone mapping, effects, the active camera) move with the scene; read the view from `ctx.view` (or `objects.view`), not from a variable kept in `setup`.
|
|
164
|
+
|
|
165
|
+
## PixiJS 2D by code (`scene2d`)
|
|
166
|
+
|
|
167
|
+
131. **`update` SETS each object from `t`; it never adds to it.** `g.x += 2` per call, a velocity kept between calls, or a particle list that grows each frame draws a different picture in a seek and an export than in playback (`check` names the layer). Compute where each object is at `t` (`x = x0 + v * t`, `y = y0 + g * t * t / 2`); draw random numbers once with `random(seed)` (before the layer or in `setup`), never `Math.random` in `update`.
|
|
168
|
+
132. **Keyframe paths into a scene2d layer are raw PixiJS values.** `'scene2d.dot.rotation'` is in radians (the layer's own `rotation` keyframe is degrees); `'scene2d.dot.angle'` is degrees. Scale is `'scene2d.dot.scale.x'` and `'…scale.y'` (two paths), colour `'scene2d.dot.tint'`.
|
|
169
|
+
133. **Name what `movie.pick` should name.** A node made without a `label` carries its kind (`'Graphics'`), which counts as no name: the hit is named by its nearest labelled ancestor, or `''`. Give the objects you want to tell apart a `label`, or label a `Container` to name a group. Empty space in the layer's box is not the layer (the point falls through).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pixi-effects recipes
|
|
2
2
|
|
|
3
|
-
Copy, adapt, run. Every block marked `@recipe` is a function body that **returns the composition's `sequences`** (or `{ sequences, transitions, duration }`); the repo's tests build each one and fail on any warning, so they stay correct. Blocks marked `@docs-only` need a browser (canvas / three.js) and are not executed by the tests.
|
|
3
|
+
Copy, adapt, run. Every block marked `@recipe` is a function body that **returns the composition's `sequences`** (or `{ sequences, transitions, duration }`); the repo's tests build each one and fail on any warning, so they stay correct. Blocks marked `@docs-only` need a browser (canvas / 3D / three.js) and are not executed by the tests.
|
|
4
4
|
|
|
5
5
|
Assumed canvas: 1280×720 @ 30 fps. `kenBurns`, `withFade` and `orbit` come from `pixi-effects`.
|
|
6
6
|
|
|
@@ -1292,6 +1292,124 @@ function placeholderPhoto(name, hueA, hueB, w = 1920, h = 1080) {
|
|
|
1292
1292
|
|
|
1293
1293
|
---
|
|
1294
1294
|
|
|
1295
|
+
## PixiJS 2D built by code: a burst of confetti and a ring (`scene2d`)
|
|
1296
|
+
|
|
1297
|
+
When the data cannot say it (160 pieces that each fly their own way), build PixiJS display objects in `setup` and set them from the time in `update`. The pieces' angles and speeds come from `random(seed)`, a repeatable stream, computed once before the layer: every load, every seek and the export draw the same burst. A keyframe reaches an object of the scene as `scene2d.<name>.<path>` (raw PixiJS values: `rotation` in radians). `movie.pick` names the object under a point (its `label`).
|
|
1298
|
+
|
|
1299
|
+
```js
|
|
1300
|
+
// @docs-only scene2d-basics
|
|
1301
|
+
// import { random } from 'pixi-effects'; import { Graphics } from 'pixi.js';
|
|
1302
|
+
// composition 1280 x 720, 4 s.
|
|
1303
|
+
const rand = random(7); // the same numbers at every load
|
|
1304
|
+
const COLORS = ['#ffd166', '#ef476f', '#06d6a0', '#118ab2'];
|
|
1305
|
+
const bits = Array.from({ length: 160 }, () => ({ angle: rand() * Math.PI * 2, speed: 260 + rand() * 520, spin: (rand() - 0.5) * 14, size: 10 + rand() * 16, color: COLORS[Math.floor(rand() * 4)] }));
|
|
1306
|
+
return [
|
|
1307
|
+
{ type: 'shape', shape: 'rect', width: 'GW', height: 'GH', initial: { x: 'GW/2', y: 'GH/2', fillColor: '#101425' } },
|
|
1308
|
+
{ type: 'scene2d', name: 'burst',
|
|
1309
|
+
setup: ({ root, width, height }) => {
|
|
1310
|
+
const pieces = bits.map((b) => root.addChild(new Graphics().rect(-b.size / 2, -b.size / 4, b.size, b.size / 2).fill(b.color)));
|
|
1311
|
+
const ring = new Graphics({ label: 'ring' }).circle(0, 0, 120).stroke({ width: 8, color: '#ffffff' });
|
|
1312
|
+
ring.position.set(width / 2, height / 2);
|
|
1313
|
+
root.addChild(ring);
|
|
1314
|
+
return { objects: { ring, pieces } }; // keyframes reach them as scene2d.ring.…
|
|
1315
|
+
},
|
|
1316
|
+
update: (t, { objects, width, height }) => {
|
|
1317
|
+
objects.pieces.forEach((g, i) => { // where each piece is at t: set, never added to
|
|
1318
|
+
const b = bits[i], d = b.speed * t;
|
|
1319
|
+
g.position.set(width / 2 + Math.cos(b.angle) * d, height / 2 + Math.sin(b.angle) * d + 420 * t * t);
|
|
1320
|
+
g.rotation = b.spin * t;
|
|
1321
|
+
});
|
|
1322
|
+
},
|
|
1323
|
+
keyframes: [
|
|
1324
|
+
{ at: 0, from: { 'scene2d.ring.scale.x': 0.2, 'scene2d.ring.scale.y': 0.2 }, to: { 'scene2d.ring.scale.x': 2.4, 'scene2d.ring.scale.y': 2.4 }, duration: 1, ease: 'expo.out' },
|
|
1325
|
+
{ at: 0.3, to: { 'scene2d.ring.alpha': 0 }, duration: 0.9 },
|
|
1326
|
+
] },
|
|
1327
|
+
{ type: 'text', text: 'HAPPY NEW YEAR', style: { fontSize: 84, fontWeight: '900', fill: '#ffffff', letterSpacing: 6 },
|
|
1328
|
+
initial: { x: 'GW/2', y: 'GH/2', anchorX: 0.5, anchorY: 0.5 },
|
|
1329
|
+
keyframes: [{ at: 0.5, from: { alpha: 0, scale: 0.8 }, duration: 0.6, ease: 'back.out' }] },
|
|
1330
|
+
];
|
|
1331
|
+
```
|
|
1332
|
+
|
|
1333
|
+
The layer is a layer like any other (`at`, `alpha`, `mask`, `filters`, `blendMode`, `threeD`). An `AnimatedSprite` left playing is posed from the layer's time; `ctx.texture(name)` gives a movie asset's texture for a `Sprite`.
|
|
1334
|
+
|
|
1335
|
+
---
|
|
1336
|
+
|
|
1337
|
+
## A 3D scene with a model, a light and a shadow, under a title (`scene3d`)
|
|
1338
|
+
|
|
1339
|
+
A real 3D scene as one layer, drawn by PixiJS 3D inside PixiJS itself: a floor that receives a shadow, a glTF model whose clip is posed from the time, a cube moved by a keyframe, a sun that casts. The layer takes a mask like any layer: a circle wipe reveals it, and a 2D title sits over it. Everything in `update` is set from `t` (a function of the time: playback, a seek and an export agree). `loadModel` names the clips (`model.clips`), measures the model (`model.size()`), fits it (`model.fit(n)`: the longest side n units) and poses it from the time (`model.pose(t)`); `fitCamera(ctx, model.object)` puts the camera where the whole model fits.
|
|
1340
|
+
|
|
1341
|
+
```js
|
|
1342
|
+
// @docs-only scene3d-basics
|
|
1343
|
+
// import { register3D, loadModel } from 'pixi-effects/3d'; register3D(); (before movie.init)
|
|
1344
|
+
// import { Mesh3D, PlaneGeometry, CubeGeometry, Material3D, DirectionalLight } from '@pixi/3d';
|
|
1345
|
+
// composition 1280 x 720, 5 s. 'model.glb' sits next to the page.
|
|
1346
|
+
let model;
|
|
1347
|
+
return [
|
|
1348
|
+
{ type: 'shape', shape: 'rect', width: 'GW', height: 'GH', initial: { x: 'GW/2', y: 'GH/2', fillColor: '#0d1220' } },
|
|
1349
|
+
{ type: 'scene3d', name: 'stage',
|
|
1350
|
+
view: { toneMapping: 'aces', shadowQuality: 'medium', environment: { skyColor: 0xbcd4f0, groundColor: 0x3a3226, intensity: 0.6 } },
|
|
1351
|
+
setup: async ({ root, camera }) => {
|
|
1352
|
+
const floor = new Mesh3D({ geometry: new PlaneGeometry({ width: 40, height: 40 }), material: new Material3D({ baseColor: 0x2a3142, roughness: 0.9 }),
|
|
1353
|
+
rotation: { x: -Math.PI / 2, y: 0, z: 0 }, receiveShadow: true }); // a plane lies in XY: turned flat, it is a floor
|
|
1354
|
+
model = await loadModel('model.glb'); // its first clip, posed from t below
|
|
1355
|
+
model.fit(1.6); // the longest side 1.6 units, whatever the file's own size
|
|
1356
|
+
model.object.position.set(0, 0.8, 0); // its centre: standing on the floor
|
|
1357
|
+
const cube = new Mesh3D({ geometry: new CubeGeometry({ width: 0.8, height: 0.8, depth: 0.8 }), material: new Material3D({ baseColor: 0xff7a1a, roughness: 0.4 }),
|
|
1358
|
+
position: { x: -2.2, y: 0.4, z: 0 }, castShadow: true });
|
|
1359
|
+
const sun = new DirectionalLight({ intensity: 3, castShadow: true });
|
|
1360
|
+
sun.rotation.set(-0.9, 0.6, 0); // lights look down their -z: aim by rotation
|
|
1361
|
+
root.addChild(floor, model.object, cube, sun);
|
|
1362
|
+
camera.position.set(0, 2.2, 6);
|
|
1363
|
+
camera.lookAt({ x: 0, y: 0.6, z: 0 });
|
|
1364
|
+
return { objects: { cube } }; // keyframes reach it as scene3d.cube.…
|
|
1365
|
+
},
|
|
1366
|
+
update: (t, { objects }) => {
|
|
1367
|
+
model.pose(t); // the clip at the layer's time
|
|
1368
|
+
objects.cube.rotation.y = t * 1.5;
|
|
1369
|
+
},
|
|
1370
|
+
keyframes: [{ at: 1, to: { 'scene3d.cube.position.x': 2.2 }, duration: 2.5, ease: 'sine.inOut' }],
|
|
1371
|
+
mask: { type: 'shape', shape: 'circle', radius: 1, initial: { x: 'GW/2', y: 'GH/2', fillColor: '#ffffff' }, // a mask shape needs a fill
|
|
1372
|
+
keyframes: [{ at: 0, to: { radius: 760 }, duration: 1.2, ease: 'power2.inOut' }] } }, // a circle wipe reveals the scene
|
|
1373
|
+
{ type: 'text', text: 'PIXIJS 3D', style: { fontSize: 72, fontWeight: '900', fill: '#ffffff', letterSpacing: 8 },
|
|
1374
|
+
initial: { x: 'GW/2', y: 110, anchorX: 0.5, anchorY: 0.5 },
|
|
1375
|
+
keyframes: [{ at: 0.8, from: { alpha: 0, y: '+=20' }, duration: 0.6, ease: 'power2.out' }] },
|
|
1376
|
+
];
|
|
1377
|
+
```
|
|
1378
|
+
|
|
1379
|
+
Opacity, blend modes and filters work on the layer as on any layer (`alpha` fades the whole scene). It can live in the composition's own camera and lights (`followCamera`, `followLights`: recipe `scene3d-in-space`), be clicked into (`movie.pick` names the object), and show a 2D layer on a mesh (`layerTexture`).
|
|
1380
|
+
|
|
1381
|
+
---
|
|
1382
|
+
|
|
1383
|
+
## A model that runs through the composition's space, past a title (`scene3d`, `followCamera`, `followLights`)
|
|
1384
|
+
|
|
1385
|
+
The scene3d layer lives in the composition's space: its camera IS the composition's `camera` layer and the composition's `light` layers light it, so one camera move carries a flat text card and a 3D model together. The scene is in composition pixels with y flipped: a composition point (x, y, z) is (x, −y, z) in the scene; `fit(px)` sizes a model in metres to pixels. PixiJS 3D lights in linear light, so a 3D surface is brighter in the shade than a flat card under the same light: pick the intensities by looking.
|
|
1386
|
+
|
|
1387
|
+
```js
|
|
1388
|
+
// @docs-only scene3d-in-space
|
|
1389
|
+
// import { register3D, loadModel } from 'pixi-effects/3d'; register3D(); (before movie.init)
|
|
1390
|
+
// composition 1280 x 720, 4 s. 'Fox.glb' sits next to the page; the model faces +z in the file: rotation.y = PI / 2 runs it toward +x.
|
|
1391
|
+
let fox;
|
|
1392
|
+
return [
|
|
1393
|
+
{ type: 'camera', keyframes: [{ at: 0, from: { x: 420, lookAtX: 'GW/2' }, to: { x: 860 }, duration: 4, ease: 'sine.inOut' }] }, // moves the text card AND the model
|
|
1394
|
+
{ type: 'light', kind: 'ambient', initial: { intensity: 0.4 } },
|
|
1395
|
+
{ type: 'light', kind: 'parallel', initial: { x: 140, y: -260, z: 900, lookAtX: 'GW/2', lookAtY: 'GH/2', lookAtZ: 0, intensity: 0.8 } },
|
|
1396
|
+
{ type: 'text', text: 'FOX', threeD: true, style: { fontSize: 420, fontWeight: '900', fill: '#ff7a1a' }, initial: { x: 'GW/2', y: 'GH/2', z: -500, anchorX: 0.5, anchorY: 0.5 } },
|
|
1397
|
+
{ type: 'scene3d', name: 'fox', followCamera: true, followLights: true, // covers the composition and sits in the stack like any layer
|
|
1398
|
+
setup: async ({ root }) => {
|
|
1399
|
+
fox = await loadModel('Fox.glb', { animation: 'Run' }); // a clip name, an index, or false (the bind pose); speed, offset, loop
|
|
1400
|
+
fox.fit(480); // the longest side in px: the model is in metres, the scene in pixels
|
|
1401
|
+
fox.object.rotation.y = Math.PI / 2; // faces +x
|
|
1402
|
+
fox.object.position.set(640, -400, 0); // the composition point (640, 400), y flipped
|
|
1403
|
+
root.addChild(fox.object);
|
|
1404
|
+
},
|
|
1405
|
+
update: (t) => fox.pose(t) }, // the clip at the layer's time: a function of t
|
|
1406
|
+
];
|
|
1407
|
+
```
|
|
1408
|
+
|
|
1409
|
+
Shadows: give a `parallel` or `spot` light `castsShadows: true` (meshes cast and receive by default in PixiJS 3D; a point light casts none). Fog: `followFog: true`.
|
|
1410
|
+
|
|
1411
|
+
---
|
|
1412
|
+
|
|
1295
1413
|
## Metallic three.js object
|
|
1296
1414
|
|
|
1297
1415
|
`ctx.renderer` is a normal `WebGLRenderer`, so an environment map works; lights alone make `metalness: 1` look black. `three/addons` is not in the importmap: build the environment yourself. Size the layer to the area you want (it clips at its own rectangle) and pull the camera back (`z ≈ 5.4` for a radius-1 torus knot at fov 50).
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>pixi-effects video</title>
|
|
7
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
7
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.50.0/dist/loader.css" /> <!-- the loader: HTML + CSS only, so it is on screen at once and keeps moving while the page builds the video -->
|
|
8
8
|
<style>
|
|
9
9
|
body { background: #0d1220; margin: 0; padding: 16px; }
|
|
10
10
|
.stage { position: relative; width: min(960px, 100%); } /* the player and the loader both sit inside this box */
|
|
@@ -35,24 +35,30 @@
|
|
|
35
35
|
<script type="importmap">
|
|
36
36
|
{
|
|
37
37
|
"imports": {
|
|
38
|
-
"pixi.js": "https://esm.sh/pixi.js@8.
|
|
38
|
+
"pixi.js": "https://esm.sh/pixi.js@8.23.0?bundle-deps",
|
|
39
39
|
"mediabunny": "https://esm.sh/mediabunny",
|
|
40
|
-
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
41
|
-
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
42
|
-
"pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
43
|
-
"pixi-effects/
|
|
40
|
+
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.50.0/dist/index.js",
|
|
41
|
+
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.50.0/dist/Controller.js",
|
|
42
|
+
"pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.50.0/dist/present.js",
|
|
43
|
+
"pixi-effects/3d": "https://cdn.jsdelivr.net/npm/pixi-effects@0.50.0/dist/3d.js",
|
|
44
|
+
"@pixi/3d": "https://cdn.jsdelivr.net/npm/@pixi/3d@0.1.0/dist/pixijs-3d.min.mjs",
|
|
45
|
+
"@pixi/3d/extras": "https://cdn.jsdelivr.net/npm/@pixi/3d@0.1.0/dist/pixijs-3d-extras.min.mjs",
|
|
46
|
+
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.50.0/dist/three.js",
|
|
44
47
|
"pixi-filters": "https://esm.sh/pixi-filters@6.1.5?external=pixi.js",
|
|
45
48
|
"three": "https://esm.sh/three@0.178.0",
|
|
46
49
|
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.178.0/examples/jsm/"
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
52
|
</script>
|
|
50
|
-
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
53
|
+
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.50.0/dist/index.js" /> <!-- after the import map: the library downloads while the page is parsed -->
|
|
51
54
|
|
|
52
55
|
<script type="module">
|
|
53
56
|
import { Movie, kenBurns, withFade } from 'pixi-effects';
|
|
54
57
|
import { Controller } from 'pixi-effects/controller';
|
|
55
|
-
//
|
|
58
|
+
// PixiJS 2D objects built by code (Graphics, Sprites, a thousand pieces): { type: 'scene2d', setup({ root }) { root.addChild(…); return { objects } }, update(t, { objects }) { … } } — no import; recipe scene2d-basics
|
|
59
|
+
// import { register3D } from 'pixi-effects/3d'; // a 3D scene (PixiJS 3D: meshes, glTF models, lights) as a layer: call register3D() before init; recipe scene3d-basics
|
|
60
|
+
// import { Mesh3D, Model3D, CubeGeometry, Material3D, DirectionalLight } from '@pixi/3d';
|
|
61
|
+
// import { registerThree, threeGltf } from 'pixi-effects/three'; // a three.js layer instead, for three.js's own loaders, materials and post-processing (call registerThree() before init); threeGltf loads a .glb: recipe gltf-in-space
|
|
56
62
|
// import * as THREE from 'three';
|
|
57
63
|
// A talk instead of a video: import { deck, Presenter } from 'pixi-effects/present'; (the import map above has "pixi-effects/present")
|
|
58
64
|
// const d = deck({ transition: { kind: 'slide', duration: 0.6 }, pages: [{ name, duration, stops: [at, ...], notes, sequences }] });
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export { CompositionSequence } from './chunk-6D4W2KZQ.js';
|
|
2
|
-
import './chunk-IVKI6CPH.js';
|
|
3
|
-
import './chunk-JBDBICS2.js';
|
|
4
|
-
import './chunk-BFRWSV3R.js';
|
|
5
|
-
import './chunk-WGRWFQOB.js';
|
|
6
|
-
//# sourceMappingURL=Composition-5FKV2IBP.js.map
|
|
7
|
-
//# sourceMappingURL=Composition-5FKV2IBP.js.map
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunkCCPJD76D_cjs = require('./chunk-CCPJD76D.cjs');
|
|
4
|
-
require('./chunk-Z3DM4ETD.cjs');
|
|
5
|
-
require('./chunk-WYR2AU4W.cjs');
|
|
6
|
-
require('./chunk-NGJXVJB7.cjs');
|
|
7
|
-
require('./chunk-DOLTCXCL.cjs');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Object.defineProperty(exports, "CompositionSequence", {
|
|
12
|
-
enumerable: true,
|
|
13
|
-
get: function () { return chunkCCPJD76D_cjs.CompositionSequence; }
|
|
14
|
-
});
|
|
15
|
-
//# sourceMappingURL=Composition-TJ5D7Z63.cjs.map
|
|
16
|
-
//# sourceMappingURL=Composition-TJ5D7Z63.cjs.map
|