@woosh/meep-engine 3.63.2 → 3.64.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/package.json +1 -1
  3. package/src/core/color/xyz/m3_chromatic_adaptation_bradford.d.ts +14 -0
  4. package/src/core/color/xyz/m3_chromatic_adaptation_bradford.d.ts.map +1 -0
  5. package/src/core/color/xyz/m3_chromatic_adaptation_bradford.js +51 -0
  6. package/src/core/math/linalg/eigen/matrix_symmetric_3x3_eigen.d.ts +2 -1
  7. package/src/core/math/linalg/eigen/matrix_symmetric_3x3_eigen.d.ts.map +1 -1
  8. package/src/core/math/linalg/eigen/matrix_symmetric_3x3_eigen.js +51 -70
  9. package/src/core/math/linalg/eigen/matrix_symmetric_4x4_eigen.d.ts.map +1 -1
  10. package/src/core/math/linalg/eigen/matrix_symmetric_4x4_eigen.js +8 -64
  11. package/src/core/math/linalg/eigen/matrix_symmetric_jacobi_rotate_in_place.d.ts +17 -0
  12. package/src/core/math/linalg/eigen/matrix_symmetric_jacobi_rotate_in_place.d.ts.map +1 -0
  13. package/src/core/math/linalg/eigen/matrix_symmetric_jacobi_rotate_in_place.js +64 -0
  14. package/src/core/math/linalg/solve_box_constrained_qp.d.ts +30 -0
  15. package/src/core/math/linalg/solve_box_constrained_qp.d.ts.map +1 -0
  16. package/src/core/math/linalg/solve_box_constrained_qp.js +205 -0
  17. package/src/engine/physics/constraint/soft_spring_coefficients.d.ts +24 -0
  18. package/src/engine/physics/constraint/soft_spring_coefficients.d.ts.map +1 -0
  19. package/src/engine/physics/constraint/soft_spring_coefficients.js +38 -0
  20. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
  21. package/src/engine/physics/constraint/solve_constraints.js +16 -8
  22. package/src/engine/physics/ecs/PhysicsSystem.d.ts +54 -0
  23. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  24. package/src/engine/physics/ecs/PhysicsSystem.js +192 -7
  25. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  26. package/src/engine/physics/inertia/world_inverse_inertia.d.ts +3 -5
  27. package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
  28. package/src/engine/physics/inertia/world_inverse_inertia.js +3 -5
  29. package/src/engine/physics/vehicle/RaycastVehicle.d.ts +183 -12
  30. package/src/engine/physics/vehicle/RaycastVehicle.d.ts.map +1 -1
  31. package/src/engine/physics/vehicle/RaycastVehicle.js +1046 -121
  32. package/src/shade/device/mock/SoftwareGPUCommandEncoder.d.ts +17 -0
  33. package/src/shade/device/mock/SoftwareGPUCommandEncoder.d.ts.map +1 -1
  34. package/src/shade/device/mock/SoftwareGPUCommandEncoder.js +46 -0
  35. package/src/shade/playground/tonemap_sdr/README.md +11 -8
  36. package/src/shade/playground/tonemap_sdr/ToneMapCompare.js +2 -2
  37. package/src/shade/renderer/Renderer.d.ts +5 -6
  38. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  39. package/src/shade/renderer/Renderer.js +15 -88
  40. package/src/shade/renderer/animation/chunk_animation_curve_evaluate_keys.d.ts +14 -0
  41. package/src/shade/renderer/animation/chunk_animation_curve_evaluate_keys.d.ts.map +1 -0
  42. package/src/shade/renderer/animation/chunk_animation_curve_evaluate_keys.js +62 -0
  43. package/src/shade/renderer/buffer/table/GPUDatabase.d.ts +12 -0
  44. package/src/shade/renderer/buffer/table/GPUDatabase.d.ts.map +1 -1
  45. package/src/shade/renderer/buffer/table/GPUDatabase.js +85 -0
  46. package/src/shade/renderer/buffer/table/GPUDatabaseDefinition.d.ts +2 -1
  47. package/src/shade/renderer/buffer/table/GPUDatabaseDefinition.d.ts.map +1 -1
  48. package/src/shade/renderer/buffer/table/GPUDatabaseDefinition.js +2 -1
  49. package/src/shade/renderer/deferred/tonemap/ColorGrade.d.ts +158 -0
  50. package/src/shade/renderer/deferred/tonemap/ColorGrade.d.ts.map +1 -0
  51. package/src/shade/renderer/deferred/tonemap/ColorGrade.js +337 -0
  52. package/src/shade/renderer/deferred/tonemap/SAMPLER_TONEMAP_LUT.d.ts +9 -0
  53. package/src/shade/renderer/deferred/tonemap/SAMPLER_TONEMAP_LUT.d.ts.map +1 -0
  54. package/src/shade/renderer/deferred/tonemap/SAMPLER_TONEMAP_LUT.js +15 -0
  55. package/src/shade/renderer/deferred/tonemap/TONE_MAP_OPERATOR_ACES.d.ts +6 -0
  56. package/src/shade/renderer/deferred/tonemap/TONE_MAP_OPERATOR_ACES.d.ts.map +1 -0
  57. package/src/shade/renderer/deferred/tonemap/TONE_MAP_OPERATOR_ACES.js +84 -0
  58. package/src/shade/renderer/deferred/tonemap/TONE_MAP_OPERATOR_GT7.d.ts +10 -0
  59. package/src/shade/renderer/deferred/tonemap/TONE_MAP_OPERATOR_GT7.d.ts.map +1 -0
  60. package/src/shade/renderer/deferred/tonemap/TONE_MAP_OPERATOR_GT7.js +25 -0
  61. package/src/shade/renderer/deferred/tonemap/TONE_MAP_OPERATOR_NONE.d.ts +9 -0
  62. package/src/shade/renderer/deferred/tonemap/TONE_MAP_OPERATOR_NONE.d.ts.map +1 -0
  63. package/src/shade/renderer/deferred/tonemap/TONE_MAP_OPERATOR_NONE.js +20 -0
  64. package/src/shade/renderer/deferred/tonemap/TONE_MAP_OPERATOR_PBR_NEUTRAL.d.ts +14 -0
  65. package/src/shade/renderer/deferred/tonemap/TONE_MAP_OPERATOR_PBR_NEUTRAL.d.ts.map +1 -0
  66. package/src/shade/renderer/deferred/tonemap/TONE_MAP_OPERATOR_PBR_NEUTRAL.js +30 -0
  67. package/src/shade/renderer/deferred/tonemap/ToneMapOperator.d.ts +54 -0
  68. package/src/shade/renderer/deferred/tonemap/ToneMapOperator.d.ts.map +1 -0
  69. package/src/shade/renderer/deferred/tonemap/ToneMapOperator.js +63 -0
  70. package/src/shade/renderer/deferred/tonemap/ToneMapper.d.ts +106 -0
  71. package/src/shade/renderer/deferred/tonemap/ToneMapper.d.ts.map +1 -0
  72. package/src/shade/renderer/deferred/tonemap/ToneMapper.js +474 -0
  73. package/src/shade/renderer/deferred/tonemap/TonemapAces2Tables.d.ts +5 -13
  74. package/src/shade/renderer/deferred/tonemap/TonemapAces2Tables.d.ts.map +1 -1
  75. package/src/shade/renderer/deferred/tonemap/TonemapAces2Tables.js +5 -44
  76. package/src/shade/renderer/deferred/tonemap/TonemapDisplaySettings.d.ts +7 -25
  77. package/src/shade/renderer/deferred/tonemap/TonemapDisplaySettings.d.ts.map +1 -1
  78. package/src/shade/renderer/deferred/tonemap/TonemapDisplaySettings.js +11 -51
  79. package/src/shade/renderer/deferred/tonemap/m3_white_balance_rec709.d.ts +20 -0
  80. package/src/shade/renderer/deferred/tonemap/m3_white_balance_rec709.d.ts.map +1 -0
  81. package/src/shade/renderer/deferred/tonemap/m3_white_balance_rec709.js +103 -0
  82. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_lut.d.ts +9 -0
  83. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_lut.d.ts.map +1 -0
  84. package/src/shade/renderer/deferred/tonemap/{shader_tonemap_HDR_none.js → shader_tonemap_HDR_lut.js} +22 -18
  85. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_lut.d.ts +9 -0
  86. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_lut.d.ts.map +1 -0
  87. package/src/shade/renderer/deferred/tonemap/{shader_tonemap_LDR_aces.js → shader_tonemap_LDR_lut.js} +24 -24
  88. package/src/shade/renderer/deferred/tonemap/shader_tonemap_lut_bake.d.ts +23 -0
  89. package/src/shade/renderer/deferred/tonemap/shader_tonemap_lut_bake.d.ts.map +1 -0
  90. package/src/shade/renderer/deferred/tonemap/shader_tonemap_lut_bake.js +92 -0
  91. package/src/shade/renderer/deferred/tonemap/shader_tonemap_lut_sdr_from_hdr.d.ts +15 -0
  92. package/src/shade/renderer/deferred/tonemap/shader_tonemap_lut_sdr_from_hdr.d.ts.map +1 -0
  93. package/src/shade/renderer/deferred/tonemap/shader_tonemap_lut_sdr_from_hdr.js +73 -0
  94. package/src/shade/renderer/fluid/GPUFluidCascade.d.ts +3 -3
  95. package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts +1 -1
  96. package/src/shade/renderer/fluid/forcing/GPUFluidForcing.d.ts +6 -6
  97. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts +1 -1
  98. package/src/shade/renderer/fluid/query/GPUFluidQueries.d.ts +1 -1
  99. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts +1 -1
  100. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  101. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  102. package/src/shade/renderer/shader/chunk/color/conversion/chunk_CRM_REC709_AP1.d.ts +22 -0
  103. package/src/shade/renderer/shader/chunk/color/conversion/chunk_CRM_REC709_AP1.d.ts.map +1 -0
  104. package/src/shade/renderer/shader/chunk/color/conversion/chunk_CRM_REC709_AP1.js +62 -0
  105. package/src/shade/renderer/shader/chunk/color/grade/chunk_color_grade.d.ts +13 -0
  106. package/src/shade/renderer/shader/chunk/color/grade/chunk_color_grade.d.ts.map +1 -0
  107. package/src/shade/renderer/shader/chunk/color/grade/chunk_color_grade.js +140 -0
  108. package/src/shade/renderer/shader/chunk/color/oklab/chunk_oklab_compute_max_saturation.d.ts +11 -0
  109. package/src/shade/renderer/shader/chunk/color/oklab/chunk_oklab_compute_max_saturation.d.ts.map +1 -0
  110. package/src/shade/renderer/shader/chunk/color/oklab/chunk_oklab_compute_max_saturation.js +65 -0
  111. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_bt2390_eetf.d.ts +14 -0
  112. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_bt2390_eetf.d.ts.map +1 -0
  113. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_bt2390_eetf.js +38 -0
  114. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_lut.d.ts +16 -0
  115. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_lut.d.ts.map +1 -0
  116. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_lut.js +41 -0
  117. package/src/shade/renderer/water/query/GPUWaterQueries.d.ts +1 -1
  118. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +8 -8
  119. package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
  120. package/src/shade/wgsl/emulator/WGSLLanguageCore.js +2 -0
  121. package/src/shade/renderer/ShadeToneMappingMode.d.ts +0 -15
  122. package/src/shade/renderer/ShadeToneMappingMode.d.ts.map +0 -1
  123. package/src/shade/renderer/ShadeToneMappingMode.js +0 -41
  124. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR.d.ts +0 -9
  125. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR.d.ts.map +0 -1
  126. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR.js +0 -79
  127. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_aces.d.ts +0 -17
  128. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_aces.d.ts.map +0 -1
  129. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_aces.js +0 -101
  130. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_none.d.ts +0 -9
  131. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_none.d.ts.map +0 -1
  132. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_pbr_neutral.d.ts +0 -10
  133. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_pbr_neutral.d.ts.map +0 -1
  134. package/src/shade/renderer/deferred/tonemap/shader_tonemap_HDR_pbr_neutral.js +0 -80
  135. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR.d.ts +0 -9
  136. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR.d.ts.map +0 -1
  137. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR.js +0 -80
  138. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_aces.d.ts +0 -10
  139. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_aces.d.ts.map +0 -1
  140. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_none.d.ts +0 -9
  141. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_none.d.ts.map +0 -1
  142. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_none.js +0 -71
  143. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_pbr_neutral.d.ts +0 -10
  144. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_pbr_neutral.d.ts.map +0 -1
  145. package/src/shade/renderer/deferred/tonemap/shader_tonemap_LDR_pbr_neutral.js +0 -76
  146. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_hdr_bt2390.d.ts +0 -3
  147. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_hdr_bt2390.d.ts.map +0 -1
  148. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_tonemap_hdr_bt2390.js +0 -44
package/CHANGELOG.md CHANGED
@@ -2,6 +2,57 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ### Colour grading in the tone-mapping LUT
6
+
7
+ - **Added:** `ColorGrade`, as `renderer.tone_mapper.grade`, neutral by default and baked into the tone mapper's LUTs
8
+ ahead of the operator, so it costs nothing per pixel. Any change bakes both LUTs again; a LUT the user supplies is
9
+ not graded. In order:
10
+ - white balance: Unreal's temperature white balance (`white_temperature`, `white_tint`), Planckian and daylight
11
+ loci, Bradford to D65;
12
+ - primaries: Unreal's `ColorCorrectAll` in AP1, saturation, contrast, gamma, gain and offset for `global`,
13
+ `shadows`, `midtones` and `highlights` (`ColorGradeZone`, each a `Vector4` of RGB and master), blended by
14
+ `shadows_max`, `highlights_min` and `highlights_max`. Values from an Unreal project give the same result;
15
+ - hue curves: `hue_shift` (degrees), `hue_saturation` (chroma multiplier) and `hue_lightness` (stops), each an
16
+ `AnimationCurve` over the OkLCh hue in degrees. Lightness is exposure at the colour's chromaticity, weighted by
17
+ its saturation against the Rec.709 gamut's edge at its hue, so greys never move.
18
+ - **Added:** `chunk_animation_curve_evaluate_keys`, `AnimationCurve.evaluate` on the GPU over a plain keyframe array;
19
+ `chunk_color_grade`; `chunk_CRM_REC709_AP1` (Rec.709↔AP1 and AP1 luminance, derived from the chromaticities);
20
+ `chunk_oklab_compute_max_saturation`; `m3_chromatic_adaptation_bradford`; `m3_white_balance_rec709`. The WGSL
21
+ emulator gains `degrees` and `radians`.
22
+ - **Changed:** a bake reads the grade. On an RTX 4090 (volumetrics_froxel, 1258 × 646) the ACES bake at 32³ costs
23
+ 4–6 µs neutral and 6–7 µs with a full grade on SDR, 10–14 µs and 12–14 µs on HDR at 1000 cd/m²; 9 and 13 µs at
24
+ 64³ on SDR, 24 and 28 µs on HDR. The apply pass is unchanged.
25
+ - **Changed:** a LUT's error against direct evaluation grows with the grade's detail. With three 20°-wide hue bands
26
+ and a full primary grade, ACES at 32³ has a p99 of 6.3 8-bit codes on SDR (2.5 neutral) and 15.5 10-bit PQ codes
27
+ on HDR (5.8 neutral); 64³ brings them to 2.4 and 5.4. Raise `lut_resolution` for such a grade.
28
+
29
+ ### Tone mapping through a baked 3D LUT
30
+
31
+ - **Added:** `ToneMapper`, owned by the renderer as `renderer.tone_mapper`, made by `initialize`. It bakes its
32
+ `operator` into a 3D LUT per output, at `lut_resolution` (default 32, as in Unreal; at least 2, at most the
33
+ device's `maxTextureDimension3D`), and draws every frame through one trilinear fetch. A LUT is baked again only
34
+ when the operator, the resolution or, for HDR, the display's peak (`tone_mapper.display.peak_nits`) changes. A
35
+ LUT maps the PQ signal of exposed scene-linear Rec.709 to the PQ signal of linear Rec.2020 display light, 1 at
36
+ the paper white on both sides; scene colour past 10000 cd/m² (49.3 × SDR white) clamps there, as Unreal's does.
37
+ - **Added:** `ToneMapOperator` (a `CodeChunk` defining `tone_map_operator(scene, peak_nits)`, plus `declare`
38
+ and `bind` for bindings of its own), and the built-ins `TONE_MAP_OPERATOR_ACES` (the default),
39
+ `TONE_MAP_OPERATOR_GT7`, `TONE_MAP_OPERATOR_PBR_NEUTRAL` and `TONE_MAP_OPERATOR_NONE`, unchanged in what
40
+ they compute.
41
+ - **Added:** user LUTs: `set_lut_sdr(resolution, data)`, `set_lut_hdr(resolution, data, peak_nits)`,
42
+ `clear_lut_sdr()`, `clear_lut_hdr()`, and `build_lut_sdr_from_hdr()`, which maps the HDR LUT down to the paper
43
+ white through the BT.2390 EETF on the GPU, keeping hue. `chunk_bt2390_eetf` and `chunk_tonemap_lut`.
44
+ - **Changed:** the ACES HDR pass cost 51–68 µs at 1258 × 646 on an RTX 4090 and now costs 7–12 µs; SDR 11–22 µs,
45
+ now 9–10 µs. A bake costs 7 µs at 32³ (ACES HDR, both ACES 2.0 transforms per texel), 20 µs at 64³. At 32³ the
46
+ picture moves by a mean of +0.13 8-bit codes on SDR, under the frame-to-frame noise of a live scene.
47
+ - **Breaking:** `renderer.tone_mapping_mode` and `ShadeToneMappingMode` are removed. Set
48
+ `renderer.tone_mapper.operator` to one of the built-in operators instead.
49
+ - **Breaking:** the eight `shader_tonemap_{LDR,HDR}[_aces|_none|_pbr_neutral]` passes are removed, replaced by
50
+ `shader_tonemap_LDR_lut` and `shader_tonemap_HDR_lut`. `chunk_tonemap_hdr_bt2390` is removed;
51
+ `chunk_bt2390_eetf` is the EETF itself. `TonemapDisplaySettings` keeps only the Rec.2020 columns, and
52
+ `TONEMAP_SETTINGS_REC709`, `TONEMAP_SETTINGS_REC709_PEAK`, `TONEMAP_SETTINGS_REC2020` and
53
+ `TONEMAP_SETTINGS_REC2020_PEAK` become `TONEMAP_SETTINGS`. `TonemapAces2Tables` holds the tables only; the
54
+ operator uploads them when it bakes.
55
+
5
56
  ### Outlines: a spec per entity, drawn where the entity is hidden too
6
57
 
7
58
  - **Added:** `Outline` (`visible`, `hidden`, each an `OutlineSpec`, by default the frozen `OutlineSpec.NONE`, which
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "description": "Advanced 3D game engine",
7
7
  "type": "module",
8
8
  "author": "Alexander Goldring",
9
- "version": "3.63.2",
9
+ "version": "3.64.0",
10
10
  "exports": {
11
11
  "./build/*": "./build/*",
12
12
  "./src/*": "./src/*",
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The von Kries adaptation in Bradford cone space that takes the `source` white to the `destination` white, as a
3
+ * matrix on CIE XYZ. Row-major. A colour seen under the source white maps to the colour that looks the same under the
4
+ * destination white.
5
+ *
6
+ * @param {Float64Array|number[]} out 9 elements, row-major
7
+ * @param {number} source_x chromaticity of the source white
8
+ * @param {number} source_y
9
+ * @param {number} destination_x chromaticity of the destination white
10
+ * @param {number} destination_y
11
+ * @see http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
12
+ */
13
+ export function m3_chromatic_adaptation_bradford(out: Float64Array | number[], source_x: number, source_y: number, destination_x: number, destination_y: number): void;
14
+ //# sourceMappingURL=m3_chromatic_adaptation_bradford.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"m3_chromatic_adaptation_bradford.d.ts","sourceRoot":"","sources":["../../../../../src/core/color/xyz/m3_chromatic_adaptation_bradford.js"],"names":[],"mappings":"AAkBA;;;;;;;;;;;GAWG;AACH,sDAPW,YAAY,GAAC,MAAM,EAAE,YACrB,MAAM,YACN,MAAM,iBACN,MAAM,iBACN,MAAM,QAuBhB"}
@@ -0,0 +1,51 @@
1
+ import { m3_invert } from "../../geom/mat3/m3_invert.js";
2
+ import { m3_multiply } from "../../geom/mat3/m3_multiply.js";
3
+
4
+ /**
5
+ * Bradford cone response, XYZ to LMS. Row-major.
6
+ * @type {number[]}
7
+ */
8
+ const BRADFORD = [
9
+ 0.8951, 0.2664, -0.1614,
10
+ -0.7502, 1.7135, 0.0367,
11
+ 0.0389, -0.0685, 1.0296,
12
+ ];
13
+
14
+ const BRADFORD_INVERSE = new Float64Array(9);
15
+ m3_invert(BRADFORD_INVERSE, BRADFORD);
16
+
17
+ const scratch = new Float64Array(9);
18
+
19
+ /**
20
+ * The von Kries adaptation in Bradford cone space that takes the `source` white to the `destination` white, as a
21
+ * matrix on CIE XYZ. Row-major. A colour seen under the source white maps to the colour that looks the same under the
22
+ * destination white.
23
+ *
24
+ * @param {Float64Array|number[]} out 9 elements, row-major
25
+ * @param {number} source_x chromaticity of the source white
26
+ * @param {number} source_y
27
+ * @param {number} destination_x chromaticity of the destination white
28
+ * @param {number} destination_y
29
+ * @see http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
30
+ */
31
+ export function m3_chromatic_adaptation_bradford(out, source_x, source_y, destination_x, destination_y) {
32
+ // each white's cone response, at Y = 1
33
+ const sX = source_x / source_y;
34
+ const sZ = (1 - source_x - source_y) / source_y;
35
+ const dX = destination_x / destination_y;
36
+ const dZ = (1 - destination_x - destination_y) / destination_y;
37
+
38
+ for (let row = 0; row < 3; row++) {
39
+ const r0 = BRADFORD[row * 3];
40
+ const r1 = BRADFORD[row * 3 + 1];
41
+ const r2 = BRADFORD[row * 3 + 2];
42
+
43
+ const gain = (r0 * dX + r1 + r2 * dZ) / (r0 * sX + r1 + r2 * sZ);
44
+
45
+ scratch[row * 3] = r0 * gain;
46
+ scratch[row * 3 + 1] = r1 * gain;
47
+ scratch[row * 3 + 2] = r2 * gain;
48
+ }
49
+
50
+ m3_multiply(out, BRADFORD_INVERSE, scratch);
51
+ }
@@ -4,7 +4,8 @@
4
4
  * Only the upper triangle of `m` is read (the matrix is assumed symmetric);
5
5
  * the input is not modified. Eigenpairs are emitted sorted by **ascending
6
6
  * eigenvalue**, so index 0 is the smallest — convenient for proxy-plane fits,
7
- * where the plane normal is the eigenvector of the smallest variance.
7
+ * where the plane normal is the eigenvector of the smallest variance. Equal
8
+ * eigenvalues keep their column order.
8
9
  *
9
10
  * Eigenvector sign is arbitrary (an eigenvector and its negation are both
10
11
  * valid); callers that need a consistent orientation must fix the sign
@@ -1 +1 @@
1
- {"version":3,"file":"matrix_symmetric_3x3_eigen.d.ts","sourceRoot":"","sources":["../../../../../../src/core/math/linalg/eigen/matrix_symmetric_3x3_eigen.js"],"names":[],"mappings":"AA8DA;;;;;;;;;;;;;;;;GAgBG;AACH,uDALW,YAAY,GAAC,YAAY,GAAC,MAAM,EAAE,eAClC,YAAY,GAAC,YAAY,GAAC,MAAM,EAAE,KAElC,YAAY,GAAC,YAAY,GAAC,MAAM,EAAE,QAuC5C"}
1
+ {"version":3,"file":"matrix_symmetric_3x3_eigen.d.ts","sourceRoot":"","sources":["../../../../../../src/core/math/linalg/eigen/matrix_symmetric_3x3_eigen.js"],"names":[],"mappings":"AA4BA;;;;;;;;;;;;;;;;;GAiBG;AACH,uDALW,YAAY,GAAC,YAAY,GAAC,MAAM,EAAE,eAClC,YAAY,GAAC,YAAY,GAAC,MAAM,EAAE,KAElC,YAAY,GAAC,YAAY,GAAC,MAAM,EAAE,QAqD5C"}
@@ -1,3 +1,5 @@
1
+ import { matrix_symmetric_jacobi_rotate_in_place } from "./matrix_symmetric_jacobi_rotate_in_place.js";
2
+
1
3
  /**
2
4
  * Maximum number of cyclic Jacobi sweeps. A symmetric 3x3 reaches full double
3
5
  * precision in ~5-6 sweeps (Jacobi converges quadratically); 50 is a generous
@@ -7,58 +9,22 @@
7
9
  const MAX_SWEEPS = 50;
8
10
 
9
11
  /**
10
- * One Jacobi rotation in the (p, q) plane that zeroes the off-diagonal entry
11
- * a[p][q]. `a` and `v` are 3x3 matrices stored row-major (index = row*3 + col).
12
- * `a` is the symmetric working matrix; `v` accumulates the rotations so its
13
- * columns become the eigenvectors.
14
- *
15
- * @param {number[]} a symmetric 3x3, mutated in place
16
- * @param {number[]} v 3x3 rotation accumulator, mutated in place
17
- * @param {number} p
18
- * @param {number} q
12
+ * Working copy of the input, row-major (index = row*3 + col).
13
+ * @type {Float64Array}
19
14
  */
20
- function jacobi_rotate(a, v, p, q) {
21
- const apq = a[p * 3 + q];
22
-
23
- if (apq === 0) {
24
- return;
25
- }
26
-
27
- const app = a[p * 3 + p];
28
- const aqq = a[q * 3 + q];
29
-
30
- // tan of the rotation angle (Numerical Recipes form, |angle| <= pi/4).
31
- const theta = (aqq - app) / (2 * apq);
32
- const t = (theta >= 0)
33
- ? 1 / (theta + Math.sqrt(theta * theta + 1))
34
- : -1 / (-theta + Math.sqrt(theta * theta + 1));
35
- const c = 1 / Math.sqrt(t * t + 1);
36
- const s = t * c;
37
-
38
- // The third (untouched) index.
39
- const r = 3 - p - q;
40
- const arp = a[r * 3 + p];
41
- const arq = a[r * 3 + q];
15
+ const scratch_a = new Float64Array(9);
42
16
 
43
- a[p * 3 + p] = app - t * apq;
44
- a[q * 3 + q] = aqq + t * apq;
45
- a[p * 3 + q] = 0;
46
- a[q * 3 + p] = 0;
47
-
48
- const new_rp = c * arp - s * arq;
49
- const new_rq = s * arp + c * arq;
50
- a[r * 3 + p] = new_rp;
51
- a[p * 3 + r] = new_rp;
52
- a[r * 3 + q] = new_rq;
53
- a[q * 3 + r] = new_rq;
17
+ /**
18
+ * Rotation accumulator, row-major; its columns become the eigenvectors.
19
+ * @type {Float64Array}
20
+ */
21
+ const scratch_v = new Float64Array(9);
54
22
 
55
- for (let row = 0; row < 3; row++) {
56
- const vrp = v[row * 3 + p];
57
- const vrq = v[row * 3 + q];
58
- v[row * 3 + p] = c * vrp - s * vrq;
59
- v[row * 3 + q] = s * vrp + c * vrq;
60
- }
61
- }
23
+ /**
24
+ * Column index of each eigenpair, sorted by ascending eigenvalue.
25
+ * @type {Uint8Array}
26
+ */
27
+ const scratch_order = new Uint8Array(3);
62
28
 
63
29
  /**
64
30
  * Eigendecomposition of a symmetric 3x3 matrix via cyclic Jacobi rotations.
@@ -66,7 +32,8 @@ function jacobi_rotate(a, v, p, q) {
66
32
  * Only the upper triangle of `m` is read (the matrix is assumed symmetric);
67
33
  * the input is not modified. Eigenpairs are emitted sorted by **ascending
68
34
  * eigenvalue**, so index 0 is the smallest — convenient for proxy-plane fits,
69
- * where the plane normal is the eigenvector of the smallest variance.
35
+ * where the plane normal is the eigenvector of the smallest variance. Equal
36
+ * eigenvalues keep their column order.
70
37
  *
71
38
  * Eigenvector sign is arbitrary (an eigenvector and its negation are both
72
39
  * valid); callers that need a consistent orientation must fix the sign
@@ -78,16 +45,19 @@ function jacobi_rotate(a, v, p, q) {
78
45
  * @param {Float64Array|Float32Array|number[]} m symmetric 3x3, row-major (index = row*3 + col)
79
46
  */
80
47
  export function matrix_symmetric_3x3_eigen(out_values, out_vectors, m) {
81
- const a = [
82
- m[0], m[1], m[2],
83
- m[1], m[4], m[5],
84
- m[2], m[5], m[8],
85
- ];
86
- const v = [
87
- 1, 0, 0,
88
- 0, 1, 0,
89
- 0, 0, 1,
90
- ];
48
+ const a = scratch_a;
49
+ const v = scratch_v;
50
+
51
+ for (let row = 0; row < 3; row++) {
52
+ for (let col = row; col < 3; col++) {
53
+ const value = m[row * 3 + col];
54
+ a[row * 3 + col] = value;
55
+ a[col * 3 + row] = value;
56
+
57
+ v[row * 3 + col] = row === col ? 1 : 0;
58
+ v[col * 3 + row] = row === col ? 1 : 0;
59
+ }
60
+ }
91
61
 
92
62
  for (let sweep = 0; sweep < MAX_SWEEPS; sweep++) {
93
63
  const scale = Math.abs(a[0]) + Math.abs(a[4]) + Math.abs(a[8]);
@@ -97,21 +67,32 @@ export function matrix_symmetric_3x3_eigen(out_values, out_vectors, m) {
97
67
  break;
98
68
  }
99
69
 
100
- jacobi_rotate(a, v, 0, 1);
101
- jacobi_rotate(a, v, 0, 2);
102
- jacobi_rotate(a, v, 1, 2);
70
+ matrix_symmetric_jacobi_rotate_in_place(a, v, 0, 1, 3);
71
+ matrix_symmetric_jacobi_rotate_in_place(a, v, 0, 2, 3);
72
+ matrix_symmetric_jacobi_rotate_in_place(a, v, 1, 2, 3);
103
73
  }
104
74
 
105
75
  // Eigenvalues live on the diagonal; eigenvector k is column k of V.
106
- const eig = [a[0], a[4], a[8]];
107
- const order = [0, 1, 2];
108
- order.sort((i, j) => eig[i] - eig[j]);
76
+ // Stable insertion sort of the three column indices by eigenvalue.
77
+ const order = scratch_order;
78
+
79
+ for (let k = 0; k < 3; k++) {
80
+ const value = a[k * 4];
81
+ let slot = k;
82
+
83
+ while (slot > 0 && a[order[slot - 1] * 4] > value) {
84
+ order[slot] = order[slot - 1];
85
+ slot--;
86
+ }
87
+
88
+ order[slot] = k;
89
+ }
109
90
 
110
91
  for (let k = 0; k < 3; k++) {
111
92
  const col = order[k];
112
- out_values[k] = eig[col];
113
- out_vectors[k * 3 + 0] = v[0 * 3 + col];
114
- out_vectors[k * 3 + 1] = v[1 * 3 + col];
115
- out_vectors[k * 3 + 2] = v[2 * 3 + col];
93
+ out_values[k] = a[col * 4];
94
+ out_vectors[k * 3] = v[col];
95
+ out_vectors[k * 3 + 1] = v[3 + col];
96
+ out_vectors[k * 3 + 2] = v[6 + col];
116
97
  }
117
98
  }
@@ -1 +1 @@
1
- {"version":3,"file":"matrix_symmetric_4x4_eigen.d.ts","sourceRoot":"","sources":["../../../../../../src/core/math/linalg/eigen/matrix_symmetric_4x4_eigen.js"],"names":[],"mappings":"AAsFA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,uDALW,YAAY,GAAC,YAAY,GAAC,MAAM,EAAE,eAClC,YAAY,GAAC,YAAY,GAAC,MAAM,EAAE,KAElC,YAAY,GAAC,YAAY,GAAC,MAAM,EAAE,QA0D5C"}
1
+ {"version":3,"file":"matrix_symmetric_4x4_eigen.d.ts","sourceRoot":"","sources":["../../../../../../src/core/math/linalg/eigen/matrix_symmetric_4x4_eigen.js"],"names":[],"mappings":"AA8BA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,uDALW,YAAY,GAAC,YAAY,GAAC,MAAM,EAAE,eAClC,YAAY,GAAC,YAAY,GAAC,MAAM,EAAE,KAElC,YAAY,GAAC,YAAY,GAAC,MAAM,EAAE,QA0D5C"}
@@ -1,3 +1,5 @@
1
+ import { matrix_symmetric_jacobi_rotate_in_place } from "./matrix_symmetric_jacobi_rotate_in_place.js";
2
+
1
3
  /**
2
4
  * Maximum number of cyclic Jacobi sweeps. A symmetric 4x4 reaches full double
3
5
  * precision in 3-6 sweeps (Jacobi converges quadratically, whatever the gaps
@@ -26,64 +28,6 @@ const scratch_v = new Float64Array(16);
26
28
  */
27
29
  const scratch_order = new Uint8Array(4);
28
30
 
29
- /**
30
- * One Jacobi rotation in the (p, q) plane that zeroes the off-diagonal entry
31
- * a[p][q]. `a` and `v` are 4x4 matrices stored row-major (index = row*4 + col).
32
- * `a` is the symmetric working matrix; `v` accumulates the rotations so its
33
- * columns become the eigenvectors.
34
- *
35
- * @param {Float64Array} a symmetric 4x4, mutated in place
36
- * @param {Float64Array} v 4x4 rotation accumulator, mutated in place
37
- * @param {number} p
38
- * @param {number} q
39
- */
40
- function jacobi_rotate(a, v, p, q) {
41
- const apq = a[p * 4 + q];
42
-
43
- if (apq === 0) {
44
- return;
45
- }
46
-
47
- const app = a[p * 4 + p];
48
- const aqq = a[q * 4 + q];
49
-
50
- // tan of the rotation angle (Numerical Recipes form, |angle| <= pi/4).
51
- const theta = (aqq - app) / (2 * apq);
52
- const t = (theta >= 0)
53
- ? 1 / (theta + Math.sqrt(theta * theta + 1))
54
- : -1 / (-theta + Math.sqrt(theta * theta + 1));
55
- const c = 1 / Math.sqrt(t * t + 1);
56
- const s = t * c;
57
-
58
- a[p * 4 + p] = app - t * apq;
59
- a[q * 4 + q] = aqq + t * apq;
60
- a[p * 4 + q] = 0;
61
- a[q * 4 + p] = 0;
62
-
63
- for (let r = 0; r < 4; r++) {
64
- if (r === p || r === q) {
65
- continue;
66
- }
67
-
68
- const arp = a[r * 4 + p];
69
- const arq = a[r * 4 + q];
70
-
71
- const new_rp = c * arp - s * arq;
72
- const new_rq = s * arp + c * arq;
73
- a[r * 4 + p] = new_rp;
74
- a[p * 4 + r] = new_rp;
75
- a[r * 4 + q] = new_rq;
76
- a[q * 4 + r] = new_rq;
77
- }
78
-
79
- for (let row = 0; row < 4; row++) {
80
- const vrp = v[row * 4 + p];
81
- const vrq = v[row * 4 + q];
82
- v[row * 4 + p] = c * vrp - s * vrq;
83
- v[row * 4 + q] = s * vrp + c * vrq;
84
- }
85
- }
86
-
87
31
  /**
88
32
  * Eigendecomposition of a symmetric 4x4 matrix via cyclic Jacobi rotations.
89
33
  *
@@ -131,12 +75,12 @@ export function matrix_symmetric_4x4_eigen(out_values, out_vectors, m) {
131
75
  break;
132
76
  }
133
77
 
134
- jacobi_rotate(a, v, 0, 1);
135
- jacobi_rotate(a, v, 0, 2);
136
- jacobi_rotate(a, v, 0, 3);
137
- jacobi_rotate(a, v, 1, 2);
138
- jacobi_rotate(a, v, 1, 3);
139
- jacobi_rotate(a, v, 2, 3);
78
+ matrix_symmetric_jacobi_rotate_in_place(a, v, 0, 1, 4);
79
+ matrix_symmetric_jacobi_rotate_in_place(a, v, 0, 2, 4);
80
+ matrix_symmetric_jacobi_rotate_in_place(a, v, 0, 3, 4);
81
+ matrix_symmetric_jacobi_rotate_in_place(a, v, 1, 2, 4);
82
+ matrix_symmetric_jacobi_rotate_in_place(a, v, 1, 3, 4);
83
+ matrix_symmetric_jacobi_rotate_in_place(a, v, 2, 3, 4);
140
84
  }
141
85
 
142
86
  // Eigenvalues live on the diagonal; eigenvector k is column k of V.
@@ -0,0 +1,17 @@
1
+ /**
2
+ * One Jacobi rotation in the (p, q) plane that zeroes the off-diagonal entry
3
+ * a[p][q]. `a` and `v` are d x d matrices stored row-major (index = row*d + col).
4
+ * `a` is the symmetric working matrix; `v` accumulates the rotations so its
5
+ * columns become the eigenvectors.
6
+ *
7
+ * Callers pass `d` as a literal from an unrolled sweep, so the index arithmetic
8
+ * folds once this is inlined.
9
+ *
10
+ * @param {Float64Array|number[]} a symmetric d x d, mutated in place
11
+ * @param {Float64Array|number[]} v d x d rotation accumulator, mutated in place
12
+ * @param {number} p row of the entry to zero, p < q
13
+ * @param {number} q column of the entry to zero
14
+ * @param {number} d dimension of the square matrices
15
+ */
16
+ export function matrix_symmetric_jacobi_rotate_in_place(a: Float64Array | number[], v: Float64Array | number[], p: number, q: number, d: number): void;
17
+ //# sourceMappingURL=matrix_symmetric_jacobi_rotate_in_place.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matrix_symmetric_jacobi_rotate_in_place.d.ts","sourceRoot":"","sources":["../../../../../../src/core/math/linalg/eigen/matrix_symmetric_jacobi_rotate_in_place.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,2DANW,YAAY,GAAC,MAAM,EAAE,KACrB,YAAY,GAAC,MAAM,EAAE,KACrB,MAAM,KACN,MAAM,KACN,MAAM,QAkDhB"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * One Jacobi rotation in the (p, q) plane that zeroes the off-diagonal entry
3
+ * a[p][q]. `a` and `v` are d x d matrices stored row-major (index = row*d + col).
4
+ * `a` is the symmetric working matrix; `v` accumulates the rotations so its
5
+ * columns become the eigenvectors.
6
+ *
7
+ * Callers pass `d` as a literal from an unrolled sweep, so the index arithmetic
8
+ * folds once this is inlined.
9
+ *
10
+ * @param {Float64Array|number[]} a symmetric d x d, mutated in place
11
+ * @param {Float64Array|number[]} v d x d rotation accumulator, mutated in place
12
+ * @param {number} p row of the entry to zero, p < q
13
+ * @param {number} q column of the entry to zero
14
+ * @param {number} d dimension of the square matrices
15
+ */
16
+ export function matrix_symmetric_jacobi_rotate_in_place(a, v, p, q, d) {
17
+ const pd = p * d;
18
+ const qd = q * d;
19
+ const apq = a[pd + q];
20
+
21
+ if (apq === 0) {
22
+ return;
23
+ }
24
+
25
+ const app = a[pd + p];
26
+ const aqq = a[qd + q];
27
+
28
+ // tan of the rotation angle (Numerical Recipes form, |angle| <= pi/4).
29
+ const theta = (aqq - app) / (2 * apq);
30
+ const t = (theta >= 0)
31
+ ? 1 / (theta + Math.sqrt(theta * theta + 1))
32
+ : -1 / (-theta + Math.sqrt(theta * theta + 1));
33
+ const c = 1 / Math.sqrt(t * t + 1);
34
+ const s = t * c;
35
+
36
+ a[pd + p] = app - t * apq;
37
+ a[qd + q] = aqq + t * apq;
38
+ a[pd + q] = 0;
39
+ a[qd + p] = 0;
40
+
41
+ for (let r = 0; r < d; r++) {
42
+ if (r === p || r === q) {
43
+ continue;
44
+ }
45
+
46
+ const rd = r * d;
47
+ const arp = a[rd + p];
48
+ const arq = a[rd + q];
49
+
50
+ const new_rp = c * arp - s * arq;
51
+ const new_rq = s * arp + c * arq;
52
+ a[rd + p] = new_rp;
53
+ a[pd + r] = new_rp;
54
+ a[rd + q] = new_rq;
55
+ a[qd + r] = new_rq;
56
+ }
57
+
58
+ for (let rd = 0; rd < d * d; rd += d) {
59
+ const vrp = v[rd + p];
60
+ const vrq = v[rd + q];
61
+ v[rd + p] = c * vrp - s * vrq;
62
+ v[rd + q] = s * vrp + c * vrq;
63
+ }
64
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Solve the bound-constrained quadratic program
3
+ *
4
+ * minimise ½·xᵀ·A·x − bᵀ·x subject to lo ≤ x ≤ hi
5
+ *
6
+ * for a symmetric positive-definite `A`; equivalently the box LCP: `x` within `[lo, hi]` where
7
+ * `w = A·x − b` is zero for every `x_i` strictly between its bounds, non-negative where `x_i = lo_i`
8
+ * and non-positive where `x_i = hi_i`. The solution is unique.
9
+ *
10
+ * Primal active-set method (Nocedal & Wright, *Numerical Optimization*, 2nd ed., §16.5). It starts
11
+ * from the point of the box nearest the origin, with every variable free. Each pass solves for the
12
+ * free variables with the held ones at their bounds, and steps toward that solution as far as the box
13
+ * allows; a variable that cuts the step short is held at the bound it reached. A full step is the
14
+ * minimum over the variables currently free: then the held variable whose `w` has the wrong sign by
15
+ * the most is freed, or, when none has, `x` is the solution. The objective falls from each such
16
+ * minimum to the next, so no set of held variables repeats and the search ends; it also ends where
17
+ * rounding stops the objective falling. When no bound is reached it is one factorisation of `A`.
18
+ *
19
+ * @param {ArrayLike<number>} A n×n, column-major (`A[j·n + i]` is row `i`, column `j`); being symmetric,
20
+ * row-major reads the same. Not modified.
21
+ * @param {ArrayLike<number>} b length n
22
+ * @param {ArrayLike<number>} lo lower bounds, length n; `-Infinity` where there is none
23
+ * @param {ArrayLike<number>} hi upper bounds, length n, none below its `lo`; `Infinity` where there is none
24
+ * @param {Float64Array|number[]} x receives the solution, length n
25
+ * @param {number} n
26
+ * @returns {boolean} true when solved; false when `A` did not factor as positive definite, `x` then
27
+ * holding the last point reached, within the bounds
28
+ */
29
+ export function solve_box_constrained_qp(A: ArrayLike<number>, b: ArrayLike<number>, lo: ArrayLike<number>, hi: ArrayLike<number>, x: Float64Array | number[], n: number): boolean;
30
+ //# sourceMappingURL=solve_box_constrained_qp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"solve_box_constrained_qp.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/linalg/solve_box_constrained_qp.js"],"names":[],"mappings":"AAiBA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,4CAVW,UAAU,MAAM,CAAC,KAEjB,UAAU,MAAM,CAAC,MACjB,UAAU,MAAM,CAAC,MACjB,UAAU,MAAM,CAAC,KACjB,YAAY,GAAC,MAAM,EAAE,KACrB,MAAM,GACJ,OAAO,CAkKnB"}