lecodes-sdk 2.8.0 → 2.8.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/global.d.ts CHANGED
@@ -248,6 +248,7 @@ declare global {
248
248
  const tileOriented: typeof SDK.tileOriented
249
249
  const tileTurns: typeof SDK.tileTurns
250
250
  const toast: typeof SDK.toast
251
+ const trackGesture: typeof SDK.trackGesture
251
252
  const use: typeof SDK.use
252
253
  type ARError = SDK.ARError
253
254
  type ARErrorCode = SDK.ARErrorCode
@@ -378,6 +379,9 @@ declare global {
378
379
  type GeoPosition = SDK.GeoPosition
379
380
  type GeoWatch = SDK.GeoWatch
380
381
  type Geometry = SDK.Geometry
382
+ type Gesture = SDK.Gesture
383
+ type GestureHandler = SDK.GestureHandler
384
+ type GestureTarget = SDK.GestureTarget
381
385
  type GizmoAnchor = SDK.GizmoAnchor
382
386
  type GizmoStyle = SDK.GizmoStyle
383
387
  type HapticStyle = SDK.HapticStyle
@@ -78,6 +78,13 @@ export declare abstract class Aspect<K extends string, P = Node, E extends Event
78
78
  static after?: Function[];
79
79
  /** Tick BEFORE these classes within the same phase (`static before = [Camera]`). */
80
80
  static before?: Function[];
81
+ /** DATA ONLY. A `use(...)` of this aspect in a scene file leaves its node STATIC for the level cache (gl/Statics.ts):
82
+ * the engine builds the node without a JS object, and the aspect is attached the first time a hit, a contact or
83
+ * a pick reaches the node — `hit.node.get(Surface)` answers then. So: fields and the methods the game calls on a
84
+ * hit (a tag like `Surface { kind }`, a breakable's hit points), no `update()` (`use()` refuses one) and nothing in
85
+ * `onAttach()` the level needs from its first frame. Without it a custom aspect keeps its node on the JS path,
86
+ * built at load like any mover. */
87
+ static passive?: boolean;
81
88
  /** Every live instance of an aspect/system class, in attach order — the registry a game used to
82
89
  * hand-roll as `static all[]`. Live, read-only: copy it (`[...Aspect.all(Barrel)]`) before a loop
83
90
  * that detaches. Empty in the scene editor (aspects are inert data there). */
@@ -3,6 +3,7 @@ import { Model, type FoliageKind } from "./Model";
3
3
  import { Node } from "./Node";
4
4
  import { Scene } from "./Scene";
5
5
  import type { SceneDef, SceneNodeDef } from "../scene/defineScene";
6
+ import type { AspectEntry } from "../scene/grammar";
6
7
  import type { BakeData } from "./Lightmap";
7
8
  import { type Digest } from "./staticsFormat";
8
9
  export { bakeDigest, type Digest } from "./staticsFormat";
@@ -17,6 +18,16 @@ export type CoveredRoot = {
17
18
  path: string;
18
19
  def: SceneNodeDef;
19
20
  };
21
+ /** A passive aspect (Aspect.passive): data the engine does not need — attached to the placement's wrapper on the
22
+ * first hit (`attachPassive`), never built at load. */
23
+ export declare const isPassive: (e: AspectEntry<any>) => boolean;
24
+ /** Attach the passive aspects of `def` to `node` (props as written: a passive aspect is data, `ref()`s are not
25
+ * resolved). Who gets them: the entity a hit comes back with. A placement's clone, on the first hit, through
26
+ * `lazyPlacements` — a covered root with a model included, since its clone is what the ray strikes, not the plain
27
+ * JS node the clone hangs under. A root WITHOUT a model (a group tagged `Surface` over its walls) has no clone:
28
+ * its JS node takes them at load, and a hit on a wall under it finds them one `parent` up (a game's surface lookup
29
+ * walks `hit.node.parent`). */
30
+ export declare const attachPassive: (node: Node, def: SceneNodeDef) => void;
20
31
  /** The maximal cacheable subtrees among `defs`, depth first: through `children`, and through a prefab's nodes
21
32
  * (paths run through the prefab's path, as the bake's keys do). */
22
33
  export declare const coveredRoots: (defs: Record<string, SceneNodeDef> | undefined, scene: SceneDef) => CoveredRoot[];
@@ -53,4 +64,4 @@ export declare const open: (url: string, defs: Digest, bake: Digest) => Promise<
53
64
  * templates load through `template` (one per kind, as the level's slow path would load them), the engine clones
54
65
  * each at its placements as it resolves. `pages` = the bake's light/aux texture ids interleaved (empty = no bake
55
66
  * bound). Resolves to the placements made. */
56
- export declare const build: (opened: OpenedCache, scene: Scene, roots: Node[], pages: Uint32Array, template: (url: string, lightmap: boolean, foliage: FoliageKind) => Promise<Model>) => Promise<number>;
67
+ export declare const build: (opened: OpenedCache, scene: Scene, roots: Node[], pages: Uint32Array, template: (url: string, lightmap: boolean, foliage: FoliageKind) => Promise<Model>, covered: CoveredRoot[], def: SceneDef) => Promise<number>;
@@ -47,13 +47,18 @@ export declare class CharacterController extends Aspect<"controller", Node> impl
47
47
  * True while a requested collider resize hasn't taken — you asked to stand up and there is something
48
48
  * overhead. Resizing goes through the `Shape` aspect itself:
49
49
  *
50
- * hero.aspect(Shape, { capsule: CROUCHED }) // always fits — you are shrinking
51
- * hero.aspect(Shape, { capsule: STANDING }) // may be refused under a low ceiling
52
- * if (hero.controller.resizing) … // still crouched; call it again next frame
50
+ * hero.shape.capsule = CROUCHED // always fits — you are shrinking
51
+ * hero.shape.capsule = STANDING // may be refused under a low ceiling
52
+ * if (hero.controller.resizing) … // still crouched; write it again next frame
53
53
  *
54
54
  * A refusal changes nothing, so the retry is just the same call again — and it is an exact headroom
55
55
  * test against the real capsule, unlike a hand-rolled raycast (a ray is a line; a capsule has girth).
56
- * The FEET stay planted across a resize, so the character neither hovers nor sinks.
56
+ * The FEET stay planted across a resize, so the character neither hovers nor sinks — which means the
57
+ * node (the capsule's centre) has ALREADY moved by the feet difference when the write returns. Place
58
+ * a first-person camera from the feet (`node.y − (halfHeight + radius)` of the capsule worn) plus an
59
+ * eased eye height; placed from the node it pops by that difference. Write the property: a second
60
+ * `node.aspect(Shape, …)` builds a fresh Shape (it is handed to the character too, but it is a
61
+ * rebuild per stance, not a resize).
57
62
  */
58
63
  get resizing(): boolean;
59
64
  /** True while standing on walkable ground. */
@@ -87,6 +87,12 @@ export declare class Physics extends Aspect<"physics", Node> implements CompWrit
87
87
  /** Whether this host has 3D physics (an engine built with CREATOR_GL_PHYSICS). False, never a
88
88
  * throw, where there is no 3D engine at all. */
89
89
  static get supported(): boolean;
90
- /** Closest pickable body hit by the ray from `origin` along `dir` (up to `maxDist`), or null. */
90
+ /** The motion of the body on `node`, read off the engine: it answers for a body the SDK never made (a static the
91
+ * level cache built, which has no `Physics` aspect until a hit reaches it, and no body-making one ever). null = no
92
+ * body on this node. The way to tell a wall from a prop on a hit: `Physics.motionOf(hit.node) === "static"`. */
93
+ static motionOf(node: Node | null): MotionType | null;
94
+ /** Closest pickable body hit by the ray from `origin` along `dir` (up to `maxDist`), or null. `node` is the body's
95
+ * node — for a static the level cache built, a wrapper made on this hit (Node.ts nodeOf), carrying the placement's
96
+ * passive aspects; its `Physics` is read with `Physics.motionOf`. */
91
97
  static raycast(origin: Vec3Like, dir: Vec3Like, maxDist?: number): RayHit | null;
92
98
  }
@@ -138,6 +138,10 @@ export declare class Shape extends Aspect<"shape", Node> {
138
138
  static fitBounds(bounds: ShapeBounds, scale: Vec3Like, kind?: ShapeKind): ShapeFit;
139
139
  private _createPickBody;
140
140
  private _rebuild;
141
+ /** The character this node belongs to tries the geometry on. Jolt validates the fit (standing up
142
+ * under a pipe) and can refuse; a refusal changes nothing in the engine. The feet stay planted, so
143
+ * on success the node has already moved by the change in the shape's lower bound when this returns. */
144
+ private _offerToCharacter;
141
145
  private _build;
142
146
  private _applyOrigin;
143
147
  private _signature;
@@ -37,6 +37,7 @@ export { audio, type PlaySoundOptions, type SpatialOptions, type Rolloff, type A
37
37
  export { openFilePicker, files, type FileData } from "./runtime/files";
38
38
  export { share } from "./runtime/share";
39
39
  export { ClickEvent, TouchStartEvent, LongPressEvent, MouseEnterEvent } from "./runtime/touch";
40
+ export { trackGesture, type Gesture, type GestureHandler, type GestureTarget } from "./runtime/gestures";
40
41
  export { Scene2D } from "./g2/Scene2D";
41
42
  export { Node2D } from "./g2/Node2D";
42
43
  export { Camera2D } from "./g2/Camera2D";
@@ -22,6 +22,7 @@ export { Vec2, Vec3, type Vec3Like } from "./math/vec";
22
22
  export { Quat, type QuatLike } from "./math/quat";
23
23
  export { Mat4 } from "./math/mat4";
24
24
  export type { ClickEvent, TouchStartEvent } from "./runtime/touch";
25
+ export { trackGesture, type Gesture, type GestureHandler, type GestureTarget } from "./runtime/gestures";
25
26
  export type { DismissOptions } from "./ui/presentable";
26
27
  /** The seam the push plugin's wrapper installs its runtime on (src/runtime/pushSeam.ts): what the app's transport
27
28
  * reads the device's address from, registers through, and a push channel's handlers are kept by. */
@@ -44,6 +44,9 @@ export type ARSceneOptions = SceneOptions & {
44
44
  /** "world" (default): planes, hit tests, anchors, image and QR targets. "images": image and QR
45
45
  * targets only — lighter on iOS; no planes, no hit test, no `root`. */
46
46
  mode?: ARMode;
47
+ /** How far in front of the user `root` is placed, metres (default 1): along the camera's heading,
48
+ * on the floor. */
49
+ rootDistance?: number;
47
50
  };
48
51
  export type AROpenOptions = PresentOptions & Omit<StartOptions, "mode" | "images" | "codes"> & {
49
52
  /** The scene's ambient (its `environmentIntensity`) follows the estimated light; turns
@@ -128,6 +131,8 @@ export declare class ARScene extends Scene {
128
131
  /** Whether this host registered the "ar" service (the app lists the plugin, the host has a half). */
129
132
  static get isSupported(): boolean;
130
133
  readonly mode: ARMode;
134
+ /** Metres between the user and `root` when it is placed (`rootDistance` option, default 1). */
135
+ readonly rootDistance: number;
131
136
  /** `open()` warm-renders the scene first (default true). */
132
137
  useWarmRender: boolean;
133
138
  private _opening;
@@ -147,7 +152,8 @@ export declare class ARScene extends Scene {
147
152
  private _codesWarned;
148
153
  private _options;
149
154
  private _root;
150
- /** The first horizontal plane's pose: where `root` is pinned. */
155
+ /** The floor's height (world Y, metres): the first horizontal plane below the camera this session
156
+ * found. Null until one is; `root` is placed on it. */
151
157
  private _floor;
152
158
  /** The image targets. `source` is held for the bytes: a fetched response owns its buffer, which the
153
159
  * host frees once the response is collected — the File the session is handed only names it. */
@@ -174,9 +180,17 @@ export declare class ARScene extends Scene {
174
180
  /** A grid drawn on every plane (each plane's child mesh). */
175
181
  get showPlanes(): boolean;
176
182
  set showPlanes(on: boolean);
177
- /** The floor: an anchor pinned on the first horizontal plane the session finds (it fires `track`
178
- * then). Until that it is the world's origin — where the session began. Not in "images" mode. */
183
+ /** The place in front of the user: an anchor `rootDistance` metres ahead of the camera along its
184
+ * heading, on the floor — the first horizontal plane below the camera the session finds; it is
185
+ * pinned there and fires `track` then — turned the way the user looks (its −Z along the heading,
186
+ * so a model's front, +Z, faces the user). Until the floor is found (and on the web, which finds
187
+ * none) it sits the same way at the camera's own height, not pinned. Made on first use; put
188
+ * content under it. `placeRoot()` puts it in front of the user again. Not in "images" mode. */
179
189
  get root(): Anchor;
190
+ /** Put `root` in front of the user again — where they look now, `rootDistance` metres ahead, on the
191
+ * floor if one is known (the camera's height otherwise), facing them: a "reset" button, or a
192
+ * placement after the user has moved. Makes `root` when there is none yet. */
193
+ placeRoot(): Anchor;
180
194
  /** An anchor that follows an image (a printed marker, a poster): the session finds it in the camera
181
195
  * image and keeps the anchor on it — a rigid pose in metres (its centre, Y out of the image, the
182
196
  * image in XZ), its size `anchor.extent`. `physicalWidth` = the printed width in metres. Hidden
@@ -244,8 +258,14 @@ export declare class ARScene extends Scene {
244
258
  private _wire;
245
259
  /** Pin `anchor` at `pose` (where it is until the session holds it) — now, or when a session starts. */
246
260
  private _place;
247
- /** `root` onto the floor (the first horizontal plane of this session). */
248
- private _pinRoot;
261
+ /** Where `root` goes now: `rootDistance` metres from the camera along its heading (its forward
262
+ * flattened to the floor; looking straight down or up, the way the top of the screen points), at
263
+ * the floor's height when one is known, else the camera's; turned so its −Z runs along the
264
+ * heading — a model's front (+Z) faces the user. */
265
+ private _rootPose;
266
+ /** `root` in front of the user: moved there now, and pinned there when the floor is known (a
267
+ * session holds a pin; without one `_pinAll` does not re-pin root — it waits for its own floor). */
268
+ private _placeRoot;
249
269
  private _pin;
250
270
  /** A session began: every pose anchor is pinned where it is. */
251
271
  private _pinAll;
@@ -0,0 +1,52 @@
1
+ import type { TouchStartEvent, TrackHandler } from "./touch";
2
+ /** One reading of a gesture, handed to `onStart` / `onMove` / `onEnd` (a fresh object each time). */
3
+ export interface Gesture {
4
+ /** The centroid of the live fingers, in the event's client coordinates (logical px). */
5
+ centerX: number;
6
+ centerY: number;
7
+ /** The centroid's move since the previous `onMove` (0 on start / end). */
8
+ deltaX: number;
9
+ deltaY: number;
10
+ /** Mean finger distance to the centroid over the previous one (1 with one finger, 1 on start / end). */
11
+ scale: number;
12
+ /** The mean change of each finger's angle about the centroid since the previous `onMove`, radians,
13
+ * positive = clockwise on screen (0 with one finger, 0 on start / end). */
14
+ rotation: number;
15
+ /** The number of live fingers (0 in `onEnd`: the last one lifted). */
16
+ pointers: number;
17
+ }
18
+ /** What `trackGesture` takes: the gesture's callbacks and the claim every finger's track makes. */
19
+ export interface GestureHandler {
20
+ /** Passed to every finger's `ev.track(...)` (default `true`: a scrollable above never takes the pan). */
21
+ claim?: TrackHandler["claim"];
22
+ /** The first finger landed. */
23
+ onStart?(g: Gesture): void;
24
+ /** Any finger moved: the change since the previous `onMove`. */
25
+ onMove?(g: Gesture): void;
26
+ /** The last finger lifted. */
27
+ onEnd?(g: Gesture): void;
28
+ /** The host cancelled one of the fingers: the whole gesture is over. */
29
+ onCancel?(): void;
30
+ }
31
+ /** Anything that hands out touchstarts: a UI element (`onTouchStart`) or a scene / node
32
+ * (`addEventListener("touchstart", …)`). */
33
+ export type GestureTarget = {
34
+ onTouchStart(callback: (ev: TouchStartEvent<any>) => void): unknown;
35
+ } | {
36
+ addEventListener(channel: "touchstart", callback: (ev: TouchStartEvent<any>) => void): void;
37
+ removeEventListener(channel: "touchstart", callback: (ev: TouchStartEvent<any>) => void): void;
38
+ };
39
+ /**
40
+ * Follow pan, pinch and twist on `target` as one gesture: every finger that lands on it is tracked,
41
+ * and `onMove` reports the change of the whole set since the previous move — the centroid's move,
42
+ * the spread's ratio, the twist's angle. One finger is a gesture too (a pan, scale 1, rotation 0).
43
+ *
44
+ * ```ts
45
+ * trackGesture(scene, {
46
+ * onMove(g) { zoom *= g.scale; angle += g.rotation; panX += g.deltaX; panY += g.deltaY },
47
+ * })
48
+ * ```
49
+ */
50
+ export declare const trackGesture: (target: GestureTarget, handler: GestureHandler) => {
51
+ remove(): void;
52
+ };
@@ -28,9 +28,10 @@ export type UIModalShow = (() => boolean) | Signal<boolean>;
28
28
  * request, `onDismiss`. By default it hides an unbound modal and writes `false` into a bound
29
29
  * signal; a bound function has nowhere to write, so a modal bound to one without `onDismiss`
30
30
  * cannot be dismissed (a forced choice — the same as `dismissible(false)`). `display` is not a
31
- * modal's switch: a `.style({ display })` on it throws (a bound display died on the first
32
- * dismissal, which wrote a static `display: "none"` over the binding — the dialog never opened
33
- * again).
31
+ * modal's switch (a bound display died on the first dismissal, which wrote a static
32
+ * `display: "none"` over the binding — the dialog never opened again). Deprecated, for the
33
+ * transition period a `.style({ display })` is still accepted, with a warning: a function becomes
34
+ * `bindShow(() => fn() !== "none")`, a value a show() / hide(); it will throw again.
34
35
  */
35
36
  export interface UIModal extends UIWidget {
36
37
  /**
@@ -82,6 +83,7 @@ export declare class ModalElement extends WidgetElement {
82
83
  private _poseObj?;
83
84
  private _poseKey;
84
85
  constructor(style: UIModalStyle, children: UINodeChild[] | ChildrenFn);
86
+ private _bindDisplay;
85
87
  private _pose;
86
88
  transition(hidden: UIModalTransition): this;
87
89
  bindShow(value: UIModalShow): this;
@@ -1,4 +1,4 @@
1
- export declare const SDK_VERSION = "2.8.0";
1
+ export declare const SDK_VERSION = "2.8.2";
2
2
  /** The major of a semver string, or null when it is not one. */
3
3
  export declare const sdkMajor: (version: string | null | undefined) => number | null;
4
4
  /** The `// sdk: <version>` line of a compiled bundle's header (sdk/src/compile/header.ts), read from