lecodes-sdk 0.20.0 → 1.0.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/dist/global.d.ts +48 -5
- package/dist/inject.js +361 -260
- package/dist/types/audio/Bus.d.ts +45 -0
- package/dist/types/audio/Sound.d.ts +28 -0
- package/dist/types/audio/Voice.d.ts +27 -0
- package/dist/types/audio/audio.d.ts +83 -0
- package/dist/types/audio/support.d.ts +1 -0
- package/dist/types/canvas/Canvas.d.ts +2 -0
- package/dist/types/gl/DecalSet.d.ts +148 -0
- package/dist/types/gl/Geometry.d.ts +17 -0
- package/dist/types/gl/Light.d.ts +7 -0
- package/dist/types/gl/Lightmap.d.ts +9 -0
- package/dist/types/gl/Material.d.ts +90 -2
- package/dist/types/gl/Mesh.d.ts +18 -1
- package/dist/types/gl/Model.d.ts +34 -0
- package/dist/types/gl/Particles.d.ts +13 -0
- package/dist/types/gl/Scene.d.ts +23 -0
- package/dist/types/gl/Texture.d.ts +29 -1
- package/dist/types/gl/animation/AnimationClip.d.ts +25 -12
- package/dist/types/gl/animation/DynamicBone.d.ts +173 -0
- package/dist/types/gl/{IK.d.ts → animation/IK.d.ts} +4 -4
- package/dist/types/gl/{Locomotion.d.ts → animation/Locomotion.d.ts} +6 -6
- package/dist/types/gl/animation/core.d.ts +15 -10
- package/dist/types/gl/audio/AudioSource.d.ts +60 -0
- package/dist/types/gl/audio/AudioZone.d.ts +32 -0
- package/dist/types/gl/audio/SceneAudio.d.ts +11 -0
- package/dist/types/gl/{NavAgent.d.ts → nav/NavAgent.d.ts} +4 -4
- package/dist/types/gl/{NavMesh.d.ts → nav/NavMesh.d.ts} +4 -4
- package/dist/types/gl/{CharacterController.d.ts → physics/CharacterController.d.ts} +5 -5
- package/dist/types/gl/{Physics.d.ts → physics/Physics.d.ts} +6 -5
- package/dist/types/gl/{Ragdoll.d.ts → physics/Ragdoll.d.ts} +4 -4
- package/dist/types/gl/{Shape.d.ts → physics/Shape.d.ts} +3 -3
- package/dist/types/gl/{Trigger.d.ts → physics/Trigger.d.ts} +2 -2
- package/dist/types/gl/state.d.ts +0 -1
- package/dist/types/gl/{Terrain.d.ts → terrain/Terrain.d.ts} +6 -6
- package/dist/types/gl/{terrainMesh.d.ts → terrain/terrainMesh.d.ts} +1 -1
- package/dist/types/gl/vehicle/Vehicle.d.ts +300 -0
- package/dist/types/gl/vehicle/Wheel.d.ts +147 -0
- package/dist/types/inject.d.ts +33 -21
- package/dist/types/runtime/files.d.ts +24 -1
- package/dist/types/runtime/input.d.ts +11 -0
- package/dist/types/scene/defineScene.d.ts +10 -3
- package/dist/types/ui/UIImage.d.ts +15 -5
- package/dist/types.json +1 -1
- package/package.json +1 -1
- package/prompts/README.md +142 -142
- package/prompts/dist/2d-game.md +408 -197
- package/prompts/dist/3d-app.md +491 -166
- package/prompts/dist/ar-app.md +373 -163
- package/prompts/dist/design.md +83 -87
- package/prompts/dist/ui-app.md +325 -136
- package/src/audio/Bus.ts +102 -0
- package/src/audio/Sound.ts +96 -0
- package/src/audio/Voice.ts +102 -0
- package/src/audio/audio.ts +161 -0
- package/src/audio/support.ts +6 -0
- package/src/bridges.d.ts +279 -32
- package/src/canvas/Canvas.ts +21 -0
- package/src/compile/__tests__/compile.test.ts +11 -0
- package/src/compile/compileProject.ts +30 -15
- package/src/compile/header.ts +6 -3
- package/src/compile/index.ts +4 -0
- package/src/compile/sceneEditor.ts +42 -1
- package/src/core/Aspect.ts +33 -8
- package/src/g2/Scene2D.ts +7 -0
- package/src/gl/CameraPlace.ts +52 -52
- package/src/gl/DecalSet.ts +360 -0
- package/src/gl/Geometry.ts +348 -279
- package/src/gl/Light.ts +16 -0
- package/src/gl/Lightmap.ts +35 -7
- package/src/gl/Material.ts +173 -4
- package/src/gl/Mesh.ts +120 -83
- package/src/gl/Model.ts +33 -1
- package/src/gl/Node.ts +1 -1
- package/src/gl/Particles.ts +21 -3
- package/src/gl/Scene.ts +41 -7
- package/src/gl/Texture.ts +43 -3
- package/src/gl/animation/AnimationClip.ts +43 -20
- package/src/gl/animation/Animator.ts +4 -3
- package/src/gl/animation/DynamicBone.ts +459 -0
- package/src/gl/{IK.ts → animation/IK.ts} +4 -4
- package/src/gl/{Locomotion.ts → animation/Locomotion.ts} +7 -7
- package/src/gl/animation/core.ts +20 -15
- package/src/gl/audio/AudioSource.ts +113 -0
- package/src/gl/audio/AudioZone.ts +75 -0
- package/src/gl/audio/SceneAudio.ts +26 -0
- package/src/gl/{NavAgent.ts → nav/NavAgent.ts} +5 -5
- package/src/gl/{NavMesh.ts → nav/NavMesh.ts} +8 -8
- package/src/gl/{CharacterController.ts → physics/CharacterController.ts} +5 -5
- package/src/gl/{Physics.ts → physics/Physics.ts} +12 -5
- package/src/gl/{Ragdoll.ts → physics/Ragdoll.ts} +272 -270
- package/src/gl/{Shape.ts → physics/Shape.ts} +3 -3
- package/src/gl/{Trigger.ts → physics/Trigger.ts} +2 -2
- package/src/gl/{physicsEvents.ts → physics/physicsEvents.ts} +1 -1
- package/src/gl/scenarios.ts +291 -291
- package/src/gl/state.ts +1 -1
- package/src/gl/{Terrain.ts → terrain/Terrain.ts} +10 -10
- package/src/gl/{terrainMesh.ts → terrain/terrainMesh.ts} +1 -1
- package/src/gl/vehicle/Vehicle.ts +666 -0
- package/src/gl/vehicle/Wheel.ts +290 -0
- package/src/inject.ts +226 -212
- package/src/runtime/files.ts +32 -2
- package/src/runtime/input.ts +6 -1
- package/src/scene/defineScene.ts +26 -10
- package/src/scene/gizmos.ts +148 -148
- package/src/scene/level.ts +2 -2
- package/src/ui/UIImage.ts +21 -7
- package/dist/types/gl/Gearbox.d.ts +0 -86
- package/dist/types/gl/Vehicle.d.ts +0 -191
- package/dist/types/gl/Wheel.d.ts +0 -95
- package/src/gl/Gearbox.ts +0 -212
- package/src/gl/Vehicle.ts +0 -473
- package/src/gl/Wheel.ts +0 -240
- /package/dist/types/gl/{physicsEvents.d.ts → physics/physicsEvents.d.ts} +0 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// A 3D sound emitter, as an aspect on a Node (docs/audio-plan.md §2). The engine follows the node's
|
|
2
|
+
// world pose every frame (position, facing for the cone, velocity for doppler); voices played
|
|
3
|
+
// through it are spatialized against the scene's listener (scene.audio.listener, the camera by
|
|
4
|
+
// default). Parented, animated and physics-driven nodes just work.
|
|
5
|
+
//
|
|
6
|
+
// const gun = new Node().aspect(AudioSource, { minDistance: 1, maxDistance: 40 })
|
|
7
|
+
// gun.audio.play(shot, { pitch: 0.95 + Math.random() * 0.1 })
|
|
8
|
+
// const hum = car.audio.play(engineLoop, { loop: true }); hum.pitch = 0.8 + rpm * 0.6
|
|
9
|
+
// speaker.aspect(AudioSource, { cone: { inner: 60, outer: 120, outerGain: 0.3 } }) // facing −Z
|
|
10
|
+
// enemy.aspect(AudioSource, { occlusion: true }) // a wall between it and the listener muffles it
|
|
11
|
+
//
|
|
12
|
+
// Without host audio the aspect is inert: `play` returns an inert Voice, nothing throws.
|
|
13
|
+
|
|
14
|
+
import { Aspect } from "../../core/Aspect"
|
|
15
|
+
import type { FieldMeta } from "../../core/fields"
|
|
16
|
+
import { audio, sourceParams, type PlaySoundOptions, type Rolloff } from "../../audio/audio"
|
|
17
|
+
import type { Sound } from "../../audio/Sound"
|
|
18
|
+
import { audioSupported } from "../../audio/support"
|
|
19
|
+
import type { Voice } from "../../audio/Voice"
|
|
20
|
+
import type { Node } from "../Node"
|
|
21
|
+
|
|
22
|
+
export type AudioCone = {
|
|
23
|
+
/** Degrees of full volume around the node's −Z. */
|
|
24
|
+
inner: number
|
|
25
|
+
/** Degrees where the volume has fallen to `outerGain`. */
|
|
26
|
+
outer: number
|
|
27
|
+
/** Volume behind the source, 0 … 1. */
|
|
28
|
+
outerGain?: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class AudioSource extends Aspect<"audio", Node> {
|
|
32
|
+
static readonly aspect = "audio"
|
|
33
|
+
|
|
34
|
+
static fields: FieldMeta<AudioSource> = {
|
|
35
|
+
minDistance: { min: 0.05, max: 100, step: 0.05 },
|
|
36
|
+
maxDistance: { min: 0.5, max: 1000, step: 0.5 },
|
|
37
|
+
doppler: { min: 0, max: 1, step: 0.05 },
|
|
38
|
+
spread: { min: 0, max: 1, step: 0.05 },
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private _minDistance = 1
|
|
42
|
+
private _maxDistance = 50
|
|
43
|
+
private _rolloff: Rolloff = "inverse"
|
|
44
|
+
private _cone: AudioCone | null = null
|
|
45
|
+
private _doppler = 0
|
|
46
|
+
private _spread = 0
|
|
47
|
+
private _occlusion = false
|
|
48
|
+
private _bus = "sfx"
|
|
49
|
+
/** @internal engine source id (0 = not attached / no audio). */
|
|
50
|
+
_id = 0
|
|
51
|
+
|
|
52
|
+
/** Metres of full volume around the node. Default 1. */
|
|
53
|
+
get minDistance(): number { return this._minDistance }
|
|
54
|
+
set minDistance(v: number) { this._minDistance = v; this._push() }
|
|
55
|
+
/** Metres beyond which the source no longer gets quieter. Default 50. */
|
|
56
|
+
get maxDistance(): number { return this._maxDistance }
|
|
57
|
+
set maxDistance(v: number) { this._maxDistance = v; this._push() }
|
|
58
|
+
/** `inverse` (default), `linear`, `exponential`, `none`. */
|
|
59
|
+
get rolloff(): Rolloff { return this._rolloff }
|
|
60
|
+
set rolloff(v: Rolloff) { this._rolloff = v; this._push() }
|
|
61
|
+
/** Directional source along the node's −Z; null = omnidirectional (default). */
|
|
62
|
+
get cone(): AudioCone | null { return this._cone }
|
|
63
|
+
set cone(v: AudioCone | null) { this._cone = v; this._push() }
|
|
64
|
+
/** Doppler amount 0 … 1 (0 = off, the default) — needs the node to actually move. */
|
|
65
|
+
get doppler(): number { return this._doppler }
|
|
66
|
+
set doppler(v: number) { this._doppler = v; this._push() }
|
|
67
|
+
/** 0 = pin-point panning (default) … 1 = the same on every speaker (a big, close source). */
|
|
68
|
+
get spread(): number { return this._spread }
|
|
69
|
+
set spread(v: number) { this._spread = v; this._push() }
|
|
70
|
+
/** The engine raycasts listener → source (against solid bodies) and muffles the voices when
|
|
71
|
+
* something is in the way. Default false. */
|
|
72
|
+
get occlusion(): boolean { return this._occlusion }
|
|
73
|
+
set occlusion(v: boolean) { this._occlusion = v; this._push() }
|
|
74
|
+
/** Default bus for voices on this source. Default `sfx`. */
|
|
75
|
+
get bus(): string { return this._bus }
|
|
76
|
+
set bus(v: string) { this._bus = v; this._push() }
|
|
77
|
+
|
|
78
|
+
/** Live voices on this source. */
|
|
79
|
+
get voices(): number { return this._id && audioSupported ? _creatorAudio.sourceVoices(this._id) : 0 }
|
|
80
|
+
|
|
81
|
+
onAttach(): void {
|
|
82
|
+
if (!audioSupported) return
|
|
83
|
+
this._id = _creatorAudio.createSource()
|
|
84
|
+
if (!this._id) return
|
|
85
|
+
_creatorAudio.attachSource(this._id, this.node.id)
|
|
86
|
+
this._push()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
onDetach(): void {
|
|
90
|
+
if (this._id && audioSupported) _creatorAudio.destroySource(this._id)
|
|
91
|
+
this._id = 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Play a clip from this node. */
|
|
95
|
+
play(sound: Sound, options: PlaySoundOptions = {}): Voice {
|
|
96
|
+
return audio._play(sound, this._id, options)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Stop every voice on this source (fade in seconds). */
|
|
100
|
+
stopAll(fade = 0): void {
|
|
101
|
+
if (this._id && audioSupported) _creatorAudio.stopSource(this._id, fade)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private _push(): void {
|
|
105
|
+
if (!this._id || !audioSupported) return
|
|
106
|
+
const cone = this._cone
|
|
107
|
+
_creatorAudio.setSourceParams(this._id, sourceParams({
|
|
108
|
+
minDistance: this._minDistance, maxDistance: this._maxDistance, rolloff: this._rolloff,
|
|
109
|
+
coneInner: cone ? cone.inner : 360, coneOuter: cone ? cone.outer : 360, coneOuterGain: cone ? cone.outerGain ?? 0.25 : 1,
|
|
110
|
+
doppler: this._doppler, spread: this._spread, occlusion: this._occlusion, bus: audio._busId(this._bus),
|
|
111
|
+
}))
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// A reverb zone, as an aspect on a Node (docs/audio-plan.md §2): a box or sphere volume that
|
|
2
|
+
// follows the node; while the listener is inside, the zone's reverb blends onto a bus (the bus's
|
|
3
|
+
// own reverb outside it). Zones nest: the smallest one containing the listener wins. `blend` is
|
|
4
|
+
// how many metres inside the border the crossfade takes — walking through a doorway is smooth.
|
|
5
|
+
//
|
|
6
|
+
// new Node().aspect(AudioZone, { box: [6, 2.5, 6], reverb: 'hall', blend: 2 })
|
|
7
|
+
// corridor.aspect(Shape, { box: [1, 1.5, 8] }).aspect(AudioZone, { reverb: 'bathroom' }) // the Shape's box
|
|
8
|
+
//
|
|
9
|
+
// Sizes follow Shape's convention: `box` = HALF-extents, `sphere` = radius, in the node's local
|
|
10
|
+
// units (the node's scale applies). Without host audio the aspect is inert.
|
|
11
|
+
|
|
12
|
+
import { Aspect } from "../../core/Aspect"
|
|
13
|
+
import { reverbBlock, type ReverbParams, type ReverbPreset } from "../../audio/Bus"
|
|
14
|
+
import { audio } from "../../audio/audio"
|
|
15
|
+
import { audioSupported } from "../../audio/support"
|
|
16
|
+
import { cx, cy, cz, type Vec3Like } from "../../math/vec"
|
|
17
|
+
import type { Node } from "../Node"
|
|
18
|
+
import { Shape } from "../physics/Shape"
|
|
19
|
+
|
|
20
|
+
export class AudioZone extends Aspect<"audioZone", Node> {
|
|
21
|
+
static readonly aspect = "audioZone"
|
|
22
|
+
|
|
23
|
+
private _box: Vec3Like | null = null
|
|
24
|
+
private _sphere: number | null = null
|
|
25
|
+
private _reverb: ReverbPreset | ReverbParams = "room"
|
|
26
|
+
private _blend = 1
|
|
27
|
+
private _bus = "sfx"
|
|
28
|
+
/** @internal engine zone id. */
|
|
29
|
+
_id = 0
|
|
30
|
+
/** The engine's zone id — what `audio.stats.listenerZone` reports while the listener is inside. */
|
|
31
|
+
get id(): number { return this._id }
|
|
32
|
+
|
|
33
|
+
/** Half-extents [hx, hy, hz]; falls back to the node's Shape box, then a 1 m cube. */
|
|
34
|
+
get box(): Vec3Like | null { return this._box }
|
|
35
|
+
set box(v: Vec3Like | null) { this._box = v; if (v) this._sphere = null; this._push() }
|
|
36
|
+
/** Radius; falls back to the node's Shape sphere. */
|
|
37
|
+
get sphere(): number | null { return this._sphere }
|
|
38
|
+
set sphere(v: number | null) { this._sphere = v; if (v !== null) this._box = null; this._push() }
|
|
39
|
+
/** A preset name or explicit params (see Bus.reverb). */
|
|
40
|
+
get reverb(): ReverbPreset | ReverbParams { return this._reverb }
|
|
41
|
+
set reverb(v: ReverbPreset | ReverbParams) { this._reverb = v; this._push() }
|
|
42
|
+
/** Crossfade depth in metres inside the border (0 = a hard edge). Default 1. */
|
|
43
|
+
get blend(): number { return this._blend }
|
|
44
|
+
set blend(v: number) { this._blend = v; this._push() }
|
|
45
|
+
/** The bus the zone's reverb rides on. Default `sfx`. */
|
|
46
|
+
get bus(): string { return this._bus }
|
|
47
|
+
set bus(v: string) { this._bus = v; this._push() }
|
|
48
|
+
|
|
49
|
+
onAttach(): void {
|
|
50
|
+
if (!audioSupported) return
|
|
51
|
+
this._id = _creatorAudio.createZone()
|
|
52
|
+
if (!this._id) return
|
|
53
|
+
_creatorAudio.attachZone(this._id, this.node.id)
|
|
54
|
+
this._push()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
onDetach(): void {
|
|
58
|
+
if (this._id && audioSupported) _creatorAudio.destroyZone(this._id)
|
|
59
|
+
this._id = 0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private _push(): void {
|
|
63
|
+
if (!this._id || !audioSupported) return
|
|
64
|
+
let box = this._box, sphere = this._sphere
|
|
65
|
+
if (!box && sphere === null) {
|
|
66
|
+
const shape = this.node.get(Shape)
|
|
67
|
+
if (shape?.sphere !== undefined) sphere = shape.sphere
|
|
68
|
+
else if (shape?.box) box = shape.box
|
|
69
|
+
}
|
|
70
|
+
const dims = sphere !== null
|
|
71
|
+
? new Float32Array([ sphere * 2, sphere * 2, sphere * 2 ])
|
|
72
|
+
: new Float32Array(box ? [ cx(box) * 2, cy(box) * 2, cz(box) * 2 ] : [ 2, 2, 2 ])
|
|
73
|
+
_creatorAudio.setZone(this._id, sphere !== null ? 1 : 0, dims, this._blend, audio._busId(this._bus), reverbBlock(this._reverb))
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// `scene.audio` (docs/audio-plan.md §2): the listener and the global 3D knobs. The listener is the
|
|
2
|
+
// scene's active camera by default — a `CameraPlace` switch or a follow rig changes nothing, the
|
|
3
|
+
// engine reads whatever camera it renders with. Set a node to listen from somewhere else (the
|
|
4
|
+
// player's head in a third-person game, so sounds pan around the character, not the camera).
|
|
5
|
+
|
|
6
|
+
import { audioSupported } from "../../audio/support"
|
|
7
|
+
import type { Node } from "../Node"
|
|
8
|
+
|
|
9
|
+
export class SceneAudio {
|
|
10
|
+
private _listener: Node | null = null
|
|
11
|
+
private _doppler = 1
|
|
12
|
+
|
|
13
|
+
/** The node the engine listens from; null (default) = the active camera. */
|
|
14
|
+
get listener(): Node | null { return this._listener }
|
|
15
|
+
set listener(node: Node | null) {
|
|
16
|
+
this._listener = node
|
|
17
|
+
if (audioSupported) _creatorAudio.setListener(node ? node.id : 0)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Multiplies every source's doppler amount (0 = off everywhere). Default 1. */
|
|
21
|
+
get dopplerFactor(): number { return this._doppler }
|
|
22
|
+
set dopplerFactor(v: number) {
|
|
23
|
+
this._doppler = v
|
|
24
|
+
if (audioSupported) _creatorAudio.setListenerOptions(v)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
// Without a loaded NavMesh the aspect is inert — `goTo` returns false, nothing throws — so a
|
|
21
21
|
// project stays runnable on a host without navigation.
|
|
22
22
|
|
|
23
|
-
import { Aspect } from "
|
|
24
|
-
import type { FieldMeta } from "
|
|
25
|
-
import { Vec3, cx, cy, cz, type Vec3Like } from "
|
|
26
|
-
import { CharacterController } from "
|
|
23
|
+
import { Aspect } from "../../core/Aspect"
|
|
24
|
+
import type { FieldMeta } from "../../core/fields"
|
|
25
|
+
import { Vec3, cx, cy, cz, type Vec3Like } from "../../math/vec"
|
|
26
|
+
import { CharacterController } from "../physics/CharacterController"
|
|
27
27
|
import { NavCrowd, NavMesh, NAV_AGENT, NAV_READ } from "./NavMesh"
|
|
28
|
-
import type { Node } from "
|
|
28
|
+
import type { Node } from "../Node"
|
|
29
29
|
|
|
30
30
|
export type NavAgentState = "idle" | "moving" | "arrived" | "blocked" | "offmesh"
|
|
31
31
|
export type NavAgentAvoidance = "off" | "low" | "medium" | "high"
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
// (creator-nav) is the algorithms and the per-step hot path: ONE crowd update + ONE flat read per
|
|
19
19
|
// fixed step for every agent in the scene (`NavCrowd`, a system the mesh attaches to its scene).
|
|
20
20
|
|
|
21
|
-
import { fetch } from "
|
|
22
|
-
import { System } from "
|
|
23
|
-
import { Vec3, cx, cy, cz, type Vec3Like } from "
|
|
24
|
-
import { Geometry } from "
|
|
25
|
-
import { Material } from "
|
|
26
|
-
import { Mesh } from "
|
|
27
|
-
import type { Node } from "
|
|
28
|
-
import type { Scene } from "
|
|
21
|
+
import { fetch } from "../../runtime/fetch"
|
|
22
|
+
import { System } from "../../core/Aspect"
|
|
23
|
+
import { Vec3, cx, cy, cz, type Vec3Like } from "../../math/vec"
|
|
24
|
+
import { Geometry } from "../Geometry"
|
|
25
|
+
import { Material } from "../Material"
|
|
26
|
+
import { Mesh } from "../Mesh"
|
|
27
|
+
import type { Node } from "../Node"
|
|
28
|
+
import type { Scene } from "../Scene"
|
|
29
29
|
import type { NavAgent } from "./NavAgent"
|
|
30
30
|
|
|
31
31
|
// Fixed-order layouts — keep in step with creator-nav/creator-nav.h (CNAV_AGENT_* / CNAV_READ_*).
|
|
@@ -38,12 +38,12 @@
|
|
|
38
38
|
// Note: it collides with solid bodies but PASSES THROUGH triggers, and (v1) is not itself detected by
|
|
39
39
|
// triggers and isn't pointer-pickable while active.
|
|
40
40
|
|
|
41
|
-
import { Aspect } from "
|
|
42
|
-
import type { FieldMeta } from "
|
|
43
|
-
import { Vec3, cx, cy, cz, type Vec2Like, type Vec3Like } from "
|
|
44
|
-
import type { CompAxis, CompWriter } from "
|
|
41
|
+
import { Aspect } from "../../core/Aspect"
|
|
42
|
+
import type { FieldMeta } from "../../core/fields"
|
|
43
|
+
import { Vec3, cx, cy, cz, type Vec2Like, type Vec3Like } from "../../math/vec"
|
|
44
|
+
import type { CompAxis, CompWriter } from "../../core/compWrite"
|
|
45
45
|
import { Shape } from "./Shape"
|
|
46
|
-
import type { Node } from "
|
|
46
|
+
import type { Node } from "../Node"
|
|
47
47
|
|
|
48
48
|
/** Where the character's feet are, as reported by the solver after the last step. */
|
|
49
49
|
export type GroundState = "ground" | "slope" | "unsupported" | "air"
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
// velocity / applyImpulse. Reading node.position is free (native already wrote it). A `position` write
|
|
12
12
|
// is a TELEPORT that reaches the body (Shape routes it), which is how a kinematic body is placed too.
|
|
13
13
|
|
|
14
|
-
import { Aspect } from "
|
|
15
|
-
import type { FieldMeta } from "
|
|
16
|
-
import type { CompAxis, CompWriter } from "
|
|
17
|
-
import { Vec3, cx, cy, cz, type Vec3Like } from "
|
|
18
|
-
import { nodeRegistry, type Node } from "
|
|
14
|
+
import { Aspect } from "../../core/Aspect"
|
|
15
|
+
import type { FieldMeta } from "../../core/fields"
|
|
16
|
+
import type { CompAxis, CompWriter } from "../../core/compWrite"
|
|
17
|
+
import { Vec3, cx, cy, cz, type Vec3Like } from "../../math/vec"
|
|
18
|
+
import { nodeRegistry, type Node } from "../Node"
|
|
19
19
|
import { Shape } from "./Shape"
|
|
20
20
|
|
|
21
21
|
export type MotionType = "static" | "dynamic" | "kinematic"
|
|
@@ -95,6 +95,7 @@ export class Physics extends Aspect<"physics", Node> implements CompWriter {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
private _bodyId = 0
|
|
98
|
+
private static _refusedWarned = false
|
|
98
99
|
|
|
99
100
|
onAttach(): void {
|
|
100
101
|
if (!_creator.physicsHasSupport || !_creator.physicsHasSupport()) return
|
|
@@ -107,6 +108,12 @@ export class Physics extends Aspect<"physics", Node> implements CompWriter {
|
|
|
107
108
|
}
|
|
108
109
|
const shapeId = shape._claim() // drop the pick-only body; reuse its shape
|
|
109
110
|
this._bodyId = _creator.physicsCreateBody(this.node.id, shapeId, MOTION[this.motion], this.mass, false, false)
|
|
111
|
+
// The engine refuses a body past its limit (4 096 unless configured) — silently, and a level that
|
|
112
|
+
// crosses it loses whatever loads LAST (the terrain, streamed in after the props): say so once.
|
|
113
|
+
if (!this._bodyId && shapeId && !Physics._refusedWarned) {
|
|
114
|
+
Physics._refusedWarned = true
|
|
115
|
+
console.warn(`Physics: the engine refused a body for "${this.node.name}" — the body limit is reached? Raise it before the level loads: Physics.configure({ maxBodies })`)
|
|
116
|
+
}
|
|
110
117
|
// A fresh body already carries DEFAULT_FRICTION host-side, so only a NAMED surface has to
|
|
111
118
|
// travel — the common case costs no bridge call at all.
|
|
112
119
|
if (this._friction !== DEFAULT_FRICTION) _creator.physicsSetFriction?.(this._bodyId, this._friction)
|