lecodes-cli 0.1.5 → 0.1.7
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/index.js +300 -149
- package/package.json +5 -3
- package/runtime/sdk/animate/animate.ts +238 -0
- package/runtime/sdk/animate/bezier.ts +138 -0
- package/runtime/sdk/animate/easings.ts +126 -0
- package/runtime/sdk/core/color.ts +66 -0
- package/runtime/sdk/core/events.ts +38 -0
- package/runtime/sdk/core/registry.ts +23 -0
- package/runtime/sdk/g2/Camera2D.ts +73 -0
- package/runtime/sdk/g2/Node2D.ts +104 -0
- package/runtime/sdk/g2/Scene2D.ts +73 -0
- package/runtime/sdk/g2/Sprite.ts +164 -0
- package/runtime/sdk/g2/Texture2D.ts +37 -0
- package/runtime/sdk/g2/Tilemap.ts +41 -0
- package/runtime/sdk/g2/loop.ts +22 -0
- package/runtime/sdk/gl/Body.ts +101 -0
- package/runtime/sdk/gl/Camera.ts +40 -0
- package/runtime/sdk/gl/Geometry.ts +218 -0
- package/runtime/sdk/gl/Light.ts +37 -0
- package/runtime/sdk/gl/Material.ts +124 -0
- package/runtime/sdk/gl/Mesh.ts +123 -0
- package/runtime/sdk/gl/Node.ts +307 -0
- package/runtime/sdk/gl/Noise.ts +30 -0
- package/runtime/sdk/gl/Particles.ts +185 -0
- package/runtime/sdk/gl/Plane.ts +53 -0
- package/runtime/sdk/gl/Ray.ts +16 -0
- package/runtime/sdk/gl/Scene.ts +179 -0
- package/runtime/sdk/gl/Texture.ts +35 -0
- package/runtime/sdk/gl/controls.ts +95 -0
- package/runtime/sdk/gl/state.ts +6 -0
- package/runtime/sdk/gl/touch.ts +56 -0
- package/runtime/sdk/inject.ts +72 -0
- package/runtime/sdk/math/Mathf.ts +118 -0
- package/runtime/sdk/math/mat4.ts +278 -0
- package/runtime/sdk/math/quat.ts +232 -0
- package/runtime/sdk/math/vec.ts +255 -0
- package/runtime/sdk/plugins/qr.ts +36 -0
- package/runtime/sdk/runtime/device.ts +33 -0
- package/runtime/sdk/runtime/fetch.ts +71 -0
- package/runtime/sdk/runtime/files.ts +23 -0
- package/runtime/sdk/runtime/input.ts +12 -0
- package/runtime/sdk/runtime/media.ts +111 -0
- package/runtime/sdk/runtime/misc.ts +12 -0
- package/runtime/sdk/runtime/net.ts +36 -0
- package/runtime/sdk/runtime/storage.ts +13 -0
- package/runtime/sdk/runtime/touch.ts +44 -0
- package/runtime/sdk/ui/UI.ts +19 -0
- package/runtime/sdk/ui/UIButton.ts +84 -0
- package/runtime/sdk/ui/UIContainer.ts +100 -0
- package/runtime/sdk/ui/UIImage.ts +68 -0
- package/runtime/sdk/ui/UIInput.ts +78 -0
- package/runtime/sdk/ui/UINode.ts +342 -0
- package/runtime/sdk/ui/UIScreen.ts +122 -0
- package/runtime/sdk/ui/UIScrollable.ts +70 -0
- package/runtime/sdk/ui/UISpacer.ts +14 -0
- package/runtime/sdk/ui/UIText.ts +40 -0
- package/runtime/sdk/ui/UIVideo.ts +38 -0
- package/runtime/sdk/ui/UIVirtualizedList.ts +222 -0
- package/runtime/sdk/ui/UIWidget.ts +97 -0
- package/runtime/sdk/ui/fonts.ts +10 -0
- package/runtime/sdk/ui/router.ts +73 -0
- package/runtime/gl-matrix/common.js +0 -54
- package/runtime/gl-matrix/header.js +0 -12
- package/runtime/gl-matrix/index.js +0 -11
- package/runtime/gl-matrix/mat2.js +0 -425
- package/runtime/gl-matrix/mat2d.js +0 -476
- package/runtime/gl-matrix/mat3.js +0 -765
- package/runtime/gl-matrix/mat4.js +0 -1947
- package/runtime/gl-matrix/quat.js +0 -737
- package/runtime/gl-matrix/quat2.js +0 -822
- package/runtime/gl-matrix/vec2.js +0 -620
- package/runtime/gl-matrix/vec3.js +0 -791
- package/runtime/gl-matrix/vec4.js +0 -652
- package/runtime/worker/animate.js +0 -304
- package/runtime/worker/chunk-4ZN32GYS.js +0 -34
- package/runtime/worker/chunk-G5ZA3LKB.js +0 -52
- package/runtime/worker/chunk-MI3UXE5H.js +0 -1395
- package/runtime/worker/creator.js +0 -930
- package/runtime/worker/globals.js +0 -208
- package/runtime/worker/ui.js +0 -763
- package/runtime/worker/utils.js +0 -16
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// A 3D scene: a draw set, a Camera, lights and environment. Opening a scene asks the host to bring up
|
|
2
|
+
// the 3D engine; only the active scene renders. Grown from the Phase 0 seed. Ported from
|
|
3
|
+
// worker/src/Scene.ts (Scene + ARScene), with the camera exposed as scene.camera.
|
|
4
|
+
//
|
|
5
|
+
// Lights are explicit: a scene gets image-based ambient (IBL) by default; add a Light.sun() for
|
|
6
|
+
// direct light + shadows.
|
|
7
|
+
|
|
8
|
+
import { Color, type ColorInput } from "../core/color"
|
|
9
|
+
import type { ClickEvent, TouchStartEvent } from "../runtime/touch"
|
|
10
|
+
import type { FetchResponse } from "../runtime/fetch"
|
|
11
|
+
import { Camera } from "./Camera"
|
|
12
|
+
import { attachControls, type ControlsHandle, type ControlsOptions } from "./controls"
|
|
13
|
+
import { Material } from "./Material"
|
|
14
|
+
import { Node, nodeRegistry } from "./Node"
|
|
15
|
+
import { glState } from "./state"
|
|
16
|
+
import { registerTouchEndEvent, registerTouchStartEvent } from "./touch"
|
|
17
|
+
|
|
18
|
+
export type SceneOptions = {
|
|
19
|
+
/** Image-based ambient lighting (default on). */
|
|
20
|
+
ibl?: boolean
|
|
21
|
+
/** IBL intensity (default 20000). */
|
|
22
|
+
environmentIntensity?: number
|
|
23
|
+
/** Bloom post-processing. */
|
|
24
|
+
bloom?: boolean
|
|
25
|
+
bloomIntensity?: number
|
|
26
|
+
/** Skybox / clear color. */
|
|
27
|
+
skybox?: ColorInput
|
|
28
|
+
/** Multisample anti-aliasing. */
|
|
29
|
+
antialias?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class Scene {
|
|
33
|
+
/** @internal native scene handle. */
|
|
34
|
+
readonly _id: number
|
|
35
|
+
readonly camera: Camera
|
|
36
|
+
|
|
37
|
+
/** @internal touch listeners, read by the dispatch system. */
|
|
38
|
+
readonly _clickListeners: Array<(ev: ClickEvent<Node | null>) => void> = []
|
|
39
|
+
readonly _touchStartListeners: Array<(ev: TouchStartEvent<Node | null>) => void> = []
|
|
40
|
+
|
|
41
|
+
private _material?: Material
|
|
42
|
+
|
|
43
|
+
private static _active: Scene | null = null
|
|
44
|
+
static get active(): Scene | null { return Scene._active }
|
|
45
|
+
|
|
46
|
+
constructor(options: SceneOptions = {}) {
|
|
47
|
+
const parent = (options as any)._parent as Scene | undefined
|
|
48
|
+
if (parent) {
|
|
49
|
+
this.camera = parent.camera
|
|
50
|
+
this._id = _creator.createOverlayScene(parent._id)
|
|
51
|
+
} else {
|
|
52
|
+
this.camera = new Camera()
|
|
53
|
+
this._id = _creator.createScene()
|
|
54
|
+
}
|
|
55
|
+
this.camera._attach(this._id)
|
|
56
|
+
|
|
57
|
+
if (options.ibl !== false) _creator.setDefaultIbl(this._id, options.environmentIntensity ?? 20000)
|
|
58
|
+
if (options.bloom) _creator.setBloomOptions(this._id, true, options.bloomIntensity ?? 0.2, 1)
|
|
59
|
+
if (options.skybox !== undefined) _creator.setSkybox(this._id, Color.toPackedRgb(options.skybox))
|
|
60
|
+
if (options.antialias) _creator.setSceneMultiSampleAntiAliasing(this._id, true, 4)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Scene occlusion material. */
|
|
64
|
+
get occlusionMaterial(): Material {
|
|
65
|
+
if (!this._material) this._material = new Material(_creator.getSceneMaterial(this._id))
|
|
66
|
+
return this._material
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
set skybox(color: ColorInput) { _creator.setSkybox(this._id, Color.toPackedRgb(color)) }
|
|
70
|
+
|
|
71
|
+
setMaterialGlobalParameter(i: number, x: number, y: number, z: number, w: number): void {
|
|
72
|
+
_creator.setMaterialGlobalParameter(this._id, i, x, y, z, w)
|
|
73
|
+
}
|
|
74
|
+
setAntialias(enabled: boolean, scale = 4): void {
|
|
75
|
+
_creator.setSceneMultiSampleAntiAliasing(this._id, enabled, scale)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
add(...nodes: Node[]): this {
|
|
79
|
+
for (const n of nodes) _creator.addEntityToScene(this._id, n.id)
|
|
80
|
+
return this
|
|
81
|
+
}
|
|
82
|
+
remove(...nodes: Node[]): this {
|
|
83
|
+
for (const n of nodes) _creator.removeEntityFromScene(this._id, n.id)
|
|
84
|
+
return this
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
createOverlay(options: SceneOptions = {}): Scene {
|
|
88
|
+
return new Scene({ ...options, _parent: this } as any)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
warmRender(): Promise<void> {
|
|
92
|
+
return new Promise<void>((res) => _creator.warmRender(this._id, res))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Make this the active scene (brings up the 3D engine; closes any other). */
|
|
96
|
+
open(): void {
|
|
97
|
+
_creator.openScene(this._id)
|
|
98
|
+
Scene._active = this
|
|
99
|
+
glState.activeScene = this
|
|
100
|
+
}
|
|
101
|
+
close(): void {
|
|
102
|
+
_creator.closeScene()
|
|
103
|
+
if (Scene._active === this) Scene._active = null
|
|
104
|
+
if (glState.activeScene === this) glState.activeScene = null
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
addEventListener(channel: "click", callback: (ev: ClickEvent<Node | null>) => void): void
|
|
108
|
+
addEventListener(channel: "touchstart", callback: (ev: TouchStartEvent<Node | null>) => void): void
|
|
109
|
+
addEventListener(channel: "click" | "touchstart", callback: (ev: any) => void): void {
|
|
110
|
+
if (channel === "click") {
|
|
111
|
+
if (!this._clickListeners.includes(callback)) { this._clickListeners.push(callback); registerTouchEndEvent() }
|
|
112
|
+
} else if (channel === "touchstart") {
|
|
113
|
+
if (!this._touchStartListeners.includes(callback)) { this._touchStartListeners.push(callback); registerTouchStartEvent() }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
removeEventListener(channel: "click" | "touchstart", callback: (ev: any) => void): void {
|
|
117
|
+
const list = channel === "click" ? this._clickListeners : this._touchStartListeners
|
|
118
|
+
const i = list.indexOf(callback)
|
|
119
|
+
if (i >= 0) list.splice(i, 1)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// --- AR ---
|
|
124
|
+
|
|
125
|
+
export type ARMode = "default" | "markers" | "arcore" | "detached"
|
|
126
|
+
|
|
127
|
+
const onTrackStateChange = (entityId: number, track: boolean): void => {
|
|
128
|
+
const node = nodeRegistry.get(entityId)
|
|
129
|
+
if (!node) return
|
|
130
|
+
node.isTracked = track
|
|
131
|
+
if (track) node._dispatchEvent("track")
|
|
132
|
+
else node._dispatchEvent("untrack")
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export class ARScene<T extends ARMode = "default"> extends Scene {
|
|
136
|
+
readonly mode: T
|
|
137
|
+
useWarmRender = true
|
|
138
|
+
private _root?: Node
|
|
139
|
+
|
|
140
|
+
constructor(options: SceneOptions & { mode?: T } = {}) {
|
|
141
|
+
super(options)
|
|
142
|
+
this.mode = options.mode ?? ("default" as T)
|
|
143
|
+
_creator.createARController(this._id, this.camera.id, this.mode, onTrackStateChange)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Root anchor node (not available in marker mode). */
|
|
147
|
+
get root(): Node {
|
|
148
|
+
if (this._root) return this._root
|
|
149
|
+
this._root = new Node(_creator.createRootAnchor(this._id))
|
|
150
|
+
return this._root
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Create an anchor from a tracking source (e.g. an image target). */
|
|
154
|
+
createAnchor(source: FetchResponse, physicalWidth = 0.2): Node {
|
|
155
|
+
return new Node(_creator.createAnchor(this._id, physicalWidth, (source as any)._id))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Let the user place & manipulate a node with touch gestures — the staple AR object-placement
|
|
160
|
+
* interaction: ONE finger drags it across the floor (relative to where the camera looks), TWO fingers
|
|
161
|
+
* pinch to scale and twist to rotate. Returns a handle; call `.remove()` to detach. Tunable via
|
|
162
|
+
* options (min/max scale, and toggles for `pan` / `pinch` / `twist`).
|
|
163
|
+
*/
|
|
164
|
+
addControls(target: Node, options: ControlsOptions = {}): ControlsHandle {
|
|
165
|
+
return attachControls(this, target, options)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
override async open(): Promise<void> {
|
|
169
|
+
await new Promise<void>((res, rej) => _creator.requestCamera(res, rej))
|
|
170
|
+
if (this.useWarmRender) { _creator.createGLView(); await this.warmRender() }
|
|
171
|
+
await new Promise<void>((res, rej) => _creator.launchAR(this._id, res, rej))
|
|
172
|
+
super.open()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
override close(): void {
|
|
176
|
+
_creator.stopAR()
|
|
177
|
+
super.close()
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// A 3D GPU texture, decoded natively by creator-gl. Load from a URL or an already-fetched response.
|
|
2
|
+
|
|
3
|
+
import { fetch, type FetchResponse } from "../runtime/fetch"
|
|
4
|
+
|
|
5
|
+
export class Texture {
|
|
6
|
+
/** @internal native texture handle. */
|
|
7
|
+
readonly _id: number
|
|
8
|
+
readonly width: number
|
|
9
|
+
readonly height: number
|
|
10
|
+
|
|
11
|
+
/** Horizontal wrap mode (U). */
|
|
12
|
+
wrapS = 1
|
|
13
|
+
/** Vertical wrap mode (V). */
|
|
14
|
+
wrapT = 1
|
|
15
|
+
|
|
16
|
+
constructor(width: number, height: number, id: number) {
|
|
17
|
+
this.width = width
|
|
18
|
+
this.height = height
|
|
19
|
+
this._id = id
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static load(source: string | FetchResponse): Promise<Texture> {
|
|
23
|
+
if (typeof source === "string") {
|
|
24
|
+
return fetch(source, { useOnce: true }).then((resp) => {
|
|
25
|
+
if (resp.status >= 400) {
|
|
26
|
+
return Promise.reject(new Error(`Failed to fetch texture from ${source}. HTTP ${resp.status}`))
|
|
27
|
+
}
|
|
28
|
+
return Texture.load(resp)
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
return new Promise<Texture>((resolve, reject) => {
|
|
32
|
+
_creator.createTexture((source as any)._id, (id, w, h) => resolve(new Texture(w, h, id)), reject)
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// AR object-placement controls — the classic "drop a model in the room and manipulate it" gesture
|
|
2
|
+
// recipe: ONE finger drags the target across the ground plane (camera-relative), TWO fingers pinch to
|
|
3
|
+
// scale and twist to rotate (yaw). Lives on ARScene (see Scene.ts) because it orchestrates the scene's
|
|
4
|
+
// camera + touch input against a target node; it's built entirely on the public API (no native calls
|
|
5
|
+
// the user couldn't make), so it's a convenience, not a primitive.
|
|
6
|
+
|
|
7
|
+
import { Vec2, Vec3 } from "../math/vec"
|
|
8
|
+
import { Quat } from "../math/quat"
|
|
9
|
+
import type { Node } from "./Node"
|
|
10
|
+
import type { Scene } from "./Scene"
|
|
11
|
+
import type { TouchStartEvent } from "../runtime/touch"
|
|
12
|
+
|
|
13
|
+
export type ControlsOptions = {
|
|
14
|
+
/** Smallest uniform scale the pinch gesture allows (default 0.08). */
|
|
15
|
+
minScale?: number
|
|
16
|
+
/** Largest uniform scale the pinch gesture allows (default 2). */
|
|
17
|
+
maxScale?: number
|
|
18
|
+
/** One-finger drag moves the target across the ground plane (default true). */
|
|
19
|
+
pan?: boolean
|
|
20
|
+
/** Two-finger pinch scales the target (default true). */
|
|
21
|
+
pinch?: boolean
|
|
22
|
+
/** Two-finger twist rotates the target around its Y axis (default true). */
|
|
23
|
+
twist?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Returned by `addControls` — call `remove()` to detach the gesture listeners. */
|
|
27
|
+
export type ControlsHandle = { remove(): void }
|
|
28
|
+
|
|
29
|
+
type ActiveTouch = { pointerId: number, x: number, y: number }
|
|
30
|
+
|
|
31
|
+
const clamp = (v: number, lo: number, hi: number): number => (v < lo ? lo : v > hi ? hi : v)
|
|
32
|
+
|
|
33
|
+
export const attachControls = (scene: Scene, target: Node, options: ControlsOptions = {}): ControlsHandle => {
|
|
34
|
+
const minScale = options.minScale ?? 0.08
|
|
35
|
+
const maxScale = options.maxScale ?? 2
|
|
36
|
+
const pan = options.pan ?? true
|
|
37
|
+
const pinch = options.pinch ?? true
|
|
38
|
+
const twist = options.twist ?? true
|
|
39
|
+
|
|
40
|
+
const active: ActiveTouch[] = []
|
|
41
|
+
|
|
42
|
+
const onTouchStart = (e: TouchStartEvent<Node | null>): void => {
|
|
43
|
+
const touch: ActiveTouch = { pointerId: e.pointerId, x: e.clientX, y: e.clientY }
|
|
44
|
+
active.push(touch)
|
|
45
|
+
e.track({
|
|
46
|
+
onMove(m) {
|
|
47
|
+
if (active.length === 2) {
|
|
48
|
+
// --- two-finger: pinch-scale + twist-rotate about the gesture axis ---
|
|
49
|
+
const other = active.find((t) => t !== touch)
|
|
50
|
+
if (other) {
|
|
51
|
+
const axis = new Vec2(other.x - touch.x, other.y - touch.y)
|
|
52
|
+
const span = axis.length()
|
|
53
|
+
if (span > 1e-3) {
|
|
54
|
+
const dir = axis.scale(1 / span)
|
|
55
|
+
const delta = new Vec2(m.deltaX, m.deltaY)
|
|
56
|
+
if (pinch) {
|
|
57
|
+
const along = -dir.dot(delta) // movement along the pinch axis
|
|
58
|
+
target.scale = clamp(target.scale.x * (span + along) / span, minScale * 0.4, maxScale * 1.5)
|
|
59
|
+
}
|
|
60
|
+
if (twist) {
|
|
61
|
+
const tangent = dir.perp().dot(delta) // movement across the axis
|
|
62
|
+
target.quaternion = target.quaternion.mul(Quat.fromAxisAngle(Vec3.up, Math.atan2(tangent, span)))
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} else if (active.length === 1 && pan) {
|
|
67
|
+
// --- one-finger: drag across the ground plane, relative to where the camera looks ---
|
|
68
|
+
let dir = scene.camera.forward
|
|
69
|
+
const parent = target.parent
|
|
70
|
+
if (parent) dir = dir.rotate(parent.quaternion.invert()) // into the parent's local frame
|
|
71
|
+
dir = dir.withY(0)
|
|
72
|
+
const k = Math.hypot(dir.x, dir.z)
|
|
73
|
+
const screen = Math.max(...scene.camera.displaySize)
|
|
74
|
+
if (k > 1e-4 && screen > 0) {
|
|
75
|
+
const dist = target.worldPosition.distanceTo(scene.camera.worldPosition)
|
|
76
|
+
const right = new Vec3(-dir.z, 0, dir.x).scale(m.deltaX / k / screen * dist)
|
|
77
|
+
const fwd = dir.scale(-m.deltaY / k / screen * dist)
|
|
78
|
+
const move = right.add(fwd)
|
|
79
|
+
const p = target.position
|
|
80
|
+
target.position = new Vec3(p.x + move.x, p.y, p.z + move.z) // keep height fixed
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
touch.x = m.clientX
|
|
84
|
+
touch.y = m.clientY
|
|
85
|
+
},
|
|
86
|
+
onEnd() {
|
|
87
|
+
const i = active.indexOf(touch)
|
|
88
|
+
if (i >= 0) active.splice(i, 1)
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
scene.addEventListener("touchstart", onTouchStart)
|
|
94
|
+
return { remove() { scene.removeEventListener("touchstart", onTouchStart); active.length = 0 } }
|
|
95
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Tiny shared module to break the Node ↔ Scene ↔ touch dependency cycle. The active 3D scene is set
|
|
2
|
+
// here by Scene.open()/close() and read by the touch dispatch system.
|
|
3
|
+
|
|
4
|
+
import type { Scene } from "./Scene"
|
|
5
|
+
|
|
6
|
+
export const glState: { activeScene: Scene | null } = { activeScene: null }
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// 3D pointer dispatch. Registers the host touch callbacks lazily (first listener) and routes hits to
|
|
2
|
+
// the struck Node and to the active scene's listeners. Ported from worker/src/TouchEventSystem.ts.
|
|
3
|
+
|
|
4
|
+
import { ClickEvent, TouchStartEvent, type TrackHandler } from "../runtime/touch"
|
|
5
|
+
import { nodeRegistry } from "./Node"
|
|
6
|
+
import { glState } from "./state"
|
|
7
|
+
|
|
8
|
+
const onTouchEnd = (pointerId: number, clientX: number, clientY: number, entityId: number): TrackHandler | null => {
|
|
9
|
+
const scene = glState.activeScene
|
|
10
|
+
if (!scene) return null
|
|
11
|
+
const ev = new ClickEvent<any>(clientX, clientY, pointerId)
|
|
12
|
+
if (entityId > 0) {
|
|
13
|
+
const node = nodeRegistry.get(entityId) ?? null
|
|
14
|
+
ev.target = node
|
|
15
|
+
if (node) node._dispatchEvent("click", ev)
|
|
16
|
+
} else {
|
|
17
|
+
ev.target = null
|
|
18
|
+
}
|
|
19
|
+
for (const listener of scene._clickListeners.slice()) listener(ev)
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const onTouchStart = (pointerId: number, clientX: number, clientY: number, entityId: number): TrackHandler | null => {
|
|
24
|
+
const scene = glState.activeScene
|
|
25
|
+
if (!scene) return null
|
|
26
|
+
const ev = new TouchStartEvent<any>(clientX, clientY, pointerId)
|
|
27
|
+
if (entityId > 0) {
|
|
28
|
+
const node = nodeRegistry.get(entityId) ?? null
|
|
29
|
+
ev.target = node
|
|
30
|
+
if (node) {
|
|
31
|
+
node._dispatchEvent("touchstart", ev)
|
|
32
|
+
if (ev._trackHandler) return ev._trackHandler
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
ev.target = null
|
|
36
|
+
}
|
|
37
|
+
for (const listener of scene._touchStartListeners.slice()) {
|
|
38
|
+
listener(ev)
|
|
39
|
+
if (ev._trackHandler) return ev._trackHandler
|
|
40
|
+
}
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let touchStartRegistered = false
|
|
45
|
+
export const registerTouchStartEvent = (): void => {
|
|
46
|
+
if (touchStartRegistered) return
|
|
47
|
+
touchStartRegistered = true
|
|
48
|
+
_creatorUI.registerTouchStartEvent(onTouchStart)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let touchEndRegistered = false
|
|
52
|
+
export const registerTouchEndEvent = (): void => {
|
|
53
|
+
if (touchEndRegistered) return
|
|
54
|
+
touchEndRegistered = true
|
|
55
|
+
_creatorUI.registerTouchEndEvent(onTouchEnd)
|
|
56
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// THE aggregation point. Every public global the SDK provides is re-exported here under the exact
|
|
2
|
+
// name user code uses it as. The bundler passes this file to esbuild's `inject:` option, so user
|
|
3
|
+
// code references `Scene`, `Sprite`, `toast`, … with no imports; esbuild tree-shakes away whatever
|
|
4
|
+
// a given project does not use (so a pure-2D project drops all 3D classes, and vice versa).
|
|
5
|
+
//
|
|
6
|
+
// Rule: this is the ONLY file that `export`s public names. Everything else is internal and imported
|
|
7
|
+
// by it. Host-provided globals (setLoop, console, setTimeout, DEG2RAD, asset, …) are NOT here —
|
|
8
|
+
// they live in src/host.d.ts as declarations and stay as free references at runtime.
|
|
9
|
+
|
|
10
|
+
// ---- math ----
|
|
11
|
+
export { Mathf } from "./math/Mathf"
|
|
12
|
+
export { Vec2, Vec3 } from "./math/vec"
|
|
13
|
+
export { Quat } from "./math/quat"
|
|
14
|
+
export { Mat4 } from "./math/mat4"
|
|
15
|
+
export { Color } from "./core/color"
|
|
16
|
+
|
|
17
|
+
// ---- platform runtime (injected globals; the host provides the lower-level _creator* bridges) ----
|
|
18
|
+
export { toast, SvgSource } from "./runtime/misc"
|
|
19
|
+
export { fetch, fetchLocal, FormData, File } from "./runtime/fetch"
|
|
20
|
+
export { localStorage } from "./runtime/storage"
|
|
21
|
+
export { device } from "./runtime/device"
|
|
22
|
+
export { Input } from "./runtime/input"
|
|
23
|
+
export { WebSocket } from "./runtime/net"
|
|
24
|
+
export { AudioPlayer, VideoPlayer } from "./runtime/media"
|
|
25
|
+
export { openFilePicker } from "./runtime/files"
|
|
26
|
+
export { ClickEvent, TouchStartEvent } from "./runtime/touch"
|
|
27
|
+
|
|
28
|
+
// ---- 2D engine (creator-2d) ----
|
|
29
|
+
export { Scene2D } from "./g2/Scene2D"
|
|
30
|
+
export { Node2D } from "./g2/Node2D"
|
|
31
|
+
export { Sprite } from "./g2/Sprite"
|
|
32
|
+
export { Tilemap } from "./g2/Tilemap"
|
|
33
|
+
export { Texture2D } from "./g2/Texture2D"
|
|
34
|
+
|
|
35
|
+
// ---- UI (creator-ui) ----
|
|
36
|
+
export {
|
|
37
|
+
UIScreen, type UIScreenStyle,
|
|
38
|
+
UIRow, UIColumn, UIBox, type UIContainerStyle,
|
|
39
|
+
UIText, type UITextStyle,
|
|
40
|
+
UIButton, type UIButtonStyle,
|
|
41
|
+
UIImage, type UIImageStyle,
|
|
42
|
+
UIVideo, type UIVideoStyle,
|
|
43
|
+
UIInput, UITextArea, type UIInputStyle,
|
|
44
|
+
UIScrollable, type UIScrollableStyle,
|
|
45
|
+
UIWidget, type UIWidgetStyle,
|
|
46
|
+
UISpacer, type UISpacerStyle,
|
|
47
|
+
UIVirtualizedList,
|
|
48
|
+
Router,
|
|
49
|
+
registerFont,
|
|
50
|
+
} from "./ui/UI"
|
|
51
|
+
|
|
52
|
+
// ---- animate ----
|
|
53
|
+
export { animate, animateMat4, stopAnimation, pauseAnimation, resumeAnimation } from "./animate/animate"
|
|
54
|
+
export { cubicBezier } from "./animate/bezier"
|
|
55
|
+
export { easeIn, easeOut, easeInOut } from "./animate/easings"
|
|
56
|
+
|
|
57
|
+
// ---- plugins (optional, host-provided capabilities — see src/plugins/) ----
|
|
58
|
+
export { QRScanner } from "./plugins/qr"
|
|
59
|
+
|
|
60
|
+
// ---- 3D engine (creator-gl) ----
|
|
61
|
+
export { Scene, ARScene } from "./gl/Scene"
|
|
62
|
+
export { Node } from "./gl/Node"
|
|
63
|
+
export { Mesh } from "./gl/Mesh"
|
|
64
|
+
export { Light } from "./gl/Light"
|
|
65
|
+
export { Material } from "./gl/Material"
|
|
66
|
+
export { Geometry } from "./gl/Geometry"
|
|
67
|
+
export { Texture } from "./gl/Texture"
|
|
68
|
+
export { Camera } from "./gl/Camera"
|
|
69
|
+
export { Ray } from "./gl/Ray"
|
|
70
|
+
export { Plane } from "./gl/Plane"
|
|
71
|
+
export { Noise } from "./gl/Noise"
|
|
72
|
+
export { Particles, dynamic, dynamicColor } from "./gl/Particles"
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Scalar / general math helpers, exposed to user code as the `Mathf` global. Vector & quaternion
|
|
2
|
+
// helpers live with the engine that uses them (vec2 in g2/, vec3+quat in gl/). `smoothDamp` works on
|
|
3
|
+
// both numbers and numeric arrays here (no gl-matrix), which covers 2D (vec2) and 3D position damping.
|
|
4
|
+
|
|
5
|
+
export type Damper<T extends number | number[]> = ((target: T, dt: number) => T) & {
|
|
6
|
+
/** Snap the damper's internal current value (e.g. on teleport). */
|
|
7
|
+
setValue(value: T): void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const clamp = (v: number, min: number, max: number): number => (v < min ? min : v > max ? max : v)
|
|
11
|
+
|
|
12
|
+
const smoothDampNumber = (initial: number, smoothTime: number, maxSpeed: number): Damper<number> => {
|
|
13
|
+
let current = initial
|
|
14
|
+
let velocity = 0
|
|
15
|
+
const damp = ((target: number, dt: number): number => {
|
|
16
|
+
smoothTime = Math.max(0.0001, smoothTime)
|
|
17
|
+
const omega = 2 / smoothTime
|
|
18
|
+
const x = omega * dt
|
|
19
|
+
const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x)
|
|
20
|
+
const maxDelta = maxSpeed * smoothTime
|
|
21
|
+
let delta = clamp(current - target, -maxDelta, maxDelta)
|
|
22
|
+
const newTarget = current - delta
|
|
23
|
+
const temp = (velocity + omega * delta) * dt
|
|
24
|
+
velocity = (velocity - omega * temp) * exp
|
|
25
|
+
current = newTarget + (delta + temp) * exp
|
|
26
|
+
return current
|
|
27
|
+
}) as Damper<number>
|
|
28
|
+
damp.setValue = (v) => { current = v; velocity = 0 }
|
|
29
|
+
return damp
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function smoothDamp(initial: number, smoothTime: number, maxSpeed?: number): Damper<number>
|
|
33
|
+
function smoothDamp(initial: number[], smoothTime: number, maxSpeed?: number): Damper<number[]>
|
|
34
|
+
function smoothDamp(initial: any, smoothTime: number, maxSpeed = Infinity): any {
|
|
35
|
+
return typeof initial === "number"
|
|
36
|
+
? smoothDampNumber(initial, smoothTime, maxSpeed)
|
|
37
|
+
: smoothDampArray(initial, smoothTime, maxSpeed)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const smoothDampArray = (initial: number[], smoothTime: number, maxSpeed: number): Damper<number[]> => {
|
|
41
|
+
const n = initial.length
|
|
42
|
+
let current = initial.slice()
|
|
43
|
+
const velocity = new Array(n).fill(0)
|
|
44
|
+
const damp = ((target: number[], dt: number): number[] => {
|
|
45
|
+
smoothTime = Math.max(0.0001, smoothTime)
|
|
46
|
+
const omega = 2 / smoothTime
|
|
47
|
+
const x = omega * dt
|
|
48
|
+
const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x)
|
|
49
|
+
const maxDelta = maxSpeed * smoothTime
|
|
50
|
+
|
|
51
|
+
const delta = new Array(n)
|
|
52
|
+
let mag = 0
|
|
53
|
+
for (let i = 0; i < n; i++) { delta[i] = current[i] - target[i]; mag += delta[i] * delta[i] }
|
|
54
|
+
mag = Math.sqrt(mag)
|
|
55
|
+
if (mag > maxDelta && mag > 0) {
|
|
56
|
+
const s = maxDelta / mag
|
|
57
|
+
for (let i = 0; i < n; i++) delta[i] *= s
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const out = new Array(n)
|
|
61
|
+
for (let i = 0; i < n; i++) {
|
|
62
|
+
const newTarget = current[i] - delta[i]
|
|
63
|
+
const temp = (velocity[i] + omega * delta[i]) * dt
|
|
64
|
+
velocity[i] = (velocity[i] - omega * temp) * exp
|
|
65
|
+
out[i] = newTarget + (delta[i] + temp) * exp
|
|
66
|
+
}
|
|
67
|
+
current = out
|
|
68
|
+
return out.slice()
|
|
69
|
+
}) as Damper<number[]>
|
|
70
|
+
damp.setValue = (v) => { current = v.slice(); velocity.fill(0) }
|
|
71
|
+
return damp
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const Mathf = {
|
|
75
|
+
PI: Math.PI,
|
|
76
|
+
TAU: Math.PI * 2,
|
|
77
|
+
EPSILON: 1e-6,
|
|
78
|
+
DEG2RAD: Math.PI / 180,
|
|
79
|
+
RAD2DEG: 180 / Math.PI,
|
|
80
|
+
|
|
81
|
+
deg2rad: (deg: number): number => deg * (Math.PI / 180),
|
|
82
|
+
rad2deg: (rad: number): number => rad * (180 / Math.PI),
|
|
83
|
+
|
|
84
|
+
clamp,
|
|
85
|
+
clamp01: (v: number): number => clamp(v, 0, 1),
|
|
86
|
+
lerp: (a: number, b: number, t: number): number => a + (b - a) * t,
|
|
87
|
+
inverseLerp: (a: number, b: number, v: number): number => (a === b ? 0 : (v - a) / (b - a)),
|
|
88
|
+
remap: (v: number, inMin: number, inMax: number, outMin: number, outMax: number): number =>
|
|
89
|
+
outMin + ((v - inMin) / (inMax - inMin)) * (outMax - outMin),
|
|
90
|
+
|
|
91
|
+
/** Move `a` toward `b` by at most `maxDelta`. */
|
|
92
|
+
moveTowards: (a: number, b: number, maxDelta: number): number => {
|
|
93
|
+
const d = b - a
|
|
94
|
+
return Math.abs(d) <= maxDelta ? b : a + Math.sign(d) * maxDelta
|
|
95
|
+
},
|
|
96
|
+
/** Frame-rate-independent exponential approach (good default: lambda 5..15). */
|
|
97
|
+
damp: (a: number, b: number, lambda: number, dt: number): number => a + (b - a) * (1 - Math.exp(-lambda * dt)),
|
|
98
|
+
|
|
99
|
+
sign: Math.sign,
|
|
100
|
+
/** Loop a value into [0, length). */
|
|
101
|
+
repeat: (t: number, length: number): number => clamp(t - Math.floor(t / length) * length, 0, length),
|
|
102
|
+
/** Ping-pong a value in [0, length]. */
|
|
103
|
+
pingPong: (t: number, length: number): number => {
|
|
104
|
+
const r = clamp(t - Math.floor(t / (length * 2)) * (length * 2), 0, length * 2)
|
|
105
|
+
return length - Math.abs(r - length)
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
random: (min = 0, max = 1): number => min + Math.random() * (max - min),
|
|
109
|
+
randomInt: (min: number, max: number): number => Math.floor(min + Math.random() * (max - min + 1)),
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Critically-damped follow (Unity SmoothDamp). Returns a stateful damper you call every frame:
|
|
113
|
+
* const d = Mathf.smoothDamp([x, y], 0.15)
|
|
114
|
+
* pos = d(targetPos, dt)
|
|
115
|
+
* Works on a number or a numeric array (vec2 / vec3).
|
|
116
|
+
*/
|
|
117
|
+
smoothDamp,
|
|
118
|
+
}
|