three-cad-viewer 5.0.6 → 5.0.7

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.
@@ -25,7 +25,10 @@ export declare const BACKGROUND_ID = 0;
25
25
  * `Uint32BufferAttribute` under this name; the pick pass reads it as an **integer**
26
26
  * attribute (`gpuType = THREE.IntType`, GLSL3 `in uint`) so ids stay exact past
27
27
  * 2^24. The geometry side must set `gpuType = THREE.IntType` on the attribute for
28
- * the pick shader to read it.
28
+ * the pick shader to read it. Between the stages the id travels as two float
29
+ * varyings (16-bit halves), deliberately NOT as a `flat uint` — see the note in
30
+ * highlight.ts: a `flat` varying costs ~25x the scene's buffer bytes in graphics
31
+ * memory under WebKit's ANGLE-on-Metal backend.
29
32
  */
30
33
  export declare const COMPONENT_ID_ATTRIBUTE = "componentId";
31
34
  /**
@@ -83999,7 +83999,10 @@ const BACKGROUND_ID = 0;
83999
83999
  * `Uint32BufferAttribute` under this name; the pick pass reads it as an **integer**
84000
84000
  * attribute (`gpuType = THREE.IntType`, GLSL3 `in uint`) so ids stay exact past
84001
84001
  * 2^24. The geometry side must set `gpuType = THREE.IntType` on the attribute for
84002
- * the pick shader to read it.
84002
+ * the pick shader to read it. Between the stages the id travels as two float
84003
+ * varyings (16-bit halves), deliberately NOT as a `flat uint` — see the note in
84004
+ * highlight.ts: a `flat` varying costs ~25x the scene's buffer bytes in graphics
84005
+ * memory under WebKit's ANGLE-on-Metal backend.
84003
84006
  */
84004
84007
  const COMPONENT_ID_ATTRIBUTE = "componentId";
84005
84008
  /**
@@ -84297,8 +84300,9 @@ const VERTEX_DEPTH_BIAS_FACTOR = 2e-4;
84297
84300
  */
84298
84301
  const PICK_POINT_SIZE = 3;
84299
84302
  /**
84300
- * Shared GLSL3 MRT fragment shader for all pick materials (face/vertex/edge): packs
84301
- * the flat `vId` into attachment 0 (byte order MUST match `packId()`: low byte in R)
84303
+ * Shared GLSL3 MRT fragment shader for all pick materials (face/vertex/edge):
84304
+ * reassembles the id from its two interpolated halves and packs it into attachment 0
84305
+ * (byte order MUST match `packId()`: low byte in R)
84302
84306
  * and writes the interpolated world position into attachment 1 (RGBA32F; `w=1` marks
84303
84307
  * "has position"). The fragment clip chunk only `discard`s (non-ALPHA_TO_COVERAGE),
84304
84308
  * so no `diffuseColor` symbol is required.
@@ -84306,13 +84310,15 @@ const PICK_POINT_SIZE = 3;
84306
84310
  const PICK_FRAGMENT_SHADER = /* glsl */ `
84307
84311
  #include <clipping_planes_pars_fragment>
84308
84312
 
84309
- flat in uint vId;
84313
+ in float vIdLo;
84314
+ in float vIdHi;
84310
84315
  in vec3 vWorldPos;
84311
84316
  layout(location = 0) out vec4 fragId;
84312
84317
  layout(location = 1) out vec4 fragPos;
84313
84318
 
84314
84319
  void main() {
84315
84320
  #include <clipping_planes_fragment>
84321
+ uint vId = ( uint( vIdHi + 0.5 ) << 16 ) | uint( vIdLo + 0.5 );
84316
84322
  fragId = vec4(
84317
84323
  float( vId & 0xFFu ) / 255.0,
84318
84324
  float( ( vId >> 8 ) & 0xFFu ) / 255.0,
@@ -84330,19 +84336,24 @@ const PICK_FRAGMENT_SHADER = /* glsl */ `
84330
84336
  function createFacePickMaterial(options = {}) {
84331
84337
  // GLSL3 (WebGL2). For a (non-Raw) ShaderMaterial three.js auto-injects
84332
84338
  // `in vec3 position;`, `uniform mat4 modelMatrix/modelViewMatrix/projectionMatrix`,
84333
- // so we declare ONLY the custom integer attribute + flat varying. Integer
84334
- // varyings MUST be `flat`. Clip chunks use legacy `varying`/`attribute` keywords
84339
+ // so we declare ONLY the custom integer attribute + the two id-half varyings
84340
+ // (float, interpolated — an integer varying would have to be `flat`, which is
84341
+ // the ANGLE-on-Metal memory trap described in highlight.ts; all vertices of a
84342
+ // primitive carry the same id, so interpolation + rounding is exact). Clip
84343
+ // chunks use legacy `varying`/`attribute` keywords
84335
84344
  // which three remaps via `#define` under GLSL3; the vertex chunk reads a local
84336
84345
  // `vec4 mvPosition`, which we compute before the include.
84337
84346
  const vertexShader = /* glsl */ `
84338
84347
  #include <clipping_planes_pars_vertex>
84339
84348
 
84340
84349
  in uint componentId;
84341
- flat out uint vId;
84350
+ out float vIdLo;
84351
+ out float vIdHi;
84342
84352
  out vec3 vWorldPos;
84343
84353
 
84344
84354
  void main() {
84345
- vId = componentId;
84355
+ vIdLo = float( componentId & 0xFFFFu );
84356
+ vIdHi = float( componentId >> 16 );
84346
84357
  vec4 worldPos = modelMatrix * vec4( position, 1.0 );
84347
84358
  vWorldPos = worldPos.xyz;
84348
84359
  vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
@@ -84381,13 +84392,15 @@ function createVertexPickMaterial(options = {}) {
84381
84392
  #include <clipping_planes_pars_vertex>
84382
84393
 
84383
84394
  in uint componentId;
84384
- flat out uint vId;
84395
+ out float vIdLo;
84396
+ out float vIdHi;
84385
84397
  out vec3 vWorldPos;
84386
84398
  uniform float uPickSize;
84387
84399
  uniform float uDepthBias;
84388
84400
 
84389
84401
  void main() {
84390
- vId = componentId;
84402
+ vIdLo = float( componentId & 0xFFFFu );
84403
+ vIdHi = float( componentId >> 16 );
84391
84404
  vec4 worldPos = modelMatrix * vec4( position, 1.0 );
84392
84405
  vWorldPos = worldPos.xyz;
84393
84406
  vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
@@ -84447,7 +84460,8 @@ function createEdgePickMaterial(options = {}) {
84447
84460
  in vec3 instanceEnd;
84448
84461
  in uint componentId;
84449
84462
 
84450
- flat out uint vId;
84463
+ out float vIdLo;
84464
+ out float vIdHi;
84451
84465
  out vec3 vWorldPos;
84452
84466
 
84453
84467
  void trimSegment( const in vec4 start, inout vec4 end ) {
@@ -84459,7 +84473,8 @@ function createEdgePickMaterial(options = {}) {
84459
84473
  }
84460
84474
 
84461
84475
  void main() {
84462
- vId = componentId;
84476
+ vIdLo = float( componentId & 0xFFFFu );
84477
+ vIdHi = float( componentId >> 16 );
84463
84478
 
84464
84479
  // World-space segment endpoints → approximate hit point along the edge.
84465
84480
  vWorldPos = ( position.y < 0.5 )
@@ -84997,20 +85012,41 @@ const U_HIGHLIGHT_HOVER_COLOR = "uHighlightHoverColor";
84997
85012
  //
84998
85013
  // three upgrades stock materials to GLSL ES 3.00 on WebGL2 via `#define attribute
84999
85014
  // in` / `#define varying out|in` macros (WebGLProgram.js), so writing `attribute` /
85000
- // `flat varying` here is converted automatically; `usampler2D` / `texelFetch` /
85015
+ // `varying` here is converted automatically; `usampler2D` / `texelFetch` /
85001
85016
  // integer attributes are then available.
85017
+ //
85018
+ // The component id crosses to the fragment stage as TWO ordinary (interpolated)
85019
+ // float varyings holding its 16-bit halves — NOT as a `flat uint`. A `flat`
85020
+ // varying is what an integer varying would require, and it is exactly what must
85021
+ // be avoided here: under WebKit's ANGLE-on-Metal backend, every draw whose
85022
+ // program has a `flat` varying makes ANGLE allocate a converted copy of the
85023
+ // draw's index data (>= 64 KB each, pooled, never returned to the GL on
85024
+ // deleteBuffer). With this varying on every visible material that was ~25x the
85025
+ // scene's vertex/index bytes in graphics memory (26 MB of buffers -> 812 MB;
85026
+ // 100 MB -> 2.3 GB) and it is charged to the page's WebContent process in the
85027
+ // system WebKit, which killed build123d Studio at 16 GB after a few large
85028
+ // shows. Without `flat` the same scene takes 63 MB / 160 MB. Measured 2026-09-17
85029
+ // (working-docs/leak-harness.html + probe2.html, Playwright WebKit and Safari).
85030
+ //
85031
+ // Interpolating floats is exact for the id's purpose: every vertex of a face,
85032
+ // every vertex of an instanced segment and a point carry the SAME id, so the
85033
+ // interpolated value equals it up to rounding, and `uint(x + 0.5)` recovers it.
85034
+ // Two 16-bit halves (both < 65536, exactly representable in float32) keep the
85035
+ // full 32-bit id range that `applyComponentIds` promises.
85002
85036
  // ---------------------------------------------------------------------------
85003
85037
  /**
85004
85038
  * Shared state-fetch GLSL, injected into BOTH stages: the vertex stage needs it for
85005
85039
  * widening / point size, the fragment for color. Declares the sampler + texWidth +
85006
- * the flat varying and a helper returning the component's {@link HighlightFlag} bits
85007
- * (0 for background / nothing).
85040
+ * the two id-half varyings and a helper returning the component's
85041
+ * {@link HighlightFlag} bits (0 for background / nothing).
85008
85042
  */
85009
85043
  const HL_STATE_GLSL = `
85010
- flat varying uint vHighlightId;
85044
+ varying float vHighlightIdLo;
85045
+ varying float vHighlightIdHi;
85011
85046
  uniform highp usampler2D ${U_HIGHLIGHT_STATE};
85012
85047
  uniform int ${U_HIGHLIGHT_TEX_WIDTH};
85013
85048
  uint highlightState() {
85049
+ uint vHighlightId = (uint(vHighlightIdHi + 0.5) << 16) | uint(vHighlightIdLo + 0.5);
85014
85050
  if (vHighlightId == 0u) return 0u;
85015
85051
  ivec2 hlUv = ivec2(
85016
85052
  int(vHighlightId) % ${U_HIGHLIGHT_TEX_WIDTH},
@@ -85025,8 +85061,8 @@ uint highlightState() {
85025
85061
  const HL_VERTEX_HEADER = `
85026
85062
  attribute uint ${COMPONENT_ID_ATTRIBUTE};
85027
85063
  ${HL_STATE_GLSL}`;
85028
- /** Vertex main: forward the id. Injected right after `void main() {`. */
85029
- const HL_VERTEX_ASSIGN = `vHighlightId = ${COMPONENT_ID_ATTRIBUTE};`;
85064
+ /** Vertex main: forward the id as two 16-bit halves. Injected right after `void main() {`. */
85065
+ const HL_VERTEX_ASSIGN = `vHighlightIdLo = float(${COMPONENT_ID_ATTRIBUTE} & 0xFFFFu); vHighlightIdHi = float(${COMPONENT_ID_ATTRIBUTE} >> 16);`;
85030
85066
  /** Fragment header: the shared state fetch + the two highlight colors. */
85031
85067
  const HL_FRAGMENT_HEADER = `
85032
85068
  ${HL_STATE_GLSL}
@@ -85276,7 +85312,7 @@ class HighlightController {
85276
85312
  this.uniforms.uHighlightSelectedColor;
85277
85313
  shader.uniforms[U_HIGHLIGHT_HOVER_COLOR] =
85278
85314
  this.uniforms.uHighlightHoverColor;
85279
- // Common: forward the component id as a flat varying.
85315
+ // Common: forward the component id (as two interpolated float halves).
85280
85316
  shader.vertexShader =
85281
85317
  HL_VERTEX_HEADER +
85282
85318
  "\n" +
@@ -88808,7 +88844,14 @@ class Grid extends Group {
88808
88844
  group.name = `GridHelper-${i}`;
88809
88845
  group.add(new GridHelper(this.size, 2 * this.ticks, this.colors[this.theme][i === 0 ? 1 : i === 1 ? 0 : 2], this.colors[this.theme][i === 0 ? 0 : i === 1 ? 2 : 1], this.theme == "dark" ? 0x7777777 : 0xbbbbbb));
88810
88846
  let label;
88811
- for (let x = -this.size / 2; x <= this.size / 2; x += this.delta / 2) {
88847
+ // A grid of no size has no labels, and asking for them hangs the tab:
88848
+ // `niceBounds` answers [0, 0, 0] for a bounding box whose largest extent
88849
+ // is zero - which is what an empty model gives it - so `delta` is zero
88850
+ // and the loop below steps by zero, never advancing and never throwing.
88851
+ // Guarded here rather than in `niceBounds`, whose answer is right: there
88852
+ // is nothing to place ticks on.
88853
+ const step = this.delta / 2;
88854
+ for (let x = -this.size / 2; step > 0 && x <= this.size / 2; x += step) {
88812
88855
  if (Math.abs(x) < 1e-6) {
88813
88856
  continue;
88814
88857
  } // skip center label
@@ -97350,7 +97393,7 @@ class Tools {
97350
97393
  }
97351
97394
  }
97352
97395
 
97353
- const version = "5.0.6";
97396
+ const version = "5.0.7";
97354
97397
 
97355
97398
  /**
97356
97399
  * `PickedComponent` over a GPU id-pick result. Drives the shader
@@ -110820,6 +110863,11 @@ class Viewer {
110820
110863
  deepDispose(this._rendered.camera);
110821
110864
  deepDispose(this._rendered.controls);
110822
110865
  deepDispose(this._rendered.treeview);
110866
+ // The orientation marker owns its own THREE.Scene (cones, labels, sphere,
110867
+ // axes), not part of the main scene above, so it needs its own dispose —
110868
+ // without it every clear()/render() cycle left 8 geometries and 3 programs
110869
+ // behind on the GL (measured 2026-09-17, working-docs/leak-harness.html).
110870
+ this._rendered.orientationMarker.dispose();
110823
110871
  // clear tree view
110824
110872
  this.display.clearCadTree();
110825
110873
  // clear info