@photivra/engine 2.9.0 → 2.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/CHANGELOG.md +223 -1
  2. package/CITATION.cff +1 -1
  3. package/NODE_SUPPORT.md +21 -3
  4. package/README.md +6 -6
  5. package/SECURITY.md +1 -1
  6. package/THIRD_PARTY.md +2 -0
  7. package/dist/capture/native-environment-lean.js +89 -5
  8. package/dist/capture/native-environment-lean.js.map +1 -1
  9. package/dist/capture/native-environment-tabulated.d.ts +37 -0
  10. package/dist/capture/native-environment-tabulated.js +42 -6
  11. package/dist/capture/native-environment-tabulated.js.map +1 -1
  12. package/dist/core/portable-math.d.ts +19 -0
  13. package/dist/core/portable-math.js +82 -0
  14. package/dist/core/portable-math.js.map +1 -1
  15. package/dist/core/version.d.ts +1 -1
  16. package/dist/core/version.js +1 -1
  17. package/dist/core/version.js.map +1 -1
  18. package/dist/facade/channel-response.d.ts +15 -0
  19. package/dist/facade/channel-response.js +33 -0
  20. package/dist/facade/channel-response.js.map +1 -0
  21. package/dist/facade/chart-material-layers.d.ts +115 -0
  22. package/dist/facade/chart-material-layers.js +224 -0
  23. package/dist/facade/chart-material-layers.js.map +1 -0
  24. package/dist/facade/chart-radiance-batch.d.ts +8 -2
  25. package/dist/facade/chart-radiance-batch.js +11 -4
  26. package/dist/facade/chart-radiance-batch.js.map +1 -1
  27. package/dist/facade/chart-scenes.d.ts +134 -0
  28. package/dist/facade/chart-scenes.js +32 -22
  29. package/dist/facade/chart-scenes.js.map +1 -1
  30. package/dist/facade/diffraction-provider.d.ts +8 -2
  31. package/dist/facade/diffraction-provider.js +120 -16
  32. package/dist/facade/diffraction-provider.js.map +1 -1
  33. package/dist/facade/display-view-gpu-session.d.ts +18 -0
  34. package/dist/facade/display-view-gpu-session.js +280 -0
  35. package/dist/facade/display-view-gpu-session.js.map +1 -0
  36. package/dist/facade/display-view-gpu.d.ts +146 -0
  37. package/dist/facade/display-view-gpu.js +59 -0
  38. package/dist/facade/display-view-gpu.js.map +1 -0
  39. package/dist/facade/display-view.d.ts +123 -1
  40. package/dist/facade/display-view.js +478 -116
  41. package/dist/facade/display-view.js.map +1 -1
  42. package/dist/facade/expected-signal-setup.d.ts +5 -0
  43. package/dist/facade/expected-signal-setup.js +18 -1
  44. package/dist/facade/expected-signal-setup.js.map +1 -1
  45. package/dist/facade/expected-signal.d.ts +80 -6
  46. package/dist/facade/expected-signal.js +200 -24
  47. package/dist/facade/expected-signal.js.map +1 -1
  48. package/dist/facade/scene-radiance.d.ts +4 -2
  49. package/dist/facade/scene-radiance.js +7 -4
  50. package/dist/facade/scene-radiance.js.map +1 -1
  51. package/dist/facade/simulate-capture.d.ts +81 -7
  52. package/dist/facade/simulate-capture.js +276 -53
  53. package/dist/facade/simulate-capture.js.map +1 -1
  54. package/dist/facade/simulate-display-view.d.ts +54 -3
  55. package/dist/facade/simulate-display-view.js +120 -27
  56. package/dist/facade/simulate-display-view.js.map +1 -1
  57. package/dist/gpu/chart-composite-gpu.d.ts +119 -0
  58. package/dist/gpu/chart-composite-gpu.js +572 -0
  59. package/dist/gpu/chart-composite-gpu.js.map +1 -0
  60. package/dist/gpu/chart-radiance-gpu.d.ts +6 -6
  61. package/dist/gpu/chart-radiance-gpu.js +9 -11
  62. package/dist/gpu/chart-radiance-gpu.js.map +1 -1
  63. package/dist/gpu/display-view-chart-material-module.d.ts +10 -0
  64. package/dist/gpu/display-view-chart-material-module.js +58 -0
  65. package/dist/gpu/display-view-chart-material-module.js.map +1 -0
  66. package/dist/gpu/display-view-composite-material-module.d.ts +68 -0
  67. package/dist/gpu/display-view-composite-material-module.js +248 -0
  68. package/dist/gpu/display-view-composite-material-module.js.map +1 -0
  69. package/dist/gpu/display-view-quality-gpu.d.ts +185 -0
  70. package/dist/gpu/display-view-quality-gpu.js +1000 -0
  71. package/dist/gpu/display-view-quality-gpu.js.map +1 -0
  72. package/dist/gpu/display-view-speed-gpu.d.ts +156 -0
  73. package/dist/gpu/display-view-speed-gpu.js +524 -0
  74. package/dist/gpu/display-view-speed-gpu.js.map +1 -0
  75. package/dist/gpu/image-kernel-accelerator.d.ts +23 -4
  76. package/dist/gpu/image-kernel-accelerator.js +58 -10
  77. package/dist/gpu/image-kernel-accelerator.js.map +1 -1
  78. package/dist/gpu/image-kernel-execution.d.ts +60 -3
  79. package/dist/gpu/image-kernel-execution.js +2 -2
  80. package/dist/gpu/image-kernel-execution.js.map +1 -1
  81. package/dist/gpu/sensor-stage-gpu.d.ts +11 -0
  82. package/dist/gpu/sensor-stage-gpu.js +42 -13
  83. package/dist/gpu/sensor-stage-gpu.js.map +1 -1
  84. package/dist/gpu/webgpu-runtime.d.ts +4 -2
  85. package/dist/gpu/webgpu-runtime.js +13 -4
  86. package/dist/gpu/webgpu-runtime.js.map +1 -1
  87. package/dist/gpu/webgpu-switch.d.ts +4 -0
  88. package/dist/gpu/webgpu-switch.js +29 -0
  89. package/dist/gpu/webgpu-switch.js.map +1 -0
  90. package/dist/gpu/wgsl-chart-composite-kernel.d.ts +52 -0
  91. package/dist/gpu/wgsl-chart-composite-kernel.js +515 -0
  92. package/dist/gpu/wgsl-chart-composite-kernel.js.map +1 -0
  93. package/dist/gpu/wgsl-chart-kernel.d.ts +11 -9
  94. package/dist/gpu/wgsl-chart-kernel.js +33 -27
  95. package/dist/gpu/wgsl-chart-kernel.js.map +1 -1
  96. package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +95 -0
  97. package/dist/gpu/wgsl-display-view-quality-kernel.js +769 -0
  98. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -0
  99. package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +75 -0
  100. package/dist/gpu/wgsl-display-view-speed-kernel.js +386 -0
  101. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -0
  102. package/dist/gpu/wgsl-sensor-kernel.d.ts +28 -9
  103. package/dist/gpu/wgsl-sensor-kernel.js +94 -50
  104. package/dist/gpu/wgsl-sensor-kernel.js.map +1 -1
  105. package/dist/index.d.ts +6 -3
  106. package/dist/index.js +4 -2
  107. package/dist/index.js.map +1 -1
  108. package/dist/optics/defocus-disc-kernel.d.ts +61 -0
  109. package/dist/optics/defocus-disc-kernel.js +191 -0
  110. package/dist/optics/defocus-disc-kernel.js.map +1 -0
  111. package/dist/optics/irradiance-convolution.d.ts +11 -0
  112. package/dist/optics/irradiance-convolution.js +64 -24
  113. package/dist/optics/irradiance-convolution.js.map +1 -1
  114. package/dist/optics/scene-to-sensor-irradiance.d.ts +13 -0
  115. package/dist/optics/scene-to-sensor-irradiance.js +58 -24
  116. package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
  117. package/dist/optics/sensor-aperture-rays.js +2 -1
  118. package/dist/optics/sensor-aperture-rays.js.map +1 -1
  119. package/dist/optics/sensor-environment-query.js +4 -3
  120. package/dist/optics/sensor-environment-query.js.map +1 -1
  121. package/dist/optics/site-diffraction-kernel.d.ts +16 -0
  122. package/dist/optics/site-diffraction-kernel.js +60 -8
  123. package/dist/optics/site-diffraction-kernel.js.map +1 -1
  124. package/dist/processing/demosaic.js +114 -37
  125. package/dist/processing/demosaic.js.map +1 -1
  126. package/dist/schema/scene-radiance.d.ts +8 -0
  127. package/dist/schema/scene-radiance.js +28 -17
  128. package/dist/schema/scene-radiance.js.map +1 -1
  129. package/dist/sensor/environment-photo-signal.d.ts +21 -0
  130. package/dist/sensor/environment-photo-signal.js +68 -2
  131. package/dist/sensor/environment-photo-signal.js.map +1 -1
  132. package/docs/API_REFERENCE.md +60 -1
  133. package/docs/QUICK_START.md +120 -11
  134. package/docs/USAGE.md +82 -2
  135. package/docs/api/core-version.md +1 -1
  136. package/docs/api/exports.json +157 -2
  137. package/docs/api/facade-chart-material-layers.md +137 -0
  138. package/docs/api/facade-chart-radiance-batch.md +7 -2
  139. package/docs/api/facade-display-view-gpu.md +94 -0
  140. package/docs/api/facade-display-view.md +50 -0
  141. package/docs/api/facade-scene-radiance.md +3 -2
  142. package/docs/api/facade-simulate-capture.md +81 -12
  143. package/docs/api/facade-simulate-display-view.md +39 -2
  144. package/docs/api/gpu-image-kernel-accelerator.md +10 -3
  145. package/docs/api/gpu-image-kernel-execution.md +76 -4
  146. package/docs/api/gpu-webgpu-switch.md +23 -0
  147. package/package.json +3 -1
@@ -0,0 +1,15 @@
1
+ import type { SceneRadianceEvaluationRequest, SceneRadianceEvaluationResult } from "../schema/scene-radiance.js";
2
+ /**
3
+ * Electrons per unit spectral radiance at each node, sorted by wavelength, for an environment record with one temporal
4
+ * sample. The chain runs once with radiance 1 at every node; each node's value is its own rate term times the time step.
5
+ *
6
+ * Exact: the spatial reduction and the per-node rate are per spectral node, the spectral sum is a compensated sum of
7
+ * per-node terms and the temporal sum is rate × time step. An execution with radiance 1 at one node and 0 elsewhere (the
8
+ * earlier method, one execution per node) therefore adds +0 terms that leave the compensated sum unchanged and returns
9
+ * exactly `rate_n × integrationMeasureSeconds` (test/channel-response.test.ts compares both with Object.is).
10
+ *
11
+ * The environment record must be the engine's own (built by the caller, never shown to supplied code or changed after):
12
+ * it is planned as engine-owned data without a copy. Retention is photo-only, as on the capture path; photo values are
13
+ * identical for either retention.
14
+ */
15
+ export declare function calculateChannelElectronsPerRadiance(environment: Record<string, unknown>, evaluateRadiance: (request: Readonly<SceneRadianceEvaluationRequest>) => SceneRadianceEvaluationResult, caller: string): Map<number, number>;
@@ -0,0 +1,33 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Internal (#443): one channel's expected photo electrons per unit spectral radiance at each spectral node, from one
4
+ * execution of the environment photo-signal chain. Used by the facade's `axisChannelResponse` (display view, ideal
5
+ * reference and the capture's tabulated RAW path).
6
+ */
7
+ import { InvalidConfigurationError } from "../core/configuration-error.js";
8
+ import { executeEnvironmentSensorPhotoSignal, planEnvironmentSensorPhotoSignal } from "../sensor/environment-photo-signal.js";
9
+ /**
10
+ * Electrons per unit spectral radiance at each node, sorted by wavelength, for an environment record with one temporal
11
+ * sample. The chain runs once with radiance 1 at every node; each node's value is its own rate term times the time step.
12
+ *
13
+ * Exact: the spatial reduction and the per-node rate are per spectral node, the spectral sum is a compensated sum of
14
+ * per-node terms and the temporal sum is rate × time step. An execution with radiance 1 at one node and 0 elsewhere (the
15
+ * earlier method, one execution per node) therefore adds +0 terms that leave the compensated sum unchanged and returns
16
+ * exactly `rate_n × integrationMeasureSeconds` (test/channel-response.test.ts compares both with Object.is).
17
+ *
18
+ * The environment record must be the engine's own (built by the caller, never shown to supplied code or changed after):
19
+ * it is planned as engine-owned data without a copy. Retention is photo-only, as on the capture path; photo values are
20
+ * identical for either retention.
21
+ */
22
+ export function calculateChannelElectronsPerRadiance(environment, evaluateRadiance, caller) {
23
+ const plan = planEnvironmentSensorPhotoSignal(environment, "engine-owned", "photo-only");
24
+ const result = executeEnvironmentSensorPhotoSignal(plan, (request) => ({ ...evaluateRadiance(request), spectralRadianceWattsPerSquareMeterSteradianNanometer: 1 }), undefined, "photo-only");
25
+ const samples = result.value.photo.value.exposure.value.samples;
26
+ if (samples.length !== 1)
27
+ throw new InvalidConfigurationError(caller + ": the channel response expects one temporal sample.");
28
+ const sample = samples[0];
29
+ return new Map(sample.stages.electronRate.value.perWavelength
30
+ .map((node) => [node.wavelengthNanometers, node.expectedGeneratedElectronRatePerSecond * sample.integrationMeasureSeconds])
31
+ .sort((a, b) => a[0] - b[0]));
32
+ }
33
+ //# sourceMappingURL=channel-response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-response.js","sourceRoot":"","sources":["../../src/facade/channel-response.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;GAIG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAE3E,OAAO,EAAE,mCAAmC,EAAE,gCAAgC,EAAE,MAAM,uCAAuC,CAAC;AAM9H;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,oCAAoC,CAAC,WAAoC,EACvF,gBAAsG,EAAE,MAAc;IACtH,MAAM,IAAI,GAAG,gCAAgC,CAAC,WAAmC,EAAE,cAAc,EAAE,YAAY,CAAC,CAAC;IACjH,MAAM,MAAM,GAAG,mCAAmC,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CACnE,CAAC,EAAE,GAAG,gBAAgB,CAAC,OAAO,CAAC,EAAE,qDAAqD,EAAE,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;IACzH,MAAM,OAAO,GAAI,MAAM,CAAC,KAAoC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC;IAChG,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,yBAAyB,CAAC,MAAM,GAAG,qDAAqD,CAAC,CAAC;IAC9H,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;IAC3B,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,aAAa;SAC1D,GAAG,CAAC,CAAC,IAAI,EAAoB,EAAE,CAAC,CAAC,IAAI,CAAC,oBAAoB,EAAE,IAAI,CAAC,sCAAsC,GAAG,MAAM,CAAC,yBAAyB,CAAC,CAAC;SAC5I,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Internal (#443): one channel's expected photo electrons per unit spectral radiance at each spectral node, from one\n * execution of the environment photo-signal chain. Used by the facade's `axisChannelResponse` (display view, ideal\n * reference and the capture's tabulated RAW path).\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport type { SceneRadianceEvaluationRequest, SceneRadianceEvaluationResult } from \"../schema/scene-radiance.js\";\nimport { executeEnvironmentSensorPhotoSignal, planEnvironmentSensorPhotoSignal } from \"../sensor/environment-photo-signal.js\";\n\ntype PlanInput = Parameters<typeof planEnvironmentSensorPhotoSignal>[0];\ninterface PerNodeExposure { photo: { value: { exposure: { value: { samples: readonly { integrationMeasureSeconds: number;\n stages: { electronRate: { value: { perWavelength: readonly { wavelengthNanometers: number; expectedGeneratedElectronRatePerSecond: number }[] } } } }[] } } } } }\n\n/**\n * Electrons per unit spectral radiance at each node, sorted by wavelength, for an environment record with one temporal\n * sample. The chain runs once with radiance 1 at every node; each node's value is its own rate term times the time step.\n *\n * Exact: the spatial reduction and the per-node rate are per spectral node, the spectral sum is a compensated sum of\n * per-node terms and the temporal sum is rate × time step. An execution with radiance 1 at one node and 0 elsewhere (the\n * earlier method, one execution per node) therefore adds +0 terms that leave the compensated sum unchanged and returns\n * exactly `rate_n × integrationMeasureSeconds` (test/channel-response.test.ts compares both with Object.is).\n *\n * The environment record must be the engine's own (built by the caller, never shown to supplied code or changed after):\n * it is planned as engine-owned data without a copy. Retention is photo-only, as on the capture path; photo values are\n * identical for either retention.\n */\nexport function calculateChannelElectronsPerRadiance(environment: Record<string, unknown>,\n evaluateRadiance: (request: Readonly<SceneRadianceEvaluationRequest>) => SceneRadianceEvaluationResult, caller: string): Map<number, number> {\n const plan = planEnvironmentSensorPhotoSignal(environment as unknown as PlanInput, \"engine-owned\", \"photo-only\");\n const result = executeEnvironmentSensorPhotoSignal(plan, (request) =>\n ({ ...evaluateRadiance(request), spectralRadianceWattsPerSquareMeterSteradianNanometer: 1 }), undefined, \"photo-only\");\n const samples = (result.value as unknown as PerNodeExposure).photo.value.exposure.value.samples;\n if (samples.length !== 1) throw new InvalidConfigurationError(caller + \": the channel response expects one temporal sample.\");\n const sample = samples[0]!;\n return new Map(sample.stages.electronRate.value.perWavelength\n .map((node): [number, number] => [node.wavelengthNanometers, node.expectedGeneratedElectronRatePerSecond * sample.integrationMeasureSeconds])\n .sort((a, b) => a[0] - b[0]));\n}\n"]}
@@ -0,0 +1,115 @@
1
+ import { type ChartReflectance, type ChartScene, type ResolvedChartScene } from "./chart-scenes.js";
2
+ import type { CallerMaterialScene } from "./scene-radiance.js";
3
+ /** A filled polygon: rings as flat [x0, y0, x1, y1, …] in mask units (y down), non-zero winding over all rings. */
4
+ export interface ChartMaskPolygon {
5
+ kind: "polygon";
6
+ rings: readonly (readonly number[])[];
7
+ }
8
+ /** A stroked circle: the points at distance innerRadius ≤ d ≤ outerRadius from the centre, mask units. */
9
+ export interface ChartMaskAnnulus {
10
+ kind: "annulus";
11
+ centerX: number;
12
+ centerY: number;
13
+ innerRadius: number;
14
+ outerRadius: number;
15
+ }
16
+ export type ChartMaskShape = ChartMaskPolygon | ChartMaskAnnulus;
17
+ /** A shape mask printed over the chart: its box [0, unitsWidth] × [0, unitsHeight] (y down) fills `rectangle` (frame units, v up). */
18
+ export interface ChartMaskLayer {
19
+ kind: "shape-mask";
20
+ rectangle: {
21
+ u0: number;
22
+ v0: number;
23
+ u1: number;
24
+ v1: number;
25
+ };
26
+ unitsWidth: number;
27
+ unitsHeight: number;
28
+ /** 1 to 4096 shapes, in order; the ink material where any contains the point. */
29
+ shapes: readonly ChartMaskShape[];
30
+ backgroundReflectance: ChartReflectance;
31
+ inkReflectance: ChartReflectance;
32
+ }
33
+ /** A chart under 1 to 16 shape-mask layers, as data; `resolveChartMaterialLayers` evaluates it. */
34
+ export interface ChartMaterialLayerScene {
35
+ kind: "material-layers";
36
+ sceneId: string;
37
+ chart: ChartScene;
38
+ layers: readonly ChartMaskLayer[];
39
+ }
40
+ /** The cell index grain of every layer: cells across and down the mask box. */
41
+ export declare const CHART_MASK_INDEX_GRAIN: Readonly<{
42
+ cellsX: 64;
43
+ cellsY: 16;
44
+ }>;
45
+ /** Limits of a declared scene. */
46
+ export declare const CHART_MATERIAL_LAYER_LIMITS: Readonly<{
47
+ maximumLayers: 16;
48
+ maximumShapes: 4096;
49
+ maximumRings: 256;
50
+ maximumEdges: 65536;
51
+ }>;
52
+ /** Internal: a shape with its bounding box and flattened edges (polygon) or squared radii (annulus). */
53
+ export type ResolvedChartMaskShape = {
54
+ kind: "polygon";
55
+ box: readonly [number, number, number, number];
56
+ edges: Float64Array;
57
+ } | {
58
+ kind: "annulus";
59
+ box: readonly [number, number, number, number];
60
+ centerX: number;
61
+ centerY: number;
62
+ inner2: number;
63
+ outer2: number;
64
+ };
65
+ export interface ResolvedChartMaskLayer {
66
+ rectangle: {
67
+ u0: number;
68
+ v0: number;
69
+ u1: number;
70
+ v1: number;
71
+ };
72
+ unitsWidth: number;
73
+ unitsHeight: number;
74
+ /** Mask units per frame unit: unitsWidth / (u1 − u0) and unitsHeight / (v1 − v0). */
75
+ scaleX: number;
76
+ scaleY: number;
77
+ shapes: readonly ResolvedChartMaskShape[];
78
+ cellsX: number;
79
+ cellsY: number;
80
+ cellWidth: number;
81
+ cellHeight: number;
82
+ /** Per cell (row by row), the shape indices whose bounding box meets the cell grown by one cell. */
83
+ cells: readonly Uint32Array[];
84
+ backgroundMaterial: number;
85
+ inkMaterial: number;
86
+ /** Whether a point in mask units is ink (through the cell index). */
87
+ containsUnits(x: number, y: number): boolean;
88
+ }
89
+ export interface ResolvedChartMaterialLayers {
90
+ kind: "material-layers";
91
+ sceneId: string;
92
+ /** The chart under the layers; its materials come first in `materials`. */
93
+ chart: ResolvedChartScene;
94
+ layers: readonly ResolvedChartMaskLayer[];
95
+ materials: ResolvedChartScene["materials"];
96
+ /** Internal: the caller input behind each material (undefined for an engine neutral), for the scene digest. */
97
+ materialInputs: readonly unknown[];
98
+ /** Material index at a frame point (u right, v up): the topmost layer whose rectangle holds the point, else the chart. */
99
+ materialAt(u: number, v: number): number;
100
+ /** The same scene as a caller material scene for `simulateCapture` and the display view (chart-local coordinates when the chart has a size). */
101
+ callerScene: CallerMaterialScene;
102
+ }
103
+ /** Non-zero winding over the edges (x0, y0, x1, y1), as the reference evaluates it; the GPU runs the same comparisons. */
104
+ export declare function polygonWinding(edges: Float64Array, x: number, y: number): number;
105
+ /** Whether a resolved shape contains a point in mask units (the full test, without the cell index). */
106
+ export declare function shapeContains(shape: ResolvedChartMaskShape, x: number, y: number): boolean;
107
+ /**
108
+ * Validates a declared material scene and resolves its chart (in a frame of `aspect` = height / width) and its
109
+ * layers. `materialAt` is the float64 reference the GPU stage is checked against, and `callerScene` is the same
110
+ * scene for `simulateCapture`'s `scene` option.
111
+ */
112
+ export declare function resolveChartMaterialLayers(input: {
113
+ scene: ChartMaterialLayerScene;
114
+ aspect: number;
115
+ }): ResolvedChartMaterialLayers;
@@ -0,0 +1,224 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Declared material scenes (#477): an engine chart under shape-mask layers, given as data rather than as a
4
+ * `materialAt` function, so that both the float64 reference and the `webgpu-f32` chart stage can evaluate
5
+ * them. An application that prints a label on a chart (a logo and a line of text as filled polygons and
6
+ * stroked rings, in the label's own units) describes it here; the engine holds no label of its own.
7
+ *
8
+ * A layer maps its mask box, [0, unitsWidth] × [0, unitsHeight] with y down, onto a frame rectangle. Inside
9
+ * that rectangle the layer decides the material: the ink material where any of its shapes contains the
10
+ * point, its background material elsewhere. Later layers cover earlier ones, and the chart shows outside
11
+ * every layer. Shapes are polygons under the non-zero winding rule (several rings per shape; a stroked
12
+ * polyline is its rectangles as rings of one orientation) and annuli (stroked circles).
13
+ *
14
+ * Each layer carries a cell index of {@link CHART_MASK_INDEX_GRAIN} cells over its box: a cell lists the
15
+ * shapes whose bounding box meets the cell grown by one cell on every side, so `containsUnits` tests only
16
+ * those. A computed cell can be off by one from the true one only through the rounding of one subtraction
17
+ * and one multiplication, far less than a cell (the grain is checked against the coordinates' magnitude
18
+ * when the layer is resolved), and a shape left out of a cell's list lies a whole cell away from any point
19
+ * that resolves to it, so the indexed test gives the full test's answer at every point. The GPU uses the same
20
+ * index (docs/WEBGPU.md, "Scene radiance on the GPU").
21
+ */
22
+ import { InvalidConfigurationError } from "../core/configuration-error.js";
23
+ import { requireInputKeys, requireInputObject } from "../core/record-validation.js";
24
+ import { resolveChartScene, resolveChartMaterial, chartMaterialInputs } from "./chart-scenes.js";
25
+ /** The cell index grain of every layer: cells across and down the mask box. */
26
+ export const CHART_MASK_INDEX_GRAIN = Object.freeze({ cellsX: 64, cellsY: 16 });
27
+ /** Limits of a declared scene. */
28
+ export const CHART_MATERIAL_LAYER_LIMITS = Object.freeze({ maximumLayers: 16, maximumShapes: 4096, maximumRings: 256, maximumEdges: 65_536 });
29
+ const SCENE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/u;
30
+ const finite = (value, name) => {
31
+ if (typeof value !== "number" || !Number.isFinite(value))
32
+ throw new InvalidConfigurationError(name + " must be a finite number.");
33
+ return value;
34
+ };
35
+ /** The smallest cell the one-cell margin covers: the rounding of (x − 0)·scale is a few units in the last place of the coordinates. */
36
+ const minimumCell = (extent) => Math.max(extent, Number.MIN_VALUE) * 2 ** -20;
37
+ function resolvePolygon(shape, name) {
38
+ const rings = shape.rings;
39
+ if (!Array.isArray(rings) || rings.length < 1 || rings.length > CHART_MATERIAL_LAYER_LIMITS.maximumRings)
40
+ throw new InvalidConfigurationError(name + " needs 1 to 256 rings.");
41
+ let count = 0;
42
+ for (const ring of rings) {
43
+ if (!Array.isArray(ring) || ring.length < 6 || ring.length % 2 !== 0)
44
+ throw new InvalidConfigurationError(name + " rings need at least three (x, y) vertices.");
45
+ count += ring.length / 2;
46
+ }
47
+ if (count > CHART_MATERIAL_LAYER_LIMITS.maximumEdges)
48
+ throw new InvalidConfigurationError(name + " has more than 65536 edges.");
49
+ const edges = new Float64Array(4 * count);
50
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity, e = 0;
51
+ for (const ring of rings) {
52
+ const n = ring.length / 2;
53
+ for (let k = 0; k < n; k++) {
54
+ const x0 = finite(ring[2 * k], name + " vertex"), y0 = finite(ring[2 * k + 1], name + " vertex");
55
+ const next = k + 1 === n ? 0 : k + 1, x1 = finite(ring[2 * next], name + " vertex"), y1 = finite(ring[2 * next + 1], name + " vertex");
56
+ edges[e++] = x0;
57
+ edges[e++] = y0;
58
+ edges[e++] = x1;
59
+ edges[e++] = y1;
60
+ minX = Math.min(minX, x0);
61
+ minY = Math.min(minY, y0);
62
+ maxX = Math.max(maxX, x0);
63
+ maxY = Math.max(maxY, y0);
64
+ }
65
+ }
66
+ return { kind: "polygon", box: [minX, minY, maxX, maxY], edges };
67
+ }
68
+ /** Non-zero winding over the edges (x0, y0, x1, y1), as the reference evaluates it; the GPU runs the same comparisons. */
69
+ export function polygonWinding(edges, x, y) {
70
+ let winding = 0;
71
+ for (let k = 0; k < edges.length; k += 4) {
72
+ const x0 = edges[k], y0 = edges[k + 1], x1 = edges[k + 2], y1 = edges[k + 3];
73
+ const side = (x1 - x0) * (y - y0) - (x - x0) * (y1 - y0);
74
+ if (y0 <= y) {
75
+ if (y1 > y && side > 0)
76
+ winding++;
77
+ }
78
+ else if (y1 <= y && side < 0)
79
+ winding--;
80
+ }
81
+ return winding;
82
+ }
83
+ /** Whether a resolved shape contains a point in mask units (the full test, without the cell index). */
84
+ export function shapeContains(shape, x, y) {
85
+ const b = shape.box;
86
+ if (!(x >= b[0] && x <= b[2] && y >= b[1] && y <= b[3]))
87
+ return false;
88
+ if (shape.kind === "annulus") {
89
+ const d2 = (x - shape.centerX) * (x - shape.centerX) + (y - shape.centerY) * (y - shape.centerY);
90
+ return d2 >= shape.inner2 && d2 <= shape.outer2;
91
+ }
92
+ return polygonWinding(shape.edges, x, y) !== 0;
93
+ }
94
+ function resolveLayer(value, name, aspect, materials, inputs) {
95
+ requireInputObject(value, name);
96
+ const layer = value;
97
+ for (const key of Object.keys(layer)) {
98
+ if (!["kind", "rectangle", "unitsWidth", "unitsHeight", "shapes", "backgroundReflectance", "inkReflectance"].includes(key))
99
+ throw new InvalidConfigurationError("Unknown " + name + " field: " + key + ".");
100
+ }
101
+ if (layer.kind !== "shape-mask")
102
+ throw new InvalidConfigurationError(name + ".kind must be shape-mask.");
103
+ requireInputObject(layer.rectangle, name + ".rectangle");
104
+ const r = layer.rectangle;
105
+ for (const key of Object.keys(r))
106
+ if (!["u0", "v0", "u1", "v1"].includes(key))
107
+ throw new InvalidConfigurationError("Unknown " + name + ".rectangle field: " + key + ".");
108
+ const u0 = finite(r.u0, name + ".rectangle.u0"), v0 = finite(r.v0, name + ".rectangle.v0"), u1 = finite(r.u1, name + ".rectangle.u1"), v1 = finite(r.v1, name + ".rectangle.v1");
109
+ if (!(u1 > u0 && v1 > v0) || Math.max(Math.abs(u0), Math.abs(u1)) > 1e6 || Math.max(Math.abs(v0), Math.abs(v1)) > 1e6 * Math.max(1, aspect)) {
110
+ throw new InvalidConfigurationError(name + ".rectangle needs u0 < u1 and v0 < v1 within 10^6 frame units.");
111
+ }
112
+ const unitsWidth = finite(layer.unitsWidth, name + ".unitsWidth"), unitsHeight = finite(layer.unitsHeight, name + ".unitsHeight");
113
+ if (!(unitsWidth > 0 && unitsHeight > 0 && unitsWidth <= 1e9 && unitsHeight <= 1e9))
114
+ throw new InvalidConfigurationError(name + " units must be positive and at most 10^9.");
115
+ const list = layer.shapes;
116
+ if (!Array.isArray(list) || list.length < 1 || list.length > CHART_MATERIAL_LAYER_LIMITS.maximumShapes)
117
+ throw new InvalidConfigurationError(name + ".shapes must list 1 to 4096 shapes.");
118
+ const shapes = list.map((shape, index) => {
119
+ const label = name + ".shapes[" + index + "]";
120
+ requireInputObject(shape, label);
121
+ const s = shape;
122
+ if (s.kind === "polygon") {
123
+ for (const key of Object.keys(s))
124
+ if (key !== "kind" && key !== "rings")
125
+ throw new InvalidConfigurationError("Unknown " + label + " field: " + key + ".");
126
+ return resolvePolygon(s, label);
127
+ }
128
+ if (s.kind !== "annulus")
129
+ throw new InvalidConfigurationError(label + ".kind must be polygon or annulus.");
130
+ for (const key of Object.keys(s))
131
+ if (!["kind", "centerX", "centerY", "innerRadius", "outerRadius"].includes(key))
132
+ throw new InvalidConfigurationError("Unknown " + label + " field: " + key + ".");
133
+ const cx = finite(s.centerX, label + ".centerX"), cy = finite(s.centerY, label + ".centerY");
134
+ const inner = finite(s.innerRadius, label + ".innerRadius"), outer = finite(s.outerRadius, label + ".outerRadius");
135
+ if (!(inner >= 0 && outer > inner))
136
+ throw new InvalidConfigurationError(label + " needs 0 ≤ innerRadius < outerRadius.");
137
+ return { kind: "annulus", box: [cx - outer, cy - outer, cx + outer, cy + outer], centerX: cx, centerY: cy, inner2: inner * inner, outer2: outer * outer };
138
+ });
139
+ for (const shape of shapes) {
140
+ if (!shape.box.every((c) => Math.abs(c) <= 1e9))
141
+ throw new InvalidConfigurationError(name + " shape coordinates must stay within 10^9 units.");
142
+ }
143
+ const backgroundMaterial = materials.length;
144
+ materials.push(resolveChartMaterial(layer.backgroundReflectance, name + ".backgroundReflectance"));
145
+ inputs.push(typeof layer.backgroundReflectance === "number" ? undefined : layer.backgroundReflectance);
146
+ const inkMaterial = materials.length;
147
+ materials.push(resolveChartMaterial(layer.inkReflectance, name + ".inkReflectance"));
148
+ inputs.push(typeof layer.inkReflectance === "number" ? undefined : layer.inkReflectance);
149
+ // The cell index over the mask box; the grain must stay large against the coordinates' rounding (see the header).
150
+ const { cellsX, cellsY } = CHART_MASK_INDEX_GRAIN, cellWidth = unitsWidth / cellsX, cellHeight = unitsHeight / cellsY;
151
+ const extentX = Math.max(unitsWidth, ...shapes.map((s) => Math.max(Math.abs(s.box[0]), Math.abs(s.box[2]))));
152
+ const extentY = Math.max(unitsHeight, ...shapes.map((s) => Math.max(Math.abs(s.box[1]), Math.abs(s.box[3]))));
153
+ if (!(cellWidth > minimumCell(extentX) && cellHeight > minimumCell(extentY)))
154
+ throw new InvalidConfigurationError(name + ": the shapes extend too far beyond the mask box for its cell index.");
155
+ const cells = [];
156
+ for (let j = 0; j < cellsY; j++) {
157
+ for (let i = 0; i < cellsX; i++) {
158
+ const x0 = (i - 1) * cellWidth, x1 = (i + 2) * cellWidth, y0 = (j - 1) * cellHeight, y1 = (j + 2) * cellHeight, listed = [];
159
+ shapes.forEach((s, k) => { if (s.box[0] <= x1 && s.box[2] >= x0 && s.box[1] <= y1 && s.box[3] >= y0)
160
+ listed.push(k); });
161
+ cells.push(Uint32Array.from(listed));
162
+ }
163
+ }
164
+ const scaleX = unitsWidth / (u1 - u0), scaleY = unitsHeight / (v1 - v0);
165
+ return Object.freeze({ rectangle: Object.freeze({ u0, v0, u1, v1 }), unitsWidth, unitsHeight, scaleX, scaleY, shapes: Object.freeze(shapes), cellsX, cellsY, cellWidth, cellHeight,
166
+ cells: Object.freeze(cells), backgroundMaterial, inkMaterial,
167
+ containsUnits(x, y) {
168
+ let i = Math.floor(x / cellWidth), j = Math.floor(y / cellHeight);
169
+ if (!(i > 0))
170
+ i = 0;
171
+ else if (i >= cellsX)
172
+ i = cellsX - 1;
173
+ if (!(j > 0))
174
+ j = 0;
175
+ else if (j >= cellsY)
176
+ j = cellsY - 1;
177
+ const list = cells[j * cellsX + i];
178
+ for (let k = 0; k < list.length; k++)
179
+ if (shapeContains(shapes[list[k]], x, y))
180
+ return true;
181
+ return false;
182
+ } });
183
+ }
184
+ /**
185
+ * Validates a declared material scene and resolves its chart (in a frame of `aspect` = height / width) and its
186
+ * layers. `materialAt` is the float64 reference the GPU stage is checked against, and `callerScene` is the same
187
+ * scene for `simulateCapture`'s `scene` option.
188
+ */
189
+ export function resolveChartMaterialLayers(input) {
190
+ requireInputKeys(input, "resolveChartMaterialLayers", ["scene", "aspect"]);
191
+ const { scene, aspect } = input;
192
+ requireInputObject(scene, "scene");
193
+ const record = scene;
194
+ for (const key of Object.keys(record))
195
+ if (!["kind", "sceneId", "chart", "layers"].includes(key))
196
+ throw new InvalidConfigurationError("Unknown scene field: " + key + ".");
197
+ if (record.kind !== "material-layers")
198
+ throw new InvalidConfigurationError("scene.kind must be material-layers.");
199
+ if (typeof record.sceneId !== "string" || !SCENE_ID.test(record.sceneId))
200
+ throw new InvalidConfigurationError("A material-layers scene needs a public sceneId.");
201
+ if (typeof aspect !== "number" || !Number.isFinite(aspect) || aspect <= 0)
202
+ throw new InvalidConfigurationError("Chart aspect must be positive.");
203
+ const chart = resolveChartScene({ scene: record.chart, aspect });
204
+ const list = record.layers;
205
+ if (!Array.isArray(list) || list.length < 1 || list.length > CHART_MATERIAL_LAYER_LIMITS.maximumLayers)
206
+ throw new InvalidConfigurationError("layers must list 1 to 16 shape-mask layers.");
207
+ const materials = [...chart.materials], inputs = [...chartMaterialInputs(chart)];
208
+ const layers = list.map((layer, index) => resolveLayer(layer, "layers[" + index + "]", aspect, materials, inputs));
209
+ const materialAt = (u, v) => {
210
+ for (let l = layers.length - 1; l >= 0; l--) {
211
+ const layer = layers[l], r = layer.rectangle;
212
+ if (u < r.u0 || u > r.u1 || v < r.v0 || v > r.v1)
213
+ continue;
214
+ return layer.containsUnits((u - r.u0) * layer.scaleX, (r.v1 - v) * layer.scaleY) ? layer.inkMaterial : layer.backgroundMaterial;
215
+ }
216
+ return chart.materialAt(u, v);
217
+ };
218
+ const callerMaterials = materials.map((m, k) => (m.kind === "neutral" ? m.reflectance : inputs[k]));
219
+ const callerScene = Object.freeze({ kind: "material-function", sceneId: record.sceneId, materials: Object.freeze(callerMaterials),
220
+ materialAt: (p) => materialAt(p.chartU ?? p.u, p.chartV ?? p.v) });
221
+ return Object.freeze({ kind: "material-layers", sceneId: record.sceneId, chart, layers: Object.freeze(layers), materials: Object.freeze(materials),
222
+ materialInputs: Object.freeze(inputs), materialAt, callerScene });
223
+ }
224
+ //# sourceMappingURL=chart-material-layers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chart-material-layers.js","sourceRoot":"","sources":["../../src/facade/chart-material-layers.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,mBAAmB,EAAmE,MAAM,mBAAmB,CAAC;AAsBlK,+EAA+E;AAC/E,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;AAChF,kCAAkC;AAClC,MAAM,CAAC,MAAM,2BAA2B,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,CAAC;AAwC9I,MAAM,QAAQ,GAAG,qCAAqC,CAAC;AACvD,MAAM,MAAM,GAAG,CAAC,KAAc,EAAE,IAAY,EAAU,EAAE;IACtD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,yBAAyB,CAAC,IAAI,GAAG,2BAA2B,CAAC,CAAC;IAClI,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AACF,uIAAuI;AACvI,MAAM,WAAW,GAAG,CAAC,MAAc,EAAU,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;AAE9F,SAAS,cAAc,CAAC,KAAuB,EAAE,IAAY;IAC3D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IAC1B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,2BAA2B,CAAC,YAAY;QAAE,MAAM,IAAI,yBAAyB,CAAC,IAAI,GAAG,wBAAwB,CAAC,CAAC;IAC/K,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,yBAAyB,CAAC,IAAI,GAAG,6CAA6C,CAAC,CAAC;QAChK,KAAK,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IAC3B,CAAC;IACD,IAAI,KAAK,GAAG,2BAA2B,CAAC,YAAY;QAAE,MAAM,IAAI,yBAAyB,CAAC,IAAI,GAAG,6BAA6B,CAAC,CAAC;IAChI,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;IAC1C,IAAI,IAAI,GAAG,QAAQ,EAAE,IAAI,GAAG,QAAQ,EAAE,IAAI,GAAG,CAAC,QAAQ,EAAE,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;IAChF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC,CAAC;YACjG,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC,CAAC;YACvI,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC;YAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC;YAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC;YAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC;YACnE,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC7G,CAAC;IACH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC;AACnE,CAAC;AAED,0HAA0H;AAC1H,MAAM,UAAU,cAAc,CAAC,KAAmB,EAAE,CAAS,EAAE,CAAS;IACtE,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAE,EAAE,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAE,EAAE,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAE,EAAE,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC;QACjF,MAAM,IAAI,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;QACzD,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YACZ,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC;gBAAE,OAAO,EAAE,CAAC;QACpC,CAAC;aAAM,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC;YAAE,OAAO,EAAE,CAAC;IAC5C,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AACD,uGAAuG;AACvG,MAAM,UAAU,aAAa,CAAC,KAA6B,EAAE,CAAS,EAAE,CAAS;IAC/E,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC;IACpB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACtE,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;QACjG,OAAO,EAAE,IAAI,KAAK,CAAC,MAAM,IAAI,EAAE,IAAI,KAAK,CAAC,MAAM,CAAC;IAClD,CAAC;IACD,OAAO,cAAc,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;AACjD,CAAC;AAED,SAAS,YAAY,CAAC,KAAc,EAAE,IAAY,EAAE,MAAc,EAAE,SAAoD,EAAE,MAAiB;IACzI,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,KAAgC,CAAC;IAC/C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,uBAAuB,EAAE,gBAAgB,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,MAAM,IAAI,yBAAyB,CAAC,UAAU,GAAG,IAAI,GAAG,UAAU,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;IAC9M,CAAC;IACD,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY;QAAE,MAAM,IAAI,yBAAyB,CAAC,IAAI,GAAG,2BAA2B,CAAC,CAAC;IACzG,kBAAkB,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,GAAG,YAAY,CAAC,CAAC;IACzD,MAAM,CAAC,GAAG,KAAK,CAAC,SAAoC,CAAC;IACrD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,MAAM,IAAI,yBAAyB,CAAC,UAAU,GAAG,IAAI,GAAG,oBAAoB,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;IACzK,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,GAAG,eAAe,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,GAAG,eAAe,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,GAAG,eAAe,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,GAAG,eAAe,CAAC,CAAC;IACjL,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC;QAC5I,MAAM,IAAI,yBAAyB,CAAC,IAAI,GAAG,+DAA+D,CAAC,CAAC;IAC9G,CAAC;IACD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,IAAI,GAAG,aAAa,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,GAAG,cAAc,CAAC,CAAC;IAClI,IAAI,CAAC,CAAC,UAAU,GAAG,CAAC,IAAI,WAAW,GAAG,CAAC,IAAI,UAAU,IAAI,GAAG,IAAI,WAAW,IAAI,GAAG,CAAC;QAAE,MAAM,IAAI,yBAAyB,CAAC,IAAI,GAAG,2CAA2C,CAAC,CAAC;IAC7K,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC;IAC1B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,2BAA2B,CAAC,aAAa;QAAE,MAAM,IAAI,yBAAyB,CAAC,IAAI,GAAG,qCAAqC,CAAC,CAAC;IAC1L,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,KAAc,EAAE,KAAa,EAA0B,EAAE;QAChF,MAAM,KAAK,GAAG,IAAI,GAAG,UAAU,GAAG,KAAK,GAAG,GAAG,CAAC;QAC9C,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACjC,MAAM,CAAC,GAAG,KAAgC,CAAC;QAC3C,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACzB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;gBAAE,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,OAAO;oBAAE,MAAM,IAAI,yBAAyB,CAAC,UAAU,GAAG,KAAK,GAAG,UAAU,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;YAC1J,OAAO,cAAc,CAAC,CAAgC,EAAE,KAAK,CAAC,CAAC;QACjE,CAAC;QACD,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;YAAE,MAAM,IAAI,yBAAyB,CAAC,KAAK,GAAG,mCAAmC,CAAC,CAAC;QAC3G,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;YAAE,IAAI,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAAE,MAAM,IAAI,yBAAyB,CAAC,UAAU,GAAG,KAAK,GAAG,UAAU,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;QACpM,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,GAAG,UAAU,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,GAAG,UAAU,CAAC,CAAC;QAC7F,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,GAAG,cAAc,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,GAAG,cAAc,CAAC,CAAC;QACnH,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,IAAI,KAAK,GAAG,KAAK,CAAC;YAAE,MAAM,IAAI,yBAAyB,CAAC,KAAK,GAAG,uCAAuC,CAAC,CAAC;QACzH,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,KAAK,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,GAAG,KAAK,EAAE,MAAM,EAAE,KAAK,GAAG,KAAK,EAAE,CAAC;IAC5J,CAAC,CAAC,CAAC;IACH,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;YAAE,MAAM,IAAI,yBAAyB,CAAC,IAAI,GAAG,iDAAiD,CAAC,CAAC;IACjJ,CAAC;IACD,MAAM,kBAAkB,GAAG,SAAS,CAAC,MAAM,CAAC;IAC5C,SAAS,CAAC,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,qBAAqB,EAAE,IAAI,GAAG,wBAAwB,CAAC,CAAC,CAAC;IACnG,MAAM,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,qBAAqB,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACvG,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC;IACrC,SAAS,CAAC,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,cAAc,EAAE,IAAI,GAAG,iBAAiB,CAAC,CAAC,CAAC;IACrF,MAAM,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,cAAc,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IACzF,kHAAkH;IAClH,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,sBAAsB,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,EAAE,UAAU,GAAG,WAAW,GAAG,MAAM,CAAC;IACtH,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7G,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9G,IAAI,CAAC,CAAC,SAAS,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,UAAU,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;QAAE,MAAM,IAAI,yBAAyB,CAAC,IAAI,GAAG,qEAAqE,CAAC,CAAC;IAChM,MAAM,KAAK,GAAkB,EAAE,CAAC;IAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAChC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,SAAS,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,SAAS,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,UAAU,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,UAAU,EAAE,MAAM,GAAa,EAAE,CAAC;YACtI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE;gBAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACxH,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,UAAU,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;IACxE,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU;QAChL,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,kBAAkB,EAAE,WAAW;QAC5D,aAAa,CAAC,CAAS,EAAE,CAAS;YAChC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC;YAClE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;gBAAE,CAAC,GAAG,CAAC,CAAC;iBAAM,IAAI,CAAC,IAAI,MAAM;gBAAE,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC;YAC1D,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;gBAAE,CAAC,GAAG,CAAC,CAAC;iBAAM,IAAI,CAAC,IAAI,MAAM;gBAAE,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC;YAC1D,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,GAAG,MAAM,GAAG,CAAC,CAAE,CAAC;YACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE;gBAAE,IAAI,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAE,CAAE,EAAE,CAAC,EAAE,CAAC,CAAC;oBAAE,OAAO,IAAI,CAAC;YAC9F,OAAO,KAAK,CAAC;QACf,CAAC,EAAE,CAAC,CAAC;AACT,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CAAC,KAAyD;IAClG,gBAAgB,CAAC,KAAK,EAAE,4BAA4B,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC3E,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IAChC,kBAAkB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,KAA2C,CAAC;IAC3D,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,IAAI,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,MAAM,IAAI,yBAAyB,CAAC,uBAAuB,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;IAC3K,IAAI,MAAM,CAAC,IAAI,KAAK,iBAAiB;QAAE,MAAM,IAAI,yBAAyB,CAAC,qCAAqC,CAAC,CAAC;IAClH,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;QAAE,MAAM,IAAI,yBAAyB,CAAC,iDAAiD,CAAC,CAAC;IACjK,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC;QAAE,MAAM,IAAI,yBAAyB,CAAC,gCAAgC,CAAC,CAAC;IACjJ,MAAM,KAAK,GAAG,iBAAiB,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAmB,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/E,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC;IAC3B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,2BAA2B,CAAC,aAAa;QAAE,MAAM,IAAI,yBAAyB,CAAC,6CAA6C,CAAC,CAAC;IAC3L,MAAM,SAAS,GAA8C,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,GAAc,CAAC,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC;IACvI,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,KAAc,EAAE,KAAa,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,GAAG,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;IACpI,MAAM,UAAU,GAAG,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE;QAClD,KAAK,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAE,EAAE,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC;YAC9C,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE;gBAAE,SAAS;YAC3D,OAAO,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,kBAAkB,CAAC;QAClI,CAAC;QACD,OAAO,KAAK,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAChC,CAAC,CAAC;IACF,MAAM,eAAe,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAqB,CAAC,CAAC,CAAC;IACxH,MAAM,WAAW,GAAwB,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,mBAA4B,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC;QAC7J,UAAU,EAAE,CAAC,CAA+B,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACnG,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,iBAA0B,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC;QACzJ,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,CAAC;AACtE,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Declared material scenes (#477): an engine chart under shape-mask layers, given as data rather than as a\n * `materialAt` function, so that both the float64 reference and the `webgpu-f32` chart stage can evaluate\n * them. An application that prints a label on a chart (a logo and a line of text as filled polygons and\n * stroked rings, in the label's own units) describes it here; the engine holds no label of its own.\n *\n * A layer maps its mask box, [0, unitsWidth] × [0, unitsHeight] with y down, onto a frame rectangle. Inside\n * that rectangle the layer decides the material: the ink material where any of its shapes contains the\n * point, its background material elsewhere. Later layers cover earlier ones, and the chart shows outside\n * every layer. Shapes are polygons under the non-zero winding rule (several rings per shape; a stroked\n * polyline is its rectangles as rings of one orientation) and annuli (stroked circles).\n *\n * Each layer carries a cell index of {@link CHART_MASK_INDEX_GRAIN} cells over its box: a cell lists the\n * shapes whose bounding box meets the cell grown by one cell on every side, so `containsUnits` tests only\n * those. A computed cell can be off by one from the true one only through the rounding of one subtraction\n * and one multiplication, far less than a cell (the grain is checked against the coordinates' magnitude\n * when the layer is resolved), and a shape left out of a cell's list lies a whole cell away from any point\n * that resolves to it, so the indexed test gives the full test's answer at every point. The GPU uses the same\n * index (docs/WEBGPU.md, \"Scene radiance on the GPU\").\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireInputKeys, requireInputObject } from \"../core/record-validation.js\";\nimport { resolveChartScene, resolveChartMaterial, chartMaterialInputs, type ChartReflectance, type ChartScene, type ResolvedChartScene } from \"./chart-scenes.js\";\nimport type { CallerMaterialScene, SceneMaterialPoint } from \"./scene-radiance.js\";\n\n/** A filled polygon: rings as flat [x0, y0, x1, y1, …] in mask units (y down), non-zero winding over all rings. */\nexport interface ChartMaskPolygon { kind: \"polygon\"; rings: readonly (readonly number[])[] }\n/** A stroked circle: the points at distance innerRadius ≤ d ≤ outerRadius from the centre, mask units. */\nexport interface ChartMaskAnnulus { kind: \"annulus\"; centerX: number; centerY: number; innerRadius: number; outerRadius: number }\nexport type ChartMaskShape = ChartMaskPolygon | ChartMaskAnnulus;\n/** A shape mask printed over the chart: its box [0, unitsWidth] × [0, unitsHeight] (y down) fills `rectangle` (frame units, v up). */\nexport interface ChartMaskLayer {\n kind: \"shape-mask\";\n rectangle: { u0: number; v0: number; u1: number; v1: number };\n unitsWidth: number;\n unitsHeight: number;\n /** 1 to 4096 shapes, in order; the ink material where any contains the point. */\n shapes: readonly ChartMaskShape[];\n backgroundReflectance: ChartReflectance;\n inkReflectance: ChartReflectance;\n}\n/** A chart under 1 to 16 shape-mask layers, as data; `resolveChartMaterialLayers` evaluates it. */\nexport interface ChartMaterialLayerScene { kind: \"material-layers\"; sceneId: string; chart: ChartScene; layers: readonly ChartMaskLayer[] }\n\n/** The cell index grain of every layer: cells across and down the mask box. */\nexport const CHART_MASK_INDEX_GRAIN = Object.freeze({ cellsX: 64, cellsY: 16 });\n/** Limits of a declared scene. */\nexport const CHART_MATERIAL_LAYER_LIMITS = Object.freeze({ maximumLayers: 16, maximumShapes: 4096, maximumRings: 256, maximumEdges: 65_536 });\n\n/** Internal: a shape with its bounding box and flattened edges (polygon) or squared radii (annulus). */\nexport type ResolvedChartMaskShape =\n | { kind: \"polygon\"; box: readonly [number, number, number, number]; edges: Float64Array }\n | { kind: \"annulus\"; box: readonly [number, number, number, number]; centerX: number; centerY: number; inner2: number; outer2: number };\nexport interface ResolvedChartMaskLayer {\n rectangle: { u0: number; v0: number; u1: number; v1: number };\n unitsWidth: number;\n unitsHeight: number;\n /** Mask units per frame unit: unitsWidth / (u1 − u0) and unitsHeight / (v1 − v0). */\n scaleX: number;\n scaleY: number;\n shapes: readonly ResolvedChartMaskShape[];\n cellsX: number;\n cellsY: number;\n cellWidth: number;\n cellHeight: number;\n /** Per cell (row by row), the shape indices whose bounding box meets the cell grown by one cell. */\n cells: readonly Uint32Array[];\n backgroundMaterial: number;\n inkMaterial: number;\n /** Whether a point in mask units is ink (through the cell index). */\n containsUnits(x: number, y: number): boolean;\n}\nexport interface ResolvedChartMaterialLayers {\n kind: \"material-layers\";\n sceneId: string;\n /** The chart under the layers; its materials come first in `materials`. */\n chart: ResolvedChartScene;\n layers: readonly ResolvedChartMaskLayer[];\n materials: ResolvedChartScene[\"materials\"];\n /** Internal: the caller input behind each material (undefined for an engine neutral), for the scene digest. */\n materialInputs: readonly unknown[];\n /** Material index at a frame point (u right, v up): the topmost layer whose rectangle holds the point, else the chart. */\n materialAt(u: number, v: number): number;\n /** The same scene as a caller material scene for `simulateCapture` and the display view (chart-local coordinates when the chart has a size). */\n callerScene: CallerMaterialScene;\n}\n\nconst SCENE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/u;\nconst finite = (value: unknown, name: string): number => {\n if (typeof value !== \"number\" || !Number.isFinite(value)) throw new InvalidConfigurationError(name + \" must be a finite number.\");\n return value;\n};\n/** The smallest cell the one-cell margin covers: the rounding of (x − 0)·scale is a few units in the last place of the coordinates. */\nconst minimumCell = (extent: number): number => Math.max(extent, Number.MIN_VALUE) * 2 ** -20;\n\nfunction resolvePolygon(shape: ChartMaskPolygon, name: string): ResolvedChartMaskShape {\n const rings = shape.rings;\n if (!Array.isArray(rings) || rings.length < 1 || rings.length > CHART_MATERIAL_LAYER_LIMITS.maximumRings) throw new InvalidConfigurationError(name + \" needs 1 to 256 rings.\");\n let count = 0;\n for (const ring of rings) {\n if (!Array.isArray(ring) || ring.length < 6 || ring.length % 2 !== 0) throw new InvalidConfigurationError(name + \" rings need at least three (x, y) vertices.\");\n count += ring.length / 2;\n }\n if (count > CHART_MATERIAL_LAYER_LIMITS.maximumEdges) throw new InvalidConfigurationError(name + \" has more than 65536 edges.\");\n const edges = new Float64Array(4 * count);\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity, e = 0;\n for (const ring of rings) {\n const n = ring.length / 2;\n for (let k = 0; k < n; k++) {\n const x0 = finite(ring[2 * k], name + \" vertex\"), y0 = finite(ring[2 * k + 1], name + \" vertex\");\n const next = k + 1 === n ? 0 : k + 1, x1 = finite(ring[2 * next], name + \" vertex\"), y1 = finite(ring[2 * next + 1], name + \" vertex\");\n edges[e++] = x0; edges[e++] = y0; edges[e++] = x1; edges[e++] = y1;\n minX = Math.min(minX, x0); minY = Math.min(minY, y0); maxX = Math.max(maxX, x0); maxY = Math.max(maxY, y0);\n }\n }\n return { kind: \"polygon\", box: [minX, minY, maxX, maxY], edges };\n}\n\n/** Non-zero winding over the edges (x0, y0, x1, y1), as the reference evaluates it; the GPU runs the same comparisons. */\nexport function polygonWinding(edges: Float64Array, x: number, y: number): number {\n let winding = 0;\n for (let k = 0; k < edges.length; k += 4) {\n const x0 = edges[k]!, y0 = edges[k + 1]!, x1 = edges[k + 2]!, y1 = edges[k + 3]!;\n const side = (x1 - x0) * (y - y0) - (x - x0) * (y1 - y0);\n if (y0 <= y) {\n if (y1 > y && side > 0) winding++;\n } else if (y1 <= y && side < 0) winding--;\n }\n return winding;\n}\n/** Whether a resolved shape contains a point in mask units (the full test, without the cell index). */\nexport function shapeContains(shape: ResolvedChartMaskShape, x: number, y: number): boolean {\n const b = shape.box;\n if (!(x >= b[0] && x <= b[2] && y >= b[1] && y <= b[3])) return false;\n if (shape.kind === \"annulus\") {\n const d2 = (x - shape.centerX) * (x - shape.centerX) + (y - shape.centerY) * (y - shape.centerY);\n return d2 >= shape.inner2 && d2 <= shape.outer2;\n }\n return polygonWinding(shape.edges, x, y) !== 0;\n}\n\nfunction resolveLayer(value: unknown, name: string, aspect: number, materials: ResolvedChartScene[\"materials\"][number][], inputs: unknown[]): ResolvedChartMaskLayer {\n requireInputObject(value, name);\n const layer = value as Record<string, unknown>;\n for (const key of Object.keys(layer)) {\n if (![\"kind\", \"rectangle\", \"unitsWidth\", \"unitsHeight\", \"shapes\", \"backgroundReflectance\", \"inkReflectance\"].includes(key)) throw new InvalidConfigurationError(\"Unknown \" + name + \" field: \" + key + \".\");\n }\n if (layer.kind !== \"shape-mask\") throw new InvalidConfigurationError(name + \".kind must be shape-mask.\");\n requireInputObject(layer.rectangle, name + \".rectangle\");\n const r = layer.rectangle as Record<string, unknown>;\n for (const key of Object.keys(r)) if (![\"u0\", \"v0\", \"u1\", \"v1\"].includes(key)) throw new InvalidConfigurationError(\"Unknown \" + name + \".rectangle field: \" + key + \".\");\n const u0 = finite(r.u0, name + \".rectangle.u0\"), v0 = finite(r.v0, name + \".rectangle.v0\"), u1 = finite(r.u1, name + \".rectangle.u1\"), v1 = finite(r.v1, name + \".rectangle.v1\");\n if (!(u1 > u0 && v1 > v0) || Math.max(Math.abs(u0), Math.abs(u1)) > 1e6 || Math.max(Math.abs(v0), Math.abs(v1)) > 1e6 * Math.max(1, aspect)) {\n throw new InvalidConfigurationError(name + \".rectangle needs u0 < u1 and v0 < v1 within 10^6 frame units.\");\n }\n const unitsWidth = finite(layer.unitsWidth, name + \".unitsWidth\"), unitsHeight = finite(layer.unitsHeight, name + \".unitsHeight\");\n if (!(unitsWidth > 0 && unitsHeight > 0 && unitsWidth <= 1e9 && unitsHeight <= 1e9)) throw new InvalidConfigurationError(name + \" units must be positive and at most 10^9.\");\n const list = layer.shapes;\n if (!Array.isArray(list) || list.length < 1 || list.length > CHART_MATERIAL_LAYER_LIMITS.maximumShapes) throw new InvalidConfigurationError(name + \".shapes must list 1 to 4096 shapes.\");\n const shapes = list.map((shape: unknown, index: number): ResolvedChartMaskShape => {\n const label = name + \".shapes[\" + index + \"]\";\n requireInputObject(shape, label);\n const s = shape as Record<string, unknown>;\n if (s.kind === \"polygon\") {\n for (const key of Object.keys(s)) if (key !== \"kind\" && key !== \"rings\") throw new InvalidConfigurationError(\"Unknown \" + label + \" field: \" + key + \".\");\n return resolvePolygon(s as unknown as ChartMaskPolygon, label);\n }\n if (s.kind !== \"annulus\") throw new InvalidConfigurationError(label + \".kind must be polygon or annulus.\");\n for (const key of Object.keys(s)) if (![\"kind\", \"centerX\", \"centerY\", \"innerRadius\", \"outerRadius\"].includes(key)) throw new InvalidConfigurationError(\"Unknown \" + label + \" field: \" + key + \".\");\n const cx = finite(s.centerX, label + \".centerX\"), cy = finite(s.centerY, label + \".centerY\");\n const inner = finite(s.innerRadius, label + \".innerRadius\"), outer = finite(s.outerRadius, label + \".outerRadius\");\n if (!(inner >= 0 && outer > inner)) throw new InvalidConfigurationError(label + \" needs 0 ≤ innerRadius < outerRadius.\");\n return { kind: \"annulus\", box: [cx - outer, cy - outer, cx + outer, cy + outer], centerX: cx, centerY: cy, inner2: inner * inner, outer2: outer * outer };\n });\n for (const shape of shapes) {\n if (!shape.box.every((c) => Math.abs(c) <= 1e9)) throw new InvalidConfigurationError(name + \" shape coordinates must stay within 10^9 units.\");\n }\n const backgroundMaterial = materials.length;\n materials.push(resolveChartMaterial(layer.backgroundReflectance, name + \".backgroundReflectance\"));\n inputs.push(typeof layer.backgroundReflectance === \"number\" ? undefined : layer.backgroundReflectance);\n const inkMaterial = materials.length;\n materials.push(resolveChartMaterial(layer.inkReflectance, name + \".inkReflectance\"));\n inputs.push(typeof layer.inkReflectance === \"number\" ? undefined : layer.inkReflectance);\n // The cell index over the mask box; the grain must stay large against the coordinates' rounding (see the header).\n const { cellsX, cellsY } = CHART_MASK_INDEX_GRAIN, cellWidth = unitsWidth / cellsX, cellHeight = unitsHeight / cellsY;\n const extentX = Math.max(unitsWidth, ...shapes.map((s) => Math.max(Math.abs(s.box[0]), Math.abs(s.box[2]))));\n const extentY = Math.max(unitsHeight, ...shapes.map((s) => Math.max(Math.abs(s.box[1]), Math.abs(s.box[3]))));\n if (!(cellWidth > minimumCell(extentX) && cellHeight > minimumCell(extentY))) throw new InvalidConfigurationError(name + \": the shapes extend too far beyond the mask box for its cell index.\");\n const cells: Uint32Array[] = [];\n for (let j = 0; j < cellsY; j++) {\n for (let i = 0; i < cellsX; i++) {\n const x0 = (i - 1) * cellWidth, x1 = (i + 2) * cellWidth, y0 = (j - 1) * cellHeight, y1 = (j + 2) * cellHeight, listed: number[] = [];\n shapes.forEach((s, k) => { if (s.box[0] <= x1 && s.box[2] >= x0 && s.box[1] <= y1 && s.box[3] >= y0) listed.push(k); });\n cells.push(Uint32Array.from(listed));\n }\n }\n const scaleX = unitsWidth / (u1 - u0), scaleY = unitsHeight / (v1 - v0);\n return Object.freeze({ rectangle: Object.freeze({ u0, v0, u1, v1 }), unitsWidth, unitsHeight, scaleX, scaleY, shapes: Object.freeze(shapes), cellsX, cellsY, cellWidth, cellHeight,\n cells: Object.freeze(cells), backgroundMaterial, inkMaterial,\n containsUnits(x: number, y: number): boolean {\n let i = Math.floor(x / cellWidth), j = Math.floor(y / cellHeight);\n if (!(i > 0)) i = 0; else if (i >= cellsX) i = cellsX - 1;\n if (!(j > 0)) j = 0; else if (j >= cellsY) j = cellsY - 1;\n const list = cells[j * cellsX + i]!;\n for (let k = 0; k < list.length; k++) if (shapeContains(shapes[list[k]!]!, x, y)) return true;\n return false;\n } });\n}\n\n/**\n * Validates a declared material scene and resolves its chart (in a frame of `aspect` = height / width) and its\n * layers. `materialAt` is the float64 reference the GPU stage is checked against, and `callerScene` is the same\n * scene for `simulateCapture`'s `scene` option.\n */\nexport function resolveChartMaterialLayers(input: { scene: ChartMaterialLayerScene; aspect: number }): ResolvedChartMaterialLayers {\n requireInputKeys(input, \"resolveChartMaterialLayers\", [\"scene\", \"aspect\"]);\n const { scene, aspect } = input;\n requireInputObject(scene, \"scene\");\n const record = scene as unknown as Record<string, unknown>;\n for (const key of Object.keys(record)) if (![\"kind\", \"sceneId\", \"chart\", \"layers\"].includes(key)) throw new InvalidConfigurationError(\"Unknown scene field: \" + key + \".\");\n if (record.kind !== \"material-layers\") throw new InvalidConfigurationError(\"scene.kind must be material-layers.\");\n if (typeof record.sceneId !== \"string\" || !SCENE_ID.test(record.sceneId)) throw new InvalidConfigurationError(\"A material-layers scene needs a public sceneId.\");\n if (typeof aspect !== \"number\" || !Number.isFinite(aspect) || aspect <= 0) throw new InvalidConfigurationError(\"Chart aspect must be positive.\");\n const chart = resolveChartScene({ scene: record.chart as ChartScene, aspect });\n const list = record.layers;\n if (!Array.isArray(list) || list.length < 1 || list.length > CHART_MATERIAL_LAYER_LIMITS.maximumLayers) throw new InvalidConfigurationError(\"layers must list 1 to 16 shape-mask layers.\");\n const materials: ResolvedChartScene[\"materials\"][number][] = [...chart.materials], inputs: unknown[] = [...chartMaterialInputs(chart)];\n const layers = list.map((layer: unknown, index: number) => resolveLayer(layer, \"layers[\" + index + \"]\", aspect, materials, inputs));\n const materialAt = (u: number, v: number): number => {\n for (let l = layers.length - 1; l >= 0; l--) {\n const layer = layers[l]!, r = layer.rectangle;\n if (u < r.u0 || u > r.u1 || v < r.v0 || v > r.v1) continue;\n return layer.containsUnits((u - r.u0) * layer.scaleX, (r.v1 - v) * layer.scaleY) ? layer.inkMaterial : layer.backgroundMaterial;\n }\n return chart.materialAt(u, v);\n };\n const callerMaterials = materials.map((m, k) => (m.kind === \"neutral\" ? m.reflectance : inputs[k] as ChartReflectance));\n const callerScene: CallerMaterialScene = Object.freeze({ kind: \"material-function\" as const, sceneId: record.sceneId, materials: Object.freeze(callerMaterials),\n materialAt: (p: Readonly<SceneMaterialPoint>) => materialAt(p.chartU ?? p.u, p.chartV ?? p.v) });\n return Object.freeze({ kind: \"material-layers\" as const, sceneId: record.sceneId, chart, layers: Object.freeze(layers), materials: Object.freeze(materials),\n materialInputs: Object.freeze(inputs), materialAt, callerScene });\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import { type ChartScene, type ResolvedChartScene } from "./chart-scenes.js";
2
+ import { type ChartMaterialLayerScene, type ResolvedChartMaterialLayers } from "./chart-material-layers.js";
2
3
  import { type CaptureIlluminant, type ResolvedCaptureIlluminant } from "./scene-radiance.js";
3
4
  export declare const CHART_RADIANCE_BATCH_SCHEMA_VERSION: "0.1.0";
4
5
  /** `maximumQueries` is the default batch limit; a request may raise it with its own `maximumQueries`, up to `ceilingQueries`. */
@@ -10,8 +11,8 @@ export declare const CHART_RADIANCE_BATCH_LIMITS: Readonly<{
10
11
  }>;
11
12
  export interface ChartRadianceBatchInput {
12
13
  schemaVersion: typeof CHART_RADIANCE_BATCH_SCHEMA_VERSION;
13
- /** An engine chart preset (caller scene functions are not supported here). */
14
- scene: ChartScene;
14
+ /** An engine chart preset, or (#477) a declared `material-layers` scene: a chart under shape-mask layers. Caller scene functions are not supported here. */
15
+ scene: ChartScene | ChartMaterialLayerScene;
15
16
  illuminant: CaptureIlluminant;
16
17
  illuminanceLux: number;
17
18
  /** Frame height / width. */
@@ -32,7 +33,12 @@ export interface ChartRadianceBatchInput {
32
33
  }
33
34
  /** Validated plan with the resolved chart, illuminant and irradiance scale. Arrays are borrowed. */
34
35
  export interface ChartRadianceBatchPlan {
36
+ /** The resolved chart; with a `material-layers` scene, the chart under the layers. */
35
37
  chart: ResolvedChartScene;
38
+ /** The resolved layers of a `material-layers` scene, or null for a chart preset. */
39
+ layers: ResolvedChartMaterialLayers | null;
40
+ /** Every material a query can resolve to: the chart's, then each layer's background and ink. */
41
+ materials: ResolvedChartScene["materials"];
36
42
  illuminant: ResolvedCaptureIlluminant;
37
43
  irradianceScale: number;
38
44
  queryCount: number;
@@ -19,6 +19,7 @@ import { InvalidConfigurationError } from "../core/configuration-error.js";
19
19
  import { requireAllowlistedRecord } from "../core/record-validation.js";
20
20
  import { FULL_RESOLUTION_CEILING_PIXELS, resolveRaisedLimit } from "../core/pixel-limits.js";
21
21
  import { resolveChartScene } from "./chart-scenes.js";
22
+ import { resolveChartMaterialLayers } from "./chart-material-layers.js";
22
23
  import { buildPlanarSceneRadianceProvider, calculateIlluminantIrradianceScale, resolveCaptureIlluminant, tabulatedSpectrum } from "./scene-radiance.js";
23
24
  export const CHART_RADIANCE_BATCH_SCHEMA_VERSION = "0.1.0";
24
25
  /** `maximumQueries` is the default batch limit; a request may raise it with its own `maximumQueries`, up to `ceilingQueries`. */
@@ -31,7 +32,11 @@ export function calculateChartRadianceBatchPlan(input) {
31
32
  throw new InvalidConfigurationError("Unsupported chart radiance batch schema.");
32
33
  if (typeof r.aspect !== "number" || !(r.aspect > 0 && r.aspect <= Number.MAX_VALUE))
33
34
  throw new InvalidConfigurationError("aspect must be positive.");
34
- const chart = resolveChartScene({ scene: r.scene, aspect: r.aspect });
35
+ const sceneRecord = r.scene;
36
+ const layers = sceneRecord !== null && typeof sceneRecord === "object" && sceneRecord.kind === "material-layers"
37
+ ? resolveChartMaterialLayers({ scene: r.scene, aspect: r.aspect }) : null;
38
+ const chart = layers ? layers.chart : resolveChartScene({ scene: r.scene, aspect: r.aspect });
39
+ const materials = layers ? layers.materials : chart.materials;
35
40
  const illuminant = resolveCaptureIlluminant(r.illuminant);
36
41
  const irradianceScale = calculateIlluminantIrradianceScale(illuminant.shape, r.illuminanceLux);
37
42
  const L = CHART_RADIANCE_BATCH_LIMITS, points = r.points;
@@ -63,19 +68,21 @@ export function calculateChartRadianceBatchPlan(input) {
63
68
  throw new InvalidConfigurationError("channelWeights must hold 1 to 4 rows of one finite weight per wavelength.");
64
69
  }
65
70
  // The provider's own radiance function: chart reflectance × scaled illuminant / π, in its float64 order.
66
- const provider = buildPlanarSceneRadianceProvider({ scene: chart, sceneId: "chart-radiance-batch", providerProfileId: "chart-radiance-batch", illuminant,
71
+ const providerScene = layers ? { kind: "material-function", sceneId: layers.sceneId, materials: layers.materials, materialInputs: layers.materialInputs,
72
+ materialAt: (point) => layers.materialAt(point.u, point.v) } : chart;
73
+ const provider = buildPlanarSceneRadianceProvider({ scene: providerScene, sceneId: "chart-radiance-batch", providerProfileId: "chart-radiance-batch", illuminant,
67
74
  irradianceScale, geometry: { distanceM: 1, metresPerFrameUnit: 1 }, evidence: [], limitations: [] });
68
75
  const channelWeights = weights === undefined ? null : weights.map(row => Object.freeze([...row]));
69
76
  const nm = Object.freeze([...wavelengths]);
70
77
  const shapeAt = tabulatedSpectrum(illuminant.shape);
71
78
  const materialRadiance = (material, wavelength) => {
72
- const m = chart.materials[material];
79
+ const m = materials[material];
73
80
  const reflectance = m.kind === "neutral" ? m.reflectance : m.spectrum.evaluate(wavelength);
74
81
  if (m.kind === "spectrum" && !(reflectance >= 0 && reflectance <= 1))
75
82
  throw new InvalidConfigurationError("Chart reflectance spectrum left [0, 1].");
76
83
  return reflectance * irradianceScale * shapeAt(wavelength) / Math.PI;
77
84
  };
78
- return Object.freeze({ chart, illuminant, irradianceScale, queryCount, columns: channelWeights === null ? 1 : channelWeights.length,
85
+ return Object.freeze({ chart, layers, materials, illuminant, irradianceScale, queryCount, columns: channelWeights === null ? 1 : channelWeights.length,
79
86
  mode: channelWeights === null ? "radiance" : "spectral-integral", wavelengthsNanometers: nm, channelWeights, materialRadiance,
80
87
  queryValue(p, w, query, column) {
81
88
  const u = p[2 * query], v = p[2 * query + 1];