lecodes-sdk 2.4.2 → 2.4.4
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/plugins/gen/ar/contract.d.ts +5 -5
- package/dist/types/plugins/gen/ar/sdk/ar.gen.d.ts +2 -2
- package/dist/types/plugins/gen/ar/sdk/controls.d.ts +3 -13
- package/dist/types/plugins/gen/browser/sdk/browser.d.ts +2 -1
- 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/gl/CameraPlace.ts +52 -52
- package/src/gl/Light.ts +257 -257
- package/src/gl/Mesh.ts +120 -120
- package/src/gl/scenarios.ts +291 -291
- package/src/plugins/gen/ar/contract.d.ts +5 -5
- package/src/plugins/gen/ar/sdk/ar.gen.ts +2 -2
- package/src/plugins/gen/ar/sdk/controls.ts +28 -77
- package/src/plugins/gen/browser/sdk/browser.ts +11 -1
- package/src/version.ts +1 -1
|
@@ -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
|
}
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
// const back = await Browser.open(checkoutUrl, { returnUrl: [paid, failed], embedded: true })
|
|
9
9
|
// a checkout in an embedded page,
|
|
10
10
|
// resolved with the URL it reached
|
|
11
|
+
// (navigated to in the page, or
|
|
12
|
+
// opened by a bank's app through
|
|
13
|
+
// the app's verified link)
|
|
11
14
|
//
|
|
12
15
|
// It resolves with the return URL reached, or null when the user left. `returnUrl` says what ENDS
|
|
13
16
|
// the page; `embedded` says WHERE it runs — the system sheet (shared logins, passkeys, Apple Pay;
|
|
@@ -38,7 +41,8 @@ export interface BrowserOpenOptions {
|
|
|
38
41
|
/** What ends the page: a URL prefix, or several (a checkout's success and failure URLs). The
|
|
39
42
|
* promise resolves with the URL reached. In the sheet every prefix must share one scheme — the
|
|
40
43
|
* platform intercepts one; a custom scheme needs app.json `urlScheme`, an https one the app's
|
|
41
|
-
* verified links.
|
|
44
|
+
* verified links (app.json `appLinks`). Embedded, the view intercepts the navigation itself,
|
|
45
|
+
* and a verified https return from another app (a bank's) ends the page too. */
|
|
42
46
|
returnUrl?: string | string[]
|
|
43
47
|
/** Run the page in an embedded WebView under a close bar instead of the system sheet. The
|
|
44
48
|
* WebView intercepts any return URL itself, https included. */
|
|
@@ -157,10 +161,16 @@ const openEmbedded = (url: string, options: BrowserOpenOptions): Promise<string
|
|
|
157
161
|
if (routed) Router.pop()
|
|
158
162
|
else { screen.close(); previous?.open() }
|
|
159
163
|
}
|
|
164
|
+
// Two ways the return URL arrives: the page navigates to it inside the view (`stop`), or
|
|
165
|
+
// another app does — a bank's app the checkout handed off to sends the person back through the
|
|
166
|
+
// app's verified link (app.json "appLinks"), which reaches the app as its "url" event while the
|
|
167
|
+
// page still shows the bank's form. Both end the flow; `done` makes the second a no-op.
|
|
160
168
|
let done = false
|
|
169
|
+
const off = stops.length ? _onAppUrl(back => { if (matching(back, stops)) finish(back) }) : () => {}
|
|
161
170
|
const finish = (back: string | null): void => {
|
|
162
171
|
if (done) return
|
|
163
172
|
done = true
|
|
173
|
+
off()
|
|
164
174
|
leave()
|
|
165
175
|
resolve(back)
|
|
166
176
|
}
|
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.4"
|
|
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 => {
|