@woosh/meep-engine 3.14.0 → 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 +33 -64
  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,496 @@
1
+ import { etc1s_block_colors } from "../etc1s/etc1s_block_colors.js";
2
+ import { build_range_plan } from "./build_range_plan.js";
3
+ import { fit_endpoints, SELECTOR_RANGES } from "./fit_endpoints.js";
4
+
5
+ /**
6
+ * Bytes one BC7 block occupies.
7
+ *
8
+ * @readonly
9
+ * @type {number}
10
+ */
11
+ const BC7_BLOCK_BYTES = 16;
12
+
13
+ /**
14
+ * BC7's interpolation weights for four-bit indices, out of 64.
15
+ *
16
+ * @readonly
17
+ * @type {Int32Array}
18
+ */
19
+ const WEIGHT4 = new Int32Array([0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64]);
20
+
21
+ /**
22
+ * BC7's interpolation weights for two-bit indices, out of 64.
23
+ *
24
+ * @readonly
25
+ * @type {Int32Array}
26
+ */
27
+ const WEIGHT2 = new Int32Array([0, 21, 43, 64]);
28
+
29
+ /**
30
+ * @param {number} a endpoint, eight bits
31
+ * @param {number} b endpoint, eight bits
32
+ * @param {number} weight out of 64
33
+ * @returns {number} the interpolated channel, as BC7 defines it
34
+ */
35
+ function interpolate(a, b, weight) {
36
+ return (a * (64 - weight) + b * weight + 32) >> 6;
37
+ }
38
+
39
+ /**
40
+ * The eight-bit value a seven-bit endpoint decodes to, by BC7's bit replication.
41
+ *
42
+ * @param {number} value seven bits
43
+ * @returns {number}
44
+ */
45
+ function replicate7(value) {
46
+ return ((value << 1) | (value >> 6)) & 255;
47
+ }
48
+
49
+ /**
50
+ * A little-endian bit accumulator over one block's sixteen bytes.
51
+ *
52
+ * BC7 fields are specified least significant bit first from byte zero, so writing them in the order
53
+ * the specification lists them is exactly writing the block.
54
+ */
55
+ class Bc7BlockWriter {
56
+ /**
57
+ * @type {Uint8Array}
58
+ */
59
+ #out = null;
60
+
61
+ /**
62
+ * Bit position within {@link #out}, not within the block.
63
+ * @type {number}
64
+ */
65
+ #bit = 0;
66
+
67
+ /**
68
+ * @param {Uint8Array} out
69
+ * @param {number} byte_offset of the block
70
+ * @returns {Bc7BlockWriter} this
71
+ */
72
+ init(out, byte_offset) {
73
+ this.#out = out;
74
+ this.#bit = byte_offset * 8;
75
+
76
+ return this;
77
+ }
78
+
79
+ /**
80
+ * @param {number} value
81
+ * @param {number} count bits, at most 8
82
+ */
83
+ write(value, count) {
84
+ for (let i = 0; i < count; i++) {
85
+ if (((value >> i) & 1) !== 0) {
86
+ this.#out[(this.#bit + i) >> 3] |= 1 << ((this.#bit + i) & 7);
87
+ }
88
+ }
89
+
90
+ this.#bit += count;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Transcode a decoded ETC1S slice to BC7.
96
+ *
97
+ * **Two modes, chosen by whether the payload carries alpha, because BC7 cannot do both at once.**
98
+ * Mode 6 gives one index per texel, shared by all four channels, with four bits of it; mode 5 gives
99
+ * colour and alpha their own indices but only two bits each. An opaque payload wants the finer
100
+ * index; an alpha payload needs the independent one, because an ETC1S file's alpha slice has its own
101
+ * selectors, unrelated to the colour slice's, and a shared index could not express both.
102
+ *
103
+ * **The work is per codebook pairing, not per block.** What a block decodes to depends only on which
104
+ * endpoint entry and which selector entry it names, so endpoints are fitted once per (endpoint
105
+ * entry, selector span) pairing — see {@link fit_endpoints} for why the span is part of the key —
106
+ * and every block naming that pairing just writes the result out. Colour and alpha are tabulated
107
+ * separately, which is what lets mode 5 pair any colour entry with any alpha entry without a table
108
+ * of their product.
109
+ *
110
+ * @param {Etc1sSlice} slice the colour slice
111
+ * @param {Etc1sSlice|undefined} alpha_slice the alpha slice, when the payload has one
112
+ * @param {Etc1sEndpointCodebook} endpoints
113
+ * @param {Etc1sSelectorCodebook} selectors
114
+ * @returns {Uint8Array} tightly packed BC7 blocks, in raster order
115
+ *
116
+ * @author Alex Goldring
117
+ * @copyright Company Named Limited (c) 2026
118
+ */
119
+ export function etc1s_to_bc7(slice, alpha_slice, endpoints, selectors) {
120
+ return alpha_slice === undefined
121
+ ? to_mode6(slice, endpoints, selectors)
122
+ : to_mode5(slice, alpha_slice, endpoints, selectors);
123
+ }
124
+
125
+ /**
126
+ * The opaque path: one four-bit index per texel, alpha pinned opaque.
127
+ *
128
+ * @param {Etc1sSlice} slice
129
+ * @param {Etc1sEndpointCodebook} endpoints
130
+ * @param {Etc1sSelectorCodebook} selectors
131
+ * @returns {Uint8Array}
132
+ */
133
+ function to_mode6(slice, endpoints, selectors) {
134
+ const plan = build_range_plan(selectors);
135
+
136
+ const templates = build_color_templates(endpoints, plan, WEIGHT4, quantise_mode6);
137
+
138
+ const block_count = slice.blocks_x * slice.blocks_y;
139
+
140
+ const out = new Uint8Array(block_count * BC7_BLOCK_BYTES);
141
+
142
+ const writer = new Bc7BlockWriter();
143
+
144
+ const indices = new Uint8Array(16);
145
+
146
+ for (let block = 0; block < block_count; block++) {
147
+ const selector_entry = slice.selector_index[block];
148
+ const selector_base = selector_entry * 16;
149
+
150
+ const pairing = slice.endpoint_index[block] * plan.slot_count
151
+ + plan.slot_of_entry[selector_entry];
152
+
153
+ const e = pairing * 3;
154
+
155
+ const flipped = fill_indices(
156
+ indices, templates.selector_to_index, pairing, selectors, selector_base, WEIGHT4.length
157
+ );
158
+
159
+ writer.init(out, block * BC7_BLOCK_BYTES);
160
+
161
+ // mode 6 is signalled by six zero bits then a one
162
+ writer.write(0x40, 7);
163
+
164
+ /*
165
+ The endpoints go out in whichever order {@link fill_indices} settled on. Swapping them and
166
+ inverting every index leaves the decoded block identical — BC7's weight tables are symmetric
167
+ about their midpoint — which is what makes the anchor rule satisfiable without re-fitting.
168
+ */
169
+ const first = flipped ? templates.high : templates.low;
170
+ const second = flipped ? templates.low : templates.high;
171
+
172
+ writer.write(first[e], 7);
173
+ writer.write(second[e], 7);
174
+ writer.write(first[e + 1], 7);
175
+ writer.write(second[e + 1], 7);
176
+ writer.write(first[e + 2], 7);
177
+ writer.write(second[e + 2], 7);
178
+
179
+ /*
180
+ Alpha, opaque. Both endpoints are seven ones and both parity bits are one, so each decodes
181
+ to exactly 255 — which is why the parity is fixed rather than fitted: an endpoint's parity
182
+ bit is shared with its alpha channel, and an opaque texture that decoded to 254 would be a
183
+ texture with alpha nobody asked for. It costs each colour channel one step out of 255.
184
+ */
185
+ writer.write(0x7F, 7);
186
+ writer.write(0x7F, 7);
187
+
188
+ writer.write(1, 1);
189
+ writer.write(1, 1);
190
+
191
+ for (let texel = 0; texel < 16; texel++) {
192
+ writer.write(indices[texel], texel === 0 ? 3 : 4);
193
+ }
194
+ }
195
+
196
+ return out;
197
+ }
198
+
199
+ /**
200
+ * The alpha path: two-bit colour and alpha indices, each with their own endpoints.
201
+ *
202
+ * @param {Etc1sSlice} slice
203
+ * @param {Etc1sSlice} alpha_slice
204
+ * @param {Etc1sEndpointCodebook} endpoints
205
+ * @param {Etc1sSelectorCodebook} selectors
206
+ * @returns {Uint8Array}
207
+ */
208
+ function to_mode5(slice, alpha_slice, endpoints, selectors) {
209
+ const plan = build_range_plan(selectors);
210
+
211
+ const color = build_color_templates(endpoints, plan, WEIGHT2, quantise_mode5);
212
+ const alpha = build_alpha_templates(endpoints, plan);
213
+
214
+ const block_count = slice.blocks_x * slice.blocks_y;
215
+
216
+ const out = new Uint8Array(block_count * BC7_BLOCK_BYTES);
217
+
218
+ const writer = new Bc7BlockWriter();
219
+
220
+ const color_indices = new Uint8Array(16);
221
+ const alpha_indices = new Uint8Array(16);
222
+
223
+ for (let block = 0; block < block_count; block++) {
224
+ const selector_entry = slice.selector_index[block];
225
+ const selector_base = selector_entry * 16;
226
+
227
+ const alpha_selector_entry = alpha_slice.selector_index[block];
228
+ const alpha_selector_base = alpha_selector_entry * 16;
229
+
230
+ const pairing = slice.endpoint_index[block] * plan.slot_count
231
+ + plan.slot_of_entry[selector_entry];
232
+
233
+ const alpha_pairing = alpha_slice.endpoint_index[block] * plan.slot_count
234
+ + plan.slot_of_entry[alpha_selector_entry];
235
+
236
+ const e = pairing * 3;
237
+
238
+ /*
239
+ Mode 5 keeps two index sets and each has its own anchor, so each is normalised on its own —
240
+ which is exactly why the colour and alpha endpoints had to stay independent.
241
+ */
242
+ const color_flipped = fill_indices(
243
+ color_indices, color.selector_to_index, pairing, selectors, selector_base,
244
+ WEIGHT2.length
245
+ );
246
+
247
+ const alpha_flipped = fill_indices(
248
+ alpha_indices, alpha.selector_to_index, alpha_pairing, selectors, alpha_selector_base,
249
+ WEIGHT2.length
250
+ );
251
+
252
+ writer.init(out, block * BC7_BLOCK_BYTES);
253
+
254
+ // mode 5 is signalled by five zero bits then a one, followed by a two-bit channel rotation
255
+ writer.write(0x20, 6);
256
+ writer.write(0, 2);
257
+
258
+ const first = color_flipped ? color.high : color.low;
259
+ const second = color_flipped ? color.low : color.high;
260
+
261
+ writer.write(first[e], 7);
262
+ writer.write(second[e], 7);
263
+ writer.write(first[e + 1], 7);
264
+ writer.write(second[e + 1], 7);
265
+ writer.write(first[e + 2], 7);
266
+ writer.write(second[e + 2], 7);
267
+
268
+ writer.write(alpha_flipped ? alpha.high[alpha_pairing] : alpha.low[alpha_pairing], 8);
269
+ writer.write(alpha_flipped ? alpha.low[alpha_pairing] : alpha.high[alpha_pairing], 8);
270
+
271
+ for (let texel = 0; texel < 16; texel++) {
272
+ writer.write(color_indices[texel], texel === 0 ? 1 : 2);
273
+ }
274
+
275
+ for (let texel = 0; texel < 16; texel++) {
276
+ writer.write(alpha_indices[texel], texel === 0 ? 1 : 2);
277
+ }
278
+ }
279
+
280
+ return out;
281
+ }
282
+
283
+ /**
284
+ * Quantise a fitted eight-bit channel the way mode 6 stores it, with the parity bit fixed at one.
285
+ *
286
+ * @param {number} value
287
+ * @returns {{stored: number, decoded: number}}
288
+ */
289
+ function quantise_mode6(value) {
290
+ const decoded = value | 1;
291
+
292
+ return { stored: decoded >> 1, decoded };
293
+ }
294
+
295
+ /**
296
+ * Quantise a fitted eight-bit channel the way mode 5 stores a colour endpoint: seven bits, decoding
297
+ * by replication rather than by an appended parity bit.
298
+ *
299
+ * @param {number} value
300
+ * @returns {{stored: number, decoded: number}}
301
+ */
302
+ function quantise_mode5(value) {
303
+ const stored = Math.min(127, (value * 127 + 127) / 255 | 0);
304
+
305
+ return { stored, decoded: replicate7(stored) };
306
+ }
307
+
308
+ /**
309
+ * Fitted, quantised endpoints and a selector mapping for every (endpoint entry, selector span)
310
+ * pairing.
311
+ *
312
+ * @param {Etc1sEndpointCodebook} endpoints
313
+ * @param {{slot_count: number, range_of_slot: Uint8Array}} plan
314
+ * @param {Int32Array} weights
315
+ * @param {function(number): {stored: number, decoded: number}} quantise
316
+ * @returns {{low: Uint8Array, high: Uint8Array, selector_to_index: Uint8Array}}
317
+ */
318
+ function build_color_templates(endpoints, plan, weights, quantise) {
319
+ const pairings = endpoints.count * plan.slot_count;
320
+
321
+ const low = new Uint8Array(pairings * 3);
322
+ const high = new Uint8Array(pairings * 3);
323
+ const selector_to_index = new Uint8Array(pairings * 4);
324
+
325
+ const colors = new Uint8Array(12);
326
+ const fitted_low = new Uint8Array(3);
327
+ const fitted_high = new Uint8Array(3);
328
+ const decoded_low = new Uint8Array(3);
329
+ const decoded_high = new Uint8Array(3);
330
+
331
+ for (let entry = 0; entry < endpoints.count; entry++) {
332
+ etc1s_block_colors(colors, 0, endpoints.color5, endpoints.intensity, entry);
333
+
334
+ for (let slot = 0; slot < plan.slot_count; slot++) {
335
+ const [min, max] = SELECTOR_RANGES[plan.range_of_slot[slot]];
336
+
337
+ for (let c = 0; c < 3; c++) {
338
+ fitted_low[c] = colors[min * 3 + c];
339
+ fitted_high[c] = colors[max * 3 + c];
340
+ }
341
+
342
+ fit_endpoints(fitted_low, fitted_high, colors, weights, min, max);
343
+
344
+ const pairing = entry * plan.slot_count + slot;
345
+ const e = pairing * 3;
346
+
347
+ for (let c = 0; c < 3; c++) {
348
+ const a = quantise(fitted_low[c]);
349
+ const b = quantise(fitted_high[c]);
350
+
351
+ low[e + c] = a.stored;
352
+ high[e + c] = b.stored;
353
+
354
+ decoded_low[c] = a.decoded;
355
+ decoded_high[c] = b.decoded;
356
+ }
357
+
358
+ /*
359
+ Assigned against the endpoints as they will *decode*, not as they were fitted.
360
+ Quantisation moves them, and an index chosen against the unquantised pair would be a
361
+ step off wherever that move mattered.
362
+ */
363
+ for (let selector = 0; selector < 4; selector++) {
364
+ selector_to_index[pairing * 4 + selector] = closest_weight(
365
+ decoded_low, decoded_high, weights, colors, selector * 3
366
+ );
367
+ }
368
+ }
369
+ }
370
+
371
+ return { low, high, selector_to_index };
372
+ }
373
+
374
+ /**
375
+ * The same, read as alpha.
376
+ *
377
+ * The alpha slice is an ordinary ETC1S image whose green channel carries the alpha, which is what
378
+ * the specification says of it — so the same codebook is read and one channel of it is kept. Mode 5
379
+ * gives alpha a full eight bits, so no quantisation happens and the endpoints of a two-value span
380
+ * are exact.
381
+ *
382
+ * @param {Etc1sEndpointCodebook} endpoints
383
+ * @param {{slot_count: number, range_of_slot: Uint8Array}} plan
384
+ * @returns {{low: Uint8Array, high: Uint8Array, selector_to_index: Uint8Array}}
385
+ */
386
+ function build_alpha_templates(endpoints, plan) {
387
+ const pairings = endpoints.count * plan.slot_count;
388
+
389
+ const low = new Uint8Array(pairings);
390
+ const high = new Uint8Array(pairings);
391
+ const selector_to_index = new Uint8Array(pairings * 4);
392
+
393
+ const colors = new Uint8Array(12);
394
+
395
+ for (let entry = 0; entry < endpoints.count; entry++) {
396
+ etc1s_block_colors(colors, 0, endpoints.color5, endpoints.intensity, entry);
397
+
398
+ for (let slot = 0; slot < plan.slot_count; slot++) {
399
+ const [min, max] = SELECTOR_RANGES[plan.range_of_slot[slot]];
400
+
401
+ const pairing = entry * plan.slot_count + slot;
402
+
403
+ const a = colors[min * 3 + 1];
404
+ const b = colors[max * 3 + 1];
405
+
406
+ low[pairing] = a;
407
+ high[pairing] = b;
408
+
409
+ for (let selector = 0; selector < 4; selector++) {
410
+ const target = colors[selector * 3 + 1];
411
+
412
+ let best = 0;
413
+ let best_error = Infinity;
414
+
415
+ for (let i = 0; i < WEIGHT2.length; i++) {
416
+ const error = Math.abs(interpolate(a, b, WEIGHT2[i]) - target);
417
+
418
+ if (error < best_error) {
419
+ best_error = error;
420
+ best = i;
421
+ }
422
+ }
423
+
424
+ selector_to_index[pairing * 4 + selector] = best;
425
+ }
426
+ }
427
+ }
428
+
429
+ return { low, high, selector_to_index };
430
+ }
431
+
432
+ /**
433
+ * Fill one block's indices, flipping them if the first texel's would not fit the anchor.
434
+ *
435
+ * **BC7 stores the first texel's index one bit short**, because a block is required to be written so
436
+ * that index's most significant bit is zero. Nothing about an ETC1S block guarantees that — the
437
+ * first texel is whatever it is — so a block whose first index is in the upper half is rewritten
438
+ * against the endpoints in the other order, which inverts every index and lands the first one in the
439
+ * lower half. The decoded result is unchanged, since BC7's weight tables are symmetric about their
440
+ * midpoint.
441
+ *
442
+ * @param {Uint8Array} out sixteen indices
443
+ * @param {Uint8Array} selector_to_index the pairing's mapping
444
+ * @param {number} pairing
445
+ * @param {Etc1sSelectorCodebook} selectors
446
+ * @param {number} selector_base into `selectors.selectors`
447
+ * @param {number} index_count how many index values the mode has
448
+ * @returns {boolean} whether the endpoints must be written in the other order
449
+ */
450
+ function fill_indices(out, selector_to_index, pairing, selectors, selector_base, index_count) {
451
+ const map = pairing * 4;
452
+
453
+ const first = selector_to_index[map + selectors.selectors[selector_base]];
454
+
455
+ const flipped = first >= index_count / 2;
456
+
457
+ for (let texel = 0; texel < 16; texel++) {
458
+ const index = selector_to_index[map + selectors.selectors[selector_base + texel]];
459
+
460
+ out[texel] = flipped ? index_count - 1 - index : index;
461
+ }
462
+
463
+ return flipped;
464
+ }
465
+
466
+ /**
467
+ * Which interpolation weight best reproduces a target colour between two endpoints.
468
+ *
469
+ * @param {Uint8Array} a three channels, as they decode
470
+ * @param {Uint8Array} b three channels, as they decode
471
+ * @param {Int32Array} weights
472
+ * @param {Uint8Array} target
473
+ * @param {number} target_offset
474
+ * @returns {number} an index into `weights`
475
+ */
476
+ function closest_weight(a, b, weights, target, target_offset) {
477
+ let best = 0;
478
+ let best_error = Infinity;
479
+
480
+ for (let i = 0; i < weights.length; i++) {
481
+ let error = 0;
482
+
483
+ for (let c = 0; c < 3; c++) {
484
+ const delta = interpolate(a[c], b[c], weights[i]) - target[target_offset + c];
485
+
486
+ error += delta * delta;
487
+ }
488
+
489
+ if (error < best_error) {
490
+ best_error = error;
491
+ best = i;
492
+ }
493
+ }
494
+
495
+ return best;
496
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Which entry of {@link SELECTOR_RANGES} a `[min, max]` span is.
3
+ *
4
+ * @param {number} min
5
+ * @param {number} max
6
+ * @returns {number}
7
+ */
8
+ export function selector_range_index(min: number, max: number): number;
9
+ /**
10
+ * Fit a pair of endpoints to the colours a block actually uses, by least squares.
11
+ *
12
+ * **Why the obvious choice is not good enough.** An ETC1S block's four colours are its base colour
13
+ * plus a modifier applied equally to every channel, so before clamping they lie on a line and the
14
+ * darkest and brightest of them span it exactly. Clamping breaks that. A base colour near white with
15
+ * a large modifier gives, say, red `{67, 203, 255, 255}` beside green `{0, 0, 57, 193}` — still
16
+ * ordered, but no longer collinear, because red saturated and green did not. Taking the outer two as
17
+ * endpoints then leaves the middle two nowhere near the line between them, and no interpolation
18
+ * weight can recover them.
19
+ *
20
+ * So the endpoints are fitted rather than read off: each colour is assigned the weight that suits it
21
+ * best, the endpoints are re-solved to minimise squared error against all of them at once, and the
22
+ * two steps alternate.
23
+ *
24
+ * **And the fit is over `[min_selector, max_selector]` only.** Which colours a block uses is a
25
+ * property of its *selector* codebook entry, and most entries do not use all four — a block spanning
26
+ * only the darkest two is a line those two define exactly, and fitting it against colours it never
27
+ * samples throws that away for nothing. Conditioning on the span is what closes most of the distance
28
+ * to the reference transcoder on saturated content.
29
+ *
30
+ * @param {Uint8Array} low three channels, seeded with a guess and overwritten with the fit
31
+ * @param {Uint8Array} high three channels, likewise
32
+ * @param {Uint8Array} colors twelve bytes, the block's four `[r, g, b]` colours
33
+ * @param {Int32Array} weights the destination format's interpolation weights, out of 64
34
+ * @param {number} min_selector lowest selector value any block using this pairing has
35
+ * @param {number} max_selector highest
36
+ *
37
+ * @author Alex Goldring
38
+ * @copyright Company Named Limited (c) 2026
39
+ */
40
+ export function fit_endpoints(low: Uint8Array, high: Uint8Array, colors: Uint8Array, weights: Int32Array, min_selector: number, max_selector: number): void;
41
+ /**
42
+ * The `[min, max]` selector span of every entry of a selector codebook.
43
+ *
44
+ * @param {Etc1sSelectorCodebook} selectors
45
+ * @returns {Uint8Array} one index into {@link SELECTOR_RANGES} per entry
46
+ */
47
+ export function selector_codebook_ranges(selectors: Etc1sSelectorCodebook): Uint8Array;
48
+ /**
49
+ * Every `[min, max]` span of selector values a block can use, as pairs.
50
+ *
51
+ * Ten of them, because a span is an ordered pair drawn from four values. A block that only uses the
52
+ * darkest two colours is fitted against those two alone, which is the whole reason this exists — see
53
+ * {@link fit_endpoints}.
54
+ *
55
+ * @readonly
56
+ * @type {number[][]}
57
+ */
58
+ export const SELECTOR_RANGES: number[][];
59
+ //# sourceMappingURL=fit_endpoints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fit_endpoints.d.ts","sourceRoot":"","sources":["../../../../src/basis/transcode/fit_endpoints.js"],"names":[],"mappings":"AA2BA;;;;;;GAMG;AACH,0CAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAUlB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,mCAVW,UAAU,QACV,UAAU,UACV,UAAU,WACV,UAAU,gBACV,MAAM,gBACN,MAAM,QA+EhB;AA+CD;;;;;GAKG;AACH,4EAFa,UAAU,CAyBtB;AAvND;;;;;;;;;GASG;AACH,8BAFU,MAAM,EAAE,EAAE,CAOlB"}