lecodes-sdk 2.2.8 → 2.2.10
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 +42 -1
- package/dist/types/gl/Camera.d.ts +5 -2
- package/dist/types/gl/Node.d.ts +0 -7
- package/dist/types/gl/Scene.d.ts +6 -24
- package/dist/types/inject.d.ts +5 -1
- package/dist/types/plugin.d.ts +27 -0
- package/dist/types/plugins/gen/ar/sdk/ar.d.ts +228 -0
- package/dist/types/plugins/gen/ar/sdk/ar.gen.d.ts +73 -0
- package/dist/types/plugins/gen/ar/sdk/controls.d.ts +28 -0
- package/dist/types/plugins/gen/audio-recorder/sdk/audio-recorder.d.ts +43 -0
- package/dist/types/plugins/gen/audio-recorder/sdk/audio-recorder.gen.d.ts +47 -0
- package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +6 -2
- package/dist/types/plugins/gen/speech/sdk/speech.d.ts +44 -0
- package/dist/types/plugins/gen/speech/sdk/speech.gen.d.ts +39 -0
- package/dist/types/plugins/gen/webrtc/sdk/webrtc.d.ts +3 -0
- package/dist/types/runtime/permissions.d.ts +16 -0
- package/dist/types/version.d.ts +1 -1
- package/dist/types.json +1 -1
- package/package.json +1 -1
- package/prompts/ar.md +81 -48
- package/prompts/dist/2d-game.md +1567 -0
- package/prompts/dist/3d-app.md +1712 -0
- package/prompts/dist/ar-app.md +1585 -0
- package/prompts/dist/concept.md +49 -0
- package/prompts/dist/design.md +521 -0
- package/prompts/dist/ui-app.md +1175 -0
- package/prompts/index.md +3 -1
- package/src/animate/tween/Timeline.ts +175 -175
- package/src/bridges/device.d.ts +20 -6
- package/src/bridges/gl.d.ts +33 -19
- package/src/compile/header.ts +11 -4
- package/src/gl/Camera.ts +5 -2
- package/src/gl/CameraPlace.ts +52 -52
- package/src/gl/Light.ts +257 -257
- package/src/gl/Mesh.ts +120 -120
- package/src/gl/Node.ts +0 -8
- package/src/gl/Scene.ts +14 -81
- package/src/gl/animation/DynamicBone.ts +482 -482
- package/src/gl/scenarios.ts +291 -291
- package/src/inject.ts +13 -1
- package/src/plugin.ts +45 -2
- package/src/plugins/gen/ar/contract.d.ts +118 -0
- package/src/plugins/gen/ar/sdk/ar.gen.ts +112 -0
- package/src/plugins/gen/ar/sdk/ar.ts +699 -0
- package/src/plugins/gen/ar/sdk/controls.ts +148 -0
- package/src/plugins/gen/audio-recorder/contract.d.ts +64 -0
- package/src/plugins/gen/audio-recorder/sdk/audio-recorder.gen.ts +76 -0
- package/src/plugins/gen/audio-recorder/sdk/audio-recorder.ts +113 -0
- package/src/plugins/gen/geolocation/contract.d.ts +7 -5
- package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +6 -2
- package/src/plugins/gen/speech/contract.d.ts +67 -0
- package/src/plugins/gen/speech/sdk/speech.gen.ts +64 -0
- package/src/plugins/gen/speech/sdk/speech.ts +104 -0
- package/src/plugins/gen/webrtc/sdk/webrtc.ts +19 -14
- package/src/runtime/permissions.ts +40 -0
- package/src/server/auth/host.ts +1 -1
- package/src/ui/presentable.ts +5 -0
- package/src/ui/router.ts +4 -3
- package/src/version.ts +1 -1
- package/dist/types/gl/controls.d.ts +0 -19
- package/dist/types/plugins/permission.d.ts +0 -1
- package/src/gl/controls.ts +0 -95
- package/src/plugins/permission.ts +0 -8
package/src/gl/Mesh.ts
CHANGED
|
@@ -1,120 +1,120 @@
|
|
|
1
|
-
// A renderable 3D node from raw geometry — the primitive shapes (box/sphere/cylinder/capsule/plane) and
|
|
2
|
-
// Mesh.from for a custom Geometry. A GLB import is a different kind: see Model.load.
|
|
3
|
-
|
|
4
|
-
import { cx, cy, cz, type Vec3Like } from "../math/vec"
|
|
5
|
-
import {
|
|
6
|
-
Geometry,
|
|
7
|
-
type CapsuleOptions,
|
|
8
|
-
type CylinderOptions,
|
|
9
|
-
type PlaneOptions,
|
|
10
|
-
type SphereOptions,
|
|
11
|
-
} from "./Geometry"
|
|
12
|
-
import { Material } from "./Material"
|
|
13
|
-
import { Node } from "./Node"
|
|
14
|
-
|
|
15
|
-
/** Common transform/render options every primitive factory accepts. */
|
|
16
|
-
export type MeshOptions = {
|
|
17
|
-
material?: Material
|
|
18
|
-
position?: Vec3Like
|
|
19
|
-
eulerAngles?: Vec3Like
|
|
20
|
-
scale?: Vec3Like | number
|
|
21
|
-
name?: string
|
|
22
|
-
castShadows?: boolean
|
|
23
|
-
receiveShadows?: boolean
|
|
24
|
-
/** Coarse draw order, 0 (first) … 7 (last); default 4. See `Mesh.renderPriority`. */
|
|
25
|
-
renderPriority?: number
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export class Mesh extends Node {
|
|
29
|
-
private _geometry?: Geometry
|
|
30
|
-
|
|
31
|
-
constructor(geometry?: Geometry, material?: Material) {
|
|
32
|
-
super()
|
|
33
|
-
if (geometry) {
|
|
34
|
-
const mat = material ?? Material.unlit()
|
|
35
|
-
this._geometry = geometry
|
|
36
|
-
this._materials = [ mat ]
|
|
37
|
-
_creator.setMesh(this.id, Material.idOf(mat), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind, geometry.uv1, geometry.colors)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
override get geometry(): Geometry | undefined { return this._geometry }
|
|
42
|
-
/** Replace the geometry in place — the node, its transform and its material stay, the vertex and
|
|
43
|
-
* index buffers are rebuilt. What an editor overlay or a debug drawer redraws with. */
|
|
44
|
-
setGeometry(geometry: Geometry): this {
|
|
45
|
-
this._geometry = geometry
|
|
46
|
-
_creator.setMesh(this.id, Material.idOf(this._materials![0]!), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind, geometry.uv1, geometry.colors)
|
|
47
|
-
// the host rebuilds the renderable for a new geometry (creator-gl createMesh destroys + builds), which
|
|
48
|
-
// resets its priority / culling / shadow flags to the defaults — an overlay redrawn every frame lost its
|
|
49
|
-
// renderPriority 7 after one frame and hid inside the model. What was set on this mesh is set again.
|
|
50
|
-
if (this._renderPriority !== undefined) this.renderPriority = this._renderPriority
|
|
51
|
-
if (this._culling !== undefined) this.culling = this._culling
|
|
52
|
-
if (this._castShadows !== undefined) this.castShadows = this._castShadows
|
|
53
|
-
if (this._receiveShadows !== undefined) this.receiveShadows = this._receiveShadows
|
|
54
|
-
return this
|
|
55
|
-
}
|
|
56
|
-
private _renderPriority?: number
|
|
57
|
-
private _culling?: boolean
|
|
58
|
-
private _castShadows?: boolean
|
|
59
|
-
private _receiveShadows?: boolean
|
|
60
|
-
|
|
61
|
-
/** A Mesh always carries a material (slot 0) — see Node.setMaterial for the slot API. */
|
|
62
|
-
override get material(): Material { return this._materials![0]! }
|
|
63
|
-
override set material(m: Material) { this.setMaterial(m, 0) }
|
|
64
|
-
|
|
65
|
-
set culling(v: boolean) { this._culling = v; _creator.setCulling(this.id, v) }
|
|
66
|
-
/** Coarse draw order within the frame: 0 draws first, 7 last, 4 is the default (Filament's
|
|
67
|
-
* renderable priority; within one priority opaque draws sort front-to-back, blended back-to-front).
|
|
68
|
-
* Something drawn over the scene with `Material.depthTest = false` goes in 7, so nothing drawn
|
|
69
|
-
* after it can cover it. Write-only; a host that predates the call ignores it. */
|
|
70
|
-
set renderPriority(v: number) {
|
|
71
|
-
this._renderPriority = v
|
|
72
|
-
if (_creator.setRenderPriority) _creator.setRenderPriority(this.id, Math.max(0, Math.min(7, Math.round(v))))
|
|
73
|
-
else if (!Mesh._warnedPriority) { Mesh._warnedPriority = true; console.warn("[mesh] this host has no setRenderPriority — renderPriority ignored") }
|
|
74
|
-
}
|
|
75
|
-
private static _warnedPriority = false
|
|
76
|
-
set castShadows(v: boolean) { this._castShadows = v; _creator.setCastShadows(this.id, v) }
|
|
77
|
-
set receiveShadows(v: boolean) { this._receiveShadows = v; _creator.setReceiveShadows(this.id, v) }
|
|
78
|
-
|
|
79
|
-
// --- factories ---
|
|
80
|
-
|
|
81
|
-
static box(options: MeshOptions & { size?: Vec3Like | number } = {}): Mesh {
|
|
82
|
-
// the size goes into the geometry (not a scale after): the lightmap chart is laid out per face area
|
|
83
|
-
return apply(new Mesh(Geometry.box(options.size ?? 1), options.material), options)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
static sphere(options: MeshOptions & SphereOptions = {}): Mesh {
|
|
87
|
-
return apply(new Mesh(Geometry.sphere(options), options.material), options)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
static cylinder(options: MeshOptions & CylinderOptions = {}): Mesh {
|
|
91
|
-
return apply(new Mesh(Geometry.cylinder(options), options.material), options)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** A capsule along Y (`radius`, `length` between the cap centres) — a character's or a collider's shape in one mesh. */
|
|
95
|
-
static capsule(options: MeshOptions & CapsuleOptions = {}): Mesh {
|
|
96
|
-
return apply(new Mesh(Geometry.capsule(options), options.material), options)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
static plane(options: MeshOptions & PlaneOptions = {}): Mesh {
|
|
100
|
-
return apply(new Mesh(Geometry.plane(options), options.material), options)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Wrap a custom Geometry. */
|
|
104
|
-
static from(geometry: Geometry, options: MeshOptions = {}): Mesh {
|
|
105
|
-
return apply(new Mesh(geometry, options.material), options)
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const sizeToVec = (s: Vec3Like | number): [number, number, number] => (typeof s === "number" ? [ s, s, s ] : [ cx(s), cy(s), cz(s) ])
|
|
110
|
-
|
|
111
|
-
const apply = (mesh: Mesh, o: MeshOptions): Mesh => {
|
|
112
|
-
if (o.position) mesh.position = o.position
|
|
113
|
-
if (o.eulerAngles) mesh.eulerAngles = o.eulerAngles
|
|
114
|
-
if (o.scale !== undefined) mesh.scale = typeof o.scale === "number" ? [ o.scale, o.scale, o.scale ] : o.scale
|
|
115
|
-
if (o.name !== undefined) mesh.name = o.name
|
|
116
|
-
if (o.castShadows !== undefined) mesh.castShadows = o.castShadows
|
|
117
|
-
if (o.receiveShadows !== undefined) mesh.receiveShadows = o.receiveShadows
|
|
118
|
-
if (o.renderPriority !== undefined) mesh.renderPriority = o.renderPriority
|
|
119
|
-
return mesh
|
|
120
|
-
}
|
|
1
|
+
// A renderable 3D node from raw geometry — the primitive shapes (box/sphere/cylinder/capsule/plane) and
|
|
2
|
+
// Mesh.from for a custom Geometry. A GLB import is a different kind: see Model.load.
|
|
3
|
+
|
|
4
|
+
import { cx, cy, cz, type Vec3Like } from "../math/vec"
|
|
5
|
+
import {
|
|
6
|
+
Geometry,
|
|
7
|
+
type CapsuleOptions,
|
|
8
|
+
type CylinderOptions,
|
|
9
|
+
type PlaneOptions,
|
|
10
|
+
type SphereOptions,
|
|
11
|
+
} from "./Geometry"
|
|
12
|
+
import { Material } from "./Material"
|
|
13
|
+
import { Node } from "./Node"
|
|
14
|
+
|
|
15
|
+
/** Common transform/render options every primitive factory accepts. */
|
|
16
|
+
export type MeshOptions = {
|
|
17
|
+
material?: Material
|
|
18
|
+
position?: Vec3Like
|
|
19
|
+
eulerAngles?: Vec3Like
|
|
20
|
+
scale?: Vec3Like | number
|
|
21
|
+
name?: string
|
|
22
|
+
castShadows?: boolean
|
|
23
|
+
receiveShadows?: boolean
|
|
24
|
+
/** Coarse draw order, 0 (first) … 7 (last); default 4. See `Mesh.renderPriority`. */
|
|
25
|
+
renderPriority?: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class Mesh extends Node {
|
|
29
|
+
private _geometry?: Geometry
|
|
30
|
+
|
|
31
|
+
constructor(geometry?: Geometry, material?: Material) {
|
|
32
|
+
super()
|
|
33
|
+
if (geometry) {
|
|
34
|
+
const mat = material ?? Material.unlit()
|
|
35
|
+
this._geometry = geometry
|
|
36
|
+
this._materials = [ mat ]
|
|
37
|
+
_creator.setMesh(this.id, Material.idOf(mat), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind, geometry.uv1, geometry.colors)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
override get geometry(): Geometry | undefined { return this._geometry }
|
|
42
|
+
/** Replace the geometry in place — the node, its transform and its material stay, the vertex and
|
|
43
|
+
* index buffers are rebuilt. What an editor overlay or a debug drawer redraws with. */
|
|
44
|
+
setGeometry(geometry: Geometry): this {
|
|
45
|
+
this._geometry = geometry
|
|
46
|
+
_creator.setMesh(this.id, Material.idOf(this._materials![0]!), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind, geometry.uv1, geometry.colors)
|
|
47
|
+
// the host rebuilds the renderable for a new geometry (creator-gl createMesh destroys + builds), which
|
|
48
|
+
// resets its priority / culling / shadow flags to the defaults — an overlay redrawn every frame lost its
|
|
49
|
+
// renderPriority 7 after one frame and hid inside the model. What was set on this mesh is set again.
|
|
50
|
+
if (this._renderPriority !== undefined) this.renderPriority = this._renderPriority
|
|
51
|
+
if (this._culling !== undefined) this.culling = this._culling
|
|
52
|
+
if (this._castShadows !== undefined) this.castShadows = this._castShadows
|
|
53
|
+
if (this._receiveShadows !== undefined) this.receiveShadows = this._receiveShadows
|
|
54
|
+
return this
|
|
55
|
+
}
|
|
56
|
+
private _renderPriority?: number
|
|
57
|
+
private _culling?: boolean
|
|
58
|
+
private _castShadows?: boolean
|
|
59
|
+
private _receiveShadows?: boolean
|
|
60
|
+
|
|
61
|
+
/** A Mesh always carries a material (slot 0) — see Node.setMaterial for the slot API. */
|
|
62
|
+
override get material(): Material { return this._materials![0]! }
|
|
63
|
+
override set material(m: Material) { this.setMaterial(m, 0) }
|
|
64
|
+
|
|
65
|
+
set culling(v: boolean) { this._culling = v; _creator.setCulling(this.id, v) }
|
|
66
|
+
/** Coarse draw order within the frame: 0 draws first, 7 last, 4 is the default (Filament's
|
|
67
|
+
* renderable priority; within one priority opaque draws sort front-to-back, blended back-to-front).
|
|
68
|
+
* Something drawn over the scene with `Material.depthTest = false` goes in 7, so nothing drawn
|
|
69
|
+
* after it can cover it. Write-only; a host that predates the call ignores it. */
|
|
70
|
+
set renderPriority(v: number) {
|
|
71
|
+
this._renderPriority = v
|
|
72
|
+
if (_creator.setRenderPriority) _creator.setRenderPriority(this.id, Math.max(0, Math.min(7, Math.round(v))))
|
|
73
|
+
else if (!Mesh._warnedPriority) { Mesh._warnedPriority = true; console.warn("[mesh] this host has no setRenderPriority — renderPriority ignored") }
|
|
74
|
+
}
|
|
75
|
+
private static _warnedPriority = false
|
|
76
|
+
set castShadows(v: boolean) { this._castShadows = v; _creator.setCastShadows(this.id, v) }
|
|
77
|
+
set receiveShadows(v: boolean) { this._receiveShadows = v; _creator.setReceiveShadows(this.id, v) }
|
|
78
|
+
|
|
79
|
+
// --- factories ---
|
|
80
|
+
|
|
81
|
+
static box(options: MeshOptions & { size?: Vec3Like | number } = {}): Mesh {
|
|
82
|
+
// the size goes into the geometry (not a scale after): the lightmap chart is laid out per face area
|
|
83
|
+
return apply(new Mesh(Geometry.box(options.size ?? 1), options.material), options)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
static sphere(options: MeshOptions & SphereOptions = {}): Mesh {
|
|
87
|
+
return apply(new Mesh(Geometry.sphere(options), options.material), options)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static cylinder(options: MeshOptions & CylinderOptions = {}): Mesh {
|
|
91
|
+
return apply(new Mesh(Geometry.cylinder(options), options.material), options)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** A capsule along Y (`radius`, `length` between the cap centres) — a character's or a collider's shape in one mesh. */
|
|
95
|
+
static capsule(options: MeshOptions & CapsuleOptions = {}): Mesh {
|
|
96
|
+
return apply(new Mesh(Geometry.capsule(options), options.material), options)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static plane(options: MeshOptions & PlaneOptions = {}): Mesh {
|
|
100
|
+
return apply(new Mesh(Geometry.plane(options), options.material), options)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Wrap a custom Geometry. */
|
|
104
|
+
static from(geometry: Geometry, options: MeshOptions = {}): Mesh {
|
|
105
|
+
return apply(new Mesh(geometry, options.material), options)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const sizeToVec = (s: Vec3Like | number): [number, number, number] => (typeof s === "number" ? [ s, s, s ] : [ cx(s), cy(s), cz(s) ])
|
|
110
|
+
|
|
111
|
+
const apply = (mesh: Mesh, o: MeshOptions): Mesh => {
|
|
112
|
+
if (o.position) mesh.position = o.position
|
|
113
|
+
if (o.eulerAngles) mesh.eulerAngles = o.eulerAngles
|
|
114
|
+
if (o.scale !== undefined) mesh.scale = typeof o.scale === "number" ? [ o.scale, o.scale, o.scale ] : o.scale
|
|
115
|
+
if (o.name !== undefined) mesh.name = o.name
|
|
116
|
+
if (o.castShadows !== undefined) mesh.castShadows = o.castShadows
|
|
117
|
+
if (o.receiveShadows !== undefined) mesh.receiveShadows = o.receiveShadows
|
|
118
|
+
if (o.renderPriority !== undefined) mesh.renderPriority = o.renderPriority
|
|
119
|
+
return mesh
|
|
120
|
+
}
|
package/src/gl/Node.ts
CHANGED
|
@@ -68,10 +68,6 @@ export const nodeOf = (id: number): Node => {
|
|
|
68
68
|
export type NodeEvents = {
|
|
69
69
|
click: (ev: ClickEvent<Node | null>) => void
|
|
70
70
|
touchstart: (ev: TouchStartEvent<Node | null>) => void
|
|
71
|
-
/** AR anchors only (`scene.root` / `scene.createAnchor()`): the anchor began tracking. */
|
|
72
|
-
track: () => void
|
|
73
|
-
/** AR anchors only: the anchor lost tracking. */
|
|
74
|
-
untrack: () => void
|
|
75
71
|
/** A physics contact/trigger overlap began (the other body's node). Needs a Shape + Physics/Trigger. */
|
|
76
72
|
enter: (other: Node) => void
|
|
77
73
|
/** A physics contact/trigger overlap ended. */
|
|
@@ -106,10 +102,6 @@ export class Node extends AspectHost<NodeEvents> implements CompWriter {
|
|
|
106
102
|
/** Native entity id. */
|
|
107
103
|
get id(): number { return this._h.id }
|
|
108
104
|
|
|
109
|
-
/** AR anchors only — whether the anchor is currently being tracked. Kept in step with the
|
|
110
|
-
* 'track' / 'untrack' events; `false` for any non-anchor node. */
|
|
111
|
-
isTracked = false
|
|
112
|
-
|
|
113
105
|
private _matrix?: Float32Array
|
|
114
106
|
private _worldMatrix?: Float32Array
|
|
115
107
|
private _boneCache?: Map<string, Node | null>
|
package/src/gl/Scene.ts
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
// A 3D scene: a draw set, a Camera, lights and environment. Opening a scene asks the host to bring up
|
|
2
2
|
// the 3D engine; only the active scene renders. Grown from the Phase 0 seed. Ported from
|
|
3
|
-
// worker/src/Scene.ts
|
|
3
|
+
// worker/src/Scene.ts, with the camera exposed as scene.camera. AR is the "ar" plugin's ARScene (a
|
|
4
|
+
// Scene whose camera and background its session drives — docs/plans/ar-plugin-plan.md); VRScene is here.
|
|
4
5
|
//
|
|
5
6
|
// Lights are explicit: a scene gets image-based ambient (IBL) by default; add a Light.sun() for
|
|
6
7
|
// direct light + shadows.
|
|
7
8
|
|
|
8
9
|
import { Color, type ColorInput } from "../core/color"
|
|
9
10
|
import { _installAspectFrames, _installTimeScale, _attachSystem, _detachSystem, _detachAllSystems, type Aspect, type AspectCtor, type FieldOf, type TargetOf } from "../core/Aspect"
|
|
10
|
-
import { _bumpNavEpoch,
|
|
11
|
+
import { _bumpNavEpoch, _setCurrent, Presentable, type DestTuple, type DismissOptions, type PresentOptions } from "../ui/presentable"
|
|
11
12
|
import { _transitionId, TRANSITION_NONE } from "../ui/transitions"
|
|
12
13
|
import { TREE_DEST_SCENE3D, TREE_FLAG_BACK, ensureEmitter, registerDest, unregisterDest } from "../ui/tree"
|
|
13
14
|
import { _surfaceWidgets, type LayerElement, type WidgetSet } from "../ui/UILayer"
|
|
14
15
|
import { device } from "../runtime/device"
|
|
16
|
+
import { File } from "../runtime/fetch"
|
|
15
17
|
import type { ClickEvent, TouchStartEvent } from "../runtime/touch"
|
|
16
|
-
import type { FetchResponse } from "../runtime/fetch"
|
|
17
|
-
import { _requestCameraPermission } from "../plugins/permission"
|
|
18
18
|
import { Camera } from "./Camera"
|
|
19
19
|
import { SceneAudio } from "./audio/SceneAudio"
|
|
20
|
-
import { attachControls, type ControlsHandle, type ControlsOptions } from "./controls"
|
|
21
20
|
import { Material } from "./Material"
|
|
22
21
|
import { PostProcessing, type PostProcessingOptions } from "./postProcessing"
|
|
23
22
|
import { Texture } from "./Texture"
|
|
@@ -409,6 +408,16 @@ export class Scene implements Presentable {
|
|
|
409
408
|
return new Promise<void>((res) => _creator.warmRender(this._id, res))
|
|
410
409
|
}
|
|
411
410
|
|
|
411
|
+
/** The scene's picture as a PNG `File`: the composite of its next frame — the scene, its overlays
|
|
412
|
+
* and its background (an AR scene's camera image), not the UI over them — encoded by the host
|
|
413
|
+
* (a share, an upload, a `UIImage` source). Rejects when the scene is not the one on screen, or
|
|
414
|
+
* something else is shown before the frame. */
|
|
415
|
+
capture(): Promise<File> {
|
|
416
|
+
return new Promise<File>((res, rej) => _creator.captureImage(this._id,
|
|
417
|
+
(bufferId, name, size) => res(new File(bufferId, name, size)),
|
|
418
|
+
() => rej(new Error("Scene.capture(): the scene is not the one on screen"))))
|
|
419
|
+
}
|
|
420
|
+
|
|
412
421
|
/** Precompile the scene's shader variants so nothing is compiled mid-game. Every material the host
|
|
413
422
|
* knows is queued for the sun / shadow / fog / skinning variants and the dynamic-light key — the
|
|
414
423
|
* first point light (a muzzle flash, an explosion) would otherwise rebuild every lit shader in view
|
|
@@ -516,82 +525,6 @@ export class Scene implements Presentable {
|
|
|
516
525
|
}
|
|
517
526
|
}
|
|
518
527
|
|
|
519
|
-
// --- AR ---
|
|
520
|
-
|
|
521
|
-
export type ARMode = "default" | "markers" | "arcore" | "detached"
|
|
522
|
-
|
|
523
|
-
const onTrackStateChange = (entityId: number, track: boolean): void => {
|
|
524
|
-
const node = nodes.get(entityId)
|
|
525
|
-
if (!node) return
|
|
526
|
-
node.isTracked = track
|
|
527
|
-
if (track) node._dispatchEvent("track")
|
|
528
|
-
else node._dispatchEvent("untrack")
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
export class ARScene<T extends ARMode = "default"> extends Scene {
|
|
532
|
-
readonly mode: T
|
|
533
|
-
useWarmRender = true
|
|
534
|
-
private _root?: Node
|
|
535
|
-
|
|
536
|
-
constructor(options: SceneOptions & { mode?: T } = {}) {
|
|
537
|
-
super(options)
|
|
538
|
-
this.mode = options.mode ?? ("default" as T)
|
|
539
|
-
_creator.createARController(this._id, this.camera.id, this.mode, onTrackStateChange)
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
/** Root anchor node (not available in marker mode). */
|
|
543
|
-
get root(): Node {
|
|
544
|
-
if (this._root) return this._root
|
|
545
|
-
this._root = new Node(_creator.createRootAnchor(this._id))
|
|
546
|
-
return this._root
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
/** Create an anchor from a tracking source (e.g. an image target). */
|
|
550
|
-
createAnchor(source: FetchResponse, physicalWidth = 0.2): Node {
|
|
551
|
-
return new Node(_creator.createAnchor(this._id, physicalWidth, (source as any)._h.id))
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
* Let the user place & manipulate a node with touch gestures — the staple AR object-placement
|
|
556
|
-
* interaction: ONE finger drags it across the floor (relative to where the camera looks), TWO fingers
|
|
557
|
-
* pinch to scale and twist to rotate. Returns a handle; call `.remove()` to detach. Tunable via
|
|
558
|
-
* options (min/max scale, and toggles for `pan` / `pinch` / `twist`).
|
|
559
|
-
*/
|
|
560
|
-
addControls(target: Node, options: ControlsOptions = {}): ControlsHandle {
|
|
561
|
-
return attachControls(this, target, options)
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
override async open(options?: PresentOptions): Promise<void> {
|
|
565
|
-
await this._prepare()
|
|
566
|
-
super.open(options)
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
/** @internal Presentable prepare hook: camera permission + warm render + AR session, all while
|
|
570
|
-
* the previous destination (typically a loading screen) stays visible — the swap to this scene
|
|
571
|
-
* happens only after it resolves. Warm render is headless on every platform (offscreen
|
|
572
|
-
* swapchain), so no GL view is created here — the host mounts the scene view at present time.
|
|
573
|
-
* Rejects on camera denial / AR launch failure, or if another navigation happened while
|
|
574
|
-
* preparing (superseded) — in every rejection case the current destination stays visible. */
|
|
575
|
-
async _prepare(): Promise<void> {
|
|
576
|
-
const epoch = _navEpoch()
|
|
577
|
-
await _requestCameraPermission()
|
|
578
|
-
if (this.useWarmRender) await this.warmRender()
|
|
579
|
-
await new Promise<void>((res, rej) => _creator.launchAR(this._id, res, rej))
|
|
580
|
-
if (_navEpoch() !== epoch) {
|
|
581
|
-
_creator.stopAR()
|
|
582
|
-
throw new Error("ARScene.open() superseded: another destination was opened while preparing")
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
override close(options?: DismissOptions): void {
|
|
587
|
-
// The session is this scene's unless another ARScene is on screen now: a late close of a scene
|
|
588
|
-
// something already replaced must not stop the next one's session.
|
|
589
|
-
const current = Presentable.current
|
|
590
|
-
if (!(current instanceof ARScene) || current === this) _creator.stopAR()
|
|
591
|
-
super.close(options)
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
528
|
// --- VR ---
|
|
596
529
|
|
|
597
530
|
/**
|