lecodes-sdk 2.0.9 → 2.0.11
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 +1 -0
- package/dist/host.d.ts +6 -2
- package/dist/types/gl/Light.d.ts +0 -11
- package/dist/types/runtime/misc.d.ts +4 -0
- package/dist/types/ui/UITabs.d.ts +16 -3
- package/dist/types/version.d.ts +1 -1
- package/dist/types.json +1 -1
- package/package.json +1 -1
- package/prompts/core.md +1 -1
- package/prompts/ui.md +5 -7
- package/src/compile/assetMacro.ts +69 -4
- package/src/compile/bundler.ts +10 -3
- package/src/gl/Light.ts +257 -270
- package/src/host.d.ts +6 -2
- package/src/runtime/misc.ts +4 -0
- package/src/ui/UIPager.ts +2 -0
- package/src/ui/UITabs.ts +43 -5
- package/src/version.ts +1 -1
package/src/gl/Light.ts
CHANGED
|
@@ -1,270 +1,257 @@
|
|
|
1
|
-
// Lights. A directional sun (the engine's primary light), punctual point lights and spot lights. Add one to a
|
|
2
|
-
// scene like any node; `intensity` and `color` stay writable so a light can be animated.
|
|
3
|
-
|
|
4
|
-
import { Color, type ColorInput } from "../core/color"
|
|
5
|
-
import type { Vec3Like } from "../math/vec"
|
|
6
|
-
import { Node, type NodeTweenProps } from "./Node"
|
|
7
|
-
import { DOM_GL_LIGHT, colorValue, floatValue, type TweenChannel, type TweenMeta } from "../animate/tween/spec"
|
|
8
|
-
import type { Animation } from "../animate/tween/Animation"
|
|
9
|
-
|
|
10
|
-
/** Animatable light props on top of the node transform. */
|
|
11
|
-
export type LightTweenProps = NodeTweenProps & {
|
|
12
|
-
intensity?: number | number[],
|
|
13
|
-
color?: ColorInput | ColorInput[],
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type SunOptions = {
|
|
17
|
-
/** Light direction (points where the light travels). Defaults to a typical key-light angle. */
|
|
18
|
-
direction?: Vec3Like
|
|
19
|
-
/** Luminous intensity. */
|
|
20
|
-
intensity?: number
|
|
21
|
-
color?: ColorInput
|
|
22
|
-
/**
|
|
23
|
-
* Shadow quality (default 1):
|
|
24
|
-
* 0 - no shadows
|
|
25
|
-
* 1 - 1024 map, hard edges (one filtered tap; the cheap level, ~3 % of a frame on an iGPU)
|
|
26
|
-
* 2 - 1024 map, soft edges (variance shadows + blur; ~+15 % of a frame on an iGPU)
|
|
27
|
-
* 3 - 2048 map, contact-hardening soft edges (PCSS: sharp where the caster touches, softer
|
|
28
|
-
* away; discrete-GPU territory, ~+60 % of a frame on an iGPU)
|
|
29
|
-
* A lightmapped level already carries every static shadow, so 0 is a legitimate choice there.
|
|
30
|
-
* On a level with a baked light grid the variance / PCSS filters cannot be used (they need every
|
|
31
|
-
* receiver in the shadow map, and the baked statics are kept out of it): 3 renders as PCSS on the
|
|
32
|
-
* depth map - the same contact-hardening look - and 2 renders as 1.
|
|
33
|
-
*/
|
|
34
|
-
shadowsQuality?: 0 | 1 | 2 | 3
|
|
35
|
-
/**
|
|
36
|
-
* Metres from the camera within which the sun casts shadows (default 100). Shadows fade out by
|
|
37
|
-
* this distance and the shadow map covers only this range, so a shorter distance is crisper for
|
|
38
|
-
* the same quality and puts fewer casters in the shadow pass. 30-40 is plenty in first person.
|
|
39
|
-
*/
|
|
40
|
-
shadowDistance?: number
|
|
41
|
-
/**
|
|
42
|
-
* How many shadow-map cascades split `shadowDistance` (default 1). One map over the whole range gives a tall
|
|
43
|
-
* tree 5-10 cm shadow texels on its own trunk; 3 cascades put a tight map on the first metres and coarser
|
|
44
|
-
* ones behind, fit to their texel grids so the shadow does not swim as the camera moves. Each cascade is
|
|
45
|
-
* another caster pass, so it costs in proportion to what casts: forests, not a lightmapped level.
|
|
46
|
-
*/
|
|
47
|
-
shadowCascades?: 1 | 2 | 3 | 4
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export type PointOptions = {
|
|
51
|
-
/**
|
|
52
|
-
* Luminous POWER in lumens — the same physical scale as the sun's lux and `environmentIntensity`,
|
|
53
|
-
* so a light keeps its look when the scene's exposure changes. A candle is ~12 lm, a bare 60 W
|
|
54
|
-
* bulb ~800, a car headlight ~1500, a fireball millions. Default 1000.
|
|
55
|
-
*/
|
|
56
|
-
intensity?: number
|
|
57
|
-
color?: ColorInput
|
|
58
|
-
/**
|
|
59
|
-
* Metres of influence — past it the light contributes nothing. This is the performance knob:
|
|
60
|
-
* overlapping point lights are the expensive case, so keep it as small as the look allows.
|
|
61
|
-
* Default 10.
|
|
62
|
-
*/
|
|
63
|
-
range?: number
|
|
64
|
-
/** Point-light shadows are a cubemap render per light; off by default. */
|
|
65
|
-
castShadows?: boolean
|
|
66
|
-
/**
|
|
67
|
-
* Whether `lecodes lightmap bake` bakes this light into the level's lightmap. A baked lamp lights the statics
|
|
68
|
-
* from the atlas and the MOVERS from the level's light grid, and is held dark in real time while that grid is
|
|
69
|
-
* loaded (without a grid it keeps lighting the movers live); a light
|
|
70
|
-
* that must reach the statics live too — a flicker, a lamp the player can shoot out, a muzzle flash —
|
|
71
|
-
* says `baked: false`: it is kept out of the bake and put on the statics' light channel. Default true.
|
|
72
|
-
*/
|
|
73
|
-
baked?: boolean
|
|
74
|
-
/**
|
|
75
|
-
* THE BAKE'S SHAPE of this lamp: `[width, height]` in metres = an AREA light - a rectangle in the light's local XZ
|
|
76
|
-
* plane, emitting along its local -Y (down, for an unrotated node) with the same lumens: a ceiling panel. The bake
|
|
77
|
-
* lights from the whole rectangle (soft shadows, light from where the panel is and not from a point inside the
|
|
78
|
-
* fixture). Real time it is still the point light above. Ignored with `baked: false`.
|
|
79
|
-
*/
|
|
80
|
-
bakeArea?: readonly [number, number]
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export type SpotOptions = {
|
|
84
|
-
/**
|
|
85
|
-
* Luminous power in lumens, as a point light's. The cone does not change the brightness: widen it and the
|
|
86
|
-
* same lumens light a larger patch at the same level. On its axis a spot is 4x brighter than a point light of
|
|
87
|
-
* the same lumens (its light is not spread over the whole sphere). Default 1000.
|
|
88
|
-
*/
|
|
89
|
-
intensity?: number
|
|
90
|
-
color?: ColorInput
|
|
91
|
-
/** Metres of influence along the cone, the performance knob as for a point light. Default 10. */
|
|
92
|
-
range?: number
|
|
93
|
-
/** The cone's full angle in degrees, up to 180: past it the light contributes nothing. Default 45. */
|
|
94
|
-
angle?: number
|
|
95
|
-
/** The full angle in degrees of the cone's fully lit core; the light fades from it out to `angle`. Default 0.75 x `angle`. */
|
|
96
|
-
innerAngle?: number
|
|
97
|
-
/** A spot's shadow is one 2D shadow map (a point light's is a cubemap); off by default. */
|
|
98
|
-
castShadows?: boolean
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export class Light extends Node {
|
|
102
|
-
private _intensity = 0
|
|
103
|
-
|
|
104
|
-
/** Tween `intensity` / `color` (a flash, a sunrise) and the transform — see {@link Node.animateTo}. */
|
|
105
|
-
override animateTo(props: LightTweenProps & TweenMeta): Animation { return super.animateTo(props) }
|
|
106
|
-
override animateFrom(props: LightTweenProps & TweenMeta): Animation { return super.animateFrom(props) }
|
|
107
|
-
/** @internal */
|
|
108
|
-
override _tweenChannel(prop: string): TweenChannel | null {
|
|
109
|
-
const id = () => this.id
|
|
110
|
-
if (prop === "intensity") return { domain: DOM_GL_LIGHT, id, value: floatValue, commit: (v) => { this._intensity = v as number } }
|
|
111
|
-
if (prop === "color") return { domain: DOM_GL_LIGHT, id, value: (v) => colorValue(v, Color.toRgba01) }
|
|
112
|
-
return super._tweenChannel(prop)
|
|
113
|
-
}
|
|
114
|
-
/** @internal PointOptions.baked — a candidate for the lightmap bake (default true). */
|
|
115
|
-
_baked = true
|
|
116
|
-
/** @internal every live point light, for the bake / the switch-off; a destroyed one leaves. */
|
|
117
|
-
static _points = new Set<Light>()
|
|
118
|
-
/** @internal sun direction as created (the engine keeps it). */
|
|
119
|
-
_direction: [number, number, number] = [ 0, -1, 0 ]
|
|
120
|
-
/** The most recently created sun. */
|
|
121
|
-
static lastSun: Light | null = null
|
|
122
|
-
/** @internal the colour as created / last set (a settings menu recreates a sun from these). */
|
|
123
|
-
_color: ColorInput = 0xffffff
|
|
124
|
-
/** @internal sun shadow options as created (creation-time in the engine). */
|
|
125
|
-
_shadowsQuality = 1
|
|
126
|
-
_shadowDistance = 100
|
|
127
|
-
_shadowCascades = 1
|
|
128
|
-
|
|
129
|
-
/** A directional sun light. */
|
|
130
|
-
static sun(options: SunOptions = {}): Light {
|
|
131
|
-
const light = new Light()
|
|
132
|
-
const [ dx, dy, dz ] = options.direction ?? [ 0.548267, -0.473983, -0.689016 ]
|
|
133
|
-
light._direction = [ dx, dy, dz ]
|
|
134
|
-
light._intensity = options.intensity ?? 100000
|
|
135
|
-
light._color = options.color ?? 0xffffff
|
|
136
|
-
light._shadowsQuality = options.shadowsQuality ?? 1
|
|
137
|
-
light._shadowDistance = options.shadowDistance ?? 100
|
|
138
|
-
light._shadowCascades = options.shadowCascades ?? 1
|
|
139
|
-
Light.lastSun = light
|
|
140
|
-
_creator.createSunLight(
|
|
141
|
-
light.id, dx, dy, dz,
|
|
142
|
-
light._intensity,
|
|
143
|
-
Color.toPackedRgb(options.color ?? 0xffffff),
|
|
144
|
-
options.shadowsQuality ?? 1,
|
|
145
|
-
options.shadowDistance ?? 100,
|
|
146
|
-
light._shadowCascades,
|
|
147
|
-
)
|
|
148
|
-
return light
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* A point light — a lamp, a muzzle flash, a fireball. Position it like any node.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
this.
|
|
229
|
-
_creator.
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
set color(value: ColorInput) {
|
|
261
|
-
this._color = value
|
|
262
|
-
_creator.setLightColor?.(this.id, Color.toPackedRgb(value))
|
|
263
|
-
}
|
|
264
|
-
get color(): ColorInput { return this._color }
|
|
265
|
-
/** Sun direction as created (the engine keeps it; a settings menu rebuilds a sun from it). */
|
|
266
|
-
get direction(): [number, number, number] { return this._direction }
|
|
267
|
-
/** Sun shadow options as created — creation-time in the engine, so a change means a new sun. */
|
|
268
|
-
get shadowsQuality(): number { return this._shadowsQuality }
|
|
269
|
-
get shadowDistance(): number { return this._shadowDistance }
|
|
270
|
-
}
|
|
1
|
+
// Lights. A directional sun (the engine's primary light), punctual point lights and spot lights. Add one to a
|
|
2
|
+
// scene like any node; `intensity` and `color` stay writable so a light can be animated.
|
|
3
|
+
|
|
4
|
+
import { Color, type ColorInput } from "../core/color"
|
|
5
|
+
import type { Vec3Like } from "../math/vec"
|
|
6
|
+
import { Node, type NodeTweenProps } from "./Node"
|
|
7
|
+
import { DOM_GL_LIGHT, colorValue, floatValue, type TweenChannel, type TweenMeta } from "../animate/tween/spec"
|
|
8
|
+
import type { Animation } from "../animate/tween/Animation"
|
|
9
|
+
|
|
10
|
+
/** Animatable light props on top of the node transform. */
|
|
11
|
+
export type LightTweenProps = NodeTweenProps & {
|
|
12
|
+
intensity?: number | number[],
|
|
13
|
+
color?: ColorInput | ColorInput[],
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type SunOptions = {
|
|
17
|
+
/** Light direction (points where the light travels). Defaults to a typical key-light angle. */
|
|
18
|
+
direction?: Vec3Like
|
|
19
|
+
/** Luminous intensity. */
|
|
20
|
+
intensity?: number
|
|
21
|
+
color?: ColorInput
|
|
22
|
+
/**
|
|
23
|
+
* Shadow quality (default 1):
|
|
24
|
+
* 0 - no shadows
|
|
25
|
+
* 1 - 1024 map, hard edges (one filtered tap; the cheap level, ~3 % of a frame on an iGPU)
|
|
26
|
+
* 2 - 1024 map, soft edges (variance shadows + blur; ~+15 % of a frame on an iGPU)
|
|
27
|
+
* 3 - 2048 map, contact-hardening soft edges (PCSS: sharp where the caster touches, softer
|
|
28
|
+
* away; discrete-GPU territory, ~+60 % of a frame on an iGPU)
|
|
29
|
+
* A lightmapped level already carries every static shadow, so 0 is a legitimate choice there.
|
|
30
|
+
* On a level with a baked light grid the variance / PCSS filters cannot be used (they need every
|
|
31
|
+
* receiver in the shadow map, and the baked statics are kept out of it): 3 renders as PCSS on the
|
|
32
|
+
* depth map - the same contact-hardening look - and 2 renders as 1.
|
|
33
|
+
*/
|
|
34
|
+
shadowsQuality?: 0 | 1 | 2 | 3
|
|
35
|
+
/**
|
|
36
|
+
* Metres from the camera within which the sun casts shadows (default 100). Shadows fade out by
|
|
37
|
+
* this distance and the shadow map covers only this range, so a shorter distance is crisper for
|
|
38
|
+
* the same quality and puts fewer casters in the shadow pass. 30-40 is plenty in first person.
|
|
39
|
+
*/
|
|
40
|
+
shadowDistance?: number
|
|
41
|
+
/**
|
|
42
|
+
* How many shadow-map cascades split `shadowDistance` (default 1). One map over the whole range gives a tall
|
|
43
|
+
* tree 5-10 cm shadow texels on its own trunk; 3 cascades put a tight map on the first metres and coarser
|
|
44
|
+
* ones behind, fit to their texel grids so the shadow does not swim as the camera moves. Each cascade is
|
|
45
|
+
* another caster pass, so it costs in proportion to what casts: forests, not a lightmapped level.
|
|
46
|
+
*/
|
|
47
|
+
shadowCascades?: 1 | 2 | 3 | 4
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type PointOptions = {
|
|
51
|
+
/**
|
|
52
|
+
* Luminous POWER in lumens — the same physical scale as the sun's lux and `environmentIntensity`,
|
|
53
|
+
* so a light keeps its look when the scene's exposure changes. A candle is ~12 lm, a bare 60 W
|
|
54
|
+
* bulb ~800, a car headlight ~1500, a fireball millions. Default 1000.
|
|
55
|
+
*/
|
|
56
|
+
intensity?: number
|
|
57
|
+
color?: ColorInput
|
|
58
|
+
/**
|
|
59
|
+
* Metres of influence — past it the light contributes nothing. This is the performance knob:
|
|
60
|
+
* overlapping point lights are the expensive case, so keep it as small as the look allows.
|
|
61
|
+
* Default 10.
|
|
62
|
+
*/
|
|
63
|
+
range?: number
|
|
64
|
+
/** Point-light shadows are a cubemap render per light; off by default. */
|
|
65
|
+
castShadows?: boolean
|
|
66
|
+
/**
|
|
67
|
+
* Whether `lecodes lightmap bake` bakes this light into the level's lightmap. A baked lamp lights the statics
|
|
68
|
+
* from the atlas and the MOVERS from the level's light grid, and is held dark in real time while that grid is
|
|
69
|
+
* loaded (without a grid it keeps lighting the movers live); a light
|
|
70
|
+
* that must reach the statics live too — a flicker, a lamp the player can shoot out, a muzzle flash —
|
|
71
|
+
* says `baked: false`: it is kept out of the bake and put on the statics' light channel. Default true.
|
|
72
|
+
*/
|
|
73
|
+
baked?: boolean
|
|
74
|
+
/**
|
|
75
|
+
* THE BAKE'S SHAPE of this lamp: `[width, height]` in metres = an AREA light - a rectangle in the light's local XZ
|
|
76
|
+
* plane, emitting along its local -Y (down, for an unrotated node) with the same lumens: a ceiling panel. The bake
|
|
77
|
+
* lights from the whole rectangle (soft shadows, light from where the panel is and not from a point inside the
|
|
78
|
+
* fixture). Real time it is still the point light above. Ignored with `baked: false`.
|
|
79
|
+
*/
|
|
80
|
+
bakeArea?: readonly [number, number]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type SpotOptions = {
|
|
84
|
+
/**
|
|
85
|
+
* Luminous power in lumens, as a point light's. The cone does not change the brightness: widen it and the
|
|
86
|
+
* same lumens light a larger patch at the same level. On its axis a spot is 4x brighter than a point light of
|
|
87
|
+
* the same lumens (its light is not spread over the whole sphere). Default 1000.
|
|
88
|
+
*/
|
|
89
|
+
intensity?: number
|
|
90
|
+
color?: ColorInput
|
|
91
|
+
/** Metres of influence along the cone, the performance knob as for a point light. Default 10. */
|
|
92
|
+
range?: number
|
|
93
|
+
/** The cone's full angle in degrees, up to 180: past it the light contributes nothing. Default 45. */
|
|
94
|
+
angle?: number
|
|
95
|
+
/** The full angle in degrees of the cone's fully lit core; the light fades from it out to `angle`. Default 0.75 x `angle`. */
|
|
96
|
+
innerAngle?: number
|
|
97
|
+
/** A spot's shadow is one 2D shadow map (a point light's is a cubemap); off by default. */
|
|
98
|
+
castShadows?: boolean
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export class Light extends Node {
|
|
102
|
+
private _intensity = 0
|
|
103
|
+
|
|
104
|
+
/** Tween `intensity` / `color` (a flash, a sunrise) and the transform — see {@link Node.animateTo}. */
|
|
105
|
+
override animateTo(props: LightTweenProps & TweenMeta): Animation { return super.animateTo(props) }
|
|
106
|
+
override animateFrom(props: LightTweenProps & TweenMeta): Animation { return super.animateFrom(props) }
|
|
107
|
+
/** @internal */
|
|
108
|
+
override _tweenChannel(prop: string): TweenChannel | null {
|
|
109
|
+
const id = () => this.id
|
|
110
|
+
if (prop === "intensity") return { domain: DOM_GL_LIGHT, id, value: floatValue, commit: (v) => { this._intensity = v as number } }
|
|
111
|
+
if (prop === "color") return { domain: DOM_GL_LIGHT, id, value: (v) => colorValue(v, Color.toRgba01) }
|
|
112
|
+
return super._tweenChannel(prop)
|
|
113
|
+
}
|
|
114
|
+
/** @internal PointOptions.baked — a candidate for the lightmap bake (default true). */
|
|
115
|
+
_baked = true
|
|
116
|
+
/** @internal every live point light, for the bake / the switch-off; a destroyed one leaves. */
|
|
117
|
+
static _points = new Set<Light>()
|
|
118
|
+
/** @internal sun direction as created (the engine keeps it). */
|
|
119
|
+
_direction: [number, number, number] = [ 0, -1, 0 ]
|
|
120
|
+
/** The most recently created sun. */
|
|
121
|
+
static lastSun: Light | null = null
|
|
122
|
+
/** @internal the colour as created / last set (a settings menu recreates a sun from these). */
|
|
123
|
+
_color: ColorInput = 0xffffff
|
|
124
|
+
/** @internal sun shadow options as created (creation-time in the engine). */
|
|
125
|
+
_shadowsQuality = 1
|
|
126
|
+
_shadowDistance = 100
|
|
127
|
+
_shadowCascades = 1
|
|
128
|
+
|
|
129
|
+
/** A directional sun light. */
|
|
130
|
+
static sun(options: SunOptions = {}): Light {
|
|
131
|
+
const light = new Light()
|
|
132
|
+
const [ dx, dy, dz ] = options.direction ?? [ 0.548267, -0.473983, -0.689016 ]
|
|
133
|
+
light._direction = [ dx, dy, dz ]
|
|
134
|
+
light._intensity = options.intensity ?? 100000
|
|
135
|
+
light._color = options.color ?? 0xffffff
|
|
136
|
+
light._shadowsQuality = options.shadowsQuality ?? 1
|
|
137
|
+
light._shadowDistance = options.shadowDistance ?? 100
|
|
138
|
+
light._shadowCascades = options.shadowCascades ?? 1
|
|
139
|
+
Light.lastSun = light
|
|
140
|
+
_creator.createSunLight(
|
|
141
|
+
light.id, dx, dy, dz,
|
|
142
|
+
light._intensity,
|
|
143
|
+
Color.toPackedRgb(options.color ?? 0xffffff),
|
|
144
|
+
options.shadowsQuality ?? 1,
|
|
145
|
+
options.shadowDistance ?? 100,
|
|
146
|
+
light._shadowCascades,
|
|
147
|
+
)
|
|
148
|
+
return light
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* A point light — a lamp, a muzzle flash, a fireball. Position it like any node.
|
|
153
|
+
*/
|
|
154
|
+
static point(options: PointOptions = {}): Light {
|
|
155
|
+
const light = new Light()
|
|
156
|
+
light._intensity = options.intensity ?? 1000
|
|
157
|
+
light._color = options.color ?? 0xffffff
|
|
158
|
+
light._baked = options.baked ?? true
|
|
159
|
+
Light._points.add(light)
|
|
160
|
+
_creator.createPointLight?.(
|
|
161
|
+
light.id,
|
|
162
|
+
light._intensity,
|
|
163
|
+
Color.toPackedRgb(options.color ?? 0xffffff),
|
|
164
|
+
options.range ?? 10,
|
|
165
|
+
options.castShadows ?? false,
|
|
166
|
+
)
|
|
167
|
+
if (options.bakeArea && light._baked) _creator.setLightBakeArea?.(light.id, options.bakeArea[0], options.bakeArea[1])
|
|
168
|
+
// an unbaked lamp lights the baked statics live: Filament channel 1 (see Lightmap)
|
|
169
|
+
if (!light._baked) _creator.setLightChannel?.(light.id, 1, true)
|
|
170
|
+
return light
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** @internal a spot's cone, full angles in degrees (the engine takes half-angles in radians). */
|
|
174
|
+
_angle = 0
|
|
175
|
+
_innerAngle = 0
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* A spot light — a flashlight, a headlight, a stage light. It shines along the node's forward (-Z): position
|
|
179
|
+
* it like any node and aim it with `lookAt`. Real time only: the lightmap bake takes no spot lights, so a spot
|
|
180
|
+
* lights the baked statics live too.
|
|
181
|
+
*/
|
|
182
|
+
static spot(options: SpotOptions = {}): Light {
|
|
183
|
+
const light = new Light()
|
|
184
|
+
light._intensity = options.intensity ?? 1000
|
|
185
|
+
light._color = options.color ?? 0xffffff
|
|
186
|
+
light._baked = false
|
|
187
|
+
light._angle = options.angle ?? 45
|
|
188
|
+
light._innerAngle = options.innerAngle ?? light._angle * 0.75
|
|
189
|
+
const [ inner, outer ] = light._cone()
|
|
190
|
+
_creator.createSpotLight?.(
|
|
191
|
+
light.id,
|
|
192
|
+
light._intensity,
|
|
193
|
+
Color.toPackedRgb(light._color),
|
|
194
|
+
options.range ?? 10,
|
|
195
|
+
inner, outer,
|
|
196
|
+
options.castShadows ?? false,
|
|
197
|
+
)
|
|
198
|
+
// the statics of a lightmapped level are on Filament channel 1 alone (see Lightmap)
|
|
199
|
+
_creator.setLightChannel?.(light.id, 1, true)
|
|
200
|
+
return light
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** @internal the cone as the engine takes it: half-angles in radians, outer in (0, 90°], inner in [0, outer]. */
|
|
204
|
+
_cone(): [number, number] {
|
|
205
|
+
const rad = Math.PI / 360
|
|
206
|
+
const outer = Math.min(Math.max(this._angle, 0.1), 180) * rad
|
|
207
|
+
const inner = Math.min(Math.max(this._innerAngle, 0), this._angle) * rad
|
|
208
|
+
return [ Math.min(inner, outer), outer ]
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** A spot's full cone angle in degrees — live (a flashlight's focus). */
|
|
212
|
+
get angle(): number { return this._angle }
|
|
213
|
+
set angle(value: number) {
|
|
214
|
+
this._innerAngle *= this._angle > 0 ? value / this._angle : 0
|
|
215
|
+
this._angle = value
|
|
216
|
+
_creator.setLightCone?.(this.id, ...this._cone())
|
|
217
|
+
}
|
|
218
|
+
/** A spot's fully lit core, full angle in degrees — live. Setting `angle` scales it along. */
|
|
219
|
+
get innerAngle(): number { return this._innerAngle }
|
|
220
|
+
set innerAngle(value: number) {
|
|
221
|
+
this._innerAngle = value
|
|
222
|
+
_creator.setLightCone?.(this.id, ...this._cone())
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Live intensity (sun: lux, point / spot: lumens) — animate a flash without rebuilding the light. */
|
|
226
|
+
get intensity(): number { return this._intensity }
|
|
227
|
+
set intensity(value: number) {
|
|
228
|
+
this._intensity = value
|
|
229
|
+
if (!this._heldDark) _creator.setLightIntensity?.(this.id, value)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** @internal A BAKED lamp is held dark while the level's LIGHT GRID is loaded (Lightmap): the statics have it in the
|
|
233
|
+
* atlas and the movers in the grid's ambient cubes - left burning it lit every mover and unbaked prop a second time,
|
|
234
|
+
* without a shadow (through walls), and put a hot spot on its own fixture. `intensity` keeps the authored value. */
|
|
235
|
+
_heldDark = false
|
|
236
|
+
_holdDark(dark: boolean): void {
|
|
237
|
+
if (this._heldDark === dark) return
|
|
238
|
+
this._heldDark = dark
|
|
239
|
+
_creator.setLightIntensity?.(this.id, dark ? 0 : this._intensity)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
destroy(): void {
|
|
243
|
+
Light._points.delete(this)
|
|
244
|
+
super.destroy()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
set color(value: ColorInput) {
|
|
248
|
+
this._color = value
|
|
249
|
+
_creator.setLightColor?.(this.id, Color.toPackedRgb(value))
|
|
250
|
+
}
|
|
251
|
+
get color(): ColorInput { return this._color }
|
|
252
|
+
/** Sun direction as created (the engine keeps it; a settings menu rebuilds a sun from it). */
|
|
253
|
+
get direction(): [number, number, number] { return this._direction }
|
|
254
|
+
/** Sun shadow options as created — creation-time in the engine, so a change means a new sun. */
|
|
255
|
+
get shadowsQuality(): number { return this._shadowsQuality }
|
|
256
|
+
get shadowDistance(): number { return this._shadowDistance }
|
|
257
|
+
}
|
package/src/host.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// JS-runtime globals provided by the host engine (web viewer / desktop / iOS), NOT by the SDK.
|
|
2
2
|
// Declared only so SDK and user code type-check — never bundled or injected.
|
|
3
3
|
|
|
4
|
+
// The one SDK type the macros return: an SVG image source, the same `SvgSource` a project has as
|
|
5
|
+
// a global (build-types.ts points this import into dist/types when it copies the file).
|
|
6
|
+
import type { SvgSource } from "./runtime/misc"
|
|
7
|
+
|
|
4
8
|
declare global {
|
|
5
9
|
function setTimeout(handler: (...args: any[]) => void, timeout?: number): number
|
|
6
10
|
function setInterval(handler: (...args: any[]) => void, timeout?: number): number
|
|
@@ -33,7 +37,7 @@ declare global {
|
|
|
33
37
|
function asset(path: `${string}.json`): any
|
|
34
38
|
/** Compile-time macro: `asset('./logo.svg')` yields the file as an SVG image source — for
|
|
35
39
|
* `UIImage(...)` and `bgImage`. */
|
|
36
|
-
function asset(path: `${string}.svg`):
|
|
40
|
+
function asset(path: `${string}.svg`): SvgSource
|
|
37
41
|
/** Compile-time macro: `asset('./hero.png')` is desugared by the bundler into the module import
|
|
38
42
|
* for that resource. The file must exist — a path that resolves to nothing fails the compile
|
|
39
43
|
* (`asset not found: ./hero.png (main.ts:3)`); there is no runtime fallback. Calls inside
|
|
@@ -53,7 +57,7 @@ declare global {
|
|
|
53
57
|
* (e.g. `"lucide:bell"`), string literal only. Recolor via `{ color }`: a hex LITERAL is baked
|
|
54
58
|
* into the SVG at compile time, a token/expression is applied as a tint (as is the `tintColor`
|
|
55
59
|
* style prop). */
|
|
56
|
-
function assetIcon(id: string, opts?: { color?: string }):
|
|
60
|
+
function assetIcon(id: string, opts?: { color?: string }): SvgSource
|
|
57
61
|
|
|
58
62
|
/** Type-only editor convenience: the style object type of a UI element. `Style<UIButton>` (or
|
|
59
63
|
* `Style<typeof myButton>`) is what you'd pass to `el.style(...)` — for typing reusable style
|
package/src/runtime/misc.ts
CHANGED
|
@@ -18,3 +18,7 @@ export type SvgSourceValue = {
|
|
|
18
18
|
|
|
19
19
|
/** Wrap raw SVG XML so it can be used as an image source. */
|
|
20
20
|
export const SvgSource = (svg: string): SvgSourceValue => ({ svg, tintColor: null })
|
|
21
|
+
/** The type of the value: what `SvgSource(xml)`, `assetIcon("lucide:bell")` and `asset("./logo.svg")`
|
|
22
|
+
* all return, and what `UIImage` / `bgImage` take — ONE name for an SVG image source, global to a
|
|
23
|
+
* project like the function (host.d.ts names it for the macros). */
|
|
24
|
+
export type SvgSource = SvgSourceValue
|
package/src/ui/UIPager.ts
CHANGED
|
@@ -31,6 +31,8 @@ export type UIPagerStyle = ElementStyle & DrawableStyle
|
|
|
31
31
|
/** The pagers whose screen is currently visible, outermost → innermost. `UIPager.current` is the
|
|
32
32
|
* last (innermost). Maintained by each owned screen's onOpen/onClose via _activate/_deactivate. */
|
|
33
33
|
const _activeStack: PagerElement[] = []
|
|
34
|
+
/** @internal The active pagers, outermost → innermost — what `UITabs.current` searches. */
|
|
35
|
+
export const _activePagers = (): readonly PagerElement[] => _activeStack
|
|
34
36
|
|
|
35
37
|
/**
|
|
36
38
|
* The screen-navigation element: sibling **tabs** that swipe natively side to side, each with its
|