incanto 0.59.0 → 0.61.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/bin/incanto-check.mjs +58 -4
- package/dist/2d.d.ts +22 -3
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +5 -5
- package/dist/3d.js +5 -4
- package/dist/{pathfinding-BqWBb0kh.d.ts → audio-player-D5GJgb_x.d.ts} +103 -38
- package/dist/{behavior-DWKTUzKI.d.ts → behavior-DsgayMsH.d.ts} +79 -1
- package/dist/{create-game-CHDLDQsQ.js → create-game-DH7JI5xx.js} +76 -11
- package/dist/{create-game-BiW8Men_.js → create-game-IX5lEH0P.js} +21 -294
- package/dist/debug.d.ts +1 -1
- package/dist/debug.js +1 -1
- package/dist/{duplicate-DJQd44CD.js → duplicate-CGqAmK2h.js} +1 -1
- package/dist/editor.js +2 -1
- package/dist/{environment-presets-DRAz5EV9.js → environment-presets-CNxCuhZF.js} +48 -13
- package/dist/{gameplay-BBEjPFsR.js → gameplay-Dtzd2itW.js} +53 -11
- package/dist/gameplay.d.ts +1 -1
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +23 -6
- package/dist/index.js +7 -7
- package/dist/{loader-D8n7TU8W.js → loader-D7jTvDQv.js} +797 -2
- package/dist/{loader-TvkRFbyL.d.ts → loader-DolLJWJn.d.ts} +13 -1
- package/dist/net.d.ts +2 -2
- package/dist/net.js +1 -1
- package/dist/pathfinding-_fGrCFmH.d.ts +28 -0
- package/dist/{physics-2d-BaRSRrrZ.js → physics-2d-CBnor8Zf.js} +2 -2
- package/dist/{physics-3d-CYxjh-HW.js → physics-3d-BTUfUSWO.js} +3 -3
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-BpFcgdcL.js → register-DL3izw8j.js} +46 -9
- package/dist/{register-CDrAQqPp.js → register-xuSRyD6b.js} +119 -21
- package/dist/{registry-WWcQcfMr.js → registry-CF70EArN.js} +55 -3
- package/dist/{replay-O-yAGM76.d.ts → replay-C5x2vPF5.d.ts} +16 -3
- package/dist/{replay-CEPyQtF_.js → replay-D7-yle3s.js} +56 -205
- package/dist/{split-screen-DDMZutQ6.js → split-screen-5Ban4q4n.js} +3 -3
- package/dist/{split-screen-BQ3tAsf-.d.ts → split-screen-D7OopelJ.d.ts} +2 -2
- package/dist/{src-CY21B462.js → src-DozXvyZS.js} +1 -1
- package/dist/{teardown-RApWnM1G.js → teardown-B6rwJOyS.js} +1 -1
- package/dist/{test-DHYuFyAu.js → test-Dch_7VQD.js} +19 -14
- package/dist/test.d.ts +24 -4
- package/dist/test.js +2 -2
- package/dist/vite.js +2 -2
- package/editor/assets/{agent8-BoRGtVxK.js → agent8-CF1JL2tR.js} +1 -1
- package/editor/assets/{debug-CzdyCg75.js → debug-3QzYhOPA.js} +1 -1
- package/editor/assets/{index-VesuVEhe.js → index-CAD2c5ug.js} +92 -92
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +4 -0
- package/skills/incanto-3d-models.md +1 -1
- package/skills/incanto-assets.md +10 -1
- package/skills/incanto-audio.md +21 -13
- package/skills/incanto-behaviors-and-scripts.md +7 -3
- package/skills/incanto-building-2d-games.md +12 -3
- package/skills/incanto-editor.md +1 -1
- package/skills/incanto-gameplay-behaviors.md +24 -1
- package/skills/incanto-hud.md +5 -3
- package/skills/incanto-localization.md +31 -0
- package/skills/incanto-node-reference.md +1 -0
- package/skills/incanto-scene-json-authoring.md +6 -1
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/platformer-2d/package.json +1 -1
- package/templates-app/star-survivor/package.json +1 -1
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/village-quest-3d/package.json +1 -1
- package/dist/particle-sim-C5OfBbmU.d.ts +0 -77
package/bin/incanto-check.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* npx incanto-check src/game.scene.json → specific files (or directories)
|
|
9
9
|
* npx incanto-check --json → machine-readable report
|
|
10
10
|
* npx incanto-check --strict-behaviors → unregistered script names fail too
|
|
11
|
+
* npx incanto-check --strict-warnings → a warning is a failure (CI, agents)
|
|
11
12
|
*
|
|
12
13
|
* Unregistered behaviors are STUBBED by default: structure-only validation
|
|
13
14
|
* that needs no TypeScript. Sub-scene `instance` paths resolve relative to
|
|
@@ -25,6 +26,7 @@ function parseArgs(argv) {
|
|
|
25
26
|
for (const a of argv) {
|
|
26
27
|
if (a === '--json') args.json = true;
|
|
27
28
|
else if (a === '--strict-behaviors') args.strict = true;
|
|
29
|
+
else if (a === '--strict-warnings') args.strictWarnings = true;
|
|
28
30
|
else if (a === '--help' || a === '-h') args.help = true;
|
|
29
31
|
else if (a.startsWith('--')) {
|
|
30
32
|
console.error(`unknown flag: ${a}`);
|
|
@@ -36,7 +38,7 @@ function parseArgs(argv) {
|
|
|
36
38
|
|
|
37
39
|
const args = parseArgs(process.argv.slice(2));
|
|
38
40
|
if (args.help || args.invalid) {
|
|
39
|
-
console.log(`Usage: npx incanto
|
|
41
|
+
console.log(`Usage: npx incanto check [files|dirs...] [--json] [--strict-behaviors] [--strict-warnings]
|
|
40
42
|
|
|
41
43
|
Validates *.scene.json files headlessly with the installed engine.
|
|
42
44
|
With no arguments, scans the current directory recursively
|
|
@@ -155,6 +157,40 @@ if (files.length === 0) {
|
|
|
155
157
|
|
|
156
158
|
const { auditScene, validateScene } = await import(pathToFileURL(join(DIST, 'test.js')).href);
|
|
157
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Every `strings` table in the corpus, unioned.
|
|
162
|
+
*
|
|
163
|
+
* Localization tables MERGE across scenes at runtime — a game's shared UI
|
|
164
|
+
* strings live in the scene it boots from — so auditing one file in isolation
|
|
165
|
+
* reported a key declared in the boot scene as "nothing declares" it, and told
|
|
166
|
+
* its author to add a string they already have. Checking a project means
|
|
167
|
+
* knowing what the project declares.
|
|
168
|
+
*/
|
|
169
|
+
const declaredElsewhere = {};
|
|
170
|
+
/**
|
|
171
|
+
* Every asset the project declares.
|
|
172
|
+
*
|
|
173
|
+
* A scene a spawner instantiates resolves its `$ref`s against the HOST's
|
|
174
|
+
* `assets{}` — `remote-player.scene.json` names `$characters/base` and declares
|
|
175
|
+
* nothing itself, which is correct. Checked alone it cannot resolve; checked as
|
|
176
|
+
* part of its project it can.
|
|
177
|
+
*/
|
|
178
|
+
const declaredAssets = {};
|
|
179
|
+
for (const file of files) {
|
|
180
|
+
try {
|
|
181
|
+
const doc = JSON.parse(readFileSync(file, 'utf-8'));
|
|
182
|
+
for (const [locale, entries] of Object.entries(doc?.strings ?? {})) {
|
|
183
|
+
if (typeof entries !== 'object' || entries === null) continue;
|
|
184
|
+
declaredElsewhere[locale] = { ...(declaredElsewhere[locale] ?? {}), ...entries };
|
|
185
|
+
}
|
|
186
|
+
for (const [key, entry] of Object.entries(doc?.assets ?? {})) {
|
|
187
|
+
if (typeof entry === 'object' && entry !== null) declaredAssets[key] ??= entry;
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
/* a file that will not parse is reported below, on its own line */
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
158
194
|
const results = files.map((file) => {
|
|
159
195
|
let json;
|
|
160
196
|
try {
|
|
@@ -164,9 +200,16 @@ const results = files.map((file) => {
|
|
|
164
200
|
}
|
|
165
201
|
const res = validateScene(json, {
|
|
166
202
|
strictBehaviors: args.strict,
|
|
203
|
+
declaredAssets,
|
|
167
204
|
resolveScene: (p) => JSON.parse(readFileSync(resolve(dirname(file), p), 'utf-8')),
|
|
168
205
|
});
|
|
169
|
-
if (res.ok)
|
|
206
|
+
if (res.ok) {
|
|
207
|
+
return {
|
|
208
|
+
file,
|
|
209
|
+
ok: true,
|
|
210
|
+
warnings: [...auditScene(json, { declaredElsewhere }), ...missingArt(file, json)],
|
|
211
|
+
};
|
|
212
|
+
}
|
|
170
213
|
return {
|
|
171
214
|
file,
|
|
172
215
|
ok: false,
|
|
@@ -177,6 +220,7 @@ const results = files.map((file) => {
|
|
|
177
220
|
});
|
|
178
221
|
|
|
179
222
|
const failed = results.filter((r) => !r.ok);
|
|
223
|
+
const warned = results.reduce((n, r) => n + (r.warnings?.length ?? 0), 0);
|
|
180
224
|
if (args.json) {
|
|
181
225
|
console.log(JSON.stringify({ ok: failed.length === 0, results }, null, 2));
|
|
182
226
|
} else {
|
|
@@ -186,10 +230,20 @@ if (args.json) {
|
|
|
186
230
|
for (const w of r.warnings ?? []) console.log(` warn: ${w}`);
|
|
187
231
|
} else console.log(`FAIL ${r.file}\n [${r.code}] ${r.message}`);
|
|
188
232
|
}
|
|
189
|
-
|
|
233
|
+
// Say the warning count on the summary line. A warning printed under an `ok`
|
|
234
|
+
// and a summary that never mentions it is a finding an agent scrolls past.
|
|
235
|
+
console.log(
|
|
236
|
+
`\n${results.length - failed.length}/${results.length} scene(s) valid` +
|
|
237
|
+
(warned > 0
|
|
238
|
+
? `, ${warned} warning(s)${args.strictWarnings ? '' : ' — `--strict-warnings` to fail on them'}`
|
|
239
|
+
: ''),
|
|
240
|
+
);
|
|
190
241
|
}
|
|
191
242
|
// `exitCode`, never `process.exit()`: stdout to a PIPE is written
|
|
192
243
|
// asynchronously, and exiting discards whatever has not flushed. A --json
|
|
193
244
|
// report read by another program came back truncated — silently, and only
|
|
194
245
|
// when piped, which is the only way a program reads it.
|
|
195
|
-
|
|
246
|
+
// A warning is a finding, and a checker read by a CI step or an agent is read
|
|
247
|
+
// through its EXIT CODE. Opt-in, because turning it on by default would fail
|
|
248
|
+
// every project that has been living with one.
|
|
249
|
+
process.exitCode = failed.length === 0 && !(args.strictWarnings && warned > 0) ? 0 : 1;
|
package/dist/2d.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats } from "./behavior-DsgayMsH.js";
|
|
2
2
|
import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-DAvWQeld.js";
|
|
3
3
|
import { i as SceneJson, s as JsonObject } from "./schema-CFeioQRE.js";
|
|
4
4
|
import { r as FrameStats } from "./frame-report-DCnHFmto.js";
|
|
5
|
-
import { t as LoadSceneOptions } from "./loader-
|
|
5
|
+
import { t as LoadSceneOptions } from "./loader-DolLJWJn.js";
|
|
6
6
|
import { n as AnimationEntry } from "./sprite-animation-CMr6f1K2.js";
|
|
7
|
-
import {
|
|
7
|
+
import { i as ParticleSim, o as ParticleView, r as SpatialPose } from "./audio-player-D5GJgb_x.js";
|
|
8
8
|
import { Group, Mesh, Object3D, Scene, Texture } from "three";
|
|
9
9
|
import * as RapierNs from "@dimforge/rapier2d-compat";
|
|
10
10
|
|
|
@@ -701,6 +701,16 @@ declare class Label extends Node2D {
|
|
|
701
701
|
font: string;
|
|
702
702
|
/** 'left' | 'center' | 'right' — anchor of the text block on the node origin. */
|
|
703
703
|
align: string;
|
|
704
|
+
/**
|
|
705
|
+
* 0..1, like `Sprite2D` and `ColorRect2D`.
|
|
706
|
+
*
|
|
707
|
+
* Label was the one 2D drawable without it, and the gap showed up through
|
|
708
|
+
* `FloatAway` — whose own doc names "a 2D `Label`" as a node it works on.
|
|
709
|
+
* `FloatAway` writes `opacity` when the node has one, so on a Label the
|
|
710
|
+
* number rose to full height at FULL opacity and then vanished in a single
|
|
711
|
+
* frame: the pop that behavior exists to avoid.
|
|
712
|
+
*/
|
|
713
|
+
opacity: number;
|
|
704
714
|
private quadMesh;
|
|
705
715
|
private canvas;
|
|
706
716
|
private lastKey;
|
|
@@ -1081,6 +1091,15 @@ interface UiSize {
|
|
|
1081
1091
|
interface Sync2DScratch {
|
|
1082
1092
|
visited: Set<Object3D>;
|
|
1083
1093
|
cameras: Camera2D[];
|
|
1094
|
+
emitters: Array<{
|
|
1095
|
+
node: Node & SpatialConsumer;
|
|
1096
|
+
parent: Object3D;
|
|
1097
|
+
}>;
|
|
1098
|
+
}
|
|
1099
|
+
/** A node that accepts a per-frame spatial pose (an AudioPlayer with spatial on). */
|
|
1100
|
+
interface SpatialConsumer {
|
|
1101
|
+
spatial: boolean;
|
|
1102
|
+
_setSpatialPose(pose: SpatialPose): void;
|
|
1084
1103
|
}
|
|
1085
1104
|
declare function syncTree2D(root: Node, world: Scene, ui: Scene, assets: AssetStore2D | null, uiSize?: UiSize, scratch?: Sync2DScratch, opts?: {
|
|
1086
1105
|
ignoreStatic?: boolean;
|
package/dist/2d.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
2
|
-
import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-
|
|
3
|
-
import { _ as RigidBody2D, a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, g as PhysicsBody2D, h as CharacterBody2D, i as mergeSolidRects, l as CharacterController2D, m as Area2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D, v as StaticBody2D, y as Node2D } from "./register-
|
|
4
|
-
import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-
|
|
2
|
+
import { a as AssetStore2D, i as syncTree2D, r as Renderer2D, t as createGame2D } from "./create-game-DH7JI5xx.js";
|
|
3
|
+
import { _ as RigidBody2D, a as parseCells, c as ColorRect2D, d as AnimatedSprite2D, f as Sprite2D, g as PhysicsBody2D, h as CharacterBody2D, i as mergeSolidRects, l as CharacterController2D, m as Area2D, n as UILayer, o as Particles2D, p as Joint2D, r as TileMap2D, s as Label, t as registerNodes2D, u as Camera2D, v as StaticBody2D, y as Node2D } from "./register-DL3izw8j.js";
|
|
4
|
+
import { n as enablePhysics2D, t as Physics2D } from "./physics-2d-CBnor8Zf.js";
|
|
5
5
|
//#region src/2d/library-sprite.ts
|
|
6
6
|
/**
|
|
7
7
|
* What a `CharacterController2D`/`3D` will ask a skin to play, and the clip in
|
package/dist/3d.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as QualityCaps, P as Scene$1, S as Scheduler, T as RendererStats, b as Engine, d as PropSchema, jt as Node, n as BehaviorCtor, w as GameStats } from "./behavior-DsgayMsH.js";
|
|
2
2
|
import { n as DiagnosticSink, t as EditorSwitchOptions } from "./editor-switch-DAvWQeld.js";
|
|
3
3
|
import { i as SceneJson$1, s as JsonObject } from "./schema-CFeioQRE.js";
|
|
4
4
|
import { a as GridCell, c as diffText, d as frameText, i as FrameStatsOptions, l as frameSignature, n as FrameSignature, o as SIGNATURE_GRID, r as FrameStats, s as diffSignatures, t as FrameDiff, u as frameStats } from "./frame-report-DCnHFmto.js";
|
|
5
|
-
import { t as LoadSceneOptions } from "./loader-
|
|
6
|
-
import {
|
|
7
|
-
import { n as PathGrid
|
|
5
|
+
import { t as LoadSceneOptions } from "./loader-DolLJWJn.js";
|
|
6
|
+
import { i as ParticleSim, o as ParticleView, r as SpatialPose } from "./audio-player-D5GJgb_x.js";
|
|
7
|
+
import { n as PathGrid } from "./pathfinding-_fGrCFmH.js";
|
|
8
8
|
import { AnimationClip, AnimationMixer, BufferGeometry, Color, DirectionalLight, Group, InstancedMesh, Mesh, MeshPhysicalMaterial, Object3D, PerspectiveCamera, Scene, ShaderMaterial, Texture, Vector3, WebGLRenderer } from "three";
|
|
9
9
|
import { VRM } from "@pixiv/three-vrm";
|
|
10
10
|
import { Sky } from "three/examples/jsm/objects/Sky.js";
|
|
@@ -730,7 +730,7 @@ interface Game3D {
|
|
|
730
730
|
*/
|
|
731
731
|
declare function createGame3D(opts: CreateGame3DOptions): Promise<Game3D>;
|
|
732
732
|
//#endregion
|
|
733
|
-
//#region src/
|
|
733
|
+
//#region src/core/scene/environment.d.ts
|
|
734
734
|
/**
|
|
735
735
|
* The 3D `environment` header, parsed and validated — the renderer's
|
|
736
736
|
* "rendering stage". Pure data in, plain data out (NO three imports), so the
|
package/dist/3d.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { c as parseEnvironment3D, l as sunDirectionFromElevationAzimuth, s as horizonColorFromSky, u as sunDirectionFromSky } from "./loader-D7jTvDQv.js";
|
|
1
2
|
import { a as frameSignature, n as diffSignatures, o as frameStats, r as diffText, s as frameText, t as SIGNATURE_GRID } from "./frame-report-BSMny7oe.js";
|
|
2
|
-
import { B as StaticBody3D, F as WaterCutout3D, I as Area3D, L as CharacterBody3D, N as Water3D, P as WATER_CUTOUT_MAX, R as PhysicsBody3D, V as Node3D, W as WATER_MAX_RIPPLES, z as RigidBody3D } from "./gameplay-
|
|
3
|
-
import { A as Terrain3D, B as keyboardIntensity, C as resolveFlowerDensity, D as BoneLookAt3D, E as Camera3D, F as InstancedMesh3D, G as acquireTexture, H as rigPose, I as MeshInstance3D, M as TERRAIN_THEMES, N as terrainThemeLayers, O as BoneAttachment3D, P as Joint3D, R as QUARTER_PITCH, S as Flowers3D, T as CharacterController3D, U as TextureCache3D, V as movementState, W as acquireOwnTexture, _ as LoftMesh3D, a as Tree3D, b as Foliage3D, c as buildRiverRings, d as riverCarveChannels, f as riverStepFor, g as ModelInstance3D, h as Particles3D, i as VoxelGrid3D, j as DEFAULT_TERRAIN_TEXTURE_BASE, k as Billboard3D, l as findRiverCoverageGaps, m as traceDownhillPath, n as registerNodes3D, o as Trail3D, p as smoothCourse, r as VOXEL_PALETTE, s as River3D, u as projectToRiver, v as DirectionalLight3D, w as FLOWER_VARIETIES, x as DENSITY_PRESETS, y as OmniLight3D, z as cameraRelative } from "./environment-presets-
|
|
4
|
-
import { a as Environment3D,
|
|
3
|
+
import { B as StaticBody3D, F as WaterCutout3D, I as Area3D, L as CharacterBody3D, N as Water3D, P as WATER_CUTOUT_MAX, R as PhysicsBody3D, V as Node3D, W as WATER_MAX_RIPPLES, z as RigidBody3D } from "./gameplay-Dtzd2itW.js";
|
|
4
|
+
import { A as Terrain3D, B as keyboardIntensity, C as resolveFlowerDensity, D as BoneLookAt3D, E as Camera3D, F as InstancedMesh3D, G as acquireTexture, H as rigPose, I as MeshInstance3D, M as TERRAIN_THEMES, N as terrainThemeLayers, O as BoneAttachment3D, P as Joint3D, R as QUARTER_PITCH, S as Flowers3D, T as CharacterController3D, U as TextureCache3D, V as movementState, W as acquireOwnTexture, _ as LoftMesh3D, a as Tree3D, b as Foliage3D, c as buildRiverRings, d as riverCarveChannels, f as riverStepFor, g as ModelInstance3D, h as Particles3D, i as VoxelGrid3D, j as DEFAULT_TERRAIN_TEXTURE_BASE, k as Billboard3D, l as findRiverCoverageGaps, m as traceDownhillPath, n as registerNodes3D, o as Trail3D, p as smoothCourse, r as VOXEL_PALETTE, s as River3D, u as projectToRiver, v as DirectionalLight3D, w as FLOWER_VARIETIES, x as DENSITY_PRESETS, y as OmniLight3D, z as cameraRelative } from "./environment-presets-CNxCuhZF.js";
|
|
5
|
+
import { a as Environment3D, i as syncTree, o as setEnvironment3D, r as Renderer3D, s as AssetStore3D, t as createGame3D } from "./create-game-IX5lEH0P.js";
|
|
5
6
|
import { n as splatWeights, t as buildHeightmap } from "./heightmap-CRK0M4jT.js";
|
|
6
|
-
import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-
|
|
7
|
+
import { n as enablePhysics3D, t as Physics3D } from "./physics-3d-BTUfUSWO.js";
|
|
7
8
|
//#region src/3d/model-verdict.ts
|
|
8
9
|
/** Mixamo exports every bone as `mixamorigX`; the retargeter binds by that name. */
|
|
9
10
|
const MIXAMO = /^mixamorig[:_]?/i;
|
|
@@ -1,5 +1,80 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as PropSchema, dt as Listener, jt as Node } from "./behavior-DsgayMsH.js";
|
|
2
|
+
import { a as Rng } from "./schema-CFeioQRE.js";
|
|
2
3
|
|
|
4
|
+
//#region src/core/particle-sim.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Deterministic particle pool — pure math, renderer-agnostic, headless-
|
|
7
|
+
* testable. 2D uses the xy plane (y-down, directionDeg 0 = +x, -90 = up);
|
|
8
|
+
* 3D feeds the same sim with a z spread.
|
|
9
|
+
*/
|
|
10
|
+
interface ParticleSimConfig {
|
|
11
|
+
/** Particles per second (0 = burst-only). */
|
|
12
|
+
rate: number;
|
|
13
|
+
/** Seconds, [min, max]. */
|
|
14
|
+
lifetime: [number, number];
|
|
15
|
+
/** Initial speed, [min, max] (units/sec — px in 2D, meters in 3D). */
|
|
16
|
+
speed: [number, number];
|
|
17
|
+
/** Emission direction center, degrees (0 = +x, -90 = up in y-down 2D). */
|
|
18
|
+
directionDeg: number;
|
|
19
|
+
/** Cone width, degrees (360 = all directions). */
|
|
20
|
+
spreadDeg: number;
|
|
21
|
+
/** Constant acceleration (y-down in 2D). */
|
|
22
|
+
gravity: [number, number, number];
|
|
23
|
+
/** Exponential velocity damping per second (0 = none). */
|
|
24
|
+
drag: number;
|
|
25
|
+
maxParticles: number;
|
|
26
|
+
/** Spread emission into the z axis too (3D). */
|
|
27
|
+
spreadZ?: boolean;
|
|
28
|
+
}
|
|
29
|
+
interface ParticleView {
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
z: number;
|
|
33
|
+
vx: number;
|
|
34
|
+
vy: number;
|
|
35
|
+
vz: number;
|
|
36
|
+
/** Seconds alive. */
|
|
37
|
+
age: number;
|
|
38
|
+
/** Total lifetime in seconds. */
|
|
39
|
+
life: number;
|
|
40
|
+
/** age/life in [0, 1] — drives size/color/alpha ramps. */
|
|
41
|
+
t: number;
|
|
42
|
+
/** Per-particle random in [0, 1) — stable for the particle's lifetime. */
|
|
43
|
+
seed: number;
|
|
44
|
+
}
|
|
45
|
+
declare class ParticleSim {
|
|
46
|
+
private readonly config;
|
|
47
|
+
private readonly rng;
|
|
48
|
+
private readonly data;
|
|
49
|
+
private alive;
|
|
50
|
+
private spawnAccumulator;
|
|
51
|
+
private everSpawned;
|
|
52
|
+
constructor(config: ParticleSimConfig, rng: Rng);
|
|
53
|
+
/**
|
|
54
|
+
* Shift every live particle — how world-space emission is done.
|
|
55
|
+
*
|
|
56
|
+
* The pool is in the emitter's LOCAL space, so a moving emitter drags its
|
|
57
|
+
* whole plume with it: dust glued to a running player instead of left behind,
|
|
58
|
+
* and the documented "move the emitter and replay" recipe for a one-shot
|
|
59
|
+
* teleporting the previous explosion across the level. Counter-translating by
|
|
60
|
+
* the emitter's own motion each frame leaves the particles where they were
|
|
61
|
+
* born, exactly, and costs one pass over the live ones.
|
|
62
|
+
*/
|
|
63
|
+
translateAll(dx: number, dy: number, dz: number): void;
|
|
64
|
+
/** Change the emission rate LIVE (particles/sec; 0 pauses emission) — lets an
|
|
65
|
+
* emitter toggle on/off at runtime (drift smoke, throttle flames). */
|
|
66
|
+
setRate(rate: number): void;
|
|
67
|
+
get count(): number;
|
|
68
|
+
/** True when nothing is alive and at least one particle has ever spawned. */
|
|
69
|
+
get done(): boolean;
|
|
70
|
+
/** Spawn n particles immediately (fireworks, explosions, flashes). */
|
|
71
|
+
burst(n: number): void;
|
|
72
|
+
update(dt: number): void;
|
|
73
|
+
forEach(fn: (p: ParticleView) => void): void;
|
|
74
|
+
private spawn;
|
|
75
|
+
private kill;
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
3
78
|
//#region src/core/nodes/audio-player.d.ts
|
|
4
79
|
/**
|
|
5
80
|
* The per-frame spatial feed the 3D adapter pushes onto a spatial AudioPlayer:
|
|
@@ -23,7 +98,7 @@ interface AudioElementLike {
|
|
|
23
98
|
}
|
|
24
99
|
/**
|
|
25
100
|
* One sound. `play()` (or `autoplay: true`) starts it; `finished` fires when
|
|
26
|
-
* it ends. Browsers block audio before the first user gesture — a blocked
|
|
101
|
+
* it ends — for a procedural preset as well as a `src` clip. Browsers block audio before the first user gesture — a blocked
|
|
27
102
|
* play marks `pendingGesture`; `createGame` retries pending players on the
|
|
28
103
|
* first pointer/key gesture automatically.
|
|
29
104
|
*
|
|
@@ -58,10 +133,12 @@ declare class AudioPlayer extends Node {
|
|
|
58
133
|
/** Start on the first frame in the tree (subject to the gesture policy). */
|
|
59
134
|
autoplay: boolean;
|
|
60
135
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
136
|
+
* Positional audio: the sound pans + attenuates by the emitter's world
|
|
137
|
+
* position relative to the active camera (the listener). Default false →
|
|
138
|
+
* identical non-spatial behavior.
|
|
139
|
+
*
|
|
140
|
+
* Both dimensions. A 2D scene measures in PIXELS, so the distances below —
|
|
141
|
+
* metre-shaped defaults — need setting there; `auditScene` says so.
|
|
65
142
|
*/
|
|
66
143
|
spatial: boolean;
|
|
67
144
|
/** Distance at which spatial gain is full; closer never gets louder. */
|
|
@@ -81,6 +158,21 @@ declare class AudioPlayer extends Node {
|
|
|
81
158
|
loadError: string | null;
|
|
82
159
|
private element;
|
|
83
160
|
private _playing;
|
|
161
|
+
/**
|
|
162
|
+
* `engine.unscaledTime` at which the preset currently sounding ends.
|
|
163
|
+
*
|
|
164
|
+
* A preset used to be fire-and-forget: `playing` stayed false through a sound
|
|
165
|
+
* that was audibly playing, and `finished` — a signal this node DECLARES, so
|
|
166
|
+
* a connection to it loads clean — never fired at all. Wiring "when the coin
|
|
167
|
+
* chime ends, free the pickup" to a preset player produced a wire that is
|
|
168
|
+
* dead for the life of the game and says nothing.
|
|
169
|
+
*
|
|
170
|
+
* The end is known exactly: `attack + sustain + decay` is the length
|
|
171
|
+
* `synthSfx` sizes its buffer to. Measured on the UNSCALED clock because a
|
|
172
|
+
* sound does not slow down when the game does, and a paused game still hears
|
|
173
|
+
* the tail of the hit that paused it.
|
|
174
|
+
*/
|
|
175
|
+
private presetEndsAt;
|
|
84
176
|
private autoplayed;
|
|
85
177
|
/** Last spatial pose pushed by the 3D adapter (null until/unless spatial). */
|
|
86
178
|
private _spatialPose;
|
|
@@ -108,10 +200,10 @@ declare class AudioPlayer extends Node {
|
|
|
108
200
|
play(): void;
|
|
109
201
|
/**
|
|
110
202
|
* Procedural-SFX path: synthesize the preset and fire it through WebAudio
|
|
111
|
-
* (low-latency, overlap-friendly).
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
203
|
+
* (low-latency, overlap-friendly). Headless (no AudioContext) plays nothing —
|
|
204
|
+
* but still reports `playing` and still emits `finished` on schedule, because
|
|
205
|
+
* the length is arithmetic, not a device. WebAudio needs a gesture; a
|
|
206
|
+
* suspended context is resumed by the same unlock listener as `src` players.
|
|
115
207
|
*/
|
|
116
208
|
private playPreset;
|
|
117
209
|
/**
|
|
@@ -130,31 +222,4 @@ declare class AudioPlayer extends Node {
|
|
|
130
222
|
override update(_dt: number): void;
|
|
131
223
|
}
|
|
132
224
|
//#endregion
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Grid A* — the pathfinding every top-down/RPG/tower-defense asks for and
|
|
136
|
-
* agents keep re-inventing badly. Pure and dimension-free: cells in, cells
|
|
137
|
-
* out; the caller maps cells to world units (px, meters, tiles).
|
|
138
|
-
*/
|
|
139
|
-
interface PathGrid {
|
|
140
|
-
width: number;
|
|
141
|
-
height: number;
|
|
142
|
-
/** True = impassable. Out-of-bounds is always solid. */
|
|
143
|
-
solid(x: number, y: number): boolean;
|
|
144
|
-
}
|
|
145
|
-
interface FindPathOptions {
|
|
146
|
-
/** Allow diagonal steps (blocked from cutting corners). Default true. */
|
|
147
|
-
diagonal?: boolean;
|
|
148
|
-
/** Abort guard for huge/impossible searches. Default 20000 nodes. */
|
|
149
|
-
maxExpansions?: number;
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* A* from `from` to `to` (inclusive cell coords). Returns the cell path
|
|
153
|
-
* INCLUDING both endpoints, or null when unreachable. Straight steps cost
|
|
154
|
-
* 1, diagonals √2; the heuristic is octile (admissible for both modes).
|
|
155
|
-
*/
|
|
156
|
-
declare function findPath(grid: PathGrid, from: [number, number], to: [number, number], opts?: FindPathOptions): [number, number][] | null;
|
|
157
|
-
/** Build a PathGrid from row-strings ('#' = solid) — tests and tile games. */
|
|
158
|
-
declare function gridFromRows(rows: readonly string[]): PathGrid;
|
|
159
|
-
//#endregion
|
|
160
|
-
export { AudioElementLike as a, gridFromRows as i, PathGrid as n, AudioPlayer as o, findPath as r, SpatialPose as s, FindPathOptions as t };
|
|
225
|
+
export { ParticleSimConfig as a, ParticleSim as i, AudioPlayer as n, ParticleView as o, SpatialPose as r, AudioElementLike as t };
|
|
@@ -551,6 +551,14 @@ interface SynthOptions {
|
|
|
551
551
|
seed?: number;
|
|
552
552
|
}
|
|
553
553
|
/**
|
|
554
|
+
* How long a preset lasts, in seconds — the same `attack + sustain + decay`
|
|
555
|
+
* `synthSfx` uses to size its buffer.
|
|
556
|
+
*
|
|
557
|
+
* `pitch` deliberately does not enter into it: it is a frequency multiplier,
|
|
558
|
+
* not a playback rate, so a preset played an octave up is the same length.
|
|
559
|
+
*/
|
|
560
|
+
declare function sfxDuration(params: SfxParams): number;
|
|
561
|
+
/**
|
|
554
562
|
* Render a preset to mono PCM in [-1, 1]. PURE: identical inputs → identical
|
|
555
563
|
* output (the only randomness is the seeded noise generator), so determinism is
|
|
556
564
|
* unit-testable headlessly. Length = ceil((attack+sustain+decay) * sampleRate).
|
|
@@ -1780,6 +1788,44 @@ declare class Engine {
|
|
|
1780
1788
|
tick(nowMs: number): void;
|
|
1781
1789
|
}
|
|
1782
1790
|
//#endregion
|
|
1791
|
+
//#region src/core/prop-range.d.ts
|
|
1792
|
+
/**
|
|
1793
|
+
* The numeric range a prop documents, checked at LOAD.
|
|
1794
|
+
*
|
|
1795
|
+
* The docs give ranges constantly — `metalness`/`roughness` "0..1 … `-1` = keep
|
|
1796
|
+
* authored", `targetHeight` "> 0 uniformly scales the model", `opacity` 0..1 —
|
|
1797
|
+
* and the schema had no way to say so, so every one was a type check and
|
|
1798
|
+
* nothing more. Measured on `ModelInstance3D`:
|
|
1799
|
+
*
|
|
1800
|
+
* ```
|
|
1801
|
+
* targetHeight: -1.8 NO ERROR (kept -1.8)
|
|
1802
|
+
* metalness: 5 NO ERROR (kept 5)
|
|
1803
|
+
* roughness: -3 NO ERROR (kept -3)
|
|
1804
|
+
* ```
|
|
1805
|
+
*
|
|
1806
|
+
* The two failure modes differ, which is why neither is noticed. A negative
|
|
1807
|
+
* `targetHeight` folds into the same branch as "not set", so the model keeps
|
|
1808
|
+
* its authored size and looks like the prop did nothing; `metalness: 5` reaches
|
|
1809
|
+
* three and the surface goes wrong. An author who mistypes a decimal point gets
|
|
1810
|
+
* a different wrong answer depending on which prop they mistyped it in, and no
|
|
1811
|
+
* message in either case.
|
|
1812
|
+
*/
|
|
1813
|
+
interface PropRange {
|
|
1814
|
+
/** Lowest accepted value. */
|
|
1815
|
+
min?: number;
|
|
1816
|
+
/** Highest accepted value. */
|
|
1817
|
+
max?: number;
|
|
1818
|
+
/** `min` is a bound the value must EXCEED, not reach (`targetHeight > 0`). */
|
|
1819
|
+
exclusiveMin?: boolean;
|
|
1820
|
+
/**
|
|
1821
|
+
* Values outside the range that mean something anyway.
|
|
1822
|
+
*
|
|
1823
|
+
* `metalness: -1` is "keep whatever the GLB authored" — a real value with a
|
|
1824
|
+
* real meaning, and the reason a range cannot just be a clamp.
|
|
1825
|
+
*/
|
|
1826
|
+
allow?: readonly number[];
|
|
1827
|
+
}
|
|
1828
|
+
//#endregion
|
|
1783
1829
|
//#region src/core/registry.d.ts
|
|
1784
1830
|
/** A node prop definition. The default both documents and types the prop. */
|
|
1785
1831
|
interface PropDef {
|
|
@@ -1808,6 +1854,38 @@ interface PropDef {
|
|
|
1808
1854
|
*/
|
|
1809
1855
|
required?: boolean;
|
|
1810
1856
|
/**
|
|
1857
|
+
* This prop names an entry in the scene's `assets{}` block.
|
|
1858
|
+
*
|
|
1859
|
+
* Declaring it is what makes a typo a LOAD error. The check already existed —
|
|
1860
|
+
* `AssetStore2D.resolve` throws `UNKNOWN_ASSET` — and it runs inside the
|
|
1861
|
+
* renderer, in a browser, when the texture is first needed. Headless the
|
|
1862
|
+
* scene loaded clean, so `incanto-check` certified a sprite that will draw
|
|
1863
|
+
* nothing, and the docs' promise that "the engine hard-fails on a raw URL"
|
|
1864
|
+
* was not true anywhere an author could see it.
|
|
1865
|
+
*
|
|
1866
|
+
* The same story as `nodePath` and `required`: the rule existed, nothing
|
|
1867
|
+
* routed to it at author time.
|
|
1868
|
+
*/
|
|
1869
|
+
/**
|
|
1870
|
+
* The numeric range this prop documents.
|
|
1871
|
+
*
|
|
1872
|
+
* Without it a documented range is prose only: `metalness: 5` and
|
|
1873
|
+
* `targetHeight: -1.8` both passed the kind check and then failed in two
|
|
1874
|
+
* different silent ways. See prop-range.
|
|
1875
|
+
*/
|
|
1876
|
+
range?: PropRange;
|
|
1877
|
+
asset?: {
|
|
1878
|
+
/** The `assets{}` entry `type` a `$ref` here must point at. */kind: "texture" | "spritesheet" | "model" | "animation";
|
|
1879
|
+
/**
|
|
1880
|
+
* A value that is not a `$ref` is an error.
|
|
1881
|
+
*
|
|
1882
|
+
* False for props that legitimately take something else — a raw URL
|
|
1883
|
+
* (`UiImage.src`, `Sprite3D.texture`) or the name of a clip baked into a
|
|
1884
|
+
* GLB (`ModelInstance3D.animation`).
|
|
1885
|
+
*/
|
|
1886
|
+
refOnly?: boolean;
|
|
1887
|
+
};
|
|
1888
|
+
/**
|
|
1811
1889
|
* For an OBJECT-valued prop shaped as a tagged union — `collider`, whose
|
|
1812
1890
|
* `shape` decides which other keys are required. One declaration gives an
|
|
1813
1891
|
* inspector both the dropdown AND something valid to write when the tag
|
|
@@ -1943,4 +2021,4 @@ declare function registeredBehaviors(): string[];
|
|
|
1943
2021
|
/** Test isolation helper. */
|
|
1944
2022
|
declare function clearBehaviors(): void;
|
|
1945
2023
|
//#endregion
|
|
1946
|
-
export { T_PREFIX as $, SettingsValues as A,
|
|
2024
|
+
export { T_PREFIX as $, SettingsValues as A, BusName as At, captureBehaviors as B, EngineStats as C, sfxDuration as Ct, QualityCaps as D, MusicTrack as Dt, DeviceHints as E, MusicManager as Et, BehaviorState as F, LogLevel as Ft, ORDER_GROUP_BASE as G, savesWithoutUid as H, RestoreReport as I, LogManager as It, effectiveOrder as J, OrderGroup as K, SaveSlot as L, Signal as Lt, readDeviceHints as M, NodeLifecycle as Mt, suggestQuality as N, SceneTree as Nt, QualityTier as O, PlayMusicOptions as Ot, Scene as P, LogEntry as Pt, Localization as Q, SaveSlots as R, SignalListener as Rt, Scheduler as S, SynthOptions as St, RendererStats as T, MusicBackend as Tt, SaveStore as U, restoreBehaviors as V, createSaveStore as W, BASE_LOCALE as X, resolveOrderGroups as Y, LocaleTables as Z, mergeStaticSignals as _, spatialPan as _t, clearBehaviors as a, EffectLog as at, Engine as b, SfxParams as bt, registeredBehaviors as c, isAudioContextAvailable as ct, PropSchema as d, Listener as dt, suggestLocale as et, clearRegistry as f, ROLLOFF_MODELS as ft, getNodeType as g, spatialGain as gt, getNodeSignals as h, Vec3 as ht, behaviorSignals as i, EffectKind as it, qualityEnvironment as j, Node as jt, Settings as k, AudioBuses as kt, NodeCtor as l, Voice as lt, getNodeSchema as m, SpatialParams as mt, BehaviorCtor as n, InputMap as nt, getBehavior as o, SfxEngine as ot, createNode as p, RolloffModel as pt, OrderGroupTable as q, behaviorSchema as r, EffectEvent as rt, registerBehavior as s, SfxPlayOptions as st, Behavior as t, translationKey as tt, PropDef as u, VoicePreset as ut, registerNode as v, SFX_PRESETS as vt, GameStats as w, synthSfx as wt, EngineOptions as x, SfxWave as xt, registeredTypes as y, SFX_PRESET_NAMES as yt, behaviorsWithoutSave as z };
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import {
|
|
3
|
-
import { _ as Engine, g as AudioPlayer } from "./register-
|
|
2
|
+
import { d as computeViewport, f as resolveViewport, k as diagnose, n as loadScene, z as registerBehavior } from "./loader-D7jTvDQv.js";
|
|
3
|
+
import { _ as Engine, g as AudioPlayer } from "./register-xuSRyD6b.js";
|
|
4
4
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
5
5
|
import { i as resolveRendering, n as attachTouchControls } from "./touch-DESwnpOc.js";
|
|
6
|
-
import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-
|
|
6
|
+
import { a as openBundledEditor, c as claimCanvasGestures, i as devServerLibrary, l as audioErrors, n as pauseWhenHidden, r as crossFade, s as wireDevChannel, t as teardown } from "./teardown-B6rwJOyS.js";
|
|
7
7
|
import { o as frameStats } from "./frame-report-BSMny7oe.js";
|
|
8
|
-
import { n as registerGameplayBehaviors } from "./gameplay-
|
|
9
|
-
import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-
|
|
8
|
+
import { n as registerGameplayBehaviors } from "./gameplay-Dtzd2itW.js";
|
|
9
|
+
import { g as PhysicsBody2D, n as UILayer, t as registerNodes2D, u as Camera2D, y as Node2D } from "./register-DL3izw8j.js";
|
|
10
10
|
import { t as debugSources } from "./debug-draw-BM3DsvtT.js";
|
|
11
|
-
import { n as enablePhysics2D } from "./physics-2d-
|
|
11
|
+
import { n as enablePhysics2D } from "./physics-2d-CBnor8Zf.js";
|
|
12
12
|
import { Box3, BufferAttribute, BufferGeometry, Color, LineBasicMaterial, LineSegments, LinearFilter, NearestFilter, OrthographicCamera, Raycaster, SRGBColorSpace, Scene, TextureLoader, Vector2, Vector3, WebGLRenderer } from "three";
|
|
13
13
|
//#region src/2d/assets.ts
|
|
14
14
|
/**
|
|
@@ -199,14 +199,19 @@ function selectionOutline2D(node) {
|
|
|
199
199
|
function createSync2DScratch() {
|
|
200
200
|
return {
|
|
201
201
|
visited: /* @__PURE__ */ new Set(),
|
|
202
|
-
cameras: []
|
|
202
|
+
cameras: [],
|
|
203
|
+
emitters: []
|
|
203
204
|
};
|
|
204
205
|
}
|
|
206
|
+
function isSpatialConsumer(node) {
|
|
207
|
+
return node.spatial === true && typeof node._setSpatialPose === "function";
|
|
208
|
+
}
|
|
205
209
|
function syncTree2D(root, world, ui, assets, uiSize, scratch, opts) {
|
|
206
210
|
const s = scratch ?? createSync2DScratch();
|
|
207
211
|
s.visited.clear();
|
|
208
212
|
s.cameras.length = 0;
|
|
209
|
-
|
|
213
|
+
s.emitters.length = 0;
|
|
214
|
+
walk(root, world, ui, false, s.visited, s.cameras, s.emitters, assets, uiSize, opts?.ignoreStatic === true);
|
|
210
215
|
prune(world, s.visited);
|
|
211
216
|
prune(ui, s.visited);
|
|
212
217
|
let current = null;
|
|
@@ -218,9 +223,66 @@ function syncTree2D(root, world, ui, assets, uiSize, scratch, opts) {
|
|
|
218
223
|
}
|
|
219
224
|
}
|
|
220
225
|
if (!current) current = s.cameras[0] ?? null;
|
|
226
|
+
feedSpatialEmitters2D(s.emitters, current);
|
|
221
227
|
return { activeCamera: current };
|
|
222
228
|
}
|
|
223
|
-
|
|
229
|
+
const emitterScratch = new Vector3();
|
|
230
|
+
const listenerScratch = new Vector3();
|
|
231
|
+
const upScratch = new Vector3();
|
|
232
|
+
/**
|
|
233
|
+
* Give a 2D scene's spatial AudioPlayers the same per-frame pose the 3D sync
|
|
234
|
+
* gives its own.
|
|
235
|
+
*
|
|
236
|
+
* `spatial`, `refDistance`, `maxDistance` and `rolloff` have been props of
|
|
237
|
+
* `AudioPlayer` since positional audio landed, and in a 2D scene all four did
|
|
238
|
+
* NOTHING: the node only attenuates when an adapter pushes it a pose, and only
|
|
239
|
+
* the 3D one ever did. The scene loaded, `incanto check` said valid, and the
|
|
240
|
+
* sound played at full volume from the far side of the level.
|
|
241
|
+
*
|
|
242
|
+
* The listener is the active `Camera2D` — its world position is the view
|
|
243
|
+
* centre, its own rotation turns the stereo image with it, and the view looks
|
|
244
|
+
* down −Z like every other camera. Distances are therefore in the same units
|
|
245
|
+
* the rest of a 2D scene is in: PIXELS. `refDistance: 1` / `maxDistance: 50`
|
|
246
|
+
* are metre-shaped defaults and read as "silent one sprite away" here, so a 2D
|
|
247
|
+
* scene has to set them; `auditScene` says so when it sees the defaults.
|
|
248
|
+
*/
|
|
249
|
+
function feedSpatialEmitters2D(emitters, camera) {
|
|
250
|
+
if (emitters.length === 0 || !camera) return;
|
|
251
|
+
const view = camera._ensureObject2D();
|
|
252
|
+
view.updateWorldMatrix(true, false);
|
|
253
|
+
view.getWorldPosition(listenerScratch);
|
|
254
|
+
upScratch.set(0, 1, 0).applyQuaternion(view.quaternion);
|
|
255
|
+
const listener = {
|
|
256
|
+
position: [
|
|
257
|
+
listenerScratch.x,
|
|
258
|
+
listenerScratch.y,
|
|
259
|
+
0
|
|
260
|
+
],
|
|
261
|
+
forward: [
|
|
262
|
+
0,
|
|
263
|
+
0,
|
|
264
|
+
-1
|
|
265
|
+
],
|
|
266
|
+
up: [
|
|
267
|
+
upScratch.x,
|
|
268
|
+
upScratch.y,
|
|
269
|
+
upScratch.z
|
|
270
|
+
]
|
|
271
|
+
};
|
|
272
|
+
for (const { node, parent } of emitters) {
|
|
273
|
+
parent.updateWorldMatrix(true, false);
|
|
274
|
+
parent.getWorldPosition(emitterScratch);
|
|
275
|
+
node._setSpatialPose({
|
|
276
|
+
position: [
|
|
277
|
+
emitterScratch.x,
|
|
278
|
+
emitterScratch.y,
|
|
279
|
+
0
|
|
280
|
+
],
|
|
281
|
+
listener
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function walk(node, parentObj, ui, inUi, visited, cameras, emitters, assets, uiSize, ignoreStatic) {
|
|
224
286
|
let nextParent = parentObj;
|
|
225
287
|
let nextInUi = inUi;
|
|
226
288
|
if (node instanceof Node2D && !(node instanceof UILayer)) {
|
|
@@ -249,8 +311,11 @@ function walk(node, parentObj, ui, inUi, visited, cameras, assets, uiSize, ignor
|
|
|
249
311
|
visited.add(obj);
|
|
250
312
|
if (node instanceof Camera2D && !inUi) cameras.push(node);
|
|
251
313
|
nextParent = obj;
|
|
252
|
-
}
|
|
253
|
-
|
|
314
|
+
} else if (isSpatialConsumer(node)) emitters.push({
|
|
315
|
+
node,
|
|
316
|
+
parent: nextParent
|
|
317
|
+
});
|
|
318
|
+
for (const child of node.children) walk(child, nextParent, ui, nextInUi, visited, cameras, emitters, assets, uiSize, ignoreStatic);
|
|
254
319
|
if (node instanceof Node2D && !(node instanceof UILayer)) {
|
|
255
320
|
const obj = node._ensureObject2D();
|
|
256
321
|
if (node.static && !ignoreStatic) {
|