@woosh/meep-engine 3.14.1 → 3.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/README.md +31 -24
  2. package/package.json +1 -1
  3. package/src/avif/native/DECISIONS.md +544 -536
  4. package/src/avif/native/heif/parse_avif_file.d.ts.map +1 -1
  5. package/src/avif/native/heif/parse_avif_file.js +384 -383
  6. package/src/avif/native/heif/parse_image_grid.d.ts.map +1 -1
  7. package/src/avif/native/heif/parse_image_grid.js +49 -48
  8. package/src/basis/BasisBitReader.d.ts +77 -0
  9. package/src/basis/BasisBitReader.d.ts.map +1 -0
  10. package/src/basis/BasisBitReader.js +231 -0
  11. package/src/basis/BasisHuffmanTable.d.ts +53 -0
  12. package/src/basis/BasisHuffmanTable.d.ts.map +1 -0
  13. package/src/basis/BasisHuffmanTable.js +263 -0
  14. package/src/basis/basis_read_huffman_table.d.ts +20 -0
  15. package/src/basis/basis_read_huffman_table.d.ts.map +1 -0
  16. package/src/basis/basis_read_huffman_table.js +187 -0
  17. package/src/basis/etc1s/Etc1sSelectorHistory.d.ts +50 -0
  18. package/src/basis/etc1s/Etc1sSelectorHistory.d.ts.map +1 -0
  19. package/src/basis/etc1s/Etc1sSelectorHistory.js +89 -0
  20. package/src/basis/etc1s/etc1s_block_colors.d.ts +14 -0
  21. package/src/basis/etc1s/etc1s_block_colors.d.ts.map +1 -0
  22. package/src/basis/etc1s/etc1s_block_colors.js +75 -0
  23. package/src/basis/etc1s/etc1s_decode_slice.d.ts +57 -0
  24. package/src/basis/etc1s/etc1s_decode_slice.d.ts.map +1 -0
  25. package/src/basis/etc1s/etc1s_decode_slice.js +258 -0
  26. package/src/basis/etc1s/etc1s_read_endpoint_codebook.d.ts +48 -0
  27. package/src/basis/etc1s/etc1s_read_endpoint_codebook.d.ts.map +1 -0
  28. package/src/basis/etc1s/etc1s_read_endpoint_codebook.js +111 -0
  29. package/src/basis/etc1s/etc1s_read_selector_codebook.d.ts +42 -0
  30. package/src/basis/etc1s/etc1s_read_selector_codebook.d.ts.map +1 -0
  31. package/src/basis/etc1s/etc1s_read_selector_codebook.js +127 -0
  32. package/src/basis/etc1s/etc1s_read_slice_tables.d.ts +54 -0
  33. package/src/basis/etc1s/etc1s_read_slice_tables.d.ts.map +1 -0
  34. package/src/basis/etc1s/etc1s_read_slice_tables.js +47 -0
  35. package/src/basis/transcode/build_range_plan.js +54 -0
  36. package/src/basis/transcode/etc1s_to_bc1.d.ts +24 -0
  37. package/src/basis/transcode/etc1s_to_bc1.d.ts.map +1 -0
  38. package/src/basis/transcode/etc1s_to_bc1.js +205 -0
  39. package/src/basis/transcode/etc1s_to_bc7.d.ts +30 -0
  40. package/src/basis/transcode/etc1s_to_bc7.d.ts.map +1 -0
  41. package/src/basis/transcode/etc1s_to_bc7.js +496 -0
  42. package/src/basis/transcode/fit_endpoints.d.ts +59 -0
  43. package/src/basis/transcode/fit_endpoints.d.ts.map +1 -0
  44. package/src/basis/transcode/fit_endpoints.js +226 -0
  45. package/src/{avif/native/isobmff → core/binary}/ByteCursor.d.ts +19 -6
  46. package/src/core/binary/ByteCursor.d.ts.map +1 -0
  47. package/src/{avif/native/isobmff → core/binary}/ByteCursor.js +41 -14
  48. package/src/engine/EngineHarness.js +1 -1
  49. package/src/engine/asset/codec/Codec.d.ts.map +1 -0
  50. package/src/engine/asset/codec/CodecWithFallback.d.ts.map +1 -0
  51. package/src/engine/asset/{loaders/image/codec → codec}/CodecWithFallback.js +2 -2
  52. package/src/engine/asset/loaders/image/IMAGE_CODEC_REVIEW_2026_08_28.md +4 -4
  53. package/src/engine/asset/loaders/image/ImageRGBADataLoader.d.ts +1 -1
  54. package/src/engine/asset/loaders/image/ImageRGBADataLoader.d.ts.map +1 -1
  55. package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +1 -1
  56. package/src/engine/asset/loaders/image/codec/NativeImageDecoder.d.ts +1 -1
  57. package/src/engine/asset/loaders/image/codec/NativeImageDecoder.d.ts.map +1 -1
  58. package/src/engine/asset/loaders/image/codec/NativeImageDecoder.js +1 -1
  59. package/src/engine/asset/loaders/image/codec/ThreadedImageDecoder.d.ts +1 -1
  60. package/src/engine/asset/loaders/image/codec/ThreadedImageDecoder.d.ts.map +1 -1
  61. package/src/engine/asset/loaders/image/codec/ThreadedImageDecoder.js +1 -1
  62. package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts.map +1 -1
  63. package/src/engine/graphics/texture/atlas/TextureAtlas.js +13 -0
  64. package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
  65. package/src/engine/physics/broadphase/generate_pairs.js +7 -1
  66. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  67. package/src/ktx2/KTX2_IDENTIFIER.d.ts +14 -0
  68. package/src/ktx2/KTX2_IDENTIFIER.d.ts.map +1 -0
  69. package/src/ktx2/KTX2_IDENTIFIER.js +15 -0
  70. package/src/ktx2/KhrDfModel.d.ts +35 -0
  71. package/src/ktx2/KhrDfModel.d.ts.map +1 -0
  72. package/src/ktx2/KhrDfModel.js +49 -0
  73. package/src/ktx2/SupercompressionScheme.d.ts +16 -0
  74. package/src/ktx2/SupercompressionScheme.d.ts.map +1 -0
  75. package/src/ktx2/SupercompressionScheme.js +23 -0
  76. package/src/ktx2/ktx2_read.d.ts +71 -0
  77. package/src/ktx2/ktx2_read.d.ts.map +1 -0
  78. package/src/ktx2/ktx2_read.js +581 -0
  79. package/src/ktx2/ktx2_read_basis_lz_global_data.d.ts +27 -0
  80. package/src/ktx2/ktx2_read_basis_lz_global_data.d.ts.map +1 -0
  81. package/src/ktx2/ktx2_read_basis_lz_global_data.js +189 -0
  82. package/src/ktx2/vk_format_to_texture_format.d.ts +55 -0
  83. package/src/ktx2/vk_format_to_texture_format.d.ts.map +1 -0
  84. package/src/ktx2/vk_format_to_texture_format.js +290 -0
  85. package/src/shade/descriptor/GPUTextureUsage.d.ts +23 -0
  86. package/src/shade/descriptor/GPUTextureUsage.d.ts.map +1 -0
  87. package/src/shade/descriptor/GPUTextureUsage.js +24 -0
  88. package/src/shade/descriptor/WebGPUExtensionType.d.ts +3 -0
  89. package/src/shade/descriptor/WebGPUExtensionType.js +26 -0
  90. package/src/shade/descriptor/texture/TextureDescriptor.d.ts +2 -6
  91. package/src/shade/descriptor/texture/TextureDescriptor.d.ts.map +1 -1
  92. package/src/shade/descriptor/texture/TextureDescriptor.js +24 -40
  93. package/src/shade/descriptor/texture/format/TextureFormat.d.ts +125 -0
  94. package/src/shade/descriptor/texture/format/TextureFormat.d.ts.map +1 -0
  95. package/src/shade/descriptor/texture/format/TextureFormat.js +146 -0
  96. package/src/shade/descriptor/texture/format/gpu_texture_format_info.d.ts +60 -0
  97. package/src/shade/descriptor/texture/format/gpu_texture_format_info.d.ts.map +1 -1
  98. package/src/shade/descriptor/texture/format/gpu_texture_format_info.js +102 -0
  99. package/src/shade/device/graph/TextureResourceDescriptor.d.ts.map +1 -1
  100. package/src/shade/device/graph/TextureResourceDescriptor.js +160 -155
  101. package/src/shade/device/mock/SoftwareGPUQueue.d.ts +11 -4
  102. package/src/shade/device/mock/SoftwareGPUQueue.d.ts.map +1 -1
  103. package/src/shade/device/mock/SoftwareGPUQueue.js +31 -37
  104. package/src/shade/device/mock/SoftwareGPUTexture.d.ts.map +1 -1
  105. package/src/shade/device/mock/SoftwareGPUTexture.js +8 -4
  106. package/src/shade/device/timing/profile/frame_graph_extract_topology.d.ts.map +1 -1
  107. package/src/shade/device/timing/profile/frame_graph_extract_topology.js +164 -174
  108. package/src/shade/playground/ground_seam/README.md +235 -0
  109. package/src/shade/playground/ground_seam/capture_scene_color.d.ts +45 -0
  110. package/src/shade/playground/ground_seam/capture_scene_color.d.ts.map +1 -0
  111. package/src/shade/playground/ground_seam/capture_scene_color.js +242 -0
  112. package/src/shade/playground/ground_seam/depth_step_across_seam.d.ts +11 -0
  113. package/src/shade/playground/ground_seam/depth_step_across_seam.d.ts.map +1 -0
  114. package/src/shade/playground/ground_seam/depth_step_across_seam.js +185 -0
  115. package/src/shade/playground/ground_seam/index.html +195 -0
  116. package/src/shade/playground/ground_seam/main.d.ts +2 -0
  117. package/src/shade/playground/ground_seam/main.d.ts.map +1 -0
  118. package/src/shade/playground/ground_seam/main.js +879 -0
  119. package/src/shade/playground/ground_seam/make_grid_texture.d.ts +29 -0
  120. package/src/shade/playground/ground_seam/make_grid_texture.d.ts.map +1 -0
  121. package/src/shade/playground/ground_seam/make_grid_texture.js +105 -0
  122. package/src/shade/playground/ground_seam/make_ground.d.ts +70 -0
  123. package/src/shade/playground/ground_seam/make_ground.d.ts.map +1 -0
  124. package/src/shade/playground/ground_seam/make_ground.js +161 -0
  125. package/src/shade/playground/ground_seam/measure_flicker.d.ts +71 -0
  126. package/src/shade/playground/ground_seam/measure_flicker.d.ts.map +1 -0
  127. package/src/shade/playground/ground_seam/measure_flicker.js +531 -0
  128. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  129. package/src/shade/renderer/Renderer.js +2519 -2511
  130. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
  131. package/src/shade/renderer/hiz/HierarchicalZBuffer.js +275 -274
  132. package/src/shade/renderer/light/GPULightCollection.d.ts.map +1 -1
  133. package/src/shade/renderer/light/GPULightCollection.js +323 -314
  134. package/src/shade/renderer/lightmap/GPULightMap.js +336 -336
  135. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts.map +1 -1
  136. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.js +12 -2
  137. package/src/shade/renderer/material/standard/fragment_gbuffer.d.ts.map +1 -1
  138. package/src/shade/renderer/material/standard/fragment_gbuffer.js +3 -1
  139. package/src/shade/renderer/material/standard/fragment_gbuffer_vt.js +169 -169
  140. package/src/shade/renderer/path_tracer/shader/chunk_sample_material_data.d.ts.map +1 -1
  141. package/src/shade/renderer/path_tracer/shader/chunk_sample_material_data.js +3 -1
  142. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  143. package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_brick4.d.ts.map +1 -1
  144. package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_brick4.js +3 -1
  145. package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_ibl.d.ts.map +1 -1
  146. package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_ibl.js +3 -1
  147. package/src/shade/renderer/shader/chunk/geometry/normal/chunk_decode_tangent_normal.d.ts +28 -0
  148. package/src/shade/renderer/shader/chunk/geometry/normal/chunk_decode_tangent_normal.d.ts.map +1 -0
  149. package/src/shade/renderer/shader/chunk/geometry/normal/chunk_decode_tangent_normal.js +39 -0
  150. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_full.d.ts +45 -0
  151. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_full.d.ts.map +1 -1
  152. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_full.js +94 -61
  153. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_solve.d.ts +24 -0
  154. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_solve.d.ts.map +1 -0
  155. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_solve.js +43 -0
  156. package/src/shade/renderer/texture/COMPRESSED_TEXTURE_PLAN_2026_09_01.md +1462 -0
  157. package/src/shade/renderer/texture/TextureManager.d.ts.map +1 -1
  158. package/src/shade/renderer/texture/TextureManager.js +20 -8
  159. package/src/shade/renderer/texture/TextureRole.d.ts +19 -0
  160. package/src/shade/renderer/texture/TextureRole.d.ts.map +1 -0
  161. package/src/shade/renderer/texture/TextureRole.js +26 -0
  162. package/src/shade/renderer/texture/TextureVariantSet.d.ts +71 -0
  163. package/src/shade/renderer/texture/TextureVariantSet.d.ts.map +1 -0
  164. package/src/shade/renderer/texture/TextureVariantSet.js +105 -0
  165. package/src/shade/renderer/texture/download_texture_data.d.ts.map +1 -1
  166. package/src/shade/renderer/texture/download_texture_data.js +22 -0
  167. package/src/shade/renderer/texture/format/gpu_texture_compression_support.d.ts +15 -0
  168. package/src/shade/renderer/texture/format/gpu_texture_compression_support.d.ts.map +1 -0
  169. package/src/shade/renderer/texture/format/gpu_texture_compression_support.js +50 -0
  170. package/src/shade/renderer/texture/format/gpu_texture_format_can_generate_mipmaps.d.ts +27 -0
  171. package/src/shade/renderer/texture/format/gpu_texture_format_can_generate_mipmaps.d.ts.map +1 -0
  172. package/src/shade/renderer/texture/format/gpu_texture_format_can_generate_mipmaps.js +65 -0
  173. package/src/shade/renderer/texture/format/infer_format_from_parameters.d.ts +11 -0
  174. package/src/shade/renderer/texture/format/infer_format_from_parameters.d.ts.map +1 -0
  175. package/src/shade/renderer/texture/format/infer_format_from_parameters.js +88 -0
  176. package/src/shade/renderer/texture/format/texture_format_from_shade_image.d.ts +24 -0
  177. package/src/shade/renderer/texture/format/texture_format_from_shade_image.d.ts.map +1 -0
  178. package/src/shade/renderer/texture/format/texture_format_from_shade_image.js +71 -0
  179. package/src/shade/renderer/texture/format/texture_format_preference.d.ts +22 -0
  180. package/src/shade/renderer/texture/format/texture_format_preference.d.ts.map +1 -0
  181. package/src/shade/renderer/texture/format/texture_format_preference.js +89 -0
  182. package/src/shade/renderer/texture/format/texture_variant_select.d.ts +24 -0
  183. package/src/shade/renderer/texture/format/texture_variant_select.d.ts.map +1 -0
  184. package/src/shade/renderer/texture/format/texture_variant_select.js +38 -0
  185. package/src/shade/renderer/texture/shade_texture_might_have_alpha.d.ts +9 -1
  186. package/src/shade/renderer/texture/shade_texture_might_have_alpha.d.ts.map +1 -1
  187. package/src/shade/renderer/texture/shade_texture_might_have_alpha.js +73 -8
  188. package/src/shade/renderer/texture/source/AlphaMode.d.ts +27 -0
  189. package/src/shade/renderer/texture/source/AlphaMode.d.ts.map +1 -0
  190. package/src/shade/renderer/texture/source/AlphaMode.js +52 -0
  191. package/src/shade/renderer/texture/source/ShadeImage.d.ts +33 -0
  192. package/src/shade/renderer/texture/source/ShadeImage.d.ts.map +1 -1
  193. package/src/shade/renderer/texture/source/ShadeImage.js +74 -0
  194. package/src/shade/renderer/texture/source/ShadeTexelData.d.ts +78 -0
  195. package/src/shade/renderer/texture/source/ShadeTexelData.d.ts.map +1 -0
  196. package/src/shade/renderer/texture/source/ShadeTexelData.js +158 -0
  197. package/src/shade/renderer/texture/texture_descriptor_from_texture_shade.d.ts +0 -10
  198. package/src/shade/renderer/texture/texture_descriptor_from_texture_shade.d.ts.map +1 -1
  199. package/src/shade/renderer/texture/texture_descriptor_from_texture_shade.js +73 -175
  200. package/src/shade/renderer/texture/texture_write_level.d.ts +32 -0
  201. package/src/shade/renderer/texture/texture_write_level.d.ts.map +1 -0
  202. package/src/shade/renderer/texture/texture_write_level.js +77 -0
  203. package/src/shade/renderer/texture/texture_write_to_gpu.d.ts +8 -1
  204. package/src/shade/renderer/texture/texture_write_to_gpu.d.ts.map +1 -1
  205. package/src/shade/renderer/texture/texture_write_to_gpu.js +62 -37
  206. package/src/shade/renderer/texture/virtual/source/VTSourceImage.d.ts +8 -0
  207. package/src/shade/renderer/texture/virtual/source/VTSourceImage.d.ts.map +1 -1
  208. package/src/shade/renderer/texture/virtual/source/VTSourceImage.js +8 -0
  209. package/src/shade/util/texture_mip_extent.d.ts +49 -0
  210. package/src/shade/util/texture_mip_extent.d.ts.map +1 -0
  211. package/src/shade/util/texture_mip_extent.js +87 -0
  212. package/src/avif/native/isobmff/ByteCursor.d.ts.map +0 -1
  213. package/src/engine/asset/loaders/image/codec/Codec.d.ts.map +0 -1
  214. package/src/engine/asset/loaders/image/codec/CodecWithFallback.d.ts.map +0 -1
  215. package/src/shade/util/compute_max_mip_count.d.ts +0 -8
  216. package/src/shade/util/compute_max_mip_count.d.ts.map +0 -1
  217. package/src/shade/util/compute_max_mip_count.js +0 -27
  218. /package/src/engine/asset/{loaders/image/codec → codec}/Codec.d.ts +0 -0
  219. /package/src/engine/asset/{loaders/image/codec → codec}/Codec.js +0 -0
  220. /package/src/engine/asset/{loaders/image/codec → codec}/CodecWithFallback.d.ts +0 -0
@@ -0,0 +1,531 @@
1
+ import { m4_invert } from "../../../core/geom/3d/mat4/m4_invert.js";
2
+ import { GPUTextureContext } from "../../renderer/texture/GPUTextureContext.js";
3
+
4
+ /**
5
+ * Temporal-difference measurement for the ground-seam repro.
6
+ *
7
+ * ## Why it renders into its own texture
8
+ *
9
+ * A canvas texture is configured by the presentation path, cannot carry `COPY_SRC`, and is rotated
10
+ * away the moment the frame is presented — a `drawImage` off it a task later returns an empty
11
+ * bitmap, and a comparison of two blanks reports perfect agreement. `Renderer.render_to_target`
12
+ * exists for exactly this and shares the view context, so all the temporal history the measurement
13
+ * is *about* is the same history the on-screen path accumulates.
14
+ *
15
+ * Driving the frames from here rather than off `requestAnimationFrame` also takes the swap chain
16
+ * out of the loop, so the number of frames the temporal filter has seen is a number this module
17
+ * chose rather than one the compositor handed it.
18
+ *
19
+ * ## Why the seam is classified, not fitted
20
+ *
21
+ * The brief this repro came from retracted a conclusion that had been *fitted* to the seam's
22
+ * apparent slope, and the fit was wrong because the observer's camera basis had a sign error in it.
23
+ * Nothing here fits anything. Each pixel is unprojected through the camera's own
24
+ * `view_projection_matrix`, intersected with the ground plane, and labelled by the sign of
25
+ * `x + z` — which is the shared edge of the default triangulation, derived from the index buffer
26
+ * rather than from the picture. {@link classify_ground} round-trips one pixel back through the
27
+ * forward matrix and reports the error, so a broken convention shows up as a number instead of as a
28
+ * plausible story.
29
+ */
30
+
31
+ /**
32
+ * @param {number} h a float16 bit pattern
33
+ * @returns {number}
34
+ */
35
+ function half_to_float(h) {
36
+ const sign = (h & 0x8000) !== 0 ? -1 : 1;
37
+ const exponent = (h & 0x7C00) >> 10;
38
+ const fraction = h & 0x03FF;
39
+
40
+ if (exponent === 0) {
41
+ return sign * Math.pow(2, -14) * (fraction / 1024);
42
+ }
43
+
44
+ if (exponent === 31) {
45
+ return fraction !== 0 ? NaN : sign * Infinity;
46
+ }
47
+
48
+ return sign * Math.pow(2, exponent - 15) * (1 + fraction / 1024);
49
+ }
50
+
51
+ /**
52
+ * Which of the ground quad's two triangles covers each pixel, and where on the plane it lands.
53
+ *
54
+ * The classification is `sign(x + z)`: the default triangulation's shared edge is the world line
55
+ * `x = −z` (v2→v1 of `make_plane_geometry`'s 1×1 quad, laid flat by the −90° X rotation), so the
56
+ * two triangles are exactly the two sides of `x + z = 0`. For the flipped index buffer the shared
57
+ * edge is `x = z` instead, so `side_flipped` carries `sign(x − z)` alongside it and a caller can
58
+ * ask about whichever diagonal is currently in the mesh.
59
+ *
60
+ * @param {PerspectiveCamera} camera must already have had `update_matrices` run for this frame
61
+ * @param {number} width in pixels
62
+ * @param {number} height in pixels
63
+ * @param {number} [extent] half-width of the quad; pixels outside it are not ground
64
+ * @returns {{side: Int8Array, side_flipped: Int8Array, round_trip_error_px: number, covered: number}}
65
+ * `side` is −1, +1 or 0 (not ground). `round_trip_error_px` is the worst error, in pixels, from
66
+ * projecting a recovered world point back through the forward matrix — a sanity check on the whole
67
+ * convention, which should be a small fraction of a pixel.
68
+ */
69
+ export function classify_ground(camera, width, height, extent = 212.5) {
70
+
71
+ const inverse = new Float64Array(16);
72
+
73
+ if (!m4_invert(inverse, camera.view_projection_matrix)) {
74
+ throw new Error('camera view-projection matrix is not invertible');
75
+ }
76
+
77
+ const forward = camera.view_projection_matrix;
78
+
79
+ const side = new Int8Array(width * height);
80
+ const side_flipped = new Int8Array(width * height);
81
+
82
+ let round_trip_error_px = 0;
83
+ let covered = 0;
84
+
85
+ /**
86
+ * NDC → world, with the homogeneous divide.
87
+ *
88
+ * @param {number} ndc_x
89
+ * @param {number} ndc_y
90
+ * @param {number} ndc_z
91
+ * @param {number[]} out
92
+ */
93
+ function unproject(ndc_x, ndc_y, ndc_z, out) {
94
+ const x = inverse[0] * ndc_x + inverse[4] * ndc_y + inverse[8] * ndc_z + inverse[12];
95
+ const y = inverse[1] * ndc_x + inverse[5] * ndc_y + inverse[9] * ndc_z + inverse[13];
96
+ const z = inverse[2] * ndc_x + inverse[6] * ndc_y + inverse[10] * ndc_z + inverse[14];
97
+ const w = inverse[3] * ndc_x + inverse[7] * ndc_y + inverse[11] * ndc_z + inverse[15];
98
+
99
+ out[0] = x / w;
100
+ out[1] = y / w;
101
+ out[2] = z / w;
102
+ }
103
+
104
+ const near_point = [0, 0, 0];
105
+ const far_point = [0, 0, 0];
106
+
107
+ for (let py = 0; py < height; py++) {
108
+
109
+ /*
110
+ Framebuffer rows run top to bottom and NDC Y runs bottom to top, so the row index is
111
+ flipped here. This is precisely the convention the retracted analysis got wrong, which is
112
+ why `round_trip_error_px` below re-derives the pixel from the world point instead of
113
+ trusting this line.
114
+ */
115
+ const ndc_y = 1 - 2 * (py + 0.5) / height;
116
+
117
+ for (let px = 0; px < width; px++) {
118
+ const ndc_x = 2 * (px + 0.5) / width - 1;
119
+
120
+ // reverse-Z with an infinite far plane: z = 1 is the near plane, z = 0 is infinity.
121
+ // Two points at different depths give a ray without needing to know which is which.
122
+ unproject(ndc_x, ndc_y, 1, near_point);
123
+ unproject(ndc_x, ndc_y, 0.5, far_point);
124
+
125
+ const dx = far_point[0] - near_point[0];
126
+ const dy = far_point[1] - near_point[1];
127
+ const dz = far_point[2] - near_point[2];
128
+
129
+ if (dy === 0) {
130
+ continue;
131
+ }
132
+
133
+ // intersect the ray with the ground plane, y = 0
134
+ const t = -near_point[1] / dy;
135
+
136
+ if (t < 0) {
137
+ // the plane is behind the ray's start — this pixel is sky
138
+ continue;
139
+ }
140
+
141
+ const wx = near_point[0] + dx * t;
142
+ const wz = near_point[2] + dz * t;
143
+
144
+ if (Math.abs(wx) > extent || Math.abs(wz) > extent) {
145
+ continue;
146
+ }
147
+
148
+ const index = py * width + px;
149
+
150
+ side[index] = wx + wz >= 0 ? 1 : -1;
151
+ side_flipped[index] = wx - wz >= 0 ? 1 : -1;
152
+
153
+ covered++;
154
+
155
+ /*
156
+ Round-trip check, on a sparse sample so it costs nothing: push the recovered world
157
+ point back through the *forward* matrix and see whether it lands on the pixel it came
158
+ from. Any sign error, row flip or depth-range mistake in the block above shows up here
159
+ as a large number rather than as a confident wrong answer.
160
+ */
161
+ if ((px & 63) === 0 && (py & 63) === 0) {
162
+ const cx = forward[0] * wx + forward[8] * wz + forward[12];
163
+ const cy = forward[1] * wx + forward[9] * wz + forward[13];
164
+ const cw = forward[3] * wx + forward[11] * wz + forward[15];
165
+
166
+ const back_px = (cx / cw * 0.5 + 0.5) * width - 0.5;
167
+ const back_py = (0.5 - cy / cw * 0.5) * height - 0.5;
168
+
169
+ round_trip_error_px = Math.max(
170
+ round_trip_error_px,
171
+ Math.abs(back_px - px),
172
+ Math.abs(back_py - py)
173
+ );
174
+ }
175
+ }
176
+ }
177
+
178
+ return { side, side_flipped, round_trip_error_px, covered };
179
+ }
180
+
181
+ /**
182
+ * Owns the render target, the readback buffer, and the frame-driving loop.
183
+ *
184
+ * @param {object} context
185
+ * @param {Renderer} context.renderer
186
+ * @param {Scene} context.scene
187
+ * @param {PerspectiveCamera} context.camera
188
+ */
189
+ export function attach_measurement({ renderer, scene, camera }) {
190
+
191
+ const device = renderer.device;
192
+
193
+ /** @type {object|null} */
194
+ let capture = null;
195
+
196
+ function make_target() {
197
+ if (capture !== null) {
198
+ capture.texture.destroy();
199
+ capture.buffer.destroy();
200
+ }
201
+
202
+ const resolution = renderer.output_resolution;
203
+
204
+ const width = resolution.x;
205
+ const height = resolution.y;
206
+
207
+ // the rule the renderer's own context configuration follows
208
+ const format = window.matchMedia('(dynamic-range: high)').matches
209
+ ? 'rgba16float'
210
+ : navigator.gpu.getPreferredCanvasFormat();
211
+
212
+ const texture = device.createTexture({
213
+ label: 'ground_seam/capture',
214
+ size: [width, height, 1],
215
+ format,
216
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
217
+ | GPUTextureUsage.COPY_SRC
218
+ | GPUTextureUsage.COPY_DST
219
+ | GPUTextureUsage.TEXTURE_BINDING,
220
+ });
221
+
222
+ const bytes_per_pixel = format === 'rgba16float' ? 8 : 4;
223
+
224
+ // copyTextureToBuffer wants rows on a 256-byte boundary
225
+ const bytes_per_row = Math.ceil(width * bytes_per_pixel / 256) * 256;
226
+
227
+ capture = {
228
+ texture,
229
+ target: GPUTextureContext.fromTexture(texture, device),
230
+ width,
231
+ height,
232
+ format,
233
+ bytes_per_row,
234
+ buffer: device.createBuffer({
235
+ label: 'ground_seam/readback',
236
+ size: bytes_per_row * height,
237
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
238
+ }),
239
+ };
240
+
241
+ return capture;
242
+ }
243
+
244
+ function target() {
245
+ const resolution = renderer.output_resolution;
246
+
247
+ if (capture === null || capture.width !== resolution.x || capture.height !== resolution.y) {
248
+ make_target();
249
+ }
250
+
251
+ return capture;
252
+ }
253
+
254
+ /**
255
+ * One frame into the owned target.
256
+ * @param {number} [time_delta_s]
257
+ */
258
+ function frame(time_delta_s = 1 / 60) {
259
+ renderer.render_to_target(camera, scene, time_delta_s, target().target);
260
+ }
261
+
262
+ /**
263
+ * `n` frames with nothing read back, for letting the temporal filter converge.
264
+ * @param {number} n
265
+ */
266
+ async function settle(n) {
267
+ for (let i = 0; i < n; i++) {
268
+ frame();
269
+ }
270
+
271
+ await device.queue.onSubmittedWorkDone();
272
+ }
273
+
274
+ /**
275
+ * Per-pixel luminance of whatever is in the capture texture.
276
+ *
277
+ * @param {Float32Array} out length width*height
278
+ */
279
+ async function read_luminance(out) {
280
+ const c = target();
281
+
282
+ const encoder = device.createCommandEncoder({ label: 'ground_seam/readback' });
283
+
284
+ encoder.copyTextureToBuffer(
285
+ { texture: c.texture },
286
+ { buffer: c.buffer, bytesPerRow: c.bytes_per_row },
287
+ [c.width, c.height, 1]
288
+ );
289
+
290
+ device.queue.submit([encoder.finish()]);
291
+
292
+ await c.buffer.mapAsync(GPUMapMode.READ);
293
+
294
+ const raw = c.buffer.getMappedRange();
295
+
296
+ /*
297
+ The channel order is not cosmetic: the preferred canvas format on Windows is `bgra8unorm`,
298
+ and weighting it as if it were RGBA swaps the 0.2126 and 0.0722 coefficients. Still a usable
299
+ difference metric, but not the luminance it claims to be.
300
+ */
301
+ if (c.format === 'rgba16float') {
302
+ const source = new Uint16Array(raw);
303
+ const stride = c.bytes_per_row / 2;
304
+
305
+ for (let y = 0; y < c.height; y++) {
306
+ for (let x = 0; x < c.width; x++) {
307
+ const i = y * stride + x * 4;
308
+
309
+ out[y * c.width + x] = 0.2126 * half_to_float(source[i])
310
+ + 0.7152 * half_to_float(source[i + 1])
311
+ + 0.0722 * half_to_float(source[i + 2]);
312
+ }
313
+ }
314
+ } else {
315
+ const source = new Uint8Array(raw);
316
+ const stride = c.bytes_per_row;
317
+
318
+ const bgra = c.format.startsWith('bgra');
319
+
320
+ const r_offset = bgra ? 2 : 0;
321
+ const b_offset = bgra ? 0 : 2;
322
+
323
+ for (let y = 0; y < c.height; y++) {
324
+ for (let x = 0; x < c.width; x++) {
325
+ const i = y * stride + x * 4;
326
+
327
+ out[y * c.width + x] = (
328
+ 0.2126 * source[i + r_offset]
329
+ + 0.7152 * source[i + 1]
330
+ + 0.0722 * source[i + b_offset]
331
+ ) / 255;
332
+ }
333
+ }
334
+ }
335
+
336
+ c.buffer.unmap();
337
+ }
338
+
339
+ /**
340
+ * Drive `frames` frames with the camera perfectly still and record, per pixel, the spread of
341
+ * luminance across the burst.
342
+ *
343
+ * `max − min` rather than a variance: a pixel that alternates between two values every frame and
344
+ * a pixel that drifts once both matter, and the spread catches both without weighting by how
345
+ * many frames were spent at each value.
346
+ *
347
+ * @param {object} [options]
348
+ * @param {number} [options.settle_frames] frames to converge before recording
349
+ * @param {number} [options.frames] frames in the burst
350
+ * @returns {Promise<{spread: Float32Array, mean: Float32Array, width: number, height: number}>}
351
+ */
352
+ async function burst({ settle_frames = 240, frames = 16 } = {}) {
353
+
354
+ await settle(settle_frames);
355
+
356
+ const c = target();
357
+
358
+ const count = c.width * c.height;
359
+
360
+ const current = new Float32Array(count);
361
+ const min = new Float32Array(count).fill(Infinity);
362
+ const max = new Float32Array(count).fill(-Infinity);
363
+ const total = new Float64Array(count);
364
+
365
+ for (let i = 0; i < frames; i++) {
366
+ frame();
367
+
368
+ await read_luminance(current);
369
+
370
+ for (let p = 0; p < count; p++) {
371
+ const v = current[p];
372
+
373
+ if (v < min[p]) {
374
+ min[p] = v;
375
+ }
376
+
377
+ if (v > max[p]) {
378
+ max[p] = v;
379
+ }
380
+
381
+ total[p] += v;
382
+ }
383
+ }
384
+
385
+ const spread = new Float32Array(count);
386
+ const mean = new Float32Array(count);
387
+
388
+ for (let p = 0; p < count; p++) {
389
+ spread[p] = max[p] - min[p];
390
+ mean[p] = total[p] / frames;
391
+ }
392
+
393
+ return { spread, mean, width: c.width, height: c.height };
394
+ }
395
+
396
+ /** @type {object|null} */
397
+ let depth_readback = null;
398
+
399
+ /**
400
+ * The frame's depth buffer, as the rasterizer left it.
401
+ *
402
+ * `Renderer.texture_depth_current` is public and the texture already carries `COPY_SRC`, so
403
+ * this needs no shader and no graph hook — what comes back is the rasterizer's own output,
404
+ * unmodified by anything downstream.
405
+ *
406
+ * It is at the *internal* resolution (output × `internal_resolution_scale`), so a caller lining
407
+ * it up against a colour readback has to use the size reported here rather than assume the two
408
+ * agree.
409
+ *
410
+ * @returns {Promise<{depth: Float32Array, width: number, height: number}>}
411
+ */
412
+ async function read_depth() {
413
+ const texture = renderer.texture_depth_current;
414
+
415
+ const width = texture.width;
416
+ const height = texture.height;
417
+
418
+ const bytes_per_row = Math.ceil(width * 4 / 256) * 256;
419
+
420
+ if (depth_readback === null
421
+ || depth_readback.width !== width
422
+ || depth_readback.height !== height) {
423
+
424
+ if (depth_readback !== null) {
425
+ depth_readback.buffer.destroy();
426
+ }
427
+
428
+ depth_readback = {
429
+ width,
430
+ height,
431
+ bytes_per_row,
432
+ buffer: device.createBuffer({
433
+ label: 'ground_seam/depth_readback',
434
+ size: bytes_per_row * height,
435
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
436
+ }),
437
+ };
438
+ }
439
+
440
+ const encoder = device.createCommandEncoder({ label: 'ground_seam/depth' });
441
+
442
+ encoder.copyTextureToBuffer(
443
+ { texture: texture.gpu_texture, mipLevel: 0 },
444
+ { buffer: depth_readback.buffer, bytesPerRow: bytes_per_row },
445
+ [width, height, 1]
446
+ );
447
+
448
+ device.queue.submit([encoder.finish()]);
449
+
450
+ await depth_readback.buffer.mapAsync(GPUMapMode.READ);
451
+
452
+ const raw = new Float32Array(depth_readback.buffer.getMappedRange());
453
+
454
+ const depth = new Float32Array(width * height);
455
+
456
+ const stride = bytes_per_row / 4;
457
+
458
+ for (let y = 0; y < height; y++) {
459
+ depth.set(raw.subarray(y * stride, y * stride + width), y * width);
460
+ }
461
+
462
+ depth_readback.buffer.unmap();
463
+
464
+ return { depth, width, height };
465
+ }
466
+
467
+ return {
468
+ frame,
469
+ settle,
470
+ burst,
471
+ read_depth,
472
+ get capture() {
473
+ return target();
474
+ },
475
+ };
476
+ }
477
+
478
+ /**
479
+ * Where a world-space line on the ground plane lands on screen, as a signed-distance function.
480
+ *
481
+ * A projective map takes a straight line to a straight line, so two projected points are the whole
482
+ * of it. This exists so a profile can be taken *across* the seam in pixels: the half-plane means a
483
+ * split reports cannot tell a step at the boundary from a smooth gradient over the whole frame, and
484
+ * that is exactly the difference between "a seam" and "one half of the view is more oblique".
485
+ *
486
+ * @param {PerspectiveCamera} camera
487
+ * @param {number} width
488
+ * @param {number} height
489
+ * @param {number[]} direction unit direction of the line in the XZ plane
490
+ * @returns {function(number, number): number} pixel → signed perpendicular distance, in pixels
491
+ */
492
+ export function screen_line_distance(camera, width, height, direction) {
493
+
494
+ const m = camera.view_projection_matrix;
495
+
496
+ /**
497
+ * @param {number} x
498
+ * @param {number} z
499
+ * @returns {number[]} pixel coordinates
500
+ */
501
+ function project(x, z) {
502
+ const cx = m[0] * x + m[8] * z + m[12];
503
+ const cy = m[1] * x + m[9] * z + m[13];
504
+ const cw = m[3] * x + m[11] * z + m[15];
505
+
506
+ return [
507
+ (cx / cw * 0.5 + 0.5) * width - 0.5,
508
+ (0.5 - cy / cw * 0.5) * height - 0.5,
509
+ ];
510
+ }
511
+
512
+ /*
513
+ Two points on the line, both well inside the frustum. Taking them near the camera rather than
514
+ at the quad's corners keeps them away from the eye plane, where a projected point's magnitude
515
+ explodes and the difference of two large numbers is all rounding.
516
+ */
517
+ const a = project(direction[0] * 5, direction[1] * 5);
518
+ const b = project(direction[0] * -5, direction[1] * -5);
519
+
520
+ const dx = b[0] - a[0];
521
+ const dy = b[1] - a[1];
522
+
523
+ const length = Math.hypot(dx, dy);
524
+
525
+ if (!(length > 1e-6)) {
526
+ throw new Error('the seam projects to a point — the camera is looking along it');
527
+ }
528
+
529
+ // 2D cross product with the line direction gives the signed perpendicular offset
530
+ return (px, py) => ((px - a[0]) * dy - (py - a[1]) * dx) / length;
531
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"Renderer.d.ts","sourceRoot":"","sources":["../../../../src/shade/renderer/Renderer.js"],"names":[],"mappings":"AA4FA;IAOI,0BAEC;IAED;;OAEG;IACH,0BAAQ;IAER;;;;;;OAMG;IACH,kDAEC;IAED;;OAEG;IACH,kBAAO;IAYP;;;OAGG;IACH,2BAEC;IAaD,0CAGC;IAED,uCAEC;IAED;;;;OAIG;IACH,wBAFU,yBAAyB,CAEoB;IAEvD,6BAA4B;IAC5B,8BAA4B;IAC5B;;;;OAIG;IACH,mCAAkC;IAClC;;;;;;;OAOG;IACH,mCAFU,OAAO,CAEyB;IAC1C,+BAA6B;IAC7B,4CAA0C;IAkB1C,uCAKC;IArBD;;;;;;;;;;OAUG;IACH,oCAGC;IASD;;;;;;;OAOG;IACH,0BAFU,OAAO,CAEe;IAQhC;;;;;OAKG;IACH,sCAOC;IAjBD,mCAEC;IAiBD;;;;;;;;;OASG;IACH,6BAFU,OAAO,CAEmB;IAWpC;;;;;OAKG;IACH,8BAA+C;IAE/C;;;;;;;;OAQG;IACH,6BAFU,OAAO,CAEmB;IAEpC;;;;;;;;;;;;;;OAcG;IACH,8BAFU,OAAO,CAEoB;IAErC;;;;;;;;;;OAUG;IACH,qBAFU,OAAO,CAEW;IAU5B;;;;;OAKG;IACH,wBAA0C;IAE1C;;;;;;;;OAQG;IACH,eAFU,UAAU,GAAC,QAAQ,CAEF;IAU3B;;;;OAIG;IACH,gCAAwD;IAExD;;;;;;;;;;;;OAYG;IACH,gBAFU,OAAO,CAEK;IAEtB;;;OAGG;IACH,4BAFU,OAAO,CAEiB;IAKlC;;;OAGG;IACH,yBAEC;IAOD;;;OAGG;IACH,gCAEC;IAcD;;;;;;;;;OASG;IACH,qBAA+B;IAE/B,wBAA+B;IAgB/B;;;OAGG;IACH,2CAeC;IAED;;;OAGG;IACH,wCAEC;IA0BD;;;OAGG;IACH,iCAEC;IAED;;;;OAIG;IACH,2BAEC;IAQD;;;OAGG;IACH,+CAIC;IAED;;;OAGG;IACH,gDAIC;IAwCD;;;OAGG;IACH,6BAcC;IA1BD;;;OAGG;IACH,0BAEC;IAsBD;;;;;;;;;;;;;;;;;;;OAmBG;IACH,iBAFU,oBAAkB,IAAI,CAET;IAEvB;;OAEG;IACH;;;;;;;;;;;;;;OAcG;IACH,2DAEC;IAED;;;;;OAKG;IACH,8CAFa,OAAO,CAInB;IAED;;;OAGG;IACH,uBAHW,UAAU,GACR,MAAM,CAIlB;IAgBD;;;;OAIG;IACH,iCAFY,gBAAgB,CAW3B;IAED;;;OAGG;IACH,6BAYC;IAED;;;;OAIG;IACH,wCAFa,2BAA2B,CAcvC;IAGD;;;OAGG;IACH,+BAuBC;IAmMD;;;;OAIG;IACH,0BAEC;IAgBD;;;;;;OAMG;IACH,cAFU,iBAAiB,CAEU;IAErC;;;;;;OAMG;IACH,eAgBC;IAiFD;;;;;OAKG;IACH,+EA0NC;IAED;;OAEG;IACH,gBAGC;IAQD,4BAwBC;IAqID;;;;;OAKG;IACH,UAHW,MAAM,KACN,MAAM,QAgBhB;IAoFD,0CAMC;IAED;;;;;;;;;;OAUG;IACH,qEAHW,MAAM,GACJ,OAAO,CAsBnB;IAED;;;;;;;;;;;;;;;OAeG;IACH,8EAJW,MAAM,UACN,iBAAiB,GACf,OAAO,CAu2BnB;;CACJ;6BA74E4B,yBAAyB;0CACZ,gCAAgC;2BApB/C,yCAAyC;6BAFvC,4CAA4C;8BAD3C,2CAA2C;4BAiC7C,uBAAuB;gCAtCnB,sBAAsB;mBArCnC,oCAAoC;oBACnC,4BAA4B;kCAuEd,gCAAgC;2BApDvC,2BAA2B;iCAkDrB,0CAA0C;4CApC/B,+DAA+D;yBA6BlF,yBAAyB;kCAMhB,wBAAwB;oBAvBtC,0BAA0B;uCAPP,sDAAsD"}
1
+ {"version":3,"file":"Renderer.d.ts","sourceRoot":"","sources":["../../../../src/shade/renderer/Renderer.js"],"names":[],"mappings":"AA4FA;IAOI,0BAEC;IAED;;OAEG;IACH,0BAAQ;IAER;;;;;;OAMG;IACH,kDAEC;IAED;;OAEG;IACH,kBAAO;IAYP;;;OAGG;IACH,2BAEC;IAaD,0CAGC;IAED,uCAEC;IAED;;;;OAIG;IACH,wBAFU,yBAAyB,CAEoB;IAEvD,6BAA4B;IAC5B,8BAA4B;IAC5B;;;;OAIG;IACH,mCAAkC;IAClC;;;;;;;OAOG;IACH,mCAFU,OAAO,CAEyB;IAC1C,+BAA6B;IAC7B,4CAA0C;IAkB1C,uCAKC;IArBD;;;;;;;;;;OAUG;IACH,oCAGC;IASD;;;;;;;OAOG;IACH,0BAFU,OAAO,CAEe;IAQhC;;;;;OAKG;IACH,sCAOC;IAjBD,mCAEC;IAiBD;;;;;;;;;OASG;IACH,6BAFU,OAAO,CAEmB;IAWpC;;;;;OAKG;IACH,8BAA+C;IAE/C;;;;;;;;OAQG;IACH,6BAFU,OAAO,CAEmB;IAEpC;;;;;;;;;;;;;;OAcG;IACH,8BAFU,OAAO,CAEoB;IAErC;;;;;;;;;;OAUG;IACH,qBAFU,OAAO,CAEW;IAU5B;;;;;OAKG;IACH,wBAA0C;IAE1C;;;;;;;;OAQG;IACH,eAFU,UAAU,GAAC,QAAQ,CAEF;IAU3B;;;;OAIG;IACH,gCAAwD;IAExD;;;;;;;;;;;;OAYG;IACH,gBAFU,OAAO,CAEK;IAEtB;;;OAGG;IACH,4BAFU,OAAO,CAEiB;IAKlC;;;OAGG;IACH,yBAEC;IAOD;;;OAGG;IACH,gCAEC;IAcD;;;;;;;;;OASG;IACH,qBAA+B;IAE/B,wBAA+B;IAgB/B;;;OAGG;IACH,2CAeC;IAED;;;OAGG;IACH,wCAEC;IA0BD;;;OAGG;IACH,iCAEC;IAED;;;;OAIG;IACH,2BAEC;IAQD;;;OAGG;IACH,+CAIC;IAED;;;OAGG;IACH,gDAIC;IAwCD;;;OAGG;IACH,6BAcC;IA1BD;;;OAGG;IACH,0BAEC;IAsBD;;;;;;;;;;;;;;;;;;;OAmBG;IACH,iBAFU,oBAAkB,IAAI,CAET;IAEvB;;OAEG;IACH;;;;;;;;;;;;;;OAcG;IACH,2DAEC;IAED;;;;;OAKG;IACH,8CAFa,OAAO,CAInB;IAED;;;OAGG;IACH,uBAHW,UAAU,GACR,MAAM,CAIlB;IAgBD;;;;OAIG;IACH,iCAFY,gBAAgB,CAW3B;IAED;;;OAGG;IACH,6BAYC;IAED;;;;OAIG;IACH,wCAFa,2BAA2B,CAcvC;IAGD;;;OAGG;IACH,+BAuBC;IAmMD;;;;OAIG;IACH,0BAEC;IAgBD;;;;;;OAMG;IACH,cAFU,iBAAiB,CAEU;IAErC;;;;;;OAMG;IACH,eAgBC;IAiFD;;;;;OAKG;IACH,+EAkOC;IAED;;OAEG;IACH,gBAGC;IAQD,4BAwBC;IAqID;;;;;OAKG;IACH,UAHW,MAAM,KACN,MAAM,QAgBhB;IAoFD,0CAMC;IAED;;;;;;;;;;OAUG;IACH,qEAHW,MAAM,GACJ,OAAO,CAsBnB;IAED;;;;;;;;;;;;;;;OAeG;IACH,8EAJW,MAAM,UACN,iBAAiB,GACf,OAAO,CAu2BnB;;CACJ;6BAr5E4B,yBAAyB;0CACZ,gCAAgC;2BApB/C,yCAAyC;6BAFvC,4CAA4C;8BAD3C,2CAA2C;4BAiC7C,uBAAuB;gCAtCnB,sBAAsB;mBArCnC,oCAAoC;oBACnC,4BAA4B;kCAuEd,gCAAgC;2BApDvC,2BAA2B;iCAkDrB,0CAA0C;4CApC/B,+DAA+D;yBA6BlF,yBAAyB;kCAMhB,wBAAwB;oBAvBtC,0BAA0B;uCAPP,sDAAsD"}