@woosh/meep-engine 3.60.2 → 3.60.3

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.
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.60.2",
9
+ "version": "3.60.3",
10
10
  "exports": {
11
11
  "./build/*": "./build/*",
12
12
  "./src/*": "./src/*",
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Samples per texel. Each reads the average whose texel matches the solid angle it stands for, so
3
- * the count sets the noise of the lobe's shape, not whether a bright texel is counted. Measured on a
4
- * single bright texel of a 1024 map at 48 places, level 1 over level 0 in solid-angle energy:
5
- * 32 → 0.995 ± 0.014, 64 → 0.998 ± 0.008 (at most 1.005), 196 → 1.001 ± 0.019 — 196 asks for a
6
- * finer average than the 2x2 one, so it point-samples level 0 again. UE filters with 32 below
7
- * roughness 0.1 and 64 above.
3
+ * fewer samples read coarser averages and more read finer ones, back toward point taps that catch or
4
+ * miss a bright texel. Measured on a single bright texel of a 1024 map at 48 places, level 1 over
5
+ * level 0 in solid-angle energy: 32 → 0.995 ± 0.014, 64 → 0.998 ± 0.008 (at most 1.005),
6
+ * 196 → 1.001 ± 0.019 — at 196 the lod is 0.5 .. 1.7, and every read below 1 blends in level 0's
7
+ * point taps. UE filters with 32 below roughness 0.1 and 64 above.
8
8
  * @type {number}
9
9
  */
10
10
  export const CONVOLVE_FROM_AVERAGES_SAMPLE_COUNT: number;
@@ -1 +1 @@
1
- {"version":3,"file":"shader_convolve_reflection_map_from_averages.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/light/environment/shader_convolve_reflection_map_from_averages.js"],"names":[],"mappings":"AAoCA;;;;;;;;GAQG;AACH,kDAFU,MAAM,CAEsC;AAuHtD,yEAII;8BAtJ0B,+BAA+B"}
1
+ {"version":3,"file":"shader_convolve_reflection_map_from_averages.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/light/environment/shader_convolve_reflection_map_from_averages.js"],"names":[],"mappings":"AAqCA;;;;;;;;GAQG;AACH,kDAFU,MAAM,CAEsC;AAmJtD,yEAII;8BAlL0B,+BAA+B"}
@@ -9,6 +9,7 @@ import {
9
9
  chunk_uv_octahedral_unit_encode
10
10
  } from "../../shader/chunk/geometry/octahedron/chunk_uv_octahedral_unit_encode.js";
11
11
  import { chunk_uniform_sample_cone } from "../../shader/chunk/geometry/ray/chunk_uniform_sample_cone.js";
12
+ import { chunk_pow2 } from "../../shader/chunk/math/chunk_pow2.js";
12
13
  import { chunk_safe_rcp } from "../../shader/chunk/math/chunk_safe_rcp.js";
13
14
  import { chunk_hammersley_2d } from "../../shader/chunk/noise/chunk_hammersley_2d.js";
14
15
  import {
@@ -36,15 +37,31 @@ resources.createGroup()
36
37
 
37
38
  /**
38
39
  * Samples per texel. Each reads the average whose texel matches the solid angle it stands for, so
39
- * the count sets the noise of the lobe's shape, not whether a bright texel is counted. Measured on a
40
- * single bright texel of a 1024 map at 48 places, level 1 over level 0 in solid-angle energy:
41
- * 32 → 0.995 ± 0.014, 64 → 0.998 ± 0.008 (at most 1.005), 196 → 1.001 ± 0.019 — 196 asks for a
42
- * finer average than the 2x2 one, so it point-samples level 0 again. UE filters with 32 below
43
- * roughness 0.1 and 64 above.
40
+ * fewer samples read coarser averages and more read finer ones, back toward point taps that catch or
41
+ * miss a bright texel. Measured on a single bright texel of a 1024 map at 48 places, level 1 over
42
+ * level 0 in solid-angle energy: 32 → 0.995 ± 0.014, 64 → 0.998 ± 0.008 (at most 1.005),
43
+ * 196 → 1.001 ± 0.019 — at 196 the lod is 0.5 .. 1.7, and every read below 1 blends in level 0's
44
+ * point taps. UE filters with 32 below roughness 0.1 and 64 above.
44
45
  * @type {number}
45
46
  */
46
47
  export const CONVOLVE_FROM_AVERAGES_SAMPLE_COUNT = 64;
47
48
 
49
+ /**
50
+ * How much a read of the averages widens the lobe, as mean squared angle per steradian a sample
51
+ * stands for. The lobe exp(2 (N·H - 1) / r^4) is a Gaussian of mean squared angle 4 r^4 (N·H =
52
+ * cos(θ/2), so N·H - 1 ≈ -θ²/8), and convolving adds variances, so the lobe is narrowed to
53
+ * r^4 - k Ω_s / 4 and lands at the width it is asked for.
54
+ *
55
+ * A box over the texel plus the bilinear tent across its neighbours predicts 1/2; 1 is measured. On
56
+ * a 1024 map's sun against level 1 from 8192 point taps (1.315° rms): k = 0 → 1.436°, 1/2 → 1.380°,
57
+ * 1 → 1.317°; the sun's tail error 110% → 49%. The two reads a lod between levels blends, and an
58
+ * average's footprint 2 + 2/n texels of the one above rather than 2, are the likely difference.
59
+ * Matching the width leaves the shape: the core is flatter than a Gaussian, and the sky's rms error
60
+ * against the reference goes 0.166 → 0.222.
61
+ * @type {number}
62
+ */
63
+ const AVERAGES_BLUR_PER_SOLID_ANGLE = 1;
64
+
48
65
  /**
49
66
  * Level 1 of the prefilter, by filtered importance sampling: each sample reads the plain average of
50
67
  * level 0 whose texel is the solid angle the sample stands for, so a texel brighter than its
@@ -53,6 +70,11 @@ export const CONVOLVE_FROM_AVERAGES_SAMPLE_COUNT = 64;
53
70
  * one bright texel's share of level 1 anywhere from 0.79 to 1.21 of its energy, and an HDR sun
54
71
  * came out 0.66% brighter than it went in.
55
72
  *
73
+ * Samples are uniform in the cone, so each stands for the same solid angle and reads the same
74
+ * average: the averages blur the lobe by one fixed amount, which the lobe gives back by narrowing
75
+ * (see AVERAGES_BLUR_PER_SOLID_ANGLE). UE's importance sampling read fine averages in the lobe's core
76
+ * and coarse ones in its tail, and the sun came out in blocks.
77
+ *
56
78
  * Level 1 only. The levels after it read the level above, already smooth at their sample spacing
57
79
  * (see shader_convolve_reflection_map). Read from level 0 instead, their samples land on averages
58
80
  * 8 to 16 texels a side, too coarse an octahedral grid to keep a bright texel's energy: ±11% at
@@ -112,6 +134,12 @@ fn main(
112
134
  // uniform in the cone, so every sample stands for the same solid angle
113
135
  let sample_solid_angle = 2.0 * PI * (1.0 - cone_cos) / f32(sample_count);
114
136
 
137
+ // narrowed by the blur the averages add, so the lobe comes out at the roughness asked for
138
+ let lobe_roughness = sqrt(sqrt(max(
139
+ pow2(pow2(roughness)) - ${AVERAGES_BLUR_PER_SOLID_ANGLE.toFixed(6)} * sample_solid_angle / 4.0,
140
+ pow2(pow2(${BSDF_ROUGHNESS_MINIMUM}))
141
+ )));
142
+
115
143
  // level 0's octahedral square, n texels a side, spans 2 in octahedron coordinates; a texel there
116
144
  // covers (2/n)^2 of it, and the octahedron's solid angle per unit area is |d|_1^3 along unit d
117
145
  let level_0_interior = f32(textureDimensions(level_0).x) - 2.0;
@@ -137,7 +165,7 @@ fn main(
137
165
  lod - lod_floor
138
166
  );
139
167
 
140
- let weight = reflection_sample_weight(out_direction, in_direction, roughness);
168
+ let weight = reflection_sample_weight(out_direction, in_direction, lobe_roughness);
141
169
 
142
170
  radiance_accum += radiance * weight;
143
171
  weight_accum += weight;
@@ -152,6 +180,7 @@ fn main(
152
180
  chunk_hammersley_2d,
153
181
  chunk_build_orthonormal_matrix_n,
154
182
  chunk_safe_rcp,
183
+ chunk_pow2,
155
184
  chunk_uniform_sample_cone,
156
185
  chunk_texture_octahedral_skirt_texel_uv,
157
186
  chunk_texture_octahedral_skirt_uv,
@@ -140,6 +140,25 @@ export class SegmentedResourceBarView extends View {
140
140
  * @private
141
141
  */
142
142
  private __columns;
143
+ /**
144
+ * scratch for {@link segment_frame_transform}: [scale, offset]
145
+ * @type {number[]}
146
+ * @private
147
+ */
148
+ private __frame;
149
+ /**
150
+ * The timer that re-checks the placement while linked; 0 when none
151
+ * @type {number}
152
+ * @private
153
+ */
154
+ private __recheck_timer;
155
+ /**
156
+ * Place again if anything moved that no event reports: an ancestor's transform, the transform of an iframe the
157
+ * bar sits in, a move by a fraction of a pixel. Cheap when nothing did — a computed width and a few rects —
158
+ * and {@link updateSegments} rebuilds only on a change.
159
+ * @private
160
+ */
161
+ private __recheck;
143
162
  /**
144
163
  *
145
164
  * @type {List<NumericInterval>}
@@ -1 +1 @@
1
- {"version":3,"file":"SegmentedResourceBarView.d.ts","sourceRoot":"","sources":["../../../../../../src/view/elements/progress/segmented/SegmentedResourceBarView.js"],"names":[],"mappings":"AA4BA;;;;;;;;;;;;;;;;;GAiBG;AACH;IACI;;;;;;;;;;;;OAYG;IACH;QAVgD,MAAM,EAA3C,CAAC,OAAO,kBAAgB,CAAC,EAAE;QAC0B,GAAG,GAAxD,OAAO,uCAAmC;QACvB,SAAS,GAA5B,MAAM,EAAE;QAC6E,KAAK,GAA1F;YAAC,IAAI,EAAC,MAAM,CAAC;YAAC,UAAU,EAAC,MAAM,CAAC;YAAC,QAAQ,EAAC,MAAM,CAAC;YAAC,cAAc,EAAC,MAAM,CAAA;SAAC,EAAE;QAC9B,KAAK,GAAjD;YAAC,KAAK,EAAC,MAAM,CAAC;YAAC,KAAK,EAAC,MAAM,CAAA;SAAC,GAAC,IAAI;QAChB,cAAc,GAA/B,MAAM;QACW,qBAAqB,GAAtC,MAAM;QACW,qBAAqB,GAAtC,MAAM;OAyShB;IA5QG;;;;OAIG;IACH,yBAA8B;IAE9B;;;;OAIG;IACH,oBAAsB;IAEtB;;;OAGG;IACH,gBAAoB;IAEpB;;;OAGG;IACH,gBAAoB;IAEpB;;;OAGG;IACH,gBAA+B;IAG/B;;;OAGG;IACH,gCAAoD;IAEpD;;;OAGG;IACH,gCAAoD;IAEpD;;;;OAIG;IACH,yBAAyB;IAEzB;;;OAGG;IACH,yBAAgH;IAEhH;;;;OAIG;IACH,oBAAuB;IAgiB3B;;OAEG;IACH,4BASC;IAxiBG,yBAA4B;IAC5B,mBAAmB;IA6fvB;;OAEG;IACH,wBAQC;IArgBG;;;;OAIG;IACH,oBAAwB;IAGxB,oBAAsB;IACtB,sBAAwB;IACxB,sBAAwB;IACxB,6BAA+B;IAE/B;;;;OAIG;IACH,sBAAyB;IAEzB;;;;OAIG;IACH,qBAAqB;IAErB;;;;OAIG;IACH,kBAAmB;IAEnB;;;OAGG;IACH,YAFU,qBAAqB,CAEH;IAG5B,mBAAuC;IASvC;;;OAGG;IACH,mBAA+C;IAG/C;;;OAGG;IACH,sBAAuB;IAEvB;;;;OAIG;IACH,sBAAuB;IAEvB;;;;OAIG;IACH,mBAAsB;IACtB,gCAA2B;IAC3B,+BAA0B;IAkBtB;;;;OAIG;IACH,oBAAgD;IAoCpD;;;OAGG;IACH,gBAFU,QAAQ,CAsChB;IAWF;;;;OAIG;IACH,uBAA2C;IAuC/C,eAGC;IAED,wBAMC;IAED;;;;;;;OAOG;IACH,uBA4DC;IAED;;;;;;OAMG;IACH,sBAKC;IAED;;;;;;OAMG;IACH,wBA2CC;IAED;;;;;OAKG;IACH,sBAoBC;IAED,mBAiDC;IAED;;;;OAIG;IACH,eAaC;IAED;;;;OAIG;IACH,qBAoBC;IAeD;;;;OAIG;IACH,mCAUC;CAeJ;iBA5rBgB,kBAAkB;iBALlB,0CAA0C;qBAGtC,6BAA6B;oBAF9B,kCAAkC"}
1
+ {"version":3,"file":"SegmentedResourceBarView.d.ts","sourceRoot":"","sources":["../../../../../../src/view/elements/progress/segmented/SegmentedResourceBarView.js"],"names":[],"mappings":"AAoCA;;;;;;;;;;;;;;;;;GAiBG;AACH;IACI;;;;;;;;;;;;OAYG;IACH;QAVgD,MAAM,EAA3C,CAAC,OAAO,kBAAgB,CAAC,EAAE;QAC0B,GAAG,GAAxD,OAAO,uCAAmC;QACvB,SAAS,GAA5B,MAAM,EAAE;QAC6E,KAAK,GAA1F;YAAC,IAAI,EAAC,MAAM,CAAC;YAAC,UAAU,EAAC,MAAM,CAAC;YAAC,QAAQ,EAAC,MAAM,CAAC;YAAC,cAAc,EAAC,MAAM,CAAA;SAAC,EAAE;QAC9B,KAAK,GAAjD;YAAC,KAAK,EAAC,MAAM,CAAC;YAAC,KAAK,EAAC,MAAM,CAAA;SAAC,GAAC,IAAI;QAChB,cAAc,GAA/B,MAAM;QACW,qBAAqB,GAAtC,MAAM;QACW,qBAAqB,GAAtC,MAAM;OAyThB;IA5RG;;;;OAIG;IACH,yBAA8B;IAE9B;;;;OAIG;IACH,oBAAsB;IAEtB;;;OAGG;IACH,gBAAoB;IAEpB;;;OAGG;IACH,gBAAoB;IAEpB;;;OAGG;IACH,gBAA+B;IAG/B;;;OAGG;IACH,gCAAoD;IAEpD;;;OAGG;IACH,gCAAoD;IAEpD;;;;OAIG;IACH,yBAAyB;IAEzB;;;OAGG;IACH,yBAAgH;IAEhH;;;;OAIG;IACH,oBAAuB;IA0kB3B;;OAEG;IACH,4BASC;IAllBG,yBAA4B;IAC5B,mBAAmB;IAuiBvB;;OAEG;IACH,wBAQC;IA/iBG;;;;OAIG;IACH,oBAAwB;IAGxB,oBAAsB;IACtB,sBAAwB;IACxB,sBAAwB;IACxB,6BAA+B;IAE/B;;;;OAIG;IACH,sBAAyB;IAEzB;;;;OAIG;IACH,qBAAqB;IAErB;;;;OAIG;IACH,kBAAmB;IAEnB;;;;OAIG;IACH,gBAAqB;IAErB;;;;OAIG;IACH,wBAAwB;IA0e5B;;;;;OAKG;IACH,kBAIC;IAhfG;;;OAGG;IACH,YAFU,qBAAqB,CAEH;IAG5B,mBAAuC;IASvC;;;OAGG;IACH,mBAA+C;IAG/C;;;OAGG;IACH,sBAAuB;IAEvB;;;;OAIG;IACH,sBAAuB;IAEvB;;;;OAIG;IACH,mBAAsB;IACtB,gCAA2B;IAC3B,+BAA0B;IAkBtB;;;;OAIG;IACH,oBAAgD;IAoCpD;;;OAGG;IACH,gBAFU,QAAQ,CAsChB;IAWF;;;;OAIG;IACH,uBAA2C;IAgD/C,eAGC;IAED,wBAMC;IAED;;;;;;;OAOG;IACH,uBAiEC;IAED;;;;;;OAMG;IACH,sBAKC;IAED;;;;;;OAMG;IACH,wBA2CC;IAED;;;;;OAKG;IACH,sBAoBC;IAED,mBAiDC;IAED;;;;OAIG;IACH,eAaC;IAED;;;;OAIG;IACH,qBAoBC;IA2BD;;;;OAIG;IACH,mCAUC;CAeJ;iBA9uBgB,kBAAkB;iBALlB,0CAA0C;qBAGtC,6BAA6B;oBAF9B,kCAAkC"}
@@ -10,6 +10,7 @@ import { GhostTrail } from "./GhostTrail.js";
10
10
  import { SEGMENT_TIERS_DEFAULT } from "./SEGMENT_TIERS_DEFAULT.js";
11
11
  import { segment_cell_extent } from "./segment_cell_extent.js";
12
12
  import { segment_cell_layout } from "./segment_cell_layout.js";
13
+ import { segment_frame_transform } from "./segment_frame_transform.js";
13
14
  import { segment_mask_apply, segment_mask_url } from "./segment_mask_url.js";
14
15
  import { segment_mask_width } from "./segment_mask_width.js";
15
16
  import { segment_tick_positions } from "./segment_tick_positions.js";
@@ -26,6 +27,13 @@ const REBUILD_WIDTH_THRESHOLD_PX = 0.5;
26
27
  */
27
28
  const UNSCALED_TOLERANCE = 2e-3;
28
29
 
30
+ /**
31
+ * How often a linked bar checks that its notches or cells still sit on the screen's pixels, in milliseconds. A
32
+ * transform on an ancestor or on a containing iframe moves them without any event the bar hears.
33
+ * @type {number}
34
+ */
35
+ const RECHECK_INTERVAL_MS = 250;
36
+
29
37
  /**
30
38
  * Resource bar in the style of a MOBA health bar: the values fill it end to end, notches mark fixed amounts, and
31
39
  * a ghost behind the fill trails a drop so the loss stays readable for a moment.
@@ -189,6 +197,22 @@ export class SegmentedResourceBarView extends View {
189
197
  */
190
198
  this.__columns = [];
191
199
 
200
+ /**
201
+ * scratch for {@link segment_frame_transform}: [scale, offset]
202
+ * @type {number[]}
203
+ * @private
204
+ */
205
+ this.__frame = [1, 0];
206
+
207
+ /**
208
+ * The timer that re-checks the placement while linked; 0 when none
209
+ * @type {number}
210
+ * @private
211
+ */
212
+ this.__recheck_timer = 0;
213
+
214
+ this.__recheck = this.__recheck.bind(this);
215
+
192
216
  /**
193
217
  *
194
218
  * @type {List<NumericInterval>}
@@ -364,6 +388,10 @@ export class SegmentedResourceBarView extends View {
364
388
  this.__sizeObserver.start();
365
389
  this.__watch_device_pixel_ratio();
366
390
  this.update();
391
+
392
+ if (typeof setInterval === 'function') {
393
+ this.__recheck_timer = setInterval(this.__recheck, RECHECK_INTERVAL_MS);
394
+ }
367
395
  }
368
396
 
369
397
  unlink() {
@@ -373,6 +401,11 @@ export class SegmentedResourceBarView extends View {
373
401
 
374
402
  this.__sizeObserver.stop();
375
403
 
404
+ if (this.__recheck_timer !== 0) {
405
+ clearInterval(this.__recheck_timer);
406
+ this.__recheck_timer = 0;
407
+ }
408
+
376
409
  if (this.__dpr_query !== null) {
377
410
  this.__dpr_query.removeEventListener('change', this.__handle_dpr_change);
378
411
  this.__dpr_query = null;
@@ -430,16 +463,21 @@ export class SegmentedResourceBarView extends View {
430
463
 
431
464
  const dpr = typeof devicePixelRatio === 'number' && devicePixelRatio > 0 ? devicePixelRatio : 1;
432
465
 
466
+ // how the iframes this bar sits in are drawn by the pages around them — invisible from in here
467
+ const frame = segment_frame_transform(el.ownerDocument.defaultView, this.__frame);
468
+ const frame_scale = frame[0];
469
+ const frame_offset = frame[1];
470
+
433
471
  // a CSS scale on an ancestor shows each CSS pixel of the bar as `scale` page pixels. The computed width is
434
472
  // serialised to a few decimals (175.844 for 175.84375), so an unscaled bar measures a hair off 1 — enough to
435
473
  // put a width that should be whole in device pixels just past it — and is read as unscaled
436
- const measured_scale = rect.width > 0 ? rect.width / layout_width : 1;
474
+ const measured_scale = (rect.width > 0 ? rect.width / layout_width : 1) * frame_scale;
437
475
  const scale = Math.abs(measured_scale - 1) < UNSCALED_TOLERANCE ? 1 : measured_scale;
438
476
 
439
477
  // device pixels per CSS pixel of the bar
440
478
  const ratio = dpr * scale;
441
479
 
442
- const origin_device = rect.left * dpr;
480
+ const origin_device = (frame_offset + rect.left * frame_scale) * dpr;
443
481
  const origin_phase = origin_device - Math.round(origin_device);
444
482
 
445
483
  const max = this.__value_max.getValue();
@@ -661,6 +699,18 @@ export class SegmentedResourceBarView extends View {
661
699
  ghost.width = `${segment_cell_extent(layout, ghost_fraction * count) / ratio}px`;
662
700
  }
663
701
 
702
+ /**
703
+ * Place again if anything moved that no event reports: an ancestor's transform, the transform of an iframe the
704
+ * bar sits in, a move by a fraction of a pixel. Cheap when nothing did — a computed width and a few rects —
705
+ * and {@link updateSegments} rebuilds only on a change.
706
+ * @private
707
+ */
708
+ __recheck() {
709
+ if (this.isLinked) {
710
+ this.updateSegments();
711
+ }
712
+ }
713
+
664
714
  /**
665
715
  * @private
666
716
  */
@@ -0,0 +1,15 @@
1
+ /**
2
+ * How a point in a window's CSS pixels lands in the top-most window this one can see: `top = offset + local * scale`,
3
+ * through every same-origin iframe it sits in, each frame's own transform included.
4
+ *
5
+ * Nothing inside an iframe can see how the page around it draws it: an iframe the host scales to fit (a preview, a
6
+ * playground) reports `devicePixelRatio` and every rect as though unscaled, so something drawn for that pixel grid
7
+ * is painted on another. Walking out through `frameElement` is the one way to find out. A cross-origin frame stops
8
+ * the walk (its `frameElement` reads null, or throws), and is taken as unscaled from there out.
9
+ *
10
+ * @param {Window|null|undefined} win the window the element is in
11
+ * @param {number[]} result receives [scale, offset]
12
+ * @returns {number[]} result
13
+ */
14
+ export function segment_frame_transform(win: Window | null | undefined, result: number[]): number[];
15
+ //# sourceMappingURL=segment_frame_transform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segment_frame_transform.d.ts","sourceRoot":"","sources":["../../../../../../src/view/elements/progress/segmented/segment_frame_transform.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,6CAJW,MAAM,GAAC,IAAI,GAAC,SAAS,UACrB,MAAM,EAAE,GACN,MAAM,EAAE,CAyDpB"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * How a point in a window's CSS pixels lands in the top-most window this one can see: `top = offset + local * scale`,
3
+ * through every same-origin iframe it sits in, each frame's own transform included.
4
+ *
5
+ * Nothing inside an iframe can see how the page around it draws it: an iframe the host scales to fit (a preview, a
6
+ * playground) reports `devicePixelRatio` and every rect as though unscaled, so something drawn for that pixel grid
7
+ * is painted on another. Walking out through `frameElement` is the one way to find out. A cross-origin frame stops
8
+ * the walk (its `frameElement` reads null, or throws), and is taken as unscaled from there out.
9
+ *
10
+ * @param {Window|null|undefined} win the window the element is in
11
+ * @param {number[]} result receives [scale, offset]
12
+ * @returns {number[]} result
13
+ */
14
+ export function segment_frame_transform(win, result) {
15
+ let scale = 1;
16
+ let offset = 0;
17
+
18
+ let w = win;
19
+
20
+ for (let depth = 0; depth < 16 && w; depth++) {
21
+ let frame = null;
22
+
23
+ try {
24
+ frame = w.frameElement;
25
+ } catch (e) {
26
+ frame = null;
27
+ }
28
+
29
+ if (!frame) {
30
+ break;
31
+ }
32
+
33
+ const host = frame.ownerDocument && frame.ownerDocument.defaultView;
34
+
35
+ if (!host) {
36
+ break;
37
+ }
38
+
39
+ const style = host.getComputedStyle(frame);
40
+
41
+ const border_left = parseFloat(style.borderLeftWidth) || 0;
42
+ const border_right = parseFloat(style.borderRightWidth) || 0;
43
+ const padding_left = parseFloat(style.paddingLeft) || 0;
44
+ const padding_right = parseFloat(style.paddingRight) || 0;
45
+
46
+ let layout_width = parseFloat(style.width);
47
+
48
+ if (style.boxSizing !== 'border-box') {
49
+ layout_width += border_left + border_right + padding_left + padding_right;
50
+ }
51
+
52
+ const rect = frame.getBoundingClientRect();
53
+
54
+ const frame_scale = layout_width > 0 && rect.width > 0 ? rect.width / layout_width : 1;
55
+
56
+ // where the frame's content starts, in the host window's CSS pixels
57
+ const content_left = rect.left + (border_left + padding_left) * frame_scale;
58
+
59
+ offset = content_left + offset * frame_scale;
60
+ scale *= frame_scale;
61
+
62
+ w = host;
63
+ }
64
+
65
+ result[0] = scale;
66
+ result[1] = offset;
67
+
68
+ return result;
69
+ }