lecodes-sdk 1.1.0 → 2.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/README.md +5 -2
- package/dist/editor.d.ts +12 -0
- package/dist/global.d.ts +4 -8
- package/dist/host.d.ts +2 -3
- package/dist/types/animate/tween/Animation.d.ts +0 -3
- package/dist/types/animate/tween/animateValue.d.ts +4 -2
- package/dist/types/animate/tween/easing.d.ts +8 -0
- package/dist/types/animate/tween/spec.d.ts +15 -7
- package/dist/types/audio/audio.d.ts +2 -1
- package/dist/types/canvas/Canvas.d.ts +40 -108
- package/dist/types/canvas/gen/cssColor.d.ts +17 -0
- package/dist/types/canvas/gen/recorder.d.ts +118 -0
- package/dist/types/canvas/gen/spec.d.ts +144 -0
- package/dist/types/core/color.d.ts +3 -1
- package/dist/types/core/pins.d.ts +18 -0
- package/dist/types/g2/Node2D.d.ts +5 -8
- package/dist/types/g2/Scene2D.d.ts +6 -2
- package/dist/types/gl/Foliage.d.ts +30 -7
- package/dist/types/gl/Light.d.ts +11 -0
- package/dist/types/gl/Lightmap.d.ts +13 -2
- package/dist/types/gl/Material.d.ts +17 -3
- package/dist/types/gl/Model.d.ts +6 -2
- package/dist/types/gl/Node.d.ts +3 -6
- package/dist/types/gl/Particles.d.ts +15 -0
- package/dist/types/gl/Scene.d.ts +41 -13
- package/dist/types/gl/Texture.d.ts +1 -1
- package/dist/types/gl/animation/Locomotion.d.ts +59 -3
- package/dist/types/gl/terrain/Terrain.d.ts +8 -0
- package/dist/types/inject.d.ts +11 -11
- package/dist/types/inject.editor.d.ts +1 -0
- package/dist/types/net/core.d.ts +7 -0
- package/dist/types/plugin.d.ts +76 -0
- package/dist/types/plugins/gen/camera/sdk/camera.d.ts +24 -0
- package/dist/types/plugins/gen/camera/sdk/camera.gen.d.ts +25 -0
- package/dist/types/plugins/{geolocation.d.ts → gen/geolocation/sdk/geolocation.d.ts} +2 -20
- package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +31 -0
- package/dist/types/plugins/{map.d.ts → gen/map/sdk/map.d.ts} +9 -50
- package/dist/types/plugins/gen/map/sdk/map.gen.d.ts +53 -0
- package/dist/types/plugins/gen/push/sdk/push.d.ts +23 -0
- package/dist/types/plugins/gen/push/sdk/push.gen.d.ts +35 -0
- package/dist/types/plugins/{qr.d.ts → gen/qr-scanner/sdk/qr-scanner.d.ts} +2 -3
- package/dist/types/plugins/gen/qr-scanner/sdk/qr-scanner.gen.d.ts +15 -0
- package/dist/types/runtime/app.d.ts +9 -2
- package/dist/types/runtime/fetch.d.ts +2 -0
- package/dist/types/runtime/input.d.ts +8 -1
- package/dist/types/runtime/media.d.ts +6 -10
- package/dist/types/runtime/misc.d.ts +4 -1
- package/dist/types/runtime/net.d.ts +3 -2
- package/dist/types/runtime/touch.d.ts +32 -0
- package/dist/types/scene/defineScene.d.ts +43 -2
- package/dist/types/scene/editor.d.ts +52 -0
- package/dist/types/scene/gizmos.d.ts +7 -4
- package/dist/types/ui/NativeView.d.ts +6 -4
- package/dist/types/ui/UI.d.ts +1 -1
- package/dist/types/ui/UIBottomSheet.d.ts +6 -12
- package/dist/types/ui/UIButton.d.ts +12 -14
- package/dist/types/ui/UIContainer.d.ts +0 -6
- package/dist/types/ui/UIImage.d.ts +1 -4
- package/dist/types/ui/UIInput.d.ts +8 -24
- package/dist/types/ui/UIModal.d.ts +0 -2
- package/dist/types/ui/UINode.d.ts +70 -56
- package/dist/types/ui/UIPager.d.ts +28 -27
- package/dist/types/ui/UIPopover.d.ts +0 -2
- package/dist/types/ui/UIScreen.d.ts +12 -17
- package/dist/types/ui/UIScrollable.d.ts +1 -4
- package/dist/types/ui/UIText.d.ts +0 -2
- package/dist/types/ui/UIVideo.d.ts +3 -5
- package/dist/types/ui/UIVirtualizedList.d.ts +14 -16
- package/dist/types/ui/UIWidget.d.ts +10 -9
- package/dist/types/ui/colorKeys.gen.d.ts +9 -0
- package/dist/types/ui/presentable.d.ts +46 -32
- package/dist/types/ui/router.d.ts +18 -7
- package/dist/types/ui/styleColor.d.ts +1 -0
- package/dist/types/ui/transitions.d.ts +18 -0
- package/dist/types/ui/tree.d.ts +75 -0
- package/dist/types/version.d.ts +10 -0
- package/dist/types.json +1 -1
- package/package.json +12 -3
- package/prompts/2d.md +2 -6
- package/prompts/3d.md +1 -5
- package/prompts/README.md +142 -142
- package/prompts/canvas.md +9 -8
- package/prompts/compose.ts +1 -1
- package/prompts/core.md +3 -3
- package/prompts/design.md +1 -1
- package/prompts/dist/2d-game.md +77 -54
- package/prompts/dist/3d-app.md +75 -52
- package/prompts/dist/ar-app.md +74 -51
- package/prompts/dist/design.md +30 -8
- package/prompts/dist/ui-app.md +73 -46
- package/prompts/ui-design.md +2 -3
- package/prompts/ui.md +45 -37
- package/src/animate/tween/Animation.ts +262 -378
- package/src/animate/tween/animateValue.ts +103 -100
- package/src/animate/tween/easing.ts +10 -3
- package/src/animate/tween/spec.ts +505 -479
- package/src/audio/Sound.ts +3 -3
- package/src/audio/audio.ts +162 -161
- package/src/bridges/2d.d.ts +317 -0
- package/src/bridges/app.d.ts +91 -0
- package/src/bridges/audio.d.ts +97 -0
- package/src/bridges/canvas.d.ts +79 -0
- package/src/bridges/device.d.ts +72 -0
- package/src/bridges/fetch.d.ts +80 -0
- package/src/bridges/files.d.ts +70 -0
- package/src/bridges/gl.d.ts +1133 -0
- package/src/bridges/input.d.ts +72 -0
- package/src/bridges/media.d.ts +55 -0
- package/src/bridges/nav.d.ts +71 -0
- package/src/bridges/net.d.ts +52 -0
- package/src/bridges/service.d.ts +52 -0
- package/src/bridges/socket.d.ts +31 -0
- package/src/bridges/storage.d.ts +33 -0
- package/src/bridges/tree.d.ts +301 -0
- package/src/bridges/types.d.ts +49 -0
- package/src/canvas/Canvas.ts +114 -159
- package/src/canvas/gen/cssColor.ts +224 -0
- package/src/canvas/gen/recorder.ts +212 -0
- package/src/canvas/gen/spec.ts +201 -0
- package/src/chisel.ts +193 -0
- package/src/compile/assetMacro.ts +1 -1
- package/src/compile/bundler.ts +11 -2
- package/src/compile/compileProject.ts +43 -4
- package/src/compile/fontMacro.ts +3 -4
- package/src/compile/header.ts +26 -5
- package/src/compile/index.ts +3 -1
- package/src/compile/liteMaterial.ts +1 -1
- package/src/compile/sceneEditor.ts +11 -26
- package/src/core/color.ts +73 -30
- package/src/core/pins.ts +51 -0
- package/src/core/signals.ts +8 -1
- package/src/g2/CharacterController2D.ts +3 -3
- package/src/g2/Node2D.ts +57 -39
- package/src/g2/Physics2D.ts +2 -2
- package/src/g2/Scene2D.ts +35 -23
- package/src/g2/Texture2D.ts +1 -1
- package/src/g2/loop.ts +4 -4
- package/src/gl/Foliage.ts +72 -17
- package/src/gl/Geometry.ts +1 -2
- package/src/gl/Light.ts +13 -0
- package/src/gl/Lightmap.ts +45 -29
- package/src/gl/Material.ts +95 -51
- package/src/gl/Model.ts +172 -167
- package/src/gl/Node.ts +91 -24
- package/src/gl/Particles.ts +20 -1
- package/src/gl/Scene.ts +100 -44
- package/src/gl/Texture.ts +8 -7
- package/src/gl/animation/AnimationClip.ts +1 -1
- package/src/gl/animation/Locomotion.ts +77 -8
- package/src/gl/nav/NavMesh.ts +3 -4
- package/src/gl/physics/Physics.ts +2 -2
- package/src/gl/physics/physicsEvents.ts +3 -3
- package/src/gl/terrain/Terrain.ts +33 -5
- package/src/gl/touch.ts +14 -15
- package/src/host.d.ts +2 -3
- package/src/inject.editor.ts +7 -0
- package/src/inject.ts +233 -236
- package/src/net/core.ts +6 -5
- package/src/net/index.ts +1 -1
- package/src/net/replication.ts +1 -1
- package/src/plugin.ts +191 -0
- package/src/plugins/gen/camera/contract.d.ts +27 -0
- package/src/plugins/gen/camera/sdk/camera.gen.ts +46 -0
- package/src/plugins/gen/camera/sdk/camera.ts +57 -0
- package/src/plugins/gen/geolocation/contract.d.ts +50 -0
- package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +54 -0
- package/src/plugins/{geolocation.ts → gen/geolocation/sdk/geolocation.ts} +22 -43
- package/src/plugins/gen/map/contract.d.ts +144 -0
- package/src/plugins/gen/map/sdk/map.gen.ts +88 -0
- package/src/plugins/{map.ts → gen/map/sdk/map.ts} +68 -102
- package/src/plugins/gen/push/contract.d.ts +61 -0
- package/src/plugins/gen/push/sdk/push.gen.ts +60 -0
- package/src/plugins/gen/push/sdk/push.ts +105 -0
- package/src/plugins/gen/qr-scanner/contract.d.ts +16 -0
- package/src/plugins/gen/qr-scanner/sdk/qr-scanner.gen.ts +29 -0
- package/src/plugins/gen/qr-scanner/sdk/qr-scanner.ts +52 -0
- package/src/plugins/permission.ts +5 -4
- package/src/runtime/app.ts +20 -9
- package/src/runtime/appEvents.ts +5 -4
- package/src/runtime/channel.ts +18 -15
- package/src/runtime/clipboard.ts +4 -3
- package/src/runtime/datetime.ts +2 -1
- package/src/runtime/device.ts +17 -15
- package/src/runtime/fetch.ts +30 -20
- package/src/runtime/files.ts +16 -15
- package/src/runtime/input.ts +21 -8
- package/src/runtime/media.ts +50 -46
- package/src/runtime/misc.ts +7 -3
- package/src/runtime/net.ts +8 -7
- package/src/runtime/rpc.ts +1 -3
- package/src/runtime/service.ts +19 -14
- package/src/runtime/share.ts +4 -3
- package/src/runtime/storage.ts +6 -4
- package/src/runtime/touch.ts +32 -0
- package/src/scene/defineScene.ts +61 -365
- package/src/scene/editor.ts +408 -0
- package/src/scene/editorPlugins.ts +3 -3
- package/src/scene/gizmos.ts +154 -148
- package/src/server/db/marci/query.ts +1 -1
- package/src/server/host.ts +1 -1
- package/src/server/runtime.ts +1 -1
- package/src/ui/NativeView.ts +72 -25
- package/src/ui/UI.ts +3 -3
- package/src/ui/UIBottomSheet.ts +16 -17
- package/src/ui/UIButton.ts +54 -16
- package/src/ui/UIContainer.ts +0 -6
- package/src/ui/UIImage.ts +34 -30
- package/src/ui/UIInput.ts +29 -37
- package/src/ui/UIModal.ts +1 -3
- package/src/ui/UINode.ts +347 -297
- package/src/ui/UIPager.ts +93 -78
- package/src/ui/UIPopover.ts +0 -2
- package/src/ui/UIScreen.ts +41 -36
- package/src/ui/UIScrollable.ts +19 -13
- package/src/ui/UISpacer.ts +1 -1
- package/src/ui/UITabs.ts +8 -6
- package/src/ui/UIText.ts +7 -17
- package/src/ui/UIVideo.ts +30 -27
- package/src/ui/UIVirtualizedList.ts +58 -59
- package/src/ui/UIWidget.ts +38 -18
- package/src/ui/colorKeys.gen.ts +37 -0
- package/src/ui/fonts.ts +2 -2
- package/src/ui/presentable.ts +59 -42
- package/src/ui/router.ts +52 -35
- package/src/ui/styleColor.ts +56 -0
- package/src/ui/theme.ts +7 -6
- package/src/ui/transitions.ts +249 -0
- package/src/ui/tree.ts +346 -0
- package/src/version.ts +24 -0
- package/tests/helpers/engineWorld.ts +11 -0
- package/tests/helpers/fakeTree.ts +353 -0
- package/tests/helpers/hostStubs.ts +31 -0
- package/tests/helpers/index.ts +14 -0
- package/tests/helpers/memoryMarci.ts +124 -0
- package/tests/helpers/phases.ts +23 -0
- package/tests/helpers/preload.ts +18 -0
- package/tests/helpers/stubApp.ts +2 -0
- package/tests/helpers/stubDevice.ts +2 -0
- package/tests/helpers/stubFetch.ts +2 -0
- package/tests/helpers/stubInput.ts +2 -0
- package/dist/inject.js +0 -4730
- package/dist/types/core/registry.d.ts +0 -7
- package/dist/types/plugins/camera.d.ts +0 -25
- package/dist/types/plugins/push.d.ts +0 -46
- package/src/bridges.d.ts +0 -1760
- package/src/compile/__tests__/assetIconMacro.test.ts +0 -219
- package/src/compile/__tests__/assetMacro.test.ts +0 -100
- package/src/compile/__tests__/assetName.test.ts +0 -55
- package/src/compile/__tests__/compile.test.ts +0 -310
- package/src/compile/__tests__/detectEntry.test.ts +0 -151
- package/src/compile/__tests__/fontMacro.test.ts +0 -199
- package/src/compile/__tests__/serverSplit.test.ts +0 -27
- package/src/core/__tests__/stateMachine.test.ts +0 -132
- package/src/core/registry.ts +0 -23
- package/src/plugins/camera.ts +0 -81
- package/src/plugins/push.ts +0 -132
- package/src/plugins/qr.ts +0 -73
package/src/gl/Scene.ts
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
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
|
-
import { _bumpNavEpoch, _navEpoch,
|
|
10
|
+
import { _bumpNavEpoch, _navEpoch, _setCurrent, Presentable, type DestTuple, type DismissOptions, type PresentOptions } from "../ui/presentable"
|
|
11
|
+
import { _transitionId, TRANSITION_NONE } from "../ui/transitions"
|
|
12
|
+
import { TREE_DEST_SCENE3D, TREE_FLAG_BACK, ensureEmitter, registerDest, unregisterDest } from "../ui/tree"
|
|
11
13
|
import { device } from "../runtime/device"
|
|
12
14
|
import type { ClickEvent, TouchStartEvent } from "../runtime/touch"
|
|
13
15
|
import type { FetchResponse } from "../runtime/fetch"
|
|
@@ -17,7 +19,7 @@ import { SceneAudio } from "./audio/SceneAudio"
|
|
|
17
19
|
import { attachControls, type ControlsHandle, type ControlsOptions } from "./controls"
|
|
18
20
|
import { Material } from "./Material"
|
|
19
21
|
import { Texture } from "./Texture"
|
|
20
|
-
import { Node,
|
|
22
|
+
import { Node, nodes } from "./Node"
|
|
21
23
|
import { glState } from "./state"
|
|
22
24
|
import { registerTouchEndEvent, registerTouchStartEvent } from "./touch"
|
|
23
25
|
|
|
@@ -32,6 +34,21 @@ export type AmbientOcclusionOptions = {
|
|
|
32
34
|
quality?: "low" | "medium" | "high" | "ultra"
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
/** `SceneOptions.taa` / `scene.setAntialias("taa", 4, taa)`: the temporal anti-aliasing knobs. */
|
|
38
|
+
export type TaaOptions = {
|
|
39
|
+
/** Render the 3D at this fraction (0.5–1) and TAA-upscale it to the viewport; 1 = none. */
|
|
40
|
+
upscale?: number
|
|
41
|
+
/** The history's share, 0 (max smoothing) … 1 (none); 0.12 default. */
|
|
42
|
+
feedback?: number
|
|
43
|
+
/** Post-TAA sharpen 0–1; defaults to 0.5 with `upscale` below 1, else 0. */
|
|
44
|
+
sharpness?: number
|
|
45
|
+
/** A first-person viewmodel: everything closer than this many metres to the camera is treated as
|
|
46
|
+
* fixed on screen — its history is read at the same pixel, not reprojected by the camera's motion,
|
|
47
|
+
* which would trail the weapon on every turn (TAA has no per-object motion vectors). 1 covers arms
|
|
48
|
+
* and weapon at eye height; 0 (the default) = off. A wall walked into within the band smears a little. */
|
|
49
|
+
viewmodel?: number
|
|
50
|
+
}
|
|
51
|
+
|
|
35
52
|
export type FogOptions = {
|
|
36
53
|
/** A TINT on the in-scattered ambient, not an absolute colour: the engine multiplies it by the
|
|
37
54
|
* environment luminance, so white (the default) means "fog as bright as the ambient" and the fog
|
|
@@ -105,10 +122,18 @@ export type SceneOptions = {
|
|
|
105
122
|
* at every distance — the skybox included — so the sky itself takes the fog colour and the horizon
|
|
106
123
|
* blends on its own; `cutoff` opts geometry beyond a distance back out. */
|
|
107
124
|
fog?: FogOptions
|
|
108
|
-
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
|
|
125
|
+
/** Anti-aliasing: `true` = 4× MSAA, `2`/`4` = that many samples, `false` = off (FXAA takes over),
|
|
126
|
+
* `"taa"` = temporal anti-aliasing (Filament's TAA: one full-screen history blend instead of the
|
|
127
|
+
* multisampled buffers — cheaper than MSAA on an integrated GPU and it also calms specular and
|
|
128
|
+
* foliage shimmer; see `taa` for its knobs). Unset keeps the host default (desktop 4×, mobile/web
|
|
129
|
+
* off). The biggest single fill-rate cost after resolution — turn it down on big screens first. */
|
|
130
|
+
antialias?: boolean | 2 | 4 | "taa"
|
|
131
|
+
/** The TAA's knobs (`antialias: "taa"`): `upscale` = render the 3D at this fraction (0.5–1) and let
|
|
132
|
+
* TAA reconstruct the full resolution — the cheap way to a lower render cost with most of the
|
|
133
|
+
* detail kept (`renderScale` shrinks the buffer and stretches it instead); `feedback` = the
|
|
134
|
+
* history's share, 0.12 by default (raise it toward 0.3 if fast motion ghosts); `sharpness` =
|
|
135
|
+
* a post-TAA sharpen 0–1, useful with `upscale`. */
|
|
136
|
+
taa?: TaaOptions
|
|
112
137
|
/** Keep a stencil buffer for this scene (off by default: it costs memory and a clear per frame).
|
|
113
138
|
* Needed before any material's `stencil` test or write does anything. */
|
|
114
139
|
stencil?: boolean
|
|
@@ -214,7 +239,7 @@ export class Scene implements Presentable {
|
|
|
214
239
|
// `asset()` hands back "id:<n>" (already staged); a bare name is resolved on the spot; -1 is
|
|
215
240
|
// "no probe named", which leaves the host on the project-wide `ibl.ktx` (the old behaviour).
|
|
216
241
|
const src = typeof options.ibl === "string" ? options.ibl : undefined
|
|
217
|
-
const iblId = src === undefined ? -1 : src.startsWith("id:") ? Number(src.slice(3)) :
|
|
242
|
+
const iblId = src === undefined ? -1 : src.startsWith("id:") ? Number(src.slice(3)) : _creatorFetch.local(src)
|
|
218
243
|
_creator.setDefaultIbl(this._id, options.environmentIntensity ?? 20000, iblId)
|
|
219
244
|
}
|
|
220
245
|
this._environmentIntensity = options.ibl === false ? 0 : (options.environmentIntensity ?? 20000)
|
|
@@ -229,7 +254,7 @@ export class Scene implements Presentable {
|
|
|
229
254
|
if (options.antialias !== undefined) {
|
|
230
255
|
const a = options.antialias
|
|
231
256
|
// `false` must reach the host: desktop defaults to 4× MSAA, so an explicit off is a real change.
|
|
232
|
-
|
|
257
|
+
this.setAntialias(a, 4, options.taa)
|
|
233
258
|
}
|
|
234
259
|
if (options.stencil !== undefined) this.setStencil(options.stencil)
|
|
235
260
|
// Before anything else in this constructor that could load a texture: the value has to be in
|
|
@@ -252,9 +277,15 @@ export class Scene implements Presentable {
|
|
|
252
277
|
_creator.setSceneRenderOptions?.(this._id, renderScale, !!dynamicResolution, min)
|
|
253
278
|
}
|
|
254
279
|
|
|
255
|
-
/** Scene occlusion material
|
|
280
|
+
/** Scene occlusion material: an unlit instance in the occluder blue (the built-in `unlit`, which
|
|
281
|
+
* every runtime carries) — one per scene, made on first use. */
|
|
256
282
|
get occlusionMaterial(): Material {
|
|
257
|
-
if (!this._material)
|
|
283
|
+
if (!this._material) {
|
|
284
|
+
const h = _creator.builtinMaterial("unlit")
|
|
285
|
+
if (!h) throw new Error("Scene.occlusionMaterial: this host carries no unlit material")
|
|
286
|
+
this._material = new Material(h)
|
|
287
|
+
this._material.uniforms.baseColor = [0.1, 0.25, 0.57, 1.0]
|
|
288
|
+
}
|
|
258
289
|
return this._material
|
|
259
290
|
}
|
|
260
291
|
|
|
@@ -263,7 +294,7 @@ export class Scene implements Presentable {
|
|
|
263
294
|
if (typeof sky === "object" && sky !== null && "texture" in sky) {
|
|
264
295
|
const src = sky.texture
|
|
265
296
|
// `asset()` hands back "id:<n>" (already staged); a bare name is resolved on the spot.
|
|
266
|
-
const id = src.startsWith("id:") ? Number(src.slice(3)) :
|
|
297
|
+
const id = src.startsWith("id:") ? Number(src.slice(3)) : _creatorFetch.local(src)
|
|
267
298
|
_creator.setSkyboxTexture?.(this._id, id)
|
|
268
299
|
return
|
|
269
300
|
}
|
|
@@ -290,8 +321,19 @@ export class Scene implements Presentable {
|
|
|
290
321
|
setMaterialGlobalParameter(i: number, x: number, y: number, z: number, w: number): void {
|
|
291
322
|
_creator.setMaterialGlobalParameter(this._id, i, x, y, z, w)
|
|
292
323
|
}
|
|
293
|
-
setAntialias(
|
|
294
|
-
|
|
324
|
+
setAntialias(mode: boolean | 2 | 4 | "taa", scale = 4, taa?: TaaOptions): void {
|
|
325
|
+
if (mode === "taa") {
|
|
326
|
+
if (!_creator.setTemporalAntiAliasing) { // an older host: the nearest thing it has
|
|
327
|
+
_creator.setSceneMultiSampleAntiAliasing(this._id, true, scale)
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
const up = taa?.upscale ?? 1
|
|
331
|
+
_creator.setTemporalAntiAliasing(this._id, true, taa?.feedback ?? 0.12, taa?.sharpness ?? (up < 1 ? 0.5 : 0), up < 0.5 ? 0.5 : up > 1 ? 1 : up, taa?.viewmodel ?? 0)
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
// `false` must reach the host: desktop defaults to 4× MSAA, so an explicit off is a real change.
|
|
335
|
+
_creator.setTemporalAntiAliasing?.(this._id, false, 0.12, 0, 1, 0)
|
|
336
|
+
_creator.setSceneMultiSampleAntiAliasing(this._id, mode !== false, typeof mode === "number" ? mode : scale)
|
|
295
337
|
}
|
|
296
338
|
/** Depth-reading effects on / off (a graphics-settings menu): soft particles and projected decals
|
|
297
339
|
* read the scene depth, which costs a depth pre-pass of every opaque draw (~12 % of a fill-bound
|
|
@@ -336,12 +378,15 @@ export class Scene implements Presentable {
|
|
|
336
378
|
else if (enabled) console.warn("[scene] this host has no setSceneStencil — stencil ignored")
|
|
337
379
|
}
|
|
338
380
|
|
|
339
|
-
add(...
|
|
340
|
-
for (const n of
|
|
381
|
+
add(...list: Node[]): this {
|
|
382
|
+
for (const n of list) { _creator.addEntityToScene(this._id, n.id); nodes.pin(n) } // the scene owns it now
|
|
341
383
|
return this
|
|
342
384
|
}
|
|
343
|
-
remove(...
|
|
344
|
-
for (const n of
|
|
385
|
+
remove(...list: Node[]): this {
|
|
386
|
+
for (const n of list) {
|
|
387
|
+
_creator.removeEntityFromScene(this._id, n.id)
|
|
388
|
+
if (!_creator.entityIsOwned(n.id)) nodes.unpin(n.id) // loose again: its handle owns it
|
|
389
|
+
}
|
|
345
390
|
return this
|
|
346
391
|
}
|
|
347
392
|
|
|
@@ -422,39 +467,47 @@ export class Scene implements Presentable {
|
|
|
422
467
|
this.cl.push(callback)
|
|
423
468
|
return this
|
|
424
469
|
}
|
|
425
|
-
|
|
470
|
+
/** Hardware/system back while the scene is current. */
|
|
471
|
+
onBack(callback: () => void): this {
|
|
426
472
|
this._backButtonCallback = callback
|
|
427
473
|
return this
|
|
428
474
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
475
|
+
/** @deprecated Renamed `onBack` (2026-09-26). An alias for the projects compiled before the
|
|
476
|
+
* rename; removed with the release that recompiles them. */
|
|
477
|
+
onBackPressed(callback: () => void): this { return this.onBack(callback) }
|
|
478
|
+
|
|
479
|
+
/** @internal The destination tuple (ui/presentable.ts). */
|
|
480
|
+
_dest(): DestTuple {
|
|
481
|
+
// Addressed by the router / an attached widget: make sure the (kind, id) resolves back to us.
|
|
482
|
+
registerDest(TREE_DEST_SCENE3D, this._id, this)
|
|
483
|
+
return [TREE_DEST_SCENE3D, this._id, "", ""]
|
|
484
|
+
}
|
|
485
|
+
/** @internal presentation lifecycle from the runtime */
|
|
486
|
+
_emitOpen(): void { for (const cb of this.ol.slice()) cb() }
|
|
487
|
+
/** @internal */
|
|
488
|
+
_emitClose(): void { for (const cb of this.cl.slice()) cb() }
|
|
489
|
+
/** @internal */
|
|
490
|
+
_emitBack(): boolean {
|
|
491
|
+
if (!this._backButtonCallback) return false
|
|
492
|
+
this._backButtonCallback()
|
|
493
|
+
return true
|
|
433
494
|
}
|
|
434
495
|
|
|
435
496
|
/** Make this the active scene — shows it as the current destination (replaces a screen /
|
|
436
|
-
* another scene; only the active scene renders).
|
|
497
|
+
* another scene; only the active scene renders). The runtime activates the engine as part of
|
|
498
|
+
* presenting the destination. */
|
|
437
499
|
open(options?: PresentOptions): void | Promise<void> {
|
|
438
500
|
_bumpNavEpoch()
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
} else {
|
|
443
|
-
_creator.openScene(this._id)
|
|
444
|
-
for (const cb of this.ol) cb()
|
|
445
|
-
}
|
|
501
|
+
ensureEmitter()
|
|
502
|
+
registerDest(TREE_DEST_SCENE3D, this._id, this)
|
|
503
|
+
_creatorTree.open(TREE_DEST_SCENE3D, this._id, "", "", _transitionId(options?.transition, TRANSITION_NONE))
|
|
446
504
|
Scene._active = this
|
|
447
505
|
glState.activeScene = this
|
|
448
506
|
_setCurrent(this)
|
|
449
507
|
}
|
|
450
|
-
close(): void {
|
|
508
|
+
close(options?: DismissOptions): void {
|
|
451
509
|
_bumpNavEpoch()
|
|
452
|
-
|
|
453
|
-
_creatorUI.closeView!()
|
|
454
|
-
} else {
|
|
455
|
-
_creator.closeScene()
|
|
456
|
-
for (const cb of this.cl) cb()
|
|
457
|
-
}
|
|
510
|
+
_creatorTree.close(TREE_DEST_SCENE3D, this._id, _transitionId(options?.transition, TRANSITION_NONE))
|
|
458
511
|
if (Scene._active === this) Scene._active = null
|
|
459
512
|
if (glState.activeScene === this) glState.activeScene = null
|
|
460
513
|
if (Presentable.current === this) _setCurrent(null)
|
|
@@ -481,7 +534,7 @@ export class Scene implements Presentable {
|
|
|
481
534
|
export type ARMode = "default" | "markers" | "arcore" | "detached"
|
|
482
535
|
|
|
483
536
|
const onTrackStateChange = (entityId: number, track: boolean): void => {
|
|
484
|
-
const node =
|
|
537
|
+
const node = nodes.get(entityId)
|
|
485
538
|
if (!node) return
|
|
486
539
|
node.isTracked = track
|
|
487
540
|
if (track) node._dispatchEvent("track")
|
|
@@ -508,7 +561,7 @@ export class ARScene<T extends ARMode = "default"> extends Scene {
|
|
|
508
561
|
|
|
509
562
|
/** Create an anchor from a tracking source (e.g. an image target). */
|
|
510
563
|
createAnchor(source: FetchResponse, physicalWidth = 0.2): Node {
|
|
511
|
-
return new Node(_creator.createAnchor(this._id, physicalWidth, (source as any).
|
|
564
|
+
return new Node(_creator.createAnchor(this._id, physicalWidth, (source as any)._h.id))
|
|
512
565
|
}
|
|
513
566
|
|
|
514
567
|
/**
|
|
@@ -543,9 +596,12 @@ export class ARScene<T extends ARMode = "default"> extends Scene {
|
|
|
543
596
|
}
|
|
544
597
|
}
|
|
545
598
|
|
|
546
|
-
override close(): void {
|
|
547
|
-
|
|
548
|
-
|
|
599
|
+
override close(options?: DismissOptions): void {
|
|
600
|
+
// The session is this scene's unless another ARScene is on screen now: a late close of a scene
|
|
601
|
+
// something already replaced must not stop the next one's session.
|
|
602
|
+
const current = Presentable.current
|
|
603
|
+
if (!(current instanceof ARScene) || current === this) _creator.stopAR()
|
|
604
|
+
super.close(options)
|
|
549
605
|
}
|
|
550
606
|
}
|
|
551
607
|
|
|
@@ -565,8 +621,8 @@ export class VRScene extends Scene {
|
|
|
565
621
|
super.open(options)
|
|
566
622
|
}
|
|
567
623
|
|
|
568
|
-
override close(): void {
|
|
624
|
+
override close(options?: DismissOptions): void {
|
|
569
625
|
_creator.stopVR()
|
|
570
|
-
super.close()
|
|
626
|
+
super.close(options)
|
|
571
627
|
}
|
|
572
628
|
}
|
package/src/gl/Texture.ts
CHANGED
|
@@ -37,8 +37,9 @@ export class Texture {
|
|
|
37
37
|
Texture._maxSize = size > 0 ? Math.floor(size) : 0
|
|
38
38
|
_creator.setTextureMaxSize?.(Texture._maxSize)
|
|
39
39
|
}
|
|
40
|
-
/** @internal
|
|
41
|
-
|
|
40
|
+
/** @internal The texture's owned handle (`_h.id` = the engine id). A texture the engine or another
|
|
41
|
+
* object owns (a canvas's, a video player's) is wrapped from its number and never freed by us. */
|
|
42
|
+
readonly _h: Handle
|
|
42
43
|
readonly width: number
|
|
43
44
|
readonly height: number
|
|
44
45
|
|
|
@@ -47,10 +48,10 @@ export class Texture {
|
|
|
47
48
|
/** Vertical wrap mode (V). */
|
|
48
49
|
wrapT = 1
|
|
49
50
|
|
|
50
|
-
constructor(width: number, height: number, id: number) {
|
|
51
|
+
constructor(width: number, height: number, id: Handle | number) {
|
|
51
52
|
this.width = width
|
|
52
53
|
this.height = height
|
|
53
|
-
this.
|
|
54
|
+
this._h = typeof id === "number" ? { id } : id
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
// Wrap a Canvas's baked surface as a 3D texture (RGBA8, already rasterized → synchronous, no decode).
|
|
@@ -72,13 +73,13 @@ export class Texture {
|
|
|
72
73
|
const channels = opts.channels ?? 4
|
|
73
74
|
if (data.length < width * height * channels) throw new Error(`Texture.fromPixels: ${data.length} bytes for ${width}×${height}×${channels}`)
|
|
74
75
|
const id = _creator.createTexturePixels(width, height, channels, data, opts.srgb ?? true)
|
|
75
|
-
if (id === 0xFFFFFFFF) throw new Error("Texture.fromPixels: the host refused the texture")
|
|
76
|
+
if (id.id === 0xFFFFFFFF) throw new Error("Texture.fromPixels: the host refused the texture")
|
|
76
77
|
return new Texture(width, height, id)
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
/** Re-upload a rectangle of a `fromPixels` texture (same channel count). */
|
|
80
81
|
update(x: number, y: number, width: number, height: number, data: Uint8Array): void {
|
|
81
|
-
_creator.updateTexturePixels?.(this.
|
|
82
|
+
_creator.updateTexturePixels?.(this._h.id, x, y, width, height, data)
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
/** Decode an image (PNG / JPG, or a KTX2 the core transcodes) into a texture. `srgb` (default
|
|
@@ -97,7 +98,7 @@ export class Texture {
|
|
|
97
98
|
}
|
|
98
99
|
const flags = (options.srgb === false ? TEXTURE_FLAG_LINEAR : 0) | (options.fullSize ? TEXTURE_FLAG_FULL_SIZE : 0)
|
|
99
100
|
return new Promise<Texture>((resolve, reject) => {
|
|
100
|
-
_creator.createTexture((source as any).
|
|
101
|
+
_creator.createTexture((source as any)._h.id, (id, w, h) => resolve(new Texture(w, h, id)), reject, flags)
|
|
101
102
|
})
|
|
102
103
|
}
|
|
103
104
|
}
|
|
@@ -270,7 +270,7 @@ export class AnimationClip {
|
|
|
270
270
|
})
|
|
271
271
|
}
|
|
272
272
|
return new Promise<[number, ClipInfo[]]>((resolve, reject) => {
|
|
273
|
-
_creator.loadClips((source as unknown as {
|
|
273
|
+
_creator.loadClips((source as unknown as { _h: Handle })._h.id, (setId: number) => {
|
|
274
274
|
if (setId === 0) { reject(new Error("AnimationClip.load: the file has no animations (or is not a GLB)")); return }
|
|
275
275
|
resolve([setId, readInfo(setId)])
|
|
276
276
|
}, reject)
|
|
@@ -37,8 +37,9 @@ import type { Node } from "../Node"
|
|
|
37
37
|
|
|
38
38
|
/** How fast the character wants to go: the gait picks its clips and its speed. */
|
|
39
39
|
export type Gait = "walk" | "run" | "sprint"
|
|
40
|
-
/** What the engine shows: the idle, a start, the gait loop, a turn while moving, a stop,
|
|
41
|
-
|
|
40
|
+
/** What the engine shows: the idle, a start, the gait loop, a turn while moving, a stop, a turn on the spot —
|
|
41
|
+
* an ACTION (`act()`): a one-shot that owns the body until it hands over — or the STRAFE blend (`move({ strafe })`). */
|
|
42
|
+
export type LocomotionState = "idle" | "start" | "move" | "turn" | "stop" | "spin" | "action" | "strafe"
|
|
42
43
|
/** Who moves the body:
|
|
43
44
|
* `'hybrid'` (the default) — the clip's own recorded motion moves it; while the gait loop shows it
|
|
44
45
|
* is ADJUSTED toward what the simulation wants by at most `tuning.adjust` metres a second, and a
|
|
@@ -70,6 +71,10 @@ export type LocomotionSet = {
|
|
|
70
71
|
turns?: LocomotionClip[]
|
|
71
72
|
/** turns on the spot — what a facing still owed by a standing body plays */
|
|
72
73
|
spins?: LocomotionClip[]
|
|
74
|
+
/** the STRAFE set: the same gait recorded forward, backward, to either side and on the diagonals — the members of
|
|
75
|
+
* the directional blend `move(dir, { face, strafe: true })` shows. `angle` = the way a take travels in the body's
|
|
76
|
+
* frame (degrees, + = left); left out, the engine reads it off the take's root. */
|
|
77
|
+
strafes?: LocomotionClip[]
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
/** The tuning. Times are seconds, speeds m/s, angles degrees. */
|
|
@@ -201,7 +206,7 @@ export type StopCandidate = {
|
|
|
201
206
|
|
|
202
207
|
const STOP_ROW = 9
|
|
203
208
|
const STOP_ROWS = 8
|
|
204
|
-
const IN = { DIR_X: 0, DIR_Z: 1, MAG: 2, FACE_X: 3, FACE_Z: 4, GAIT: 5,
|
|
209
|
+
const IN = { DIR_X: 0, DIR_Z: 1, MAG: 2, FACE_X: 3, FACE_Z: 4, GAIT: 5, ACT_SEQ: 6, ACT_SLOT: 7, ACT_DIR_X: 8, ACT_DIR_Z: 9, ACT_ANGLE: 10, ACT_TURN: 11, ACT_AT: 12, ACT_EXIT: 13, ACT_FADE: 14, ACT_RATE: 15, ACT_OUT: 16, ACT_TRAVEL: 17, ACT_TRAVEL_AT: 18, ACT_TRAVEL_AFTER: 19, STRAFE: 20, ACT_DASH: 21, ACT_DASH_TIME: 22, COUNT: 23 }
|
|
205
210
|
const OUT = { STATE: 0, SPEED: 1, VEL_X: 2, VEL_Y: 3, VEL_Z: 4, YAW: 5, YAW_RATE: 6, PHASE: 7, SEQ: 8, PRED: 9, YAW_LEFT: 21, CLIP_TIME: 22, EXIT_AT: 23, STOP_OFF: 24, STOP_STEPS: 25, STOP_WAIT: 26, COUNT: 27 }
|
|
206
211
|
const P = {
|
|
207
212
|
HALFLIFE_WALK: 0, HALFLIFE_RUN: 1, HALFLIFE_FACING: 2, SPEED_WALK: 3, SPEED_RUN: 4, SPEED_SPRINT: 5,
|
|
@@ -210,8 +215,8 @@ const P = {
|
|
|
210
215
|
HALFLIFE_STOP: 20, ADJUST: 21, YAW_WARP: 22, EXIT_TOL: 23, RESUME_SPEED: 24, EXIT_STEPS: 25, STOP_EARLY: 26, STOP_STEPS: 27, STOP_FIT: 28, STOP_POSE: 29,
|
|
211
216
|
STRIDE_SHARE: 30, STRIDE_MIN: 31, STRIDE_MAX: 32, WARP_STRIDE: 33, WARP_RATE: 34, COUNT: 35,
|
|
212
217
|
}
|
|
213
|
-
const KIND = { IDLE: 0, GAIT: 1, START: 2, STOP: 3, TURN: 4, MATCH: 5, SPIN: 6 }
|
|
214
|
-
const STATES: LocomotionState[] = [ "idle", "start", "move", "turn", "stop", "spin" ]
|
|
218
|
+
const KIND = { IDLE: 0, GAIT: 1, START: 2, STOP: 3, TURN: 4, MATCH: 5, SPIN: 6, STRAFE: 8 }
|
|
219
|
+
const STATES: LocomotionState[] = [ "idle", "start", "move", "turn", "stop", "spin", "action", "strafe" ]
|
|
215
220
|
const GAITS: Gait[] = [ "walk", "run", "sprint" ]
|
|
216
221
|
const RAD2DEG = 180 / Math.PI
|
|
217
222
|
|
|
@@ -249,6 +254,8 @@ export class Locomotion extends Aspect<"loco", Node, LocomotionEvents> {
|
|
|
249
254
|
private _mag = 0
|
|
250
255
|
private _gait: Gait = "walk"
|
|
251
256
|
private _seq = -1
|
|
257
|
+
private _act = 0
|
|
258
|
+
private _strafe = false
|
|
252
259
|
private readonly _in = new Float32Array(IN.COUNT)
|
|
253
260
|
private readonly _out = new Float32Array(OUT.COUNT)
|
|
254
261
|
private readonly _names = new Map<number, string>()
|
|
@@ -290,6 +297,7 @@ export class Locomotion extends Aspect<"loco", Node, LocomotionEvents> {
|
|
|
290
297
|
for (const c of this.set.stops ?? []) this.enter(c, KIND.STOP)
|
|
291
298
|
for (const c of this.set.turns ?? []) this.enter(c, KIND.TURN)
|
|
292
299
|
for (const c of this.set.spins ?? []) this.enter(c, KIND.SPIN)
|
|
300
|
+
for (const c of this.set.strafes ?? []) this.enter(c, KIND.STRAFE)
|
|
293
301
|
if (this.warp !== undefined) anim.warp.set(this.warp)
|
|
294
302
|
anim.feet.set(this.feet === undefined ? { lock: true } : this.feet === true ? { lock: true, ik: true } : this.feet === false ? { lock: false, ik: false } : this.feet)
|
|
295
303
|
this.pushTuning()
|
|
@@ -306,7 +314,8 @@ export class Locomotion extends Aspect<"loco", Node, LocomotionEvents> {
|
|
|
306
314
|
this._names.set(slot, n)
|
|
307
315
|
const o = typeof clip === "string" ? undefined : clip
|
|
308
316
|
const measured = anim.clipInfo(n)
|
|
309
|
-
|
|
317
|
+
// (a strafe member's angle is the way it TRAVELS, which the engine reads off its root: 1000 = measure)
|
|
318
|
+
const angle = o?.angle ?? (kind === KIND.STRAFE ? 1000 : measured ? measured.turn * RAD2DEG : 0)
|
|
310
319
|
const gait = kind === KIND.GAIT ? -1 : o?.gait ? GAITS.indexOf(o.gait) : -1
|
|
311
320
|
_creator.locoSetEntry(this._loco, slot, kind, angle, o?.speed ?? 0, gait)
|
|
312
321
|
}
|
|
@@ -379,8 +388,11 @@ export class Locomotion extends Aspect<"loco", Node, LocomotionEvents> {
|
|
|
379
388
|
/** State the movement intent for this frame: a world direction (its length is the stick's pull,
|
|
380
389
|
* clamped to 1) and the gait. `null` = no movement — the body stops, but it keeps facing where it
|
|
381
390
|
* was last asked to, and turns there on the spot if it still owes the turn.
|
|
382
|
-
* `face` states a facing of its own (aiming,
|
|
383
|
-
|
|
391
|
+
* `face` states a facing of its own (aiming, a camera-relative shooter) — and with `strafe: true` the body MOVES
|
|
392
|
+
* where the direction points while it faces there: the loop becomes the directional blend of `set.strafes` (a
|
|
393
|
+
* guard, a lock-on), the facing is steered standing too, state `'strafe'`. Stated every frame like the rest. */
|
|
394
|
+
move(direction: Vec3Like | null | undefined, options: { gait?: Gait, face?: Vec3Like, strafe?: boolean } = {}): void {
|
|
395
|
+
this._strafe = !!options.strafe
|
|
384
396
|
const dx = direction ? cx(direction) : 0, dz = direction ? cz(direction) : 0
|
|
385
397
|
const len = Math.hypot(dx, dz)
|
|
386
398
|
if (len > 1e-4) {
|
|
@@ -395,6 +407,62 @@ export class Locomotion extends Aspect<"loco", Node, LocomotionEvents> {
|
|
|
395
407
|
if (options.gait) this._gait = options.gait
|
|
396
408
|
}
|
|
397
409
|
|
|
410
|
+
/** Play an ACTION: a one-shot that OWNS the body the way a start or a stop does — a roll, a backstep, a lunging
|
|
411
|
+
* attack. Its recording moves the body alone (in every displacement mode but `'data'`, where the host does),
|
|
412
|
+
* the steering and the selector stand by, and when it hands over the usual rules carry on: a direction held
|
|
413
|
+
* starts (or, still carrying speed, runs on), nothing held stands.
|
|
414
|
+
*
|
|
415
|
+
* `direction` is where its TRAVEL must go, world (`Vec3`, or a heading in degrees, 0 = +Z, + = toward +X). A
|
|
416
|
+
* clip travels some way relative to the body — forward, left, back; measured off its own root, or `angle`
|
|
417
|
+
* (degrees, + = left) — and the body is turned by what is left between that and `direction`, over `turn`
|
|
418
|
+
* seconds (default 0.12), so the travel lands exactly where it was pointed. Which clip to play is yours to
|
|
419
|
+
* pick: the one whose travel is nearest the direction IN THE BODY'S FRAME (`direction − facing`) leaves the
|
|
420
|
+
* least to turn. Left out = it plays where the body faces.
|
|
421
|
+
*
|
|
422
|
+
* `at` enters the clip that many seconds in (a wind-up skipped), `rate` plays it faster or slower (its metres
|
|
423
|
+
* stay, the time changes), `travel` takes only that share of its recorded travel (an attack aimed at a target:
|
|
424
|
+
* 0.3 to stop at the blade's length from one that is close, 1.3 to reach one a step too far — the pose is
|
|
425
|
+
* untouched, so keep it to a fast step; `{ share, at, after }` takes another share from clip time `at` on: a
|
|
426
|
+
* swing's take travels on after its cut, into a body that stands at the blade's length), `dash` THROWS the body
|
|
427
|
+
* `distance` metres along the action's direction on top of that, inside `time` seconds (default 0.12) from its first
|
|
428
|
+
* step, eased out — the gap to a target closed in the wind-up, the blow struck from where it lands (scaling a take's
|
|
429
|
+
* own step is a slide stretched over the whole swing), `fade` is the transition into it and `out` the one out of it (default: the tuning's
|
|
430
|
+
* `blend`; an action ends in ITS stance, not the idle's, and a longer way out is what hides that), and `exit` is the
|
|
431
|
+
* clip time from which a MOVE intent may take over — the cancel window: along the way the body faces at once, a
|
|
432
|
+
* heading that needs a turn as soon as the body has slowed to where a start can answer; with nothing asked for it plays out, to
|
|
433
|
+
* its end (an action ends in its own recovery, at rest), and the body then STANDS THE WAY THE ACTION LEFT IT:
|
|
434
|
+
* the facing intent that outlives a released key is not a demand after an action — no turn on the spot.
|
|
435
|
+
* WHEN an action may be asked for (out of another one, out of a start) is the caller's rule: this cuts into
|
|
436
|
+
* whatever shows. `state` reads `'action'` while it does. False = no such clip. */
|
|
437
|
+
act(clip: string, options: { direction?: Vec3Like | number, angle?: number, turn?: number, at?: number, exit?: number, fade?: number, out?: number, rate?: number, travel?: number | { share: number, at: number, after: number }, dash?: { distance: number, time?: number } } = {}): boolean {
|
|
438
|
+
const anim = this.anim
|
|
439
|
+
if (!anim || !this._loco) return false
|
|
440
|
+
const slot = anim._slot(clip)
|
|
441
|
+
if (slot < 0) { console.warn(`Locomotion: no clip '${clip}' on ${this.node.name || "the character"}`); return false }
|
|
442
|
+
const i = this._in
|
|
443
|
+
let dx = 0, dz = 0
|
|
444
|
+
const d = options.direction
|
|
445
|
+
if (typeof d === "number") { dx = Math.sin(d / RAD2DEG); dz = Math.cos(d / RAD2DEG) }
|
|
446
|
+
else if (d) { dx = cx(d); dz = cz(d) }
|
|
447
|
+
i[IN.ACT_SEQ] = ++this._act
|
|
448
|
+
i[IN.ACT_SLOT] = slot
|
|
449
|
+
i[IN.ACT_DIR_X] = dx; i[IN.ACT_DIR_Z] = dz
|
|
450
|
+
i[IN.ACT_ANGLE] = options.angle ?? 1000 // 1000 = measure the clip's own travel
|
|
451
|
+
i[IN.ACT_TURN] = options.turn ?? 0.12
|
|
452
|
+
i[IN.ACT_AT] = options.at ?? 0
|
|
453
|
+
i[IN.ACT_EXIT] = options.exit ?? 0
|
|
454
|
+
i[IN.ACT_FADE] = options.fade ?? 0
|
|
455
|
+
i[IN.ACT_RATE] = options.rate ?? 0
|
|
456
|
+
i[IN.ACT_OUT] = options.out ?? 0
|
|
457
|
+
const tr = options.travel
|
|
458
|
+
i[IN.ACT_TRAVEL] = typeof tr === "number" ? tr : tr?.share ?? 0
|
|
459
|
+
i[IN.ACT_TRAVEL_AT] = typeof tr === "object" ? tr.at : 0
|
|
460
|
+
i[IN.ACT_TRAVEL_AFTER] = typeof tr === "object" ? tr.after : 0
|
|
461
|
+
i[IN.ACT_DASH] = options.dash?.distance ?? 0
|
|
462
|
+
i[IN.ACT_DASH_TIME] = options.dash?.time ?? 0.12
|
|
463
|
+
return true
|
|
464
|
+
}
|
|
465
|
+
|
|
398
466
|
/** Face this way without moving — the facing intent on its own (`Vec3`, or a heading in degrees,
|
|
399
467
|
* 0 = +Z, + = toward +X). A standing body turns to it on the spot. */
|
|
400
468
|
face(direction: Vec3Like | number): void {
|
|
@@ -477,6 +545,7 @@ export class Locomotion extends Aspect<"loco", Node, LocomotionEvents> {
|
|
|
477
545
|
i[IN.DIR_X] = this._dir.x; i[IN.DIR_Z] = this._dir.z; i[IN.MAG] = this._mag
|
|
478
546
|
i[IN.FACE_X] = this._face.x; i[IN.FACE_Z] = this._face.z
|
|
479
547
|
i[IN.GAIT] = Math.max(0, GAITS.indexOf(this._gait))
|
|
548
|
+
i[IN.STRAFE] = this._strafe ? 1 : 0
|
|
480
549
|
_creator.locoSetInput(this._loco, i)
|
|
481
550
|
_creator.locoUpdate(this._loco, dt)
|
|
482
551
|
_creator.locoRead(this._loco, this._out)
|
package/src/gl/nav/NavMesh.ts
CHANGED
|
@@ -185,8 +185,7 @@ export class NavMesh {
|
|
|
185
185
|
try {
|
|
186
186
|
const resp = await fetch(opts.data, { useOnce: true })
|
|
187
187
|
if (resp.status >= 400) { console.warn(`NavMesh: no navmesh at ${opts.data} (HTTP ${resp.status}) — run \`lecodes navmesh bake\``); return null }
|
|
188
|
-
id = _creatorNav.load((resp as unknown as {
|
|
189
|
-
resp.dispose()
|
|
188
|
+
id = _creatorNav.load((resp as unknown as { _h: Handle })._h.id)
|
|
190
189
|
} catch (e) {
|
|
191
190
|
console.warn(`NavMesh: could not read ${opts.data}: ${String(e)}`)
|
|
192
191
|
return null
|
|
@@ -275,7 +274,7 @@ export class NavMesh {
|
|
|
275
274
|
const e = opts?.extents
|
|
276
275
|
const max = Math.min(opts?.maxCorners ?? 256, 256)
|
|
277
276
|
const n = _creatorNav.findPath(this.id, cx(from), cy(from), cz(from), cx(to), cy(to), cz(to),
|
|
278
|
-
e ? cx(e) : 0, e ? cy(e) : 0, e ? cz(e) : 0, inc, exc, max, pathScratch)
|
|
277
|
+
e ? cx(e) : 0, e ? cy(e) : 0, e ? cz(e) : 0, inc, exc, max < 256 ? pathScratch.subarray(0, max * 3) : pathScratch)
|
|
279
278
|
const count = Math.abs(n)
|
|
280
279
|
for (let i = 0; i < count; i++) path.push(new Vec3(pathScratch[i * 3]!, pathScratch[i * 3 + 1]!, pathScratch[i * 3 + 2]!))
|
|
281
280
|
path.partial = n < 0
|
|
@@ -370,7 +369,7 @@ export class NavMesh {
|
|
|
370
369
|
if (NavMesh.baked) { console.log("[navmesh] a second NavMesh.load — skipped (one bake per run; use --entry per scene)"); return }
|
|
371
370
|
NavMesh.baked = true
|
|
372
371
|
if (!_creator.physicsStaticGeometry) {
|
|
373
|
-
console.error("[navmesh] error: this host cannot dump collision geometry (build the desktop host from
|
|
372
|
+
console.error("[navmesh] error: this host cannot dump collision geometry (build the desktop host from hosts/desktop, or update: lecodes desktop update)")
|
|
374
373
|
return
|
|
375
374
|
}
|
|
376
375
|
const areaIds = NavMesh.areaIds(opts.areas)
|
|
@@ -15,7 +15,7 @@ import { Aspect } from "../../core/Aspect"
|
|
|
15
15
|
import type { FieldMeta } from "../../core/fields"
|
|
16
16
|
import type { CompAxis, CompWriter } from "../../core/compWrite"
|
|
17
17
|
import { Vec3, cx, cy, cz, type Vec3Like } from "../../math/vec"
|
|
18
|
-
import {
|
|
18
|
+
import { nodes, type Node } from "../Node"
|
|
19
19
|
import { Shape } from "./Shape"
|
|
20
20
|
|
|
21
21
|
export type MotionType = "static" | "dynamic" | "kinematic"
|
|
@@ -220,7 +220,7 @@ export class Physics extends Aspect<"physics", Node> implements CompWriter {
|
|
|
220
220
|
const id = _creator.physicsRaycast(cx(origin), cy(origin), cz(origin), cx(dir), cy(dir), cz(dir), maxDist, out)
|
|
221
221
|
if (!id) return null
|
|
222
222
|
return {
|
|
223
|
-
node:
|
|
223
|
+
node: nodes.get(id) ?? null,
|
|
224
224
|
point: new Vec3(out[0], out[1], out[2]),
|
|
225
225
|
normal: new Vec3(out[3], out[4], out[5]),
|
|
226
226
|
fraction: out[6],
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// is delivered to BOTH nodes with the other as the argument. type: 0 = enter (contact/overlap began),
|
|
3
3
|
// 1 = exit (ended). Registered lazily on the first trigger/physics body or 'enter'/'exit' listener.
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { nodes } from "../Node"
|
|
6
6
|
|
|
7
7
|
let registered = false
|
|
8
8
|
export const ensurePhysicsEvents = (): void => {
|
|
@@ -10,8 +10,8 @@ export const ensurePhysicsEvents = (): void => {
|
|
|
10
10
|
if (!_creator.setOnPhysicsEvent) return // physics-less build / bridge without the hook
|
|
11
11
|
registered = true
|
|
12
12
|
_creator.setOnPhysicsEvent((a, b, type) => {
|
|
13
|
-
const na =
|
|
14
|
-
const nb =
|
|
13
|
+
const na = nodes.get(a)
|
|
14
|
+
const nb = nodes.get(b)
|
|
15
15
|
if (!na || !nb) return
|
|
16
16
|
const channel = type === 1 ? "exit" : "enter"
|
|
17
17
|
na._emitCollision(channel, nb)
|
|
@@ -236,9 +236,8 @@ export class Terrain {
|
|
|
236
236
|
static async load(url: string, opts: Omit<TerrainOptions, "size" | "sizeX" | "sizeZ" | "heights" | "holes" | "control"> = {}): Promise<Terrain> {
|
|
237
237
|
const resp = await fetch(url, { useOnce: true })
|
|
238
238
|
if (resp.status >= 400) throw new Error(`Terrain: could not fetch ${url} (HTTP ${resp.status})`)
|
|
239
|
-
if (!
|
|
240
|
-
const bytes =
|
|
241
|
-
resp.dispose()
|
|
239
|
+
if (!_creatorFetch.toBytes) { throw new Error("Terrain.load needs a newer host (no binary fetch reader)") }
|
|
240
|
+
const bytes = _creatorFetch.toBytes((resp as unknown as { _h: Handle })._h.id)
|
|
242
241
|
const data = Terrain.parse(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer)
|
|
243
242
|
const layers = opts.layers ?? data.layers
|
|
244
243
|
return new Terrain({ ...opts, ...data, layers, cellSize: opts.cellSize ?? data.cellSize, chunk: opts.chunk ?? data.chunk })
|
|
@@ -259,6 +258,35 @@ export class Terrain {
|
|
|
259
258
|
return new Vec3(n[0], n[1], n[2])
|
|
260
259
|
}
|
|
261
260
|
|
|
261
|
+
/** The LAYER WEIGHTS at a local (x, z) — what the splat shader blends there: the control map read bilinearly
|
|
262
|
+
* between its samples and normalised to sum 1 (`[1, 0, 0, 0]` where the map is empty, as the shader has
|
|
263
|
+
* it). Index = the layer's in `layers`. Outside the grid the edge answers. This is how a game asks WHAT the
|
|
264
|
+
* ground is under a point — footprints in the sand and none on the cobble, a footstep sound per layer, dust
|
|
265
|
+
* by surface — and the blend is already in the numbers, so a transition is a fade and not a line. */
|
|
266
|
+
weightsAt(x: number, z: number): [number, number, number, number] {
|
|
267
|
+
const fx = Math.min(this.sizeX - 1, Math.max(0, x / this.cellSize)), fz = Math.min(this.sizeZ - 1, Math.max(0, z / this.cellSize))
|
|
268
|
+
const x0 = Math.floor(fx), z0 = Math.floor(fz), x1 = Math.min(this.sizeX - 1, x0 + 1), z1 = Math.min(this.sizeZ - 1, z0 + 1)
|
|
269
|
+
const u = fx - x0, v = fz - z0
|
|
270
|
+
const c = this.control
|
|
271
|
+
const w: [number, number, number, number] = [ 0, 0, 0, 0 ]
|
|
272
|
+
for (let k = 0; k < 4; k++) {
|
|
273
|
+
const a = c[(z0 * this.sizeX + x0) * 4 + k], b = c[(z0 * this.sizeX + x1) * 4 + k]
|
|
274
|
+
const d = c[(z1 * this.sizeX + x0) * 4 + k], e = c[(z1 * this.sizeX + x1) * 4 + k]
|
|
275
|
+
w[k] = (a + (b - a) * u) * (1 - v) + (d + (e - d) * u) * v
|
|
276
|
+
}
|
|
277
|
+
const sum = w[0] + w[1] + w[2] + w[3]
|
|
278
|
+
if (sum <= 1e-4 * 255) return [ 1, 0, 0, 0 ]
|
|
279
|
+
return [ w[0] / sum, w[1] / sum, w[2] / sum, w[3] / sum ]
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** The heaviest layer's index at a local (x, z) — `weightsAt` when only "which one" is asked. */
|
|
283
|
+
layerAt(x: number, z: number): number {
|
|
284
|
+
const w = this.weightsAt(x, z)
|
|
285
|
+
let best = 0
|
|
286
|
+
for (let k = 1; k < 4; k++) if (w[k] > w[best]) best = k
|
|
287
|
+
return best
|
|
288
|
+
}
|
|
289
|
+
|
|
262
290
|
/** The smooth (vertex) normal at integer sample (ix, iz). */
|
|
263
291
|
sampleNormal(ix: number, iz: number): Vec3 {
|
|
264
292
|
const n = gridNormalAt(this._grid, ix, iz)
|
|
@@ -831,7 +859,7 @@ export class Terrain {
|
|
|
831
859
|
}
|
|
832
860
|
if (!this._controlTex) {
|
|
833
861
|
const id = _creator.createTexturePixels(this.sizeX, this.sizeZ, 4, this.control, false)
|
|
834
|
-
if (id === 0xFFFFFFFF) return
|
|
862
|
+
if (id.id === 0xFFFFFFFF) return
|
|
835
863
|
this._controlTex = new Texture(this.sizeX, this.sizeZ, id)
|
|
836
864
|
this._controlTex.wrapS = 0
|
|
837
865
|
this._controlTex.wrapT = 0
|
|
@@ -842,7 +870,7 @@ export class Terrain {
|
|
|
842
870
|
const w = r.x1 - r.x0 + 1, h = r.z1 - r.z0 + 1
|
|
843
871
|
const sub = new Uint8Array(w * h * 4)
|
|
844
872
|
for (let z = 0; z < h; z++) sub.set(this.control.subarray(((r.z0 + z) * this.sizeX + r.x0) * 4, ((r.z0 + z) * this.sizeX + r.x0 + w) * 4), z * w * 4)
|
|
845
|
-
_creator.updateTexturePixels(this._controlTex.
|
|
873
|
+
_creator.updateTexturePixels(this._controlTex._h.id, r.x0, r.z0, w, h, sub)
|
|
846
874
|
}
|
|
847
875
|
|
|
848
876
|
private async _applyLayers(normals?: TerrainOptions["normals"]): Promise<void> {
|