lecodes-cli 0.17.1 → 0.17.2
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/index.js +3 -3
- package/package.json +3 -3
- package/runtime/sdk/gl/CharacterController.ts +3 -0
- package/runtime/sdk/gl/Material.ts +12 -0
- package/runtime/sdk/gl/Particles.ts +676 -625
- package/runtime/sdk/gl/Physics.ts +126 -119
- package/runtime/sdk/gl/Scene.ts +299 -291
- package/runtime/sdk/gl/Shape.ts +37 -1
- package/runtime/sdk/gl/Trigger.ts +50 -46
- package/runtime/sdk-types.json +1 -1
package/runtime/sdk/gl/Shape.ts
CHANGED
|
@@ -28,6 +28,11 @@ export class Shape extends Aspect<"shape", Node> {
|
|
|
28
28
|
cylinder?: { halfHeight: number, radius: number }
|
|
29
29
|
/** Capsule aligned on Y: half-height of the cylinder section + radius. */
|
|
30
30
|
capsule?: { halfHeight: number, radius: number }
|
|
31
|
+
/** The node's own triangles (a Mesh's geometry or a Model's GLB, world scale applied).
|
|
32
|
+
* `true` = exact triangle mesh — static/kinematic bodies, triggers, characters' ground and picking
|
|
33
|
+
* only (a dynamic body can't be a triangle mesh; Physics throws). `'convex'` = convex hull of the
|
|
34
|
+
* vertices, works for dynamic bodies too. A Model uses its bind pose (skinned parts skipped). */
|
|
35
|
+
mesh?: boolean | "convex"
|
|
31
36
|
/** Whether pointer rays can hit this shape (click / touchstart). Default true. */
|
|
32
37
|
raycast = true
|
|
33
38
|
|
|
@@ -70,13 +75,44 @@ export class Shape extends Aspect<"shape", Node> {
|
|
|
70
75
|
if (!this.raycast && this._bodyId) _creator.physicsSetPickable(this._bodyId, false)
|
|
71
76
|
}
|
|
72
77
|
|
|
78
|
+
/** @internal true for an exact triangle mesh — Physics refuses a dynamic body on it. */
|
|
79
|
+
get _isTriangleMesh(): boolean { return this.mesh === true }
|
|
80
|
+
|
|
73
81
|
private _build(): number {
|
|
82
|
+
if (this.mesh) return this._buildMesh(this.mesh === "convex")
|
|
74
83
|
if (this.sphere !== undefined) return _creator.physicsBuildSphere(this.sphere)
|
|
75
84
|
if (this.cylinder) return _creator.physicsBuildCylinder(this.cylinder.halfHeight, this.cylinder.radius)
|
|
76
85
|
if (this.capsule) return _creator.physicsBuildCapsule(this.capsule.halfHeight, this.capsule.radius)
|
|
77
86
|
if (this.box) return _creator.physicsBuildBox(cx(this.box), cy(this.box), cz(this.box))
|
|
87
|
+
return this._buildAutoBox()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// mesh: a Mesh node hands its CPU geometry over; anything else is tried as a GLB root (the host
|
|
91
|
+
// reads the asset's triangles itself — 0 if the node is neither). Always × world scale: the
|
|
92
|
+
// triangles are node-local, so the collider must follow the rendered size.
|
|
93
|
+
private _buildMesh(convex: boolean): number {
|
|
94
|
+
if (!_creator.physicsBuildMesh) {
|
|
95
|
+
console.warn("Shape { mesh } needs a newer host — falling back to a box")
|
|
96
|
+
return this._buildAutoBox()
|
|
97
|
+
}
|
|
98
|
+
const s = this.node.worldScale
|
|
99
|
+
const geom = this.node.geometry
|
|
100
|
+
let id = 0
|
|
101
|
+
if (geom) {
|
|
102
|
+
if (geom._meshKind !== 0) throw new Error("Shape { mesh } needs a triangle geometry (not edges/vertices)")
|
|
103
|
+
id = _creator.physicsBuildMesh(geom.vertices, Uint32Array.from(geom.indices), convex, s.x, s.y, s.z)
|
|
104
|
+
} else {
|
|
105
|
+
id = _creator.physicsBuildMeshFromEntity(this.node.id, convex, s.x, s.y, s.z)
|
|
106
|
+
}
|
|
107
|
+
if (!id) {
|
|
108
|
+
console.warn(`Shape { mesh } could not build a ${convex ? "convex hull" : "triangle mesh"} for "${this.node.name}" (no triangles / not a Mesh or Model) — falling back to a box`)
|
|
109
|
+
return this._buildAutoBox()
|
|
110
|
+
}
|
|
111
|
+
return id
|
|
112
|
+
}
|
|
78
113
|
|
|
79
|
-
|
|
114
|
+
// auto: a box from the mesh AABB × world scale (so the physical box matches the rendered size).
|
|
115
|
+
private _buildAutoBox(): number {
|
|
80
116
|
const s = this.node.worldScale
|
|
81
117
|
let hx = 0.5, hy = 0.5, hz = 0.5
|
|
82
118
|
const geom = this.node.geometry
|
|
@@ -1,46 +1,50 @@
|
|
|
1
|
-
// A trigger zone, as an aspect on a 3D Node. Requires a Shape (its geometry); creates a static SENSOR
|
|
2
|
-
// body from it, so a physics body overlapping the zone fires the node's 'enter' / 'exit' events
|
|
3
|
-
// instead of colliding. A trigger does not block movement, and is still pointer-pickable.
|
|
4
|
-
//
|
|
5
|
-
// const goal = new Mesh(box(), material)
|
|
6
|
-
// .aspect(Shape, { box: [1, 2, 1] })
|
|
7
|
-
// .aspect(Trigger)
|
|
8
|
-
// goal.addEventListener('enter', other => win(other))
|
|
9
|
-
// goal.addEventListener('exit', other => …)
|
|
10
|
-
|
|
11
|
-
import { Aspect } from "../core/Aspect"
|
|
12
|
-
import { cx, cy, cz, type Vec3Like } from "../math/vec"
|
|
13
|
-
import { Shape } from "./Shape"
|
|
14
|
-
import { ensurePhysicsEvents } from "./physicsEvents"
|
|
15
|
-
import type { Node } from "./Node"
|
|
16
|
-
|
|
17
|
-
export class Trigger extends Aspect<"trigger", Node> {
|
|
18
|
-
static readonly aspect = "trigger"
|
|
19
|
-
|
|
20
|
-
private _bodyId = 0
|
|
21
|
-
/** Native body id (0 if no physics support). */
|
|
22
|
-
get id(): number { return this._bodyId }
|
|
23
|
-
|
|
24
|
-
onAttach(): void {
|
|
25
|
-
if (!_creator.physicsHasSupport || !_creator.physicsHasSupport()) return
|
|
26
|
-
const shape = this.node.get(Shape)
|
|
27
|
-
if (!shape) {
|
|
28
|
-
throw new Error("Trigger requires a Shape aspect — add it first: node.aspect(Shape, {…}).aspect(Trigger)")
|
|
29
|
-
}
|
|
30
|
-
const shapeId = shape._claim()
|
|
31
|
-
this._bodyId = _creator.physicsCreateBody(this.node.id, shapeId, 0 /* static */, 0, true /* sensor */, false)
|
|
32
|
-
shape._ownBody(this._bodyId)
|
|
33
|
-
ensurePhysicsEvents() // routes overlap events → the node's 'enter' / 'exit'
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
onDetach(): void {
|
|
37
|
-
if (this._bodyId) { _creator.physicsRemoveBody(this._bodyId); this._bodyId = 0 }
|
|
38
|
-
this.node.get(Shape)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
1
|
+
// A trigger zone, as an aspect on a 3D Node. Requires a Shape (its geometry); creates a static SENSOR
|
|
2
|
+
// body from it, so a physics body overlapping the zone fires the node's 'enter' / 'exit' events
|
|
3
|
+
// instead of colliding. A trigger does not block movement, and is still pointer-pickable.
|
|
4
|
+
//
|
|
5
|
+
// const goal = new Mesh(box(), material)
|
|
6
|
+
// .aspect(Shape, { box: [1, 2, 1] })
|
|
7
|
+
// .aspect(Trigger)
|
|
8
|
+
// goal.addEventListener('enter', other => win(other))
|
|
9
|
+
// goal.addEventListener('exit', other => …)
|
|
10
|
+
|
|
11
|
+
import { Aspect } from "../core/Aspect"
|
|
12
|
+
import { cx, cy, cz, type Vec3Like } from "../math/vec"
|
|
13
|
+
import { Shape } from "./Shape"
|
|
14
|
+
import { ensurePhysicsEvents } from "./physicsEvents"
|
|
15
|
+
import type { Node } from "./Node"
|
|
16
|
+
|
|
17
|
+
export class Trigger extends Aspect<"trigger", Node> {
|
|
18
|
+
static readonly aspect = "trigger"
|
|
19
|
+
|
|
20
|
+
private _bodyId = 0
|
|
21
|
+
/** Native body id (0 if no physics support). */
|
|
22
|
+
get id(): number { return this._bodyId }
|
|
23
|
+
|
|
24
|
+
onAttach(): void {
|
|
25
|
+
if (!_creator.physicsHasSupport || !_creator.physicsHasSupport()) return
|
|
26
|
+
const shape = this.node.get(Shape)
|
|
27
|
+
if (!shape) {
|
|
28
|
+
throw new Error("Trigger requires a Shape aspect — add it first: node.aspect(Shape, {…}).aspect(Trigger)")
|
|
29
|
+
}
|
|
30
|
+
const shapeId = shape._claim()
|
|
31
|
+
this._bodyId = _creator.physicsCreateBody(this.node.id, shapeId, 0 /* static */, 0, true /* sensor */, false)
|
|
32
|
+
shape._ownBody(this._bodyId)
|
|
33
|
+
ensurePhysicsEvents() // routes overlap events → the node's 'enter' / 'exit'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
onDetach(): void {
|
|
37
|
+
if (this._bodyId) { _creator.physicsRemoveBody(this._bodyId); this._bodyId = 0 }
|
|
38
|
+
const shape = this.node.get(Shape)
|
|
39
|
+
if (shape) {
|
|
40
|
+
shape._ownBody(0) // the Shape held OUR body id — forget it, or its own detach removes it twice (native crash)
|
|
41
|
+
shape._recreatePickBody()
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Reposition the trigger zone to a world point. */
|
|
46
|
+
moveTo(p: Vec3Like): this {
|
|
47
|
+
if (this._bodyId) _creator.physicsSetBodyPosition(this._bodyId, cx(p), cy(p), cz(p))
|
|
48
|
+
return this
|
|
49
|
+
}
|
|
50
|
+
}
|