pixi-effects 0.45.0 → 0.47.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 +5 -5
- package/ai/chat-template.html +3 -3
- package/ai/tools/check.mjs +2 -2
- package/ai/tools/lookup.mjs +6 -1
- package/ai/tools/pixi-effects.mjs +1 -0
- package/ai/tools/render.mjs +17 -1
- package/dist/Composition-NSY7A32Y.js +7 -0
- package/dist/{Composition-YGBQX3HZ.js.map → Composition-NSY7A32Y.js.map} +1 -1
- package/dist/Composition-PME3P5LP.cjs +16 -0
- package/dist/{Composition-N6BOROJ2.cjs.map → Composition-PME3P5LP.cjs.map} +1 -1
- package/dist/Controller.d.cts +2 -2
- package/dist/Controller.d.ts +2 -2
- package/dist/{Movie-C-d4zN-N.d.cts → Movie-BqrAWN0g.d.cts} +6 -1
- package/dist/{Movie-lMSS9JwX.d.ts → Movie-sV8nJr_f.d.ts} +6 -1
- package/dist/{Spans-XTIWYSNO.cjs → Spans-2TZCNREQ.cjs} +9 -9
- package/dist/{Spans-XTIWYSNO.cjs.map → Spans-2TZCNREQ.cjs.map} +1 -1
- package/dist/{Spans-6L2ZVECX.js → Spans-TESJPQKX.js} +4 -4
- package/dist/{Spans-6L2ZVECX.js.map → Spans-TESJPQKX.js.map} +1 -1
- package/dist/{chunk-O66JGPVV.cjs → chunk-23SXKSUI.cjs} +18 -4
- package/dist/chunk-23SXKSUI.cjs.map +1 -0
- package/dist/{chunk-K24FF4BT.js → chunk-4SEL6W7H.js} +18 -4
- package/dist/chunk-4SEL6W7H.js.map +1 -0
- package/dist/{chunk-3HZV3UHU.cjs → chunk-DKCMJILW.cjs} +67 -65
- package/dist/chunk-DKCMJILW.cjs.map +1 -0
- package/dist/{chunk-4QVQ5UJJ.js → chunk-DM2OLQOJ.js} +5 -6
- package/dist/chunk-DM2OLQOJ.js.map +1 -0
- package/dist/{chunk-ICI6ACHY.js → chunk-FPV3NPYC.js} +8 -6
- package/dist/chunk-FPV3NPYC.js.map +1 -0
- package/dist/{chunk-3IO5RWE4.cjs → chunk-I6VUQ2CR.cjs} +188 -74
- package/dist/chunk-I6VUQ2CR.cjs.map +1 -0
- package/dist/{chunk-X62H3RBG.cjs → chunk-LALEBV63.cjs} +5 -6
- package/dist/chunk-LALEBV63.cjs.map +1 -0
- package/dist/{chunk-Z6JQO7GX.js → chunk-W46VWAZC.js} +146 -32
- package/dist/chunk-W46VWAZC.js.map +1 -0
- package/dist/index.cjs +35 -35
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -4
- package/dist/loader.css +1 -1
- package/dist/present.cjs +4 -4
- package/dist/present.d.cts +2 -2
- package/dist/present.d.ts +2 -2
- package/dist/present.js +4 -4
- package/dist/sound.cjs +2 -2
- package/dist/sound.js +1 -1
- package/dist/three.cjs +194 -15
- package/dist/three.cjs.map +1 -1
- package/dist/three.d.cts +75 -4
- package/dist/three.d.ts +75 -4
- package/dist/three.js +191 -13
- package/dist/three.js.map +1 -1
- package/dist/{types-C5xo0fzb.d.cts → types-CTSrPvY3.d.cts} +64 -58
- package/dist/{types-C5xo0fzb.d.ts → types-CTSrPvY3.d.ts} +64 -58
- package/docs/api.md +2 -1
- package/docs/dsl.md +25 -1
- package/llms-full.txt +65 -6
- package/package.json +1 -1
- package/skills/pixi-effects/SKILL.md +1 -1
- package/skills/pixi-effects/reference/cheatsheet.md +3 -3
- package/skills/pixi-effects/reference/recipes.md +34 -0
- package/skills/pixi-effects/template.html +6 -6
- package/dist/Composition-N6BOROJ2.cjs +0 -16
- package/dist/Composition-YGBQX3HZ.js +0 -7
- package/dist/chunk-3HZV3UHU.cjs.map +0 -1
- package/dist/chunk-3IO5RWE4.cjs.map +0 -1
- package/dist/chunk-4QVQ5UJJ.js.map +0 -1
- package/dist/chunk-ICI6ACHY.js.map +0 -1
- package/dist/chunk-K24FF4BT.js.map +0 -1
- package/dist/chunk-O66JGPVV.cjs.map +0 -1
- package/dist/chunk-X62H3RBG.cjs.map +0 -1
- package/dist/chunk-Z6JQO7GX.js.map +0 -1
|
@@ -1,4 +1,61 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as pixi_js from 'pixi.js';
|
|
2
|
+
import { Container, TextureSource } from 'pixi.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolves a routed keyframe path to a concrete tween target.
|
|
6
|
+
* `path` is everything after the registered prefix + dot (for
|
|
7
|
+
* `three.cube.rotation.y` under prefix `three`, path = `cube.rotation.y`).
|
|
8
|
+
* Return null to skip the key (the router is expected to have warned).
|
|
9
|
+
*/
|
|
10
|
+
type PathRouter = (path: string) => {
|
|
11
|
+
target: object;
|
|
12
|
+
prop: string;
|
|
13
|
+
} | null;
|
|
14
|
+
type PathRouters = Record<string, PathRouter>;
|
|
15
|
+
|
|
16
|
+
/** Variables available to expression evaluation. */
|
|
17
|
+
interface Scope {
|
|
18
|
+
w: number;
|
|
19
|
+
h: number;
|
|
20
|
+
W: number;
|
|
21
|
+
H: number;
|
|
22
|
+
GW: number;
|
|
23
|
+
GH: number;
|
|
24
|
+
contain: number;
|
|
25
|
+
cover: number;
|
|
26
|
+
t: number;
|
|
27
|
+
d: number;
|
|
28
|
+
T: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare abstract class Sequence {
|
|
32
|
+
spec: SequenceSpec;
|
|
33
|
+
parent: CompositionShape | null;
|
|
34
|
+
root: CompositionShape;
|
|
35
|
+
target: Container | null;
|
|
36
|
+
intrinsicWidth: number;
|
|
37
|
+
intrinsicHeight: number;
|
|
38
|
+
at: number;
|
|
39
|
+
duration: number | undefined;
|
|
40
|
+
constructor(spec: SequenceSpec, parent: CompositionShape | null, root: CompositionShape);
|
|
41
|
+
abstract build(): Promise<void>;
|
|
42
|
+
buildFilters(): void;
|
|
43
|
+
scope(): Scope;
|
|
44
|
+
/**
|
|
45
|
+
* Prefix routers contributed to the keyframe pipeline (e.g. `three.` in
|
|
46
|
+
* pixi-effects/three). Base sequences route nothing extra.
|
|
47
|
+
*/
|
|
48
|
+
protected pathRouters(): PathRouters;
|
|
49
|
+
/**
|
|
50
|
+
* The `initial` and `keyframes` that go onto the display object. A layer whose `time` runs a clock (video, composition) takes `time`
|
|
51
|
+
* out of them: it is not a property of the display object.
|
|
52
|
+
*/
|
|
53
|
+
protected displayProps(): {
|
|
54
|
+
initial: SequenceSpec['initial'];
|
|
55
|
+
keyframes: SequenceSpec['keyframes'];
|
|
56
|
+
};
|
|
57
|
+
destroy(): void;
|
|
58
|
+
}
|
|
2
59
|
|
|
3
60
|
/** What an author writes in `composition.stops`: a time in seconds, or an object. */
|
|
4
61
|
interface StopSpec {
|
|
@@ -120,62 +177,6 @@ interface SharedGpu {
|
|
|
120
177
|
adopt(source: TextureSource, texture: unknown): void;
|
|
121
178
|
}
|
|
122
179
|
|
|
123
|
-
/**
|
|
124
|
-
* Resolves a routed keyframe path to a concrete tween target.
|
|
125
|
-
* `path` is everything after the registered prefix + dot (for
|
|
126
|
-
* `three.cube.rotation.y` under prefix `three`, path = `cube.rotation.y`).
|
|
127
|
-
* Return null to skip the key (the router is expected to have warned).
|
|
128
|
-
*/
|
|
129
|
-
type PathRouter = (path: string) => {
|
|
130
|
-
target: object;
|
|
131
|
-
prop: string;
|
|
132
|
-
} | null;
|
|
133
|
-
type PathRouters = Record<string, PathRouter>;
|
|
134
|
-
|
|
135
|
-
/** Variables available to expression evaluation. */
|
|
136
|
-
interface Scope {
|
|
137
|
-
w: number;
|
|
138
|
-
h: number;
|
|
139
|
-
W: number;
|
|
140
|
-
H: number;
|
|
141
|
-
GW: number;
|
|
142
|
-
GH: number;
|
|
143
|
-
contain: number;
|
|
144
|
-
cover: number;
|
|
145
|
-
t: number;
|
|
146
|
-
d: number;
|
|
147
|
-
T: number;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
declare abstract class Sequence {
|
|
151
|
-
spec: SequenceSpec;
|
|
152
|
-
parent: CompositionShape | null;
|
|
153
|
-
root: CompositionShape;
|
|
154
|
-
target: Container | null;
|
|
155
|
-
intrinsicWidth: number;
|
|
156
|
-
intrinsicHeight: number;
|
|
157
|
-
at: number;
|
|
158
|
-
duration: number | undefined;
|
|
159
|
-
constructor(spec: SequenceSpec, parent: CompositionShape | null, root: CompositionShape);
|
|
160
|
-
abstract build(): Promise<void>;
|
|
161
|
-
buildFilters(): void;
|
|
162
|
-
scope(): Scope;
|
|
163
|
-
/**
|
|
164
|
-
* Prefix routers contributed to the keyframe pipeline (e.g. `three.` in
|
|
165
|
-
* pixi-effects/three). Base sequences route nothing extra.
|
|
166
|
-
*/
|
|
167
|
-
protected pathRouters(): PathRouters;
|
|
168
|
-
/**
|
|
169
|
-
* The `initial` and `keyframes` that go onto the display object. A layer whose `time` runs a clock (video, composition) takes `time`
|
|
170
|
-
* out of them: it is not a property of the display object.
|
|
171
|
-
*/
|
|
172
|
-
protected displayProps(): {
|
|
173
|
-
initial: SequenceSpec['initial'];
|
|
174
|
-
keyframes: SequenceSpec['keyframes'];
|
|
175
|
-
};
|
|
176
|
-
destroy(): void;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
180
|
/**
|
|
180
181
|
* Keeping a layer's expensive state across a rebuild (`setVars`, `resize`, `rebuild`): the new tree is built while the old one still plays, and a layer that cannot be told apart from
|
|
181
182
|
* one of the old tree (same name, same setup, same size and options) may take over that layer's scene and renderer instead of building its own. Today only the `three` layer does:
|
|
@@ -949,6 +950,11 @@ interface CompositionShape {
|
|
|
949
950
|
gpu?: SharedGpu;
|
|
950
951
|
/** The layers of the tree that a rebuild replaces, that a new layer of the same kind may take the state of instead of building its own (`three` layers: see core/reuse.ts). Root shape only. */
|
|
951
952
|
reuse?: ReusePool;
|
|
953
|
+
/** A layer of this composition by its `name`, among those already built (the ones listed before the asker): a `three` layer shows one of them on a mesh (`threeLayerTexture`). */
|
|
954
|
+
findLayer?: (name: string) => Sequence | null;
|
|
955
|
+
layerNames?: () => string[];
|
|
956
|
+
/** PixiJS's renderer, on the root shape only: what draws a layer into a texture for `threeLayerTexture`. */
|
|
957
|
+
pixiRenderer?: pixi_js.Renderer;
|
|
952
958
|
}
|
|
953
959
|
/** The composition's camera and the lights alive at one moment (what its `camera` and `light` layers say, after their keyframes). */
|
|
954
960
|
interface SpaceNow {
|
|
@@ -957,4 +963,4 @@ interface SpaceNow {
|
|
|
957
963
|
fog: FogState | null;
|
|
958
964
|
}
|
|
959
965
|
|
|
960
|
-
export { type SfxPreset as $, type AssetSpec as A, type BlendModeName as B, type CompositionSpec as C, type DipTransition as D, type EllipseShapeSpec as E, type FilterSpec as F, type GradientSpec as G, type MaskRefSpec as H, type ImageSequenceSpec as I, type MatteRefSpec as J, type Keyframe as K, type LightSequenceSpec as L, type MusicOptions as M, type MusicDrum as N, type MusicDrums as O, type Props as P, type MusicInstrument as Q, type MusicLevel as R, type SequenceSpec as S, type TransitionSpec as T, type MusicTrack as U, type NullSequenceSpec as V, type PathShapeSpec as W, type PolygonShapeSpec as X, type RectShapeSpec as Y, type SfxKnobs as Z, type SfxOptions as _, type Stop as a, type SfxVoice as a0, type ShaderSequenceSpec as a1, type ShapeSequenceSpec as a2, type SlideTransition as a3, type StopSpec as a4, type TextSpan as a5, type TransitionCommon as a6, type VideoSequenceSpec as a7, type WipeTransition as a8, type ZoomTransition as a9,
|
|
966
|
+
export { type SfxPreset as $, type AssetSpec as A, type BlendModeName as B, type CompositionSpec as C, type DipTransition as D, type EllipseShapeSpec as E, type FilterSpec as F, type GradientSpec as G, type MaskRefSpec as H, type ImageSequenceSpec as I, type MatteRefSpec as J, type Keyframe as K, type LightSequenceSpec as L, type MusicOptions as M, type MusicDrum as N, type MusicDrums as O, type Props as P, type MusicInstrument as Q, type MusicLevel as R, type SequenceSpec as S, type TransitionSpec as T, type MusicTrack as U, type NullSequenceSpec as V, type PathShapeSpec as W, type PolygonShapeSpec as X, type RectShapeSpec as Y, type SfxKnobs as Z, type SfxOptions as _, type Stop as a, type SfxVoice as a0, type ShaderSequenceSpec as a1, type ShapeSequenceSpec as a2, type SlideTransition as a3, type StopSpec as a4, type TextSpan as a5, type TransitionCommon as a6, type VideoSequenceSpec as a7, type WipeTransition as a8, type ZoomTransition as a9, Sequence as b, type SpaceNow as c, type SequenceCommon as d, type PropValue as e, type PathRouters as f, type CompositionShape as g, type CameraSequenceSpec as h, type TextSequenceSpec as i, type ArcShapeSpec as j, type AudioAssetSpec as k, type AudioMusicSpec as l, type AudioSequenceSpec as m, type AudioSfxSpec as n, type CanvasInfo as o, type CanvasSequenceSpec as p, type ChromaKeyFilterSpec as q, type CircleShapeSpec as r, type CompositionSequenceSpec as s, type CrossfadeTransition as t, type CustomFilterSpec as u, type DissolveTransition as v, type Expr as w, type IrisTransition as x, type LineShapeSpec as y, type LumaTransition as z };
|
|
@@ -1,4 +1,61 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as pixi_js from 'pixi.js';
|
|
2
|
+
import { Container, TextureSource } from 'pixi.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolves a routed keyframe path to a concrete tween target.
|
|
6
|
+
* `path` is everything after the registered prefix + dot (for
|
|
7
|
+
* `three.cube.rotation.y` under prefix `three`, path = `cube.rotation.y`).
|
|
8
|
+
* Return null to skip the key (the router is expected to have warned).
|
|
9
|
+
*/
|
|
10
|
+
type PathRouter = (path: string) => {
|
|
11
|
+
target: object;
|
|
12
|
+
prop: string;
|
|
13
|
+
} | null;
|
|
14
|
+
type PathRouters = Record<string, PathRouter>;
|
|
15
|
+
|
|
16
|
+
/** Variables available to expression evaluation. */
|
|
17
|
+
interface Scope {
|
|
18
|
+
w: number;
|
|
19
|
+
h: number;
|
|
20
|
+
W: number;
|
|
21
|
+
H: number;
|
|
22
|
+
GW: number;
|
|
23
|
+
GH: number;
|
|
24
|
+
contain: number;
|
|
25
|
+
cover: number;
|
|
26
|
+
t: number;
|
|
27
|
+
d: number;
|
|
28
|
+
T: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare abstract class Sequence {
|
|
32
|
+
spec: SequenceSpec;
|
|
33
|
+
parent: CompositionShape | null;
|
|
34
|
+
root: CompositionShape;
|
|
35
|
+
target: Container | null;
|
|
36
|
+
intrinsicWidth: number;
|
|
37
|
+
intrinsicHeight: number;
|
|
38
|
+
at: number;
|
|
39
|
+
duration: number | undefined;
|
|
40
|
+
constructor(spec: SequenceSpec, parent: CompositionShape | null, root: CompositionShape);
|
|
41
|
+
abstract build(): Promise<void>;
|
|
42
|
+
buildFilters(): void;
|
|
43
|
+
scope(): Scope;
|
|
44
|
+
/**
|
|
45
|
+
* Prefix routers contributed to the keyframe pipeline (e.g. `three.` in
|
|
46
|
+
* pixi-effects/three). Base sequences route nothing extra.
|
|
47
|
+
*/
|
|
48
|
+
protected pathRouters(): PathRouters;
|
|
49
|
+
/**
|
|
50
|
+
* The `initial` and `keyframes` that go onto the display object. A layer whose `time` runs a clock (video, composition) takes `time`
|
|
51
|
+
* out of them: it is not a property of the display object.
|
|
52
|
+
*/
|
|
53
|
+
protected displayProps(): {
|
|
54
|
+
initial: SequenceSpec['initial'];
|
|
55
|
+
keyframes: SequenceSpec['keyframes'];
|
|
56
|
+
};
|
|
57
|
+
destroy(): void;
|
|
58
|
+
}
|
|
2
59
|
|
|
3
60
|
/** What an author writes in `composition.stops`: a time in seconds, or an object. */
|
|
4
61
|
interface StopSpec {
|
|
@@ -120,62 +177,6 @@ interface SharedGpu {
|
|
|
120
177
|
adopt(source: TextureSource, texture: unknown): void;
|
|
121
178
|
}
|
|
122
179
|
|
|
123
|
-
/**
|
|
124
|
-
* Resolves a routed keyframe path to a concrete tween target.
|
|
125
|
-
* `path` is everything after the registered prefix + dot (for
|
|
126
|
-
* `three.cube.rotation.y` under prefix `three`, path = `cube.rotation.y`).
|
|
127
|
-
* Return null to skip the key (the router is expected to have warned).
|
|
128
|
-
*/
|
|
129
|
-
type PathRouter = (path: string) => {
|
|
130
|
-
target: object;
|
|
131
|
-
prop: string;
|
|
132
|
-
} | null;
|
|
133
|
-
type PathRouters = Record<string, PathRouter>;
|
|
134
|
-
|
|
135
|
-
/** Variables available to expression evaluation. */
|
|
136
|
-
interface Scope {
|
|
137
|
-
w: number;
|
|
138
|
-
h: number;
|
|
139
|
-
W: number;
|
|
140
|
-
H: number;
|
|
141
|
-
GW: number;
|
|
142
|
-
GH: number;
|
|
143
|
-
contain: number;
|
|
144
|
-
cover: number;
|
|
145
|
-
t: number;
|
|
146
|
-
d: number;
|
|
147
|
-
T: number;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
declare abstract class Sequence {
|
|
151
|
-
spec: SequenceSpec;
|
|
152
|
-
parent: CompositionShape | null;
|
|
153
|
-
root: CompositionShape;
|
|
154
|
-
target: Container | null;
|
|
155
|
-
intrinsicWidth: number;
|
|
156
|
-
intrinsicHeight: number;
|
|
157
|
-
at: number;
|
|
158
|
-
duration: number | undefined;
|
|
159
|
-
constructor(spec: SequenceSpec, parent: CompositionShape | null, root: CompositionShape);
|
|
160
|
-
abstract build(): Promise<void>;
|
|
161
|
-
buildFilters(): void;
|
|
162
|
-
scope(): Scope;
|
|
163
|
-
/**
|
|
164
|
-
* Prefix routers contributed to the keyframe pipeline (e.g. `three.` in
|
|
165
|
-
* pixi-effects/three). Base sequences route nothing extra.
|
|
166
|
-
*/
|
|
167
|
-
protected pathRouters(): PathRouters;
|
|
168
|
-
/**
|
|
169
|
-
* The `initial` and `keyframes` that go onto the display object. A layer whose `time` runs a clock (video, composition) takes `time`
|
|
170
|
-
* out of them: it is not a property of the display object.
|
|
171
|
-
*/
|
|
172
|
-
protected displayProps(): {
|
|
173
|
-
initial: SequenceSpec['initial'];
|
|
174
|
-
keyframes: SequenceSpec['keyframes'];
|
|
175
|
-
};
|
|
176
|
-
destroy(): void;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
180
|
/**
|
|
180
181
|
* Keeping a layer's expensive state across a rebuild (`setVars`, `resize`, `rebuild`): the new tree is built while the old one still plays, and a layer that cannot be told apart from
|
|
181
182
|
* one of the old tree (same name, same setup, same size and options) may take over that layer's scene and renderer instead of building its own. Today only the `three` layer does:
|
|
@@ -949,6 +950,11 @@ interface CompositionShape {
|
|
|
949
950
|
gpu?: SharedGpu;
|
|
950
951
|
/** The layers of the tree that a rebuild replaces, that a new layer of the same kind may take the state of instead of building its own (`three` layers: see core/reuse.ts). Root shape only. */
|
|
951
952
|
reuse?: ReusePool;
|
|
953
|
+
/** A layer of this composition by its `name`, among those already built (the ones listed before the asker): a `three` layer shows one of them on a mesh (`threeLayerTexture`). */
|
|
954
|
+
findLayer?: (name: string) => Sequence | null;
|
|
955
|
+
layerNames?: () => string[];
|
|
956
|
+
/** PixiJS's renderer, on the root shape only: what draws a layer into a texture for `threeLayerTexture`. */
|
|
957
|
+
pixiRenderer?: pixi_js.Renderer;
|
|
952
958
|
}
|
|
953
959
|
/** The composition's camera and the lights alive at one moment (what its `camera` and `light` layers say, after their keyframes). */
|
|
954
960
|
interface SpaceNow {
|
|
@@ -957,4 +963,4 @@ interface SpaceNow {
|
|
|
957
963
|
fog: FogState | null;
|
|
958
964
|
}
|
|
959
965
|
|
|
960
|
-
export { type SfxPreset as $, type AssetSpec as A, type BlendModeName as B, type CompositionSpec as C, type DipTransition as D, type EllipseShapeSpec as E, type FilterSpec as F, type GradientSpec as G, type MaskRefSpec as H, type ImageSequenceSpec as I, type MatteRefSpec as J, type Keyframe as K, type LightSequenceSpec as L, type MusicOptions as M, type MusicDrum as N, type MusicDrums as O, type Props as P, type MusicInstrument as Q, type MusicLevel as R, type SequenceSpec as S, type TransitionSpec as T, type MusicTrack as U, type NullSequenceSpec as V, type PathShapeSpec as W, type PolygonShapeSpec as X, type RectShapeSpec as Y, type SfxKnobs as Z, type SfxOptions as _, type Stop as a, type SfxVoice as a0, type ShaderSequenceSpec as a1, type ShapeSequenceSpec as a2, type SlideTransition as a3, type StopSpec as a4, type TextSpan as a5, type TransitionCommon as a6, type VideoSequenceSpec as a7, type WipeTransition as a8, type ZoomTransition as a9,
|
|
966
|
+
export { type SfxPreset as $, type AssetSpec as A, type BlendModeName as B, type CompositionSpec as C, type DipTransition as D, type EllipseShapeSpec as E, type FilterSpec as F, type GradientSpec as G, type MaskRefSpec as H, type ImageSequenceSpec as I, type MatteRefSpec as J, type Keyframe as K, type LightSequenceSpec as L, type MusicOptions as M, type MusicDrum as N, type MusicDrums as O, type Props as P, type MusicInstrument as Q, type MusicLevel as R, type SequenceSpec as S, type TransitionSpec as T, type MusicTrack as U, type NullSequenceSpec as V, type PathShapeSpec as W, type PolygonShapeSpec as X, type RectShapeSpec as Y, type SfxKnobs as Z, type SfxOptions as _, type Stop as a, type SfxVoice as a0, type ShaderSequenceSpec as a1, type ShapeSequenceSpec as a2, type SlideTransition as a3, type StopSpec as a4, type TextSpan as a5, type TransitionCommon as a6, type VideoSequenceSpec as a7, type WipeTransition as a8, type ZoomTransition as a9, Sequence as b, type SpaceNow as c, type SequenceCommon as d, type PropValue as e, type PathRouters as f, type CompositionShape as g, type CameraSequenceSpec as h, type TextSequenceSpec as i, type ArcShapeSpec as j, type AudioAssetSpec as k, type AudioMusicSpec as l, type AudioSequenceSpec as m, type AudioSfxSpec as n, type CanvasInfo as o, type CanvasSequenceSpec as p, type ChromaKeyFilterSpec as q, type CircleShapeSpec as r, type CompositionSequenceSpec as s, type CrossfadeTransition as t, type CustomFilterSpec as u, type DissolveTransition as v, type Expr as w, type IrisTransition as x, type LineShapeSpec as y, type LumaTransition as z };
|
package/docs/api.md
CHANGED
|
@@ -44,7 +44,7 @@ interface MovieOptions {
|
|
|
44
44
|
composition?: CompositionSpec; // root composition (see DSL reference)
|
|
45
45
|
vars?: Record<string, number | string>; // named values the composition reads: a number as a name in an expression, any as {name} in a string (see Variables)
|
|
46
46
|
loop?: boolean; // start with movie.loop on: playing goes back to the start at the end (default false, like a <video>)
|
|
47
|
-
loudness?: number | false; // the whole mix at this many LUFS (-16 is usual for web video): one gain, up or down, the
|
|
47
|
+
loudness?: number | false; // the whole mix at this many LUFS (-16 is usual for web video): one gain, up or down, and a look-ahead limiter keeping the true peak at -1 dBTP or under (a click over a quiet bed is pulled down; more than 10 dB of limiting is out of reach and `check` names it); false / left out: the mix as the layers make it (check says when it is quiet)
|
|
48
48
|
poster?: number; // seconds: the frame shown before play (negative: from the end), see movie.poster
|
|
49
49
|
motionBlur?: boolean | number | { samples?: number; shutter?: number }; // blur for render() / snapshot() / contactSheet(), not live playback; see Motion blur
|
|
50
50
|
loader?: HTMLElement | string | false; // the page's loader (see pixi-effects/loader.css): shows how far init is (a bar, "BUILDING LAYERS 62%"), faded out when ready, stopped if init fails; default: a .pe-loader next to the canvas
|
|
@@ -177,6 +177,7 @@ interface PickHit {
|
|
|
177
177
|
kind: 'flat' | 'card' | 'three'; // a 2D layer, a threeD layer (its projected card), a three layer
|
|
178
178
|
point: { x: number; y: number }; // the point asked about
|
|
179
179
|
local?: { x: number; y: number }; // a flat layer: the point in the layer's own pixels
|
|
180
|
+
via?: { path: string; object: string }; // the layer is shown on a mesh of a `three` layer (threeLayerTexture) and the point is on that mesh: the three layer and the object it came through
|
|
180
181
|
three?: { object: string; point: [number, number, number]; distance: number; object3d: unknown; // a three layer: what the ray met first
|
|
181
182
|
grab?: (plane?) => { move: (x, y) => { point, delta } | null } }; // drag it: see below
|
|
182
183
|
}
|
package/docs/dsl.md
CHANGED
|
@@ -224,7 +224,7 @@ Audio-only sequence. No visual. Volume is animatable via keyframes.
|
|
|
224
224
|
|
|
225
225
|
Audio is mixed during `Movie.init()` and during `Movie.render()`. Volume keyframes interpolate linearly.
|
|
226
226
|
|
|
227
|
-
**The loudness of the whole mix: `movie.init({ loudness: -16 })`.** Layers make whatever level their `volume` gives (a few `sfx` at volume 0.15 mix to about -40 LUFS, far quieter than web video's -14 to -16). `loudness` is a target for the finished mix in LUFS (ITU-R BS.1770): one gain, up or down,
|
|
227
|
+
**The loudness of the whole mix: `movie.init({ loudness: -16 })`.** Layers make whatever level their `volume` gives (a few `sfx` at volume 0.15 mix to about -40 LUFS, far quieter than web video's -14 to -16). `loudness` is a target for the finished mix in LUFS (ITU-R BS.1770): one gain, up or down, and a look-ahead limiter that takes a click or a hit on top of a quiet bed down so that no peak passes -1 dBTP (true peak), the same for playback and export. A mix whose peaks are within 10 dB of what the limiter can take reaches the target (`check` says where the limiter worked hardest and which sounds play there when it took 3 dB or more off); a click 30 dB over its bed is out of reach and stops short (`reached -19.4 LUFS`), and `check` / `inspectAudio()` name the moment and the sounds that make it, with the fix (lower that sound, or ask for less). **Raising a `sfx` over music is safe:** the limiter keeps the peak, so give an accent the volume the `buried` note suggests (an accent over music needs 2 to 3). `false` or no option leaves the mix as the layers make it; `check` then says when it is quiet (found in a trial of nine films with sound: eight were -22 to -26 LUFS and only one was raised).
|
|
228
228
|
|
|
229
229
|
#### Sound effects without files: `sfx`
|
|
230
230
|
|
|
@@ -850,6 +850,30 @@ three({ type: 'three', width: 'GW', height: 'GH',
|
|
|
850
850
|
- **`threeFrameCamera(camera, object, { direction, margin })`** puts a `PerspectiveCamera` where the whole of `object` (its box now, with its scale and place) fits the frame, looking at its centre, from a three-quarter view (`direction` default `[0.45, 0.3, 1]`) with 10 % room (`margin` 1.1): the smallest distance at which the 8 corners of the box fit both the vertical and the horizontal field of view (a bounding sphere would leave a long model small and head-on). It uses the camera's own `fov` and `aspect` and sets `near` / `far`. The box is the bind pose: a clip that travels far needs a camera of your own.
|
|
851
851
|
- With `followCamera` the composition's camera is the layer's camera (do not call `threeFrameCamera`); with `followLights` its lights light the model. See the recipes `gltf-in-space` and `gltf-shadow-room`. `orbit()` and `cameraPath()` drive the composition's `camera` layer, so they carry a following scene too.
|
|
852
852
|
|
|
853
|
+
### A 2D layer on a mesh: `threeLayerTexture`
|
|
854
|
+
|
|
855
|
+
`threeLayerTexture(ctx, 'screen')` makes a live 2D layer (a `composition`) the picture on a mesh of a `three` layer: a phone or laptop screen with an app running, a television, a billboard, a label on a bottle, a card with moving type. The 2D layer stays an ordinary layer (the timeline drives it, its own `at` / `duration` / keyframes / filters apply) but is no longer drawn on the stage: just before the three layer draws, PixiJS draws it into a texture and the mesh shows that texture.
|
|
856
|
+
|
|
857
|
+
```js
|
|
858
|
+
import { threeLayerTexture } from 'pixi-effects/three';
|
|
859
|
+
// in the composition, the 2D layer FIRST, then the three layer
|
|
860
|
+
{ type: 'composition', name: 'screen', width: 480, height: 270, duration: 8, sequences: [ /* a background rect, your UI */ ] },
|
|
861
|
+
three({ type: 'three', width: 'GW', height: 'GH',
|
|
862
|
+
setup: async (ctx) => {
|
|
863
|
+
const screen = await threeLayerTexture(ctx, 'screen', { resolution: 2 }); // { material, texture, width, height, dispose }
|
|
864
|
+
ctx.scene.add(new THREE.Mesh(new THREE.PlaneGeometry(1.6, 0.9), screen.material));
|
|
865
|
+
} })
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
- **The rules.** The layer must be a `composition` with a `width` and `height`, in the same composition as the three layer and **listed before it** (a layer built later cannot be found: the error lists the names that are there). `await` the call in `setup`. `resolution` (default 1) is the pixels of the texture per pixel of the layer: 2 for a screen that is seen large. `transparent: true` lets what is behind the mesh show through the empty and half-transparent parts of the composition (a sticker, a label with rounded corners; the material blends and does not write depth); default `false`, an opaque screen. The layer's own place, scale and rotation do not matter: its content is drawn from (0, 0) to (width, height); its alpha and filters do.
|
|
869
|
+
- **The material.** `screen.material` is unlit (a screen shines by itself; scene lights do not shade it) and carries the colour decoding; put it on a mesh, or on one face of a box (`[dark, dark, dark, dark, screen.material, dark]`). Use `screen.texture` yourself and its colours may be wrong. Without `transparent`, give the composition a background rect: what it leaves empty is black on the mesh.
|
|
870
|
+
- **Two routes, the same colours** (measured: the background, a moving dot and a growing bar equal the 2D layer, channel for channel, on both). When the three layer has `renderer: 'webgpu'` and draws on PixiJS's own GPU device (a WebGPU movie) the texture is PixiJS's own GPU texture used through three's `ExternalTexture`: no copy, 0.3 ms a frame, and the page's import map needs `"three/webgpu"` and `"three/tsl"` next to `"three"` (the template's has only `"three"` and `"three/addons/"`: add the two lines for this route). Otherwise (the layer has the default `renderer: 'webgl'`, a canvas of its own with `shareGpu: false`, or PixiJS on WebGL) the pixels are read back into a canvas: 1.6 ms a frame at 480 x 270, 4 ms at 1280 x 720, and no import-map entry beyond the template's.
|
|
871
|
+
- **A function of the time.** The 2D layer is drawn when the three layer draws, after the timeline has put every layer at the frame: a seek, an export and a repeat give the same picture (`check` draws the layer twice, in two orders, as for any three layer). A layer that has not started yet, or has ended, shows nothing on the mesh.
|
|
872
|
+
- **A rebuild** (`setVars`, `resize`) keeps the three layer and shows the layer of the new tree (the text that changed shows on the mesh); a layer that changed size is drawn stretched to the texture.
|
|
873
|
+
- **Seen small or at a slant** the texture is filtered, not aliased (1-pixel stripes on a plane a quarter of the picture come out as one even grey, not black and white stripes; four taps along a slant). The mip levels are PixiJS's own on the shared GPU, averaged in the stored sRGB values, so very fine bright detail seen very small is a little darker there than on a read-back canvas, whose mipmaps three makes in linear light (stripes average to 128 and 188).
|
|
874
|
+
- **A click reaches the screen.** `movie.pick(x, y)` on a point of the mesh answers for the layers INSIDE the 2D layer, at the point of its picture under the pointer: the top hit is `screen/button` (a flat hit with `local` in the layer's own pixels), with `via: { path: 'phone', object: 'face' }` naming the three layer and the mesh it came through; under it come the screen's other layers, then the mesh itself. `only` / `ignore` see through the mesh (`only: ['screen']` is the screen's layers wherever they are shown), `movie.hover` and the click recipes work on a phone's screen as they do on a flat layer, and a layer that is not alive does not answer. A page asks the layer by its own name, so the button is one `pick` away: `if (movie.pick(...movie.canvasPoint(event), { only: ['button'] })) …`.
|
|
875
|
+
- **Not in this version:** several meshes showing the same layer are answered through the first one the ray meets (that is the right one), but a layer shown on a mesh seen through another transparent mesh is not looked behind.
|
|
876
|
+
|
|
853
877
|
### Keyframe paths: `three.<name>.<path>`
|
|
854
878
|
|
|
855
879
|
Objects returned from `setup`'s `{ objects }` are addressable from keyframes as `three.<name>.<path>`, using the same `from`/`to`/`set` vocabulary as every other prop:
|
package/llms-full.txt
CHANGED
|
@@ -6,7 +6,7 @@ Concatenation of the skill, cheatsheet, pitfalls, recipes and the DSL / API / me
|
|
|
6
6
|
|
|
7
7
|
<!-- ===== skills/pixi-effects/SKILL.md ===== -->
|
|
8
8
|
|
|
9
|
-
> Written for pixi-effects 0.
|
|
9
|
+
> Written for pixi-effects 0.47.0. Update this skill with `npx skills update`; the newest docs are at https://yjmtmtk.github.io/pixi-effects/
|
|
10
10
|
|
|
11
11
|
# pixi-effects — write a video as data
|
|
12
12
|
|
|
@@ -169,7 +169,7 @@ blendMode: 'normal' | 'add' | 'screen' | 'multiply' | 'overlay' | 'soft-light' |
|
|
|
169
169
|
|
|
170
170
|
Animatable props (`initial` / keyframes): `x y alpha rotation scale scaleX scaleY pivotX pivotY anchorX anchorY skewX skewY tint width height visible autoAlpha`, plus for shapes their style/geometry, for text `fill`, for audio `volume`, for 3D `z rotationX rotationY`, filter params as `'filters.<name>.<param>'`, three objects as `'three.<obj>.<path>'`. **These go in `initial` / keyframes, never at the top level of a shape or text layer** (top-level `x`, `rotation`, `strokeColor`, `fillAlpha` each warn "is not a … key"): the top level of a shape holds its geometry (`width height radius d startAngle endAngle cornerRadius anchorX anchorY`), `trimStart` / `trimEnd`, `strokeCap` / `strokeJoin`, `fillGradient`.
|
|
171
171
|
|
|
172
|
-
Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate to), `from` (animate from), `from`+`to`; a value may be relative to where the layer stands (`from: { x: '-=36' }` slides in from 36 px to the left of its place). `from` / `from+to` show their start value from the layer's start, so a delayed fade-in needs no `initial.alpha`. `repeat` (finite extra plays), `yoyo`, `repeatDelay` loop any of them (endless repeats are not allowed). `ease` = an ease name (`'none'`, `'power2.out'`, `'expo.out'`, `'back.out(1.7)'`, `'elastic.out(1,0.5)'`, `'sine.inOut'`, `'bounce.out'`, **`'cubic-bezier(.4, 0, .2, 1)'`** (CSS syntax, x1 and x2 in 0…1; `.4, 0, .2, 1` is a gentle in-out; `.2, .8, .2, 1` is FRONT-LOADED, not slow motion: half of the change in the first 13 % of the time, 90 % by 39 %, 99 % by 74 %, then a long settle, so what sits at the END of the content gets the glide; `.2, .8, .5, 1` has a shorter tail). `power1…4` are the powers 2…5: `power2` is a cubic, `power2.in` has done only 12.5 % at half time (42 % at three quarters)… and springs: `'spring(mass, stiffness, damping)'` or `'spring.gentle' | 'snappy' | 'bouncy' | 'wobbly' | 'slow'`; **`duration: 'auto'` = the time the spring takes to settle** (presets, settle time / highest value reached as a multiple of the target: `snappy` 0.42 s / 1.01, `gentle` 0.72 s / 1.02, `bouncy` 1.08 s / 1.28, `slow` 1.29 s / 1.00, `wobbly` 1.61 s / 1.40: time the next beat from these) (real physics; with a number `duration` is the settle time and only the damping ratio shapes it; a damping ratio of 1 or more never overshoots, so `spring(1,170,26)` does not bounce: use damping below 20 at stiffness 170; `'auto'` works on keyframes only, not in `animateText` / `orbit` tweens; a `stagger` ease may be a spring, its delays are held inside the spread). Default ease is linear.
|
|
172
|
+
Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate to), `from` (animate from), `from`+`to`; a value may be relative to where the layer stands (`from: { x: '-=36' }` slides in from 36 px to the left of its place). `from` / `from+to` show their start value from the layer's start, so a delayed fade-in needs no `initial.alpha`. `repeat` (finite extra plays), `yoyo`, `repeatDelay` loop any of them (endless repeats are not allowed: `repeat: -1` (the GSAP habit) is refused with a warning, write a count that covers the layer, `repeat: 40`; a `from` keyframe with a later `at` puts its start value on the layer from frame 0, the layer does not wait for the `at`). `ease` = an ease name (`'none'`, `'power2.out'`, `'expo.out'`, `'back.out(1.7)'`, `'elastic.out(1,0.5)'`, `'sine.inOut'`, `'bounce.out'`, **`'cubic-bezier(.4, 0, .2, 1)'`** (CSS syntax, x1 and x2 in 0…1; `.4, 0, .2, 1` is a gentle in-out; `.2, .8, .2, 1` is FRONT-LOADED, not slow motion: half of the change in the first 13 % of the time, 90 % by 39 %, 99 % by 74 %, then a long settle, so what sits at the END of the content gets the glide; `.2, .8, .5, 1` has a shorter tail). `power1…4` are the powers 2…5: `power2` is a cubic, `power2.in` has done only 12.5 % at half time (42 % at three quarters)… and springs: `'spring(mass, stiffness, damping)'` or `'spring.gentle' | 'snappy' | 'bouncy' | 'wobbly' | 'slow'`; **`duration: 'auto'` = the time the spring takes to settle** (presets, settle time / highest value reached as a multiple of the target: `snappy` 0.42 s / 1.01, `gentle` 0.72 s / 1.02, `bouncy` 1.08 s / 1.28, `slow` 1.29 s / 1.00, `wobbly` 1.61 s / 1.40: time the next beat from these) (real physics; with a number `duration` is the settle time and only the damping ratio shapes it; a damping ratio of 1 or more never overshoots, so `spring(1,170,26)` does not bounce: use damping below 20 at stiffness 170; `'auto'` works on keyframes only, not in `animateText` / `orbit` tweens; a `stagger` ease may be a spring, its delays are held inside the spread). Default ease is linear.
|
|
173
173
|
|
|
174
174
|
## Layer types
|
|
175
175
|
|
|
@@ -209,7 +209,7 @@ Style (`initial` / keyframes): `fillColor fillAlpha strokeColor strokeAlpha stro
|
|
|
209
209
|
|
|
210
210
|
**camera** (2.5D) — props in `initial`/keyframes: `x y z lookAtX lookAtY lookAtZ fov focus aperture` (defaults: centred, looking at the z = 0 plane, `fov` 40, `z` auto = `(H/2)/tan(fov/2)` ≈ 989 at 720p). **Depth of field:** `focus` = the sharp plane (a `threeD` layer's name = its first z, or a z number), `aperture` = the lens diameter in px (30 default, 0 off); writing either blurs the other `threeD` layers by their distance from the focus (a rack focus is a keyframe on `focus`; recipe `rack-focus`; `inspect` reports `depthBlur`, and `check --at 2.4` prints each layer's blur). A dolly with no typed distance: leave `z` automatic and animate `offsetZ`. Never on the camera object itself. An orbit is `orbit()`; by hand: `x = cx + R·sin θ`, `z = R·cos θ`, `R = (H/2)/tan(fov/2)`, `lookAt` = the centre at `z = 0`.
|
|
211
211
|
|
|
212
|
-
**three** (optional entry `pixi-effects/three`) — `three({ type:'three', width, height, setup(ctx) { …; return { objects: { knot } } }, update?, dispose? })`; drive with `'three.knot.rotation.y'` keyframes. Call `registerThree()` before `init`. **In the composition's space:** `followCamera: true` (the layer's PerspectiveCamera is the composition's `camera`; the scene is in composition px with y flipped: the composition's `(x, y, z)` is `(x, -y, z)`; give the layer the composition's size), `followLights: true` (the `light` layers become three lights) and `followFog: true` (the camera's fog becomes the scene's `Fog`: close to, not equal to, the card fog); a light's `castsShadows` casts in three (mark your meshes `castShadow` / `receiveShadow`); `ctx.space` has the raw numbers. **glTF:** `import { threeGltf, threeFrameCamera } from 'pixi-effects/three'`: `const m = await threeGltf(url, { animation: 'Run' | 0 | false, speed, offset, loop })` in `setup` (`ctx.scene.add(m.object)`; `m.fit(px)` scales the longest side to px; `m.clips` names the clips), `update: (t) => m.pose(t)` (a function of t: never `mixer.update(dt)`), `threeFrameCamera(ctx.camera, m.object)` frames the whole model from a three-quarter view. Needs `"three/addons/"` in the import map. Recipe `gltf-in-space`.
|
|
212
|
+
**three** (optional entry `pixi-effects/three`) — `three({ type:'three', width, height, setup(ctx) { …; return { objects: { knot } } }, update?, dispose? })`; drive with `'three.knot.rotation.y'` keyframes. Call `registerThree()` before `init`. **In the composition's space:** `followCamera: true` (the layer's PerspectiveCamera is the composition's `camera`; the scene is in composition px with y flipped: the composition's `(x, y, z)` is `(x, -y, z)`; give the layer the composition's size), `followLights: true` (the `light` layers become three lights) and `followFog: true` (the camera's fog becomes the scene's `Fog`: close to, not equal to, the card fog); a light's `castsShadows` casts in three (mark your meshes `castShadow` / `receiveShadow`); `ctx.space` has the raw numbers. **glTF:** `import { threeGltf, threeFrameCamera } from 'pixi-effects/three'`: `const m = await threeGltf(url, { animation: 'Run' | 0 | false, speed, offset, loop })` in `setup` (`ctx.scene.add(m.object)`; `m.fit(px)` scales the longest side to px; `m.clips` names the clips), `update: (t) => m.pose(t)` (a function of t: never `mixer.update(dt)`), `threeFrameCamera(ctx.camera, m.object)` frames the whole model from a three-quarter view. Needs `"three/addons/"` in the import map. Recipe `gltf-in-space`. **A 2D layer on a mesh** (a phone screen, a television): `const screen = await threeLayerTexture(ctx, 'screen')` in `setup`, `screen.material` on a mesh; `screen` is a `composition` listed BEFORE the three layer (recipe `three-screen`).
|
|
213
213
|
|
|
214
214
|
## Camera flight and a camera shake
|
|
215
215
|
|
|
@@ -280,7 +280,7 @@ measureText('Hello', style) // → { width, height,
|
|
|
280
280
|
splitText('Hello world', style, { by: 'chars' | 'words' | 'lines', x, y, align: 'left' | 'center' | 'right' })
|
|
281
281
|
// → [{ text, x, y, width, index, line }] — one text layer per piece, same style, anchor top-left
|
|
282
282
|
```
|
|
283
|
-
Per-letter / per-word / per-line animation in one call: `...animateText(text, style, { x, y, align, vertical: true /* tategaki: down a column, new line = new column to the left, x = RIGHT edge of the first column; 、。 ー 「」 placed and turned for you */, by: 'chars'|'words'|'lines', at, duration /* required: until the last piece is gone */, in: 'rise'|'drop'|'fade'|'pop'|'zoom'|'slide'|'spin'|{ from, to, duration, ease }|false, out: <same, in the same wave (leave `out` out and the pieces stay until the layer ends); by default BACKWARDS FROM THE LAYER'S END (at + duration, so to be gone at time T set duration = T − at); `out: { preset: 'fade', at: 6 }` starts the exit at composition second 6 instead
|
|
283
|
+
Per-letter / per-word / per-line animation in one call: `...animateText(text, style, { x, y, align, vertical: true /* tategaki: down a column, new line = new column to the left, x = RIGHT edge of the first column; 、。 ー 「」 placed and turned for you */, by: 'chars'|'words'|'lines', at, duration /* required: until the last piece is gone */, in: 'rise'|'drop'|'fade'|'pop'|'zoom'|'slide'|'spin'|{ from, to, duration, ease }|false, out: <same, in the same wave (leave `out` out and the pieces stay until the layer ends); by default BACKWARDS FROM THE LAYER'S END (at + duration, so to be gone at time T set duration = T − at); `out: { preset: 'fade', at: 6 }` starts the exit at composition second 6 instead; `out: { preset: 'fade', at: 6, stagger: 0 }` makes everything leave AT ONCE after it came in letter by letter (or `stagger: { each: 0.1 }`: its own wave); an exit is described by `from` = the state the piece leaves TO: `out: { from: { y: 18, alpha: 0 } }`; no exit: omit `out`, or `out: false` / `'none'`>, stagger: { each, from, … }, idle: { y: 4, rotation: 1 }, styleFor: p => ({ fill }) })` → text layers `<name>-0…` (anchor centre; `x`/`y` in `from` are offsets) to spread into `sequences`. **`x` / `y` are pixel NUMBERS (an expression like `'GW/2'` throws: compute `W / 2`), and `y` is the TOP of the line box**: the visible text is centred `lineHeight / 2` below it, so an underline goes at `y + lineHeight`, not at `y` (recipe `animate-text`). By hand: `splitText(...)` + one layer per piece (recipe `split-text`). A web font must be loaded before measuring.
|
|
284
284
|
|
|
285
285
|
## Assets
|
|
286
286
|
|
|
@@ -1856,6 +1856,40 @@ return [
|
|
|
1856
1856
|
|
|
1857
1857
|
A moving, shadow-casting model is held to "a function of the time" by `check`: place it from `t` in `update` (as above) and let `threeGltf` load it (it draws a skinned mesh always: three culls it by a bounding sphere that does not follow it).
|
|
1858
1858
|
|
|
1859
|
+
## An app or video on a phone, a television, a billboard (`threeLayerTexture`)
|
|
1860
|
+
|
|
1861
|
+
A 2D composition is the screen; a `three` layer holds the object it is on. The composition keeps animating (its own keyframes, text, charts, a counter) and the mesh shows it live, turning with the object. Put the composition BEFORE the three layer, give it a background rect, and `await threeLayerTexture(ctx, 'screen')` in `setup`: `screen.material` goes on the face of the mesh. The three layer as written here (the default `renderer`, WebGL) needs nothing beyond the template's import map: the screen's pixels are read back each frame. Only a layer with `renderer: 'webgpu'` (the no-copy route) also needs `"three/webgpu"` and `"three/tsl"` next to `"three"`.
|
|
1862
|
+
|
|
1863
|
+
```js
|
|
1864
|
+
// @docs-only three-screen
|
|
1865
|
+
// import { registerThree, threeLayerTexture } from 'pixi-effects/three'; registerThree(); (before movie.init)
|
|
1866
|
+
// composition 1280 x 720, 8 s
|
|
1867
|
+
let phone;
|
|
1868
|
+
return [
|
|
1869
|
+
{ type: 'shape', shape: 'rect', width: 'GW', height: 'GH', initial: { x: 'GW/2', y: 'GH/2', fillColor: '#e9e4dc' } },
|
|
1870
|
+
{ type: 'composition', name: 'screen', width: 390, height: 844, duration: 8, sequences: [ // the app: any 2D layers; this is what the phone shows
|
|
1871
|
+
{ type: 'shape', shape: 'rect', width: 390, height: 844, initial: { x: 195, y: 422, fillColor: '#101a33' } },
|
|
1872
|
+
{ type: 'text', text: 'Hello', style: { fontSize: 72, fill: '#ffffff', fontWeight: '800' }, initial: { x: 195, y: 160, anchorX: 0.5, anchorY: 0.5 },
|
|
1873
|
+
keyframes: [{ at: 0.4, from: { alpha: 0, y: 200 }, to: { alpha: 1, y: 160 }, duration: 0.8, ease: 'power3.out' }] },
|
|
1874
|
+
] },
|
|
1875
|
+
{ type: 'three', name: 'phone', width: 'GW', height: 'GH',
|
|
1876
|
+
setup: async (ctx) => {
|
|
1877
|
+
const screen = await threeLayerTexture(ctx, 'screen', { resolution: 2 }); // a screen that is seen large: 2 x the pixels
|
|
1878
|
+
const body = new THREE.Mesh(new THREE.BoxGeometry(1.0, 2.1, 0.09), new THREE.MeshStandardMaterial({ color: 0x15171c, metalness: 0.6, roughness: 0.35 }));
|
|
1879
|
+
const face = new THREE.Mesh(new THREE.PlaneGeometry(0.92, 2.0), screen.material); // the layer's colours, as they are in 2D
|
|
1880
|
+
face.position.z = 0.046; // just in front of the body
|
|
1881
|
+
phone = new THREE.Group(); phone.add(body, face);
|
|
1882
|
+
ctx.scene.add(phone, new THREE.AmbientLight(0xffffff, 0.8), new THREE.DirectionalLight(0xffffff, 2.2));
|
|
1883
|
+
ctx.camera.position.set(0, 0, 2.9);
|
|
1884
|
+
},
|
|
1885
|
+
update: (t) => { phone.rotation.y = 0.6 * Math.sin(t * 0.7); } }, // a function of t, like any three layer
|
|
1886
|
+
];
|
|
1887
|
+
```
|
|
1888
|
+
|
|
1889
|
+
A tap on the phone's screen reaches the app: `movie.pick(...movie.canvasPoint(event))` answers with the layer inside the screen (`screen/button`, with `via` naming the phone), so the click and hover recipes work on a mesh as on a flat layer.
|
|
1890
|
+
|
|
1891
|
+
Rules: the layer must be a `composition` with a `width` and `height`; a mistake in the name lists the names that are there; the material is unlit (a screen shines by itself) so keep the scene's lights for the body; empty parts of the composition are black on the mesh unless you pass `{ transparent: true }` (a sticker or a rounded label). The full rules, the two routes (no copy on the shared GPU, a read-back otherwise) and what is not in this version: `pixi-effects help threeLayerTexture`.
|
|
1892
|
+
|
|
1859
1893
|
## Click a layer: what is under the pointer (`movie.pick`)
|
|
1860
1894
|
|
|
1861
1895
|
`movie.pick(x, y)` answers "what is drawn under this point, at the frame on screen" with no hit-testing code of yours: a flat layer by its own shape, a `threeD` card by its projected quad, a `three` layer by a ray (the hit says which object). `movie.canvasPoint(event)` turns a pointer event into canvas pixels, whatever size the canvas is shown at. Name the layers you want to react to: `only` / `ignore` take layer names (or paths; a group's name stands for everything in it). A `three` layer covers its whole picture but only what the ray meets is a hit: elsewhere the layers below are hit (the title, the background).
|
|
@@ -2499,7 +2533,7 @@ Audio-only sequence. No visual. Volume is animatable via keyframes.
|
|
|
2499
2533
|
|
|
2500
2534
|
Audio is mixed during `Movie.init()` and during `Movie.render()`. Volume keyframes interpolate linearly.
|
|
2501
2535
|
|
|
2502
|
-
**The loudness of the whole mix: `movie.init({ loudness: -16 })`.** Layers make whatever level their `volume` gives (a few `sfx` at volume 0.15 mix to about -40 LUFS, far quieter than web video's -14 to -16). `loudness` is a target for the finished mix in LUFS (ITU-R BS.1770): one gain, up or down,
|
|
2536
|
+
**The loudness of the whole mix: `movie.init({ loudness: -16 })`.** Layers make whatever level their `volume` gives (a few `sfx` at volume 0.15 mix to about -40 LUFS, far quieter than web video's -14 to -16). `loudness` is a target for the finished mix in LUFS (ITU-R BS.1770): one gain, up or down, and a look-ahead limiter that takes a click or a hit on top of a quiet bed down so that no peak passes -1 dBTP (true peak), the same for playback and export. A mix whose peaks are within 10 dB of what the limiter can take reaches the target (`check` says where the limiter worked hardest and which sounds play there when it took 3 dB or more off); a click 30 dB over its bed is out of reach and stops short (`reached -19.4 LUFS`), and `check` / `inspectAudio()` name the moment and the sounds that make it, with the fix (lower that sound, or ask for less). **Raising a `sfx` over music is safe:** the limiter keeps the peak, so give an accent the volume the `buried` note suggests (an accent over music needs 2 to 3). `false` or no option leaves the mix as the layers make it; `check` then says when it is quiet (found in a trial of nine films with sound: eight were -22 to -26 LUFS and only one was raised).
|
|
2503
2537
|
|
|
2504
2538
|
#### Sound effects without files: `sfx`
|
|
2505
2539
|
|
|
@@ -3125,6 +3159,30 @@ three({ type: 'three', width: 'GW', height: 'GH',
|
|
|
3125
3159
|
- **`threeFrameCamera(camera, object, { direction, margin })`** puts a `PerspectiveCamera` where the whole of `object` (its box now, with its scale and place) fits the frame, looking at its centre, from a three-quarter view (`direction` default `[0.45, 0.3, 1]`) with 10 % room (`margin` 1.1): the smallest distance at which the 8 corners of the box fit both the vertical and the horizontal field of view (a bounding sphere would leave a long model small and head-on). It uses the camera's own `fov` and `aspect` and sets `near` / `far`. The box is the bind pose: a clip that travels far needs a camera of your own.
|
|
3126
3160
|
- With `followCamera` the composition's camera is the layer's camera (do not call `threeFrameCamera`); with `followLights` its lights light the model. See the recipes `gltf-in-space` and `gltf-shadow-room`. `orbit()` and `cameraPath()` drive the composition's `camera` layer, so they carry a following scene too.
|
|
3127
3161
|
|
|
3162
|
+
### A 2D layer on a mesh: `threeLayerTexture`
|
|
3163
|
+
|
|
3164
|
+
`threeLayerTexture(ctx, 'screen')` makes a live 2D layer (a `composition`) the picture on a mesh of a `three` layer: a phone or laptop screen with an app running, a television, a billboard, a label on a bottle, a card with moving type. The 2D layer stays an ordinary layer (the timeline drives it, its own `at` / `duration` / keyframes / filters apply) but is no longer drawn on the stage: just before the three layer draws, PixiJS draws it into a texture and the mesh shows that texture.
|
|
3165
|
+
|
|
3166
|
+
```js
|
|
3167
|
+
import { threeLayerTexture } from 'pixi-effects/three';
|
|
3168
|
+
// in the composition, the 2D layer FIRST, then the three layer
|
|
3169
|
+
{ type: 'composition', name: 'screen', width: 480, height: 270, duration: 8, sequences: [ /* a background rect, your UI */ ] },
|
|
3170
|
+
three({ type: 'three', width: 'GW', height: 'GH',
|
|
3171
|
+
setup: async (ctx) => {
|
|
3172
|
+
const screen = await threeLayerTexture(ctx, 'screen', { resolution: 2 }); // { material, texture, width, height, dispose }
|
|
3173
|
+
ctx.scene.add(new THREE.Mesh(new THREE.PlaneGeometry(1.6, 0.9), screen.material));
|
|
3174
|
+
} })
|
|
3175
|
+
```
|
|
3176
|
+
|
|
3177
|
+
- **The rules.** The layer must be a `composition` with a `width` and `height`, in the same composition as the three layer and **listed before it** (a layer built later cannot be found: the error lists the names that are there). `await` the call in `setup`. `resolution` (default 1) is the pixels of the texture per pixel of the layer: 2 for a screen that is seen large. `transparent: true` lets what is behind the mesh show through the empty and half-transparent parts of the composition (a sticker, a label with rounded corners; the material blends and does not write depth); default `false`, an opaque screen. The layer's own place, scale and rotation do not matter: its content is drawn from (0, 0) to (width, height); its alpha and filters do.
|
|
3178
|
+
- **The material.** `screen.material` is unlit (a screen shines by itself; scene lights do not shade it) and carries the colour decoding; put it on a mesh, or on one face of a box (`[dark, dark, dark, dark, screen.material, dark]`). Use `screen.texture` yourself and its colours may be wrong. Without `transparent`, give the composition a background rect: what it leaves empty is black on the mesh.
|
|
3179
|
+
- **Two routes, the same colours** (measured: the background, a moving dot and a growing bar equal the 2D layer, channel for channel, on both). When the three layer has `renderer: 'webgpu'` and draws on PixiJS's own GPU device (a WebGPU movie) the texture is PixiJS's own GPU texture used through three's `ExternalTexture`: no copy, 0.3 ms a frame, and the page's import map needs `"three/webgpu"` and `"three/tsl"` next to `"three"` (the template's has only `"three"` and `"three/addons/"`: add the two lines for this route). Otherwise (the layer has the default `renderer: 'webgl'`, a canvas of its own with `shareGpu: false`, or PixiJS on WebGL) the pixels are read back into a canvas: 1.6 ms a frame at 480 x 270, 4 ms at 1280 x 720, and no import-map entry beyond the template's.
|
|
3180
|
+
- **A function of the time.** The 2D layer is drawn when the three layer draws, after the timeline has put every layer at the frame: a seek, an export and a repeat give the same picture (`check` draws the layer twice, in two orders, as for any three layer). A layer that has not started yet, or has ended, shows nothing on the mesh.
|
|
3181
|
+
- **A rebuild** (`setVars`, `resize`) keeps the three layer and shows the layer of the new tree (the text that changed shows on the mesh); a layer that changed size is drawn stretched to the texture.
|
|
3182
|
+
- **Seen small or at a slant** the texture is filtered, not aliased (1-pixel stripes on a plane a quarter of the picture come out as one even grey, not black and white stripes; four taps along a slant). The mip levels are PixiJS's own on the shared GPU, averaged in the stored sRGB values, so very fine bright detail seen very small is a little darker there than on a read-back canvas, whose mipmaps three makes in linear light (stripes average to 128 and 188).
|
|
3183
|
+
- **A click reaches the screen.** `movie.pick(x, y)` on a point of the mesh answers for the layers INSIDE the 2D layer, at the point of its picture under the pointer: the top hit is `screen/button` (a flat hit with `local` in the layer's own pixels), with `via: { path: 'phone', object: 'face' }` naming the three layer and the mesh it came through; under it come the screen's other layers, then the mesh itself. `only` / `ignore` see through the mesh (`only: ['screen']` is the screen's layers wherever they are shown), `movie.hover` and the click recipes work on a phone's screen as they do on a flat layer, and a layer that is not alive does not answer. A page asks the layer by its own name, so the button is one `pick` away: `if (movie.pick(...movie.canvasPoint(event), { only: ['button'] })) …`.
|
|
3184
|
+
- **Not in this version:** several meshes showing the same layer are answered through the first one the ray meets (that is the right one), but a layer shown on a mesh seen through another transparent mesh is not looked behind.
|
|
3185
|
+
|
|
3128
3186
|
### Keyframe paths: `three.<name>.<path>`
|
|
3129
3187
|
|
|
3130
3188
|
Objects returned from `setup`'s `{ objects }` are addressable from keyframes as `three.<name>.<path>`, using the same `from`/`to`/`set` vocabulary as every other prop:
|
|
@@ -4153,7 +4211,7 @@ interface MovieOptions {
|
|
|
4153
4211
|
composition?: CompositionSpec; // root composition (see DSL reference)
|
|
4154
4212
|
vars?: Record<string, number | string>; // named values the composition reads: a number as a name in an expression, any as {name} in a string (see Variables)
|
|
4155
4213
|
loop?: boolean; // start with movie.loop on: playing goes back to the start at the end (default false, like a <video>)
|
|
4156
|
-
loudness?: number | false; // the whole mix at this many LUFS (-16 is usual for web video): one gain, up or down, the
|
|
4214
|
+
loudness?: number | false; // the whole mix at this many LUFS (-16 is usual for web video): one gain, up or down, and a look-ahead limiter keeping the true peak at -1 dBTP or under (a click over a quiet bed is pulled down; more than 10 dB of limiting is out of reach and `check` names it); false / left out: the mix as the layers make it (check says when it is quiet)
|
|
4157
4215
|
poster?: number; // seconds: the frame shown before play (negative: from the end), see movie.poster
|
|
4158
4216
|
motionBlur?: boolean | number | { samples?: number; shutter?: number }; // blur for render() / snapshot() / contactSheet(), not live playback; see Motion blur
|
|
4159
4217
|
loader?: HTMLElement | string | false; // the page's loader (see pixi-effects/loader.css): shows how far init is (a bar, "BUILDING LAYERS 62%"), faded out when ready, stopped if init fails; default: a .pe-loader next to the canvas
|
|
@@ -4286,6 +4344,7 @@ interface PickHit {
|
|
|
4286
4344
|
kind: 'flat' | 'card' | 'three'; // a 2D layer, a threeD layer (its projected card), a three layer
|
|
4287
4345
|
point: { x: number; y: number }; // the point asked about
|
|
4288
4346
|
local?: { x: number; y: number }; // a flat layer: the point in the layer's own pixels
|
|
4347
|
+
via?: { path: string; object: string }; // the layer is shown on a mesh of a `three` layer (threeLayerTexture) and the point is on that mesh: the three layer and the object it came through
|
|
4289
4348
|
three?: { object: string; point: [number, number, number]; distance: number; object3d: unknown; // a three layer: what the ray met first
|
|
4290
4349
|
grab?: (plane?) => { move: (x, y) => { point, delta } | null } }; // drag it: see below
|
|
4291
4350
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ name: pixi-effects
|
|
|
3
3
|
description: Use when asked to make, animate, preview or export a video, motion graphics, an animated title, a lower third, kinetic type, a promo, a slideshow with transitions, an animated chart or data story, a social clip, a 2.5D / depth scene, or music and sound for a video, written as code in JavaScript or HTML. pixi-effects is a JS library where a video is a plain-object composition (text, shape, image, video, audio and file-free sfx / music, camera, light and shader layers with keyframes) that plays in the browser, is checked by a tool the agent can run, and exports MP4 / WebM / MOV with no server. Not for a project that already uses another video tool (Remotion, Motion Canvas, ffmpeg); follow that tool instead.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
> Written for pixi-effects 0.
|
|
6
|
+
> Written for pixi-effects 0.47.0. Update this skill with `npx skills update`; the newest docs are at https://yjmtmtk.github.io/pixi-effects/
|
|
7
7
|
|
|
8
8
|
# pixi-effects — write a video as data
|
|
9
9
|
|
|
@@ -47,7 +47,7 @@ blendMode: 'normal' | 'add' | 'screen' | 'multiply' | 'overlay' | 'soft-light' |
|
|
|
47
47
|
|
|
48
48
|
Animatable props (`initial` / keyframes): `x y alpha rotation scale scaleX scaleY pivotX pivotY anchorX anchorY skewX skewY tint width height visible autoAlpha`, plus for shapes their style/geometry, for text `fill`, for audio `volume`, for 3D `z rotationX rotationY`, filter params as `'filters.<name>.<param>'`, three objects as `'three.<obj>.<path>'`. **These go in `initial` / keyframes, never at the top level of a shape or text layer** (top-level `x`, `rotation`, `strokeColor`, `fillAlpha` each warn "is not a … key"): the top level of a shape holds its geometry (`width height radius d startAngle endAngle cornerRadius anchorX anchorY`), `trimStart` / `trimEnd`, `strokeCap` / `strokeJoin`, `fillGradient`.
|
|
49
49
|
|
|
50
|
-
Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate to), `from` (animate from), `from`+`to`; a value may be relative to where the layer stands (`from: { x: '-=36' }` slides in from 36 px to the left of its place). `from` / `from+to` show their start value from the layer's start, so a delayed fade-in needs no `initial.alpha`. `repeat` (finite extra plays), `yoyo`, `repeatDelay` loop any of them (endless repeats are not allowed). `ease` = an ease name (`'none'`, `'power2.out'`, `'expo.out'`, `'back.out(1.7)'`, `'elastic.out(1,0.5)'`, `'sine.inOut'`, `'bounce.out'`, **`'cubic-bezier(.4, 0, .2, 1)'`** (CSS syntax, x1 and x2 in 0…1; `.4, 0, .2, 1` is a gentle in-out; `.2, .8, .2, 1` is FRONT-LOADED, not slow motion: half of the change in the first 13 % of the time, 90 % by 39 %, 99 % by 74 %, then a long settle, so what sits at the END of the content gets the glide; `.2, .8, .5, 1` has a shorter tail). `power1…4` are the powers 2…5: `power2` is a cubic, `power2.in` has done only 12.5 % at half time (42 % at three quarters)… and springs: `'spring(mass, stiffness, damping)'` or `'spring.gentle' | 'snappy' | 'bouncy' | 'wobbly' | 'slow'`; **`duration: 'auto'` = the time the spring takes to settle** (presets, settle time / highest value reached as a multiple of the target: `snappy` 0.42 s / 1.01, `gentle` 0.72 s / 1.02, `bouncy` 1.08 s / 1.28, `slow` 1.29 s / 1.00, `wobbly` 1.61 s / 1.40: time the next beat from these) (real physics; with a number `duration` is the settle time and only the damping ratio shapes it; a damping ratio of 1 or more never overshoots, so `spring(1,170,26)` does not bounce: use damping below 20 at stiffness 170; `'auto'` works on keyframes only, not in `animateText` / `orbit` tweens; a `stagger` ease may be a spring, its delays are held inside the spread). Default ease is linear.
|
|
50
|
+
Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate to), `from` (animate from), `from`+`to`; a value may be relative to where the layer stands (`from: { x: '-=36' }` slides in from 36 px to the left of its place). `from` / `from+to` show their start value from the layer's start, so a delayed fade-in needs no `initial.alpha`. `repeat` (finite extra plays), `yoyo`, `repeatDelay` loop any of them (endless repeats are not allowed: `repeat: -1` (the GSAP habit) is refused with a warning, write a count that covers the layer, `repeat: 40`; a `from` keyframe with a later `at` puts its start value on the layer from frame 0, the layer does not wait for the `at`). `ease` = an ease name (`'none'`, `'power2.out'`, `'expo.out'`, `'back.out(1.7)'`, `'elastic.out(1,0.5)'`, `'sine.inOut'`, `'bounce.out'`, **`'cubic-bezier(.4, 0, .2, 1)'`** (CSS syntax, x1 and x2 in 0…1; `.4, 0, .2, 1` is a gentle in-out; `.2, .8, .2, 1` is FRONT-LOADED, not slow motion: half of the change in the first 13 % of the time, 90 % by 39 %, 99 % by 74 %, then a long settle, so what sits at the END of the content gets the glide; `.2, .8, .5, 1` has a shorter tail). `power1…4` are the powers 2…5: `power2` is a cubic, `power2.in` has done only 12.5 % at half time (42 % at three quarters)… and springs: `'spring(mass, stiffness, damping)'` or `'spring.gentle' | 'snappy' | 'bouncy' | 'wobbly' | 'slow'`; **`duration: 'auto'` = the time the spring takes to settle** (presets, settle time / highest value reached as a multiple of the target: `snappy` 0.42 s / 1.01, `gentle` 0.72 s / 1.02, `bouncy` 1.08 s / 1.28, `slow` 1.29 s / 1.00, `wobbly` 1.61 s / 1.40: time the next beat from these) (real physics; with a number `duration` is the settle time and only the damping ratio shapes it; a damping ratio of 1 or more never overshoots, so `spring(1,170,26)` does not bounce: use damping below 20 at stiffness 170; `'auto'` works on keyframes only, not in `animateText` / `orbit` tweens; a `stagger` ease may be a spring, its delays are held inside the spread). Default ease is linear.
|
|
51
51
|
|
|
52
52
|
## Layer types
|
|
53
53
|
|
|
@@ -87,7 +87,7 @@ Style (`initial` / keyframes): `fillColor fillAlpha strokeColor strokeAlpha stro
|
|
|
87
87
|
|
|
88
88
|
**camera** (2.5D) — props in `initial`/keyframes: `x y z lookAtX lookAtY lookAtZ fov focus aperture` (defaults: centred, looking at the z = 0 plane, `fov` 40, `z` auto = `(H/2)/tan(fov/2)` ≈ 989 at 720p). **Depth of field:** `focus` = the sharp plane (a `threeD` layer's name = its first z, or a z number), `aperture` = the lens diameter in px (30 default, 0 off); writing either blurs the other `threeD` layers by their distance from the focus (a rack focus is a keyframe on `focus`; recipe `rack-focus`; `inspect` reports `depthBlur`, and `check --at 2.4` prints each layer's blur). A dolly with no typed distance: leave `z` automatic and animate `offsetZ`. Never on the camera object itself. An orbit is `orbit()`; by hand: `x = cx + R·sin θ`, `z = R·cos θ`, `R = (H/2)/tan(fov/2)`, `lookAt` = the centre at `z = 0`.
|
|
89
89
|
|
|
90
|
-
**three** (optional entry `pixi-effects/three`) — `three({ type:'three', width, height, setup(ctx) { …; return { objects: { knot } } }, update?, dispose? })`; drive with `'three.knot.rotation.y'` keyframes. Call `registerThree()` before `init`. **In the composition's space:** `followCamera: true` (the layer's PerspectiveCamera is the composition's `camera`; the scene is in composition px with y flipped: the composition's `(x, y, z)` is `(x, -y, z)`; give the layer the composition's size), `followLights: true` (the `light` layers become three lights) and `followFog: true` (the camera's fog becomes the scene's `Fog`: close to, not equal to, the card fog); a light's `castsShadows` casts in three (mark your meshes `castShadow` / `receiveShadow`); `ctx.space` has the raw numbers. **glTF:** `import { threeGltf, threeFrameCamera } from 'pixi-effects/three'`: `const m = await threeGltf(url, { animation: 'Run' | 0 | false, speed, offset, loop })` in `setup` (`ctx.scene.add(m.object)`; `m.fit(px)` scales the longest side to px; `m.clips` names the clips), `update: (t) => m.pose(t)` (a function of t: never `mixer.update(dt)`), `threeFrameCamera(ctx.camera, m.object)` frames the whole model from a three-quarter view. Needs `"three/addons/"` in the import map. Recipe `gltf-in-space`.
|
|
90
|
+
**three** (optional entry `pixi-effects/three`) — `three({ type:'three', width, height, setup(ctx) { …; return { objects: { knot } } }, update?, dispose? })`; drive with `'three.knot.rotation.y'` keyframes. Call `registerThree()` before `init`. **In the composition's space:** `followCamera: true` (the layer's PerspectiveCamera is the composition's `camera`; the scene is in composition px with y flipped: the composition's `(x, y, z)` is `(x, -y, z)`; give the layer the composition's size), `followLights: true` (the `light` layers become three lights) and `followFog: true` (the camera's fog becomes the scene's `Fog`: close to, not equal to, the card fog); a light's `castsShadows` casts in three (mark your meshes `castShadow` / `receiveShadow`); `ctx.space` has the raw numbers. **glTF:** `import { threeGltf, threeFrameCamera } from 'pixi-effects/three'`: `const m = await threeGltf(url, { animation: 'Run' | 0 | false, speed, offset, loop })` in `setup` (`ctx.scene.add(m.object)`; `m.fit(px)` scales the longest side to px; `m.clips` names the clips), `update: (t) => m.pose(t)` (a function of t: never `mixer.update(dt)`), `threeFrameCamera(ctx.camera, m.object)` frames the whole model from a three-quarter view. Needs `"three/addons/"` in the import map. Recipe `gltf-in-space`. **A 2D layer on a mesh** (a phone screen, a television): `const screen = await threeLayerTexture(ctx, 'screen')` in `setup`, `screen.material` on a mesh; `screen` is a `composition` listed BEFORE the three layer (recipe `three-screen`).
|
|
91
91
|
|
|
92
92
|
## Camera flight and a camera shake
|
|
93
93
|
|
|
@@ -158,7 +158,7 @@ measureText('Hello', style) // → { width, height,
|
|
|
158
158
|
splitText('Hello world', style, { by: 'chars' | 'words' | 'lines', x, y, align: 'left' | 'center' | 'right' })
|
|
159
159
|
// → [{ text, x, y, width, index, line }] — one text layer per piece, same style, anchor top-left
|
|
160
160
|
```
|
|
161
|
-
Per-letter / per-word / per-line animation in one call: `...animateText(text, style, { x, y, align, vertical: true /* tategaki: down a column, new line = new column to the left, x = RIGHT edge of the first column; 、。 ー 「」 placed and turned for you */, by: 'chars'|'words'|'lines', at, duration /* required: until the last piece is gone */, in: 'rise'|'drop'|'fade'|'pop'|'zoom'|'slide'|'spin'|{ from, to, duration, ease }|false, out: <same, in the same wave (leave `out` out and the pieces stay until the layer ends); by default BACKWARDS FROM THE LAYER'S END (at + duration, so to be gone at time T set duration = T − at); `out: { preset: 'fade', at: 6 }` starts the exit at composition second 6 instead
|
|
161
|
+
Per-letter / per-word / per-line animation in one call: `...animateText(text, style, { x, y, align, vertical: true /* tategaki: down a column, new line = new column to the left, x = RIGHT edge of the first column; 、。 ー 「」 placed and turned for you */, by: 'chars'|'words'|'lines', at, duration /* required: until the last piece is gone */, in: 'rise'|'drop'|'fade'|'pop'|'zoom'|'slide'|'spin'|{ from, to, duration, ease }|false, out: <same, in the same wave (leave `out` out and the pieces stay until the layer ends); by default BACKWARDS FROM THE LAYER'S END (at + duration, so to be gone at time T set duration = T − at); `out: { preset: 'fade', at: 6 }` starts the exit at composition second 6 instead; `out: { preset: 'fade', at: 6, stagger: 0 }` makes everything leave AT ONCE after it came in letter by letter (or `stagger: { each: 0.1 }`: its own wave); an exit is described by `from` = the state the piece leaves TO: `out: { from: { y: 18, alpha: 0 } }`; no exit: omit `out`, or `out: false` / `'none'`>, stagger: { each, from, … }, idle: { y: 4, rotation: 1 }, styleFor: p => ({ fill }) })` → text layers `<name>-0…` (anchor centre; `x`/`y` in `from` are offsets) to spread into `sequences`. **`x` / `y` are pixel NUMBERS (an expression like `'GW/2'` throws: compute `W / 2`), and `y` is the TOP of the line box**: the visible text is centred `lineHeight / 2` below it, so an underline goes at `y + lineHeight`, not at `y` (recipe `animate-text`). By hand: `splitText(...)` + one layer per piece (recipe `split-text`). A web font must be loaded before measuring.
|
|
162
162
|
|
|
163
163
|
## Assets
|
|
164
164
|
|