copper3d 3.6.2 → 3.6.4

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 (54) hide show
  1. package/dist/Loader/copperNrrdLoader.js +1 -1
  2. package/dist/Loader/copperNrrdLoader.js.map +1 -1
  3. package/dist/Scene/copperScene.d.ts +4 -4
  4. package/dist/Scene/copperScene.js +4 -4
  5. package/dist/Scene/copperScene.js.map +1 -1
  6. package/dist/Utils/surfaceAnnotation/MeshGraph.d.ts +12 -10
  7. package/dist/Utils/surfaceAnnotation/MeshGraph.js +14 -12
  8. package/dist/Utils/surfaceAnnotation/MeshGraph.js.map +1 -1
  9. package/dist/Utils/surfaceAnnotation/SurfaceAnnotator.d.ts +48 -28
  10. package/dist/Utils/surfaceAnnotation/SurfaceAnnotator.js +76 -50
  11. package/dist/Utils/surfaceAnnotation/SurfaceAnnotator.js.map +1 -1
  12. package/dist/Utils/surfaceAnnotation/annotationStore.d.ts +4 -4
  13. package/dist/Utils/surfaceAnnotation/annotationStore.js +4 -4
  14. package/dist/Utils/surfaceAnnotation/annotationStore.js.map +1 -1
  15. package/dist/Utils/surfaceAnnotation/contourRender.d.ts +1 -1
  16. package/dist/Utils/surfaceAnnotation/contourRender.js +16 -11
  17. package/dist/Utils/surfaceAnnotation/contourRender.js.map +1 -1
  18. package/dist/Utils/surfaceAnnotation/geodesicContour.d.ts +11 -10
  19. package/dist/Utils/surfaceAnnotation/geodesicContour.js +16 -15
  20. package/dist/Utils/surfaceAnnotation/geodesicContour.js.map +1 -1
  21. package/dist/Utils/surfaceAnnotation/pointMarkers.d.ts +3 -2
  22. package/dist/Utils/surfaceAnnotation/pointMarkers.js +6 -2
  23. package/dist/Utils/surfaceAnnotation/pointMarkers.js.map +1 -1
  24. package/dist/Utils/surfaceAnnotation/raycastSurface.d.ts +3 -2
  25. package/dist/Utils/surfaceAnnotation/raycastSurface.js +71 -5
  26. package/dist/Utils/surfaceAnnotation/raycastSurface.js.map +1 -1
  27. package/dist/Utils/surfaceAnnotation/strokeContour.d.ts +9 -6
  28. package/dist/Utils/surfaceAnnotation/strokeContour.js +15 -10
  29. package/dist/Utils/surfaceAnnotation/strokeContour.js.map +1 -1
  30. package/dist/Utils/surfaceAnnotation/types.d.ts +5 -5
  31. package/dist/Utils/surfaceAnnotation/types.js +2 -2
  32. package/dist/Utils/surfaceAnnotation/types.js.map +1 -1
  33. package/dist/Utils/texture2d.js +4 -2
  34. package/dist/Utils/texture2d.js.map +1 -1
  35. package/dist/Utils/workers/reformatSaveDataWorker.js +2 -2
  36. package/dist/Utils/workers/reformatSaveDataWorker.js.map +1 -1
  37. package/dist/bundle.esm.js +238 -129
  38. package/dist/bundle.umd.js +238 -129
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +1 -1
  41. package/dist/lib/three-vignette.js +8 -2
  42. package/dist/lib/three-vignette.js.map +1 -1
  43. package/dist/types/Scene/copperScene.d.ts +4 -4
  44. package/dist/types/Utils/surfaceAnnotation/MeshGraph.d.ts +12 -10
  45. package/dist/types/Utils/surfaceAnnotation/SurfaceAnnotator.d.ts +48 -28
  46. package/dist/types/Utils/surfaceAnnotation/annotationStore.d.ts +4 -4
  47. package/dist/types/Utils/surfaceAnnotation/contourRender.d.ts +1 -1
  48. package/dist/types/Utils/surfaceAnnotation/geodesicContour.d.ts +11 -10
  49. package/dist/types/Utils/surfaceAnnotation/pointMarkers.d.ts +3 -2
  50. package/dist/types/Utils/surfaceAnnotation/raycastSurface.d.ts +3 -2
  51. package/dist/types/Utils/surfaceAnnotation/strokeContour.d.ts +9 -6
  52. package/dist/types/Utils/surfaceAnnotation/types.d.ts +5 -5
  53. package/dist/types/index.d.ts +1 -1
  54. package/package.json +1 -1
@@ -40865,9 +40865,9 @@ class CameraViewPoint {
40865
40865
  }
40866
40866
  }
40867
40867
 
40868
- var vert = "#define GLSLIFY 1\nattribute vec3 position;uniform mat4 modelViewMatrix;uniform mat4 projectionMatrix;varying vec2 vUv;void main(){gl_Position=vec4(position,1.0);vUv=vec2(position.x,position.y)*0.5+0.5;}"; // eslint-disable-line
40868
+ var vert = "attribute vec3 position;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nvarying vec2 vUv;\nvoid main() {\n gl_Position = vec4(position, 1.0);\n vUv = vec2(position.x, position.y) * 0.5 + 0.5;\n}\n";
40869
40869
 
40870
- var frag = "precision mediump float;\n#define GLSLIFY 1\nvec3 mod289_0(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289_0(vec4 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 permute_0(vec4 x){return mod289_0(((x*34.0)+1.0)*x);}vec4 taylorInvSqrt_0(vec4 r){return 1.79284291400159-0.85373472095314*r;}vec3 fade(vec3 t){return t*t*t*(t*(t*6.0-15.0)+10.0);}float pnoise(vec3 P,vec3 rep){vec3 Pi0=mod(floor(P),rep);vec3 Pi1=mod(Pi0+vec3(1.0),rep);Pi0=mod289_0(Pi0);Pi1=mod289_0(Pi1);vec3 Pf0=fract(P);vec3 Pf1=Pf0-vec3(1.0);vec4 ix=vec4(Pi0.x,Pi1.x,Pi0.x,Pi1.x);vec4 iy=vec4(Pi0.yy,Pi1.yy);vec4 iz0=Pi0.zzzz;vec4 iz1=Pi1.zzzz;vec4 ixy=permute_0(permute_0(ix)+iy);vec4 ixy0=permute_0(ixy+iz0);vec4 ixy1=permute_0(ixy+iz1);vec4 gx0=ixy0*(1.0/7.0);vec4 gy0=fract(floor(gx0)*(1.0/7.0))-0.5;gx0=fract(gx0);vec4 gz0=vec4(0.5)-abs(gx0)-abs(gy0);vec4 sz0=step(gz0,vec4(0.0));gx0-=sz0*(step(0.0,gx0)-0.5);gy0-=sz0*(step(0.0,gy0)-0.5);vec4 gx1=ixy1*(1.0/7.0);vec4 gy1=fract(floor(gx1)*(1.0/7.0))-0.5;gx1=fract(gx1);vec4 gz1=vec4(0.5)-abs(gx1)-abs(gy1);vec4 sz1=step(gz1,vec4(0.0));gx1-=sz1*(step(0.0,gx1)-0.5);gy1-=sz1*(step(0.0,gy1)-0.5);vec3 g000=vec3(gx0.x,gy0.x,gz0.x);vec3 g100=vec3(gx0.y,gy0.y,gz0.y);vec3 g010=vec3(gx0.z,gy0.z,gz0.z);vec3 g110=vec3(gx0.w,gy0.w,gz0.w);vec3 g001=vec3(gx1.x,gy1.x,gz1.x);vec3 g101=vec3(gx1.y,gy1.y,gz1.y);vec3 g011=vec3(gx1.z,gy1.z,gz1.z);vec3 g111=vec3(gx1.w,gy1.w,gz1.w);vec4 norm0=taylorInvSqrt_0(vec4(dot(g000,g000),dot(g010,g010),dot(g100,g100),dot(g110,g110)));g000*=norm0.x;g010*=norm0.y;g100*=norm0.z;g110*=norm0.w;vec4 norm1=taylorInvSqrt_0(vec4(dot(g001,g001),dot(g011,g011),dot(g101,g101),dot(g111,g111)));g001*=norm1.x;g011*=norm1.y;g101*=norm1.z;g111*=norm1.w;float n000=dot(g000,Pf0);float n100=dot(g100,vec3(Pf1.x,Pf0.yz));float n010=dot(g010,vec3(Pf0.x,Pf1.y,Pf0.z));float n110=dot(g110,vec3(Pf1.xy,Pf0.z));float n001=dot(g001,vec3(Pf0.xy,Pf1.z));float n101=dot(g101,vec3(Pf1.x,Pf0.y,Pf1.z));float n011=dot(g011,vec3(Pf0.x,Pf1.yz));float n111=dot(g111,Pf1);vec3 fade_xyz=fade(Pf0);vec4 n_z=mix(vec4(n000,n100,n010,n110),vec4(n001,n101,n011,n111),fade_xyz.z);vec2 n_yz=mix(n_z.xy,n_z.zw,fade_xyz.y);float n_xyz=mix(n_yz.x,n_yz.y,fade_xyz.x);return 2.2*n_xyz;}vec3 mod289_1(vec3 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 mod289_1(vec4 x){return x-floor(x*(1.0/289.0))*289.0;}vec4 permute_1(vec4 x){return mod289_1(((x*34.0)+1.0)*x);}vec4 taylorInvSqrt_1(vec4 r){return 1.79284291400159-0.85373472095314*r;}float snoise(vec3 v){const vec2 C=vec2(1.0/6.0,1.0/3.0);const vec4 D=vec4(0.0,0.5,1.0,2.0);vec3 i=floor(v+dot(v,C.yyy));vec3 x0=v-i+dot(i,C.xxx);vec3 g_0=step(x0.yzx,x0.xyz);vec3 l=1.0-g_0;vec3 i1=min(g_0.xyz,l.zxy);vec3 i2=max(g_0.xyz,l.zxy);vec3 x1=x0-i1+C.xxx;vec3 x2=x0-i2+C.yyy;vec3 x3=x0-D.yyy;i=mod289_1(i);vec4 p=permute_1(permute_1(permute_1(i.z+vec4(0.0,i1.z,i2.z,1.0))+i.y+vec4(0.0,i1.y,i2.y,1.0))+i.x+vec4(0.0,i1.x,i2.x,1.0));float n_=0.142857142857;vec3 ns=n_*D.wyz-D.xzx;vec4 j=p-49.0*floor(p*ns.z*ns.z);vec4 x_=floor(j*ns.z);vec4 y_=floor(j-7.0*x_);vec4 x=x_*ns.x+ns.yyyy;vec4 y=y_*ns.x+ns.yyyy;vec4 h=1.0-abs(x)-abs(y);vec4 b0=vec4(x.xy,y.xy);vec4 b1=vec4(x.zw,y.zw);vec4 s0=floor(b0)*2.0+1.0;vec4 s1=floor(b1)*2.0+1.0;vec4 sh=-step(h,vec4(0.0));vec4 a0=b0.xzyw+s0.xzyw*sh.xxyy;vec4 a1=b1.xzyw+s1.xzyw*sh.zzww;vec3 p0=vec3(a0.xy,h.x);vec3 p1=vec3(a0.zw,h.y);vec3 p2=vec3(a1.xy,h.z);vec3 p3=vec3(a1.zw,h.w);vec4 norm=taylorInvSqrt_1(vec4(dot(p0,p0),dot(p1,p1),dot(p2,p2),dot(p3,p3)));p0*=norm.x;p1*=norm.y;p2*=norm.z;p3*=norm.w;vec4 m=max(0.6-vec4(dot(x0,x0),dot(x1,x1),dot(x2,x2),dot(x3,x3)),0.0);m=m*m;return 42.0*dot(m*m,vec4(dot(p0,x0),dot(p1,x1),dot(p2,x2),dot(p3,x3)));}float grain(vec2 texCoord,vec2 resolution,float frame,float multiplier){vec2 mult=texCoord*resolution;float offset=snoise(vec3(mult/multiplier,frame));float n1=pnoise(vec3(mult,offset),vec3(1.0/texCoord*resolution,1.0));return n1/2.0+0.5;}float grain(vec2 texCoord,vec2 resolution,float frame){return grain(texCoord,resolution,frame,2.5);}float grain(vec2 texCoord,vec2 resolution){return grain(texCoord,resolution,0.0);}vec3 blendSoftLight(vec3 base,vec3 blend){return mix(sqrt(base)*(2.0*blend-1.0)+2.0*base*(1.0-blend),2.0*base*blend+base*base*(1.0-2.0*blend),step(base,vec3(0.5)));}uniform vec3 color1;uniform vec3 color2;uniform float aspect;uniform vec2 offset;uniform vec2 scale;uniform float noiseAlpha;uniform bool aspectCorrection;uniform float grainScale;uniform float grainTime;uniform vec2 smooth;varying vec2 vUv;void main(){vec2 q=vec2(vUv-0.5);if(aspectCorrection){q.x*=aspect;}q/=scale;q-=offset;float dst=length(q);dst=smoothstep(smooth.x,smooth.y,dst);vec3 color=mix(color1,color2,dst);if(noiseAlpha>0.0&&grainScale>0.0){float gSize=1.0/grainScale;float g=grain(vUv,vec2(gSize*aspect,gSize),grainTime);vec3 noiseColor=blendSoftLight(color,vec3(g));gl_FragColor.rgb=mix(color,noiseColor,noiseAlpha);}else{gl_FragColor.rgb=color;}gl_FragColor.a=1.0;}"; // eslint-disable-line
40870
+ var frag = "precision mediump float;\n#pragma glslify: grain = require('glsl-film-grain')\n#pragma glslify: blend = require('glsl-blend-soft-light')\n\nuniform vec3 color1;\nuniform vec3 color2;\nuniform float aspect;\nuniform vec2 offset;\nuniform vec2 scale;\nuniform float noiseAlpha;\nuniform bool aspectCorrection;\nuniform float grainScale;\nuniform float grainTime;\nuniform vec2 smooth;\n\nvarying vec2 vUv;\n\nvoid main() {\n vec2 q = vec2(vUv - 0.5);\n if (aspectCorrection) {\n q.x *= aspect;\n }\n q /= scale;\n q -= offset;\n float dst = length(q);\n dst = smoothstep(smooth.x, smooth.y, dst);\n vec3 color = mix(color1, color2, dst);\n\n if (noiseAlpha > 0.0 && grainScale > 0.0) {\n float gSize = 1.0 / grainScale;\n float g = grain(vUv, vec2(gSize * aspect, gSize), grainTime);\n vec3 noiseColor = blend(color, vec3(g));\n gl_FragColor.rgb = mix(color, noiseColor, noiseAlpha);\n } else {\n gl_FragColor.rgb = color;\n }\n gl_FragColor.a = 1.0;\n}\n";
40871
40871
 
40872
40872
  function createBackground(opt) {
40873
40873
  opt = opt || {};
@@ -41395,9 +41395,9 @@ function getLut(data, windowWidth, windowCenter, invert, voiLUT) {
41395
41395
  };
41396
41396
  }
41397
41397
 
41398
- var vert_2d = "#define GLSLIFY 1\nout vec2 vUv;void main(){gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);vUv=vec2(uv.x,1.0-uv.y);}"; // eslint-disable-line
41398
+ var vert_2d = "out vec2 vUv;\n\nvoid main() {\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\n // Use the geometry's own UVs; DICOM rows are top-down so flip v.\n vUv = vec2( uv.x, 1.0 - uv.y );\n\n}\n";
41399
41399
 
41400
- var frag_2d = "precision highp float;precision highp int;precision highp sampler2DArray;\n#define GLSLIFY 1\nuniform sampler2DArray diffuse;in vec2 vUv;uniform int depth;uniform float uOpacity;out vec4 outColor;void main(){vec4 color=texture(diffuse,vec3(vUv,depth));outColor=vec4(color.rrr*1.5,uOpacity);}"; // eslint-disable-line
41400
+ var frag_2d = "precision highp float;\nprecision highp int;\nprecision highp sampler2DArray;\n\nuniform sampler2DArray diffuse;\nin vec2 vUv;\nuniform int depth;\nuniform float uOpacity;\n\nout vec4 outColor;\n\nvoid main() {\n\n vec4 color = texture( diffuse, vec3( vUv, depth ) );\n\n // lighten a bit\n outColor = vec4( color.rrr * 1.5, uOpacity );\n\n}";
41401
41401
 
41402
41402
  let planeWidth = 80;
41403
41403
  let planeHeight = 80;
@@ -46328,7 +46328,7 @@ function copperNrrdTexture3dLoader(url, scene, container, callback) {
46328
46328
  mesh.position.y += mesh.position.y - center.y;
46329
46329
  mesh.position.z += mesh.position.z - center.z;
46330
46330
  const boundingBox = new Box3().setFromObject(mesh);
46331
- // 获取边界框的尺寸
46331
+ // Get the size of the bounding box
46332
46332
  const size_ = new Vector3();
46333
46333
  boundingBox.getSize(size_);
46334
46334
  scene.add(mesh);
@@ -58678,7 +58678,7 @@ const _endEvent = { type: 'end' };
58678
58678
  const _EPS = 0.000001;
58679
58679
  const _STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
58680
58680
 
58681
- const _v2 = new Vector2();
58681
+ const _v2$1 = new Vector2();
58682
58682
  const _mouseChange = new Vector2();
58683
58683
  const _objectUp = new Vector3();
58684
58684
  const _pan = new Vector3();
@@ -59074,23 +59074,23 @@ class TrackballControls extends Controls$1 {
59074
59074
 
59075
59075
  _getMouseOnScreen( pageX, pageY ) {
59076
59076
 
59077
- _v2.set(
59077
+ _v2$1.set(
59078
59078
  ( pageX - this.screen.left ) / this.screen.width,
59079
59079
  ( pageY - this.screen.top ) / this.screen.height
59080
59080
  );
59081
59081
 
59082
- return _v2;
59082
+ return _v2$1;
59083
59083
 
59084
59084
  }
59085
59085
 
59086
59086
  _getMouseOnCircle( pageX, pageY ) {
59087
59087
 
59088
- _v2.set(
59088
+ _v2$1.set(
59089
59089
  ( ( pageX - this.screen.width * 0.5 - this.screen.left ) / ( this.screen.width * 0.5 ) ),
59090
59090
  ( ( this.screen.height + 2 * ( this.screen.top - pageY ) ) / this.screen.width ) // screen.width intentional
59091
59091
  );
59092
59092
 
59093
- return _v2;
59093
+ return _v2$1;
59094
59094
 
59095
59095
  }
59096
59096
 
@@ -59511,7 +59511,7 @@ function onTouchEnd( event ) {
59511
59511
  }
59512
59512
 
59513
59513
  const _inv = new Matrix4();
59514
- /** 世界系命中点 → local 顶点(位置 worldToLocal,法线用逆变换) */
59514
+ /** World-space hit point → local vertex (position via worldToLocal, normal via inverse transform). */
59515
59515
  function worldHitToLocalVertex(h, mesh) {
59516
59516
  const lp = mesh.worldToLocal(h.point.clone());
59517
59517
  _inv.copy(mesh.matrixWorld).invert();
@@ -59526,20 +59526,80 @@ function worldHitToLocalVertex(h, mesh) {
59526
59526
  faceIndex: h.faceIndex,
59527
59527
  };
59528
59528
  }
59529
- const _nm = new Matrix3();
59530
- /** local 顶点 → world 位置 + world 法线(供渲染) */
59529
+ const _nm$1 = new Matrix3();
59530
+ /** Local vertex → world position + world normal (for rendering). */
59531
59531
  function localVertexToWorld(v, mesh) {
59532
59532
  const p = new Vector3(v.x, v.y, v.z).applyMatrix4(mesh.matrixWorld);
59533
- _nm.getNormalMatrix(mesh.matrixWorld);
59534
- const n = new Vector3(v.nx, v.ny, v.nz).applyMatrix3(_nm).normalize();
59533
+ _nm$1.getNormalMatrix(mesh.matrixWorld);
59534
+ const n = new Vector3(v.nx, v.ny, v.nz).applyMatrix3(_nm$1).normalize();
59535
59535
  return { p, n };
59536
59536
  }
59537
59537
 
59538
59538
  const ray = new Raycaster();
59539
59539
  const ndc = new Vector2();
59540
+ // scratch objects for the smooth-normal interpolation (avoid per-call allocation)
59541
+ const _nm = new Matrix3();
59542
+ const _lp = new Vector3();
59543
+ const _pa = new Vector3();
59544
+ const _pb = new Vector3();
59545
+ const _pc = new Vector3();
59546
+ const _na = new Vector3();
59547
+ const _nb = new Vector3();
59548
+ const _nc = new Vector3();
59549
+ const _v0 = new Vector3();
59550
+ const _v1 = new Vector3();
59551
+ const _v2 = new Vector3();
59540
59552
  /**
59541
- * 把屏幕坐标(clientX/Y)投射到 mesh 表面,返回命中点、world-space 法线与 faceIndex。
59542
- * 未命中返回 null。本地实现,不依赖 Copper3D 未导出的 raycast 内部函数。
59553
+ * Interpolate a "smooth per-vertex normal" across the hit triangle using barycentric coordinates,
59554
+ * instead of using the per-face normal.
59555
+ *
59556
+ * Per-face normals on voxel / marching-cubes meshes are axis-aligned stair directions (±x/±y/±z);
59557
+ * using them to push the annotation line outward along the normal shoves points toward neighboring
59558
+ * stairs rather than cleanly "outward", making the line dip in and out on bumpy surfaces and get
59559
+ * occluded by the steps. A smooth normal varies continuously along the surface, so the outward
59560
+ * direction is stable and consistent and the line can float smoothly above the relief. Returns false
59561
+ * when interpolation isn't possible (caller falls back to the face normal).
59562
+ */
59563
+ function interpolateLocalNormal(geom, face, localPoint, out) {
59564
+ const posAttr = geom.getAttribute("position");
59565
+ const norAttr = geom.getAttribute("normal");
59566
+ if (!posAttr || !norAttr)
59567
+ return false;
59568
+ _pa.fromBufferAttribute(posAttr, face.a);
59569
+ _pb.fromBufferAttribute(posAttr, face.b);
59570
+ _pc.fromBufferAttribute(posAttr, face.c);
59571
+ // Barycentric coordinates (local space): u/v/w
59572
+ _v0.subVectors(_pb, _pa);
59573
+ _v1.subVectors(_pc, _pa);
59574
+ _v2.subVectors(localPoint, _pa);
59575
+ const d00 = _v0.dot(_v0);
59576
+ const d01 = _v0.dot(_v1);
59577
+ const d11 = _v1.dot(_v1);
59578
+ const d20 = _v2.dot(_v0);
59579
+ const d21 = _v2.dot(_v1);
59580
+ const denom = d00 * d11 - d01 * d01;
59581
+ if (Math.abs(denom) < 1e-12)
59582
+ return false;
59583
+ const v = (d11 * d20 - d01 * d21) / denom;
59584
+ const w = (d00 * d21 - d01 * d20) / denom;
59585
+ const u = 1 - v - w;
59586
+ _na.fromBufferAttribute(norAttr, face.a);
59587
+ _nb.fromBufferAttribute(norAttr, face.b);
59588
+ _nc.fromBufferAttribute(norAttr, face.c);
59589
+ out
59590
+ .set(0, 0, 0)
59591
+ .addScaledVector(_na, u)
59592
+ .addScaledVector(_nb, v)
59593
+ .addScaledVector(_nc, w);
59594
+ if (out.lengthSq() < 1e-12)
59595
+ return false;
59596
+ out.normalize();
59597
+ return true;
59598
+ }
59599
+ /**
59600
+ * Project screen coordinates (clientX/Y) onto the mesh surface, returning the hit point,
59601
+ * world-space normal, and faceIndex. Returns null on a miss. Local implementation that does not
59602
+ * depend on Copper3D's unexported raycast internals.
59543
59603
  */
59544
59604
  function raycastSurface(camera, container, mesh, clientX, clientY) {
59545
59605
  var _a;
@@ -59554,19 +59614,29 @@ function raycastSurface(camera, container, mesh, clientX, clientY) {
59554
59614
  const point = h.point.clone();
59555
59615
  const normal = new Vector3(0, 0, 1);
59556
59616
  if (h.face) {
59557
- normal.copy(h.face.normal);
59558
- const nm = new Matrix3().getNormalMatrix(mesh.matrixWorld);
59559
- normal.applyMatrix3(nm).normalize();
59617
+ // Prefer a smooth interpolated normal (stable outward direction on bumpy/voxel meshes);
59618
+ // fall back to the flat face normal if the geometry has no normal attribute.
59619
+ _lp.copy(h.point);
59620
+ mesh.worldToLocal(_lp);
59621
+ if (!interpolateLocalNormal(mesh.geometry, h.face, _lp, normal)) {
59622
+ normal.copy(h.face.normal);
59623
+ }
59624
+ _nm.getNormalMatrix(mesh.matrixWorld);
59625
+ normal.applyMatrix3(_nm).normalize();
59560
59626
  }
59561
59627
  return { point, normal, faceIndex: (_a = h.faceIndex) !== null && _a !== void 0 ? _a : -1 };
59562
59628
  }
59563
59629
 
59564
59630
  /**
59565
- * local 顶点处生成一个标记小球(fiducial)local→world 后沿 world 法线略外移,
59566
- * 避免一半埋进表面。radius 由调用方按模型 bbox 缩放传入。
59631
+ * Create a marker sphere (fiducial) at a local vertex. After local→world, nudge it
59632
+ * outward along the world normal so it doesn't sink halfway into the surface.
59633
+ * radius is passed in by the caller, scaled to the model bbox.
59567
59634
  */
59568
59635
  function makePointMarker(v, mesh, color, radius) {
59569
59636
  const geo = new SphereGeometry(radius, 16, 16);
59637
+ // depthTest ON so markers are occluded by the model when they are on the far side (no
59638
+ // see-through), consistent with the contour lines. Markers are volumetric spheres lifted off
59639
+ // the surface, so they still show on the visible side without z-fighting.
59570
59640
  const mat = new MeshBasicMaterial({ color });
59571
59641
  const m = new Mesh(geo, mat);
59572
59642
  const { p, n } = localVertexToWorld(v, mesh);
@@ -59576,15 +59646,18 @@ function makePointMarker(v, mesh, color, radius) {
59576
59646
  }
59577
59647
 
59578
59648
  /**
59579
- * 模式 A(自由手绘)一笔的状态机。
59580
- * pointermove 时不断 addSample;与上一采样点世界距离 < minGap 的样本丢弃,避免过密。
59581
- * 顶点以 local 存(由 mesh 把世界命中点转 local)。
59649
+ * State machine for one stroke in mode A (freehand).
59650
+ * On pointermove it keeps calling addSample; samples whose world distance from the previous
59651
+ * sample is < minGap are dropped to avoid over-density.
59652
+ * Vertices are stored in local space (the mesh converts the world hit point to local).
59582
59653
  */
59583
59654
  class StrokeContour {
59584
59655
  /**
59585
- * @param minGap 采样最小间距(世界系),去抖
59586
- * @param maxJump 跳变阈值(世界系):距离 > maxJump 且法线大幅翻转时丢弃该样本,
59587
- * 避免笔触掠过沟缝/轮廓边时相邻点落在不同深度、直线段横穿模型。
59656
+ * @param minGap minimum sample spacing (world space), debounce
59657
+ * @param maxJump jump threshold (world space): drop the sample when distance > maxJump and the
59658
+ * normal flips sharply, to prevent adjacent points landing at different depths
59659
+ * (and the straight segment cutting through the model) when the stroke grazes a
59660
+ * groove or contour edge.
59588
59661
  */
59589
59662
  constructor(minGap, maxJump, mesh) {
59590
59663
  this.minGap = minGap;
@@ -59603,10 +59676,12 @@ class StrokeContour {
59603
59676
  if (this.has) {
59604
59677
  const d = hit.point.distanceTo(this.last);
59605
59678
  if (d < this.minGap)
59606
- return; // 太近,去抖
59607
- // 跳变剔除:距离突然很大「且」法线大幅翻转(>~75°)→ 多半跳到了背面/远面,丢弃,
59608
- // 否则相邻两点之间的直线段会横穿模型内部。两个条件同时满足才剔除:
59609
- // 快速画(距离大、法线相近)不误删;画过尖锐棱边(法线变、距离近)也不误删。
59679
+ return; // too close, debounce
59680
+ // Jump rejection: distance suddenly large AND normal flips sharply (>~75°) likely jumped to
59681
+ // the back/far side, so drop it, otherwise the straight segment between adjacent points cuts
59682
+ // through the model interior. Only reject when both conditions hold:
59683
+ // fast drawing (large distance, similar normals) isn't falsely dropped, and drawing over a
59684
+ // sharp edge (normal changes, distance small) isn't falsely dropped either.
59610
59685
  if (d > this.maxJump && hit.normal.dot(this.lastNormal) < 0.25)
59611
59686
  return;
59612
59687
  }
@@ -60904,9 +60979,10 @@ class Line2 extends LineSegments2 {
60904
60979
  }
60905
60980
 
60906
60981
  /**
60907
- * 把标注顶点(local)序列展平成 Line2 需要的 world [x,y,z, ...]
60908
- * 每个点 local→world,再沿 world 法线外移 epsilon,避免与模型表面 z-fighting。
60909
- * closed 时把首点追加到末尾形成环。
60982
+ * Flatten a sequence of annotation vertices (local) into the world [x,y,z, ...] that Line2 needs.
60983
+ * Each point goes local→world, then is pushed outward along the world normal by epsilon to avoid
60984
+ * z-fighting with the model surface.
60985
+ * When closed, the first point is appended to the end to form a ring.
60910
60986
  */
60911
60987
  function flatten(verts, closed, epsilon, mesh) {
60912
60988
  const pts = [];
@@ -60922,27 +60998,31 @@ function flatten(verts, closed, epsilon, mesh) {
60922
60998
  function makeContourLine(verts, color, closed, container, epsilon, mesh) {
60923
60999
  const geo = new LineGeometry();
60924
61000
  const pos = flatten(verts, closed, epsilon, mesh);
60925
- // Line2 至少需要 2 个点;不足时给一个退化占位,后续 update 会补上。
61001
+ // Line2 needs at least 2 points; when there aren't enough, give a degenerate placeholder that a later update fills in.
60926
61002
  geo.setPositions(pos.length >= 6 ? pos : [0, 0, 0, 0, 0, 0]);
60927
61003
  const mat = new LineMaterial({
60928
61004
  color: new Color$1(color).getHex(),
60929
- linewidth: 3, // 像素宽( resolution 配合)
61005
+ linewidth: 3, // width in pixels (requires resolution to be set)
61006
+ // depthTest stays ON so the model correctly occludes the line when it is on the far side
61007
+ // (no see-through). The host applies polygonOffset to the SURFACE material so the line still
61008
+ // wins where it lies coplanar with the surface (no z-fighting / sinking on bumpy meshes).
60930
61009
  });
60931
61010
  mat.resolution.set(container.clientWidth, container.clientHeight);
60932
61011
  const line = new Line2(geo, mat);
60933
61012
  line.computeLineDistances();
60934
61013
  line.renderOrder = 998;
60935
- // 关键:Line2 的包围球停留在初始点,拖拽生长后真实几何会被错误剔除。
60936
- // 关闭视锥剔除,保证线始终渲染。
61014
+ // Important: Line2's bounding sphere stays at the initial point, so once the geometry grows by
61015
+ // dragging, the real geometry gets wrongly culled. Disable frustum culling so the line always renders.
60937
61016
  line.frustumCulled = false;
60938
61017
  return line;
60939
61018
  }
60940
61019
  function updateContourLine(line, verts, closed, epsilon, mesh) {
60941
61020
  const pos = flatten(verts, closed, epsilon, mesh);
60942
61021
  if (pos.length < 6)
60943
- return; // Line2 需要 >=2
60944
- // 关键:Line2 在原 geometry setPositions 扩容后只渲染首段(已知坑)。
60945
- // 改为每次重建一个新 LineGeometry 并替换,保证实例数正确、全段渲染。
61022
+ return; // Line2 needs >=2 points
61023
+ // Important: calling setPositions on the existing geometry to grow it only renders the first
61024
+ // segment (a known pitfall). Instead, rebuild and swap in a new LineGeometry each time so the
61025
+ // instance count is correct and every segment renders.
60946
61026
  const geo = new LineGeometry();
60947
61027
  geo.setPositions(pos);
60948
61028
  const old = line.geometry;
@@ -60950,20 +61030,22 @@ function updateContourLine(line, verts, closed, epsilon, mesh) {
60950
61030
  old.dispose();
60951
61031
  line.computeLineDistances();
60952
61032
  }
60953
- /** 改变已有 contour 线的颜色。 */
61033
+ /** Change the color of an existing contour line. */
60954
61034
  function setContourColor(line, color) {
60955
61035
  line.material.color.set(color);
60956
61036
  }
60957
61037
 
60958
61038
  /**
60959
- * BufferGeometry 的索引构建顶点邻接图,用于模式 B(测地线)的最短路径。
60960
- * 顶点坐标按 local 存;传入的查询点需先转为 local(mesh.worldToLocal)。
61039
+ * Build a vertex adjacency graph from a BufferGeometry's index, used for the shortest path in
61040
+ * mode B (geodesic). Vertex coordinates are stored in local space; query points must be converted
61041
+ * to local first (mesh.worldToLocal).
60961
61042
  *
60962
- * 说明:几何须为已索引(indexed)。modelLoader mergeVertices 焊接重复顶点,
60963
- * 保证同一表面位置共享顶点,邻接图才连通。
61043
+ * Note: the geometry must be indexed. modelLoader welds duplicate vertices with mergeVertices so
61044
+ * that vertices at the same surface position are shared, otherwise the adjacency graph won't be connected.
60964
61045
  *
60965
- * 性能:O(V²) Dijkstra + O(V) 最近顶点查找。对几万顶点单次点击可接受;
60966
- * 若过大可后续换二叉堆 + 空间网格加速(留作升级)。
61046
+ * Performance: O(V²) Dijkstra + O(V) nearest-vertex lookup. Acceptable for a single click on tens
61047
+ * of thousands of vertices; if too large it can later be sped up with a binary heap + spatial grid
61048
+ * (left as a future upgrade).
60967
61049
  */
60968
61050
  class MeshGraph {
60969
61051
  constructor(geometry) {
@@ -60991,7 +61073,7 @@ class MeshGraph {
60991
61073
  }
60992
61074
  }
60993
61075
  else {
60994
- // 非索引兜底(连通性差,仅避免崩溃)
61076
+ // Non-indexed fallback (poor connectivity, only to avoid crashing).
60995
61077
  for (let i = 0; i + 2 < this.vertexCount; i += 3) {
60996
61078
  addEdge(i, i + 1);
60997
61079
  addEdge(i + 1, i + 2);
@@ -61008,7 +61090,7 @@ class MeshGraph {
61008
61090
  pz(i) {
61009
61091
  return this.positions[i * 3 + 2];
61010
61092
  }
61011
- /** 最近顶点(传入 local 坐标) */
61093
+ /** Nearest vertex (pass in local coordinates). */
61012
61094
  nearestVertex(localPoint) {
61013
61095
  let best = -1;
61014
61096
  let bestD = Infinity;
@@ -61025,8 +61107,8 @@ class MeshGraph {
61025
61107
  return best;
61026
61108
  }
61027
61109
  /**
61028
- * 两顶点间最短路径(含端点的顶点索引序列)。二叉堆 Dijkstra,O(E log V)
61029
- * 不连通时返回 [start,end] 兜底。
61110
+ * Shortest path between two vertices (sequence of vertex indices including endpoints).
61111
+ * Binary-heap Dijkstra, O(E log V). Falls back to [start,end] when disconnected.
61030
61112
  */
61031
61113
  shortestPath(startV, endV) {
61032
61114
  if (startV === endV)
@@ -61083,7 +61165,7 @@ class MeshGraph {
61083
61165
  const nm = new Matrix3().getNormalMatrix(mesh.matrixWorld);
61084
61166
  return n.applyMatrix3(nm).normalize();
61085
61167
  }
61086
- /** 顶点的 local 坐标 + local 法线(图几何即 local 空间) */
61168
+ /** Vertex local coordinates + local normal (the graph geometry is already local space). */
61087
61169
  vertexLocal(i) {
61088
61170
  return {
61089
61171
  x: this.px(i),
@@ -61096,7 +61178,7 @@ class MeshGraph {
61096
61178
  };
61097
61179
  }
61098
61180
  }
61099
- /** 极简二叉最小堆( priority 排序,存顶点索引)。用于 Dijkstra */
61181
+ /** Minimal binary min-heap (ordered by priority, stores vertex indices). Used for Dijkstra. */
61100
61182
  class MinHeap {
61101
61183
  constructor() {
61102
61184
  this.ids = [];
@@ -61153,22 +61235,23 @@ class MinHeap {
61153
61235
  }
61154
61236
 
61155
61237
  /**
61156
- * 模式 B(测地线)状态机:逐次点击锚点,相邻锚点之间用 MeshGraph 求最短路径,
61157
- * 拼成一条贴合表面的折线。Enter 结束并可闭合(首尾再求一段)。
61238
+ * State machine for mode B (geodesic): click anchors one by one, find the shortest path between
61239
+ * adjacent anchors with MeshGraph, and stitch them into a polyline that hugs the surface.
61240
+ * Enter finishes and can close the loop (computing one more segment between first and last).
61158
61241
  */
61159
61242
  class GeodesicContour {
61160
61243
  constructor(graph, mesh) {
61161
61244
  this.graph = graph;
61162
61245
  this.mesh = mesh;
61163
- this.anchors = []; // 顶点索引
61164
- this.segments = []; // 相邻锚点间路径(含端点)
61165
- this.history = []; // 每次增删前的锚点快照,用于撤销
61246
+ this.anchors = []; // vertex indices
61247
+ this.segments = []; // path between adjacent anchors (endpoints included)
61248
+ this.history = []; // anchor snapshot before each add/remove, for undo
61166
61249
  }
61167
- /** 任何改动锚点前先存一份快照(顶点索引数组很小,快照成本可忽略) */
61250
+ /** Save a snapshot before any anchor change (the vertex-index array is tiny, so snapshot cost is negligible). */
61168
61251
  snapshot() {
61169
61252
  this.history.push(this.anchors.slice());
61170
61253
  }
61171
- /** 传入 local 命中点,snap 到最近顶点并对上一锚点求路径。 */
61254
+ /** Given a local hit point, snap to the nearest vertex and compute the path to the previous anchor. */
61172
61255
  addAnchor(localHitPoint) {
61173
61256
  const v = this.graph.nearestVertex(localHitPoint);
61174
61257
  this.snapshot();
@@ -61178,7 +61261,7 @@ class GeodesicContour {
61178
61261
  }
61179
61262
  this.anchors.push(v);
61180
61263
  }
61181
- /** 删除指定下标的锚点,重算相邻段(支持取消中间任意一点) */
61264
+ /** Remove the anchor at the given index and recompute adjacent segments (allows canceling any middle point). */
61182
61265
  removeAnchorAt(index) {
61183
61266
  if (index < 0 || index >= this.anchors.length)
61184
61267
  return;
@@ -61186,11 +61269,11 @@ class GeodesicContour {
61186
61269
  this.anchors.splice(index, 1);
61187
61270
  this.rebuildSegments();
61188
61271
  }
61189
- /** 是否还有可撤销的编辑(加点/删点) */
61272
+ /** Whether there is still an editable change to undo (add/remove point). */
61190
61273
  canUndo() {
61191
61274
  return this.history.length > 0;
61192
61275
  }
61193
- /** 撤销上一次锚点编辑(加点删回去;删点恢复)。无历史返回 false */
61276
+ /** Undo the last anchor edit (addremove it; remove restore it). Returns false when no history. */
61194
61277
  undoEdit() {
61195
61278
  const prev = this.history.pop();
61196
61279
  if (!prev)
@@ -61199,21 +61282,21 @@ class GeodesicContour {
61199
61282
  this.rebuildSegments();
61200
61283
  return true;
61201
61284
  }
61202
- /** 据当前锚点序列整体重算所有相邻段路径。 */
61285
+ /** Recompute all adjacent-segment paths from the current anchor sequence. */
61203
61286
  rebuildSegments() {
61204
61287
  this.segments = [];
61205
61288
  for (let i = 1; i < this.anchors.length; i++) {
61206
61289
  this.segments.push(this.graph.shortestPath(this.anchors[i - 1], this.anchors[i]));
61207
61290
  }
61208
61291
  }
61209
- /** 各锚点的 local 顶点(用于绘制可见的锚点标记) */
61292
+ /** Local vertex of each anchor (used to draw visible anchor markers). */
61210
61293
  getAnchorLocals() {
61211
61294
  return this.anchors.map((i) => this.graph.vertexLocal(i));
61212
61295
  }
61213
61296
  get anchorCount() {
61214
61297
  return this.anchors.length;
61215
61298
  }
61216
- /** 把所有路径顶点(world)+ 法线 拼成折线;closed 时补一段首尾路径。 */
61299
+ /** Stitch all path vertices (world) + normals into a polyline; when closed, add one first-to-last segment. */
61217
61300
  buildVertices(closed) {
61218
61301
  const segs = this.segments.slice();
61219
61302
  if (closed && this.anchors.length > 2) {
@@ -61221,22 +61304,22 @@ class GeodesicContour {
61221
61304
  }
61222
61305
  const idxPath = [];
61223
61306
  segs.forEach((s, si) => {
61224
- const start = si === 0 ? 0 : 1; // 去重相邻段共享端点
61307
+ const start = si === 0 ? 0 : 1; // dedupe the endpoint shared between adjacent segments
61225
61308
  for (let k = start; k < s.length; k++)
61226
61309
  idxPath.push(s[k]);
61227
61310
  });
61228
61311
  if (idxPath.length === 0 && this.anchors.length === 1) {
61229
61312
  idxPath.push(this.anchors[0]);
61230
61313
  }
61231
- // 图几何即 local 空间,直接产出 local 顶点(渲染时再派生 world)
61314
+ // The graph geometry is already local space, so emit local vertices directly (world is derived at render time).
61232
61315
  return idxPath.map((i) => this.graph.vertexLocal(i));
61233
61316
  }
61234
61317
  }
61235
61318
 
61236
61319
  /**
61237
- * 标注数据模型 + 撤销栈 + 导出。纯数据,无 three / DOM 依赖。
61238
- * 撤销只记录 add / remove 两类原子操作(满足"撤销最近添加 / 恢复删除")
61239
- * 通过 subscribe 通知 UI 与渲染层(后者据 list() 做场景对账)
61320
+ * Annotation data model + undo stack + export. Pure data, no three / DOM dependencies.
61321
+ * Undo only records two atomic operations, add / remove (enough for "undo last add / restore delete").
61322
+ * Notifies the UI and render layer via subscribe (the latter reconciles the scene against list()).
61240
61323
  */
61241
61324
  class AnnotationStore {
61242
61325
  constructor() {
@@ -61279,7 +61362,7 @@ class AnnotationStore {
61279
61362
  }
61280
61363
  this.notify();
61281
61364
  }
61282
- /** 清空,返回被清掉的项(供渲染层 dispose three 对象) */
61365
+ /** Clear all, returning the removed items (so the render layer can dispose three objects). */
61283
61366
  clear() {
61284
61367
  const old = this.items;
61285
61368
  this.items = [];
@@ -61340,13 +61423,14 @@ class AnnotationStore {
61340
61423
  const LINE_W = 3;
61341
61424
  const LINE_W_SEL = 6;
61342
61425
  /**
61343
- * 表面标注主控制器(Phase 4):navigate / freehand / geodesic / point 四模式,
61344
- * Enter 闭合,数据交给 AnnotationStore 管理(多条带颜色标签、撤销/删除/清空、导出)。
61345
- * 渲染层据 store.list() 做场景对账(reconcile),撤销/删除自动加/移 three 对象。
61426
+ * Main surface-annotation controller (Phase 4): four modes navigate / freehand / geodesic / point,
61427
+ * Enter closes the loop, and data is managed by AnnotationStore (multiple color-labeled strips,
61428
+ * undo/delete/clear, export).
61429
+ * The render layer reconciles the scene against store.list(); undo/delete automatically add/remove three objects.
61346
61430
  */
61347
61431
  class SurfaceAnnotator {
61348
61432
  constructor(opts) {
61349
- var _a, _b;
61433
+ var _a, _b, _c;
61350
61434
  this.mode = "navigate";
61351
61435
  this.spaceHeld = false;
61352
61436
  this.pointerDown = false;
@@ -61354,12 +61438,12 @@ class SurfaceAnnotator {
61354
61438
  this.managed = new Set();
61355
61439
  this.seq = 0;
61356
61440
  this.selectedId = null;
61357
- this.activeGeoMarkers = []; // 进行中测地线的可见锚点
61441
+ this.activeGeoMarkers = []; // visible anchors of the in-progress geodesic
61358
61442
  this.geoRay = new Raycaster();
61359
61443
  this.geoNdc = new Vector2();
61360
61444
  this.hoveredGeoMarker = -1;
61361
61445
  this._projV = new Vector3();
61362
- /** 窗口尺寸变化时更新所有 fat line 的像素分辨率,否则线宽会失真。 */
61446
+ /** Update the pixel resolution of all fat lines on window resize, otherwise line width distorts. */
61363
61447
  this.onResize = () => {
61364
61448
  const w = this.o.container.clientWidth;
61365
61449
  const h = this.o.container.clientHeight;
@@ -61378,7 +61462,7 @@ class SurfaceAnnotator {
61378
61462
  if (this.spaceHeld)
61379
61463
  return;
61380
61464
  if (e.button !== 0)
61381
- return; // 仅左键
61465
+ return; // left button only
61382
61466
  if (!this.insideContainer(e))
61383
61467
  return;
61384
61468
  this.pointerDown = true;
@@ -61413,7 +61497,7 @@ class SurfaceAnnotator {
61413
61497
  return;
61414
61498
  }
61415
61499
  if (this.mode === "geodesic") {
61416
- // 先判断是否点中了一个已有锚点取消该点(支持取消其中任意一点)
61500
+ // First check whether an existing anchor was clicked cancel that point (any point can be canceled).
61417
61501
  const pick = this.pickGeoMarker(e);
61418
61502
  if (pick >= 0 && this.activeGeo) {
61419
61503
  this.activeGeo.removeAnchorAt(pick);
@@ -61425,7 +61509,7 @@ class SurfaceAnnotator {
61425
61509
  }
61426
61510
  return;
61427
61511
  }
61428
- // 否则在表面落一个新锚点。
61512
+ // Otherwise drop a new anchor on the surface.
61429
61513
  const h = this.hit(e);
61430
61514
  if (!h)
61431
61515
  return;
@@ -61443,7 +61527,7 @@ class SurfaceAnnotator {
61443
61527
  this.setGeoHover(-1);
61444
61528
  return;
61445
61529
  }
61446
- // 测地线模式下,悬停到锚点小球时显示"✕"提示(该点可被点击取消)
61530
+ // In geodesic mode, show the "✕" hint when hovering an anchor sphere (that point can be clicked to cancel).
61447
61531
  if (this.mode === "geodesic" && !this.pointerDown) {
61448
61532
  this.setGeoHover(this.insideContainer(e) ? this.pickGeoMarker(e) : -1);
61449
61533
  }
@@ -61508,7 +61592,7 @@ class SurfaceAnnotator {
61508
61592
  return;
61509
61593
  }
61510
61594
  if (e.key === "Enter") {
61511
- // 测地线进行中结束并闭合;否则闭合最近一条自由手绘线。
61595
+ // Geodesic in progress finish and close it; otherwise close the most recent freehand line.
61512
61596
  if (this.activeGeo)
61513
61597
  this.finishGeodesic();
61514
61598
  else
@@ -61533,24 +61617,25 @@ class SurfaceAnnotator {
61533
61617
  this.o = opts;
61534
61618
  const meshGeo = opts.mesh.geometry;
61535
61619
  if (!meshGeo.getAttribute("normal"))
61536
- meshGeo.computeVertexNormals(); // 渲染需要法线
61620
+ meshGeo.computeVertexNormals(); // rendering needs normals
61537
61621
  meshGeo.computeBoundingBox();
61538
61622
  const diag = (_a = opts.bboxDiagonal) !== null && _a !== void 0 ? _a : meshGeo.boundingBox.getSize(new Vector3()).length();
61539
61623
  this.markerRadius = (_b = opts.markerRadius) !== null && _b !== void 0 ? _b : diag * 0.0032;
61540
- this.epsilon = diag * 0.002;
61624
+ this.epsilon = (_c = opts.epsilon) !== null && _c !== void 0 ? _c : diag * 0.002;
61541
61625
  this.minGap = diag * 0.004;
61542
61626
  this.maxJump = diag * 0.05;
61543
- // 测地线连通性:单独焊接一份"仅位置"的几何给 MeshGraph 用——
61544
- // 不动被渲染的 mesh(保留其法线/UV/纹理)。仅位置可保证同表面位置的顶点
61545
- // mergeVertices 合并(否则逐面法线/UV 会阻止合并,图断裂 → 闭合穿模)。
61627
+ // Geodesic connectivity: weld a separate "position-only" geometry for MeshGraph to use —
61628
+ // leaving the rendered mesh untouched (keeping its normals/UV/texture). Position-only ensures
61629
+ // vertices at the same surface position get merged by mergeVertices (otherwise per-face
61630
+ // normals/UV would block the merge, breaking the graph → the closed loop cuts through the model).
61546
61631
  const posOnly = new BufferGeometry();
61547
61632
  posOnly.setAttribute("position", meshGeo.getAttribute("position").clone());
61548
61633
  const graphGeo = mergeVertices(posOnly);
61549
61634
  graphGeo.computeVertexNormals();
61550
61635
  this.graph = new MeshGraph(graphGeo);
61551
61636
  this.store.subscribe(() => this.reconcile());
61552
- // 监听挂到 window 的捕获阶段(capture=true):在 copper TrackballControls
61553
- // 之前拿到每一个指针事件,避免它的 setPointerCapture / 事件路由导致拖拽中途丢 move。
61637
+ // Listen on window in the capture phase (capture=true): receive every pointer event before
61638
+ // copper's TrackballControls, so its setPointerCapture / event routing doesn't drop moves mid-drag.
61554
61639
  window.addEventListener("pointerdown", this.onPointerDown, true);
61555
61640
  window.addEventListener("pointermove", this.onPointerMove, true);
61556
61641
  window.addEventListener("pointerup", this.onPointerUp, true);
@@ -61571,13 +61656,13 @@ class SurfaceAnnotator {
61571
61656
  var _a;
61572
61657
  return (_a = this.o.pointColor) !== null && _a !== void 0 ? _a : "#ffd166";
61573
61658
  }
61574
- // ---- 对外 API( Vue 调用) ----
61659
+ // ---- Public API (called from Vue) ----
61575
61660
  getMode() {
61576
61661
  return this.mode;
61577
61662
  }
61578
61663
  setMode(m) {
61579
61664
  var _a, _b;
61580
- // 离开测地线模式时丢弃尚未 Enter 落定的进行中测地线(清掉残留的锚点与线)
61665
+ // When leaving geodesic mode, discard the in-progress geodesic not yet committed with Enter (clear leftover anchors and line).
61581
61666
  if (m !== "geodesic" && this.activeGeo)
61582
61667
  this.clearActiveGeo();
61583
61668
  this.mode = m;
@@ -61587,13 +61672,13 @@ class SurfaceAnnotator {
61587
61672
  getStore() {
61588
61673
  return this.store;
61589
61674
  }
61590
- /** 当前标注列表快照。 */
61675
+ /** Snapshot of the current annotation list. */
61591
61676
  getAnnotations() {
61592
61677
  return this.store.list();
61593
61678
  }
61594
61679
  undo() {
61595
- // 测地线进行中撤销最近一次锚点编辑(加点 / 取消点都能回退);
61596
- // 否则撤销最近一条已落定的标注。
61680
+ // Geodesic in progress undo the most recent anchor edit (both adding and canceling a point can be rolled back);
61681
+ // otherwise undo the most recently committed annotation.
61597
61682
  if (this.activeGeo && this.activeGeo.canUndo()) {
61598
61683
  this.activeGeo.undoEdit();
61599
61684
  if (this.activeGeo.anchorCount === 0)
@@ -61627,7 +61712,7 @@ class SurfaceAnnotator {
61627
61712
  this.selectedId = id;
61628
61713
  this.applySelection();
61629
61714
  }
61630
- /** 颜色变更后重画对应 three 对象。 */
61715
+ /** Redraw the corresponding three object after a color change. */
61631
61716
  refreshAnnotation(id) {
61632
61717
  const a = this.store.get(id);
61633
61718
  if (!a || !a.object3D)
@@ -61643,12 +61728,12 @@ class SurfaceAnnotator {
61643
61728
  exportJSON(modelName, opts) {
61644
61729
  return this.store.toJSON(modelName, this.o.mesh, opts);
61645
61730
  }
61646
- // ---- 内部 ----
61731
+ // ---- Internal ----
61647
61732
  applyCameraGating() {
61648
- // 按住 Space 临时旋转优先;否则仅 navigate 模式开相机。
61733
+ // Holding Space for temporary rotation takes priority; otherwise enable the camera only in navigate mode.
61649
61734
  this.o.controls.enabled = this.spaceHeld || this.mode === "navigate";
61650
61735
  }
61651
- /** store.list() 对账场景:补齐缺失对象、移除已删对象( dispose,留给撤销恢复) */
61736
+ /** Reconcile the scene against store.list(): add missing objects, remove deleted ones (no dispose, kept for undo restore). */
61652
61737
  reconcile() {
61653
61738
  var _a, _b;
61654
61739
  const present = new Set();
@@ -61698,29 +61783,53 @@ class SurfaceAnnotator {
61698
61783
  return raycastSurface(this.o.camera, this.o.container, this.o.mesh, e.clientX, e.clientY);
61699
61784
  }
61700
61785
  /**
61701
- * 事件目标是否是容器内的 WebGL canvas
61702
- * 仅认 canvas:面板(GUIDE / 控制面板 / 标注列表的按钮)都是 container 的子元素,
61703
- * 若只判断 contains 会把面板上的点击也当成在模型上作画 —— 这会"穿透"删除按钮、
61704
- * 难以点中。只响应 canvas 上的指针事件即可彻底隔离 UI 与画布。
61786
+ * Whether the event target is the WebGL canvas inside the container.
61787
+ * Only accept the canvas: panels (GUIDE / control panel / thebuttons in the annotation list)
61788
+ * are all children of the container, so checking contains alone would treat clicks on the panels
61789
+ * as drawing on the model which "leaks through" the delete buttons and makes ✕ hard to click.
61790
+ * Responding only to pointer events on the canvas fully isolates the UI from the drawing surface.
61705
61791
  */
61706
61792
  insideContainer(e) {
61707
61793
  const t = e.target;
61708
61794
  return !!t && t.tagName === "CANVAS" && this.o.container.contains(t);
61709
61795
  }
61710
- /** 射线拾取进行中的锚点小球,返回锚点下标;未命中返回 -1。 */
61796
+ /**
61797
+ * Pick an in-progress anchor: return the index of the nearest anchor (screen pixel distance <
61798
+ * tolerance), or -1 on a miss.
61799
+ *
61800
+ * Uses "screen-space pixel distance" instead of ray/sphere intersection: the anchor spheres are
61801
+ * tiny, so strict ray/sphere hits require pixel-perfect aiming and most clicks miss — and a miss
61802
+ * falls through to the "add anchor" branch, so trying to delete a point ends up adding one. That
61803
+ * was the root cause of the old "deleting a point takes several clicks, must hit the sphere dead
61804
+ * center" problem. Projecting each anchor to the screen and taking the nearest within tolerance
61805
+ * makes deletion stable and reliable. The tolerance is enlarged relative to the sphere's screen
61806
+ * radius to make it easy to hit.
61807
+ */
61711
61808
  pickGeoMarker(e) {
61712
61809
  if (!this.activeGeoMarkers.length)
61713
61810
  return -1;
61714
61811
  const rect = this.o.container.getBoundingClientRect();
61715
- this.geoNdc.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
61716
- this.geoNdc.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
61717
- this.geoRay.setFromCamera(this.geoNdc, this.o.camera);
61718
- const hits = this.geoRay.intersectObjects(this.activeGeoMarkers, false);
61719
- if (!hits.length)
61720
- return -1;
61721
- return this.activeGeoMarkers.indexOf(hits[0].object);
61812
+ const px = e.clientX - rect.left;
61813
+ const py = e.clientY - rect.top;
61814
+ const TOL = 16; // hit tolerance (pixels), larger than the sphere itself for forgiving clicks
61815
+ let best = -1;
61816
+ let bestDist = TOL;
61817
+ const v = this._projV;
61818
+ for (let i = 0; i < this.activeGeoMarkers.length; i++) {
61819
+ v.copy(this.activeGeoMarkers[i].position).project(this.o.camera);
61820
+ if (v.z > 1)
61821
+ continue; // projects behind the camera, skip
61822
+ const sx = (v.x * 0.5 + 0.5) * rect.width;
61823
+ const sy = (-v.y * 0.5 + 0.5) * rect.height;
61824
+ const d = Math.hypot(sx - px, sy - py);
61825
+ if (d < bestDist) {
61826
+ bestDist = d;
61827
+ best = i;
61828
+ }
61829
+ }
61830
+ return best;
61722
61831
  }
61723
- /** 设置当前悬停的锚点(-1 = ):更新高亮缩放、光标与"✕(可取消)"悬浮标。 */
61832
+ /** Set the currently hovered anchor (-1 = none): update highlight scale, cursor, and the "✕ (cancel)" floating badge. */
61724
61833
  setGeoHover(idx) {
61725
61834
  if (idx === this.hoveredGeoMarker) {
61726
61835
  if (idx >= 0)
@@ -61744,7 +61853,7 @@ class SurfaceAnnotator {
61744
61853
  this.o.container.style.cursor = "";
61745
61854
  }
61746
61855
  }
61747
- /** 懒创建悬浮(挂在 container 内,pointer-events:none 不挡点击) */
61856
+ /** Lazily create the floating badge (appended inside the container, pointer-events:none so it doesn't block clicks). */
61748
61857
  ensureGeoBadge() {
61749
61858
  if (this.geoHoverBadge)
61750
61859
  return this.geoHoverBadge;
@@ -61772,7 +61881,7 @@ class SurfaceAnnotator {
61772
61881
  this.geoHoverBadge = b;
61773
61882
  return b;
61774
61883
  }
61775
- /** 标定位到锚点投影到屏幕的位置(正中心,即光标悬停处,避免歧义) */
61884
+ /** Position the badge at the anchor's screen projection (dead center, i.e. where the cursor hovers, to avoid ambiguity). */
61776
61885
  positionGeoBadge(marker) {
61777
61886
  if (!marker)
61778
61887
  return;
@@ -61784,7 +61893,7 @@ class SurfaceAnnotator {
61784
61893
  b.style.left = `${x}px`;
61785
61894
  b.style.top = `${y}px`;
61786
61895
  }
61787
- /** 据当前锚点重建可见的锚点小球(锚点比放点稍大,便于看见与点中取消) */
61896
+ /** Rebuild the visible anchor spheres from the current anchors (slightly larger than placed points, so they're easy to see and click to cancel). */
61788
61897
  rebuildGeoMarkers() {
61789
61898
  this.setGeoHover(-1);
61790
61899
  for (const m of this.activeGeoMarkers) {
@@ -61800,7 +61909,7 @@ class SurfaceAnnotator {
61800
61909
  this.activeGeoMarkers.push(marker);
61801
61910
  }
61802
61911
  }
61803
- /** 据当前锚点重画进行中的测地线(不闭合);不足两点时移除线。 */
61912
+ /** Redraw the in-progress geodesic from the current anchors (not closed); remove the line when fewer than two points. */
61804
61913
  redrawGeoLine() {
61805
61914
  if (!this.activeGeo)
61806
61915
  return;
@@ -61821,7 +61930,7 @@ class SurfaceAnnotator {
61821
61930
  updateContourLine(this.activeGeoLine, verts, false, this.epsilon, this.o.mesh);
61822
61931
  }
61823
61932
  }
61824
- /** 丢弃进行中的测地线:移除并 dispose 线与全部锚点。 */
61933
+ /** Discard the in-progress geodesic: remove and dispose the line and all anchors. */
61825
61934
  clearActiveGeo() {
61826
61935
  this.setGeoHover(-1);
61827
61936
  if (this.activeGeoLine) {
@@ -61836,7 +61945,7 @@ class SurfaceAnnotator {
61836
61945
  this.activeGeoMarkers = [];
61837
61946
  this.activeGeo = undefined;
61838
61947
  }
61839
- /** 移除进行中测地线的全部锚点小球(落定后调用:只留下线) */
61948
+ /** Remove all anchor spheres of the in-progress geodesic (called after committing: only the line remains). */
61840
61949
  removeGeoMarkers() {
61841
61950
  this.setGeoHover(-1);
61842
61951
  for (const m of this.activeGeoMarkers) {
@@ -61849,7 +61958,7 @@ class SurfaceAnnotator {
61849
61958
  const last = this.lastFreehand;
61850
61959
  if (!last || last.closed || last.vertices.length < 3 || !last.object3D)
61851
61960
  return;
61852
- // 用测地线沿表面把末点连回首点,避免直线弦从模型内部"抄近路"被遮挡。
61961
+ // Use a geodesic along the surface to connect the last point back to the first, so a straight chord doesn't "shortcut" through the model interior and get occluded.
61853
61962
  const tail = last.vertices[last.vertices.length - 1];
61854
61963
  const head = last.vertices[0];
61855
61964
  const closing = this.surfacePathBetween(tail, head);
@@ -61857,14 +61966,14 @@ class SurfaceAnnotator {
61857
61966
  last.closed = true;
61858
61967
  updateContourLine(last.object3D, last.vertices, true, this.epsilon, this.o.mesh);
61859
61968
  }
61860
- /** 沿网格表面求 a→b 的测地路径(local 顶点),去掉与 a 重合的首点。a/b 已是 local */
61969
+ /** Compute the geodesic path a→b along the mesh surface (local vertices), dropping the first point that coincides with a. a/b are already local. */
61861
61970
  surfacePathBetween(a, b) {
61862
61971
  const va = this.graph.nearestVertex(new Vector3(a.x, a.y, a.z));
61863
61972
  const vb = this.graph.nearestVertex(new Vector3(b.x, b.y, b.z));
61864
61973
  const path = this.graph.shortestPath(va, vb);
61865
61974
  return path.slice(1).map((i) => this.graph.vertexLocal(i));
61866
61975
  }
61867
- /** 结束当前测地线:闭合成环并落定为一条 contour */
61976
+ /** Finish the current geodesic: close it into a ring and commit it as one contour. */
61868
61977
  finishGeodesic() {
61869
61978
  if (!this.activeGeo || !this.activeGeoLine)
61870
61979
  return;
@@ -61888,7 +61997,7 @@ class SurfaceAnnotator {
61888
61997
  this.o.scene.remove(this.activeGeoLine);
61889
61998
  this.disposeObject(this.activeGeoLine);
61890
61999
  }
61891
- // 落定后清掉可见锚点,只留下贴合表面的线。
62000
+ // After committing, clear the visible anchors and leave only the surface-hugging line.
61892
62001
  this.removeGeoMarkers();
61893
62002
  this.activeGeo = undefined;
61894
62003
  this.activeGeoLine = undefined;
@@ -62003,9 +62112,9 @@ class copperScene extends baseScene {
62003
62112
  copperVtkLoader(url, this.scene, this.content);
62004
62113
  }
62005
62114
  /**
62006
- * 在给定模型表面创建标注器( contour / 放点 / 导出坐标)
62007
- * target 可为单个 Mesh,或 Group/Object3D(自动选顶点数最多的 mesh)
62008
- * 复用本 scene camera / container / controls;几何非索引时内部自动焊接索引化。
62115
+ * Create an annotator on the surface of a given model (draw contours / place points / export coordinates).
62116
+ * target can be a single Mesh, or a Group/Object3D (the mesh with the most vertices is chosen automatically).
62117
+ * Reuses this scene's camera / container / controls; non-indexed geometry is welded and indexed automatically.
62009
62118
  */
62010
62119
  createSurfaceAnnotator(target, opts) {
62011
62120
  const mesh = this.pickAnnotatableMesh(target);
@@ -62033,7 +62142,7 @@ class copperScene extends baseScene {
62033
62142
  });
62034
62143
  return best;
62035
62144
  }
62036
- /** 释放本 scene 创建的所有标注器(移除事件监听与标注对象) */
62145
+ /** Dispose all annotators created on this scene (removes event listeners and annotation objects). */
62037
62146
  disposeSurfaceAnnotators() {
62038
62147
  this.surfaceAnnotators.forEach((a) => a.dispose());
62039
62148
  this.surfaceAnnotators = [];
@@ -82892,7 +83001,7 @@ function evaluateElement(element, weights) {
82892
83001
  }
82893
83002
 
82894
83003
  // import * as kiwrious from "copper3d_plugin_heart_k";
82895
- const REVISION = "v3.6.2-beta";
83004
+ const REVISION = "v3.6.4-beta";
82896
83005
  console.log(`%cCopper3D Visualisation %cBeta:${REVISION}`, "padding: 3px;color:white; background:#023047", "padding: 3px;color:white; background:#f50a25");
82897
83006
 
82898
83007
  export { AI_CHANNEL_HEX_COLORS, AI_MASK_CHANNEL_COLORS, CHANNEL_COLORS, CHANNEL_HEX_COLORS, CameraViewPoint, Copper3dTrackballControls, GaussianSmoother, MeshNodeTool, NrrdTools, REVISION, SurfaceAnnotator, addBoxHelper, addLabelToScene, configKiwriousHeart, convert3DPostoScreenPos, convertScreenPosto3DPos, copperMScene, copperMSceneRenderer, copperRenderer, copperRendererOnDemond, copperScene, copperSceneOnDemond, createTexture2D_NRRD, fullScreenListenner, kiwrious, loading, removeGuiFolderChilden, rgbaToCss, rgbaToHex, setHDRFilePath, throttle };