reze-engine 0.62.1 → 0.64.0

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.
Files changed (83) hide show
  1. package/dist/engine.d.ts +95 -57
  2. package/dist/engine.d.ts.map +1 -1
  3. package/dist/engine.js +466 -162
  4. package/dist/graph/compile.d.ts.map +1 -1
  5. package/dist/graph/compile.js +4 -1
  6. package/dist/graph/registry.d.ts +3 -0
  7. package/dist/graph/registry.d.ts.map +1 -1
  8. package/dist/graph/registry.js +133 -0
  9. package/dist/graph/schema.d.ts +12 -0
  10. package/dist/graph/schema.d.ts.map +1 -1
  11. package/dist/graph/slots.d.ts.map +1 -1
  12. package/dist/graph/slots.js +11 -20
  13. package/dist/index.d.ts +9 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/shaders/lights.d.ts +3 -2
  17. package/dist/shaders/lights.d.ts.map +1 -1
  18. package/dist/shaders/lights.js +16 -3
  19. package/dist/shaders/materials/common.d.ts.map +1 -1
  20. package/dist/shaders/materials/common.js +63 -56
  21. package/dist/shaders/materials/nodes.d.ts +1 -1
  22. package/dist/shaders/materials/nodes.d.ts.map +1 -1
  23. package/dist/shaders/materials/nodes.js +210 -0
  24. package/dist/shaders/passes/composite.d.ts.map +1 -1
  25. package/dist/shaders/passes/composite.js +13 -1
  26. package/dist/shaders/passes/cull.d.ts +1 -1
  27. package/dist/shaders/passes/cull.d.ts.map +1 -1
  28. package/dist/shaders/passes/cull.js +7 -3
  29. package/dist/shaders/passes/ground.d.ts.map +1 -1
  30. package/dist/shaders/passes/ground.js +32 -55
  31. package/dist/shaders/passes/particles.d.ts +11 -1
  32. package/dist/shaders/passes/particles.d.ts.map +1 -1
  33. package/dist/shaders/passes/particles.js +9 -4
  34. package/dist/shaders/scene-light-api.d.ts +18 -10
  35. package/dist/shaders/scene-light-api.d.ts.map +1 -1
  36. package/dist/shaders/scene-light-api.js +58 -46
  37. package/dist/shadow-cascades.d.ts +47 -20
  38. package/dist/shadow-cascades.d.ts.map +1 -1
  39. package/dist/shadow-cascades.js +36 -13
  40. package/dist/unity/character.d.ts +59 -0
  41. package/dist/unity/character.d.ts.map +1 -0
  42. package/dist/unity/character.js +120 -0
  43. package/dist/unity/globals.d.ts +93 -0
  44. package/dist/unity/globals.d.ts.map +1 -0
  45. package/dist/unity/globals.js +340 -0
  46. package/dist/unity/host.d.ts +137 -0
  47. package/dist/unity/host.d.ts.map +1 -0
  48. package/dist/unity/host.js +489 -0
  49. package/dist/unity/looks.d.ts +88 -0
  50. package/dist/unity/looks.d.ts.map +1 -0
  51. package/dist/unity/looks.js +295 -0
  52. package/dist/unity/skin.d.ts +9 -0
  53. package/dist/unity/skin.d.ts.map +1 -0
  54. package/dist/unity/skin.js +117 -0
  55. package/dist/unity/stage.d.ts +148 -0
  56. package/dist/unity/stage.d.ts.map +1 -0
  57. package/dist/unity/stage.js +375 -0
  58. package/dist/unity/state.d.ts +22 -0
  59. package/dist/unity/state.d.ts.map +1 -0
  60. package/dist/unity/state.js +170 -0
  61. package/package.json +1 -1
  62. package/src/engine.ts +504 -176
  63. package/src/graph/compile.ts +3 -1
  64. package/src/graph/registry.ts +142 -0
  65. package/src/graph/schema.ts +9 -0
  66. package/src/graph/slots.ts +11 -20
  67. package/src/index.ts +11 -0
  68. package/src/shaders/lights.ts +16 -3
  69. package/src/shaders/materials/common.ts +63 -57
  70. package/src/shaders/materials/nodes.ts +210 -0
  71. package/src/shaders/passes/composite.ts +13 -1
  72. package/src/shaders/passes/cull.ts +7 -3
  73. package/src/shaders/passes/ground.ts +32 -55
  74. package/src/shaders/passes/particles.ts +19 -5
  75. package/src/shaders/scene-light-api.ts +59 -46
  76. package/src/shadow-cascades.ts +61 -32
  77. package/src/unity/character.ts +169 -0
  78. package/src/unity/globals.ts +443 -0
  79. package/src/unity/host.ts +587 -0
  80. package/src/unity/looks.ts +372 -0
  81. package/src/unity/skin.ts +128 -0
  82. package/src/unity/stage.ts +568 -0
  83. package/src/unity/state.ts +172 -0
@@ -0,0 +1,443 @@
1
+ // The game pipeline's per-frame globals, computed from the engine's scene.
2
+ //
3
+ // The game's shaders read what its render pipeline (UnityEngine.Rendering.
4
+ // Replica / ReplicaExt, decompiled from GameAssembly.dll) sets before a camera
5
+ // renders: the camera, the main light, the Forward+ light table and its bins,
6
+ // the cascades, the ambient SH, fog, time. ag-rip's AGSimPipeline.cs and
7
+ // AGSimShadows.cs reproduce those calls in Unity, method by method, and the
8
+ // compare page proved them against the game's frames; this is that code again,
9
+ // fed by the engine instead of by a Unity scene.
10
+ //
11
+ // SPACE. The game's shaders work in the game's world, and the engine's scene
12
+ // is that world as ag-rip exports it to MMD: `scale` PMX units per game unit
13
+ // (8: the one-scene-scale rule) and turned half round Y — game (x, y, z) is
14
+ // PMX (-x, y, -z), the convention its camera and motion VMDs and its stages
15
+ // share. Everything positional is handed over through that map (gameToEngine
16
+ // and back) — the camera, the lights and their reach, the cascades — so a
17
+ // shader's distances (outline widths, light ranges, fog) and directions mean
18
+ // what they meant in the game. A half turn keeps handedness: both are
19
+ // left-handed and Y-up.
20
+ //
21
+ // CLIP SPACE. naga negates y on the way out of every vertex shader (Vulkan to
22
+ // WebGPU), so the projection handed to the shaders is the engine's with y
23
+ // flipped: the two flips cancel and a native draw lands exactly where the
24
+ // engine's own draw of the same triangle would, on the same depth.
25
+
26
+ import type { NativeValue } from "./host"
27
+
28
+ export type Mat4Like = ArrayLike<number>
29
+
30
+ export type UnityLight = {
31
+ /** World position, engine units. */
32
+ position: [number, number, number]
33
+ /** Reach, engine units. */
34
+ radius: number
35
+ /** Linear colour times intensity. */
36
+ color: [number, number, number]
37
+ /** Unit aim, away from the light; zero for a point light. */
38
+ aim: [number, number, number]
39
+ cosOuter: number
40
+ cosInner: number
41
+ /** Rendering-layer bits it reaches. */
42
+ layers: number
43
+ /** What the game's pipeline packs beside a light it lit with, when the light
44
+ * came from a game stage (stage_native.py): spot direction with w = 1 /
45
+ * its sphere's radius (the area term reads it), colour alpha, Extra,
46
+ * WorldToLight and type — all in the game's world, used as they are. */
47
+ unity?: {
48
+ spotDir: number[]
49
+ colorW: number
50
+ extra: number[]
51
+ worldToLight: number[] | null
52
+ lightType: number
53
+ simSpotDir?: number[] | null
54
+ simColorW?: number
55
+ }
56
+ }
57
+
58
+ export type UnityFrameInput = {
59
+ /** Engine units per game unit. */
60
+ scale: number
61
+ view: Mat4Like
62
+ proj: Mat4Like
63
+ eye: [number, number, number]
64
+ near: number
65
+ far: number
66
+ width: number
67
+ height: number
68
+ /** Scene clock and its step, seconds. */
69
+ time: number
70
+ dt: number
71
+ /** The direction the main light travels, unit; null for none. */
72
+ sunDirection: [number, number, number] | null
73
+ sunColor: [number, number, number]
74
+ sunShadow: number
75
+ lights: UnityLight[]
76
+ /** Ambient as the engine's folded SH9 (27 floats, ibl.ts), or null for flat. */
77
+ ambientSH: ArrayLike<number> | null
78
+ ambientFlat: [number, number, number]
79
+ /** The cascades as the engine drew them: render view-projections (engine
80
+ * units, the atlas's own depth convention), their tiles, and the spheres
81
+ * they were fitted to. */
82
+ shadow: {
83
+ viewProj: Mat4Like[]
84
+ tiles: [number, number][]
85
+ tileSize: number
86
+ atlasSize: number
87
+ spheres: { center: [number, number, number]; radius: number }[]
88
+ } | null
89
+ /** What a loaded game stage sets for its scene (stage.ts): fog, tint, ambient
90
+ * SH, the environment cube's numbers — the pipeline's per-scene globals, in
91
+ * place of the defaults below. Never camera, time, lights or shadows. */
92
+ settings?: Record<string, NativeValue> | null
93
+ }
94
+
95
+ const PLUS_MAX = 255
96
+
97
+ /** Column-major 4x4 multiply. */
98
+ export function mul4(a: Mat4Like, b: Mat4Like): Float32Array {
99
+ const o = new Float32Array(16)
100
+ for (let c = 0; c < 4; c++)
101
+ for (let r = 0; r < 4; r++) {
102
+ let s = 0
103
+ for (let k = 0; k < 4; k++) s += a[k * 4 + r] * b[c * 4 + k]
104
+ o[c * 4 + r] = s
105
+ }
106
+ return o
107
+ }
108
+
109
+ export function invert4(m: Mat4Like): Float32Array {
110
+ const a = m
111
+ const o = new Float32Array(16)
112
+ const a00 = a[0],
113
+ a01 = a[1],
114
+ a02 = a[2],
115
+ a03 = a[3],
116
+ a10 = a[4],
117
+ a11 = a[5],
118
+ a12 = a[6],
119
+ a13 = a[7]
120
+ const a20 = a[8],
121
+ a21 = a[9],
122
+ a22 = a[10],
123
+ a23 = a[11],
124
+ a30 = a[12],
125
+ a31 = a[13],
126
+ a32 = a[14],
127
+ a33 = a[15]
128
+ const b00 = a00 * a11 - a01 * a10,
129
+ b01 = a00 * a12 - a02 * a10,
130
+ b02 = a00 * a13 - a03 * a10
131
+ const b03 = a01 * a12 - a02 * a11,
132
+ b04 = a01 * a13 - a03 * a11,
133
+ b05 = a02 * a13 - a03 * a12
134
+ const b06 = a20 * a31 - a21 * a30,
135
+ b07 = a20 * a32 - a22 * a30,
136
+ b08 = a20 * a33 - a23 * a30
137
+ const b09 = a21 * a32 - a22 * a31,
138
+ b10 = a21 * a33 - a23 * a31,
139
+ b11 = a22 * a33 - a23 * a32
140
+ let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06
141
+ if (!det) return Float32Array.from([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1])
142
+ det = 1 / det
143
+ o[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det
144
+ o[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det
145
+ o[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det
146
+ o[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det
147
+ o[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det
148
+ o[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det
149
+ o[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det
150
+ o[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det
151
+ o[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det
152
+ o[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det
153
+ o[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det
154
+ o[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det
155
+ o[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det
156
+ o[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det
157
+ o[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det
158
+ o[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det
159
+ return o
160
+ }
161
+
162
+ /** Game world -> engine world: `s` PMX per unit, half a turn about Y. */
163
+ export function gameToEngine(s: number): Float32Array {
164
+ return Float32Array.from([-s, 0, 0, 0, 0, s, 0, 0, 0, 0, -s, 0, 0, 0, 0, 1])
165
+ }
166
+
167
+ /** Engine world -> game world. */
168
+ export function engineToGame(s: number): Float32Array {
169
+ return Float32Array.from([-1 / s, 0, 0, 0, 0, 1 / s, 0, 0, 0, 0, -1 / s, 0, 0, 0, 0, 1])
170
+ }
171
+
172
+ /** An engine point in the game's world. */
173
+ export function gamePoint(p: ArrayLike<number>, s: number): [number, number, number] {
174
+ return [-p[0] / s, p[1] / s, -p[2] / s]
175
+ }
176
+
177
+ /** An engine direction in the game's world. */
178
+ export function gameDir(d: ArrayLike<number>): [number, number, number] {
179
+ return [-d[0], d[1], -d[2]]
180
+ }
181
+
182
+ /** Uniform scale, as a column-major matrix. */
183
+ export function scale4(s: number): Float32Array {
184
+ return Float32Array.from([s, 0, 0, 0, 0, s, 0, 0, 0, 0, s, 0, 0, 0, 0, 1])
185
+ }
186
+
187
+ /** The projection with clip y negated — undone by naga's own negation. */
188
+ export function flipY(p: Mat4Like): Float32Array {
189
+ const o = Float32Array.from(p)
190
+ for (let c = 0; c < 4; c++) o[c * 4 + 1] = -p[c * 4 + 1]
191
+ return o
192
+ }
193
+
194
+ /**
195
+ * The engine's folded SH9 (ibl.ts: c0 + c1·y + c2·z + c3·x + c4·xy + c5·yz +
196
+ * c6·(3z²−1) + c7·xz + c8·(x²−y²)) as Unity's seven shader constants —
197
+ * SHA = (x, y, z, constant), SHB = (xy, yz, z², zx), SHC = (x²−y²) — the same
198
+ * polynomial, regrouped.
199
+ */
200
+ export function unitySH(sh: ArrayLike<number> | null, flat: [number, number, number]): Record<string, number[]> {
201
+ const out: Record<string, number[]> = {}
202
+ const ch = ["r", "g", "b"]
203
+ const c = (i: number, k: number) => (sh ? sh[i * 3 + k] : i === 0 ? flat[k] : 0)
204
+ for (let k = 0; k < 3; k++) {
205
+ out[`_Replica_SHA${ch[k]}`] = [c(3, k), c(1, k), c(2, k), c(0, k) - c(6, k)]
206
+ out[`_Replica_SHB${ch[k]}`] = [c(4, k), c(5, k), 3 * c(6, k), c(7, k)]
207
+ }
208
+ out._Replica_SHC = [c(8, 0), c(8, 1), c(8, 2), 1]
209
+ return out
210
+ }
211
+
212
+ /** Everything the frame sets, by name. */
213
+ export function unityFrameGlobals(f: UnityFrameInput): Record<string, NativeValue> {
214
+ const s = f.scale
215
+ const g: Record<string, NativeValue> = {}
216
+
217
+ // ---- the camera (UnityPerFrame), in game units
218
+ // Unity's own pair, so what a shader rebuilds from them means what it did
219
+ // in the game: the view in game units, right-handed and looking down -Z;
220
+ // the projection on game-unit depths (the engine's, conjugated by the scale
221
+ // and the z flip, and divided through by s, which leaves the same NDC). Their
222
+ // product puts every vertex exactly where the engine's own draw of it lands.
223
+ const zFlip = Float32Array.from([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1])
224
+ const view = mul4(zFlip, mul4(scale4(1 / s), mul4(f.view, gameToEngine(s))))
225
+ const toEngineView = mul4(scale4(s), zFlip)
226
+ const proj = flipY(mul4(f.proj, toEngineView))
227
+ for (let i = 0; i < 16; i++) proj[i] /= s
228
+ const vp = mul4(proj, view)
229
+ const w = Math.max(1, f.width)
230
+ const h = Math.max(1, f.height)
231
+ const near = f.near / s
232
+ const far = f.far / s
233
+ g._WorldSpaceCameraPos = [...gamePoint(f.eye, s), 1]
234
+ g.unity_MatrixV = view
235
+ g.unity_MatrixInvV = invert4(view)
236
+ g.unity_MatrixP = proj
237
+ g.glstate_matrix_projection = proj
238
+ g.unity_MatrixVP = vp
239
+ g._NonJitteredViewProjMatrix = vp
240
+ // x = -1: a flipped projection, as Unity's into a texture.
241
+ g._ProjectionParams = [-1, near, far, 1 / far]
242
+ g._ScreenParams = [w, h, 1 + 1 / w, 1 + 1 / h]
243
+ g._ScaledScreenParams = [w, h, 1 + 1 / w, 1 + 1 / h]
244
+ g._ZBufferParams = [-1 + far / near, 1, (-1 + far / near) / far, 1 / far]
245
+ g.unity_OrthoParams = [0, 0, 0, 0]
246
+ const t = f.time
247
+ g._Time = [t / 20, t, t * 2, t * 3]
248
+ g._SinTime = [Math.sin(t / 8), Math.sin(t / 4), Math.sin(t / 2), Math.sin(t)]
249
+ g._CosTime = [Math.cos(t / 8), Math.cos(t / 4), Math.cos(t / 2), Math.cos(t)]
250
+ const dt = Math.max(f.dt, 1e-4)
251
+ g.unity_DeltaTime = [dt, 1 / dt, dt, 1 / dt]
252
+ // UpdateRenderSettings: sim_Time = (t - floor t, floor t)
253
+ g.sim_Time = [t - Math.floor(t), Math.floor(t), 0, 0]
254
+ g.identity_matrix = scale4(1)
255
+
256
+ // ---- LightingFeatures.SetupLightings / PlusLighting SetupMainLightConstants
257
+ if (f.sunDirection) {
258
+ const d = gameDir(f.sunDirection)
259
+ g.SimMainLightDir = [-d[0], -d[1], -d[2], 0]
260
+ g.SimMainLightColor = [...f.sunColor, 1]
261
+ g.SimMainLightColorNoInt = [...f.sunColor, 1]
262
+ // As the game's pipeline leaves it (recorded): the way the light travels.
263
+ g._MainLightPosition = [d[0], d[1], d[2], 0]
264
+ g.sim_ShadowLightDirection = [-d[0], -d[1], -d[2], 0]
265
+ g._MainLightColor = [...f.sunColor, 1]
266
+ } else {
267
+ g.SimMainLightDir = [0, 0, 0, 0]
268
+ g.SimMainLightColor = [0, 0, 0, 0]
269
+ g.SimMainLightColorNoInt = [0, 0, 0, 0]
270
+ g._MainLightPosition = [0, 1, 0, 0]
271
+ g._MainLightColor = [0, 0, 0, 0]
272
+ }
273
+
274
+ // ---- ForwardFeature.SetupRenderFeature, UpdateRenderSettings: a scene
275
+ // with no stage of the game's has no settings of its own — the defaults the
276
+ // game's SceneSetting starts from.
277
+ g.SimSceneTint = [1, 1, 1, 1]
278
+ g._ReceiveSceneShadow = 1
279
+ g.sim_FogRotation = 0
280
+ g.sim_FogInverseRootRotationMatrix = scale4(1)
281
+ g.sim_FogColor = [0, 0, 0, 0]
282
+ g.sim_FogColor2 = [0, 0, 0, 0]
283
+ g.sim_FogParams = [0, 1, 0, 1]
284
+ g.sim_FogDirectionalColor = [0, 0, 0, 0]
285
+ g.sim_FogDirectionalDir = [0, 0, 1, 0]
286
+ g.sim_FogDirectionalFalloff = 0
287
+ g.sim_DYN_FOG_LINEAR_Variable = 0
288
+ g.sim_DynFogColor = [0, 0, 0, 0]
289
+ g.sim_DynFogParams = [0, 1, 0, 1]
290
+ g._ProbeLightingBase = [1, 1, 1, 1]
291
+ g._ProbeLightingScale = 0
292
+ g._AcceptLightProbe = 0
293
+ g.sim_VertexAmbientScale = 1
294
+ g._EnableShadowScatter = 0
295
+ g._ShadowScatterColor = [0, 0, 0, 0]
296
+ g._ShadowScatterRange = 0
297
+ // realtimeShadowColor's default grey, made linear (ConvertSRGBToActiveColorSpace)
298
+ g._SubtractiveShadowColor = [0.2158605, 0.2158605, 0.2158605, 1]
299
+ g._AmbientOcclusionParam = [0, 0, 0, 0]
300
+ g.sim_EnvCubeScale = 0
301
+ g.sim_EnvCube_BoxMin = [0, 0, 0, 0]
302
+ g.sim_EnvCube_BoxMax = [0, 0, 0, 0]
303
+ g.sim_EnvCube_ProbePosition = [0, 0, 0, 0]
304
+
305
+ // ---- SetupEnvironmentLighting: the scene's ambient SH
306
+ // The half turn negates x and z: the terms odd in either (z, x, xy, yz) flip.
307
+ const sh = f.ambientSH ? Array.from(f.ambientSH) : null
308
+ if (sh) for (const i of [2, 3, 4, 5]) for (let k = 0; k < 3; k++) sh[i * 3 + k] = -sh[i * 3 + k]
309
+ Object.assign(g, unitySH(sh, f.ambientFlat))
310
+
311
+ // ---- a game stage's own settings over all of the above
312
+ if (f.settings) Object.assign(g, f.settings)
313
+
314
+ // ---- PlusLightingFeature: the light table, one tile and one depth bin
315
+ // holding every light (binning only culls; the shader applies each range)
316
+ const n = Math.min(f.lights.length, PLUS_MAX)
317
+ const pos: number[][] = [],
318
+ col: number[][] = [],
319
+ att: number[][] = [],
320
+ dir: number[][] = []
321
+ const extra: number[][] = [],
322
+ w2l: Float32Array[] = []
323
+ const simDir: number[][] = [],
324
+ simColorW: number[] = []
325
+ const types = new Float32Array(PLUS_MAX)
326
+ const masks = new Uint32Array(PLUS_MAX)
327
+ const ident = scale4(1)
328
+ for (let i = 0; i < PLUS_MAX; i++) {
329
+ const l = f.lights[i]
330
+ if (i >= n || !l) {
331
+ pos.push([0, 0, 0, 0])
332
+ col.push([0, 0, 0, 0])
333
+ att.push([0, 1, 0, 1])
334
+ dir.push([0, 0, 0, 0])
335
+ extra.push([0, 0, 0, 0])
336
+ w2l.push(ident)
337
+ continue
338
+ }
339
+ const spot = l.aim[0] !== 0 || l.aim[1] !== 0 || l.aim[2] !== 0
340
+ const r = l.radius / s
341
+ const r2 = r * r
342
+ // LightInfo.GetPunctualLightDistanceAttenuation / GetSpotAngleAttenuation
343
+ let z = 0
344
+ let ww = 1
345
+ if (spot) {
346
+ const inv = 1 / Math.max(l.cosInner - l.cosOuter, 0.001)
347
+ z = inv
348
+ ww = -l.cosOuter * inv
349
+ }
350
+ pos.push([...gamePoint(l.position, s), 1])
351
+ // The engine's lamp brightness is per its own unit; the inverse square in
352
+ // game units is s² larger at the same place, so the colour carries 1/s².
353
+ col.push([l.color[0] / (s * s), l.color[1] / (s * s), l.color[2] / (s * s), 1])
354
+ att.push([1 / Math.max(r2, 1e-4), -r2 / (r2 * 0.64 - r2), z, ww])
355
+ const u = l.unity
356
+ if (u) {
357
+ col[col.length - 1][3] = u.colorW
358
+ dir.push(u.spotDir.slice(0, 4))
359
+ extra.push(u.extra.slice(0, 4))
360
+ w2l.push(u.worldToLight ? Float32Array.from(u.worldToLight) : ident)
361
+ simDir.push((u.simSpotDir ?? u.spotDir).slice(0, 4))
362
+ simColorW.push(u.simColorW ?? 1)
363
+ types[i] = u.lightType
364
+ } else {
365
+ // The pipeline's own defaults for a light it did not author: forward
366
+ // along the aim, w = 1 / the 0.1 sphere every game lamp of these stages has.
367
+ const a = gameDir(l.aim)
368
+ dir.push(spot ? [...a, 10] : [0, 0, 1, 10])
369
+ extra.push([0, 0, 0, 0])
370
+ w2l.push(ident)
371
+ simDir.push(spot ? [...a, 10] : [0, 0, 1, 10])
372
+ simColorW.push(1)
373
+ types[i] = spot ? 0 : 2
374
+ }
375
+ masks[i] = l.layers >>> 0
376
+ }
377
+ g._AdditionalLightsPosition = pos
378
+ g._AdditionalLightsColor = col
379
+ g._AdditionalLightsAttenuation = att
380
+ g._AdditionalLightsSpotDir = dir
381
+ g._AdditionalLightsExtra = extra
382
+ g._AdditionalLightsWorldToLights = w2l
383
+ g._AdditionalLightsLightTypes = types
384
+ g._AdditionalLightsLayerMasks = masks
385
+ g._AdditionalLightsCookieEnableBits = new Float32Array(8)
386
+ const words = Math.max(1, Math.ceil(n / 32))
387
+ const zb = new Uint32Array(4096)
388
+ const tiles = new Uint32Array(16384)
389
+ zb[0] = n > 0 ? ((n - 1) << 16) >>> 0 : 0xffff
390
+ for (let k = 0; k < words; k++) {
391
+ const bits = Math.min(Math.max(n - k * 32, 0), 32)
392
+ const m = bits >= 32 ? 0xffffffff : ((1 << bits) >>> 0) - 1
393
+ zb[1 + k] = m >>> 0
394
+ tiles[k] = m >>> 0
395
+ }
396
+ // LightingFeatures' own copy for the per-vertex lights: the first 16, colour
397
+ // without the intensity in w.
398
+ g.SimAdditionalLightPosition = pos.slice(0, 16)
399
+ g.SimAdditionalLightColor = col
400
+ .slice(0, 16)
401
+ .map((c, i) => (c[0] || c[1] || c[2] ? [c[0], c[1], c[2], simColorW[i] ?? 1] : [0, 0, 0, 0]))
402
+ g.SimAdditionalLightAttenuation = att.slice(0, 16)
403
+ g.SimAdditionalLightSpotDir = simDir.slice(0, 16).concat(dir.slice(simDir.length, 16))
404
+ g._PlusLighting_ZBins = zb
405
+ g._PlusLighting_Tiles = tiles
406
+ g._PlusLightingParams0 = [0, 0, 1, words]
407
+ g._PlusLightingParams1 = [1000, 0, 0, 0]
408
+
409
+ // ---- the main light's cascades, sampled from the engine's atlas
410
+ if (f.shadow) {
411
+ const sh = f.shadow
412
+ const unit = sh.tileSize / sh.atlasSize
413
+ const w2s: Float32Array[] = []
414
+ for (let i = 0; i < 4; i++) {
415
+ const vpe = sh.viewProj[i]
416
+ const tile = sh.tiles[i]
417
+ if (!vpe || !tile) {
418
+ w2s.push(scale4(0))
419
+ continue
420
+ }
421
+ // ndc -> the tile's uv (y down, as the atlas is addressed), depth as drawn
422
+ const tx = tile[0] / sh.atlasSize
423
+ const ty = tile[1] / sh.atlasSize
424
+ const toTile = Float32Array.from([0.5 * unit, 0, 0, 0, 0, -0.5 * unit, 0, 0, 0, 0, 1, 0, 0.5 * unit + tx, 0.5 * unit + ty, 0, 1])
425
+ w2s.push(mul4(toTile, mul4(vpe, gameToEngine(s))))
426
+ }
427
+ g._MainLightWorldToShadow = w2s
428
+ for (let i = 0; i < 4; i++) {
429
+ const sp = sh.spheres[i]
430
+ g[`_CascadeShadowSplitSpheres${i}`] = sp ? [...gamePoint(sp.center, s), sp.radius / s] : [0, 0, 0, 0]
431
+ }
432
+ const r = (i: number) => (sh.spheres[i] ? (sh.spheres[i].radius / s) ** 2 : 0)
433
+ g._CascadeShadowSplitSphereRadii = [r(0), r(1), r(2), r(3)]
434
+ const half = 0.5 / sh.atlasSize
435
+ g._MainLightShadowOffset0 = [-half, -half, half, -half]
436
+ g._MainLightShadowOffset1 = [-half, half, half, half]
437
+ g._MainLightShadowmapSize = [1 / sh.atlasSize, 1 / sh.atlasSize, sh.atlasSize, sh.atlasSize]
438
+ g._MainLightShadowParams = [f.sunShadow, 1, 0, 0]
439
+ } else {
440
+ g._MainLightShadowParams = [0, 0, 0, 0]
441
+ }
442
+ return g
443
+ }