lecodes-sdk 2.4.3 → 2.4.5

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.
@@ -1,22 +1,23 @@
1
1
  // GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/ar/sdk/controls.ts — do not edit here.
2
2
  // Change it in the plugin repo and run `bun run vendor:plugins`; `bun run check:plugins` fails when this copy is stale.
3
3
  // AR object-placement controls — the classic "drop a model in the room and manipulate it" gesture
4
- // recipe: ONE finger drags the target over what the session finds under the finger (`hitTest`: a
5
- // plane), and where it finds nothing — the web, which finds no surfaces, or before one is known —
6
- // across the ground plane, relative to where the camera looks; TWO fingers pinch to scale and twist
7
- // to rotate (yaw). Lives on ARScene (`scene.addControls`) because it orchestrates the scene's camera,
8
- // its hit test and touch input against a target node; it's built entirely on the public API, so
9
- // it's a convenience, not a primitive.
4
+ // recipe: ONE finger drags the target across the ground plane at its own height, by the finger's
5
+ // move and relative to where the camera looks — the model never jumps to the finger, and no session
6
+ // call is in the loop, so it behaves the same on every host (the web too). Not under the finger on
7
+ // purpose: the finger's ray onto the floor (a hit test, or the plane at the target's height) is
8
+ // metres per point near the horizon, and there the target flies off at the slightest move. TWO
9
+ // fingers pinch to scale and twist to rotate (yaw). Lives on ARScene (`scene.addControls`) because it
10
+ // orchestrates the scene's camera and touch input against a target node; it's built entirely on the
11
+ // public API, so it's a convenience, not a primitive.
10
12
 
11
13
  import { Quat, Vec2, Vec3, type Node, type Scene, type TouchStartEvent } from "../../../../plugin"
12
- import type { HitTarget } from "../contract"
13
14
 
14
15
  export type ControlsOptions = {
15
16
  /** Smallest uniform scale the pinch gesture allows (default 0.08). */
16
17
  minScale?: number
17
18
  /** Largest uniform scale the pinch gesture allows (default 2). */
18
19
  maxScale?: number
19
- /** One-finger drag moves the target over the surfaces under the finger (default true). */
20
+ /** One-finger drag moves the target across the ground plane, at its height (default true). */
20
21
  pan?: boolean
21
22
  /** Two-finger pinch scales the target (default true). */
22
23
  pinch?: boolean
@@ -27,16 +28,11 @@ export type ControlsOptions = {
27
28
  /** Returned by `addControls` — call `remove()` to detach the gesture listeners. */
28
29
  export type ControlsHandle = { remove(): void }
29
30
 
30
- /** The scene the controls work on: ARScene's hit test, a Scene's camera and touches. */
31
- type PlacingScene = Scene & {
32
- hitTest(x: number, y: number, target?: HitTarget): Promise<{ pose: { position: Vec3 } }[]>
33
- }
34
-
35
31
  type ActiveTouch = { pointerId: number, x: number, y: number }
36
32
 
37
33
  const clamp = (v: number, lo: number, hi: number): number => (v < lo ? lo : v > hi ? hi : v)
38
34
 
39
- export const attachControls = (scene: PlacingScene, target: Node, options: ControlsOptions = {}): ControlsHandle => {
35
+ export const attachControls = (scene: Scene, target: Node, options: ControlsOptions = {}): ControlsHandle => {
40
36
  const minScale = options.minScale ?? 0.08
41
37
  const maxScale = options.maxScale ?? 2
42
38
  const pan = options.pan ?? true
@@ -44,60 +40,6 @@ export const attachControls = (scene: PlacingScene, target: Node, options: Contr
44
40
  const twist = options.twist ?? true
45
41
 
46
42
  const active: ActiveTouch[] = []
47
- let removed = false
48
-
49
- // --- one finger: the hit test is a call to the session, so ONE is in flight at a time; the moves
50
- // that come meanwhile add up and go with the next ---
51
- let pending: { x: number, y: number, dx: number, dy: number } | null = null
52
- let asking = false
53
-
54
- /** The target onto a point of the world, its height and turn kept by the surface's. */
55
- const placeAt = (world: Vec3): void => {
56
- const w = target.worldMatrix.toArray()
57
- w[12] = world.x; w[13] = world.y; w[14] = world.z
58
- target.worldMatrix = w
59
- }
60
-
61
- /** Nothing under the finger: across the ground plane, relative to where the camera looks. */
62
- const slide = (dx: number, dy: number): void => {
63
- let dir = scene.camera.forward
64
- const parent = target.parent
65
- if (parent) dir = dir.rotate(parent.quaternion.invert()) // into the parent's local frame
66
- dir = dir.withY(0)
67
- const k = Math.hypot(dir.x, dir.z)
68
- // the move's delta over the view's size: both logical units (a scene touch's, displaySize's), so
69
- // a drag across the view slides the target by its distance from the camera on every host
70
- const screen = Math.max(...scene.camera.displaySize)
71
- if (k > 1e-4 && screen > 0) {
72
- const dist = target.worldPosition.distanceTo(scene.camera.worldPosition)
73
- const right = new Vec3(-dir.z, 0, dir.x).scale(dx / k / screen * dist)
74
- const fwd = dir.scale(-dy / k / screen * dist)
75
- const move = right.add(fwd)
76
- const p = target.position
77
- target.position = new Vec3(p.x + move.x, p.y, p.z + move.z) // keep height fixed
78
- }
79
- }
80
-
81
- const ask = (): void => {
82
- const drag = pending
83
- if (!drag || asking) return
84
- pending = null
85
- asking = true
86
- scene.hitTest(drag.x, drag.y, "planes").catch(() => []).then((hits) => {
87
- asking = false
88
- if (removed) return
89
- const hit = hits[0]
90
- if (hit) placeAt(hit.pose.position)
91
- else slide(drag.dx, drag.dy)
92
- ask()
93
- })
94
- }
95
-
96
- const drag = (x: number, y: number, dx: number, dy: number): void => {
97
- if (pending) { pending.x = x; pending.y = y; pending.dx += dx; pending.dy += dy }
98
- else pending = { x, y, dx, dy }
99
- ask()
100
- }
101
43
 
102
44
  const onTouchStart = (e: TouchStartEvent<Node | null>): void => {
103
45
  const touch: ActiveTouch = { pointerId: e.pointerId, x: e.clientX, y: e.clientY }
@@ -124,7 +66,23 @@ export const attachControls = (scene: PlacingScene, target: Node, options: Contr
124
66
  }
125
67
  }
126
68
  } else if (active.length === 1 && pan) {
127
- drag(m.clientX, m.clientY, m.deltaX, m.deltaY)
69
+ // --- one-finger: drag across the ground plane, relative to where the camera looks ---
70
+ let dir = scene.camera.forward
71
+ const parent = target.parent
72
+ if (parent) dir = dir.rotate(parent.quaternion.invert()) // into the parent's local frame
73
+ dir = dir.withY(0)
74
+ const k = Math.hypot(dir.x, dir.z)
75
+ // the move's delta over the view's size: both logical units (a scene touch's, displaySize's), so
76
+ // a drag across the view slides the target by its distance from the camera on every host
77
+ const screen = Math.max(...scene.camera.displaySize)
78
+ if (k > 1e-4 && screen > 0) {
79
+ const dist = target.worldPosition.distanceTo(scene.camera.worldPosition)
80
+ const right = new Vec3(-dir.z, 0, dir.x).scale(m.deltaX / k / screen * dist)
81
+ const fwd = dir.scale(-m.deltaY / k / screen * dist)
82
+ const move = right.add(fwd)
83
+ const p = target.position
84
+ target.position = new Vec3(p.x + move.x, p.y, p.z + move.z) // keep height fixed
85
+ }
128
86
  }
129
87
  touch.x = m.clientX
130
88
  touch.y = m.clientY
@@ -137,12 +95,5 @@ export const attachControls = (scene: PlacingScene, target: Node, options: Contr
137
95
  }
138
96
 
139
97
  scene.addEventListener("touchstart", onTouchStart)
140
- return {
141
- remove() {
142
- removed = true
143
- pending = null
144
- scene.removeEventListener("touchstart", onTouchStart)
145
- active.length = 0
146
- },
147
- }
98
+ return { remove() { scene.removeEventListener("touchstart", onTouchStart); active.length = 0 } }
148
99
  }
package/src/version.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  // same manifest). Its MAJOR is the bundle ↔ runtime contract: a bundle's `// sdk:` header line
5
5
  // and a host's embedded version must agree on the major for the host to run the bundle; minor
6
6
  // and patch never gate anything (features are detected, never versioned).
7
- export const SDK_VERSION = "2.4.3"
7
+ export const SDK_VERSION = "2.4.5"
8
8
 
9
9
  /** The major of a semver string, or null when it is not one. */
10
10
  export const sdkMajor = (version: string | null | undefined): number | null => {
@@ -1,15 +1,16 @@
1
1
  // The aspect frame source for every test file, installed by the preload (bunfig.toml) BEFORE any
2
- // SDK module runs. core/Aspect.ts wires its phase runners ONCE per process: the first engine layer's
3
- // installer wins (`_installAspectFrames`, called by the first Scene / Scene2D), and the first
4
- // attached updater hands the runners to it — or, with no installer yet, to the `setLoop` of that
5
- // moment. bun test shares one module registry across files, so a file's own
6
- // `_creator.setEarlyUpdate` stub only ever saw them if that file happened to attach first (a
7
- // DynamicBone attached with no Scene, in an earlier file, sent them into its `setLoop = () => 1`).
8
- // A file that drives the phases by hand ticks through `tick` here instead.
2
+ // SDK module runs. core/Aspect.ts wires its sources ONCE per process, at the first attached updater
3
+ // (`_installAspectFrames`: this one, then each engine layer's at its first Scene / Scene2D — or, with
4
+ // none wired, the `setLoop` of that moment). This source has no `presented` and so always drives;
5
+ // the engine layers' sources are never wired here, since the host stubs carry no setEarlyUpdate.
6
+ // bun test shares one module registry across files, so a file's own `_creator.setEarlyUpdate` stub
7
+ // only ever saw the runners if that file happened to attach first (a DynamicBone attached with no
8
+ // Scene, in an earlier file, sent them into its `setLoop = () => 1`). A file that drives the phases
9
+ // by hand ticks through `tick` here instead.
9
10
  //
10
11
  // No fixed hook is installed: the SDK's own 1/60 accumulator steps `updateFixed` inside the early
11
12
  // phase, as on a host without setFixedUpdate. Files that compile a bundle (aspect-update,
12
- // aspect-lifecycle) run their own copy of the SDK and still capture the bridges' hooks themselves.
13
+ // aspect-lifecycle, aspect-sources) run their own copy of the SDK and capture the bridges' hooks themselves.
13
14
  import { _installAspectFrames } from "../../src/core/Aspect"
14
15
 
15
16
  type PhaseFn = (dt: number) => void