@aethervtt/shard-render 0.0.1

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 (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/dist/assets.d.ts +104 -0
  4. package/dist/assets.js +213 -0
  5. package/dist/atmosphere-model.d.ts +96 -0
  6. package/dist/atmosphere-model.js +370 -0
  7. package/dist/atmosphere-nodes.d.ts +55 -0
  8. package/dist/atmosphere-nodes.js +780 -0
  9. package/dist/atmosphere-plugin.d.ts +5 -0
  10. package/dist/atmosphere-plugin.js +42 -0
  11. package/dist/atmosphere-shaders.d.ts +7 -0
  12. package/dist/atmosphere-shaders.js +595 -0
  13. package/dist/atmosphere-state.d.ts +72 -0
  14. package/dist/atmosphere-state.js +18 -0
  15. package/dist/atmosphere.d.ts +191 -0
  16. package/dist/atmosphere.js +950 -0
  17. package/dist/camera.d.ts +83 -0
  18. package/dist/camera.js +163 -0
  19. package/dist/clusters.d.ts +68 -0
  20. package/dist/clusters.js +288 -0
  21. package/dist/culling-shaders.d.ts +2 -0
  22. package/dist/culling-shaders.js +203 -0
  23. package/dist/culling.d.ts +84 -0
  24. package/dist/culling.js +498 -0
  25. package/dist/debug-shaders.d.ts +3 -0
  26. package/dist/debug-shaders.js +207 -0
  27. package/dist/debug-views.d.ts +60 -0
  28. package/dist/debug-views.js +197 -0
  29. package/dist/deferred-plugin.d.ts +5 -0
  30. package/dist/deferred-plugin.js +20 -0
  31. package/dist/deferred-shaders.d.ts +2 -0
  32. package/dist/deferred-shaders.js +151 -0
  33. package/dist/deferred.d.ts +22 -0
  34. package/dist/deferred.js +296 -0
  35. package/dist/deform.d.ts +80 -0
  36. package/dist/deform.js +213 -0
  37. package/dist/describe-plugin.d.ts +5 -0
  38. package/dist/describe-plugin.js +19 -0
  39. package/dist/display-nodes.d.ts +5 -0
  40. package/dist/display-nodes.js +189 -0
  41. package/dist/dynamic-resolution.d.ts +47 -0
  42. package/dist/dynamic-resolution.js +191 -0
  43. package/dist/environment-plugin.d.ts +6 -0
  44. package/dist/environment-plugin.js +44 -0
  45. package/dist/environment-shaders.d.ts +2 -0
  46. package/dist/environment-shaders.js +260 -0
  47. package/dist/environment-state.d.ts +71 -0
  48. package/dist/environment-state.js +144 -0
  49. package/dist/environment.d.ts +70 -0
  50. package/dist/environment.js +529 -0
  51. package/dist/forward.d.ts +123 -0
  52. package/dist/forward.js +1216 -0
  53. package/dist/fxaa.d.ts +2 -0
  54. package/dist/fxaa.js +65 -0
  55. package/dist/gizmo-font.d.ts +21 -0
  56. package/dist/gizmo-font.js +132 -0
  57. package/dist/gizmos-plugin.d.ts +5 -0
  58. package/dist/gizmos-plugin.js +34 -0
  59. package/dist/gizmos.d.ts +156 -0
  60. package/dist/gizmos.js +772 -0
  61. package/dist/gpu-assets.d.ts +114 -0
  62. package/dist/gpu-assets.js +494 -0
  63. package/dist/graph.d.ts +204 -0
  64. package/dist/graph.js +417 -0
  65. package/dist/index.d.ts +53 -0
  66. package/dist/index.js +540 -0
  67. package/dist/instances.d.ts +349 -0
  68. package/dist/instances.js +1335 -0
  69. package/dist/large-world.d.ts +13 -0
  70. package/dist/large-world.js +20 -0
  71. package/dist/lights.d.ts +160 -0
  72. package/dist/lights.js +468 -0
  73. package/dist/material-noise.d.ts +19 -0
  74. package/dist/material-noise.js +7 -0
  75. package/dist/material-pipelines.d.ts +46 -0
  76. package/dist/material-pipelines.js +166 -0
  77. package/dist/materials.d.ts +108 -0
  78. package/dist/materials.js +219 -0
  79. package/dist/noise.d.ts +76 -0
  80. package/dist/noise.js +286 -0
  81. package/dist/overlay-registry.d.ts +18 -0
  82. package/dist/overlay-registry.js +30 -0
  83. package/dist/overlays.d.ts +35 -0
  84. package/dist/overlays.js +344 -0
  85. package/dist/paths.d.ts +20 -0
  86. package/dist/paths.js +46 -0
  87. package/dist/picking-plugin.d.ts +2 -0
  88. package/dist/picking-plugin.js +20 -0
  89. package/dist/picking.d.ts +94 -0
  90. package/dist/picking.js +456 -0
  91. package/dist/pixel-perfect-plugin.d.ts +9 -0
  92. package/dist/pixel-perfect-plugin.js +135 -0
  93. package/dist/pixel-perfect.d.ts +27 -0
  94. package/dist/pixel-perfect.js +70 -0
  95. package/dist/plugin.d.ts +111 -0
  96. package/dist/plugin.js +182 -0
  97. package/dist/pool.d.ts +19 -0
  98. package/dist/pool.js +43 -0
  99. package/dist/post-common.d.ts +51 -0
  100. package/dist/post-common.js +148 -0
  101. package/dist/post-nodes.d.ts +39 -0
  102. package/dist/post-nodes.js +849 -0
  103. package/dist/post-plugin.d.ts +5 -0
  104. package/dist/post-plugin.js +26 -0
  105. package/dist/post-shaders.d.ts +8 -0
  106. package/dist/post-shaders.js +698 -0
  107. package/dist/post.d.ts +175 -0
  108. package/dist/post.js +453 -0
  109. package/dist/readback.d.ts +4 -0
  110. package/dist/readback.js +95 -0
  111. package/dist/render-scale.d.ts +53 -0
  112. package/dist/render-scale.js +63 -0
  113. package/dist/shaders.d.ts +122 -0
  114. package/dist/shaders.js +1168 -0
  115. package/dist/shadows.d.ts +119 -0
  116. package/dist/shadows.js +562 -0
  117. package/dist/skin-asset.d.ts +26 -0
  118. package/dist/skin-asset.js +25 -0
  119. package/dist/skinning-plugin.d.ts +5 -0
  120. package/dist/skinning-plugin.js +16 -0
  121. package/dist/skinning.d.ts +19 -0
  122. package/dist/skinning.js +343 -0
  123. package/dist/standard-fields.d.ts +67 -0
  124. package/dist/standard-fields.js +73 -0
  125. package/dist/standard.d.ts +7 -0
  126. package/dist/standard.js +37 -0
  127. package/dist/stats.d.ts +25 -0
  128. package/dist/stats.js +18 -0
  129. package/dist/target.d.ts +55 -0
  130. package/dist/target.js +70 -0
  131. package/dist/testing.d.ts +34 -0
  132. package/dist/testing.js +126 -0
  133. package/dist/timer.d.ts +28 -0
  134. package/dist/timer.js +136 -0
  135. package/dist/view.d.ts +117 -0
  136. package/dist/view.js +389 -0
  137. package/dist/visibility.d.ts +10 -0
  138. package/dist/visibility.js +84 -0
  139. package/package.json +49 -0
@@ -0,0 +1,950 @@
1
+ import {
2
+ defineComponent,
3
+ defineSchema,
4
+ defineSystem,
5
+ findComponent,
6
+ ShardError,
7
+ t
8
+ } from "@aethervtt/shard-core";
9
+ import { LogResource } from "@aethervtt/shard-runtime";
10
+ import { GlobalTransform } from "@aethervtt/shard-transform";
11
+ import {
12
+ createModel,
13
+ createSkySample,
14
+ integrateSky,
15
+ multiscatterLut,
16
+ transmittanceLut,
17
+ transmittanceToTop
18
+ } from "./atmosphere-model.js";
19
+ import {
20
+ Atmospheres
21
+ } from "./atmosphere-state.js";
22
+ import { Camera3d } from "./camera.js";
23
+ import { DefaultEnvironment, EnvironmentMap, ProceduralSky } from "./environment.js";
24
+ import { DirectionalLight, Lights, MAX_DIRECTIONAL } from "./lights.js";
25
+ import { Views } from "./plugin.js";
26
+ import { PostEffect, postAliases } from "./post.js";
27
+ import { cameraOf, viewAliases } from "./view.js";
28
+ const Atmosphere = defineComponent(
29
+ "render/Atmosphere",
30
+ {
31
+ bottomRadius: t.f64({
32
+ min: 0,
33
+ unit: "m",
34
+ description: "Where the atmosphere starts: the visible surface, or a gas giant's cloud top. 0: the entity's terrain/Planet radius, else 6 360 km (Earth 6 360 000, Mars 3 389 500)."
35
+ }),
36
+ thickness: t.f32({
37
+ default: 6e4,
38
+ unit: "m",
39
+ description: "Top radius = bottomRadius + thickness. Earth 60 000\u2013100 000, Mars 80 000."
40
+ }),
41
+ rayleighScattering: t.vec3({
42
+ default: [5802e-9, 13558e-9, 331e-7],
43
+ min: 0,
44
+ unit: "1/m",
45
+ description: "Molecular scattering at the bottom, red/green/blue. Blue above red makes a blue sky and orange sunsets; swap them for a red sky with blue sunsets. Earth [5.8e-6, 13.6e-6, 33.1e-6], Mars about 2% of that."
46
+ }),
47
+ rayleighScale: t.f32({
48
+ default: 8e3,
49
+ min: 1,
50
+ unit: "m",
51
+ description: "Rayleigh scale height: the air thins by e every this many metres. Earth 8 000, Mars 11 100."
52
+ }),
53
+ mieScattering: t.f32({
54
+ default: 3996e-9,
55
+ min: 0,
56
+ unit: "1/m",
57
+ description: "Aerosol (dust, haze) scattering at the bottom: the white glow around the sun and hazy horizons. Earth 4e-6 (very clear) to 1e-4 (hazy), Mars dust 3e-5."
58
+ }),
59
+ mieAbsorption: t.f32({
60
+ default: 44e-7,
61
+ min: 0,
62
+ unit: "1/m",
63
+ description: "Aerosol absorption: darker, dirtier haze. Earth 4.4e-6."
64
+ }),
65
+ mieScale: t.f32({
66
+ default: 1200,
67
+ min: 1,
68
+ unit: "m",
69
+ description: "Aerosol scale height. Earth 1 200, Mars dust 11 000."
70
+ }),
71
+ mieG: t.f32({
72
+ default: 0.8,
73
+ min: -0.99,
74
+ max: 0.99,
75
+ description: "Aerosol forward scattering: 0 even, 0.8 a tight bright halo around the sun."
76
+ }),
77
+ mieGOffset: t.vec3({
78
+ default: [0, 0, 0],
79
+ min: -0.5,
80
+ max: 0.5,
81
+ description: "Added to mieG per channel (red, green, blue). Fine dust that scatters blue more forward, like Mars' [-0.15, 0, 0.2], gives a blue glow around a setting sun."
82
+ }),
83
+ absorption: t.vec3({
84
+ default: [65e-8, 1881e-9, 85e-9],
85
+ min: 0,
86
+ unit: "1/m",
87
+ description: "Ozone-like absorption in a tent-shaped layer, red/green/blue: Earth's ozone absorbs green and red, keeping twilight skies blue. Methane-like [3e-6, 0.3e-6, 0] gives Neptune blues."
88
+ }),
89
+ absorptionCenter: t.f32({
90
+ default: 25e3,
91
+ unit: "m",
92
+ description: "Altitude of the absorbing layer. Earth 25 000; 0 with a wide layer for dust near the ground."
93
+ }),
94
+ absorptionWidth: t.f32({
95
+ default: 3e4,
96
+ min: 1,
97
+ unit: "m",
98
+ description: "Full width of the absorbing layer. Earth 30 000."
99
+ }),
100
+ groundAlbedo: t.color({
101
+ default: [0.3, 0.3, 0.3, 1],
102
+ description: "Surface color below the horizon and for light bounced into the sky. Earth 0.3, Mars [0.45, 0.25, 0.15]."
103
+ }),
104
+ intensity: t.f32({
105
+ default: 1,
106
+ min: 0,
107
+ description: "Artistic multiplier on the sky and haze brightness. 1 is physical."
108
+ }),
109
+ deckDepth: t.f32({
110
+ min: 0,
111
+ unit: "m",
112
+ description: "Gas giants: the depth below bottomRadius over which haze thickens to opaque cloud. 0 (default): a solid surface at bottomRadius."
113
+ })
114
+ },
115
+ {
116
+ description: "A planet's atmosphere (Hillaire 2020): the sky from the surface, flight, and orbit, haze over distant terrain, and sunlight dimmed and reddened on its way down. Centered on the entity's origin. Presets (AtmospherePresets): earth, mars, thin, thick-haze, alien-violet, gas-giant."
117
+ }
118
+ );
119
+ const AtmosphereSettings = defineComponent(
120
+ "render/AtmosphereSettings",
121
+ {
122
+ aerialPerspective: t.bool({
123
+ default: true,
124
+ description: "Haze over geometry (distant terrain turns blue). False: only the sky."
125
+ }),
126
+ skyViewSize: t.vec2({
127
+ default: [192, 108],
128
+ min: 16,
129
+ max: 1024,
130
+ description: "Sky-view LUT size (azimuth \xD7 elevation)."
131
+ }),
132
+ froxels: t.vec3({
133
+ default: [32, 32, 32],
134
+ min: 4,
135
+ max: 128,
136
+ description: "Aerial-perspective volume size (x, y, depth slices)."
137
+ }),
138
+ maxDistance: t.f32({
139
+ default: 32e3,
140
+ min: 100,
141
+ unit: "m",
142
+ description: "The froxel volume reaches at least this far, and from altitude on to the horizon and the peaks past it; beyond it the haze is raymarched per pixel."
143
+ })
144
+ },
145
+ { description: "A camera's atmosphere quality settings.", requires: [Camera3d] }
146
+ );
147
+ const EARTH = {
148
+ bottomRadius: 0,
149
+ thickness: 6e4,
150
+ rayleighScattering: [5802e-9, 13558e-9, 331e-7],
151
+ rayleighScale: 8e3,
152
+ mieScattering: 3996e-9,
153
+ mieAbsorption: 44e-7,
154
+ mieScale: 1200,
155
+ mieG: 0.8,
156
+ mieGOffset: [0, 0, 0],
157
+ absorption: [65e-8, 1881e-9, 85e-9],
158
+ absorptionCenter: 25e3,
159
+ absorptionWidth: 3e4,
160
+ groundAlbedo: [0.3, 0.3, 0.3, 1],
161
+ intensity: 1,
162
+ deckDepth: 0
163
+ };
164
+ const AtmospherePresets = {
165
+ earth: EARTH,
166
+ /** Thin CO₂ and fine dust: butterscotch by day, a blue glow around the setting sun. */
167
+ mars: {
168
+ ...EARTH,
169
+ thickness: 8e4,
170
+ rayleighScattering: [11e-8, 26e-8, 63e-8],
171
+ rayleighScale: 11100,
172
+ mieScattering: 3e-5,
173
+ mieAbsorption: 3e-6,
174
+ mieScale: 11e3,
175
+ mieG: 0.72,
176
+ mieGOffset: [-0.15, 0, 0.2],
177
+ absorption: [3e-6, 8e-6, 16e-6],
178
+ absorptionCenter: 0,
179
+ absorptionWidth: 44e3,
180
+ groundAlbedo: [0.45, 0.25, 0.15, 1]
181
+ },
182
+ /** A small world's thin air: a dark blue sky and faint haze. */
183
+ thin: {
184
+ ...EARTH,
185
+ thickness: 3e4,
186
+ rayleighScattering: [145e-8, 339e-8, 828e-8],
187
+ rayleighScale: 5e3,
188
+ mieScattering: 4e-7,
189
+ mieAbsorption: 44e-8,
190
+ absorption: [0, 0, 0]
191
+ },
192
+ /** Dense aerosols: a pale sky and distant hills lost in haze a few km out. */
193
+ "thick-haze": {
194
+ ...EARTH,
195
+ mieScattering: 8e-5,
196
+ mieAbsorption: 2e-5,
197
+ mieScale: 3e3,
198
+ mieG: 0.7
199
+ },
200
+ /** Red and blue scatter, green passes: a violet sky with green-gold sunsets. */
201
+ "alien-violet": {
202
+ ...EARTH,
203
+ rayleighScattering: [26e-6, 6e-6, 331e-7]
204
+ },
205
+ /** Hydrogen and helium over a cloud deck: tall, weakly absorbing; raise absorption for Neptune. */
206
+ "gas-giant": {
207
+ ...EARTH,
208
+ bottomRadius: 69911e3,
209
+ thickness: 2e6,
210
+ rayleighScattering: [14e-7, 33e-7, 8e-6],
211
+ rayleighScale: 27e3,
212
+ mieScattering: 1e-6,
213
+ mieAbsorption: 2e-7,
214
+ mieScale: 2e4,
215
+ absorption: [3e-7, 3e-8, 0],
216
+ absorptionCenter: 0,
217
+ absorptionWidth: 4e5,
218
+ groundAlbedo: [0.8, 0.7, 0.55, 1],
219
+ deckDepth: 2e5
220
+ }
221
+ };
222
+ import {
223
+ AtmosphereStore,
224
+ Atmospheres as Atmospheres2
225
+ } from "./atmosphere-state.js";
226
+ function createRecord(entity, wrapper) {
227
+ return {
228
+ entity,
229
+ wrapper,
230
+ model: createModel(),
231
+ version: 0,
232
+ problem: void 0,
233
+ center: new Float64Array(3),
234
+ seen: 0,
235
+ cpu: void 0,
236
+ layer: -1,
237
+ layerVersion: -1
238
+ };
239
+ }
240
+ function createCameraAtmosphere(camera) {
241
+ const sunTransmittance = new Float32Array(16).fill(1);
242
+ return {
243
+ camera,
244
+ primary: void 0,
245
+ inside: false,
246
+ altitude: 0,
247
+ list: [],
248
+ origins: new Float64Array(16),
249
+ sunTransmittance,
250
+ suns: { count: 0, index: new Int32Array(2), data: new Float32Array(16) },
251
+ frame: new Float64Array(9),
252
+ background: -1,
253
+ aerialPerspective: true,
254
+ skyViewSize: [192, 108],
255
+ froxels: [32, 32, 32],
256
+ maxDistance: 32e3,
257
+ fogIgnored: false,
258
+ bakeKey: "",
259
+ pixels: new Float64Array(4)
260
+ };
261
+ }
262
+ const scratchModel = createModel();
263
+ function writeModel(v, bottomMeters, sky, into) {
264
+ const m = scratchModel;
265
+ const km = 1e3;
266
+ m.bottom = bottomMeters / km;
267
+ m.top = (bottomMeters + v.thickness) / km;
268
+ m.ground = Math.max((bottomMeters - v.deckDepth) / km, m.bottom * 0.05);
269
+ const ray = sky ? sky.rayleigh : 1;
270
+ const mie = sky ? sky.mie * sky.turbidity / 2 : 1;
271
+ for (let c = 0; c < 3; c++) {
272
+ m.rayleigh[c] = v.rayleighScattering[c] * km * ray;
273
+ m.mieG[c] = Math.min(0.99, Math.max(-0.99, v.mieG + v.mieGOffset[c]));
274
+ m.absorption[c] = v.absorption[c] * km;
275
+ m.albedo[c] = v.groundAlbedo[c];
276
+ }
277
+ m.rayleighInvScale = km / v.rayleighScale;
278
+ m.mieScattering = v.mieScattering * km * mie;
279
+ m.mieAbsorption = v.mieAbsorption * km * mie;
280
+ m.mieInvScale = km / v.mieScale;
281
+ m.absorptionCenter = v.absorptionCenter / km;
282
+ m.absorptionWidth = v.absorptionWidth / km;
283
+ m.intensity = v.intensity;
284
+ let changed = false;
285
+ const keys = [
286
+ "bottom",
287
+ "top",
288
+ "ground",
289
+ "rayleighInvScale",
290
+ "mieScattering",
291
+ "mieAbsorption",
292
+ "mieInvScale",
293
+ "absorptionCenter",
294
+ "absorptionWidth",
295
+ "intensity"
296
+ ];
297
+ for (const k of keys) {
298
+ if (into[k] !== m[k]) {
299
+ into[k] = m[k];
300
+ changed = true;
301
+ }
302
+ }
303
+ for (const k of ["rayleigh", "mieG", "absorption", "albedo"]) {
304
+ for (let c = 0; c < 3; c++) {
305
+ if (into[k][c] !== m[k][c]) {
306
+ into[k][c] = m[k][c];
307
+ changed = true;
308
+ }
309
+ }
310
+ }
311
+ return changed;
312
+ }
313
+ function atmosphereModel(value, bottomRadius) {
314
+ const m = createModel();
315
+ writeModel(value, bottomRadius, void 0, m);
316
+ return m;
317
+ }
318
+ let planetDef;
319
+ function planetRadius(world, entity) {
320
+ if (planetDef === void 0 || planetDef === null) {
321
+ try {
322
+ planetDef = findComponent("terrain/Planet") ?? null;
323
+ } catch {
324
+ planetDef = null;
325
+ }
326
+ }
327
+ if (!planetDef) return void 0;
328
+ const v = world.tryGet(entity, planetDef);
329
+ return v?.radius;
330
+ }
331
+ const EARTH_RADIUS = 636e4;
332
+ function logOnce(store, key, fn) {
333
+ if (store.logged.has(key)) return;
334
+ store.logged.add(key);
335
+ fn();
336
+ }
337
+ function updateRecords(world, store, q, since) {
338
+ const recheck = store.frame % 30 === 0;
339
+ let seenThisFrame = 0;
340
+ for (const table of q.tables) {
341
+ const g = table.column(GlobalTransform, "matrix");
342
+ const bottom = table.column(Atmosphere, "bottomRadius");
343
+ const thickness = table.column(Atmosphere, "thickness");
344
+ const changed = table.changedTicks(Atmosphere);
345
+ for (let i = 0; i < table.count; i++) {
346
+ const entity = table.entities[i];
347
+ let rec = store.records.get(entity);
348
+ if (!rec) {
349
+ rec = createRecord(entity, false);
350
+ store.records.set(entity, rec);
351
+ }
352
+ rec.seen = store.frame;
353
+ seenThisFrame++;
354
+ rec.center[0] = g[i * 12 + 3];
355
+ rec.center[1] = g[i * 12 + 7];
356
+ rec.center[2] = g[i * 12 + 11];
357
+ if (thickness[i] <= 0) {
358
+ if (!rec.problem) {
359
+ rec.problem = new ShardError(
360
+ "render/atmosphere-inside-ground",
361
+ `Atmosphere on entity ${entity} has thickness ${thickness[i]} m; its top is inside the ground`,
362
+ { hint: "Give it a positive thickness (Earth 60 000 m)." }
363
+ );
364
+ const problem = rec.problem;
365
+ logOnce(
366
+ store,
367
+ `inside-ground:${entity}`,
368
+ () => world.tryResource(LogResource)?.error(problem)
369
+ );
370
+ }
371
+ continue;
372
+ }
373
+ const stale = rec.version === 0 || rec.problem !== void 0 || changed[i] > since;
374
+ rec.problem = void 0;
375
+ if (!stale && !(recheck && bottom[i] <= 0)) continue;
376
+ const b = bottom[i] > 0 ? bottom[i] : planetRadius(world, entity) ?? EARTH_RADIUS;
377
+ const value = world.get(entity, Atmosphere);
378
+ if (writeModel(value, b, void 0, rec.model) || rec.version === 0) rec.version++;
379
+ }
380
+ }
381
+ if (store.records.size > seenThisFrame) {
382
+ for (const [entity, rec] of store.records) {
383
+ if (store.frame - rec.seen > 1) store.records.delete(entity);
384
+ }
385
+ }
386
+ }
387
+ const SKY_DEFAULTS = {
388
+ turbidity: 2,
389
+ rayleigh: 1,
390
+ mie: 1,
391
+ groundAlbedo: [0.3, 0.3, 0.3, 1],
392
+ sunDiskSize: 1
393
+ };
394
+ function skyOf(world, e) {
395
+ const own = world.isAlive(e);
396
+ const map = own ? world.tryGet(e, EnvironmentMap) : void 0;
397
+ if (map?.texture) return void 0;
398
+ const sky = own ? world.tryGet(e, ProceduralSky) : void 0;
399
+ if (sky) return { sky, background: 1 };
400
+ const defaults = world.resource(DefaultEnvironment);
401
+ if (defaults.map?.texture) return void 0;
402
+ if (!defaults.sky) return void 0;
403
+ return {
404
+ sky: { ...SKY_DEFAULTS, ...defaults.sky },
405
+ background: defaults.background ? 1 : -1
406
+ };
407
+ }
408
+ function pickSuns(world, out, diskScale) {
409
+ const lights = world.tryResource(Lights);
410
+ out.count = 0;
411
+ if (!lights) return;
412
+ const d = lights.directionalData;
413
+ let best = -1;
414
+ let bestLum = -1;
415
+ let second = -1;
416
+ let secondLum = -1;
417
+ for (let i = 0; i < lights.directionalCount; i++) {
418
+ const o = 4 + i * 8;
419
+ const lum = 0.2126 * d[o + 4] + 0.7152 * d[o + 5] + 0.0722 * d[o + 6];
420
+ if (lum > bestLum) {
421
+ second = best;
422
+ secondLum = bestLum;
423
+ best = i;
424
+ bestLum = lum;
425
+ } else if (lum > secondLum) {
426
+ second = i;
427
+ secondLum = lum;
428
+ }
429
+ }
430
+ for (let k = 0; k < 2; k++) {
431
+ const i = k === 0 ? best : second;
432
+ if (i < 0) continue;
433
+ const o = 4 + i * 8;
434
+ const s = out.count * 8;
435
+ out.index[out.count] = i;
436
+ out.data[s] = d[o];
437
+ out.data[s + 1] = d[o + 1];
438
+ out.data[s + 2] = d[o + 2];
439
+ out.data[s + 3] = d[o + 7] * diskScale;
440
+ out.data[s + 4] = d[o + 4];
441
+ out.data[s + 5] = d[o + 5];
442
+ out.data[s + 6] = d[o + 6];
443
+ out.data[s + 7] = 0;
444
+ out.count++;
445
+ }
446
+ }
447
+ const tScratch = new Float64Array(3);
448
+ function sphereVisible(cam, x, y, z, r) {
449
+ const f = cam.frustum;
450
+ for (let p = 0; p < 24; p += 4) {
451
+ if (f[p] * x + f[p + 1] * y + f[p + 2] * z + f[p + 3] < -r) return false;
452
+ }
453
+ return true;
454
+ }
455
+ function pixelRadius(cam, dist, r) {
456
+ const focal = cam.proj[5] * cam.height * 0.5;
457
+ if (cam.orthographic) return r * focal;
458
+ if (dist <= r) return Number.POSITIVE_INFINITY;
459
+ return r / Math.sqrt(dist * dist - r * r) * focal;
460
+ }
461
+ const wrapperValue = { ...EARTH, groundAlbedo: [0.3, 0.3, 0.3, 1] };
462
+ const secondaries = [];
463
+ const byDistance = (a, b) => a.distance - b.distance;
464
+ const candidates = [];
465
+ const pool = [];
466
+ const selectAtmospheres = defineSystem({
467
+ name: "render/atmosphere-select",
468
+ description: "Chooses each camera's primary and secondary atmospheres and dims directional lights by the air between the camera and each sun.",
469
+ setup: (world) => ({ q: world.query({ with: [Atmosphere, GlobalTransform] }) }),
470
+ run: ({ q }, world, ctx) => {
471
+ const store = world.resource(Atmospheres);
472
+ store.frame++;
473
+ updateRecords(world, store, q, ctx.lastRunTick);
474
+ for (const view of world.resource(Views).list) {
475
+ const cam = cameraOf(view);
476
+ if (!cam) continue;
477
+ const e = cam.entity;
478
+ let ca = store.cameras.get(e);
479
+ if (!ca) {
480
+ ca = createCameraAtmosphere(e);
481
+ store.cameras.set(e, ca);
482
+ }
483
+ selectFor(world, store, cam, ca);
484
+ view.data.atmosphere = ca.list.length > 0 ? ca : void 0;
485
+ const before = cam.post.effects;
486
+ if (ca.list.length > 0 && ca.aerialPerspective) cam.post.effects |= PostEffect.Atmosphere;
487
+ if (ca.fogIgnored) cam.post.effects &= ~PostEffect.Fog;
488
+ if (cam.post.effects !== before) view.aliases = postAliases(cam, viewAliases(cam));
489
+ }
490
+ if (store.frame % 60 === 0) {
491
+ for (const [e, rec] of store.wrappers) {
492
+ if (store.frame - rec.seen > 1) store.wrappers.delete(e);
493
+ }
494
+ for (const e of store.cameras.keys()) {
495
+ if (!world.isAlive(e)) store.cameras.delete(e);
496
+ }
497
+ }
498
+ }
499
+ });
500
+ function selectFor(world, store, cam, ca) {
501
+ const e = cam.entity;
502
+ const px = cam.position[0];
503
+ const py = cam.position[1];
504
+ const pz = cam.position[2];
505
+ const settings = world.isAlive(e) ? world.tryGet(e, AtmosphereSettings) : void 0;
506
+ ca.aerialPerspective = settings?.aerialPerspective ?? true;
507
+ ca.skyViewSize[0] = Math.round(settings?.skyViewSize[0] ?? 192);
508
+ ca.skyViewSize[1] = Math.round(settings?.skyViewSize[1] ?? 108);
509
+ ca.froxels[0] = Math.round(settings?.froxels[0] ?? 32);
510
+ ca.froxels[1] = Math.round(settings?.froxels[1] ?? 32);
511
+ ca.froxels[2] = Math.round(settings?.froxels[2] ?? 32);
512
+ ca.maxDistance = settings?.maxDistance ?? 32e3;
513
+ candidates.length = 0;
514
+ let primary;
515
+ let inside = false;
516
+ let nearest = Number.POSITIVE_INFINITY;
517
+ for (const rec of store.records.values()) {
518
+ if (rec.problem) continue;
519
+ const cx = rec.center[0] - px;
520
+ const cy = rec.center[1] - py;
521
+ const cz = rec.center[2] - pz;
522
+ const dist = Math.sqrt(cx * cx + cy * cy + cz * cz);
523
+ const top = rec.model.top * 1e3;
524
+ const toTop = dist - top;
525
+ if (toTop < 0) {
526
+ if (!inside || toTop < nearest) {
527
+ primary = rec;
528
+ nearest = toTop;
529
+ inside = true;
530
+ }
531
+ continue;
532
+ }
533
+ const visible = sphereVisible(cam, rec.center[0], rec.center[1], rec.center[2], top);
534
+ if (!visible) continue;
535
+ const c = pool[candidates.length] ?? { rec, distance: 0, pixels: 0 };
536
+ pool[candidates.length] = c;
537
+ c.rec = rec;
538
+ c.distance = dist;
539
+ c.pixels = pixelRadius(cam, dist, top);
540
+ candidates.push(c);
541
+ if (!inside && toTop < nearest) {
542
+ primary = rec;
543
+ nearest = toTop;
544
+ }
545
+ }
546
+ const sky = primary ? void 0 : skyOf(world, e);
547
+ ca.background = 1;
548
+ let diskScale = 1;
549
+ if (sky) {
550
+ let rec = store.wrappers.get(e);
551
+ if (!rec) {
552
+ rec = createRecord(e, true);
553
+ store.wrappers.set(e, rec);
554
+ }
555
+ rec.seen = store.frame;
556
+ const albedo = wrapperValue.groundAlbedo;
557
+ for (let c = 0; c < 3; c++) albedo[c] = sky.sky.groundAlbedo[c] ?? 0.3;
558
+ if (writeModel(wrapperValue, EARTH_RADIUS, sky.sky, rec.model) || rec.version === 0)
559
+ rec.version++;
560
+ rec.center[0] = px;
561
+ rec.center[1] = py - (EARTH_RADIUS + 10);
562
+ rec.center[2] = pz;
563
+ primary = rec;
564
+ inside = true;
565
+ ca.background = sky.background;
566
+ diskScale = sky.sky.sunDiskSize;
567
+ candidates.length = 0;
568
+ }
569
+ ca.primary = primary;
570
+ ca.inside = inside;
571
+ ca.list.length = 0;
572
+ candidates.sort(byDistance);
573
+ secondaries.length = 0;
574
+ for (const c of candidates) {
575
+ if (secondaries.length >= 3) break;
576
+ if (c.rec === primary || c.pixels <= 4) continue;
577
+ secondaries.push(c.rec);
578
+ }
579
+ for (let i = secondaries.length - 1; i >= 0; i--) ca.list.push(secondaries[i]);
580
+ if (primary) ca.list.push(primary);
581
+ for (let i = 0; i < ca.list.length; i++) {
582
+ const rec = ca.list[i];
583
+ const ox = (px - rec.center[0]) / 1e3;
584
+ const oy = (py - rec.center[1]) / 1e3;
585
+ const oz = (pz - rec.center[2]) / 1e3;
586
+ ca.origins[i * 4] = ox;
587
+ ca.origins[i * 4 + 1] = oy;
588
+ ca.origins[i * 4 + 2] = oz;
589
+ const r = Math.sqrt(ox * ox + oy * oy + oz * oz);
590
+ ca.origins[i * 4 + 3] = r < rec.model.top ? 1 : 0;
591
+ ca.pixels[i] = pixelRadius(cam, r * 1e3, rec.model.top * 1e3);
592
+ }
593
+ pickSuns(world, ca.suns, diskScale);
594
+ const st = ca.sunTransmittance;
595
+ st.fill(1);
596
+ ca.altitude = 0;
597
+ const lights = world.tryResource(Lights);
598
+ if (primary && lights) {
599
+ const m = primary.model;
600
+ let ox = (px - primary.center[0]) / 1e3;
601
+ let oy = (py - primary.center[1]) / 1e3;
602
+ let oz = (pz - primary.center[2]) / 1e3;
603
+ let r = Math.sqrt(ox * ox + oy * oy + oz * oz);
604
+ ca.altitude = (r - m.bottom) * 1e3;
605
+ if (r < m.ground + 1e-4) {
606
+ const k = (m.ground + 1e-4) / Math.max(r, 1e-9);
607
+ ox *= k;
608
+ oy *= k;
609
+ oz *= k;
610
+ r = m.ground + 1e-4;
611
+ }
612
+ const d = lights.directionalData;
613
+ for (let i = 0; i < lights.directionalCount && i < MAX_DIRECTIONAL; i++) {
614
+ const o = 4 + i * 8;
615
+ transmittanceToTop(m, ox, oy, oz, d[o], d[o + 1], d[o + 2], tScratch);
616
+ st[i * 4] = tScratch[0];
617
+ st[i * 4 + 1] = tScratch[1];
618
+ st[i * 4 + 2] = tScratch[2];
619
+ }
620
+ const f = ca.frame;
621
+ f[0] = ox / r;
622
+ f[1] = oy / r;
623
+ f[2] = oz / r;
624
+ let ax = 1;
625
+ let ay = 0;
626
+ let az = 0;
627
+ if (ca.suns.count > 0) {
628
+ ax = ca.suns.data[0];
629
+ ay = ca.suns.data[1];
630
+ az = ca.suns.data[2];
631
+ }
632
+ let dot = ax * f[0] + ay * f[1] + az * f[2];
633
+ let tx = ax - dot * f[0];
634
+ let ty = ay - dot * f[1];
635
+ let tz = az - dot * f[2];
636
+ let tl = Math.sqrt(tx * tx + ty * ty + tz * tz);
637
+ if (tl < 1e-4) {
638
+ ax = Math.abs(f[0]) < 0.9 ? 1 : 0;
639
+ ay = ax ? 0 : 1;
640
+ az = 0;
641
+ dot = ax * f[0] + ay * f[1];
642
+ tx = ax - dot * f[0];
643
+ ty = ay - dot * f[1];
644
+ tz = az - dot * f[2];
645
+ tl = Math.sqrt(tx * tx + ty * ty + tz * tz);
646
+ }
647
+ f[3] = tx / tl;
648
+ f[4] = ty / tl;
649
+ f[5] = tz / tl;
650
+ f[6] = f[1] * f[5] - f[2] * f[4];
651
+ f[7] = f[2] * f[3] - f[0] * f[5];
652
+ f[8] = f[0] * f[4] - f[1] * f[3];
653
+ }
654
+ ca.fogIgnored = false;
655
+ if (primary && !primary.wrapper && cam.post.effects & PostEffect.Fog && cam.post.fog.mode !== 1) {
656
+ ca.fogIgnored = true;
657
+ logOnce(
658
+ store,
659
+ `fog:${e}`,
660
+ () => world.tryResource(LogResource)?.log("warn", `Camera ${e} has Fog inside an atmosphere; the fog is ignored`, {
661
+ code: "render/fog-with-atmosphere",
662
+ hint: 'The atmosphere hazes distant terrain already. For extra ground fog, set Fog.mode to "add".'
663
+ })
664
+ );
665
+ }
666
+ if (primary && inside) {
667
+ const m = primary.model;
668
+ const r = Math.max(m.bottom + ca.altitude / 1e3, m.bottom);
669
+ const horizon = Math.sqrt(Math.max(r * r - m.bottom * m.bottom, 0));
670
+ const peak = m.bottom + Math.min(10, m.top - m.bottom);
671
+ const beyond = Math.sqrt(peak * peak - m.bottom * m.bottom);
672
+ ca.maxDistance = Math.max(ca.maxDistance, (horizon + beyond) * 1e3);
673
+ }
674
+ ca.bakeKey = primary ? bakeKey(primary, ca) : "";
675
+ }
676
+ function bakeKey(rec, ca) {
677
+ const q = (v) => Math.round(v * 229);
678
+ const alt = Math.round(Math.log(1 + Math.max(ca.altitude, 0) / 1e3) / Math.log(1.02));
679
+ const f = ca.frame;
680
+ let key = `${rec.wrapper ? "w" : "a"}${rec.entity}:${rec.version}|${alt}|${q(f[0])},${q(f[1])},${q(f[2])}`;
681
+ const s = ca.suns;
682
+ for (let i = 0; i < s.count; i++) {
683
+ const o = i * 8;
684
+ const lum = 0.2126 * s.data[o + 4] + 0.7152 * s.data[o + 5] + 0.0722 * s.data[o + 6];
685
+ const lux = lum > 0 ? Math.round(Math.log(lum) / Math.log(1.01)) : -1;
686
+ key += `|${q(s.data[o])},${q(s.data[o + 1])},${q(s.data[o + 2])},${lux}`;
687
+ }
688
+ return key;
689
+ }
690
+ function recordOf(world, atmosphere) {
691
+ const store = world.resource(Atmospheres);
692
+ const rec = store.records.get(atmosphere) ?? store.wrappers.get(atmosphere);
693
+ if (rec) {
694
+ if (rec.problem) throw rec.problem;
695
+ return rec;
696
+ }
697
+ if (!world.isAlive(atmosphere) || !world.has(atmosphere, Atmosphere)) {
698
+ throw new ShardError("render/not-an-atmosphere", `Entity ${atmosphere} has no Atmosphere`, {
699
+ hint: "Pass the entity that has render/Atmosphere (render.describe lists each camera\u2019s)."
700
+ });
701
+ }
702
+ const r = createRecord(atmosphere, false);
703
+ const value = world.get(atmosphere, Atmosphere);
704
+ const b = value.bottomRadius > 0 ? value.bottomRadius : planetRadius(world, atmosphere) ?? EARTH_RADIUS;
705
+ writeModel(value, b, void 0, r.model);
706
+ const g = world.tryGet(atmosphere, GlobalTransform)?.matrix;
707
+ if (g) {
708
+ r.center[0] = g[3];
709
+ r.center[1] = g[7];
710
+ r.center[2] = g[11];
711
+ }
712
+ return r;
713
+ }
714
+ function sunsOf(world) {
715
+ const lights = world.tryResource(Lights);
716
+ const suns = [];
717
+ if (!lights) {
718
+ for (const table of world.query({ with: [DirectionalLight, GlobalTransform] }).tables) {
719
+ const g = table.column(GlobalTransform, "matrix");
720
+ const color = table.column(DirectionalLight, "color");
721
+ const lux = table.column(DirectionalLight, "illuminance");
722
+ for (let i = 0; i < table.count; i++) {
723
+ const x = g[i * 12 + 2];
724
+ const y = g[i * 12 + 6];
725
+ const z = g[i * 12 + 10];
726
+ const len = Math.sqrt(x * x + y * y + z * z) || 1;
727
+ const e = [
728
+ color[i * 4] * lux[i],
729
+ color[i * 4 + 1] * lux[i],
730
+ color[i * 4 + 2] * lux[i]
731
+ ];
732
+ suns.push({
733
+ direction: [x / len, y / len, z / len],
734
+ illuminance: e,
735
+ lum: 0.2126 * e[0] + 0.7152 * e[1] + 0.0722 * e[2]
736
+ });
737
+ }
738
+ }
739
+ return suns.sort((a, b) => b.lum - a.lum);
740
+ }
741
+ const d = lights.directionalData;
742
+ for (let i = 0; i < lights.directionalCount; i++) {
743
+ const o = 4 + i * 8;
744
+ suns.push({
745
+ direction: [d[o], d[o + 1], d[o + 2]],
746
+ illuminance: [d[o + 4], d[o + 5], d[o + 6]],
747
+ lum: 0.2126 * d[o + 4] + 0.7152 * d[o + 5] + 0.0722 * d[o + 6]
748
+ });
749
+ }
750
+ return suns.sort((a, b) => b.lum - a.lum);
751
+ }
752
+ function sunTransmittanceAt(world, atmosphere, position, sun = 0) {
753
+ const rec = recordOf(world, atmosphere);
754
+ const s = sunsOf(world)[sun];
755
+ if (!s) return [1, 1, 1];
756
+ const m = rec.model;
757
+ let ox = (position[0] - rec.center[0]) / 1e3;
758
+ let oy = (position[1] - rec.center[1]) / 1e3;
759
+ let oz = (position[2] - rec.center[2]) / 1e3;
760
+ const r = Math.sqrt(ox * ox + oy * oy + oz * oz);
761
+ if (r < m.ground + 1e-4) {
762
+ const k = (m.ground + 1e-4) / Math.max(r, 1e-9);
763
+ ox *= k;
764
+ oy *= k;
765
+ oz *= k;
766
+ }
767
+ const out = new Float64Array(3);
768
+ transmittanceToTop(m, ox, oy, oz, s.direction[0], s.direction[1], s.direction[2], out);
769
+ return [out[0], out[1], out[2]];
770
+ }
771
+ function cpuLuts(rec) {
772
+ if (!rec.cpu || rec.cpu.version !== rec.version) {
773
+ const transmittance = transmittanceLut(rec.model);
774
+ rec.cpu = {
775
+ version: rec.version,
776
+ transmittance,
777
+ multiscatter: multiscatterLut(rec.model, transmittance)
778
+ };
779
+ }
780
+ return rec.cpu;
781
+ }
782
+ function sampleAtmosphere(world, atmosphere, position, direction) {
783
+ const rec = recordOf(world, atmosphere);
784
+ const { transmittance, multiscatter } = cpuLuts(rec);
785
+ const m = rec.model;
786
+ const o = [
787
+ (position[0] - rec.center[0]) / 1e3,
788
+ (position[1] - rec.center[1]) / 1e3,
789
+ (position[2] - rec.center[2]) / 1e3
790
+ ];
791
+ const r = Math.hypot(o[0], o[1], o[2]);
792
+ if (r < m.ground + 1e-4) for (let k = 0; k < 3; k++) o[k] = o[k] * (m.ground + 1e-4) / r;
793
+ const len = Math.hypot(direction[0], direction[1], direction[2]) || 1;
794
+ const d = [direction[0] / len, direction[1] / len, direction[2] / len];
795
+ const s = createSkySample();
796
+ integrateSky(m, transmittance, multiscatter, o, d, sunsOf(world).slice(0, 2), s);
797
+ const L = s.radiance;
798
+ return {
799
+ radiance: [L[0], L[1], L[2]],
800
+ luminance: 0.2126 * L[0] + 0.7152 * L[1] + 0.0722 * L[2],
801
+ transmittance: [s.transmittance[0], s.transmittance[1], s.transmittance[2]],
802
+ hitsGround: s.hitsGround,
803
+ altitude: (r - m.bottom) * 1e3,
804
+ inside: r < m.top
805
+ };
806
+ }
807
+ const round = (v, digits = 4) => {
808
+ const k = 10 ** digits;
809
+ return Math.round(v * k) / k;
810
+ };
811
+ function describeAtmospheres(world, timings = {}) {
812
+ const store = world.tryResource(Atmospheres);
813
+ if (!store) return void 0;
814
+ const views = {};
815
+ const lights = world.tryResource(Lights);
816
+ for (const view of world.resource(Views).list) {
817
+ const cam = cameraOf(view);
818
+ if (!cam) continue;
819
+ const ca = store.cameras.get(cam.entity);
820
+ if (!ca || ca.list.length === 0) {
821
+ views[view.name] = { primary: null, secondaries: [] };
822
+ continue;
823
+ }
824
+ const p = ca.primary;
825
+ const suns = [];
826
+ for (let k = 0; k < ca.suns.count; k++) {
827
+ const i = ca.suns.index[k];
828
+ const o = 4 + i * 8;
829
+ const d = lights?.directionalData;
830
+ const T = [
831
+ ca.sunTransmittance[i * 4],
832
+ ca.sunTransmittance[i * 4 + 1],
833
+ ca.sunTransmittance[i * 4 + 2]
834
+ ];
835
+ const lux = d ? 0.2126 * d[o + 4] + 0.7152 * d[o + 5] + 0.0722 * d[o + 6] : 0;
836
+ const atCamera = d ? 0.2126 * d[o + 4] * T[0] + 0.7152 * d[o + 5] * T[1] + 0.0722 * d[o + 6] * T[2] : 0;
837
+ suns.push({
838
+ light: i,
839
+ direction: [
840
+ round(ca.suns.data[k * 8]),
841
+ round(ca.suns.data[k * 8 + 1]),
842
+ round(ca.suns.data[k * 8 + 2])
843
+ ],
844
+ illuminance: round(lux, 1),
845
+ transmittance: T.map((v) => round(v)),
846
+ illuminanceAtCamera: round(atCamera, 1)
847
+ });
848
+ }
849
+ const describe = (rec, i) => ({
850
+ entity: rec.entity,
851
+ source: rec.wrapper ? "procedural-sky" : "atmosphere",
852
+ bottomRadius: round(rec.model.bottom * 1e3, 1),
853
+ thickness: round((rec.model.top - rec.model.bottom) * 1e3, 1),
854
+ distance: round(
855
+ Math.hypot(ca.origins[i * 4], ca.origins[i * 4 + 1], ca.origins[i * 4 + 2]) * 1e3,
856
+ 1
857
+ ),
858
+ pixels: Number.isFinite(ca.pixels[i]) ? round(ca.pixels[i], 1) : "fills the view"
859
+ });
860
+ const pi = p ? ca.list.indexOf(p) : -1;
861
+ views[view.name] = {
862
+ primary: p ? { ...describe(p, pi), inside: ca.inside, altitude: round(ca.altitude, 2) } : null,
863
+ secondaries: ca.list.filter((r) => r !== p).map((r) => describe(r, ca.list.indexOf(r))),
864
+ suns,
865
+ aerialPerspective: ca.aerialPerspective,
866
+ fogIgnored: ca.fogIgnored,
867
+ gpuMs: {
868
+ luts: timings["gpu:atmosphere/luts"]?.avg,
869
+ skyView: timings["gpu:atmosphere/sky-view"]?.avg,
870
+ aerial: timings["gpu:atmosphere/aerial"]?.avg,
871
+ sky: timings["gpu:atmosphere/sky"]?.avg,
872
+ composite: timings["gpu:post/atmosphere"]?.avg
873
+ }
874
+ };
875
+ }
876
+ return {
877
+ views,
878
+ atmospheres: [...store.records.values()].map((r) => ({
879
+ entity: r.entity,
880
+ version: r.version,
881
+ problem: r.problem ? r.problem.toJSON() : null
882
+ }))
883
+ };
884
+ }
885
+ const vec3Of = (v, what) => {
886
+ if (!Array.isArray(v) || v.length !== 3 || v.some((x) => typeof x !== "number")) {
887
+ throw new ShardError("render/bad-vector", `${what} must be [x, y, z]`, {
888
+ hint: `e.g. { "${what}": [0, 1, 0] }`
889
+ });
890
+ }
891
+ return v;
892
+ };
893
+ const atmosphereMethods = [
894
+ {
895
+ name: "atmosphere.sample",
896
+ description: "Sky radiance (cd/m\xB2) and transmittance through an atmosphere toward a direction, computed on the CPU with the renderer's model (headless-safe): e.g. whether the sky is still blue at 40 km, or how much a star field shows through at dusk. position defaults to the first camera's; entity defaults to that camera's primary atmosphere.",
897
+ params: defineSchema("render/AtmosphereSampleParams", {
898
+ entity: t.json({
899
+ description: "The Atmosphere entity (default: the first camera\u2019s primary)."
900
+ }),
901
+ position: t.json({
902
+ description: "World position [x, y, z] in metres (default: the camera)."
903
+ }),
904
+ direction: t.json({ description: "View direction [x, y, z], e.g. [0, 1, 0] straight up." })
905
+ }),
906
+ handler: ({ world }, p) => {
907
+ const store = world.resource(Atmospheres);
908
+ const first = world.resource(Views).list.map(cameraOf).find((c) => c !== void 0);
909
+ let entity = p.entity;
910
+ if (entity === void 0 || entity === null) {
911
+ const ca = first ? store.cameras.get(first.entity) : void 0;
912
+ entity = ca?.primary?.entity;
913
+ if (entity === void 0) {
914
+ const only = [...store.records.keys()];
915
+ if (only.length !== 1) {
916
+ throw new ShardError("render/which-atmosphere", "Name the atmosphere to sample", {
917
+ hint: "Pass entity: an entity with render/Atmosphere."
918
+ });
919
+ }
920
+ entity = only[0];
921
+ }
922
+ }
923
+ const position = p.position !== void 0 && p.position !== null ? vec3Of(p.position, "position") : first ? [first.position[0], first.position[1], first.position[2]] : [0, 0, 0];
924
+ const direction = vec3Of(p.direction, "direction");
925
+ const r = sampleAtmosphere(world, entity, position, direction);
926
+ return {
927
+ entity,
928
+ radiance: r.radiance.map((v) => round(v, 3)),
929
+ luminance: round(r.luminance, 3),
930
+ transmittance: r.transmittance.map((v) => round(v, 5)),
931
+ hitsGround: r.hitsGround,
932
+ altitude: round(r.altitude, 2),
933
+ inside: r.inside
934
+ };
935
+ }
936
+ }
937
+ ];
938
+ export {
939
+ Atmosphere,
940
+ AtmospherePresets,
941
+ AtmosphereSettings,
942
+ AtmosphereStore,
943
+ Atmospheres2 as Atmospheres,
944
+ atmosphereMethods,
945
+ atmosphereModel,
946
+ describeAtmospheres,
947
+ sampleAtmosphere,
948
+ selectAtmospheres,
949
+ sunTransmittanceAt
950
+ };