@realsee/dnalogel 4.0.0-webgpu.17 → 4.0.0-webgpu.19

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 (95) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/floorplan.js +1 -1
  3. package/dist/index.js +19 -19
  4. package/dist/internal/{BasePlugin-BW03eBFv.js → BasePlugin-AYcNjiyG.js} +2 -2
  5. package/dist/internal/{BasePlugin-BW03eBFv.js.map → BasePlugin-AYcNjiyG.js.map} +1 -1
  6. package/dist/internal/BasePluginWithData-CWgZY4u8.js +7 -0
  7. package/dist/internal/{BasePluginWithData-5iUuu45u.js.map → BasePluginWithData-CWgZY4u8.js.map} +1 -1
  8. package/dist/internal/{Controller-DPvcZw20.js → Controller-DPpDpYsR.js} +3 -3
  9. package/dist/internal/{Controller-DPvcZw20.js.map → Controller-DPpDpYsR.js.map} +1 -1
  10. package/dist/internal/{Controller-5BvatEWD.js → Controller-DSKeCecj.js} +171 -154
  11. package/dist/internal/Controller-DSKeCecj.js.map +1 -0
  12. package/dist/internal/{Controller-CS5gPzPg.js → Controller-DZwww-c4.js} +2 -2
  13. package/dist/internal/{Controller-CS5gPzPg.js.map → Controller-DZwww-c4.js.map} +1 -1
  14. package/dist/internal/PointHelper-G9EgUMWo.js +1052 -0
  15. package/dist/internal/PointHelper-G9EgUMWo.js.map +1 -0
  16. package/dist/internal/{Rectangle-Bw-_5i6A.js → Rectangle-DSOdWXl_.js} +3 -3
  17. package/dist/internal/{Rectangle-Bw-_5i6A.js.map → Rectangle-DSOdWXl_.js.map} +1 -1
  18. package/dist/internal/{Work-njB4pAsT.js → Work-HQHipxJj.js} +4 -4
  19. package/dist/internal/{Work-njB4pAsT.js.map → Work-HQHipxJj.js.map} +1 -1
  20. package/dist/internal/{area-maker-T7a6vfXC.js → area-maker-Diy_UkTD.js} +2 -2
  21. package/dist/internal/{area-maker-T7a6vfXC.js.map → area-maker-Diy_UkTD.js.map} +1 -1
  22. package/dist/internal/{controller-Vl3H6GBQ.js → controller-DZUdbdd8.js} +4 -4
  23. package/dist/internal/{controller-Vl3H6GBQ.js.map → controller-DZUdbdd8.js.map} +1 -1
  24. package/dist/internal/{cruise-rzsfiLn-.js → cruise-Bo8CD4vF.js} +2 -2
  25. package/dist/internal/{cruise-rzsfiLn-.js.map → cruise-Bo8CD4vF.js.map} +1 -1
  26. package/dist/internal/{current-pano-image-CAodOmyS.js → current-pano-image-Cqq4DRPh.js} +2 -2
  27. package/dist/internal/{current-pano-image-CAodOmyS.js.map → current-pano-image-Cqq4DRPh.js.map} +1 -1
  28. package/dist/internal/{edgeSnap-8fdFCcXB.js → edgeSnap-CH0HidZm.js} +206 -83
  29. package/dist/internal/edgeSnap-CH0HidZm.js.map +1 -0
  30. package/dist/internal/{floorplan-B82VmXnE.js → floorplan-DwcrbASA.js} +2 -2
  31. package/dist/internal/{floorplan-B82VmXnE.js.map → floorplan-DwcrbASA.js.map} +1 -1
  32. package/dist/internal/{measure-KVSCjRnS.js → measure-DIstq55p.js} +2 -2
  33. package/dist/internal/{measure-KVSCjRnS.js.map → measure-DIstq55p.js.map} +1 -1
  34. package/dist/internal/measure-v2-Byp1mE5q.js +215 -0
  35. package/dist/internal/measure-v2-Byp1mE5q.js.map +1 -0
  36. package/dist/internal/{model-maker-BHPMSkqn.js → model-maker-pQcaAh5M.js} +2 -2
  37. package/dist/internal/{model-maker-BHPMSkqn.js.map → model-maker-pQcaAh5M.js.map} +1 -1
  38. package/dist/internal/object3d-helper-zRXHAcbm.js +7 -0
  39. package/dist/internal/{object3d-helper-CxJ_Ju1y.js.map → object3d-helper-zRXHAcbm.js.map} +1 -1
  40. package/dist/internal/{pano-compass-2S5mt7af.js → pano-compass-CMIljzhC.js} +2 -2
  41. package/dist/internal/{pano-compass-2S5mt7af.js.map → pano-compass-CMIljzhC.js.map} +1 -1
  42. package/dist/internal/{pano-door-label-CK0sYPKY.js → pano-door-label-B4nqTTzN.js} +2 -2
  43. package/dist/internal/{pano-door-label-CK0sYPKY.js.map → pano-door-label-B4nqTTzN.js.map} +1 -1
  44. package/dist/internal/{pano-measure-DEofgQkM.js → pano-measure-C2r0ecHk.js} +2 -2
  45. package/dist/internal/{pano-measure-DEofgQkM.js.map → pano-measure-C2r0ecHk.js.map} +1 -1
  46. package/dist/internal/{pano-measure-v2-DVlj0AUQ.js → pano-measure-v2-BfSAjO-I.js} +4 -4
  47. package/dist/internal/{pano-measure-v2-DVlj0AUQ.js.map → pano-measure-v2-BfSAjO-I.js.map} +1 -1
  48. package/dist/internal/pano-ruler-BW78w4lv.js +240 -0
  49. package/dist/internal/pano-ruler-BW78w4lv.js.map +1 -0
  50. package/dist/internal/{pano-tag-BwRqx3wU.js → pano-tag-BXo_4I6D.js} +2 -2
  51. package/dist/internal/{pano-tag-BwRqx3wU.js.map → pano-tag-BXo_4I6D.js.map} +1 -1
  52. package/dist/internal/{pano-video-DF7h9OyJ.js → pano-video-CS6-eXci.js} +2 -2
  53. package/dist/internal/{pano-video-DF7h9OyJ.js.map → pano-video-CS6-eXci.js.map} +1 -1
  54. package/dist/internal/{pipeline-Bcxx8i_q.js → pipeline-BN9A4lPZ.js} +2 -2
  55. package/dist/internal/{pipeline-Bcxx8i_q.js.map → pipeline-BN9A4lPZ.js.map} +1 -1
  56. package/dist/internal/{sculpt-heTYi32h.js → sculpt-e0IRMtjB.js} +3 -3
  57. package/dist/internal/{sculpt-heTYi32h.js.map → sculpt-e0IRMtjB.js.map} +1 -1
  58. package/dist/plugins/area-maker.js +1 -1
  59. package/dist/plugins/cruise.js +2 -2
  60. package/dist/plugins/current-pano-image.js +1 -1
  61. package/dist/plugins/guide-line.js +1 -1
  62. package/dist/plugins/measure-v2.js +2 -2
  63. package/dist/plugins/measure.js +2 -2
  64. package/dist/plugins/model-maker.js +1 -1
  65. package/dist/plugins/object3d-helper.js +2 -2
  66. package/dist/plugins/pano-compass.js +1 -1
  67. package/dist/plugins/pano-door-label.js +1 -1
  68. package/dist/plugins/pano-measure-v2.js +1 -1
  69. package/dist/plugins/pano-measure.js +1 -1
  70. package/dist/plugins/pano-ruler.js +1 -1
  71. package/dist/plugins/pano-tag.js +2 -2
  72. package/dist/plugins/pano-video.js +1 -1
  73. package/dist/plugins/pipeline.js +1 -1
  74. package/dist/plugins/sculpt.js +2 -2
  75. package/dist/types/MeasurePlugin/Controller.d.ts +7 -0
  76. package/dist/types/MeasurePlugin/utils/MeasureMesh.d.ts +5 -0
  77. package/dist/types/MeasurePluginV2/Controller.d.ts +9 -0
  78. package/dist/types/MeasurePluginV2/Magnifier.d.ts +33 -0
  79. package/dist/types/MeasurePluginV2/PointOverlay.d.ts +5 -0
  80. package/dist/types/MeasurePluginV2/closeupView.d.ts +69 -0
  81. package/dist/types/MeasurePluginV2/normalMeasure.d.ts +116 -0
  82. package/dist/types/PanoRulerPlugin/room-height.d.ts +19 -0
  83. package/dist/types/shared-utils/three/Magnifier.d.ts +55 -0
  84. package/dist/utils.js +1 -1
  85. package/package.json +1 -1
  86. package/dist/internal/BasePluginWithData-5iUuu45u.js +0 -7
  87. package/dist/internal/Controller-5BvatEWD.js.map +0 -1
  88. package/dist/internal/PointHelper-grrf8vu1.js +0 -953
  89. package/dist/internal/PointHelper-grrf8vu1.js.map +0 -1
  90. package/dist/internal/edgeSnap-8fdFCcXB.js.map +0 -1
  91. package/dist/internal/measure-v2-DqjBlSeF.js +0 -83
  92. package/dist/internal/measure-v2-DqjBlSeF.js.map +0 -1
  93. package/dist/internal/object3d-helper-CxJ_Ju1y.js +0 -7
  94. package/dist/internal/pano-ruler-fzlUTkVu.js +0 -236
  95. package/dist/internal/pano-ruler-fzlUTkVu.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edgeSnap-CH0HidZm.js","names":["Magnifier","CURSOR_NOT_ALLOW_URL","getRaycasterByNdcPosition","createIntersectionFace","PointHelper2","THREERaycaster","rayOnLine","THREE","_lineDirection","_rayToLineStart","_snapPoint","_screenPoint","DYNAMIC_ADHERE_STICKY_BONUS","DYNAMIC_ADHERE_LOCK_RADIUS_SCALE","DYNAMIC_ADHERE_DEPTH_WEIGHT","getClosestPointParameterOnRay","params","line","lineLengthSq","rayOrigin","b","rayDirection","d","e","denominator","t","clampToLine","MeasurePointSelector","PointSelector","position","intersect","targetIntersect","mouseNdcPosition","size","dynamicAdhereLines","adherePlane","distance","newPoint","adhereLine","closestPoint","projectPoint","ndcPosition","positionOffset","dynamicResult","_point","point","pointIsVisible","raycaster","cameraToPointDistance","lines","cursorX","cursorY","candidates","evaluate","depthDistance","screenDistance","depthScale","isSticky","stickyLine","stickyCandidate","candidate","best","bestScore","score","snapPoint","CLOSEUP_MAX_DEVIATION","resolveCloseupView","options","isFiniteVector","cameraPosition","isFiniteQuaternion","cameraQuaternion","toPoint","cameraDistance","mainDirection","cameraForward","base","previous","normalSign","targetDirection","normal","surfaceNormal","resolveNormalSign","faceOnDirection","alpha","blend","clamp","maxDeviation","deviation","axis","quaternion","translate","closeDistance","dot","previousSign","max","min","value","vector","OVERLAY_STYLE","WORLD_AXES","AXIS_COLORS","projectToCanvas","ndc","worldPoint","viewport","screenX","screenY","getWorldNormal","face","intersection","snapNormal","n","nearestAxisIndex","nearestDot","index","snapThreshold","sign","buildPlaneFrame","snapped","upComponent","yAxis","getProbeLength","camera","projectDirection","origin","scene","tip","direction","MAX_TOWARD_CAMERA_DEPTH_RATIO","MAX_AWAY_FROM_CAMERA_DEPTH_RATIO","getWorldLengthForCanvasLength","canvasLengthAt","length","view","pointDepth","depthRate","maxLength","high","canvasLength","low","iteration","middle","isSelfIntersecting","a","c","cross","p","o","q","segmentsCross","p3","p4","p1","p2","drawPlaneFrame","halfSideLength","negativeX","frame","negativeY","plusX","minusX","plusY","minusY","depthOf","minCornerDepth","clampCorner","cornerDepth","corner","ratio","corners","context","drawAxes","radius","axisLength","tipDotRadius","canvasDirection","projected","projectedLength","worldLength","drawLine","from","to","color","drawFullAxis","opposite","positiveLength","negativeLength","negative","positive","withTipDot","drawMeasureOverlay","MeasureMagnifier","five","enabled","mode","info","centerX","centerY","error","renderCenter","restore","rotateOnly","originalPosition","originalQuaternion","snapshot","TUBE_RADIUS","createTube","path","geometry","material","mesh","MeasurePointHelperV2","frameHalfSide","xAxis","edge","DEFAULT_EDGE_SNAP_OPTIONS","resolveEdgeSnapOptions","_closestPoint","distanceSqToSegment","collectNearbyEdges","model","edges","radiusSq","item","EdgeSnapCache","stepSq"],"sources":["../../src/MeasurePluginV2/PointSelector.ts","../../src/MeasurePluginV2/closeupView.ts","../../src/MeasurePluginV2/PointOverlay.ts","../../src/MeasurePluginV2/Magnifier.ts","../../src/MeasurePluginV2/PointHelper.ts","../../src/MeasurePluginV2/edgeSnap.ts"],"sourcesContent":["import { CURSOR_NOT_ALLOW_URL } from '../shared-utils/three/PointSelector/utils/contents'\nimport * as THREE from 'three/webgpu'\nimport { PointSelector, type PointIntersection } from '../shared-utils/three/PointSelector'\nimport { getRaycasterByNdcPosition } from '../shared-utils/five/getRaycasterByNdcPosition'\nimport { createIntersectionFace } from '../shared-utils/three/createIntersectionFace'\nimport { rayOnLine } from '../Sculpt/utils/three/rayOnLine'\nimport { THREERaycaster } from '../shared-utils/three/core/Raycaster'\n\nconst _lineDirection = new THREE.Vector3()\nconst _rayToLineStart = new THREE.Vector3()\nconst _snapPoint = new THREE.Vector3()\nconst _screenPoint = new THREE.Vector3()\n\n\n/**\n * 已经吸住的边在评分时额外减去的像素值。\n * 新边必须比它好出这个「死区」,才允许切换,避免 1px 级鼠标抖动时在相邻边之间来回跳。\n */\nconst DYNAMIC_ADHERE_STICKY_BONUS = 4\n\n/**\n * 「锁定」半径(相对进入半径的比例)。\n * 光标离已吸住的边这么近时不再重新评估其它边;两条边在屏幕上重叠时,\n * 若允许重新评分,光标 1px 的移动就足以让两者互换,表现为落点来回跳。\n */\nconst DYNAMIC_ADHERE_LOCK_RADIUS_SCALE = 0.6\n\n/**\n * 动态吸附选择时,深度(米)折算成像素的权重。\n * 屏幕上一样近时优先吸离落点更近的边,避免吸到后面那层被遮挡的轮廓。\n */\nconst DYNAMIC_ADHERE_DEPTH_WEIGHT = 60\n\n/**\n * @description 求离射线(origin + s * direction, s >= 0)最近的线段参数 t。\n *\n * 不能像 `rayOnLine` 那样在屏幕上取仿射比例再回推 3D 点:透视投影沿线段不是线性的,\n * 线段两端深度不同时,屏幕上的等分点并不对应 3D 线段的等分点,落点会偏离光标。\n * 这里直接解射线与直线的最小距离,得到的就是「投影后离光标最近」的那个 3D 点。\n */\nfunction getClosestPointParameterOnRay(params: {\n rayOrigin: THREE.Vector3\n rayDirection: THREE.Vector3\n line: THREE.Line3\n clampToLine: boolean\n}): number {\n const { rayOrigin, rayDirection, line, clampToLine } = params\n _lineDirection.subVectors(line.end, line.start)\n const lineLengthSq = _lineDirection.lengthSq()\n if (lineLengthSq < 1e-12) return 0\n\n _rayToLineStart.subVectors(rayOrigin, line.start)\n const b = rayDirection.dot(_lineDirection)\n const d = rayDirection.dot(_rayToLineStart)\n const e = _lineDirection.dot(_rayToLineStart)\n const denominator = lineLengthSq - b * b\n\n let t: number\n if (denominator < 1e-9) {\n // 视线与线段几乎平行:参数对位置不敏感,退化为离射线更近的那个端点\n const startDistance = new THREE.Vector3().subVectors(line.start, rayOrigin).cross(rayDirection).length()\n const endDistance = new THREE.Vector3().subVectors(line.end, rayOrigin).cross(rayDirection).length()\n t = startDistance <= endDistance ? 0 : 1\n } else {\n t = (e - b * d) / denominator\n }\n\n return clampToLine ? Math.min(1, Math.max(0, t)) : t\n}\n\n/** V2 snapping only. The original PointSelector keeps its existing algorithm. */\nexport class MeasurePointSelector extends PointSelector {\n /**\n * @description 动态吸附线提供器:每次选点更新、吸附计算前调用,返回本次参与吸附的线段,不会写回 adhereLine。\n * 适合按光标位置实时从模型提取的边线。返回的线按有限线段处理(clampToLine),\n * 与 adhereLine 里的无限轴线(如量尺的 XYZ 轴)区分开。\n */\n public adhereLineProvider: ((params: { intersection: PointIntersection; pointSelector: PointSelector }) => THREE.Line3[]) | null = null\n\n /**\n * @description 动态吸附线的屏幕吸附半径(像素)\n * 动态线按「光标到线段投影的屏幕距离」取最近的一条,而不是按 3D 距离排序后让远边覆盖近边\n */\n public dynamicAdhereScreenRadius = 10\n\n /**\n * @description 已吸附后保持吸附的屏幕半径(像素),略大于进入半径,避免在阈值边界来回跳动\n */\n public dynamicAdhereKeepScreenRadius = 14\n\n /**\n * @description 动态吸附线允许的最大 3D 距离(米),用于排除画面深处或明显不在落点附近的边\n */\n public dynamicAdhereMaxDistance = 0.45\n\n /**\n * 上一次吸住的动态线。候选线集合会随查询球心移动而变化,\n * 把吸住的边单独留住,光标在保持半径内时优先继续用它,避免在相邻边之间来回跳。\n */\n private dynamicAdhereLine: THREE.Line3 | null = null\n\n protected override get mouseNdcPosition() {\n return this.mode === 'fixed' ? { x: 0.5, y: 0.5 } : super.mouseNdcPosition\n }\n\n public refresh() {\n const position = this.mouseNdcPosition\n if (this.enabled && position) this.updateByNdcPosition(position)\n }\n\n private readonly onFocusLost = () => {\n this.adhereEnabled = true\n this.clearDynamicAdhereLine()\n }\n\n public override enable() {\n super.enable()\n window.addEventListener('blur', this.onFocusLost)\n }\n\n public override disable() {\n window.removeEventListener('blur', this.onFocusLost)\n this.clearDynamicAdhereLine()\n super.disable()\n this.adhereEnabled = true\n }\n\n public clearDynamicAdhereLine() {\n this.dynamicAdhereLine = null\n }\n\n protected override updatePointSelectorHelperIntersect = (intersect?: PointIntersection | null) => {\n if (!intersect) {\n this.dynamicAdhereLine = null\n this.pointSelectorHelper.hide()\n this.pointSelectorHelper.position = intersect\n this.emit('intersectionUpdate', intersect)\n this.five.getElement().style.cursor = CURSOR_NOT_ALLOW_URL\n return\n }\n\n this.onEnter()\n if (!this.cursorError) this.five.getElement().style.cursor = ''\n\n // 有吸附点的话,就吸附到最近的点.adherePoints\n const targetIntersect = { ...intersect }\n\n if (this.adhereEnabled && this.mouseNdcPosition && this.five.renderer && typeof this.adhereRadius === 'number') {\n const mouseNdcPosition = {\n x: this.mouseNdcPosition.x,\n y: 1 - this.mouseNdcPosition.y,\n }\n const size = this.five.renderer!.getSize(new THREE.Vector2())\n\n // 按当前落点实时计算的吸附线(例如模型边线)。不能写回 adhereLine,\n // 否则每次 mousemove 都会把上一次的线累积进去。\n const dynamicAdhereLines = this.adhereLineProvider?.({ intersection: intersect, pointSelector: this }) ?? []\n // V2 同时处理静态目标、动态候选以及上次锁定的边。\n const hasAdhereTarget = typeof this.adherePoints === 'function' || this.adherePlane.length > 0 || this.adhereLine.length > 0 || dynamicAdhereLines.length > 0 || this.dynamicAdhereLine !== null\n\n if (hasAdhereTarget) {\n // // ========== 吸附面 ==========\n for (const adherePlane of this.adherePlane) {\n const distance = adherePlane.distanceToPoint(intersect.point)\n let newPoint = adherePlane.projectPoint(intersect.point, new THREE.Vector3())\n if (!this.plane || this.plane.distanceToPoint(newPoint) < 0.01) {\n newPoint = this.plane?.projectPoint(newPoint, new THREE.Vector3()) ?? newPoint\n if (distance < this.adhereRadius) {\n targetIntersect.point = newPoint.clone()\n targetIntersect.adherePoint = newPoint.clone()\n if (!this.plane) {\n targetIntersect.face = createIntersectionFace(adherePlane.normal.clone())\n }\n targetIntersect.isAdsorbed = true\n }\n }\n }\n\n // ========== 吸附线 ==========\n for (const adhereLine of this.adhereLine) {\n let newPoint = adhereLine.closestPointToPoint(intersect.point, false, new THREE.Vector3())\n if (newPoint.distanceTo(intersect.point) < this.adhereRadius) {\n if (!this.plane || this.plane.distanceToPoint(newPoint) < 0.01) {\n newPoint = this.plane?.projectPoint(newPoint, new THREE.Vector3()) ?? newPoint\n targetIntersect.point = newPoint.clone()\n targetIntersect.adhereLine = adhereLine.clone()\n targetIntersect.isAdsorbed = true\n }\n } else {\n const closestPoint = rayOnLine({\n raycaster: new THREERaycaster(this.five.camera.position, intersect.point.clone().sub(this.five.camera.position).normalize()),\n line: adhereLine,\n clampToLine: false,\n })\n const projectPoint = closestPoint.clone().project(this.five.camera)\n if (projectPoint.z <= 1) {\n const ndcPosition = {\n x: (projectPoint.x + 1) / 2,\n y: (1 - projectPoint.y) / 2,\n }\n\n const positionOffset = {\n x: Math.abs(ndcPosition.x - mouseNdcPosition.x) * size.width,\n y: Math.abs(ndcPosition.y - mouseNdcPosition.y) * size.height,\n }\n\n let newPoint = closestPoint.clone()\n if (positionOffset.x < 10 && positionOffset.y < 10) {\n if (!this.plane || this.plane.distanceToPoint(newPoint) < 0.01) {\n newPoint = this.plane?.projectPoint(newPoint, new THREE.Vector3()) ?? newPoint\n targetIntersect.point = newPoint.clone()\n targetIntersect.adhereLine = adhereLine.clone()\n targetIntersect.isAdsorbed = true\n }\n }\n }\n }\n }\n\n // ========== 动态吸附线(模型边) ==========\n // 取屏幕上离光标最近的一条,并吸附到线段上离光标最近的点。\n // 不能沿用「后匹配者优先」:候选边很多时,最后一条匹配的往往是远处的边,会造成落点跳动和不精确。\n const dynamicResult = this.getBestDynamicAdhereResult({\n lines: dynamicAdhereLines,\n point: intersect.point,\n mouseNdcPosition,\n size,\n })\n if (dynamicResult) {\n targetIntersect.point = dynamicResult.point\n targetIntersect.adhereLine = dynamicResult.line.clone()\n targetIntersect.isAdsorbed = true\n }\n\n // ========== 吸附点 ==========\n for (const _point of this.adherePoints?.({ intersection: intersect, pointSelectorInstance: this }) ?? []) {\n const point = _point\n point.IsAlwaysVisible = _point.IsAlwaysVisible\n\n if (this.plane && this.plane.distanceToPoint(point) > 0.01) continue\n\n const distance = point.distanceTo(intersect.point)\n if (distance < this.adhereRadius) {\n // 1. 距离吸附判断\n targetIntersect.point = this.plane?.projectPoint(point, new THREE.Vector3()) ?? point.clone()\n if (!this.plane && _point.normal) targetIntersect.face = createIntersectionFace(_point.normal)\n targetIntersect.isAdsorbed = true\n\n break\n } else {\n // 2. 屏幕位置吸附判断\n const projectPoint = point.clone().project(this.five.camera)\n if (projectPoint.z > 1) continue // 屏幕外的点不吸附\n const size = this.five.renderer!.getSize(new THREE.Vector2())\n\n const ndcPosition = {\n x: (projectPoint.x + 1) / 2,\n y: (1 - projectPoint.y) / 2,\n }\n\n const positionOffset = {\n x: Math.abs(ndcPosition.x - mouseNdcPosition.x) * size.width,\n y: Math.abs(ndcPosition.y - mouseNdcPosition.y) * size.height,\n }\n\n if (positionOffset.x < 10 && positionOffset.y < 10) {\n // 2.1 满足屏幕位置吸附条件,还需要判断点是否可见\n let pointIsVisible = false\n if (point.IsAlwaysVisible) {\n pointIsVisible = true\n } else {\n // 碰撞检测\n const raycaster = new THREE.Raycaster()\n raycaster.setFromCamera(new THREE.Vector2(ndcPosition.x, ndcPosition.y), this.five.camera)\n const intersect = this.five.model.intersectRaycaster(raycaster)[0]\n const distanceError = 0.1\n const cameraToPointDistance = this.five.camera.position.distanceTo(point)\n if (intersect && intersect.distance < cameraToPointDistance + distanceError) {\n pointIsVisible = true\n }\n }\n\n if (pointIsVisible) {\n targetIntersect.point = point.clone()\n if (!this.plane && _point.normal) {\n targetIntersect.face = createIntersectionFace(_point.normal)\n }\n targetIntersect.isAdsorbed = true\n break\n }\n }\n }\n }\n }\n } else {\n this.dynamicAdhereLine = null\n }\n this.pointSelectorHelper.updateWithIntersect(targetIntersect, { emitEvent: false }) // 关掉pointSelectorHelper的事件,自己手动触发,否则会触发两次\n this.emit('intersectionUpdate', targetIntersect)\n }\n\n /**\n * @description 在动态吸附线(模型边)中选出屏幕上离光标最近的一条。\n *\n * 三个要点:\n * 1. 用射线与线段的最近点求落点,避免透视投影下屏幕比例回推 3D 点造成的偏差;\n * 2. 光标贴着已吸住的边时直接锁定,不让相邻边参与评分,消除来回跳;\n * 3. 需要换边时按「屏幕距离 + 深度惩罚」择优,避免吸到屏幕上重叠、但实际更远的边。\n */\n private getBestDynamicAdhereResult(params: {\n lines: THREE.Line3[]\n point: THREE.Vector3\n mouseNdcPosition: { x: number; y: number }\n size: THREE.Vector2\n }): { point: THREE.Vector3; line: THREE.Line3 } | null {\n const { lines, point, mouseNdcPosition, size } = params\n if (lines.length === 0 && !this.dynamicAdhereLine) {\n this.dynamicAdhereLine = null\n return null\n }\n\n const raycaster = getRaycasterByNdcPosition(this.five, { x: mouseNdcPosition.x, y: 1 - mouseNdcPosition.y })\n const rayOrigin = raycaster.ray.origin\n const rayDirection = raycaster.ray.direction\n const cursorX = mouseNdcPosition.x * size.width\n const cursorY = mouseNdcPosition.y * size.height\n\n type Candidate = {\n line: THREE.Line3\n t: number\n screenDistance: number\n depthDistance: number\n isSticky: boolean\n }\n const candidates: Candidate[] = []\n\n const evaluate = (line: THREE.Line3, isSticky: boolean): Candidate | null => {\n const t = getClosestPointParameterOnRay({ rayOrigin, rayDirection, line, clampToLine: true })\n _snapPoint.lerpVectors(line.start, line.end, t)\n\n const depthDistance = _snapPoint.distanceTo(point)\n if (depthDistance > this.dynamicAdhereMaxDistance) return null\n if (this.plane && Math.abs(this.plane.distanceToPoint(_snapPoint)) > 0.01) return null\n\n _screenPoint.copy(_snapPoint).project(this.five.camera)\n // 点在相机后方时投影坐标不可靠,跳过\n if (_screenPoint.z > 1) return null\n\n const screenDistance = Math.hypot(\n ((_screenPoint.x + 1) / 2) * size.width - cursorX,\n ((1 - _screenPoint.y) / 2) * size.height - cursorY,\n )\n // 越深的边要求光标对得越准,避免远处轮廓在 10px 外把落点拽走\n const depthRatio = this.dynamicAdhereMaxDistance > 0 ? Math.min(1, depthDistance / this.dynamicAdhereMaxDistance) : 0\n const depthScale = 1 - 0.6 * depthRatio\n const screenRadius = (isSticky ? this.dynamicAdhereKeepScreenRadius : this.dynamicAdhereScreenRadius) * depthScale\n if (screenDistance > screenRadius) return null\n\n return { line, t, screenDistance, depthDistance, isSticky }\n }\n\n // 上一帧吸住的边可能已经不在本次 provider 返回的集合里(查询球心移动会换一批边),\n // 单独保留它,只要光标还在保持半径内就继续作为候选。\n const stickyLine = this.dynamicAdhereLine\n if (stickyLine) {\n const stickyCandidate = evaluate(stickyLine, true)\n if (stickyCandidate) candidates.push(stickyCandidate)\n }\n for (const line of lines) {\n const candidate = evaluate(line, false)\n if (candidate) candidates.push(candidate)\n }\n\n if (candidates.length === 0) {\n this.dynamicAdhereLine = null\n return null\n }\n\n // 1. 光标仍贴着已吸住的边时直接锁定它,不再和别的边比较。\n // 两条边在屏幕上重叠时,重新评分会让它们随 1px 光标抖动来回互换,落点就会跳。\n let best: Candidate | null = null\n const stickyCandidate = candidates.find((candidate) => candidate.isSticky)\n if (stickyCandidate && stickyCandidate.screenDistance <= this.dynamicAdhereScreenRadius * DYNAMIC_ADHERE_LOCK_RADIUS_SCALE) {\n best = stickyCandidate\n }\n\n // 2. 否则按「屏幕距离 + 深度惩罚 - 已吸住边的奖励」择优:\n // 深度项让更远/更可能被遮挡的边需要明显更靠近光标才可能胜出,\n // 奖励项让已吸住的边只要不是明显更差就继续用。\n if (!best) {\n let bestScore = Infinity\n for (const candidate of candidates) {\n const score =\n candidate.screenDistance +\n candidate.depthDistance * DYNAMIC_ADHERE_DEPTH_WEIGHT -\n (candidate.isSticky ? DYNAMIC_ADHERE_STICKY_BONUS : 0)\n if (score < bestScore) {\n bestScore = score\n best = candidate\n }\n }\n }\n\n if (!best) {\n this.dynamicAdhereLine = null\n return null\n }\n\n this.dynamicAdhereLine = best.line.clone()\n const snapPoint = new THREE.Vector3().lerpVectors(best.line.start, best.line.end, best.t)\n return {\n line: best.line,\n point: this.plane?.projectPoint(snapPoint, new THREE.Vector3()) ?? snapPoint,\n }\n }\n\n}\n","import * as THREE from 'three/webgpu'\n\n/**\n * 放大镜近景视角的取向计算。\n *\n * 目标(与 MP 的观感一致):放大镜不是主视图的简单放大。落点所在表面没有正对相机时,\n * 视角会朝表面法线偏一点,让斜面 / 侧面在放大镜里以更接近正面的角度呈现;\n * 同时要保证它始终是「主视图那台相机绕落点转了一点」——不换机位、不翻滚、偏转角有上限。\n *\n * 这里只做纯数学,相机怎么临时改、怎么还原由调用方负责。\n */\n\n/** 近景相机到落点的距离(米) */\nexport const CLOSEUP_DISTANCE = 0.6\n/** 朝表面转正的比例:把「主视线到正对表面」的夹角消掉这么多 */\nexport const CLOSEUP_NORMAL_BLEND = 0.5\n/** 近景视角相对主视图的最大偏转角(弧度)。超过就封顶,避免侧视时像换了个机位 */\nexport const CLOSEUP_MAX_DEVIATION = Math.PI / 6\n/** 判定法线朝向时的滞回区间(点积):视线接近贴着表面时沿用上一帧朝向,避免来回翻 */\nexport const CLOSEUP_FLIP_EPSILON = 0.05\n\n/** 跨帧状态:目前只用于法线朝向的滞回 */\nexport interface CloseupViewState {\n /** 本帧的视线方向(单位向量) */\n direction: THREE.Vector3\n /** 本帧的落点 */\n point: THREE.Vector3\n /** 本帧法线的朝向:1 = 与传入的法线同向,-1 = 反向,null = 没有法线 */\n normalSign: 1 | -1 | null\n}\n\nexport interface CloseupViewOptions {\n /** 落点(世界坐标) */\n point: THREE.Vector3\n /** 落点所在表面的法线(世界坐标,方向不限);拿不到时传 null,只推近不偏转 */\n normal: THREE.Vector3 | null\n /** 主相机位置 */\n cameraPosition: THREE.Vector3\n /** 主相机世界四元数;近景姿态由它绕落点做最小旋转得到,因此不会翻滚 */\n cameraQuaternion: THREE.Quaternion\n /** 近景距离,默认 CLOSEUP_DISTANCE;主相机比这更近时保持原距离。translate=false 时忽略 */\n distance?: number\n /**\n * 是否把相机沿最终视线移动到落点附近。\n * true = 旧行为(推到 0.6m);false = 只旋转、不平移,放大镜与主视图共用同一机位,\n * 不会产生视差,也不会让全景球心偏移导致画面扭曲。\n */\n translate?: boolean\n /** 朝法线偏转的比例,默认 CLOSEUP_NORMAL_BLEND */\n blend?: number\n /** 最大偏转角(弧度),默认 CLOSEUP_MAX_DEVIATION */\n maxDeviation?: number\n /** 上一帧的状态,只用于法线朝向的滞回;第一帧传 null */\n previous?: CloseupViewState | null\n}\n\nexport interface CloseupView {\n /** 近景相机位置 */\n position: THREE.Vector3\n /** 近景相机视线方向(单位向量) */\n direction: THREE.Vector3\n /** 近景相机世界四元数(视线方向与 direction 一致,roll 由主相机继承) */\n quaternion: THREE.Quaternion\n /** 供下一次调用使用的状态 */\n state: CloseupViewState\n}\n\n/**\n * 计算近景相机的位姿:\n * 1. translate=true 时相机落在「落点后方 distance 处、朝向落点」这条线上,落点因此仍在画面正中;\n * translate=false 时保持原机位,只改变朝向(放大镜与主视图共用视点,不产生视差);\n * 2. 姿态 = 主相机姿态先把视线校正到落点,再朝「正对表面」的方向转一小段(有上限);\n * 3. 因为是绕相机的旋转,画面不会翻滚,观感就是「同一台相机微微转了个角度」。\n */\nexport function resolveCloseupView(options: CloseupViewOptions): CloseupView | null {\n const { point, cameraPosition, cameraQuaternion } = options\n if (!isFiniteVector(point) || !isFiniteVector(cameraPosition)) return null\n if (!isFiniteQuaternion(cameraQuaternion)) return null\n\n const toPoint = point.clone().sub(cameraPosition)\n const cameraDistance = toPoint.length()\n if (!Number.isFinite(cameraDistance) || cameraDistance < 1e-4) return null\n const mainDirection = toPoint.clone().divideScalar(cameraDistance)\n\n // 主相机实际朝向和「看向落点」可能差一点点(快速移动时取到的相机不一定正对落点),以看向落点为准\n const cameraForward = new THREE.Vector3(0, 0, -1).applyQuaternion(cameraQuaternion).normalize()\n const base = new THREE.Quaternion().setFromUnitVectors(cameraForward, mainDirection).multiply(cameraQuaternion)\n base.normalize()\n\n const previous = options.previous ?? null\n let normalSign: 1 | -1 | null = null\n let targetDirection = mainDirection.clone()\n\n const normal = options.normal\n if (normal && isFiniteVector(normal) && normal.lengthSq() > 1e-12) {\n const surfaceNormal = normal.clone().normalize()\n normalSign = resolveNormalSign(surfaceNormal, toPoint, previous?.normalSign ?? null)\n // 相机站在法线一侧往回看,所以「正对表面」的视线方向是法线的反向\n const faceOnDirection = surfaceNormal.clone().multiplyScalar(-normalSign)\n\n const alpha = mainDirection.angleTo(faceOnDirection)\n const blend = clamp(options.blend ?? CLOSEUP_NORMAL_BLEND, 0, 1)\n const maxDeviation = clamp(options.maxDeviation ?? CLOSEUP_MAX_DEVIATION, 0, Math.PI)\n const deviation = Math.min(alpha * blend, maxDeviation)\n\n if (deviation > 1e-6 && alpha > 1e-6) {\n const axis = new THREE.Vector3().crossVectors(mainDirection, faceOnDirection)\n // 两条方向平行时没有唯一旋转轴,保持主视线\n if (axis.lengthSq() > 1e-12) targetDirection = mainDirection.clone().applyAxisAngle(axis.normalize(), deviation)\n }\n }\n\n const quaternion = new THREE.Quaternion().setFromUnitVectors(mainDirection, targetDirection).multiply(base)\n quaternion.normalize()\n\n const translate = options.translate !== false\n const closeDistance = Math.min(options.distance ?? CLOSEUP_DISTANCE, cameraDistance)\n return {\n // 只旋转时保持原机位:全景 / 模型都按主视图的同一视点采样,不引入视差\n position: translate ? point.clone().addScaledVector(targetDirection, -closeDistance) : cameraPosition.clone(),\n direction: targetDirection,\n quaternion,\n state: { direction: targetDirection.clone(), point: point.clone(), normalSign },\n }\n}\n\n/**\n * 决定法线要不要翻到相机一侧。\n * 法线指向相机时 `surfaceNormal · (point - camera) < 0`。\n * 视线几乎贴着表面(点积接近 0)时朝向本身没有意义,沿用上一帧,避免相邻两帧左右横跳。\n */\nfunction resolveNormalSign(\n surfaceNormal: THREE.Vector3,\n toPoint: THREE.Vector3,\n previousSign: 1 | -1 | null,\n): 1 | -1 {\n const dot = surfaceNormal.dot(toPoint)\n if (Math.abs(dot) < CLOSEUP_FLIP_EPSILON && previousSign !== null) return previousSign\n return dot > 0 ? -1 : 1\n}\n\nfunction clamp(value: number, min: number, max: number) {\n return Math.min(max, Math.max(min, value))\n}\n\nfunction isFiniteVector(vector: THREE.Vector3) {\n return Number.isFinite(vector.x) && Number.isFinite(vector.y) && Number.isFinite(vector.z)\n}\n\nfunction isFiniteQuaternion(quaternion: THREE.Quaternion) {\n return (\n Number.isFinite(quaternion.x) &&\n Number.isFinite(quaternion.y) &&\n Number.isFinite(quaternion.z) &&\n Number.isFinite(quaternion.w)\n )\n}\n","import * as THREE from 'three/webgpu'\nimport type { PointIntersection } from '../shared-utils'\n\n/**\n * 放大镜内的叠加绘制:坐标轴(PRD M1)+ 落点平面框(PRD M2)\n *\n * 为什么画在 2D canvas 上而不是往场景里加 THREE 物体:\n * 放大镜并不是「再渲染一遍场景」,而是用 five.getPixels() 读一小块像素,翻转后 putImageData 到 2D canvas 上。\n * 所以场景里加什么物体都不会出现在放大镜里,只有 putImageData 之后在 2D context 上画的才会出现。\n */\n\n/** PRD 五、视觉与设计规范 */\nexport const OVERLAY_STYLE = {\n /** M1-2 X 轴颜色 */\n axisXColor: '#EC4A4E',\n /** M1-2 Y 轴颜色 */\n axisYColor: '#1677FF',\n /** M1-2 Z 轴颜色 */\n axisZColor: '#065C08',\n /** M1-5 线条宽度 */\n axisLineWidth: 1.5,\n /** 轴线不透明度 */\n axisOpacity: 0.9,\n /** M1-4 轴线长度 = 放大镜半径 × 0.9 */\n axisLengthRadiusScale: 0.9,\n /** Z 轴端点(朝外的一端)绿色圆点半径 = 放大镜半径 × 该系数 */\n axisTipDotRadiusScale: 0.03,\n /** M2 平面框边框颜色 */\n planeBorderColor: '#FF4D4F',\n /** M2-3 边框宽度 */\n planeBorderWidth: 1.5,\n /** M2-3 边框不透明度 */\n planeBorderOpacity: 0.9,\n /** M2-2 轴对齐面框边长 = 放大镜半径 × 1.2 */\n planeSideRadiusScale: 1.2,\n /** M2 轴对齐判定:表面法线与最近世界轴的夹角小于该值即视为墙 / 地 / 顶 */\n axisSnapAngle: 15,\n} as const\n\nconst WORLD_AXES = [new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 1, 0), new THREE.Vector3(0, 0, 1)]\n\nconst AXIS_COLORS = [OVERLAY_STYLE.axisXColor, OVERLAY_STYLE.axisYColor, OVERLAY_STYLE.axisZColor]\n\ninterface CanvasPoint {\n x: number\n y: number\n}\n\n/**\n * 放大镜把渲染目标上的一块矩形区域拉伸到了 canvas 上,这里描述这块区域以及映射关系。\n * 字段取值与 Magnifier.render() 内部完全一致,父类改动时需要同步。\n */\nexport interface MagnifierViewport {\n camera: THREE.Camera\n /** 渲染目标尺寸,与 Magnifier.render() 里 getSize() 的结果一致 */\n renderSize: THREE.Vector2\n /** 读取区域左边缘在渲染目标像素坐标系中的横坐标 */\n readLeft: number\n /** 读取区域下边缘的纵坐标(渲染目标像素坐标系 y 轴向上,与 Magnifier.render() 里的同名量一致) */\n readTop: number\n /** 读取区域的高度(渲染目标像素) */\n readPixelsHeight: number\n /** 一个渲染像素在放大镜 canvas 上重复的倍数 */\n pixelRadio: number\n /** 放大镜 canvas 的宽高(设备像素) */\n canvasWidth: number\n canvasHeight: number\n /**\n * 叠加层线宽缩放:canvas 设备像素 / CSS 像素。\n * 高清读回时 canvas 变大,固定线宽需要同步放大,否则轴线和平面框会变细。\n */\n lineScale?: number\n}\n\n/** 世界坐标 → 放大镜 canvas 坐标(左上角为原点,y 轴向下) */\nfunction projectToCanvas(worldPoint: THREE.Vector3, viewport: MagnifierViewport): CanvasPoint {\n const ndc = worldPoint.clone().project(viewport.camera)\n const screenX = ((ndc.x + 1) / 2) * viewport.renderSize.x\n const screenY = ((ndc.y + 1) / 2) * viewport.renderSize.y\n return {\n x: (screenX - viewport.readLeft) * viewport.pixelRadio,\n // 读出来的像素做过 Y 翻转(canvas 与渲染目标 Y 轴相反),所以要从读取区域的底边往回收\n y: (viewport.readTop + viewport.readPixelsHeight - screenY) * viewport.pixelRadio,\n }\n}\n\n/**\n * 求交点的世界坐标系法线。\n * 点云等拿不到 face 的情况返回 null(此时只画坐标轴,不画平面框)。\n *\n * 注意:这里不能再乘模型的法线矩阵。THREE.Mesh.raycast 在生成相交结果时已经把面法线\n * 用 normalMatrix 变换到世界坐标系了;PointSelector 吸附到平面时合成的 Face3 更是直接写入\n * 世界坐标系的平面法线。再乘一次会让法线偏离真实表面法线,红色平面框也就不再贴合选点所在面。\n */\nexport function getWorldNormal(intersection: PointIntersection): THREE.Vector3 | null {\n // three 0.183 已经移除 Face3 类型,这里只依赖运行时存在的 normal 字段\n const face = (intersection as unknown as { face?: { normal?: THREE.Vector3 | null } | null }).face\n if (!face?.normal) return null\n\n const normal = face.normal.clone()\n if (normal.lengthSq() < 1e-12) return null\n return normal.normalize()\n}\n\nexport interface PlaneFrame {\n /** 平面法线,也就是绿色 Z 轴的方向(朝表面外侧) */\n normal: THREE.Vector3\n /** 平面内的水平方向 = 红色 X 轴 */\n xAxis: THREE.Vector3\n /** 平面内最接近竖直的方向 = 蓝色 Y 轴 */\n yAxis: THREE.Vector3\n /** 轴对齐面吸附到的世界轴下标(0=X / 1=Y / 2=Z),斜面为 -1 */\n alignedAxisIndex: number\n}\n\n/** 法线接近世界轴时吸附过去(保留正负号),保证墙 / 地 / 顶上的框是正的 */\nfunction snapNormal(normal: THREE.Vector3) {\n const n = normal.clone().normalize()\n let nearestAxisIndex = 0\n let nearestDot = 0\n WORLD_AXES.forEach((axis, index) => {\n const dot = Math.abs(n.dot(axis))\n if (dot > nearestDot) {\n nearestDot = dot\n nearestAxisIndex = index\n }\n })\n\n const snapThreshold = Math.cos(THREE.MathUtils.degToRad(OVERLAY_STYLE.axisSnapAngle))\n if (nearestDot < snapThreshold) return { normal: n, alignedAxisIndex: -1 }\n\n const axis = WORLD_AXES[nearestAxisIndex]\n const sign = n.dot(axis) >= 0 ? 1 : -1\n return { normal: axis.clone().multiplyScalar(sign), alignedAxisIndex: nearestAxisIndex }\n}\n\n/**\n * 由表面法线构造落点所在平面的坐标系。\n * 绿色 Z 轴 = 法线;蓝色 Y 轴取平面内最接近「世界向上」的方向;红色 X 轴 = 剩下的那条平面内方向。\n * 平面接近水平(地板 / 天花板)时平面内没有竖直方向,退化为用世界 Z 当蓝色轴。\n *\n * 放大镜里的叠加层和场景里的小光标共用这一份坐标系,保证两处看到的红 / 绿 / 蓝完全一致。\n */\nexport function buildPlaneFrame(normal: THREE.Vector3): PlaneFrame {\n const snapped = snapNormal(normal)\n const n = snapped.normal\n\n const upComponent = n.clone().multiplyScalar(n.dot(WORLD_AXES[1]))\n let yAxis = WORLD_AXES[1].clone().sub(upComponent)\n if (yAxis.lengthSq() < 1e-8) {\n yAxis = WORLD_AXES[2].clone().sub(n.clone().multiplyScalar(n.dot(WORLD_AXES[2])))\n }\n yAxis.normalize()\n\n const xAxis = new THREE.Vector3().crossVectors(yAxis, n).normalize()\n return { normal: n, xAxis, yAxis, alignedAxisIndex: snapped.alignedAxisIndex }\n}\n\n/** 探针长度:只用来求投影方向与投影比例,取相机到落点距离的 1% */\nfunction getProbeLength(point: THREE.Vector3, camera: THREE.Camera) {\n return Math.max(1e-4, camera.position.distanceTo(point) * 0.01)\n}\n\n/** 一次绘制过程中反复要用的几个量,打包传参免得函数签名过长 */\ninterface OverlayScene {\n context: CanvasRenderingContext2D\n viewport: MagnifierViewport\n /** 落点(世界坐标) */\n point: THREE.Vector3\n probeLength: number\n}\n\n/** 某世界方向上一段长度投影到 canvas 上的向量 */\nfunction projectDirection(scene: OverlayScene, direction: THREE.Vector3): CanvasPoint {\n const origin = projectToCanvas(scene.point, scene.viewport)\n const tip = projectToCanvas(scene.point.clone().addScaledVector(direction, scene.probeLength), scene.viewport)\n return { x: tip.x - origin.x, y: tip.y - origin.y }\n}\n\n/** 朝相机延伸时最多走过落点深度的 80%,端点深度至少保留 20%,避免贴脸后投影发散或翻转 */\nconst MAX_TOWARD_CAMERA_DEPTH_RATIO = 0.8\n/** 朝远处延伸时最多走过落点深度的 3 倍,避免在消失点附近无限延长 */\nconst MAX_AWAY_FROM_CAMERA_DEPTH_RATIO = 3\n\n/**\n * 想让某方向从落点出发、在 canvas 上占 canvasLength 像素时,需要多长的世界长度。\n * 端点始终限制在相机前方:\n * - 朝远处延伸时投影长度有上限(趋向消失点),够不到 canvasLength 时就停在上限附近;\n * - 朝相机延伸时端点不能越过相机,否则投影会翻转,平面框画成交叉的「星形」。\n * 在这个范围内投影长度随世界长度单调增加,所以用二分求解。\n * 需要反方向的长度时把 direction 取反再调一次,因为两侧的透视压缩不一样。\n */\nfunction getWorldLengthForCanvasLength(scene: OverlayScene, direction: THREE.Vector3, canvasLength: number) {\n const { point, viewport } = scene\n const origin = projectToCanvas(point, viewport)\n const canvasLengthAt = (length: number) => {\n const tip = projectToCanvas(point.clone().addScaledVector(direction, length), viewport)\n return Math.hypot(tip.x - origin.x, tip.y - origin.y)\n }\n\n const view = viewport.camera.matrixWorldInverse\n const pointDepth = -point.clone().applyMatrix4(view).z\n if (!(pointDepth > 0)) return 0\n // 沿 direction 每走 1 米,视线深度变化多少\n const depthRate = -point.clone().add(direction).applyMatrix4(view).z - pointDepth\n\n let maxLength = Infinity\n if (depthRate < -1e-9) maxLength = (pointDepth * MAX_TOWARD_CAMERA_DEPTH_RATIO) / -depthRate\n else if (depthRate > 1e-9) maxLength = (pointDepth * MAX_AWAY_FROM_CAMERA_DEPTH_RATIO) / depthRate\n\n let high = maxLength\n if (!Number.isFinite(high)) {\n // 与成像平面平行:投影近似线性,倍增找上界\n high = scene.probeLength\n while (canvasLengthAt(high) < canvasLength && high < pointDepth * MAX_AWAY_FROM_CAMERA_DEPTH_RATIO) high *= 2\n }\n if (!(canvasLengthAt(high) > canvasLength)) return Number.isFinite(canvasLengthAt(high)) ? high : 0\n\n let low = 0\n for (let iteration = 0; iteration < 24; iteration += 1) {\n const middle = (low + high) / 2\n if (canvasLengthAt(middle) < canvasLength) low = middle\n else high = middle\n }\n return (low + high) / 2\n}\n\n/** 四边形 ABCD 的对边 AB/CD 或 BC/DA 相交,即画出来是交叉的「蝴蝶结」 */\nfunction isSelfIntersecting([a, b, c, d]: CanvasPoint[]) {\n const cross = (o: CanvasPoint, p: CanvasPoint, q: CanvasPoint) => (p.x - o.x) * (q.y - o.y) - (p.y - o.y) * (q.x - o.x)\n const segmentsCross = (p1: CanvasPoint, p2: CanvasPoint, p3: CanvasPoint, p4: CanvasPoint) =>\n cross(p3, p4, p1) * cross(p3, p4, p2) < 0 && cross(p1, p2, p3) * cross(p1, p2, p4) < 0\n return segmentsCross(a, b, c, d) || segmentsCross(b, c, d, a)\n}\n\n/** 平面框:以落点为中心的四边形,只描边不填充 */\nfunction drawPlaneFrame(scene: OverlayScene, frame: PlaneFrame) {\n const { context, viewport, point } = scene\n const radius = Math.min(viewport.canvasWidth, viewport.canvasHeight) / 2\n // M2-2:边长 = 半径 × 1.2,框以落点为中心 => 半边长 = 边长 / 2\n const halfSideLength = (radius * OVERLAY_STYLE.planeSideRadiusScale) / 2\n\n // 四个方向分别求长度:掠射角下朝相机的一侧和朝远处的一侧透视压缩不同,共用一个长度会让近侧角跑到相机背后\n const negativeX = frame.xAxis.clone().negate()\n const negativeY = frame.yAxis.clone().negate()\n const plusX = getWorldLengthForCanvasLength(scene, frame.xAxis, halfSideLength)\n const minusX = getWorldLengthForCanvasLength(scene, negativeX, halfSideLength)\n const plusY = getWorldLengthForCanvasLength(scene, frame.yAxis, halfSideLength)\n const minusY = getWorldLengthForCanvasLength(scene, negativeY, halfSideLength)\n if (!(plusX > 0) || !(minusX > 0) || !(plusY > 0) || !(minusY > 0)) return\n\n // 单轴长度各自在相机前方,但角点是 X、Y 两段相加:两段都朝相机时深度变化会叠加,角点可能跑到相机背后,\n // 投影翻转后整个框画成交叉的「蝴蝶结」。所以每个角再单独限制一次深度:\n // 沿「落点 → 角点」方向缩短到深度不低于落点深度的 1 - MAX_TOWARD_CAMERA_DEPTH_RATIO。\n // 四个角仍在平面内围绕落点的四条射线上,且都在相机前方,投影后不会自交。\n const view = viewport.camera.matrixWorldInverse\n const depthOf = (worldPoint: THREE.Vector3) => -worldPoint.clone().applyMatrix4(view).z\n const pointDepth = depthOf(point)\n const minCornerDepth = pointDepth * (1 - MAX_TOWARD_CAMERA_DEPTH_RATIO)\n const clampCorner = (corner: THREE.Vector3) => {\n const cornerDepth = depthOf(corner)\n if (cornerDepth >= minCornerDepth) return corner\n const ratio = (pointDepth - minCornerDepth) / (pointDepth - cornerDepth)\n return point.clone().lerp(corner, ratio)\n }\n\n // 四个角都真实投影一次,这样斜面和强透视下得到的是梯形而不是平行四边形\n const corners = [\n point.clone().addScaledVector(frame.xAxis, plusX).addScaledVector(frame.yAxis, plusY),\n point.clone().addScaledVector(frame.xAxis, -minusX).addScaledVector(frame.yAxis, plusY),\n point.clone().addScaledVector(frame.xAxis, -minusX).addScaledVector(frame.yAxis, -minusY),\n point.clone().addScaledVector(frame.xAxis, plusX).addScaledVector(frame.yAxis, -minusY),\n ].map((corner) => projectToCanvas(clampCorner(corner), viewport))\n\n if (corners.some((corner) => !Number.isFinite(corner.x) || !Number.isFinite(corner.y))) return\n // 兜底:平面几乎侧对相机时,四个角挤成一条细线,数值误差仍可能让投影自交。这时框已经没有意义,不画\n if (isSelfIntersecting(corners)) return\n\n context.save()\n context.beginPath()\n corners.forEach((corner, index) => {\n if (index === 0) context.moveTo(corner.x, corner.y)\n else context.lineTo(corner.x, corner.y)\n })\n context.closePath()\n // M2-3 / M2-4:只描边,不填充\n context.strokeStyle = OVERLAY_STYLE.planeBorderColor\n context.globalAlpha = OVERLAY_STYLE.planeBorderOpacity\n context.lineWidth = OVERLAY_STYLE.planeBorderWidth * (viewport.lineScale ?? 1)\n context.stroke()\n context.restore()\n}\n\n/**\n * 坐标轴。\n * X(红)/ Y(蓝)是贯穿落点的整条线;Z(绿)= 平面法线,只画朝外的一半,端点带一个绿色圆点,\n * 与 PRD 图示一致。线段在 canvas 上的长度固定为半径 × 0.9(M1-4),超出放大镜圆形的部分由 CSS 裁掉。\n */\nfunction drawAxes(scene: OverlayScene, frame: PlaneFrame | null) {\n const { context, viewport, point } = scene\n const radius = Math.min(viewport.canvasWidth, viewport.canvasHeight) / 2\n const axisLength = radius * OVERLAY_STYLE.axisLengthRadiusScale\n const tipDotRadius = radius * OVERLAY_STYLE.axisTipDotRadiusScale\n\n const origin = projectToCanvas(point, viewport)\n if (!Number.isFinite(origin.x) || !Number.isFinite(origin.y)) return\n\n /** 某方向投影到 canvas 上的单位向量;几乎正对相机时返回 null(方向会乱跳,不画) */\n const canvasDirection = (direction: THREE.Vector3) => {\n const projected = projectDirection(scene, direction)\n const projectedLength = Math.hypot(projected.x, projected.y)\n if (!Number.isFinite(projectedLength) || projectedLength < 1e-3) return null\n return { x: projected.x / projectedLength, y: projected.y / projectedLength }\n }\n\n const worldLength = (direction: THREE.Vector3) => getWorldLengthForCanvasLength(scene, direction, axisLength)\n\n const drawLine = (from: CanvasPoint, to: CanvasPoint, color: string) => {\n // NaN 会让 arc() 直接抛异常,异常从放大镜的 renderFrame 回调里抛出去会打断 five 的渲染循环\n if (![from.x, from.y, to.x, to.y].every((value) => Number.isFinite(value))) return\n context.strokeStyle = color\n context.globalAlpha = OVERLAY_STYLE.axisOpacity\n context.lineWidth = OVERLAY_STYLE.axisLineWidth * (viewport.lineScale ?? 1)\n context.beginPath()\n context.moveTo(from.x, from.y)\n context.lineTo(to.x, to.y)\n context.stroke()\n }\n\n /** 贯穿落点的整条轴:两侧分别求解,保证每侧端点都距落点 0.9R */\n const drawFullAxis = (direction: THREE.Vector3, color: string) => {\n if (!canvasDirection(direction)) return\n const opposite = direction.clone().negate()\n const positiveLength = getWorldLengthForCanvasLength(scene, direction, axisLength)\n const negativeLength = getWorldLengthForCanvasLength(scene, opposite, axisLength)\n if (!(positiveLength > 0) || !(negativeLength > 0)) return\n const negative = projectToCanvas(point.clone().addScaledVector(opposite, negativeLength), viewport)\n const positive = projectToCanvas(point.clone().addScaledVector(direction, positiveLength), viewport)\n drawLine(negative, positive, color)\n }\n\n /** 从落点朝外的半条轴;withTipDot 为 true 时在端点补一个实心圆点 */\n const drawHalfAxis = (direction: THREE.Vector3, color: string, withTipDot: boolean) => {\n if (!canvasDirection(direction)) return\n const length = worldLength(direction)\n if (!(length > 0)) return\n const tip = projectToCanvas(point.clone().addScaledVector(direction, length), viewport)\n drawLine(origin, tip, color)\n\n if (!withTipDot) return\n if (!Number.isFinite(tip.x) || !Number.isFinite(tip.y)) return\n context.fillStyle = color\n context.globalAlpha = OVERLAY_STYLE.axisOpacity\n context.beginPath()\n context.arc(tip.x, tip.y, tipDotRadius, 0, Math.PI * 2)\n context.fill()\n }\n\n context.save()\n context.lineCap = 'round'\n\n if (frame) {\n drawFullAxis(frame.xAxis, OVERLAY_STYLE.axisXColor)\n drawFullAxis(frame.yAxis, OVERLAY_STYLE.axisYColor)\n drawHalfAxis(frame.normal, OVERLAY_STYLE.axisZColor, true)\n } else {\n // 点云等拿不到法线:三条世界轴都画整条\n WORLD_AXES.forEach((axis, index) => drawFullAxis(axis, AXIS_COLORS[index]))\n }\n\n context.restore()\n}\n\n/**\n * 绘制落点叠加层。\n * 调用时机:Magnifier.render() 里 putImageData 之后。\n */\nexport function drawMeasureOverlay(context: CanvasRenderingContext2D, viewport: MagnifierViewport, intersection: PointIntersection) {\n const point = intersection.point\n if (!point) return\n if (!Number.isFinite(point.x) || !Number.isFinite(point.y) || !Number.isFinite(point.z)) return\n\n const scene: OverlayScene = { context, viewport, point, probeLength: getProbeLength(point, viewport.camera) }\n const normal = getWorldNormal(intersection)\n const frame = normal ? buildPlaneFrame(normal) : null\n\n if (frame) drawPlaneFrame(scene, frame)\n drawAxes(scene, frame)\n}\n","import type { Five } from '@realsee/five-webgpu'\nimport * as THREE from 'three/webgpu'\nimport type { PointIntersection } from '../shared-utils'\nimport { Magnifier, type MagnifierParameter, type MagnifierRenderInfo } from '../shared-utils/three/Magnifier'\nimport { CLOSEUP_MAX_DEVIATION, resolveCloseupView, type CloseupViewState } from './closeupView'\nimport { drawMeasureOverlay, getWorldNormal, type MagnifierViewport } from './PointOverlay'\n\nexport { CLOSEUP_DISTANCE } from './closeupView'\n\n/** 近景取景方式;与 Editor V2 保持同一套语义 */\nexport type MeasureCloseupMode =\n /** 只旋转、不平移:与主视图共用机位,无新增视差;默认 */\n | 'rotate'\n /** 旧行为:把相机推到落点附近 0.6m,再朝法线偏转;放大倍率高,但会产生视差 */\n | 'push'\n /** 完全沿用主相机,只放大局部像素 */\n | 'off'\n\nexport interface MeasureMagnifierOptions extends MagnifierParameter {\n /** 是否绘制坐标轴 / 平面框叠加层,默认 true(关掉后行为与现状 Magnifier 完全一致) */\n overlayEnabled?: boolean\n /** 兼容旧参数:false 等价于 closeupMode='off' */\n closeupEnabled?: boolean\n /** 近景取景方式,默认 'rotate' */\n closeupMode?: MeasureCloseupMode\n}\n\n/**\n * 量尺 V2 用的放大镜。\n *\n * 完全复用 Magnifier 的「读像素 + 放大」逻辑,只在像素绘制完成后往同一个 2D canvas 上叠加坐标轴与落点平面框。\n * 这里不重写 render():V1 的 Magnifier 是异步读回像素的,用 afterRender 回调才能保证叠加层和当前这帧像素严格对应。\n */\nexport class MeasureMagnifier extends Magnifier {\n /** 父类把 five 存成了 private,这里自己留一份 */\n private readonly overlayFive: Five\n /** 父类把 context 存成了 private;getContext('2d') 拿到的是同一个实例 */\n private overlayContext: CanvasRenderingContext2D | null = null\n /** 由 PointSelector 的 intersectionUpdate 事件注入 */\n private overlayIntersection: PointIntersection | null = null\n private overlayEnabled: boolean\n private closeupMode: MeasureCloseupMode\n /** 本次渲染实际使用的相机(推近后的副本),叠加层投影必须和它一致 */\n private overlayCamera: THREE.Camera | null = null\n /** 近景视角的平滑状态 */\n private closeupState: CloseupViewState | null = null\n\n public constructor(five: Five, options?: MeasureMagnifierOptions) {\n super(five, options ?? {})\n this.overlayFive = five\n this.overlayEnabled = options?.overlayEnabled !== false\n this.closeupMode = options?.closeupMode ?? (options?.closeupEnabled === false ? 'off' : 'rotate')\n this.beforeRender = this.prepareCloseup\n this.afterRender = this.renderOverlay\n }\n\n /** 每次交点更新时调用;传 null 表示当前没有命中任何模型 */\n public setIntersection(intersection: PointIntersection | null) {\n this.overlayIntersection = intersection\n }\n\n /** 开关叠加层,用于灰度 / 排查问题 */\n public setOverlayEnabled(enabled: boolean) {\n this.overlayEnabled = enabled\n }\n\n /** 开关近景 3D 视角,用于灰度 / 对比;开启时使用默认的 rotate 模式 */\n public setCloseupEnabled(enabled: boolean) {\n this.setCloseupMode(enabled ? 'rotate' : 'off')\n }\n\n /** 切换近景取景方式,用于三种模式的对比 */\n public setCloseupMode(mode: MeasureCloseupMode) {\n this.closeupMode = mode\n if (mode === 'off') this.closeupState = null\n }\n\n public getCloseupMode() {\n return this.closeupMode\n }\n\n private renderOverlay = (info: MagnifierRenderInfo) => {\n if (!this.overlayEnabled) return\n if (!this.state.enabled) return\n\n const intersection = this.overlayIntersection\n // 未命中模型(悬空处生成的虚拟点)不画:PRD M2 要求「不绘制平面框和轴线」\n if (!intersection || intersection.isVirtual || !intersection.point) return\n\n const camera = this.overlayCamera\n if (!camera) return\n // 像素读回是异步的:只有本次读取的中心仍是当前落点时才画,避免快速移动时叠加层和新像素错开。\n const ndc = intersection.point.clone().project(camera)\n if (![ndc.x, ndc.y].every((value) => Number.isFinite(value))) return\n const screenX = ((ndc.x + 1) / 2) * info.renderSize.x\n const screenY = ((ndc.y + 1) / 2) * info.renderSize.y\n const centerX = info.readLeft + info.readWidth / 2\n const centerY = info.readTop + info.readHeight / 2\n if (Math.abs(screenX - centerX) > 1 || Math.abs(screenY - centerY) > 1) return\n\n const context = this.getOverlayContext()\n if (!context) return\n\n const viewport: MagnifierViewport = {\n camera,\n renderSize: info.renderSize,\n readLeft: info.readLeft,\n readTop: info.readTop,\n readPixelsHeight: info.readHeight,\n pixelRadio: info.pixelRatio,\n canvasWidth: info.canvasWidth,\n canvasHeight: info.canvasHeight,\n // 高清读回时 canvas 变大,线宽按 backing / CSS 的比例放大,保持和旧版一致的观感\n lineScale: info.canvasWidth / this.width,\n }\n\n try {\n drawMeasureOverlay(context, viewport, intersection)\n } catch (error) {\n console.warn('MeasureMagnifier: 叠加层绘制失败', error)\n }\n }\n\n /**\n * 近景 3D 视角:读像素前把相机推到落点附近,并让视线朝落点所在表面的法线偏一点\n * (斜向 / 侧向的表面在放大镜里以更接近正面的角度呈现),读完立即还原。\n * 姿态由主相机绕落点做最小旋转得到:偏转角有上限、画面不翻滚,\n * 相机始终朝向落点,落点因此仍在画面正中,父类按落点投影取的读取区域依旧对准落点。\n */\n private prepareCloseup = (renderCenter: THREE.Vector3 | { x: number; y: number }) => {\n if (!(renderCenter instanceof THREE.Vector3)) {\n this.snapshotOverlayCamera()\n return\n }\n const restore = this.moveCameraCloseup(renderCenter)\n if (!restore) this.snapshotOverlayCamera()\n return restore\n }\n\n private moveCameraCloseup(point: THREE.Vector3): (() => void) | undefined {\n if (this.closeupMode === 'off' || !this.state.enabled) {\n this.closeupState = null\n return\n }\n const intersection = this.overlayIntersection\n if (!intersection || intersection.isVirtual || !point) {\n this.closeupState = null\n return\n }\n\n const camera = this.overlayFive.camera\n if (!camera || (camera as any).isOrthographicCamera) return\n\n // 交点与本次渲染的落点不是同一帧时不做偏转,只推进距离,等下一次渲染跟上\n const normal = intersection.point && intersection.point.distanceTo(point) < 1e-4 ? getWorldNormal(intersection) : null\n\n camera.updateMatrixWorld()\n const rotateOnly = this.closeupMode === 'rotate'\n // 只旋转时,偏转会把落点从画面中心推开;按当前 fov 留出 4° 余量,保证落点仍在视野内\n const maxDeviation = rotateOnly\n ? Math.max(0, Math.min(CLOSEUP_MAX_DEVIATION, THREE.MathUtils.degToRad(camera.fov || 60) / 2 - THREE.MathUtils.degToRad(4)))\n : undefined\n const view = resolveCloseupView({\n point,\n normal,\n cameraPosition: camera.position,\n cameraQuaternion: camera.quaternion,\n previous: this.closeupState,\n translate: !rotateOnly,\n maxDeviation,\n })\n if (!view) return\n this.closeupState = view.state\n\n const originalPosition = camera.position.clone()\n const originalQuaternion = camera.quaternion.clone()\n\n camera.position.copy(view.position)\n camera.quaternion.copy(view.quaternion)\n camera.updateMatrixWorld(true)\n this.snapshotOverlayCamera()\n\n return () => {\n camera.position.copy(originalPosition)\n camera.quaternion.copy(originalQuaternion)\n camera.updateMatrixWorld(true)\n }\n }\n\n /** 只记录投影要用的矩阵;Five 的相机 clone() 开销大,也不保证可用 */\n private snapshotOverlayCamera() {\n const camera = this.overlayFive.camera\n if (!camera) return\n const snapshot = (this.overlayCamera ??= new THREE.Camera())\n snapshot.matrixWorld.copy(camera.matrixWorld)\n snapshot.matrixWorldInverse.copy(camera.matrixWorldInverse)\n snapshot.projectionMatrix.copy(camera.projectionMatrix)\n snapshot.projectionMatrixInverse.copy(camera.projectionMatrixInverse)\n snapshot.position.copy(camera.position)\n }\n\n private getOverlayContext() {\n if (!this.overlayContext) this.overlayContext = this.canvas.getContext('2d')\n return this.overlayContext\n }\n}\n","import * as THREE from 'three/webgpu'\nimport type { Five } from '@realsee/five-webgpu'\nimport type { Intersection } from '../typings/typings'\nimport type { PointIntersection } from '../shared-utils'\nimport { PointHelper2 } from '../shared-utils/three/PointSelector/utils/PointHelper2'\nimport { buildPlaneFrame, getWorldNormal, OVERLAY_STYLE } from './PointOverlay'\n\n/** 与 PointHelper2 里那根法线管的半径一致 */\nconst TUBE_RADIUS = 0.003\n\nfunction createTube(from: THREE.Vector3, to: THREE.Vector3, color: string) {\n const path = new THREE.CurvePath<THREE.Vector3>()\n path.add(new THREE.LineCurve3(from, to))\n const geometry = new THREE.TubeGeometry(path, 4, TUBE_RADIUS, 8)\n const material = new THREE.MeshBasicMaterial({\n color,\n depthTest: false,\n transparent: true, // 与 PointHelper2 的管一致,要参与透明排序\n })\n const mesh = new THREE.Mesh(geometry, material)\n // 放大镜是读像素放大的,这些物体不能被画进放大镜里\n ;(mesh as any).disableGetPixel = true\n return mesh\n}\n\n/**\n * 场景里的小光标(选点处那团白圈 + 法线管)的 V2 版本。\n *\n * 完全继承 PointHelper2,只做两件事:\n * 1. 把那根浅蓝色的法线管换成绿色(PRD 里绿色 = Z 轴);\n * 2. 按和放大镜同一套平面坐标系(buildPlaneFrame)补上红色 X、蓝色 Y 和红色截面框。\n *\n * 选点算法(射线、吸附、打点)全都在 PointSelector 里,这里只负责画,不参与任何选点判断。\n */\nexport class MeasurePointHelperV2 extends PointHelper2 {\n /** 只放 V2 新增的红 / 蓝轴和截面框,拿不到法线时整体隐藏 */\n private readonly overlayGroup = new THREE.Group()\n\n public constructor(five: Five) {\n super(five)\n\n // V1 光标自带的蓝色 CSS 小球也是法线端点,和绿色 Z 轴的球重叠;\n // PRD 里端点只保留绿色,这里去掉它。\n this.remove(this.cssBallMesh)\n\n this.paintAxisMesh(this.lineMesh, OVERLAY_STYLE.axisZColor)\n this.paintAxisMesh(this.ballMesh, OVERLAY_STYLE.axisZColor)\n\n // 绿管就是 Z 轴,球在管端,所以球的 z 就是轴长,红 / 蓝轴沿用同一个尺度\n const axisLength = this.ballMesh.position.z || 0.18\n // 红框半边长度按放大镜里的比例换算:框边长 / 轴长 = planeSideRadiusScale / axisLengthRadiusScale\n const frameHalfSide = (axisLength * OVERLAY_STYLE.planeSideRadiusScale) / (2 * OVERLAY_STYLE.axisLengthRadiusScale)\n\n // X(红)/ Y(蓝):贯穿落点的整条线,与放大镜里的画法一致\n const xAxis = createTube(new THREE.Vector3(-axisLength, 0, 0), new THREE.Vector3(axisLength, 0, 0), OVERLAY_STYLE.axisXColor)\n const yAxis = createTube(new THREE.Vector3(0, -axisLength, 0), new THREE.Vector3(0, axisLength, 0), OVERLAY_STYLE.axisYColor)\n xAxis.renderOrder = 3\n yAxis.renderOrder = 3\n this.overlayGroup.add(xAxis, yAxis)\n\n // 截面框:落点所在平面内的正方形,只描边(用四根细管拼出来)\n const corners = [\n new THREE.Vector3(-frameHalfSide, -frameHalfSide, 0),\n new THREE.Vector3(frameHalfSide, -frameHalfSide, 0),\n new THREE.Vector3(frameHalfSide, frameHalfSide, 0),\n new THREE.Vector3(-frameHalfSide, frameHalfSide, 0),\n ]\n corners.forEach((corner, index) => {\n const edge = createTube(corner, corners[(index + 1) % corners.length], OVERLAY_STYLE.planeBorderColor)\n edge.renderOrder = 2\n this.overlayGroup.add(edge)\n })\n\n this.add(this.overlayGroup)\n this.hide()\n }\n\n public override updateWithIntersect(intersect: Intersection) {\n const point = intersect?.point\n if (!point) return\n\n this.position.copy(point)\n\n const normal = (intersect as PointIntersection).isVirtual ? null : getWorldNormal(intersect as unknown as PointIntersection)\n // 点云 / 全景等拿不到法线时不画红蓝轴和平面框,避免沿用上一次的姿态\n this.overlayGroup.visible = normal !== null\n this.lineMesh.visible = normal !== null\n this.ballMesh.visible = normal !== null\n this.planeMesh.visible = normal !== null\n this.ringMesh.visible = normal !== null\n if (!normal) return\n\n const frame = buildPlaneFrame(normal)\n // 直接用平面坐标系当姿态:局部 +X = 红、+Y = 蓝、+Z = 绿(法线)。\n // 这样绿管沿法线朝外、红蓝两条线落在平面内、红框与选点所在面平行。\n this.quaternion.setFromRotationMatrix(new THREE.Matrix4().makeBasis(frame.xAxis, frame.yAxis, frame.normal))\n }\n\n /** 不挂载 cssBallMesh,避免蓝色小球盖住绿色 Z 轴端点 */\n public override show() {\n this.visible = true\n }\n\n public override hide() {\n this.visible = false\n }\n\n private paintAxisMesh(mesh: THREE.Mesh, color: string) {\n const material = mesh.material as THREE.MeshBasicMaterial\n material.color.set(color)\n material.needsUpdate = true\n }\n}\n","import * as THREE from 'three/webgpu'\n\n/**\n * 模型边缘吸附。\n *\n * 选点器本身已经支持吸附到 `THREE.Line3`,five 的 Model 也提供了 `getEdgesBySphere()`,\n * 这里只做两件事:\n * 1. 把光标附近的模型边界线取出来、按离落点的距离排序并限量;\n * 2. 缓存查询结果,避免每次 mousemove 都重走一遍 BVH。\n *\n * 取出来的线交给 `PointSelector.adhereLineProvider`,由选点器完成真正的吸附。\n */\nexport interface EdgeSnapOptions {\n /** 是否启用。V2 默认 true;原测量插件不接入此能力 */\n enabled?: boolean\n /** 查询球半径(米),只在这个范围内找模型边线,默认 0.3 */\n radius?: number\n /** 面片夹角阈值(度),越小越只保留硬边,默认 30 */\n threshold?: number\n /** 单次最多参与吸附的边数,默认 240 */\n maxEdges?: number\n /** 光标移动超过该距离(米)才重新查询,默认 0.12 */\n cacheStep?: number\n}\n\nexport interface ResolvedEdgeSnapOptions extends Required<EdgeSnapOptions> {}\n\nexport const DEFAULT_EDGE_SNAP_OPTIONS: ResolvedEdgeSnapOptions = {\n enabled: true,\n radius: 0.3,\n threshold: 30,\n maxEdges: 240,\n cacheStep: 0.12,\n}\n\nexport function resolveEdgeSnapOptions(options?: boolean | EdgeSnapOptions): ResolvedEdgeSnapOptions {\n // V2 默认开启,支持整体开关和细项配置。\n if (options === undefined) return { ...DEFAULT_EDGE_SNAP_OPTIONS }\n if (options === false) return { ...DEFAULT_EDGE_SNAP_OPTIONS, enabled: false }\n if (options === true) return { ...DEFAULT_EDGE_SNAP_OPTIONS, enabled: true }\n return { ...DEFAULT_EDGE_SNAP_OPTIONS, enabled: true, ...options }\n}\n\n/** 只依赖 getEdgesBySphere,避免把 dnalogel 绑死在某个 five 版本上 */\ntype EdgeSource = {\n getEdgesBySphere?: (sphere: THREE.Sphere, threshold?: number) => THREE.Line3[]\n}\n\nconst _closestPoint = new THREE.Vector3()\n\n/** 点到线段的最短距离平方(clamp 到线段内,模型边不是无限直线) */\nfunction distanceSqToSegment(point: THREE.Vector3, line: THREE.Line3) {\n line.closestPointToPoint(point, true, _closestPoint)\n return _closestPoint.distanceToSquared(point)\n}\n\n/**\n * 取落点附近的模型边界线,按离落点从近到远排序,最多 maxEdges 条。\n * five 的 getEdgesBySphere 返回的是世界坐标线段,可以直接交给 PointSelector。\n *\n * 注意:getEdgesBySphere 是按「三角形与球相交」筛选的,大三角形的边可能落在球外很远,\n * 所以这里再按「点到线段」的真实距离过滤一次,避免把远处的边也当成候选。\n */\nexport function collectNearbyEdges(\n model: EdgeSource | null | undefined,\n point: THREE.Vector3,\n options: ResolvedEdgeSnapOptions,\n): THREE.Line3[] {\n if (!options.enabled) return []\n if (!model || typeof model.getEdgesBySphere !== 'function') return []\n if (![point.x, point.y, point.z].every((value) => Number.isFinite(value))) return []\n\n const edges = model.getEdgesBySphere(new THREE.Sphere(point.clone(), options.radius), THREE.MathUtils.degToRad(options.threshold))\n if (!edges?.length) return []\n\n const radiusSq = options.radius * options.radius\n return edges\n .map((line) => ({ line, distanceSq: distanceSqToSegment(point, line) }))\n .filter((item) => item.distanceSq <= radiusSq)\n .sort((a, b) => a.distanceSq - b.distanceSq)\n .slice(0, options.maxEdges)\n .map((item) => item.line)\n}\n\n/**\n * `getEdgesBySphere` 每次都要遍历可见瓦片、合几何、重建邻接关系,开销不低;\n * 光标在 cacheStep 范围内移动时复用上一次结果,足够贴近“附近有哪些边”的事实。\n */\nexport class EdgeSnapCache {\n private center = new THREE.Vector3()\n private edges: THREE.Line3[] = []\n private valid = false\n\n public query(model: EdgeSource | null | undefined, point: THREE.Vector3, options: ResolvedEdgeSnapOptions): THREE.Line3[] {\n if (!options.enabled) {\n this.clear()\n return []\n }\n\n const stepSq = options.cacheStep * options.cacheStep\n if (this.valid && this.center.distanceToSquared(point) <= stepSq) return this.edges\n\n this.center.copy(point)\n this.edges = collectNearbyEdges(model, point, options)\n this.valid = true\n return this.edges\n }\n\n public clear() {\n this.valid = false\n this.edges = []\n }\n\n /** 最近一次查询到的边数,调试 / 示例页展示用 */\n public get size() {\n return this.edges.length\n }\n}\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { t } from "./throttle-YRIkRKaX.js";
3
3
  import { a as n } from "./defaultUrls-CfMtGzqM.js";
4
4
  import { i as r, n as i, r as a, t as o } from "./changeMode-D_I2AOjH.js";
5
5
  import { t as s } from "./equal-BiDAilPV.js";
6
- import { t as c } from "./BasePluginWithData-5iUuu45u.js";
6
+ import { t as c } from "./BasePluginWithData-CWgZY4u8.js";
7
7
  import { C as l, D as u, F as d, G as f, H as p, J as m, K as h, L as g, M as _, N as v, P as y, Q as b, R as x, U as S, V as C, W as w, X as T, Y as E, _ as D, b as O, ct as k, et as A, f as j, g as M, i as N, it as P, j as F, k as I, m as L, n as R, o as z, p as B, q as V, r as H, rt as U, st as W, t as G, tt as K, x as q, y as ee, z as J } from "./component.svelte-D5LYqt50.js";
8
8
  import { t as Y } from "./rad2Deg-DutG55Wb.js";
9
9
  import { i as te, n as ne, r as re, t as ie } from "./fontSize-DMhRSrtx.js";
@@ -6056,4 +6056,4 @@ var kn = class extends c {
6056
6056
  //#endregion
6057
6057
  export { an as a, Mt as c, An as i, Q as l, jn as n, Bt as o, Nn as r, Rt as s, Mn as t };
6058
6058
 
6059
- //# sourceMappingURL=floorplan-B82VmXnE.js.map
6059
+ //# sourceMappingURL=floorplan-DwcrbASA.js.map