hayao 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/bin/create-hayao.mjs +1 -1
- package/dist/anim/blend.d.ts +68 -0
- package/dist/anim/clip.d.ts +87 -0
- package/dist/anim/ik.d.ts +40 -0
- package/dist/anim/skeleton.d.ts +64 -0
- package/dist/app/browser.d.ts +33 -3
- package/dist/app/game.d.ts +15 -11
- package/dist/audio/audio.d.ts +18 -1
- package/dist/audio/synth.d.ts +7 -0
- package/dist/audio/zzfx.d.ts +14 -0
- package/dist/core/clock.d.ts +1 -1
- package/dist/core/projection.d.ts +36 -0
- package/dist/core/rng.d.ts +9 -0
- package/dist/hayao.global.js +190 -5
- package/dist/index.d.ts +20 -1
- package/dist/index.js +4159 -979
- package/dist/index.js.map +4 -4
- package/dist/index.min.js +190 -5
- package/dist/input/actions.d.ts +60 -6
- package/dist/input/gamepad.d.ts +101 -0
- package/dist/input/source.d.ts +85 -4
- package/dist/logic/coroutine.d.ts +68 -0
- package/dist/render/canvas.d.ts +3 -7
- package/dist/render/canvas2d-core.d.ts +13 -0
- package/dist/render/commands.d.ts +55 -2
- package/dist/render/lightRun.d.ts +35 -0
- package/dist/render/paint.d.ts +8 -0
- package/dist/render/renderer.d.ts +25 -0
- package/dist/render/svg.d.ts +2 -1
- package/dist/render/svgString.d.ts +8 -3
- package/dist/render/webgl.d.ts +176 -0
- package/dist/scene/clipPlayer.d.ts +58 -0
- package/dist/scene/ikTarget.d.ts +25 -0
- package/dist/scene/iso.d.ts +73 -0
- package/dist/scene/light.d.ts +80 -0
- package/dist/scene/node.d.ts +81 -2
- package/dist/scene/nodes.d.ts +34 -0
- package/dist/scene/particles.d.ts +19 -0
- package/dist/scene/shadow2d.d.ts +25 -0
- package/dist/scene/skeletonDebug.d.ts +28 -0
- package/dist/scene/verletChain.d.ts +76 -0
- package/dist/ui/touch.d.ts +51 -0
- package/dist/verify/dom.d.ts +26 -0
- package/dist/verify/gates.d.ts +25 -0
- package/dist/world.d.ts +72 -7
- package/docs/API.md +138 -27
- package/docs/CONVENTIONS.md +346 -2
- package/docs/EMBED.md +5 -0
- package/docs/QUICKSTART.md +13 -1
- package/docs/VERIFICATION.md +34 -4
- package/package.json +2 -1
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import type { DrawCommand } from './commands';
|
|
2
|
+
import { type Renderer, type RendererConfig, type Viewport } from './renderer';
|
|
3
|
+
import type { Vec2 } from '../core/math';
|
|
4
|
+
/** Value for a GLSL uniform: float, vec2, vec3, or vec4. */
|
|
5
|
+
export type UniformValue = number | [number, number] | [number, number, number] | [number, number, number, number];
|
|
6
|
+
/**
|
|
7
|
+
* One pass in a post-processing pipeline. Each pass is a GLSL fragment shader
|
|
8
|
+
* that receives:
|
|
9
|
+
* uniform sampler2D u_scene — the original Canvas2D rasterized frame
|
|
10
|
+
* uniform sampler2D u_prev — output of the previous pass (= u_scene for pass 0)
|
|
11
|
+
* uniform float u_time — wall-clock seconds (cosmetic only, never hashed)
|
|
12
|
+
* uniform vec2 u_resolution — display canvas size in pixels
|
|
13
|
+
* in vec2 v_uv — 0..1 UV with (0,0) at top-left
|
|
14
|
+
* out vec4 fragColor — output pixel
|
|
15
|
+
*
|
|
16
|
+
* Plus any custom uniforms added via setUniform() or the pass's own `uniforms` map.
|
|
17
|
+
*/
|
|
18
|
+
export interface PostProcessPass {
|
|
19
|
+
/** GLSL #version 300 es fragment shader source. */
|
|
20
|
+
shader: string;
|
|
21
|
+
/** Per-pass custom uniforms (merged with the renderer-level ones, pass overrides renderer). */
|
|
22
|
+
uniforms?: Record<string, UniformValue>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Ready-made GLSL fragment shaders for common post-processing effects.
|
|
26
|
+
* Use in setPostProcess() or as passes in setPipeline().
|
|
27
|
+
*
|
|
28
|
+
* Each effect references u_scene / u_prev / u_time / u_resolution as needed.
|
|
29
|
+
* Custom tuning: pass a custom uniform via setUniform() — names are noted below.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { WebGL2Renderer, WEBGL_EFFECTS } from '@hayao';
|
|
34
|
+
* const renderer = new WebGL2Renderer({ width, height, postProcess: WEBGL_EFFECTS.vignette });
|
|
35
|
+
* // Tune: renderer.setUniform('u_vignette', 0.5);
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare const WEBGL_EFFECTS: {
|
|
39
|
+
/** Passthrough — no effect. */
|
|
40
|
+
readonly passthrough: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_scene;\nuniform sampler2D u_prev;\nout vec4 fragColor;\nvoid main() { fragColor = texture(u_prev, v_uv); }";
|
|
41
|
+
/** Pixelate: blockSize in pixels (default 4). setUniform('u_block', N) */
|
|
42
|
+
readonly pixelate: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform vec2 u_resolution;\nuniform float u_block;\nout vec4 fragColor;\nvoid main() {\n float block = max(1.0, u_block > 0.0 ? u_block : 4.0);\n vec2 px = floor(v_uv * u_resolution / block) * block / u_resolution;\n fragColor = texture(u_prev, px);\n}";
|
|
43
|
+
/** Vignette: darkens edges. setUniform('u_vignette', 0.0..1.0, default 0.4) */
|
|
44
|
+
readonly vignette: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform float u_vignette;\nout vec4 fragColor;\nvoid main() {\n vec4 col = texture(u_prev, v_uv);\n vec2 uv = v_uv - 0.5;\n float d = dot(uv, uv);\n float strength = u_vignette > 0.0 ? u_vignette : 0.4;\n fragColor = col * (1.0 - d * strength * 4.0);\n}";
|
|
45
|
+
/** CRT scanlines + slight barrel distortion. u_time drives the scan roll. */
|
|
46
|
+
readonly crt: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform vec2 u_resolution;\nuniform float u_time;\nuniform float u_crt_lines; // scanline density, default 600\nuniform float u_crt_warp; // barrel strength, default 0.08\nout vec4 fragColor;\nvoid main() {\n float lines = u_crt_lines > 0.0 ? u_crt_lines : 600.0;\n float warp = u_crt_warp > 0.0 ? u_crt_warp : 0.08;\n // barrel distortion\n vec2 uv = v_uv - 0.5;\n float r2 = dot(uv, uv);\n uv *= 1.0 + warp * r2;\n uv += 0.5;\n if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {\n fragColor = vec4(0.0, 0.0, 0.0, 1.0);\n return;\n }\n vec4 col = texture(u_prev, uv);\n // rolling scanlines\n float scan = sin((uv.y * lines + u_time * 40.0) * 3.14159) * 0.5 + 0.5;\n col.rgb *= 0.75 + 0.25 * scan;\n // slight green phosphor tint\n col.rgb = mix(col.rgb, col.rgb * vec3(0.9, 1.05, 0.85), 0.3);\n fragColor = col;\n}";
|
|
47
|
+
/** Chromatic aberration / RGB fringe. setUniform('u_aberration', 0..0.01, default 0.004) */
|
|
48
|
+
readonly chromaticAberration: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform float u_aberration;\nout vec4 fragColor;\nvoid main() {\n float amt = u_aberration > 0.0 ? u_aberration : 0.004;\n vec2 dir = v_uv - 0.5;\n float r = texture(u_prev, v_uv + dir * amt * 2.0).r;\n float g = texture(u_prev, v_uv).g;\n float b = texture(u_prev, v_uv - dir * amt * 2.0).b;\n fragColor = vec4(r, g, b, 1.0);\n}";
|
|
49
|
+
/** Palette-shift / hue rotation. u_hue_shift in radians, driven by u_time for animation. */
|
|
50
|
+
readonly hueRotate: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform float u_hue_shift; // radians; if 0 uses u_time\nuniform float u_time;\nout vec4 fragColor;\nvoid main() {\n float angle = u_hue_shift != 0.0 ? u_hue_shift : u_time * 0.5;\n vec4 col = texture(u_prev, v_uv);\n // Hue rotate via Rodrigues in YIQ-ish space\n float c = cos(angle), s = sin(angle);\n mat3 m = mat3(\n 0.299+0.701*c+0.168*s, 0.587-0.587*c+0.330*s, 0.114-0.114*c-0.497*s,\n 0.299-0.299*c-0.328*s, 0.587+0.413*c+0.035*s, 0.114-0.114*c+0.292*s,\n 0.299-0.300*c+1.250*s, 0.587-0.588*c-1.050*s, 0.114+0.886*c-0.203*s\n );\n fragColor = vec4(m * col.rgb, col.a);\n}";
|
|
51
|
+
/** Wave distortion (heat haze, underwater). setUniform('u_wave_amp', default 0.003) */
|
|
52
|
+
readonly wave: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform float u_time;\nuniform float u_wave_amp;\nuniform float u_wave_freq;\nout vec4 fragColor;\nvoid main() {\n float amp = u_wave_amp > 0.0 ? u_wave_amp : 0.003;\n float freq = u_wave_freq > 0.0 ? u_wave_freq : 8.0;\n vec2 uv = v_uv;\n uv.x += sin(uv.y * freq + u_time * 2.5) * amp;\n uv.y += cos(uv.x * freq + u_time * 2.5) * amp;\n fragColor = texture(u_prev, clamp(uv, 0.0, 1.0));\n}";
|
|
53
|
+
/** Bloom pass 1/4: extract bright pixels above threshold (default 0.6). */
|
|
54
|
+
readonly bloomBrightpass: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform float u_bloom_threshold;\nout vec4 fragColor;\nvoid main() {\n vec4 col = texture(u_prev, v_uv);\n float lum = dot(col.rgb, vec3(0.2126, 0.7152, 0.0722));\n float thresh = u_bloom_threshold > 0.0 ? u_bloom_threshold : 0.55;\n float weight = smoothstep(thresh - 0.05, thresh + 0.05, lum);\n fragColor = col * weight;\n}";
|
|
55
|
+
/** Bloom pass 2/4: horizontal gaussian blur (13-tap). */
|
|
56
|
+
readonly bloomHBlur: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform vec2 u_resolution;\nuniform float u_bloom_spread;\nout vec4 fragColor;\nvoid main() {\n float spread = u_bloom_spread > 0.0 ? u_bloom_spread : 1.0;\n vec2 texel = vec2(spread / u_resolution.x, 0.0);\n const float W[7] = float[](0.0625, 0.125, 0.1875, 0.25, 0.1875, 0.125, 0.0625);\n vec4 sum = vec4(0.0);\n for (int i = 0; i < 7; i++)\n sum += texture(u_prev, clamp(v_uv + float(i-3) * texel, 0.0, 1.0)) * W[i];\n fragColor = sum;\n}";
|
|
57
|
+
/** Bloom pass 3/4: vertical gaussian blur (13-tap). */
|
|
58
|
+
readonly bloomVBlur: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_prev;\nuniform vec2 u_resolution;\nuniform float u_bloom_spread;\nout vec4 fragColor;\nvoid main() {\n float spread = u_bloom_spread > 0.0 ? u_bloom_spread : 1.0;\n vec2 texel = vec2(0.0, spread / u_resolution.y);\n const float W[7] = float[](0.0625, 0.125, 0.1875, 0.25, 0.1875, 0.125, 0.0625);\n vec4 sum = vec4(0.0);\n for (int i = 0; i < 7; i++)\n sum += texture(u_prev, clamp(v_uv + float(i-3) * texel, 0.0, 1.0)) * W[i];\n fragColor = sum;\n}";
|
|
59
|
+
/** Bloom pass 4/4: composite original + blurred bloom. u_bloom_intensity controls strength. */
|
|
60
|
+
readonly bloomComposite: "#version 300 es\nprecision mediump float;\nin vec2 v_uv;\nuniform sampler2D u_scene;\nuniform sampler2D u_prev;\nuniform float u_bloom_intensity;\nout vec4 fragColor;\nvoid main() {\n vec4 scene = texture(u_scene, v_uv);\n vec4 bloom = texture(u_prev, v_uv);\n float intensity = u_bloom_intensity > 0.0 ? u_bloom_intensity : 0.8;\n fragColor = vec4(scene.rgb + bloom.rgb * intensity, scene.a);\n}";
|
|
61
|
+
};
|
|
62
|
+
export declare const BLOOM_PIPELINE: PostProcessPass[];
|
|
63
|
+
export interface WebGLRendererConfig extends RendererConfig {
|
|
64
|
+
/**
|
|
65
|
+
* Initial post-processing shader or pipeline. Pass a GLSL string for a
|
|
66
|
+
* single-pass effect, or use setPipeline() for multi-pass. Defaults to
|
|
67
|
+
* passthrough.
|
|
68
|
+
*/
|
|
69
|
+
postProcess?: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* GPU-accelerated renderer with a programmable GLSL post-processing pipeline.
|
|
73
|
+
*
|
|
74
|
+
* ## Quick start
|
|
75
|
+
* ```ts
|
|
76
|
+
* import { WebGL2Renderer, WEBGL_EFFECTS, BLOOM_PIPELINE } from '@hayao';
|
|
77
|
+
*
|
|
78
|
+
* // Single-pass:
|
|
79
|
+
* const renderer = new WebGL2Renderer({ width, height,
|
|
80
|
+
* postProcess: WEBGL_EFFECTS.vignette });
|
|
81
|
+
* renderer.setUniform('u_vignette', 0.5);
|
|
82
|
+
*
|
|
83
|
+
* // Multi-pass bloom:
|
|
84
|
+
* const renderer = new WebGL2Renderer({ width, height });
|
|
85
|
+
* renderer.setPipeline(BLOOM_PIPELINE);
|
|
86
|
+
* renderer.setUniform('u_bloom_intensity', 1.2);
|
|
87
|
+
*
|
|
88
|
+
* // Runtime effect swap:
|
|
89
|
+
* renderer.setPostProcess(WEBGL_EFFECTS.crt);
|
|
90
|
+
* renderer.clearPostProcess(); // back to passthrough
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* ## Auto-uniforms (available in every shader, every pass)
|
|
94
|
+
* - `uniform float u_time` — wall-clock seconds (cosmetic, never hashed)
|
|
95
|
+
* - `uniform vec2 u_resolution` — display canvas size in px (w, h)
|
|
96
|
+
* - `uniform sampler2D u_scene` — original Canvas2D rasterized frame
|
|
97
|
+
* - `uniform sampler2D u_prev` — output of the previous pass (= u_scene for pass 0)
|
|
98
|
+
*
|
|
99
|
+
* ## Determinism
|
|
100
|
+
* `u_time` is wall-clock and NOT part of `world.hash()`. The renderer is a
|
|
101
|
+
* pure display-list consumer — it never writes state back into the engine.
|
|
102
|
+
* All verify passes run against `HeadlessRenderer` / `SvgRenderer` and see
|
|
103
|
+
* the same DrawCommand[] regardless of post-processing.
|
|
104
|
+
*/
|
|
105
|
+
export declare class WebGL2Renderer implements Renderer {
|
|
106
|
+
readonly width: number;
|
|
107
|
+
readonly height: number;
|
|
108
|
+
private background;
|
|
109
|
+
private glCanvas;
|
|
110
|
+
private gl;
|
|
111
|
+
private offscreen;
|
|
112
|
+
private ctx2d;
|
|
113
|
+
private dpr;
|
|
114
|
+
/** Original rasterized scene texture. */
|
|
115
|
+
private sceneTex;
|
|
116
|
+
/** Intermediate pass framebuffers (one per non-final pass). */
|
|
117
|
+
private fbos;
|
|
118
|
+
/** Compiled programs, one per pass. */
|
|
119
|
+
private programs;
|
|
120
|
+
/** Per-pass uniform maps (merged with global uniforms). */
|
|
121
|
+
private passUniforms;
|
|
122
|
+
/** Empty VAO for gl_VertexID-only draws. */
|
|
123
|
+
private vao;
|
|
124
|
+
/** Frames drawn, driving u_time at a nominal 60 fps (cosmetic, but deterministic). */
|
|
125
|
+
private framesDrawn;
|
|
126
|
+
/** Global custom uniforms applied to every pass. */
|
|
127
|
+
private uniforms;
|
|
128
|
+
constructor(config: WebGLRendererConfig);
|
|
129
|
+
mount(parent: HTMLElement): void;
|
|
130
|
+
private resize;
|
|
131
|
+
private buildPipeline;
|
|
132
|
+
draw(commands: DrawCommand[]): void;
|
|
133
|
+
/**
|
|
134
|
+
* Set a custom uniform applied to every pass. Useful for knob-driven effect
|
|
135
|
+
* parameters (intensity, scale, colour) without recompiling the shader.
|
|
136
|
+
* The value is set on every draw() until cleared or overridden.
|
|
137
|
+
*
|
|
138
|
+
* ```ts
|
|
139
|
+
* renderer.setUniform('u_vignette', 0.5);
|
|
140
|
+
* renderer.setUniform('u_bloom_intensity', [1.0, 0.8]);
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
setUniform(name: string, value: UniformValue): this;
|
|
144
|
+
/** Remove a custom uniform. */
|
|
145
|
+
clearUniform(name: string): this;
|
|
146
|
+
/**
|
|
147
|
+
* Replace the post-processing pipeline. Accepts an array of passes; each
|
|
148
|
+
* pass reads `u_prev` (previous output) and `u_scene` (original frame).
|
|
149
|
+
* Throws on shader compile error — previous pipeline stays active.
|
|
150
|
+
*
|
|
151
|
+
* For single-pass effects, prefer `setPostProcess(shader)`. For the standard
|
|
152
|
+
* bloom setup, use `setPipeline(BLOOM_PIPELINE)`.
|
|
153
|
+
*
|
|
154
|
+
* ```ts
|
|
155
|
+
* renderer.setPipeline([
|
|
156
|
+
* { shader: WEBGL_EFFECTS.bloomBrightpass },
|
|
157
|
+
* { shader: WEBGL_EFFECTS.bloomHBlur },
|
|
158
|
+
* { shader: WEBGL_EFFECTS.bloomVBlur },
|
|
159
|
+
* { shader: WEBGL_EFFECTS.bloomComposite },
|
|
160
|
+
* ]);
|
|
161
|
+
* renderer.setUniform('u_bloom_intensity', 1.0);
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
setPipeline(passes: PostProcessPass[]): this;
|
|
165
|
+
/**
|
|
166
|
+
* Convenience: set a single-pass post-processing shader.
|
|
167
|
+
* Throws on compile error — previous effect stays active.
|
|
168
|
+
*/
|
|
169
|
+
setPostProcess(fragmentShader: string): this;
|
|
170
|
+
/** Restore passthrough (no post-processing). */
|
|
171
|
+
clearPostProcess(): this;
|
|
172
|
+
get element(): HTMLCanvasElement;
|
|
173
|
+
toDesign(clientX: number, clientY: number): Vec2;
|
|
174
|
+
viewport(): Viewport;
|
|
175
|
+
dispose(): void;
|
|
176
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Node, type NodeConfig } from './node';
|
|
2
|
+
import { type ClipDef } from '../anim/clip';
|
|
3
|
+
export interface ClipPlayerConfig extends NodeConfig {
|
|
4
|
+
/** The rig root this player poses. Defaults to the player's own parent at ready. */
|
|
5
|
+
rig?: Node;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Plays named clips onto a rig. Add clips with `add(name, def)`, then `play(name,
|
|
9
|
+
* {fade})`. Reads: `time` (current playhead, s), `current` (playing clip name).
|
|
10
|
+
* Signals: `event` (payload = event name) and `finished` (a `once` clip ended).
|
|
11
|
+
*/
|
|
12
|
+
export declare class ClipPlayer extends Node {
|
|
13
|
+
readonly type = "ClipPlayer";
|
|
14
|
+
private rig;
|
|
15
|
+
private skeleton;
|
|
16
|
+
private clips;
|
|
17
|
+
private currentName;
|
|
18
|
+
private elapsed;
|
|
19
|
+
private prevName;
|
|
20
|
+
private prevElapsed;
|
|
21
|
+
private fadeDur;
|
|
22
|
+
private fadeT;
|
|
23
|
+
private poseA;
|
|
24
|
+
private poseB;
|
|
25
|
+
private poseMix;
|
|
26
|
+
constructor(config?: ClipPlayerConfig);
|
|
27
|
+
protected onReady(): void;
|
|
28
|
+
/** Register a clip under `name`. Rebinds its tracks against the current rig. */
|
|
29
|
+
add(name: string, def: ClipDef): this;
|
|
30
|
+
/** Re-resolve the rig + every clip's tracks (call after the rig subtree changes). */
|
|
31
|
+
rebind(rig?: Node): void;
|
|
32
|
+
private ensureSkeleton;
|
|
33
|
+
/**
|
|
34
|
+
* Play `name`, optionally crossfading over `fade` seconds from whatever is
|
|
35
|
+
* playing. Restarting the same clip with no fade rewinds it. A fade freezes the
|
|
36
|
+
* outgoing clip's playhead and blends it out with EASINGS.quadInOut.
|
|
37
|
+
*/
|
|
38
|
+
play(name: string, opts?: {
|
|
39
|
+
fade?: number;
|
|
40
|
+
}): void;
|
|
41
|
+
/** Stop playback (freezes the rig at its current pose). */
|
|
42
|
+
stop(): void;
|
|
43
|
+
/** Current playhead in seconds (raw elapsed, pre-loop-wrap). */
|
|
44
|
+
get time(): number;
|
|
45
|
+
/** The clip currently playing, or null. */
|
|
46
|
+
get current(): string | null;
|
|
47
|
+
/** Fired with the event name each time the playhead crosses a clip event. */
|
|
48
|
+
get event(): import("..").Signal<string>;
|
|
49
|
+
/** Fired when a `once` clip reaches its end. */
|
|
50
|
+
get finished(): import("..").Signal<string>;
|
|
51
|
+
protected onProcess(dt: number): void;
|
|
52
|
+
/** Direct steady-state apply: sample each bound track and write its channel. */
|
|
53
|
+
private applyDirect;
|
|
54
|
+
/** Sample an entry's bound tracks into a reusable Pose (clears then fills). */
|
|
55
|
+
private samplePose;
|
|
56
|
+
/** Write a mixed pose back onto the incoming entry's bound nodes. */
|
|
57
|
+
private applyPose;
|
|
58
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Node, type NodeConfig } from './node';
|
|
2
|
+
import { Bone2D } from '../anim/skeleton';
|
|
3
|
+
export interface IkTargetConfig extends NodeConfig {
|
|
4
|
+
/** The bone chain to solve, ROOT first. 2 bones → analytic solve; N → FABRIK. */
|
|
5
|
+
bones?: Bone2D[];
|
|
6
|
+
/** Elbow/knee bend side for the 2-bone solve (+1 or −1). Default 1. */
|
|
7
|
+
bendDir?: 1 | -1;
|
|
8
|
+
/** FABRIK iteration cap (N-bone only). Default 16. */
|
|
9
|
+
maxIter?: number;
|
|
10
|
+
}
|
|
11
|
+
/** A cosmetic node that drives a Bone2D chain to reach the node's own world position. */
|
|
12
|
+
export declare class IkTarget extends Node {
|
|
13
|
+
readonly type = "IkTarget";
|
|
14
|
+
bones: Bone2D[];
|
|
15
|
+
bendDir: 1 | -1;
|
|
16
|
+
maxIter: number;
|
|
17
|
+
/** True if the chain reached the target on the last solve (else it was clamped). */
|
|
18
|
+
reached: boolean;
|
|
19
|
+
constructor(config?: IkTargetConfig);
|
|
20
|
+
/** Repoint the solver at a new chain (root-first). */
|
|
21
|
+
setBones(bones: Bone2D[]): void;
|
|
22
|
+
protected onProcess(_dt: number): void;
|
|
23
|
+
/** Current joint positions (parent space) for FABRIK seeding — stable elbow side. */
|
|
24
|
+
private currentJoints;
|
|
25
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Transform } from '../core/math';
|
|
2
|
+
import type { DrawCommand, Paint } from '../render/commands';
|
|
3
|
+
import { Node, type NodeConfig } from './node';
|
|
4
|
+
/**
|
|
5
|
+
* A raised isometric tile = a cube: a top diamond + a front-left and front-right
|
|
6
|
+
* parallelogram. `pos` is the GROUND cell centre (where an elev-0 diamond would
|
|
7
|
+
* sit); the block rises `height` screen-px toward −y. Give it a base `fill` and
|
|
8
|
+
* the two side faces auto-shade (left darkest, right mid) — or override any face
|
|
9
|
+
* with `top` / `left` / `right`. Emits the three faces in back-to-front order so
|
|
10
|
+
* the top always paints over the sides within the prism.
|
|
11
|
+
*/
|
|
12
|
+
export interface IsoPrismConfig extends NodeConfig {
|
|
13
|
+
/** Footprint diamond width (horizontal diagonal) in screen px. */
|
|
14
|
+
tileW: number;
|
|
15
|
+
/** Footprint diamond height (vertical diagonal) in screen px. */
|
|
16
|
+
tileH: number;
|
|
17
|
+
/** Block height in screen px (0 = a flat tile: only the top diamond). */
|
|
18
|
+
height: number;
|
|
19
|
+
/** Base colour; side faces derive from it unless overridden. */
|
|
20
|
+
fill: string;
|
|
21
|
+
/** Explicit face paints (override the auto-shade). */
|
|
22
|
+
top?: Paint;
|
|
23
|
+
left?: Paint;
|
|
24
|
+
right?: Paint;
|
|
25
|
+
/** Shared stroke for all faces (seams between tiles). */
|
|
26
|
+
stroke?: string;
|
|
27
|
+
strokeWidth?: number;
|
|
28
|
+
opacity?: number;
|
|
29
|
+
}
|
|
30
|
+
/** Multiply a #rgb / #rrggbb colour's channels by `f` (clamped) for face shading. */
|
|
31
|
+
export declare function shadeHex(hex: string, f: number): string;
|
|
32
|
+
export declare class IsoPrism extends Node {
|
|
33
|
+
readonly type = "IsoPrism";
|
|
34
|
+
tileW: number;
|
|
35
|
+
tileH: number;
|
|
36
|
+
height: number;
|
|
37
|
+
fill: string;
|
|
38
|
+
topPaint?: Paint;
|
|
39
|
+
leftPaint?: Paint;
|
|
40
|
+
rightPaint?: Paint;
|
|
41
|
+
stroke?: string;
|
|
42
|
+
strokeWidth?: number;
|
|
43
|
+
opacity?: number;
|
|
44
|
+
constructor(config: IsoPrismConfig);
|
|
45
|
+
protected draw(out: DrawCommand[], world: Transform): void;
|
|
46
|
+
protected serializeProps(): Record<string, unknown>;
|
|
47
|
+
applyProps(props: Record<string, unknown>): void;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A container that auto-assigns each child's painter `z` from a depth accessor,
|
|
51
|
+
* so overlapping sprites stack correctly without every mover re-setting `z` in
|
|
52
|
+
* its own update. The key is read at DRAW time (positions are final for the
|
|
53
|
+
* frame), integrating these children into the same global z-order as the rest of
|
|
54
|
+
* the scene. Godot's `YSort`, generalised to any axis:
|
|
55
|
+
* new DepthSort({ key: (n) => n.pos.y }) // top-down overlap
|
|
56
|
+
* new DepthSort({ key: (n) => n.userDepth }) // iso gx+gy
|
|
57
|
+
* See docs/CONVENTIONS.md §Depth for the z-from-depth convention.
|
|
58
|
+
*/
|
|
59
|
+
export interface DepthSortConfig extends NodeConfig {
|
|
60
|
+
/** Depth of a child → its `z`. Larger = drawn on top (painter's order). */
|
|
61
|
+
key: (n: Node) => number;
|
|
62
|
+
/** Multiplier applied to the key before it becomes `z` (default 1). */
|
|
63
|
+
depthScale?: number;
|
|
64
|
+
}
|
|
65
|
+
export declare class DepthSort extends Node {
|
|
66
|
+
readonly type = "DepthSort";
|
|
67
|
+
key: (n: Node) => number;
|
|
68
|
+
depthScale: number;
|
|
69
|
+
/** DepthSort is a pure ordering container — its z assignments are view-only. */
|
|
70
|
+
cosmetic: boolean;
|
|
71
|
+
constructor(config: DepthSortConfig);
|
|
72
|
+
collectDraw(out: DrawCommand[], parentWorld?: Transform): void;
|
|
73
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type Transform } from '../core/math';
|
|
2
|
+
import { type DrawCommand } from '../render/commands';
|
|
3
|
+
import { Node, type NodeConfig } from './node';
|
|
4
|
+
import { type Occluder } from './shadow2d';
|
|
5
|
+
export interface PointLightConfig extends NodeConfig {
|
|
6
|
+
/** Pool radius in design-space px. */
|
|
7
|
+
radius?: number;
|
|
8
|
+
/** Light color (hex). */
|
|
9
|
+
color?: string;
|
|
10
|
+
/** Peak intensity 0..1 (scales the pool's brightness). */
|
|
11
|
+
intensity?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Falloff exponent for the radial gradient's mid stop (>1 = tighter core,
|
|
14
|
+
* <1 = broader glow). Default 1.
|
|
15
|
+
*/
|
|
16
|
+
falloff?: number;
|
|
17
|
+
/** Flicker: sinusoidal + noise wobble of intensity. Off (amount 0) by default. */
|
|
18
|
+
flicker?: {
|
|
19
|
+
amount: number;
|
|
20
|
+
speed: number;
|
|
21
|
+
};
|
|
22
|
+
/** Private flicker-Rng seed (observer-side, not serialized). Default 43. */
|
|
23
|
+
seed?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A radial point light. Emits nothing itself (`draw` is empty) — its parent
|
|
27
|
+
* LightLayer reads its world position, radius, color, and `litIntensity` to
|
|
28
|
+
* build the run. `litIntensity` is cached each `onProcess` from the flicker
|
|
29
|
+
* stream; with flicker off it equals `intensity` exactly.
|
|
30
|
+
*/
|
|
31
|
+
export declare class PointLight extends Node {
|
|
32
|
+
readonly type: string;
|
|
33
|
+
radius: number;
|
|
34
|
+
color: string;
|
|
35
|
+
intensity: number;
|
|
36
|
+
falloff: number;
|
|
37
|
+
flicker: {
|
|
38
|
+
amount: number;
|
|
39
|
+
speed: number;
|
|
40
|
+
};
|
|
41
|
+
/** Current intensity after flicker — read by the LightLayer during draw. */
|
|
42
|
+
litIntensity: number;
|
|
43
|
+
private rng;
|
|
44
|
+
private phase;
|
|
45
|
+
constructor(config?: PointLightConfig);
|
|
46
|
+
protected onProcess(dt: number): void;
|
|
47
|
+
protected serializeProps(): Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
export interface LightLayerConfig extends NodeConfig {
|
|
50
|
+
/** Ambient darkness the pools lift out of. `color` multiplies the world; `level` blends toward white (1 = no darkening). */
|
|
51
|
+
ambient?: {
|
|
52
|
+
color?: string;
|
|
53
|
+
level?: number;
|
|
54
|
+
};
|
|
55
|
+
/** Soft shadows: add a half-opacity penumbra quad per segment (pure geometry, no blur). */
|
|
56
|
+
softShadows?: boolean;
|
|
57
|
+
/** Ambient-rect viewport size (screen space). Default 1280×720. */
|
|
58
|
+
width?: number;
|
|
59
|
+
height?: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The lighting container. Emits the whole light run from its direct PointLight
|
|
63
|
+
* children: an ambient darkness base, then per light a screen-blended pool plus
|
|
64
|
+
* its multiply shadow quads. Cosmetic; place at the tree origin in world space.
|
|
65
|
+
*/
|
|
66
|
+
export declare class LightLayer extends Node {
|
|
67
|
+
readonly type: string;
|
|
68
|
+
ambient: {
|
|
69
|
+
color: string;
|
|
70
|
+
level: number;
|
|
71
|
+
};
|
|
72
|
+
softShadows: boolean;
|
|
73
|
+
width: number;
|
|
74
|
+
height: number;
|
|
75
|
+
private occluders;
|
|
76
|
+
constructor(config?: LightLayerConfig);
|
|
77
|
+
/** Set the occluder edges (design-space segments) this layer casts shadows from. */
|
|
78
|
+
setOccluders(segs: Occluder[]): void;
|
|
79
|
+
protected draw(out: DrawCommand[], world: Transform): void;
|
|
80
|
+
}
|
package/dist/scene/node.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Rng } from '../core/rng';
|
|
2
2
|
import type { Clock } from '../core/clock';
|
|
3
3
|
import type { InputState } from '../input/actions';
|
|
4
|
-
import { Signal } from '../core/events';
|
|
4
|
+
import { Signal, type EventBus } from '../core/events';
|
|
5
5
|
import { type Transform, type Vec2 } from '../core/math';
|
|
6
6
|
import type { DrawCommand } from '../render/commands';
|
|
7
7
|
/**
|
|
@@ -19,6 +19,33 @@ export interface WorldContext {
|
|
|
19
19
|
requestFree(node: Node): void;
|
|
20
20
|
/** Seconds elapsed in sim time. */
|
|
21
21
|
readonly time: number;
|
|
22
|
+
/**
|
|
23
|
+
* Canonical out-of-tree game state (hashed + snapshotted) — the same object
|
|
24
|
+
* as `world.state`, so behaviours read/write shared sim data without closing
|
|
25
|
+
* over the world or stashing it in module variables.
|
|
26
|
+
*/
|
|
27
|
+
readonly state: Record<string, unknown>;
|
|
28
|
+
/** The world event bus — emit/subscribe without a `world` closure. */
|
|
29
|
+
readonly events: EventBus<Record<string, unknown>>;
|
|
30
|
+
/** Design-space dimensions (what the camera letterboxes to). */
|
|
31
|
+
readonly width: number;
|
|
32
|
+
readonly height: number;
|
|
33
|
+
/** True while the world is paused (only `pauseMode: 'always'` subtrees update). */
|
|
34
|
+
readonly paused: boolean;
|
|
35
|
+
/** Sim-time multiplier (1 = realtime); `'always'` subtrees receive unscaled dt. */
|
|
36
|
+
readonly timeScale: number;
|
|
37
|
+
/**
|
|
38
|
+
* The active camera's world position + zoom, or null when none is current.
|
|
39
|
+
* Structural (no Camera2D import needed) — enough for parallax, culling, and
|
|
40
|
+
* screen-edge logic without a sibling search through the tree.
|
|
41
|
+
*/
|
|
42
|
+
camera(): {
|
|
43
|
+
pos: {
|
|
44
|
+
x: number;
|
|
45
|
+
y: number;
|
|
46
|
+
};
|
|
47
|
+
zoom: number;
|
|
48
|
+
} | null;
|
|
22
49
|
}
|
|
23
50
|
/** A composable update unit attached to a node (favour over deep inheritance). */
|
|
24
51
|
export interface Behavior {
|
|
@@ -29,6 +56,15 @@ export interface Behavior {
|
|
|
29
56
|
/** Optional tag for lookup/serialization. */
|
|
30
57
|
readonly kind?: string;
|
|
31
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* How a node behaves while the world is paused. `'inherit'` (default) takes the
|
|
61
|
+
* parent's effective mode (the root default is pausable), `'always'` keeps its
|
|
62
|
+
* subtree updating through a pause (pause menus, transitions), `'stopped'`
|
|
63
|
+
* halts its subtree even when the world is running.
|
|
64
|
+
*/
|
|
65
|
+
export type PauseMode = 'inherit' | 'always' | 'stopped';
|
|
66
|
+
/** A resolved pause mode — what 'inherit' collapses to as it flows down the tree. */
|
|
67
|
+
type EffectivePauseMode = 'pausable' | 'always' | 'stopped';
|
|
32
68
|
/** Deterministic id source — reset by the World on load so ids are reproducible. */
|
|
33
69
|
export declare function resetNodeIds(start?: number): void;
|
|
34
70
|
export interface NodeConfig {
|
|
@@ -56,6 +92,24 @@ export declare class Node {
|
|
|
56
92
|
* particles, tweened positions) never pollutes the canonical, verifiable state.
|
|
57
93
|
*/
|
|
58
94
|
cosmetic: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Pause behaviour for this subtree (see PauseMode). Serialized only when
|
|
97
|
+
* non-default, so existing trees keep their pinned hashes.
|
|
98
|
+
*/
|
|
99
|
+
pauseMode: PauseMode;
|
|
100
|
+
/**
|
|
101
|
+
* Screen-space overlay: this subtree ignores the camera — its transforms
|
|
102
|
+
* compose from IDENTITY (design-space coordinates) — and every command it
|
|
103
|
+
* emits is tagged `layer: 1` so HUD/overlay content always paints above the
|
|
104
|
+
* world, whatever the z values. Serialized only when true.
|
|
105
|
+
*/
|
|
106
|
+
screenSpace: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Optional local anchor: when set, rotation/scale pivot around this point in
|
|
109
|
+
* local space instead of the node origin (the local transform gains a
|
|
110
|
+
* trailing translation of −pivot). Serialized only when set.
|
|
111
|
+
*/
|
|
112
|
+
pivot?: Vec2;
|
|
59
113
|
parent: Node | null;
|
|
60
114
|
readonly children: Node[];
|
|
61
115
|
world: WorldContext | null;
|
|
@@ -70,19 +124,41 @@ export declare class Node {
|
|
|
70
124
|
private _ready;
|
|
71
125
|
private _freed;
|
|
72
126
|
constructor(config?: NodeConfig);
|
|
127
|
+
/** Shorthand for `pos.x` — reads and writes the same vector. */
|
|
128
|
+
get x(): number;
|
|
129
|
+
set x(v: number);
|
|
130
|
+
/** Shorthand for `pos.y` — reads and writes the same vector. */
|
|
131
|
+
get y(): number;
|
|
132
|
+
set y(v: number);
|
|
73
133
|
addChild<T extends Node>(child: T): T;
|
|
74
134
|
removeChild(child: Node): void;
|
|
75
135
|
/** Deferred free: runs exit hooks and detaches at the end of the step. */
|
|
76
136
|
free(): void;
|
|
137
|
+
/**
|
|
138
|
+
* Immediately exit + detach ALL children. Unlike `free()` this is NOT
|
|
139
|
+
* deferred — the children are gone when the call returns (safe here because
|
|
140
|
+
* it iterates a snapshot of the array). Use it to rebuild a container's
|
|
141
|
+
* contents wholesale; prefer `free()` for removals during an update.
|
|
142
|
+
*/
|
|
143
|
+
clearChildren(): void;
|
|
77
144
|
/** Depth-first find by name. */
|
|
78
145
|
find(name: string): Node | null;
|
|
146
|
+
/** Depth-first find of the first node (self included) that is an instance of `ctor` — typed. */
|
|
147
|
+
findOfType<T extends Node>(ctor: new (...args: never[]) => T): T | null;
|
|
79
148
|
/** All descendants (and self) of a given type tag. */
|
|
80
149
|
query(type: string, out?: Node[]): Node[];
|
|
81
150
|
addBehavior(b: Behavior): this;
|
|
82
151
|
signal<T = void>(name: string): Signal<T>;
|
|
83
152
|
emit<T>(name: string, payload: T): void;
|
|
84
153
|
enterTree(world: WorldContext): void;
|
|
85
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Advance this subtree. `dt` is the (time-scaled) step delta; `unscaledDt`
|
|
156
|
+
* and `paused` come from the world, and `parentMode` is the effective pause
|
|
157
|
+
* mode flowing down the tree. The walk always descends — a paused node just
|
|
158
|
+
* skips its callbacks — so an `'always'` descendant (pause menu, transition)
|
|
159
|
+
* keeps running inside a paused parent.
|
|
160
|
+
*/
|
|
161
|
+
updateTree(dt: number, unscaledDt?: number, parentMode?: EffectivePauseMode, paused?: boolean): void;
|
|
86
162
|
exitTree(): void;
|
|
87
163
|
get isFreed(): boolean;
|
|
88
164
|
localTransform(): Transform;
|
|
@@ -108,6 +184,9 @@ export interface SerializedNode {
|
|
|
108
184
|
scale: Vec2;
|
|
109
185
|
z: number;
|
|
110
186
|
visible: boolean;
|
|
187
|
+
/** Local rotation/scale anchor (absent when unset). */
|
|
188
|
+
pivot?: Vec2;
|
|
111
189
|
props: Record<string, unknown>;
|
|
112
190
|
children: SerializedNode[];
|
|
113
191
|
}
|
|
192
|
+
export {};
|
package/dist/scene/nodes.d.ts
CHANGED
|
@@ -8,11 +8,22 @@ import { Node, type NodeConfig } from './node';
|
|
|
8
8
|
* middle. Pass `anchor: 'topLeft'` to place `pos` at the top-left
|
|
9
9
|
* corner instead (the canvas mental model) — no half-size offset.
|
|
10
10
|
* - `circle` → CENTER-anchored: `pos` is the center.
|
|
11
|
+
* - `ellipse`→ CENTER-anchored: `pos` is the center; `rx`/`ry` are the semi-axes.
|
|
12
|
+
* - `arc` → CENTER-anchored: `pos` is the arc's center. Radians, clockwise
|
|
13
|
+
* from +x (y-down screen convention), running `start` → `end`.
|
|
14
|
+
* `sector: true` closes through the center (a pie slice).
|
|
11
15
|
* - `glyph` → CENTER-anchored (align 'center').
|
|
12
16
|
* - `poly` → LOCAL coordinates: `points` are relative to `pos` as-is (put your
|
|
13
17
|
* own 0,0 wherever you like).
|
|
14
18
|
* - `path` → LOCAL coordinates: `d` is drawn relative to `pos`.
|
|
19
|
+
* - `diamond`→ CENTER-anchored: a `w×h` rhombus (iso tile). `pos` is its middle.
|
|
20
|
+
* - `regularPoly` → CENTER-anchored: `sides`-gon of circumradius `r`. `rotation`
|
|
21
|
+
* (radians) turns it; 0 puts the first vertex straight up.
|
|
15
22
|
* (`Text` is anchored by its `align`, not centered — see the Text node.)
|
|
23
|
+
*
|
|
24
|
+
* `diamond` and `regularPoly` are pure sugar over `poly` (resolved at draw time,
|
|
25
|
+
* no renderer change) — they exist so an iso tile or a hex reads as intent, not
|
|
26
|
+
* as four hand-typed corner numbers.
|
|
16
27
|
*/
|
|
17
28
|
export type Shape = {
|
|
18
29
|
kind: 'rect';
|
|
@@ -23,6 +34,16 @@ export type Shape = {
|
|
|
23
34
|
} | {
|
|
24
35
|
kind: 'circle';
|
|
25
36
|
radius: number;
|
|
37
|
+
} | {
|
|
38
|
+
kind: 'ellipse';
|
|
39
|
+
rx: number;
|
|
40
|
+
ry: number;
|
|
41
|
+
} | {
|
|
42
|
+
kind: 'arc';
|
|
43
|
+
radius: number;
|
|
44
|
+
start: number;
|
|
45
|
+
end: number;
|
|
46
|
+
sector?: boolean;
|
|
26
47
|
} | {
|
|
27
48
|
kind: 'poly';
|
|
28
49
|
points: number[];
|
|
@@ -34,7 +55,20 @@ export type Shape = {
|
|
|
34
55
|
kind: 'glyph';
|
|
35
56
|
char: string;
|
|
36
57
|
size: number;
|
|
58
|
+
} | {
|
|
59
|
+
kind: 'diamond';
|
|
60
|
+
w: number;
|
|
61
|
+
h: number;
|
|
62
|
+
} | {
|
|
63
|
+
kind: 'regularPoly';
|
|
64
|
+
sides: number;
|
|
65
|
+
r: number;
|
|
66
|
+
rotation?: number;
|
|
37
67
|
};
|
|
68
|
+
/** Local poly points for a center-anchored `w×h` diamond (top, right, bottom, left). */
|
|
69
|
+
export declare function diamondPoints(w: number, h: number): number[];
|
|
70
|
+
/** Local poly points for a center-anchored regular `sides`-gon of circumradius `r`. */
|
|
71
|
+
export declare function regularPolyPoints(sides: number, r: number, rotation?: number): number[];
|
|
38
72
|
export interface SpriteConfig extends NodeConfig, Paint {
|
|
39
73
|
shape: Shape;
|
|
40
74
|
}
|
|
@@ -42,6 +42,25 @@ export declare class Particles extends Node {
|
|
|
42
42
|
private gravity;
|
|
43
43
|
private drag;
|
|
44
44
|
private shrink;
|
|
45
|
+
private emitRate;
|
|
46
|
+
private emitAcc;
|
|
47
|
+
private emitStyle;
|
|
48
|
+
private emitOrigin;
|
|
49
|
+
/**
|
|
50
|
+
* Start (or retune) continuous emission at `ratePerSec` particles per second.
|
|
51
|
+
* `style.at` sets the node-local origin (default 0,0); the rest styles
|
|
52
|
+
* particles exactly like `burst()`. Omitting `style` keeps the previous
|
|
53
|
+
* emitter style (falling back to the `burst` preset if none was ever set).
|
|
54
|
+
*
|
|
55
|
+
* The signal form `emit(name, payload)` inherited from Node still works —
|
|
56
|
+
* the overload dispatches on the first argument's type.
|
|
57
|
+
*/
|
|
58
|
+
emit(ratePerSec: number, style?: ParticleStyle & {
|
|
59
|
+
at?: Vec2;
|
|
60
|
+
}): void;
|
|
61
|
+
emit<T>(name: string, payload: T): void;
|
|
62
|
+
/** Stop continuous emission. Live particles finish their lives naturally. */
|
|
63
|
+
stopEmit(): void;
|
|
45
64
|
protected onProcess(dt: number): void;
|
|
46
65
|
protected draw(out: DrawCommand[], world: Transform): void;
|
|
47
66
|
get liveCount(): number;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Vec2 } from '../core/math';
|
|
2
|
+
import { type ContourSegment } from '../art/autotile';
|
|
3
|
+
import { type TilemapData } from '../physics/tilemap';
|
|
4
|
+
/** An occluder edge in world (design-space) coordinates. */
|
|
5
|
+
export type Occluder = ContourSegment;
|
|
6
|
+
/**
|
|
7
|
+
* Occluder edges from a tilemap: the marching-squares contour of the SOLID
|
|
8
|
+
* cells, in design-space px. Wraps `marchingSquaresContours` over a boolean
|
|
9
|
+
* grid of `tileAt === TILE.SOLID`, scaled by the map's tileSize.
|
|
10
|
+
*/
|
|
11
|
+
export declare function occludersFromTilemap(map: TilemapData): Occluder[];
|
|
12
|
+
/**
|
|
13
|
+
* Cull occluders to those that can matter for a light: an endpoint within, or
|
|
14
|
+
* the segment straddling, the light's reach (radius). A cheap circle/AABB-ish
|
|
15
|
+
* test — keeps the O(lights × segments) shadow pass tight without changing the
|
|
16
|
+
* result (a segment fully outside the disc casts no shadow inside it).
|
|
17
|
+
*/
|
|
18
|
+
export declare function cullSegments(light: Vec2, radius: number, segs: readonly Occluder[]): Occluder[];
|
|
19
|
+
/**
|
|
20
|
+
* Shadow quads cast by `segs` from `light`, one flat `[ax,ay, bx,by, bx',by',
|
|
21
|
+
* ax',ay']` quad per segment (closed poly winding). Extrusion length is 2×radius
|
|
22
|
+
* so the far edge always clears the lit disc. A segment passing through the light
|
|
23
|
+
* (degenerate direction) is skipped. Culling is the caller's job (`cullSegments`).
|
|
24
|
+
*/
|
|
25
|
+
export declare function shadowQuads(light: Vec2, radius: number, segs: readonly Occluder[]): number[][];
|