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.
- package/dist/types/gl/Light.d.ts +11 -4
- package/dist/types/plugins/gen/ar/sdk/controls.d.ts +3 -13
- package/dist/types/version.d.ts +1 -1
- package/dist/types.json +1 -1
- package/package.json +1 -1
- package/src/animate/tween/Timeline.ts +175 -175
- package/src/core/Aspect.ts +55 -14
- package/src/g2/Scene2D.ts +1 -1
- package/src/gl/CameraPlace.ts +52 -52
- package/src/gl/Light.ts +274 -257
- package/src/gl/Mesh.ts +120 -120
- package/src/gl/Scene.ts +1 -1
- package/src/gl/scenarios.ts +291 -291
- package/src/plugins/gen/ar/sdk/controls.ts +28 -77
- package/src/version.ts +1 -1
- package/tests/helpers/phases.ts +9 -8
|
@@ -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
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
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
|
|
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:
|
|
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
|
|
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.
|
|
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 => {
|
package/tests/helpers/phases.ts
CHANGED
|
@@ -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
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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
|
|
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
|