lecodes-sdk 0.19.2 → 0.20.2
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 +62 -0
- package/dist/host.d.ts +3 -0
- package/dist/types/audio/Bus.d.ts +45 -0
- package/dist/types/audio/Sound.d.ts +28 -0
- package/dist/types/audio/Voice.d.ts +27 -0
- package/dist/types/audio/audio.d.ts +83 -0
- package/dist/types/audio/support.d.ts +1 -0
- package/dist/types/gl/AudioSource.d.ts +60 -0
- package/dist/types/gl/AudioZone.d.ts +32 -0
- package/dist/types/gl/DecalSet.d.ts +103 -0
- package/dist/types/gl/Geometry.d.ts +5 -0
- package/dist/types/gl/Light.d.ts +7 -0
- package/dist/types/gl/Locomotion.d.ts +3 -1
- package/dist/types/gl/Material.d.ts +86 -2
- package/dist/types/gl/Mesh.d.ts +11 -0
- package/dist/types/gl/Scene.d.ts +23 -0
- package/dist/types/gl/SceneAudio.d.ts +11 -0
- package/dist/types/gl/Texture.d.ts +29 -1
- package/dist/types/gl/animation/AnimationClip.d.ts +25 -12
- package/dist/types/gl/animation/Animator.d.ts +51 -183
- package/dist/types/gl/animation/Feet.d.ts +85 -0
- package/dist/types/gl/animation/Warp.d.ts +53 -0
- package/dist/types/gl/animation/core.d.ts +61 -17
- package/dist/types/gl/state.d.ts +0 -1
- package/dist/types/inject.d.ts +17 -2
- package/dist/types/plugins/map.d.ts +174 -0
- package/dist/types/runtime/input.d.ts +11 -0
- package/dist/types/ui/UIImage.d.ts +15 -5
- package/dist/types.json +1 -1
- package/package.json +1 -1
- package/src/audio/Bus.ts +102 -0
- package/src/audio/Sound.ts +96 -0
- package/src/audio/Voice.ts +102 -0
- package/src/audio/audio.ts +161 -0
- package/src/audio/support.ts +6 -0
- package/src/bridges.d.ts +1481 -1345
- package/src/compile/__tests__/compile.test.ts +12 -0
- package/src/compile/compileProject.ts +35 -15
- package/src/compile/index.ts +3 -0
- package/src/core/Aspect.ts +34 -9
- package/src/g2/Scene2D.ts +7 -0
- package/src/gl/AudioSource.ts +113 -0
- package/src/gl/AudioZone.ts +75 -0
- package/src/gl/DecalSet.ts +233 -0
- package/src/gl/Geometry.ts +5 -0
- package/src/gl/Light.ts +16 -0
- package/src/gl/Lightmap.ts +3 -2
- package/src/gl/Locomotion.ts +7 -5
- package/src/gl/Material.ts +152 -4
- package/src/gl/Mesh.ts +20 -1
- package/src/gl/Particles.ts +3 -3
- package/src/gl/Scene.ts +42 -8
- package/src/gl/SceneAudio.ts +26 -0
- package/src/gl/Texture.ts +43 -3
- package/src/gl/Vehicle.ts +5 -5
- package/src/gl/animation/AnimationClip.ts +43 -20
- package/src/gl/animation/Animator.ts +138 -329
- package/src/gl/animation/Feet.ts +134 -0
- package/src/gl/animation/Loop.ts +3 -1
- package/src/gl/animation/Warp.ts +96 -0
- package/src/gl/animation/core.ts +741 -670
- package/src/gl/state.ts +6 -6
- package/src/host.d.ts +3 -0
- package/src/inject.ts +23 -2
- package/src/plugins/map.ts +396 -0
- package/src/runtime/input.ts +6 -1
- package/src/ui/UIImage.ts +21 -7
package/src/gl/Light.ts
CHANGED
|
@@ -67,6 +67,11 @@ export class Light extends Node {
|
|
|
67
67
|
_direction: [number, number, number] = [ 0, -1, 0 ]
|
|
68
68
|
/** The most recently created sun — what `Lightmap.load` uses unless told otherwise. */
|
|
69
69
|
static lastSun: Light | null = null
|
|
70
|
+
/** @internal the colour as created / last set (a settings menu recreates a sun from these). */
|
|
71
|
+
_color: ColorInput = 0xffffff
|
|
72
|
+
/** @internal sun shadow options as created (creation-time in the engine). */
|
|
73
|
+
_shadowsQuality = 1
|
|
74
|
+
_shadowDistance = 100
|
|
70
75
|
|
|
71
76
|
/** A directional sun light. */
|
|
72
77
|
static sun(options: SunOptions = {}): Light {
|
|
@@ -74,6 +79,9 @@ export class Light extends Node {
|
|
|
74
79
|
const [ dx, dy, dz ] = options.direction ?? [ 0.548267, -0.473983, -0.689016 ]
|
|
75
80
|
light._direction = [ dx, dy, dz ]
|
|
76
81
|
light._intensity = options.intensity ?? 100000
|
|
82
|
+
light._color = options.color ?? 0xffffff
|
|
83
|
+
light._shadowsQuality = options.shadowsQuality ?? 1
|
|
84
|
+
light._shadowDistance = options.shadowDistance ?? 100
|
|
77
85
|
Light.lastSun = light
|
|
78
86
|
_creator.createSunLight(
|
|
79
87
|
light.id, dx, dy, dz,
|
|
@@ -95,6 +103,7 @@ export class Light extends Node {
|
|
|
95
103
|
static point(options: PointOptions = {}): Light {
|
|
96
104
|
const light = new Light()
|
|
97
105
|
light._intensity = options.intensity ?? 1000
|
|
106
|
+
light._color = options.color ?? 0xffffff
|
|
98
107
|
light._baked = options.baked ?? true
|
|
99
108
|
Light._points.add(light)
|
|
100
109
|
_creator.createPointLight?.(
|
|
@@ -130,6 +139,13 @@ export class Light extends Node {
|
|
|
130
139
|
}
|
|
131
140
|
|
|
132
141
|
set color(value: ColorInput) {
|
|
142
|
+
this._color = value
|
|
133
143
|
_creator.setLightColor?.(this.id, Color.toPackedRgb(value))
|
|
134
144
|
}
|
|
145
|
+
get color(): ColorInput { return this._color }
|
|
146
|
+
/** Sun direction as created (the engine keeps it; a settings menu rebuilds a sun from it). */
|
|
147
|
+
get direction(): [number, number, number] { return this._direction }
|
|
148
|
+
/** Sun shadow options as created — creation-time in the engine, so a change means a new sun. */
|
|
149
|
+
get shadowsQuality(): number { return this._shadowsQuality }
|
|
150
|
+
get shadowDistance(): number { return this._shadowDistance }
|
|
135
151
|
}
|
package/src/gl/Lightmap.ts
CHANGED
|
@@ -241,7 +241,8 @@ export class Lightmap {
|
|
|
241
241
|
if (pageFiles.length < pages) {
|
|
242
242
|
console.warn(`Lightmap: the bake took ${pages} atlas pages but the scene lists ${pageFiles.length} — add the other page files (${(data.textures ?? []).slice(pageFiles.length).join(", ")}) to env.lightmap.texture`)
|
|
243
243
|
}
|
|
244
|
-
|
|
244
|
+
// baked lighting is exempt from Texture.maxSize: a capped atlas blurs every shadow edge
|
|
245
|
+
const textures = await Promise.all(pageFiles.slice(0, pages).map((f) => Texture.load(f, { fullSize: true })))
|
|
245
246
|
// the light atlas: the bake carries the point lights only when the scene passes their atlas
|
|
246
247
|
const lightFiles = list(files.light)
|
|
247
248
|
let lightTextures: Texture[] = []
|
|
@@ -252,7 +253,7 @@ export class Lightmap {
|
|
|
252
253
|
console.warn(`Lightmap: the bake carries ${data.lights?.length ?? 0} baked point light(s) but the scene passes no \`light\` atlas (${data.lightTextures[0]}) — the lamps stay real-time`)
|
|
253
254
|
} else {
|
|
254
255
|
if (lightFiles.length < pages) console.warn(`Lightmap: the light atlas has ${pages} pages but the scene lists ${lightFiles.length}`)
|
|
255
|
-
lightTextures = await Promise.all(lightFiles.slice(0, pages).map((f) => Texture.load(f)))
|
|
256
|
+
lightTextures = await Promise.all(lightFiles.slice(0, pages).map((f) => Texture.load(f, { fullSize: true })))
|
|
256
257
|
lightScale = (data.lightScale ?? 0) * (options.lightBoost ?? 1)
|
|
257
258
|
volumeScale = (data.lightScale ?? 0) * (options.volumeBoost ?? options.lightBoost ?? 1)
|
|
258
259
|
}
|
package/src/gl/Locomotion.ts
CHANGED
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
|
|
30
30
|
import { Aspect } from "../core/Aspect"
|
|
31
31
|
import { Vec3, cx, cz, type Vec3Like } from "../math/vec"
|
|
32
|
-
import { Animator
|
|
32
|
+
import { Animator } from "./animation/Animator"
|
|
33
|
+
import type { FeetOptions } from "./animation/Feet"
|
|
34
|
+
import type { WarpOptions } from "./animation/Warp"
|
|
33
35
|
import { CharacterController } from "./CharacterController"
|
|
34
36
|
import type { Node } from "./Node"
|
|
35
37
|
|
|
@@ -288,8 +290,8 @@ export class Locomotion extends Aspect<"loco", Node, LocomotionEvents> {
|
|
|
288
290
|
for (const c of this.set.stops ?? []) this.enter(c, KIND.STOP)
|
|
289
291
|
for (const c of this.set.turns ?? []) this.enter(c, KIND.TURN)
|
|
290
292
|
for (const c of this.set.spins ?? []) this.enter(c, KIND.SPIN)
|
|
291
|
-
if (this.warp !== undefined) anim.warp
|
|
292
|
-
anim.feet
|
|
293
|
+
if (this.warp !== undefined) anim.warp.set(this.warp)
|
|
294
|
+
anim.feet.set(this.feet === undefined ? { lock: true } : this.feet === true ? { lock: true, ik: true } : this.feet === false ? { lock: false, ik: false } : this.feet)
|
|
293
295
|
this.pushTuning()
|
|
294
296
|
}
|
|
295
297
|
|
|
@@ -303,7 +305,7 @@ export class Locomotion extends Aspect<"loco", Node, LocomotionEvents> {
|
|
|
303
305
|
if (slot < 0) { console.warn(`Locomotion: no clip '${n}' on ${this.node.name || "the character"}`); return }
|
|
304
306
|
this._names.set(slot, n)
|
|
305
307
|
const o = typeof clip === "string" ? undefined : clip
|
|
306
|
-
const measured = anim.
|
|
308
|
+
const measured = anim.clipInfo(n)
|
|
307
309
|
const angle = o?.angle ?? (measured ? measured.turn * RAD2DEG : 0)
|
|
308
310
|
const gait = kind === KIND.GAIT ? -1 : o?.gait ? GAITS.indexOf(o.gait) : -1
|
|
309
311
|
_creator.locoSetEntry(this._loco, slot, kind, angle, o?.speed ?? 0, gait)
|
|
@@ -422,7 +424,7 @@ export class Locomotion extends Aspect<"loco", Node, LocomotionEvents> {
|
|
|
422
424
|
const named = t ? t[this._gait] : undefined
|
|
423
425
|
if (named !== undefined) return named * this._mag
|
|
424
426
|
const clip = (this.set.gaits ?? [])[GAITS.indexOf(this._gait)]
|
|
425
|
-
const measured = clip !== undefined ? this.anim?.
|
|
427
|
+
const measured = clip !== undefined ? this.anim?.clipInfo(name(clip))?.speed : undefined
|
|
426
428
|
return (measured ?? 0) * this._mag
|
|
427
429
|
}
|
|
428
430
|
|
package/src/gl/Material.ts
CHANGED
|
@@ -36,8 +36,49 @@ const applyUniform = (id: number, key: string, value: UniformValue): void => {
|
|
|
36
36
|
|
|
37
37
|
export type MaterialColorOptions = { color?: ColorInput, map?: Texture | Canvas | null }
|
|
38
38
|
|
|
39
|
+
/** How a stencil test / write on a material compares and what it writes. `test` runs against the
|
|
40
|
+
* scene's stencil buffer (`Scene.stencil` must be on); the ops say what the buffer gets when the
|
|
41
|
+
* fragment passes / fails the stencil test / fails the depth test (`replace` writes `ref`). */
|
|
42
|
+
export type StencilTest = "always" | "never" | "less" | "lessEqual" | "greater" | "greaterEqual" | "equal" | "notEqual"
|
|
43
|
+
export type StencilOp = "keep" | "zero" | "replace" | "increment" | "decrement" | "invert"
|
|
44
|
+
export type MaterialStencil = {
|
|
45
|
+
/** Write the stencil buffer at all. Default `false`. */
|
|
46
|
+
write?: boolean
|
|
47
|
+
/** The reference value, 0..255. Default 0. */
|
|
48
|
+
ref?: number
|
|
49
|
+
/** Default `"always"`. */
|
|
50
|
+
test?: StencilTest
|
|
51
|
+
onPass?: StencilOp
|
|
52
|
+
onFail?: StencilOp
|
|
53
|
+
onDepthFail?: StencilOp
|
|
54
|
+
readMask?: number
|
|
55
|
+
writeMask?: number
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Render state a material instance can override at run time (Filament keeps the defaults in the
|
|
59
|
+
* shader package; these are per-instance overrides on top). */
|
|
60
|
+
export type MaterialStateOptions = {
|
|
61
|
+
/** Test against the scene's depth buffer. `false` draws over everything already drawn — an
|
|
62
|
+
* editor gizmo, a marker that must never hide behind a wall. Pair it with a high
|
|
63
|
+
* `Mesh.renderPriority` so nothing drawn later covers it. Default `true`. */
|
|
64
|
+
depthTest?: boolean
|
|
65
|
+
/** Write to the depth buffer. Leave it on for something drawn over the scene whose own parts must
|
|
66
|
+
* still occlude each other (a gizmo's cone in front of its shaft). Unset = the shader's default
|
|
67
|
+
* (on for opaque, off for a blended one). */
|
|
68
|
+
depthWrite?: boolean
|
|
69
|
+
/** Draw both faces (no back-face culling): a plane seen from behind, a ribbon, cloth, a flat
|
|
70
|
+
* marker. Default `false` = the shader's culling (back faces dropped). */
|
|
71
|
+
doubleSided?: boolean
|
|
72
|
+
/** A stencil test / write for this material — see `MaterialStencil`. The selection-outline
|
|
73
|
+
* recipe: the object writes `{ write: true, ref: 1, onPass: "replace" }`, and a slightly larger
|
|
74
|
+
* copy of it draws with `{ test: "notEqual", ref: 1 }` + `depthTest: false` in `renderPriority` 7. */
|
|
75
|
+
stencil?: MaterialStencil
|
|
76
|
+
}
|
|
77
|
+
/** @deprecated the name before doubleSided / stencil joined it — the same type */
|
|
78
|
+
export type MaterialDepthOptions = MaterialStateOptions
|
|
79
|
+
|
|
39
80
|
/** `Material.unlit` options. */
|
|
40
|
-
export type UnlitMaterialOptions = MaterialColorOptions & {
|
|
81
|
+
export type UnlitMaterialOptions = MaterialColorOptions & MaterialStateOptions & {
|
|
41
82
|
/** Alpha-blend this material instead of drawing it opaque. Opaque is the default: a blended draw
|
|
42
83
|
* writes no depth, is sorted back-to-front and casts no shadow, which is rarely what a flat
|
|
43
84
|
* colour wants. Turn it on for anything that must show what is behind it — a glass pane, a
|
|
@@ -46,6 +87,27 @@ export type UnlitMaterialOptions = MaterialColorOptions & {
|
|
|
46
87
|
transparent?: boolean
|
|
47
88
|
}
|
|
48
89
|
|
|
90
|
+
/** `Material.decal` options — the projected-decal material a `DecalSet` draws with. */
|
|
91
|
+
export type DecalMaterialOptions = {
|
|
92
|
+
/** The atlas (a `DecalSet`'s `sheet` cuts it into cells). */
|
|
93
|
+
map?: Texture
|
|
94
|
+
/** A tangent-space normal atlas (same cell grid; +X = the image's right, +Y = its top; LINEAR —
|
|
95
|
+
* `Texture.fromPixels(…, { srgb: false })`). Turns the decal into a RELIEF decal: instead of
|
|
96
|
+
* painting a colour it bends the surface's lighting, so a footprint or a dent shows on any
|
|
97
|
+
* surface without a colour of its own. With `map` too, the colour multiplies in (a crater
|
|
98
|
+
* darkens by the map's alpha). The sun term applies in shadow as well (no shadow read). */
|
|
99
|
+
normalMap?: Texture
|
|
100
|
+
/** Relief strength — the normal map's xy scale. Default 1. */
|
|
101
|
+
bump?: number
|
|
102
|
+
/** Soft fraction (0..1) of the box's half depth at both ends. Default 0.3. */
|
|
103
|
+
edge?: number
|
|
104
|
+
/** Cosine of the surface angle past which the decal fades (0.3 ≈ 72°); 0 = project onto
|
|
105
|
+
* anything. Default 0.3. */
|
|
106
|
+
angleFade?: number
|
|
107
|
+
/** HDR boost of the image (0 = none). */
|
|
108
|
+
emissive?: number
|
|
109
|
+
}
|
|
110
|
+
|
|
49
111
|
/** `Material.particles` options — the default point-sprite material for particle systems. */
|
|
50
112
|
export type ParticlesMaterialOptions = {
|
|
51
113
|
/** Sprite texture — a single image or a flipbook sheet of frames. Unset = soft round dot. */
|
|
@@ -92,13 +154,16 @@ export const _sheetColsRows = (sheet: number | [number, number] | undefined): [n
|
|
|
92
154
|
}
|
|
93
155
|
|
|
94
156
|
/** `Material.lit` options — PBR scalars on top of the color/map pair. */
|
|
95
|
-
export type LitMaterialOptions = MaterialColorOptions & {
|
|
157
|
+
export type LitMaterialOptions = MaterialColorOptions & MaterialStateOptions & {
|
|
96
158
|
/** Perceptual roughness, 0 (mirror) … 1 (matte). Unset = the shader's default. */
|
|
97
159
|
roughness?: number
|
|
98
160
|
/** Metallic factor, 0 (dielectric) … 1 (metal). Unset = the shader's default. */
|
|
99
161
|
metallic?: number
|
|
100
162
|
}
|
|
101
163
|
|
|
164
|
+
/** `Material.lightmapShading` tiers — see the setter. */
|
|
165
|
+
export type LightmapShading = "full" | "baked" | "baked-lite"
|
|
166
|
+
|
|
102
167
|
export class Material {
|
|
103
168
|
/** @internal native material-instance handle. */
|
|
104
169
|
_id: number
|
|
@@ -133,6 +198,33 @@ export class Material {
|
|
|
133
198
|
if (this._colorKey) this.uniforms[this._colorKey] = Color.toHexString(c)
|
|
134
199
|
}
|
|
135
200
|
|
|
201
|
+
/** Depth test against the scene (write-only; see `MaterialDepthOptions`). A host that predates
|
|
202
|
+
* the call leaves the material as the shader has it. */
|
|
203
|
+
set depthTest(on: boolean) {
|
|
204
|
+
if (_creator.setMaterialDepthTest) _creator.setMaterialDepthTest(this._id, on)
|
|
205
|
+
else warnOnce("depthTest", "[material] this host has no setMaterialDepthTest — depthTest ignored")
|
|
206
|
+
}
|
|
207
|
+
/** Depth write (write-only; see `MaterialStateOptions`). */
|
|
208
|
+
set depthWrite(on: boolean) {
|
|
209
|
+
if (_creator.setMaterialDepthWrite) _creator.setMaterialDepthWrite(this._id, on)
|
|
210
|
+
else warnOnce("depthWrite", "[material] this host has no setMaterialDepthWrite — depthWrite ignored")
|
|
211
|
+
}
|
|
212
|
+
/** Both faces drawn (write-only; see `MaterialStateOptions`). */
|
|
213
|
+
set doubleSided(on: boolean) {
|
|
214
|
+
// 0 = no culling, 2 = back faces culled (the shader's usual default)
|
|
215
|
+
if (_creator.setMaterialCulling) _creator.setMaterialCulling(this._id, on ? 0 : 2)
|
|
216
|
+
else warnOnce("doubleSided", "[material] this host has no setMaterialCulling — doubleSided ignored")
|
|
217
|
+
}
|
|
218
|
+
/** The stencil test / write (write-only; `null` = back to none). See `MaterialStencil`. */
|
|
219
|
+
set stencil(s: MaterialStencil | null) {
|
|
220
|
+
if (!_creator.setMaterialStencil) { warnOnce("stencil", "[material] this host has no setMaterialStencil — stencil ignored"); return }
|
|
221
|
+
const o = s ?? {}
|
|
222
|
+
const clamp = (v: number | undefined, d: number): number => Math.max(0, Math.min(255, Math.round(v ?? d)))
|
|
223
|
+
_creator.setMaterialStencil(this._id, !!o.write, STENCIL_TEST.indexOf(o.test ?? "always"), clamp(o.ref, 0),
|
|
224
|
+
STENCIL_OP.indexOf(o.onPass ?? "keep"), STENCIL_OP.indexOf(o.onFail ?? "keep"), STENCIL_OP.indexOf(o.onDepthFail ?? "keep"),
|
|
225
|
+
clamp(o.readMask, 255), clamp(o.writeMask, 255))
|
|
226
|
+
}
|
|
227
|
+
|
|
136
228
|
// lit/unlit shade baseColor × baseColorMap (factor×map; engines default both to white), so
|
|
137
229
|
// removing the map means resetting baseColorMap to the white 1×1 — no flag to clear. (Materials
|
|
138
230
|
// used to carry a `useBaseColorMap` bool for shaders that branched instead of multiplying; the
|
|
@@ -158,6 +250,7 @@ export class Material {
|
|
|
158
250
|
if (options.map) m.map = options.map
|
|
159
251
|
if (options.roughness !== undefined) m.uniforms.roughness = options.roughness
|
|
160
252
|
if (options.metallic !== undefined) m.uniforms.metallic = options.metallic
|
|
253
|
+
applyDepth(m, options)
|
|
161
254
|
return m
|
|
162
255
|
}
|
|
163
256
|
|
|
@@ -177,6 +270,7 @@ export class Material {
|
|
|
177
270
|
m._colorKey = "baseColor"
|
|
178
271
|
if (options.color !== undefined) m.color = options.color
|
|
179
272
|
if (options.map) m.map = options.map
|
|
273
|
+
applyDepth(m, options)
|
|
180
274
|
return m
|
|
181
275
|
}
|
|
182
276
|
|
|
@@ -205,6 +299,28 @@ export class Material {
|
|
|
205
299
|
return m
|
|
206
300
|
}
|
|
207
301
|
|
|
302
|
+
/** Projected-decal material (`DecalSet`): samples the atlas where the decal's box meets the opaque
|
|
303
|
+
* scene behind it. Blended, no depth write, no shadows — the engine keeps the scene depth bound
|
|
304
|
+
* while a set with live decals is on screen. */
|
|
305
|
+
static decal(options: DecalMaterialOptions = {}): Material {
|
|
306
|
+
// Both fetchLocal literals inline so the compiler's preload header captures the shader names.
|
|
307
|
+
const m = options.normalMap
|
|
308
|
+
? new Material({ _id: _creatorUtils.fetchLocal("decal-relief.filamat") } as any)
|
|
309
|
+
: new Material({ _id: _creatorUtils.fetchLocal("decal.filamat") } as any)
|
|
310
|
+
m.uniforms.time = 0
|
|
311
|
+
m.uniforms.edge = options.edge ?? 0.3
|
|
312
|
+
m.uniforms.angleFade = options.angleFade ?? 0.3
|
|
313
|
+
if (options.normalMap) {
|
|
314
|
+
m.uniforms.normalMap = options.normalMap
|
|
315
|
+
m.uniforms.bump = options.bump ?? 1
|
|
316
|
+
m.uniforms.useColor = options.map ? 1 : 0
|
|
317
|
+
} else {
|
|
318
|
+
m.uniforms.emissive = options.emissive ?? 0
|
|
319
|
+
}
|
|
320
|
+
if (options.map) m.map = options.map
|
|
321
|
+
return m
|
|
322
|
+
}
|
|
323
|
+
|
|
208
324
|
/** Material that samples a VideoPlayer's texture. */
|
|
209
325
|
static video(map?: Texture): Material {
|
|
210
326
|
const m = new Material({ _id: _creatorUtils.fetchLocal("video.filamat") } as any)
|
|
@@ -218,8 +334,13 @@ export class Material {
|
|
|
218
334
|
* `metallicFactor`) plus `lightmap`, `lightmapST`, `ambientScale`, `sunStrength` (the sun / IBL terms come from
|
|
219
335
|
* filament's per-frame uniforms inside the shader). */
|
|
220
336
|
static lightmap(): Material {
|
|
221
|
-
// Inline
|
|
222
|
-
const
|
|
337
|
+
// Inline literals: the compiler's preload header captures the shader names from them.
|
|
338
|
+
const id = Material.lightmapShading === "baked"
|
|
339
|
+
? _creatorUtils.fetchLocal("lightmap-baked.filamat")
|
|
340
|
+
: Material.lightmapShading === "baked-lite"
|
|
341
|
+
? _creatorUtils.fetchLocal("lightmap-baked-lite.filamat")
|
|
342
|
+
: _creatorUtils.fetchLocal("lightmap.filamat")
|
|
343
|
+
const m = new Material({ _id: id } as any)
|
|
223
344
|
m.set("baseColorFactor", "#ffffffff").set("roughnessFactor", 1).set("metallicFactor", 0)
|
|
224
345
|
m.set("lightmapST", [ 1, 1, 0, 0 ]).set("ambientScale", 1).set("sunStrength", 1).set("hasNormalMap", 0)
|
|
225
346
|
// lightScale 0 = no baked point lights until Lightmap.load binds a light atlas / volume
|
|
@@ -247,6 +368,21 @@ export class Material {
|
|
|
247
368
|
static _lightmapTemplate(): Material { return (Material._lmTemplate ??= Material.lightmap()) }
|
|
248
369
|
private static _lmTemplate: Material | undefined
|
|
249
370
|
|
|
371
|
+
static _lightmapShading: LightmapShading = "full"
|
|
372
|
+
/** Which shader lightmapped models take — a graphics-quality tier, engine-wide. `"full"` is filament's
|
|
373
|
+
* lit path over the baked atlas (IBL specular, real-time point lights such as a muzzle flash, sun
|
|
374
|
+
* shadows on dynamic objects). `"baked"` keeps the baked light and an approximated ambient but drops
|
|
375
|
+
* the lit path: ~40 % cheaper per pixel on a fill-bound GPU. `"baked-lite"` is that minus the normal,
|
|
376
|
+
* metallic/roughness and occlusion map reads (the factors stand in, the atlas keeps the baked AO;
|
|
377
|
+
* emissive still glows): +20 % more at 1080p on the same GPU. Read when a lightmapped model LOADS, so set
|
|
378
|
+
* it before the level (a settings menu applies it on the next level load), like `Texture.maxSize`. */
|
|
379
|
+
static get lightmapShading(): LightmapShading { return Material._lightmapShading }
|
|
380
|
+
static set lightmapShading(mode: LightmapShading) {
|
|
381
|
+
if (mode === Material._lightmapShading) return
|
|
382
|
+
Material._lightmapShading = mode
|
|
383
|
+
Material._lmTemplate = undefined // the next load builds a template on the new shader
|
|
384
|
+
}
|
|
385
|
+
|
|
250
386
|
/** Shadow-catcher material (transparent except where shadows fall). */
|
|
251
387
|
static shadow(color: ColorInput = "#000000aa"): Material {
|
|
252
388
|
const m = new Material({ _id: _creatorUtils.fetchLocal("shadow.filamat") } as any)
|
|
@@ -277,3 +413,15 @@ export class Material {
|
|
|
277
413
|
/** @internal */
|
|
278
414
|
static idOf(material: Material): number { return material._id }
|
|
279
415
|
}
|
|
416
|
+
|
|
417
|
+
const applyDepth = (m: Material, o: MaterialStateOptions): void => {
|
|
418
|
+
if (o.depthTest !== undefined) m.depthTest = o.depthTest
|
|
419
|
+
if (o.depthWrite !== undefined) m.depthWrite = o.depthWrite
|
|
420
|
+
if (o.doubleSided !== undefined) m.doubleSided = o.doubleSided
|
|
421
|
+
if (o.stencil !== undefined) m.stencil = o.stencil
|
|
422
|
+
}
|
|
423
|
+
/** the wire order of the stencil enums (creator-gl material.cpp switches on the same numbers) */
|
|
424
|
+
const STENCIL_TEST: StencilTest[] = [ "always", "never", "less", "lessEqual", "greater", "greaterEqual", "equal", "notEqual" ]
|
|
425
|
+
const STENCIL_OP: StencilOp[] = [ "keep", "zero", "replace", "increment", "decrement", "invert" ]
|
|
426
|
+
const warned = new Set<string>()
|
|
427
|
+
const warnOnce = (key: string, msg: string): void => { if (!warned.has(key)) { warned.add(key); console.warn(msg) } }
|
package/src/gl/Mesh.ts
CHANGED
|
@@ -20,6 +20,8 @@ export type MeshOptions = {
|
|
|
20
20
|
name?: string
|
|
21
21
|
castShadows?: boolean
|
|
22
22
|
receiveShadows?: boolean
|
|
23
|
+
/** Coarse draw order, 0 (first) … 7 (last); default 4. See `Mesh.renderPriority`. */
|
|
24
|
+
renderPriority?: number
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export class Mesh extends Node {
|
|
@@ -31,17 +33,33 @@ export class Mesh extends Node {
|
|
|
31
33
|
const mat = material ?? Material.unlit()
|
|
32
34
|
this._geometry = geometry
|
|
33
35
|
this._materials = [ mat ]
|
|
34
|
-
_creator.setMesh(this.id, Material.idOf(mat), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind, geometry.uv1)
|
|
36
|
+
_creator.setMesh(this.id, Material.idOf(mat), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind, geometry.uv1, geometry.colors)
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
override get geometry(): Geometry | undefined { return this._geometry }
|
|
41
|
+
/** Replace the geometry in place — the node, its transform and its material stay, the vertex and
|
|
42
|
+
* index buffers are rebuilt. What an editor overlay or a debug drawer redraws with. */
|
|
43
|
+
setGeometry(geometry: Geometry): this {
|
|
44
|
+
this._geometry = geometry
|
|
45
|
+
_creator.setMesh(this.id, Material.idOf(this._materials![0]!), geometry.vertices, geometry.normals, geometry.indices, geometry.uv, geometry._meshKind, geometry.uv1, geometry.colors)
|
|
46
|
+
return this
|
|
47
|
+
}
|
|
39
48
|
|
|
40
49
|
/** A Mesh always carries a material (slot 0) — see Node.setMaterial for the slot API. */
|
|
41
50
|
override get material(): Material { return this._materials![0]! }
|
|
42
51
|
override set material(m: Material) { this.setMaterial(m, 0) }
|
|
43
52
|
|
|
44
53
|
set culling(v: boolean) { _creator.setCulling(this.id, v) }
|
|
54
|
+
/** Coarse draw order within the frame: 0 draws first, 7 last, 4 is the default (Filament's
|
|
55
|
+
* renderable priority; within one priority opaque draws sort front-to-back, blended back-to-front).
|
|
56
|
+
* Something drawn over the scene with `Material.depthTest = false` goes in 7, so nothing drawn
|
|
57
|
+
* after it can cover it. Write-only; a host that predates the call ignores it. */
|
|
58
|
+
set renderPriority(v: number) {
|
|
59
|
+
if (_creator.setRenderPriority) _creator.setRenderPriority(this.id, Math.max(0, Math.min(7, Math.round(v))))
|
|
60
|
+
else if (!Mesh._warnedPriority) { Mesh._warnedPriority = true; console.warn("[mesh] this host has no setRenderPriority — renderPriority ignored") }
|
|
61
|
+
}
|
|
62
|
+
private static _warnedPriority = false
|
|
45
63
|
set castShadows(v: boolean) { _creator.setCastShadows(this.id, v) }
|
|
46
64
|
set receiveShadows(v: boolean) { _creator.setReceiveShadows(this.id, v) }
|
|
47
65
|
|
|
@@ -79,5 +97,6 @@ const apply = (mesh: Mesh, o: MeshOptions): Mesh => {
|
|
|
79
97
|
if (o.name !== undefined) mesh.name = o.name
|
|
80
98
|
if (o.castShadows !== undefined) mesh.castShadows = o.castShadows
|
|
81
99
|
if (o.receiveShadows !== undefined) mesh.receiveShadows = o.receiveShadows
|
|
100
|
+
if (o.renderPriority !== undefined) mesh.renderPriority = o.renderPriority
|
|
82
101
|
return mesh
|
|
83
102
|
}
|
package/src/gl/Particles.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
//
|
|
4
4
|
// All emitter/curve config crosses the bridge as ONE Float32Array of [tag, payloadLen, ...payload]
|
|
5
5
|
// records (_creator.setParticleSystemConfig) — the constructor batches every option into a single
|
|
6
|
-
// call, live setters send a one-record buffer. The tag values mirror
|
|
7
|
-
// creator-
|
|
6
|
+
// call, live setters send a one-record buffer. The tag values mirror CPART_TAG_* in
|
|
7
|
+
// creator-particles/include/creator-particles/creator-particles.h (guarded by tests/particles-tags.test.ts).
|
|
8
8
|
//
|
|
9
9
|
// Curves over a particle's lifetime are built with curve()/colorCurve() chains — see the builders
|
|
10
10
|
// below. A builder's whole state is the flat `_data` array (the exact record payload), so the
|
|
@@ -18,7 +18,7 @@ import { Node } from "./Node"
|
|
|
18
18
|
|
|
19
19
|
type Range<T> = T | { min: T, max: T }
|
|
20
20
|
|
|
21
|
-
// --- config record tags (keep in sync with
|
|
21
|
+
// --- config record tags (keep in sync with CPART_TAG_* in creator-particles.h) ------------------
|
|
22
22
|
|
|
23
23
|
const TAG_SHAPE = 1
|
|
24
24
|
const TAG_LIFETIME = 2
|
package/src/gl/Scene.ts
CHANGED
|
@@ -13,8 +13,10 @@ import type { ClickEvent, TouchStartEvent } from "../runtime/touch"
|
|
|
13
13
|
import type { FetchResponse } from "../runtime/fetch"
|
|
14
14
|
import { _requestCameraPermission } from "../plugins/permission"
|
|
15
15
|
import { Camera } from "./Camera"
|
|
16
|
+
import { SceneAudio } from "./SceneAudio"
|
|
16
17
|
import { attachControls, type ControlsHandle, type ControlsOptions } from "./controls"
|
|
17
18
|
import { Material } from "./Material"
|
|
19
|
+
import { Texture } from "./Texture"
|
|
18
20
|
import { Node, nodeRegistry } from "./Node"
|
|
19
21
|
import { glState } from "./state"
|
|
20
22
|
import { registerTouchEndEvent, registerTouchStartEvent } from "./touch"
|
|
@@ -107,6 +109,9 @@ export type SceneOptions = {
|
|
|
107
109
|
* takes over). Unset keeps the host default (desktop 4×, mobile/web off). The biggest single
|
|
108
110
|
* fill-rate cost after resolution — turn it down on big screens before anything else. */
|
|
109
111
|
antialias?: boolean | 2 | 4
|
|
112
|
+
/** Keep a stencil buffer for this scene (off by default: it costs memory and a clear per frame).
|
|
113
|
+
* Needed before any material's `stencil` test or write does anything. */
|
|
114
|
+
stencil?: boolean
|
|
110
115
|
/** Render the 3D at this fraction of the viewport (0.25–1) and upscale; the UI stays at native
|
|
111
116
|
* resolution. A fixed, predictable cut of per-pixel GPU work — `0.75` is ~45 % cheaper and
|
|
112
117
|
* barely visible in motion, `0.5` quarters it. Headless renders ignore it. */
|
|
@@ -128,6 +133,10 @@ export type SceneOptions = {
|
|
|
128
133
|
* scene file — `env` is applied before the nodes build) and leaves already-loaded ones alone.
|
|
129
134
|
* On the desktop host `CREATOR_TEXTURE_ANISOTROPY` overrides it, for tuning without a rebuild. */
|
|
130
135
|
anisotropy?: number
|
|
136
|
+
/** Engine-wide cap on texture size, 0 / unset = none (see `Texture.maxSize`): a KTX2 above it
|
|
137
|
+
* loses its top mip levels on load, a glTF image is downsampled. Applied before this scene's
|
|
138
|
+
* assets load; like `anisotropy` it does not touch textures already loaded. */
|
|
139
|
+
maxTextureSize?: number
|
|
131
140
|
/** The look on an HDR display (a screen with headroom above SDR white — Apple XDR panels, the
|
|
132
141
|
* macOS host today); ignored on SDR. `strength` 0..1 is how much of the picture reaches for the
|
|
133
142
|
* display's headroom (0 only what SDR clipped, 1 nearly everything; default 0.35). `paperWhite`
|
|
@@ -148,17 +157,16 @@ const installRenderSyncedFrames = (): void => {
|
|
|
148
157
|
framesInstalled = true
|
|
149
158
|
_installAspectFrames((early, late) => {
|
|
150
159
|
if (typeof _creator.setEarlyUpdate !== "function") return false
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
_creator.setEarlyUpdate(
|
|
160
|
+
// (The per-phase read-cache stamp — `_phaseStamp` in core/Aspect.ts — advances inside the phase
|
|
161
|
+
// runners themselves, so it is right on this path and on the setLoop fallback alike.)
|
|
162
|
+
_creator.setEarlyUpdate(early)
|
|
154
163
|
_creator.setLateUpdate(late)
|
|
155
164
|
return true
|
|
156
165
|
}, (fixed) => {
|
|
157
|
-
// The fixed phase runs INSIDE the engine's substep loop (physics
|
|
158
|
-
// hook
|
|
159
|
-
// an older host returns false and the dispatcher steps its own 1/60 accumulator.
|
|
166
|
+
// The fixed phase runs INSIDE the engine's substep loop (creator-physics' frame protocol) when the host has the
|
|
167
|
+
// hook. Feature-detected: an older host returns false and the dispatcher steps its own 1/60 accumulator.
|
|
160
168
|
if (typeof _creator.setFixedUpdate !== "function") return false
|
|
161
|
-
_creator.setFixedUpdate(
|
|
169
|
+
_creator.setFixedUpdate(fixed)
|
|
162
170
|
return true
|
|
163
171
|
})
|
|
164
172
|
// Time.scale / Time.paused reach the engine's own clocks (physics, animators, particles) through
|
|
@@ -172,6 +180,8 @@ export class Scene implements Presentable {
|
|
|
172
180
|
/** @internal the IBL intensity this scene was built with (Lightmap.load's ambient floor). */
|
|
173
181
|
_environmentIntensity = 20000
|
|
174
182
|
readonly camera: Camera
|
|
183
|
+
/** The listener + global 3D audio knobs (docs/audio-plan.md). */
|
|
184
|
+
readonly audio: SceneAudio = new SceneAudio()
|
|
175
185
|
|
|
176
186
|
/** @internal touch listeners, read by the dispatch system. */
|
|
177
187
|
readonly _clickListeners: Array<(ev: ClickEvent<Node | null>) => void> = []
|
|
@@ -220,9 +230,11 @@ export class Scene implements Presentable {
|
|
|
220
230
|
// `false` must reach the host: desktop defaults to 4× MSAA, so an explicit off is a real change.
|
|
221
231
|
_creator.setSceneMultiSampleAntiAliasing(this._id, a !== false, typeof a === "number" ? a : 4)
|
|
222
232
|
}
|
|
233
|
+
if (options.stencil !== undefined) this.setStencil(options.stencil)
|
|
223
234
|
// Before anything else in this constructor that could load a texture: the value has to be in
|
|
224
235
|
// place by the time assets bind their samplers.
|
|
225
|
-
if (options.anisotropy !== undefined)
|
|
236
|
+
if (options.anisotropy !== undefined) Texture.anisotropy = options.anisotropy
|
|
237
|
+
if (options.maxTextureSize !== undefined) Texture.maxSize = options.maxTextureSize
|
|
226
238
|
if (options.hdr) {
|
|
227
239
|
if (options.hdr.strength !== undefined) device.hdr.strength = options.hdr.strength
|
|
228
240
|
if (options.hdr.paperWhite !== undefined) device.hdr.paperWhite = options.hdr.paperWhite
|
|
@@ -280,6 +292,28 @@ export class Scene implements Presentable {
|
|
|
280
292
|
setAntialias(enabled: boolean, scale = 4): void {
|
|
281
293
|
_creator.setSceneMultiSampleAntiAliasing(this._id, enabled, scale)
|
|
282
294
|
}
|
|
295
|
+
/** Depth-reading effects on / off (a graphics-settings menu): soft particles and projected decals
|
|
296
|
+
* read the scene depth, which costs a depth pre-pass of every opaque draw (~12 % of a fill-bound
|
|
297
|
+
* frame). Off = hard-edged particles, no decals, no pre-pass. Engine-wide, live. */
|
|
298
|
+
setDepthEffects(enabled: boolean): void {
|
|
299
|
+
_creator.setDepthEffects?.(enabled)
|
|
300
|
+
}
|
|
301
|
+
/** LOD distance (a graphics-settings menu): the engine's LOD thresholds × `bias`. 2 = every level
|
|
302
|
+
* switches at half the distance (a model must look twice as big on screen to keep its detail),
|
|
303
|
+
* 0.5 = full detail twice as far, 1 = the defaults. Engine-wide, live. Only GLBs that carry
|
|
304
|
+
* `_LOD<n>` meshes (`lecodes assets doctor --lod`) have levels to switch. */
|
|
305
|
+
setLodBias(bias: number): void {
|
|
306
|
+
_creator.setLodBias?.(bias)
|
|
307
|
+
}
|
|
308
|
+
/** Runtime form of `bloom` / `bloomIntensity` (a graphics-settings menu). */
|
|
309
|
+
setBloom(enabled: boolean, intensity = 0.2): void {
|
|
310
|
+
_creator.setBloomOptions(this._id, enabled, intensity, 1)
|
|
311
|
+
}
|
|
312
|
+
/** The scene's stencil buffer on / off (see `SceneOptions.stencil`). */
|
|
313
|
+
setStencil(enabled: boolean): void {
|
|
314
|
+
if (_creator.setSceneStencil) _creator.setSceneStencil(this._id, enabled)
|
|
315
|
+
else if (enabled) console.warn("[scene] this host has no setSceneStencil — stencil ignored")
|
|
316
|
+
}
|
|
283
317
|
|
|
284
318
|
add(...nodes: Node[]): this {
|
|
285
319
|
for (const n of nodes) _creator.addEntityToScene(this._id, n.id)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// `scene.audio` (docs/audio-plan.md §2): the listener and the global 3D knobs. The listener is the
|
|
2
|
+
// scene's active camera by default — a `CameraPlace` switch or a follow rig changes nothing, the
|
|
3
|
+
// engine reads whatever camera it renders with. Set a node to listen from somewhere else (the
|
|
4
|
+
// player's head in a third-person game, so sounds pan around the character, not the camera).
|
|
5
|
+
|
|
6
|
+
import { audioSupported } from "../audio/support"
|
|
7
|
+
import type { Node } from "./Node"
|
|
8
|
+
|
|
9
|
+
export class SceneAudio {
|
|
10
|
+
private _listener: Node | null = null
|
|
11
|
+
private _doppler = 1
|
|
12
|
+
|
|
13
|
+
/** The node the engine listens from; null (default) = the active camera. */
|
|
14
|
+
get listener(): Node | null { return this._listener }
|
|
15
|
+
set listener(node: Node | null) {
|
|
16
|
+
this._listener = node
|
|
17
|
+
if (audioSupported) _creatorAudio.setListener(node ? node.id : 0)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Multiplies every source's doppler amount (0 = off everywhere). Default 1. */
|
|
21
|
+
get dopplerFactor(): number { return this._doppler }
|
|
22
|
+
set dopplerFactor(v: number) {
|
|
23
|
+
this._doppler = v
|
|
24
|
+
if (audioSupported) _creatorAudio.setListenerOptions(v)
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/gl/Texture.ts
CHANGED
|
@@ -2,7 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
import { fetch, type FetchResponse, type File } from "../runtime/fetch"
|
|
4
4
|
|
|
5
|
+
/** `Texture.load` options. */
|
|
6
|
+
export type TextureLoadOptions = {
|
|
7
|
+
/** `true` (default): colour, stored sRGB. `false`: data (normal map, mask, heightmap) — kept linear. */
|
|
8
|
+
srgb?: boolean
|
|
9
|
+
/** Ignore `Texture.maxSize` for this texture (a lightmap page, a lookup table). */
|
|
10
|
+
fullSize?: boolean
|
|
11
|
+
}
|
|
12
|
+
/** `_creator.createTexture` flags bit: linear data (creator-gl CREATOR_TEXTURE_LINEAR). */
|
|
13
|
+
const TEXTURE_FLAG_LINEAR = 1
|
|
14
|
+
/** `_creator.createTexture` flags bit: exempt from `Texture.maxSize` (creator-gl CREATOR_TEXTURE_FULL_SIZE). */
|
|
15
|
+
const TEXTURE_FLAG_FULL_SIZE = 2
|
|
16
|
+
|
|
5
17
|
export class Texture {
|
|
18
|
+
static _maxSize = 0
|
|
19
|
+
/** Engine-wide cap on texture size (a "texture quality" setting), 0 = none. A KTX2 wider or
|
|
20
|
+
* taller than this loses its top mip levels on load (nothing resampled, less memory and
|
|
21
|
+
* bandwidth), a glTF PNG/JPEG is downsampled. Reaches textures loaded AFTER it is set — a loaded
|
|
22
|
+
* level keeps its textures — so set it up front (`SceneOptions.maxTextureSize`, or before the
|
|
23
|
+
* level loads) and apply a menu change on the next level load. Lightmap pages are exempt
|
|
24
|
+
* (`TextureLoadOptions.fullSize`). A host may pin it (desktop `CREATOR_TEXTURE_MAX_SIZE`). */
|
|
25
|
+
static _anisotropy = 2
|
|
26
|
+
/** Engine-wide anisotropic filtering, 1 (off) … 16 (default 2; `SceneOptions.anisotropy` sets it up
|
|
27
|
+
* front). A sampler is baked when its texture is bound, so like `maxSize` this reaches textures
|
|
28
|
+
* loaded AFTER it — set it before the level loads. Measured on a lightmapped interior at 720p:
|
|
29
|
+
* 4× costs ~20 % of the frame over 1× on an integrated GPU. A host may pin it. */
|
|
30
|
+
static get anisotropy(): number { return Texture._anisotropy }
|
|
31
|
+
static set anisotropy(level: number) {
|
|
32
|
+
Texture._anisotropy = level >= 1 ? Math.min(16, level) : 1
|
|
33
|
+
_creator.setTextureAnisotropy?.(Texture._anisotropy)
|
|
34
|
+
}
|
|
35
|
+
static get maxSize(): number { return Texture._maxSize }
|
|
36
|
+
static set maxSize(size: number) {
|
|
37
|
+
Texture._maxSize = size > 0 ? Math.floor(size) : 0
|
|
38
|
+
_creator.setTextureMaxSize?.(Texture._maxSize)
|
|
39
|
+
}
|
|
6
40
|
/** @internal native texture handle. */
|
|
7
41
|
readonly _id: number
|
|
8
42
|
readonly width: number
|
|
@@ -47,17 +81,23 @@ export class Texture {
|
|
|
47
81
|
_creator.updateTexturePixels?.(this._id, x, y, width, height, data)
|
|
48
82
|
}
|
|
49
83
|
|
|
50
|
-
|
|
84
|
+
/** Decode an image (PNG / JPG, or a KTX2 the core transcodes) into a texture. `srgb` (default
|
|
85
|
+
* true) says the bytes are COLOUR, stored sRGB so the GPU linearises them on sample; pass
|
|
86
|
+
* `false` for DATA — a normal map, a mask, a heightmap — which must come back as stored (a
|
|
87
|
+
* flat normal read through sRGB bends by ~35°). A KTX2 decides by its own header. Image
|
|
88
|
+
* textures get a mip chain (trilinear) on hosts that build one. */
|
|
89
|
+
static load(source: string | FetchResponse | File, options: TextureLoadOptions = {}): Promise<Texture> {
|
|
51
90
|
if (typeof source === "string") {
|
|
52
91
|
return fetch(source, { useOnce: true }).then((resp) => {
|
|
53
92
|
if (resp.status >= 400) {
|
|
54
93
|
return Promise.reject(new Error(`Failed to fetch texture from ${source}. HTTP ${resp.status}`))
|
|
55
94
|
}
|
|
56
|
-
return Texture.load(resp)
|
|
95
|
+
return Texture.load(resp, options)
|
|
57
96
|
})
|
|
58
97
|
}
|
|
98
|
+
const flags = (options.srgb === false ? TEXTURE_FLAG_LINEAR : 0) | (options.fullSize ? TEXTURE_FLAG_FULL_SIZE : 0)
|
|
59
99
|
return new Promise<Texture>((resolve, reject) => {
|
|
60
|
-
_creator.createTexture((source as any)._id, (id, w, h) => resolve(new Texture(w, h, id)), reject)
|
|
100
|
+
_creator.createTexture((source as any)._id, (id, w, h) => resolve(new Texture(w, h, id)), reject, flags)
|
|
61
101
|
})
|
|
62
102
|
}
|
|
63
103
|
}
|
package/src/gl/Vehicle.ts
CHANGED
|
@@ -25,13 +25,12 @@
|
|
|
25
25
|
//
|
|
26
26
|
// Layering, and why it is where it is: docs/vehicle-rework-plan.md.
|
|
27
27
|
|
|
28
|
-
import { Aspect } from "../core/Aspect"
|
|
28
|
+
import { Aspect, _phaseStamp } from "../core/Aspect"
|
|
29
29
|
import type { FieldMeta } from "../core/fields"
|
|
30
30
|
import { Mat4 } from "../math/mat4"
|
|
31
31
|
import { Vec3, cx, cy, cz, type Vec3Like } from "../math/vec"
|
|
32
32
|
import { cw, type QuatLike } from "../math/quat"
|
|
33
33
|
import { Gizmos } from "../scene/gizmos"
|
|
34
|
-
import { glState } from "./state"
|
|
35
34
|
import { Gearbox } from "./Gearbox"
|
|
36
35
|
import { Node } from "./Node"
|
|
37
36
|
import { DEFAULT_FRICTION, Physics } from "./Physics"
|
|
@@ -314,12 +313,13 @@ export class Vehicle extends Aspect<"vehicle", Node> {
|
|
|
314
313
|
if (this._id) _creator.vehicleSetTransmission(this._id, ratio, clamp(clutch, 0, 1))
|
|
315
314
|
}
|
|
316
315
|
|
|
317
|
-
// --- readouts (one native read per
|
|
316
|
+
// --- readouts (one native read per PHASE — early sees the pre-step state, late the settled one —
|
|
317
|
+
// shared by the car and every wheel; keyed on core/Aspect's _phaseStamp) -------------------
|
|
318
318
|
|
|
319
319
|
private _read(): Float32Array {
|
|
320
|
-
if (this._id && this._stateFrame !==
|
|
320
|
+
if (this._id && this._stateFrame !== _phaseStamp.value) {
|
|
321
321
|
_creator.vehicleGetState(this._id, this._state)
|
|
322
|
-
this._stateFrame =
|
|
322
|
+
this._stateFrame = _phaseStamp.value
|
|
323
323
|
}
|
|
324
324
|
return this._state
|
|
325
325
|
}
|