lecodes-sdk 2.4.6 → 2.4.8
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 +7 -0
- package/dist/types/gl/Camera.d.ts +100 -13
- package/dist/types/gl/Lightmap.d.ts +25 -4
- package/dist/types/gl/Model.d.ts +4 -4
- package/dist/types/gl/Node.d.ts +13 -0
- package/dist/types/gl/ReflectionProbe.d.ts +87 -0
- package/dist/types/gl/RenderTexture.d.ts +29 -0
- package/dist/types/gl/Scene.d.ts +21 -0
- package/dist/types/inject.d.ts +4 -0
- package/dist/types/scene/defineScene.d.ts +15 -2
- package/dist/types/ui/tree.d.ts +1 -0
- package/dist/types/version.d.ts +1 -1
- package/dist/types.json +1 -1
- package/package.json +1 -1
- package/src/bridges/gl.d.ts +72 -17
- package/src/gl/Camera.ts +198 -25
- package/src/gl/Lightmap.ts +127 -48
- package/src/gl/Model.ts +4 -4
- package/src/gl/Node.ts +61 -0
- package/src/gl/ReflectionProbe.ts +141 -0
- package/src/gl/RenderTexture.ts +93 -0
- package/src/gl/Scene.ts +54 -2
- package/src/inject.ts +4 -0
- package/src/math/vec.ts +1 -1
- package/src/scene/defineScene.ts +28 -13
- package/src/ui/tree.ts +1 -0
- package/src/version.ts +1 -1
package/package.json
CHANGED
package/src/bridges/gl.d.ts
CHANGED
|
@@ -14,9 +14,11 @@ export type PickBone = { name: string, weight: f32 }
|
|
|
14
14
|
* `bones` = its JOINTS_0 / WEIGHTS_0 pairs with weight > 0 (empty = unweighted). */
|
|
15
15
|
export type PickVertex = { index: u32, bind: [f32, f32, f32], world: [f32, f32, f32], bones: PickBone[] }
|
|
16
16
|
/** The closest triangle under a screen point (`pickTriangle`). `node` = the mesh node's own entity (0
|
|
17
|
-
* when the host has none), `
|
|
17
|
+
* when the host has none), `lightmapOrdinal` = the primitive's index over the file's meshes (the bake's record
|
|
18
|
+
* `<key>@<ordinal>`), `uv1` (the lightmap uv the GPU reads: the bake's packed one when loaded) only when the primitive
|
|
19
|
+
* carries one. */
|
|
18
20
|
export type TrianglePick = {
|
|
19
|
-
entity: u32, node: u32, nodeName: string,
|
|
21
|
+
entity: u32, node: u32, nodeName: string, lightmapOrdinal: u32,
|
|
20
22
|
mesh: string, primitive: u32, material: string,
|
|
21
23
|
triangle: u32, backface: boolean, distance: f32,
|
|
22
24
|
point: [f32, f32, f32], bary: [f32, f32, f32], uv1?: [f32, f32],
|
|
@@ -182,6 +184,10 @@ declare global {
|
|
|
182
184
|
* not the roughness-prefiltered `_ibl.ktx` beside it. Optional: hosts that predate it keep the
|
|
183
185
|
* flat skybox. */
|
|
184
186
|
setSkyboxTexture?(sceneId: u32, fetchId: bytes): void
|
|
187
|
+
/** Optional. Light the scene from a CUBEMAP texture (a render texture's cube, creator.h setEnvironmentTexture): the
|
|
188
|
+
* IBL reflects it, its irradiance and intensity stay; no roughness prefiltering, so only mirror-like reflections are
|
|
189
|
+
* right. The engine holds the texture while it lights; setDefaultIbl replaces it. */
|
|
190
|
+
setEnvironmentTexture?(sceneId: u32, textureId: u32): void
|
|
185
191
|
/** Temporal anti-aliasing (Filament's TAA) for the scene and its overlays: on, MSAA and FXAA are off for the
|
|
186
192
|
* view; `feedback` = the history's share (0.12 default, higher = less ghosting, less smoothing), `sharpness` =
|
|
187
193
|
* the post-TAA sharpen (0–1), `upscale` = the fraction the 3D is rendered at and TAA-upscaled from (0.5–1,
|
|
@@ -323,6 +329,30 @@ declare global {
|
|
|
323
329
|
* re-uploads a sub-rectangle with the creation channel count. Optional (Texture.fromPixels throws). @args data width height channels srgb @count pixelBytes=data @owned GlTexture */
|
|
324
330
|
createTexturePixels?(width: u32, height: u32, channels: u32, data: Uint8Array, srgb: boolean): Handle
|
|
325
331
|
updateTexturePixels?(textureId: u32, x: u32, y: u32, width: u32, height: u32, data: Uint8Array): void
|
|
332
|
+
/** Optional. A RENDER TEXTURE, what a render view draws into (creator.h createRenderTexture): `flags` bit 1 = CUBEMAP
|
|
333
|
+
* (`width` = the edge, `height` ignored), bit 2 = HDR (R11F_G11F_B10F linear light, for lighting); neither = an LDR
|
|
334
|
+
* display texture (SRGB8_A8, the tone-mapped picture); bit 4 = PREFILTER, with both others only (flags 7, a
|
|
335
|
+
* power-of-two edge of 16 at least): a probe filtered for roughness — the id is the 5-level filtered cube, its views
|
|
336
|
+
* draw a raw one behind it, filtered on the GPU at the frame after each completed cube with `samples` samples
|
|
337
|
+
* (0 = 256; read for PREFILTER only). Otherwise an ordinary texture id (setUniformTexture, setSceneBackground); its
|
|
338
|
+
* texel rows run as OpenGL renders, on every backend (a 2D texture's first row = the picture's bottom). 0xFFFFFFFF
|
|
339
|
+
* on failure. @owned GlTexture */
|
|
340
|
+
createRenderTexture?(width: u32, height: u32, flags: u32, samples: u32): Handle
|
|
341
|
+
/** Optional. A RENDER VIEW of the scene into a render texture, from node `cameraEntityId` (its world pose, read every
|
|
342
|
+
* frame; a cube takes its position only): `layerMask` = the layers it sees (setLayer), `fov` vertical degrees (a cube
|
|
343
|
+
* is 90), `flags` bit 1 = no shadows. Drawn each frame its scene is presented, before the scene's own view; freeing the
|
|
344
|
+
* texture takes its views with it. 0 on failure. @owned GlRenderView */
|
|
345
|
+
createRenderView?(sceneId: u32, textureId: u32, cameraEntityId: u32, layerMask: u8, fov: f32, near: f32, far: f32, flags: u32): Handle
|
|
346
|
+
/** Optional. `everyNth` 1 = every frame, N = one render per N frames drawn, counted afresh from this call (the next
|
|
347
|
+
* frame renders); `facesPerFrame` 1..6 for a cube (round-robin). */
|
|
348
|
+
setRenderViewOptions?(renderViewId: u32, enabled: boolean, everyNth: u32, facesPerFrame: u32): void
|
|
349
|
+
/** Optional. The view's projection, live (createRenderView's fov / near / far were the initial values): a 2D view's
|
|
350
|
+
* vertical fov in degrees and clip planes; a cube ignores `fov` (90) and takes the planes. Unknown id = no-op. */
|
|
351
|
+
setRenderViewProjection?(renderViewId: u32, fov: f32, near: f32, far: f32): void
|
|
352
|
+
/** Optional. RGBA8 bytes of a rectangle of a render texture as the next frame leaves it — rows in the texture's order
|
|
353
|
+
* (the first is texel row `y`), `face` 0..5 = +X -X +Y -Y +Z -Z of a cube (0 for 2D), an HDR one clamped to 0..1
|
|
354
|
+
* (linear); null when refused. A GPU readback, for checks and tools. @custom */
|
|
355
|
+
readRenderTexturePixels?(textureId: u32, face: u32, x: u32, y: u32, width: u32, height: u32, onDone: (pixels: Uint8Array | null) => void): void
|
|
326
356
|
|
|
327
357
|
/** A material INSTANCE of these .filamat bytes (an app's own shader, fetched); the material itself is deduplicated by content in the engine. @c createMaterialInstanceFromBytes @owned GlMaterial @throw createMaterial: no .filamat bytes behind this fetch id */
|
|
328
358
|
createMaterial(systemId: bytes): Handle
|
|
@@ -353,6 +383,12 @@ declare global {
|
|
|
353
383
|
|
|
354
384
|
setVisible(entityId: u32, visible: boolean): void
|
|
355
385
|
isVisible(entityId: u32): boolean
|
|
386
|
+
/** Optional. Visibility layers: the entity's explicit layer bits (u8), inherited by its subtree down to a
|
|
387
|
+
* descendant with its own; 0 clears them (inherit again). Nothing set = 0x01. A view draws a renderable
|
|
388
|
+
* when their layers share a bit (the scene's own view sees all). A host without it has no layers. @c setEntityLayer */
|
|
389
|
+
setLayer?(entityId: u32, mask: u8): void
|
|
390
|
+
/** Optional. The entity's effective layer bits: its own, else the nearest ancestor's, else 0x01. @c getEntityLayer */
|
|
391
|
+
getLayer?(entityId: u32): u8
|
|
356
392
|
|
|
357
393
|
/** @custom */
|
|
358
394
|
createGlb(systemId: bytes, onComplete: (root: Handle | null) => void, onReject: () => void): void
|
|
@@ -671,23 +707,31 @@ declare global {
|
|
|
671
707
|
// JoltPhysics. motionType: 0 static / 1 kinematic / 2 dynamic.
|
|
672
708
|
/** @c creatorHasPhysics */
|
|
673
709
|
physicsHasSupport(): boolean
|
|
674
|
-
/** Lightmap bake (engines/bake) — a dev-time tool compiled into the
|
|
710
|
+
/** Lightmap bake (engines/bake) — a dev-time tool compiled into the bake tool lecodes-bake only; `lightmapBake` exists
|
|
675
711
|
* only where `lightmapHasSupport()` is true. Describes the given static instances (GLB roots / Mesh entities;
|
|
676
712
|
* `keysJoined` = one key per id, '
|
|
677
713
|
'-joined), the scene's brightest sun, its point lights (spherical emitters of
|
|
678
|
-
* `lightRadius` metres) and its IBL to the bake, which traces them on the
|
|
714
|
+
* `lightRadius` metres) and its IBL to the bake, which traces them on the GPU (`tracer`: 0 auto = OptiX where an NVIDIA driver
|
|
715
|
+
* with OptiX opens, else Vulkan ray query on any card with hardware ray tracing; 1 OptiX, 2 Vulkan by name) — direct light plus
|
|
679
716
|
* `bounceRays` Lambertian paths of `bounces` vertices per texel (0 / 0 = direct only) with `patchSamples` next-event
|
|
680
717
|
* samples over the bright patches of a first direct pass — and writes
|
|
681
718
|
* `<outDir>/<stem>.bake` + `<stem>-light[_n].ktx2` (irradiance in lux / lightScale, BC6H) + `<stem>-aux[_n].ktx2`
|
|
682
719
|
* (sun / sky visibility + light direction); `split` adds `<stem>-direct[_n]` / `-indirect[_n]` page sets for the
|
|
683
|
-
* SDK's two views; `denoise` runs
|
|
720
|
+
* SDK's two views; `denoise` runs the AI denoiser (`denoiser`: 0 auto = OptiX's on an NVIDIA card, else Intel Open Image Denoise; 1 OptiX, 2 OIDN by name —
|
|
721
|
+
* independent of the tracer) over 0 nothing, 1 the indirect part, 2 + the sky and the lamps, 3 + the sun; `filter` = the texel's reconstruction filter radius in texels (1 = ±1 texel tent, 0.5 = within the texel, 0 = the centre alone);
|
|
684
722
|
* `probeSpacing` > 0 places REFLECTION PROBES on a grid of that many metres and writes `<stem>-probes.ktx2` (a BC6H atlas of octahedral
|
|
685
723
|
* maps: one column of roughness-level tiles per probe, captured with `probeSize`² cube faces and `probeRays` paths per texel; the scene's
|
|
686
724
|
* IBL cubemap is what an escaping ray sees; `probeIndoor` keeps only the probes under a roof (an open share of the sphere of at most 15 %) — the
|
|
687
725
|
* outdoors reflects the sky through the texel's baked sky visibility instead; `volumeSpacing` (metres, 0 = none) bakes THE LIGHT GRID for movers — `<name>.lgrid`, named in the .bake's `volume`: per cell
|
|
688
726
|
* in free space an ambient cube without the direct sun, links between neighbours a ray connects (what `setAmbientCube`
|
|
689
|
-
* is fed from); `minSize` (metres, 0 = off) leaves every instance smaller than that along every axis out of the atlas — an occluder
|
|
690
|
-
* only, `"small": true` + its `light` (an ambient cube baked where it stands) in the .bake, applied by lightmapObjectApply; `
|
|
727
|
+
* is fed from); `minSize` (metres, 0 = off) leaves every record (a mesh of an instance) smaller than that along every axis out of the atlas — an occluder
|
|
728
|
+
* only, `"small": true` + its `light` (an ambient cube baked where it stands) in the .bake, applied by lightmapObjectApply; `thinTexels` (0 = off) does the same to every
|
|
729
|
+
* record none of whose lightmap charts is wider than that many texels across its short side — a railing's tubes, a cable, a pipe run: a strip of 1-3 texels around a tube
|
|
730
|
+
* cannot hold the light's change around it and its uv seam shows as a step; `objectSlab` (metres, 0 = one cube an object) gives a long one of
|
|
731
|
+
* those round(length / objectSlab) cubes along the longest axis of its bounds (16 at most), blended per pixel along it; BY HAND, the same way out of
|
|
732
|
+
* the atlas: every record whose key (`<node key>@<ordinal>`) `cubeKeys` matches — an ECMAScript regular expression, searched, case-sensitive ("" = off; one
|
|
733
|
+
* that does not compile fails the bake: "error: bad --cube pattern") — and every record of the entities in `cubeIds` (of `entityIds`: a node's
|
|
734
|
+
* `lightmap: 'cube'`), whatever their size or charts; `probeLayout` 0 = ROOMS (indoor points slide to their
|
|
691
735
|
* room's middle and merge: one probe per room), 1 = GRID; `probeBox` = 6 floats, min xyz + max xyz, keeps the grid inside that box — null = the
|
|
692
736
|
* receivers' bounds) and the grid into the .bake; `debugDir` (optional) gets the float layers per page. Synchronous; progress on stdout as
|
|
693
737
|
* "[lightmap] …" lines, "[lightmap] done" on success. @c creatorHasLightmapBake */
|
|
@@ -697,7 +741,7 @@ declare global {
|
|
|
697
741
|
sunRays: u32, skyRays: u32, lightRays: u32, lightRadius: f32, sunAngleDeg: f32, bias: f32, seed: u32,
|
|
698
742
|
bounceRays: u32, bounces: u32, patchSamples: u32, split: boolean, denoise: u32, filter: f32,
|
|
699
743
|
probeSpacing: f32, probeSize: u32, probeRays: u32, probeIndoor: boolean, probeLayout: u32, probeBox: Float32Array | null, minSize: f32, volumeSpacing: f32, emissiveNits: f32, detailTexels: u32, debugDir?: string,
|
|
700
|
-
transmit?: string): boolean
|
|
744
|
+
transmit?: string, tracer?: u32, denoiser?: u32, thinTexels?: f32, objectSlab?: f32, cubeKeys?: string, cubeIds?: Uint32Array): boolean
|
|
701
745
|
/** Lightmap consumption: the next createGlb takes lightmap.filamat (the material behind that instance id) for its
|
|
702
746
|
* OPAQUE materials and the masked twin for its MASK materials instead of the ubershader, and keeps TEXCOORD_1;
|
|
703
747
|
* BLEND (glass) materials always keep the ubershader. 0xFFFFFFFF clears. The same call arms the foliage tier. @custom */
|
|
@@ -724,17 +768,22 @@ declare global {
|
|
|
724
768
|
* Returns the probe count; an invalid texture id clears. */
|
|
725
769
|
lightmapProbesSet?(atlasTextureId: u32, oct: u32, levels: u32, perRow: u32, count: u32,
|
|
726
770
|
lox: f32, loy: f32, loz: f32, sx: f32, sy: f32, sz: f32): u32
|
|
727
|
-
/** Bind the light page + the aux page +
|
|
728
|
-
*
|
|
729
|
-
*
|
|
771
|
+
/** Bind the light page + the aux page + a RECORD's rect (uv1 * [sx, sy] + [ox, oy]) and move the renderables it went on
|
|
772
|
+
* to light channel 1 alone — a baked static takes nothing from the real-time sun and lamps (channel 0). The bake's
|
|
773
|
+
* unit is the mesh: a GLB instance is one record per primitive (`<key>@<ordinal>` in the .bake); `ordinal` >= 0 binds
|
|
774
|
+
* that primitive (its index over the file's meshes) of every node that draws it, on a material instance of its own;
|
|
775
|
+
* -1 (the default) = every lightmap-material primitive of the entity (a Mesh entity, a terrain). Returns how many
|
|
730
776
|
* material instances took it (0 = not loaded lightmapped). */
|
|
731
|
-
lightmapApply?(entityId: u32, lightTextureId: u32, auxTextureId: u32, sx: f32, sy: f32, ox: f32, oy: f32,
|
|
777
|
+
lightmapApply?(entityId: u32, lightTextureId: u32, auxTextureId: u32, sx: f32, sy: f32, ox: f32, oy: f32, ordinal?: i32): u32
|
|
732
778
|
/** A SMALL STATIC the atlas left out (the bake's `minSize`: `small` + `light` in the .bake): no rect — the light
|
|
733
|
-
* baked where it stands goes into its material parameters. `data` =
|
|
734
|
-
* bounds
|
|
735
|
-
* (
|
|
736
|
-
*
|
|
737
|
-
|
|
779
|
+
* baked where it stands goes into its material parameters. `data` = 7 + 24 n floats: axis (0 x, 1 y, 2 z), from, to
|
|
780
|
+
* (its bounds along the axis), n (slabs, 1..16); then n AMBIENT CUBES, slab by slab from `from` — per side of the slab's
|
|
781
|
+
* box (+x -x +y -y +z -z) the irradiance rgb in lux and the sky visibility; then probe a, probe b, a's weight (65535 =
|
|
782
|
+
* no probe, 65534 = the sky). Any other count is refused. The shader blends the two slabs around a pixel along the
|
|
783
|
+
* axis. The textures = any page of the level (the probes' records sit under every aux page). `ordinal` as
|
|
784
|
+
* `lightmapApply` (a small record of a GLB is one primitive). Returns the material instances that took it (0 = not
|
|
785
|
+
* loaded lightmapped). @count count=data */
|
|
786
|
+
lightmapObjectApply?(entityId: u32, lightTextureId: u32, auxTextureId: u32, data: Float32Array, ordinal?: i32): u32
|
|
738
787
|
/** BAKED AMBIENT LIGHT for a mover, or anything on a standard or custom LIT shader: every renderable of the GLB
|
|
739
788
|
* instance (or the Mesh entity) takes its diffuse indirect light from `data`'s ambient cube instead of the scene's
|
|
740
789
|
* IBL, and the IBL's reflections through the sky visibility. `data` = 25 or 26 floats: six sides (+x -x +y -y +z -z) of
|
|
@@ -751,6 +800,12 @@ declare global {
|
|
|
751
800
|
* `lightVolumeSample(x, y, z)` = that cube at a point (26 floats, null = no grid or no valid cell near), for tools;
|
|
752
801
|
* `lightVolumeClear()` drops the grid and every mover's cube. */
|
|
753
802
|
lightVolumeLoad?(fetchId: bytes): boolean
|
|
803
|
+
/** THE PACKED LIGHTMAP UV1 (`<name>-uv1.bin`, the .bake's `uv1`): the bake packs every file's lightmap charts into a
|
|
804
|
+
* square of their own and its rects map THAT uv1. `lightmapUv1Load(fetchId)` hands the engine the fetched sidecar
|
|
805
|
+
* (false = not one); every GLB a rect is applied to then takes its file's packed uv1 into TEXCOORD_1, once per asset
|
|
806
|
+
* (assets bound before the call are applied again). `lightmapUv1Clear()` drops it: the models get their own uv1 back. */
|
|
807
|
+
lightmapUv1Load?(fetchId: bytes): boolean
|
|
808
|
+
lightmapUv1Clear?(): void
|
|
754
809
|
lightVolumeTrack?(entityId: u32, on: boolean, fixed?: boolean): void
|
|
755
810
|
lightVolumeClear?(): void
|
|
756
811
|
/** @out 26 @out 26 */
|
package/src/gl/Camera.ts
CHANGED
|
@@ -1,20 +1,49 @@
|
|
|
1
|
-
//
|
|
2
|
-
// screen↔world ray helpers
|
|
1
|
+
// A camera — a Node (so position / lookAt / transform all work) plus its lens. The scene camera is created and attached
|
|
2
|
+
// by Scene (`_attach`: its projection, exposure and the screen↔world ray helpers are the screen's); a SECONDARY camera is
|
|
3
|
+
// `new Camera(options)`, a plain node until `renderToTexture` gives it a render view of its scene (RenderTexture.ts,
|
|
4
|
+
// creator-gl renderview.cpp), started once the node is in a scene (Node.ts awaitScene). Ported from
|
|
5
|
+
// worker/src/components/Camera.ts.
|
|
3
6
|
|
|
4
7
|
import { Vec3 } from "../math/vec"
|
|
5
8
|
import { Aspect } from "../core/Aspect"
|
|
6
|
-
import { Node, type NodeTweenProps } from "./Node"
|
|
9
|
+
import { Node, awaitScene, type NodeTweenProps } from "./Node"
|
|
7
10
|
import { Ray } from "./Ray"
|
|
11
|
+
import { RenderTexture, makeRenderTexture, startRenderView } from "./RenderTexture"
|
|
8
12
|
import { DOM_GL_CAMERA, floatValue, type TweenChannel, type TweenMeta } from "../animate/tween/spec"
|
|
9
13
|
import type { Animation } from "../animate/tween/Animation"
|
|
10
14
|
|
|
11
|
-
/** Animatable camera props on top of the node transform.
|
|
15
|
+
/** Animatable camera props on top of the node transform. `fov` / `near` / `far` are the SCENE camera's: on a secondary
|
|
16
|
+
* camera they throw (set them directly — they are live — or from `animate({ onUpdate })`). */
|
|
12
17
|
export type CameraTweenProps = NodeTweenProps & {
|
|
13
18
|
fov?: number | number[],
|
|
14
19
|
near?: number | number[],
|
|
15
20
|
far?: number | number[],
|
|
16
21
|
}
|
|
17
22
|
|
|
23
|
+
/** `new Camera(options)`: a SECONDARY camera's lens and what its view draws (the scene's own camera is `scene.camera`). */
|
|
24
|
+
export type CameraOptions = {
|
|
25
|
+
/** Vertical field of view in degrees (default 60). Live: `camera.fov`. */
|
|
26
|
+
fov?: number
|
|
27
|
+
/** Near clip distance in metres (default 0.01). Live: `camera.near`. */
|
|
28
|
+
near?: number
|
|
29
|
+
/** Far clip distance in metres (default 1000). Live: `camera.far`. */
|
|
30
|
+
far?: number
|
|
31
|
+
/** The layers its view draws, a bit mask over `Node.layer` (default 0xff = everything): a renderable is drawn when its
|
|
32
|
+
* layers share a bit with these — the player's own body left out of a mirror. Fixed when the view starts. */
|
|
33
|
+
layers?: number
|
|
34
|
+
/** Shadows in its view (default `true`): the view renders shadow maps of its own each time it draws. Fixed when the
|
|
35
|
+
* view starts. */
|
|
36
|
+
shadows?: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** `camera.renderToTexture(options)`: the picture's size. */
|
|
40
|
+
export type RenderToTextureOptions = {
|
|
41
|
+
/** Texels wide, 1..4096 (default 256). */
|
|
42
|
+
width?: number
|
|
43
|
+
/** Texels tall, 1..4096 (default 256). */
|
|
44
|
+
height?: number
|
|
45
|
+
}
|
|
46
|
+
|
|
18
47
|
/** @internal `camera.follow(node)`: copies the node's world pose onto the camera every LATE frame
|
|
19
48
|
* (order 1000 — after every other aspect has moved things). One per camera; lives on the target. */
|
|
20
49
|
export class CameraFollowRig extends Aspect<"__cameraFollow"> {
|
|
@@ -31,16 +60,33 @@ export class CameraFollowRig extends Aspect<"__cameraFollow"> {
|
|
|
31
60
|
/** Projection every host starts a scene with (creator-gl scene.cpp / the lite core agree on these). */
|
|
32
61
|
export const CAMERA_DEFAULTS = { fov: 60, near: 0.01, far: 1000 }
|
|
33
62
|
|
|
63
|
+
/** A texture side: a whole number, 1 at least (the host refuses more than 4096). */
|
|
64
|
+
const side = (n: number): number => Math.max(1, Math.floor(n))
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A camera: a Node (position, `lookAt`, parenting — it looks down its −Z) with a lens, `fov` / `near` / `far`. Two kinds:
|
|
68
|
+
* - THE SCENE CAMERA, `scene.camera`: made by the Scene, it draws the screen. Its exposure, `projectionMatrix` and the
|
|
69
|
+
* ray helpers (`getRay`, `getViewDirection`) are the screen's, and its projection tweens (`animateTo({ fov })`).
|
|
70
|
+
* - A SECONDARY camera, `new Camera({ fov, layers, shadows })`: a plain node until `renderToTexture({ width, height })`
|
|
71
|
+
* gives it a second view of its scene, drawn into a texture every frame the scene is on show — a rear-view mirror, a
|
|
72
|
+
* monitor, a security camera. Place it like any node (`car.add(mirrorCam)`); its lens is live (`mirrorCam.fov = 50`),
|
|
73
|
+
* its view's schedule is `enabled` / `everyNth`. A cube of light for reflections is a `ReflectionProbe`.
|
|
74
|
+
*/
|
|
34
75
|
export class Camera extends Node {
|
|
76
|
+
/** The scene this camera draws the screen of (Scene `_attach`); -1 = a secondary camera. */
|
|
35
77
|
private _sceneId = -1
|
|
36
78
|
|
|
37
|
-
/** Tween `fov` / `near` / `far` (a zoom, a dolly) and the transform — see {@link Node.animateTo}. */
|
|
79
|
+
/** Tween `fov` / `near` / `far` (a zoom, a dolly — the scene camera's) and the transform — see {@link Node.animateTo}. */
|
|
38
80
|
override animateTo(props: CameraTweenProps & TweenMeta): Animation { return super.animateTo(props) }
|
|
39
81
|
override animateFrom(props: CameraTweenProps & TweenMeta): Animation { return super.animateFrom(props) }
|
|
40
|
-
/** @internal The projection channels address the SCENE (one camera per scene on the host)
|
|
82
|
+
/** @internal The projection channels address the SCENE (one camera per scene on the host): a render view's
|
|
83
|
+
* projection has no tween writer, so a secondary camera's throw. */
|
|
41
84
|
override _tweenChannel(prop: string): TweenChannel | null {
|
|
42
85
|
if (prop === "fov" || prop === "near" || prop === "far") {
|
|
43
|
-
|
|
86
|
+
if (this._sceneId < 0) {
|
|
87
|
+
throw new Error(`Camera: a secondary camera's "${prop}" is not animatable (the tween core drives the scene camera's projection only) — set camera.${prop} directly, it is live: per frame, or animate({ from, to, onUpdate: (v) => { camera.${prop} = v } })`)
|
|
88
|
+
}
|
|
89
|
+
const id = () => this._sceneId + 1 // scene ids start at 0; 0 = unaddressable
|
|
44
90
|
const commit = (v: unknown) => {
|
|
45
91
|
if (prop === "fov") this._fov = v as number
|
|
46
92
|
else if (prop === "near") this._near = v as number
|
|
@@ -51,11 +97,12 @@ export class Camera extends Node {
|
|
|
51
97
|
}
|
|
52
98
|
return super._tweenChannel(prop)
|
|
53
99
|
}
|
|
54
|
-
/**
|
|
100
|
+
/** The scene camera's live projection matrix (updated by the host when the viewport / fov changes). A secondary
|
|
101
|
+
* camera's stays zero: its projection is `fov` / `near` / `far` at its texture's aspect. */
|
|
55
102
|
readonly projectionMatrix = new Float32Array(16)
|
|
56
103
|
|
|
57
|
-
// Projection the app asked for (host defaults until it does). The host owns the aspect and
|
|
58
|
-
//
|
|
104
|
+
// Projection the app asked for (host defaults until it does). The scene camera's host owns the aspect and re-applies
|
|
105
|
+
// these on every viewport change, so they are set, not maintained; a secondary camera's are its view's.
|
|
59
106
|
private _fov = CAMERA_DEFAULTS.fov
|
|
60
107
|
private _near = CAMERA_DEFAULTS.near
|
|
61
108
|
private _far = CAMERA_DEFAULTS.far
|
|
@@ -69,7 +116,29 @@ export class Camera extends Node {
|
|
|
69
116
|
private _ev = 0
|
|
70
117
|
private _exposureSet = false
|
|
71
118
|
|
|
72
|
-
|
|
119
|
+
// A secondary camera's view (renderToTexture): what it draws with, fixed when it starts, and its schedule.
|
|
120
|
+
private readonly _layers: number
|
|
121
|
+
private readonly _shadows: boolean
|
|
122
|
+
private _enabled = true
|
|
123
|
+
private _everyNth = 1
|
|
124
|
+
/** @internal The texture `renderToTexture` made (held: it lives as long as the camera does). */
|
|
125
|
+
_texture: RenderTexture | null = null
|
|
126
|
+
/** @internal The render view's owned handle (`_view.id` = the engine's view id), null until the view starts: it lives
|
|
127
|
+
* exactly as long as this camera holds it. */
|
|
128
|
+
_view: Handle | null = null
|
|
129
|
+
|
|
130
|
+
/** A SECONDARY camera (`scene.camera` is the scene's own): a plain node until `renderToTexture`. `options`: its lens
|
|
131
|
+
* (live after, through `fov` / `near` / `far`) and what its view draws (`layers`, `shadows` — fixed). */
|
|
132
|
+
constructor(options: CameraOptions = {}) {
|
|
133
|
+
super()
|
|
134
|
+
if (options.fov !== undefined) this._fov = options.fov
|
|
135
|
+
if (options.near !== undefined) this._near = options.near
|
|
136
|
+
if (options.far !== undefined) this._far = options.far
|
|
137
|
+
this._layers = (options.layers ?? 0xff) & 0xff
|
|
138
|
+
this._shadows = options.shadows !== false
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** @internal — Scene wires the camera to its native scene id + projection callback: the SCENE camera. */
|
|
73
142
|
_attach(sceneId: number): void {
|
|
74
143
|
this._sceneId = sceneId
|
|
75
144
|
_creator.attachCameraToAR(sceneId, this.id, (mat) => {
|
|
@@ -79,11 +148,90 @@ export class Camera extends Node {
|
|
|
79
148
|
if (this._exposureSet) this._applyExposure()
|
|
80
149
|
}
|
|
81
150
|
|
|
151
|
+
/** @internal What only the scene camera has (the screen's ray helpers): a clear throw on a secondary camera. */
|
|
152
|
+
private _sceneOnly(what: string): void {
|
|
153
|
+
if (this._sceneId < 0) throw new Error(`camera.${what}: the scene camera's (scene.camera) — a secondary camera has no screen to pick through`)
|
|
154
|
+
}
|
|
155
|
+
/** @internal What only a secondary camera has (a render view): a clear throw on the scene camera. */
|
|
156
|
+
private _secondaryOnly(what: string): void {
|
|
157
|
+
if (this._sceneId >= 0) throw new Error(`camera.${what}: the scene camera draws the screen, every frame its scene is on show — a second view is a new Camera() with renderToTexture`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Draw this camera's view of its scene into a new 2D texture of `width × height` texels (default 256 × 256): the
|
|
162
|
+
* tone-mapped picture as the screen would show it (LDR) — `screen.material.map = cam.renderToTexture({ ... })`. The
|
|
163
|
+
* camera holds it (`camera.texture`).
|
|
164
|
+
*
|
|
165
|
+
* THE VIEW starts once the camera is in a scene (added to one, or under a node that is — at once if it already is)
|
|
166
|
+
* and draws every frame that scene is on show, after the frame's update passes (aspects, physics, animation: this
|
|
167
|
+
* frame's poses) and before the scene's own picture. It sees the scene's renderables, lights, environment and sky (not
|
|
168
|
+
* its overlays) from the camera's WORLD pose (scale ignored) through `fov` / `near` / `far` at the texture's aspect,
|
|
169
|
+
* with the `layers` and `shadows` the camera was made with, at the scene camera's exposure; its post-processing is the
|
|
170
|
+
* engine's default tone mapping (the scene's grading, bloom and the rest are not mirrored). Moving the camera to
|
|
171
|
+
* another scene later does not move the view.
|
|
172
|
+
*
|
|
173
|
+
* COST: every render is another pass over the scene — with `shadows`, its own shadow maps too. Keep the texture small,
|
|
174
|
+
* draw less often (`everyNth`), leave out what the view does not need (`layers`), and turn it off out of sight
|
|
175
|
+
* (`enabled = false`).
|
|
176
|
+
*
|
|
177
|
+
* Calling it again REPLACES the texture and the view: the previous view stops at once, its texture keeps its last
|
|
178
|
+
* picture while something still shows it. Throws on the scene camera, and on a host without render textures (the web
|
|
179
|
+
* hosts, for now).
|
|
180
|
+
*/
|
|
181
|
+
renderToTexture(options: RenderToTextureOptions = {}): RenderTexture {
|
|
182
|
+
this._secondaryOnly("renderToTexture")
|
|
183
|
+
const texture = makeRenderTexture("camera.renderToTexture", side(options.width ?? 256), side(options.height ?? 256), 0)
|
|
184
|
+
if (this._view) { // replaced: the old view stops now; its handle goes with this reference
|
|
185
|
+
_creator.setRenderViewOptions?.(this._view.id, false, 1, 6)
|
|
186
|
+
this._view = null
|
|
187
|
+
}
|
|
188
|
+
this._texture = texture
|
|
189
|
+
awaitScene(this)
|
|
190
|
+
return texture
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** @internal Node.ts awaitScene: the camera is in scene `sceneId` — start its view into its texture. */
|
|
194
|
+
_bindView(sceneId: number): void {
|
|
195
|
+
const texture = this._texture
|
|
196
|
+
if (this._view || !texture) return
|
|
197
|
+
this._view = startRenderView("camera", sceneId, texture, this.id, this._layers, this._fov, this._near, this._far, this._shadows)
|
|
198
|
+
if (this._view && (!this._enabled || this._everyNth !== 1)) this._sendSchedule()
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** The texture `renderToTexture` made — the latest, null before one. */
|
|
202
|
+
get texture(): RenderTexture | null { return this._texture }
|
|
203
|
+
|
|
204
|
+
/** Whether this secondary camera's view draws (default `true`). Off keeps the last picture in its texture and costs
|
|
205
|
+
* nothing — a mirror out of sight; back on, it renders on the next frame drawn. The scene camera always draws (reads
|
|
206
|
+
* `true`; setting it throws). */
|
|
207
|
+
get enabled(): boolean { return this._enabled }
|
|
208
|
+
set enabled(on: boolean) {
|
|
209
|
+
this._secondaryOnly("enabled")
|
|
210
|
+
this._enabled = on
|
|
211
|
+
this._sendSchedule()
|
|
212
|
+
}
|
|
213
|
+
/** Render this secondary camera's view once every N frames DRAWN (default 1 = every frame). The count restarts with
|
|
214
|
+
* each change: the next frame drawn renders, then one in every N. The scene camera draws every frame (reads 1;
|
|
215
|
+
* setting it throws). */
|
|
216
|
+
get everyNth(): number { return this._everyNth }
|
|
217
|
+
set everyNth(n: number) {
|
|
218
|
+
this._secondaryOnly("everyNth")
|
|
219
|
+
this._everyNth = Math.max(1, Math.floor(n))
|
|
220
|
+
this._sendSchedule()
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** @internal Send the whole schedule (`setRenderViewOptions` restarts the count, so the next frame drawn renders). */
|
|
224
|
+
private _sendSchedule(): void {
|
|
225
|
+
if (this._view) _creator.setRenderViewOptions?.(this._view.id, this._enabled, this._everyNth, 6)
|
|
226
|
+
}
|
|
227
|
+
|
|
82
228
|
/** Exposure compensation in stops (default 0): +1 doubles how bright the scene renders, −2
|
|
83
229
|
* quarters it. The one exposure knob most games need — a dark interior, a flash of white, or eye
|
|
84
230
|
* adaptation when the sun swings into view (drop it, and the sun stays at the display's peak
|
|
85
231
|
* while everything else darkens). Scene light follows it; particle `emissive` is post-exposure
|
|
86
|
-
* and does not, so a fireball keeps its on-screen brightness. Cheap to animate every frame.
|
|
232
|
+
* and does not, so a fireball keeps its on-screen brightness. Cheap to animate every frame. The
|
|
233
|
+
* scene camera's: a secondary camera's view renders at its scene camera's exposure (what is set on
|
|
234
|
+
* a secondary camera is kept, unused). */
|
|
87
235
|
/** The camera's EV100 with the compensation in: log2(aperture² / shutterSpeed x 100 / iso) - compensation. Sunny 16 is
|
|
88
236
|
* ~15; a lit surface of luminance L lands on screen at L / (1.2 x 2^ev100). */
|
|
89
237
|
get ev100(): number {
|
|
@@ -99,7 +247,7 @@ export class Camera extends Node {
|
|
|
99
247
|
|
|
100
248
|
/** The physical camera: `aperture` in f-stops, `shutterSpeed` in seconds, `iso`, plus the same
|
|
101
249
|
* `compensation` in stops — any subset, one call. `camera.setExposure({ iso: 400 })` is two stops
|
|
102
|
-
* brighter than the default f/16 · 1/125 · ISO 100. */
|
|
250
|
+
* brighter than the default f/16 · 1/125 · ISO 100. The scene camera's (as `exposureCompensation`). */
|
|
103
251
|
setExposure(exposure: { aperture?: number, shutterSpeed?: number, iso?: number, compensation?: number }): this {
|
|
104
252
|
if (exposure.aperture !== undefined) this._exposure.aperture = exposure.aperture
|
|
105
253
|
if (exposure.shutterSpeed !== undefined) this._exposure.shutterSpeed = exposure.shutterSpeed
|
|
@@ -117,17 +265,25 @@ export class Camera extends Node {
|
|
|
117
265
|
_creator.setCameraExposure?.(this._sceneId, e.aperture, e.shutterSpeed, e.iso * 2 ** this._ev)
|
|
118
266
|
}
|
|
119
267
|
|
|
120
|
-
/** Vertical field of view in degrees (default 60) — a smaller angle is a longer lens.
|
|
121
|
-
|
|
268
|
+
/** Vertical field of view in degrees (default 60) — a smaller angle is a longer lens. Live on both kinds: a
|
|
269
|
+
* secondary camera's reaches its view at the next render (before `renderToTexture`, the view's starting value). */
|
|
270
|
+
get fov(): number { return this._sceneId >= 0 ? _creator.getCameraFov(this._sceneId, 0) : this._fov }
|
|
122
271
|
set fov(degrees: number) { this._fov = degrees; this._projSet = true; this._applyProjection() }
|
|
123
|
-
|
|
272
|
+
/** The horizontal field of view in degrees: the scene camera's at the screen's aspect, a secondary camera's at its
|
|
273
|
+
* texture's (square before `renderToTexture`). */
|
|
274
|
+
get horizontalFov(): number {
|
|
275
|
+
if (this._sceneId >= 0) return _creator.getCameraFov(this._sceneId, 1)
|
|
276
|
+
const t = this._texture
|
|
277
|
+
const aspect = t ? t.width / t.height : 1
|
|
278
|
+
return Math.atan(Math.tan(this._fov * Math.PI / 360) * aspect) * 360 / Math.PI
|
|
279
|
+
}
|
|
124
280
|
|
|
125
|
-
/** Near clip distance (default 0.01) — nothing closer than this draws. */
|
|
281
|
+
/** Near clip distance (default 0.01) — nothing closer than this draws. Live, as `fov`. */
|
|
126
282
|
get near(): number { return this._near }
|
|
127
283
|
set near(distance: number) { this._near = distance; this._projSet = true; this._applyProjection() }
|
|
128
284
|
|
|
129
285
|
/** Far clip distance / view range (default 1000) — geometry past it is culled. Shadows keep
|
|
130
|
-
* their own (shorter) range, so a long view distance doesn't cost shadow sharpness. */
|
|
286
|
+
* their own (shorter) range, so a long view distance doesn't cost shadow sharpness. Live, as `fov`. */
|
|
131
287
|
get far(): number { return this._far }
|
|
132
288
|
set far(distance: number) { this._far = distance; this._projSet = true; this._applyProjection() }
|
|
133
289
|
|
|
@@ -141,11 +297,22 @@ export class Camera extends Node {
|
|
|
141
297
|
return this
|
|
142
298
|
}
|
|
143
299
|
|
|
144
|
-
/** Hosts that predate the call keep the fixed defaults (older wasm / native builds)
|
|
300
|
+
/** Hosts that predate the call keep the fixed defaults (older wasm / native builds); a secondary camera's view keeps
|
|
301
|
+
* the projection it started with (warned once). */
|
|
145
302
|
private _applyProjection(): void {
|
|
146
|
-
if (this._sceneId < 0
|
|
303
|
+
if (this._sceneId < 0) {
|
|
304
|
+
if (!this._view) return // remembered: the view starts with it
|
|
305
|
+
if (_creator.setRenderViewProjection) _creator.setRenderViewProjection(this._view.id, this._fov, this._near, this._far)
|
|
306
|
+
else if (!Camera._warnedViewProjection) {
|
|
307
|
+
Camera._warnedViewProjection = true
|
|
308
|
+
console.warn("[camera] this host has no setRenderViewProjection — a secondary camera keeps the projection its view started with")
|
|
309
|
+
}
|
|
310
|
+
return
|
|
311
|
+
}
|
|
312
|
+
if (typeof _creator.setCameraProjection !== "function") return
|
|
147
313
|
_creator.setCameraProjection(this._sceneId, this._fov, this._near, this._far)
|
|
148
314
|
}
|
|
315
|
+
private static _warnedViewProjection = false
|
|
149
316
|
private _following: Node | null = null
|
|
150
317
|
/** The node the camera currently follows (`follow()`), or null. */
|
|
151
318
|
get following(): Node | null { return this._following }
|
|
@@ -155,7 +322,9 @@ export class Camera extends Node {
|
|
|
155
322
|
* view direction), so movement aspects / animation on that node are camera moves — dolly shots,
|
|
156
323
|
* a cutscene path, a `CameraPlace` in a scene file. A node carrying a `CameraPlace` also hands
|
|
157
324
|
* over its projection (fov / near / far). `follow(null)` releases the camera where it is.
|
|
158
|
-
*
|
|
325
|
+
* Following a new node stops following the previous one, and a node leads one camera at a time.
|
|
326
|
+
* Either kind: the pose is written as the camera's own transform, so a following secondary
|
|
327
|
+
* camera is a scene root, not a child.
|
|
159
328
|
*/
|
|
160
329
|
follow(target: Node | { node: Node, applyTo(camera: Camera): void } | null): this {
|
|
161
330
|
const node = target === null ? null : target instanceof Node ? target : target.node
|
|
@@ -175,22 +344,26 @@ export class Camera extends Node {
|
|
|
175
344
|
|
|
176
345
|
/** The scene view's [width, height] in logical units — the units of the scene's touches
|
|
177
346
|
* (`clientX` / `clientY`) and of `getRay`, on every host; × `device.pixelRatio` for device px.
|
|
178
|
-
* [0, 0] before the scene has a viewport. */
|
|
347
|
+
* [0, 0] before the scene has a viewport. The screen's, whichever camera reads it. */
|
|
179
348
|
get displaySize(): [number, number] {
|
|
180
349
|
const s = _creator.getDisplaySize()
|
|
181
350
|
return [ s[0], s[1] ]
|
|
182
351
|
}
|
|
183
352
|
|
|
184
|
-
/** World-space ray direction through a point of the scene view (logical units, as a touch's).
|
|
353
|
+
/** World-space ray direction through a point of the scene view (logical units, as a touch's). The scene camera's
|
|
354
|
+
* (a secondary camera throws). */
|
|
185
355
|
getViewDirection(screenX: number, screenY: number): Vec3 {
|
|
186
|
-
|
|
356
|
+
this._sceneOnly("getViewDirection")
|
|
187
357
|
const v = new Float32Array(3)
|
|
188
358
|
_creator.getCameraViewDirection(this._sceneId, screenX, screenY, v)
|
|
189
359
|
return new Vec3(v[0], v[1], v[2])
|
|
190
360
|
}
|
|
191
361
|
|
|
192
|
-
/** A pick ray from the camera through a point of the scene view (logical units, as a touch's).
|
|
362
|
+
/** A pick ray from the camera through a point of the scene view (logical units, as a touch's). The scene camera's
|
|
363
|
+
* (a secondary camera throws). */
|
|
193
364
|
getRay(screenX: number, screenY: number): Ray {
|
|
365
|
+
this._sceneOnly("getRay")
|
|
194
366
|
return new Ray(this.worldPosition, this.getViewDirection(screenX, screenY))
|
|
195
367
|
}
|
|
196
368
|
}
|
|
369
|
+
|