lecodes-cli 0.18.1 → 0.19.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/index.js +2096 -506
- package/package.json +3 -2
- package/runtime/scene-harness.json +1 -1
- package/runtime/sdk/core/Aspect.ts +65 -9
- package/runtime/sdk/core/StateMachine.ts +308 -0
- package/runtime/sdk/core/time.ts +3 -0
- package/runtime/sdk/gl/Camera.ts +39 -0
- package/runtime/sdk/gl/Light.ts +12 -4
- package/runtime/sdk/gl/Lightmap.ts +87 -10
- package/runtime/sdk/gl/Material.ts +15 -0
- package/runtime/sdk/gl/Model.ts +68 -8
- package/runtime/sdk/gl/NavAgent.ts +337 -0
- package/runtime/sdk/gl/NavMesh.ts +397 -0
- package/runtime/sdk/gl/Ragdoll.ts +270 -0
- package/runtime/sdk/gl/Scene.ts +20 -0
- package/runtime/sdk/gl/Shape.ts +37 -5
- package/runtime/sdk/gl/Terrain.ts +1092 -0
- package/runtime/sdk/gl/Texture.ts +17 -0
- package/runtime/sdk/gl/animation/Animator.ts +35 -2
- package/runtime/sdk/gl/animation/Layer.ts +3 -1
- package/runtime/sdk/gl/animation/Loop.ts +5 -0
- package/runtime/sdk/gl/animation/Playback.ts +4 -3
- package/runtime/sdk/gl/animation/core.ts +52 -10
- package/runtime/sdk/gl/terrainMesh.ts +219 -0
- package/runtime/sdk/inject.ts +12 -1
- package/runtime/sdk/net/codec.ts +119 -0
- package/runtime/sdk/net/core.ts +384 -0
- package/runtime/sdk/net/index.ts +181 -0
- package/runtime/sdk/net/replication.ts +622 -0
- package/runtime/sdk/runtime/device.ts +102 -1
- package/runtime/sdk/scene/defineScene.ts +1353 -1227
- package/runtime/sdk/scene/editorPlugins.ts +18 -0
- package/runtime/sdk/ui/UINode.ts +4 -0
- package/runtime/sdk-types.json +1 -1
package/runtime/sdk/gl/Scene.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { Color, type ColorInput } from "../core/color"
|
|
9
9
|
import { _installAspectFrames, _installTimeScale, _attachSystem, _detachSystem, _detachAllSystems, type Aspect, type AspectCtor, type FieldOf, type TargetOf } from "../core/Aspect"
|
|
10
10
|
import { _bumpNavEpoch, _navEpoch, _navSupported, _setCurrent, Presentable, type PresentOptions } from "../ui/presentable"
|
|
11
|
+
import { device } from "../runtime/device"
|
|
11
12
|
import type { ClickEvent, TouchStartEvent } from "../runtime/touch"
|
|
12
13
|
import type { FetchResponse } from "../runtime/fetch"
|
|
13
14
|
import { _requestCameraPermission } from "../plugins/permission"
|
|
@@ -115,6 +116,14 @@ export type SceneOptions = {
|
|
|
115
116
|
* scene file — `env` is applied before the nodes build) and leaves already-loaded ones alone.
|
|
116
117
|
* On the desktop host `CREATOR_TEXTURE_ANISOTROPY` overrides it, for tuning without a rebuild. */
|
|
117
118
|
anisotropy?: number
|
|
119
|
+
/** The look on an HDR display (a screen with headroom above SDR white — Apple XDR panels, the
|
|
120
|
+
* macOS host today); ignored on SDR. `strength` 0..1 is how much of the picture reaches for the
|
|
121
|
+
* display's headroom (0 only what SDR clipped, 1 nearly everything; default 0.35). `paperWhite`
|
|
122
|
+
* 1..8 is where white lands as a multiple of SDR white — the "HDR brightness" of a console
|
|
123
|
+
* calibration screen, where 1.5–2 is the norm; default 1 keeps a white wall at the UI's white,
|
|
124
|
+
* faithful but dim next to what "HDR on" is expected to look like. Engine-wide like `anisotropy`;
|
|
125
|
+
* `device.hdr` is the runtime form and tells you whether the display has any headroom at all. */
|
|
126
|
+
hdr?: { strength?: number; paperWhite?: number }
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
// Route aspect update(dt) through the render-synced native phases (render() early = before physics,
|
|
@@ -132,6 +141,13 @@ const installRenderSyncedFrames = (): void => {
|
|
|
132
141
|
_creator.setEarlyUpdate((dt: number) => { glState.frame++; early(dt) })
|
|
133
142
|
_creator.setLateUpdate(late)
|
|
134
143
|
return true
|
|
144
|
+
}, (fixed) => {
|
|
145
|
+
// The fixed phase runs INSIDE the engine's substep loop (physics/world.cpp) when the host has the
|
|
146
|
+
// hook; each substep moves bodies natively, so the frame stamp bumps per step too. Feature-detected:
|
|
147
|
+
// an older host returns false and the dispatcher steps its own 1/60 accumulator.
|
|
148
|
+
if (typeof _creator.setFixedUpdate !== "function") return false
|
|
149
|
+
_creator.setFixedUpdate((dt: number) => { glState.frame++; fixed(dt) })
|
|
150
|
+
return true
|
|
135
151
|
})
|
|
136
152
|
// Time.scale / Time.paused reach the engine's own clocks (physics, animators, particles) through
|
|
137
153
|
// setTimeScale; a host without it keeps running its sims at wall-clock speed (feature-detected).
|
|
@@ -188,6 +204,10 @@ export class Scene implements Presentable {
|
|
|
188
204
|
// Before anything else in this constructor that could load a texture: the value has to be in
|
|
189
205
|
// place by the time assets bind their samplers.
|
|
190
206
|
if (options.anisotropy !== undefined) _creator.setTextureAnisotropy?.(options.anisotropy)
|
|
207
|
+
if (options.hdr) {
|
|
208
|
+
if (options.hdr.strength !== undefined) device.hdr.strength = options.hdr.strength
|
|
209
|
+
if (options.hdr.paperWhite !== undefined) device.hdr.paperWhite = options.hdr.paperWhite
|
|
210
|
+
}
|
|
191
211
|
if (options.renderScale !== undefined || options.dynamicResolution) {
|
|
192
212
|
this.setRenderOptions(options.renderScale ?? 1, options.dynamicResolution ?? false)
|
|
193
213
|
}
|
package/runtime/sdk/gl/Shape.ts
CHANGED
|
@@ -22,7 +22,11 @@ import { cx, cy, cz, type Vec3Like } from "../math/vec"
|
|
|
22
22
|
import type { Node } from "./Node"
|
|
23
23
|
|
|
24
24
|
/** Which geometry a Shape uses — the one-of the fields below encode (see `_build`'s order). */
|
|
25
|
-
export type ShapeKind = "auto" | "box" | "sphere" | "cylinder" | "capsule" | "mesh" | "convex"
|
|
25
|
+
export type ShapeKind = "auto" | "box" | "sphere" | "cylinder" | "capsule" | "mesh" | "convex" | "heightfield"
|
|
26
|
+
|
|
27
|
+
/** What a terrain node carries for `Shape { heightfield: true }` (gl/Terrain.ts `_data`). */
|
|
28
|
+
type TerrainData = { sizeX: number, sizeZ: number, cellSize: number, heights: Float32Array, holes: Uint8Array | null }
|
|
29
|
+
const terrainOf = (node: Node): TerrainData | null => (node as unknown as { _terrain?: { _data: TerrainData } })._terrain?._data ?? null
|
|
26
30
|
|
|
27
31
|
/** A node-local AABB, in the node's OWN frame (its own transform excluded), before world scale. */
|
|
28
32
|
export type ShapeBounds = { min: [number, number, number], max: [number, number, number] }
|
|
@@ -60,6 +64,10 @@ export class Shape extends Aspect<"shape", Node> {
|
|
|
60
64
|
* only (a dynamic body can't be a triangle mesh; Physics throws). `'convex'` = convex hull of the
|
|
61
65
|
* vertices, works for dynamic bodies too. A Model uses its bind pose (skinned parts skipped). */
|
|
62
66
|
mesh?: boolean | "convex"
|
|
67
|
+
/** The node's terrain (`Terrain.create(...).node`) as a Jolt height field — the exact drawn grid,
|
|
68
|
+
* holes included (docs/terrain-plan.md). Static / kinematic / picking / character ground only,
|
|
69
|
+
* like `mesh: true`. `terrain.commit()` pushes edits into the live shape. */
|
|
70
|
+
heightfield?: boolean
|
|
63
71
|
/**
|
|
64
72
|
* The shape's centre, relative to the node's origin — world units in the node's ROTATED frame
|
|
65
73
|
* (the node's scale is ignored, exactly like the explicit dimensions above). Default [0, 0, 0].
|
|
@@ -140,6 +148,7 @@ export class Shape extends Aspect<"shape", Node> {
|
|
|
140
148
|
|
|
141
149
|
/** Which geometry this shape currently uses (the `_build` dispatch, as a name). */
|
|
142
150
|
get kind(): ShapeKind {
|
|
151
|
+
if (this.heightfield) return "heightfield"
|
|
143
152
|
if (this.mesh === "convex") return "convex"
|
|
144
153
|
if (this.mesh) return "mesh"
|
|
145
154
|
if (this.sphere !== undefined) return "sphere"
|
|
@@ -238,8 +247,14 @@ export class Shape extends Aspect<"shape", Node> {
|
|
|
238
247
|
if (!this.raycast && this._bodyId) _creator.physicsSetPickable(this._bodyId, false)
|
|
239
248
|
}
|
|
240
249
|
|
|
241
|
-
/** @internal true for an exact triangle mesh — Physics refuses a dynamic body on it. */
|
|
242
|
-
get _isTriangleMesh(): boolean { return this.mesh === true }
|
|
250
|
+
/** @internal true for an exact triangle mesh or a height field — Physics refuses a dynamic body on it. */
|
|
251
|
+
get _isTriangleMesh(): boolean { return this.mesh === true || this.heightfield === true }
|
|
252
|
+
|
|
253
|
+
/** @internal the height range a heightfield shape can encode (`Terrain.commit` rebuilds past it). */
|
|
254
|
+
_heightfieldRange: [number, number] | null = null
|
|
255
|
+
|
|
256
|
+
/** @internal rebuild the geometry in place (a terrain edit that left the encodable range). */
|
|
257
|
+
_rebuildShape(): void { this._rebuild() }
|
|
243
258
|
|
|
244
259
|
// Rebuild the geometry in place: the body keeps its id, velocity and world transform (Physics
|
|
245
260
|
// holds that id, so re-creating the body instead would strand it). A shape a CharacterController
|
|
@@ -290,7 +305,8 @@ export class Shape extends Aspect<"shape", Node> {
|
|
|
290
305
|
this._autoOrigin = null
|
|
291
306
|
this._sig = this._signature()
|
|
292
307
|
let id = 0
|
|
293
|
-
if (this.
|
|
308
|
+
if (this.heightfield) id = this._buildHeightField()
|
|
309
|
+
else if (this.mesh) id = this._buildMesh(this.mesh === "convex")
|
|
294
310
|
else if (this.sphere !== undefined) id = _creator.physicsBuildSphere(this.sphere)
|
|
295
311
|
else if (this.cylinder) id = _creator.physicsBuildCylinder(this.cylinder.halfHeight, this.cylinder.radius)
|
|
296
312
|
else if (this.capsule) id = _creator.physicsBuildCapsule(this.capsule.halfHeight, this.capsule.radius)
|
|
@@ -321,7 +337,23 @@ export class Shape extends Aspect<"shape", Node> {
|
|
|
321
337
|
private _signature(): string {
|
|
322
338
|
const v = (a: Vec3Like | undefined) => (a ? `${cx(a)},${cy(a)},${cz(a)}` : "")
|
|
323
339
|
const t = (o: { halfHeight: number, radius: number } | undefined) => (o ? `${o.halfHeight},${o.radius}` : "")
|
|
324
|
-
return `${v(this.box)}|${this.sphere ?? ""}|${t(this.cylinder)}|${t(this.capsule)}|${this.mesh ?? ""}|${v(this.origin)}`
|
|
340
|
+
return `${v(this.box)}|${this.sphere ?? ""}|${t(this.cylinder)}|${t(this.capsule)}|${this.mesh ?? ""}|${this.heightfield ? "hf" : ""}|${v(this.origin)}`
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// heightfield: the terrain on this node hands its grid over; the engine keeps its own copy and
|
|
344
|
+
// pads it to Jolt's block grid. No terrain / no host support = no collider (0), with a warning.
|
|
345
|
+
private _buildHeightField(): number {
|
|
346
|
+
const t = terrainOf(this.node)
|
|
347
|
+
if (!t) { console.warn(`Shape { heightfield }: "${this.node.name}" carries no Terrain — no collider`); return 0 }
|
|
348
|
+
if (!_creator.physicsBuildHeightField) { console.warn("Shape { heightfield } needs a newer host — the terrain has no collider"); return 0 }
|
|
349
|
+
const s = this.node.worldScale
|
|
350
|
+
let lo = Infinity, hi = -Infinity
|
|
351
|
+
for (let i = 0; i < t.heights.length; i++) { const h = t.heights[i]; if (h < lo) lo = h; if (h > hi) hi = h }
|
|
352
|
+
const range = Math.max(1, hi - lo), margin = Math.max(2, range * 0.25)
|
|
353
|
+
this._heightfieldRange = [ lo - margin, hi + margin ] // mirrors creator-gl physicsBuildHeightField's headroom
|
|
354
|
+
const id = _creator.physicsBuildHeightField(t.heights, t.sizeX, t.sizeZ, t.cellSize, t.holes, s.x, s.y, s.z)
|
|
355
|
+
if (!id) console.warn(`Shape { heightfield }: the engine refused "${this.node.name}"'s grid (all holes?)`)
|
|
356
|
+
return id
|
|
325
357
|
}
|
|
326
358
|
|
|
327
359
|
// mesh: a Mesh node hands its CPU geometry over; anything else is tried as a GLB root (the host
|