@inweb/viewer-three 26.11.0 → 26.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +4 -7
  2. package/dist/plugins/components/AxesHelperComponent.js.map +1 -0
  3. package/dist/plugins/components/AxesHelperComponent.module.js.map +1 -0
  4. package/dist/plugins/components/ExtentsHelperComponent.js.map +1 -0
  5. package/dist/plugins/components/ExtentsHelperComponent.module.js.map +1 -0
  6. package/dist/plugins/components/GridHelperComponent.js.map +1 -0
  7. package/dist/plugins/components/GridHelperComponent.module.js.map +1 -0
  8. package/dist/plugins/components/LightHelperComponent.js.map +1 -0
  9. package/dist/plugins/components/LightHelperComponent.module.js.map +1 -0
  10. package/dist/plugins/components/RoomEnvironmentComponent.js.map +1 -0
  11. package/dist/plugins/components/RoomEnvironmentComponent.module.js.map +1 -0
  12. package/dist/plugins/components/StatsPanelComponent.js.map +1 -0
  13. package/dist/plugins/components/StatsPanelComponent.module.js.map +1 -0
  14. package/dist/plugins/loaders/GLTFCloudLoader.js.map +1 -0
  15. package/dist/plugins/loaders/GLTFCloudLoader.module.js.map +1 -0
  16. package/dist/{extensions → plugins}/loaders/GLTFFileLoader.js +1 -1
  17. package/dist/plugins/loaders/GLTFFileLoader.js.map +1 -0
  18. package/dist/plugins/loaders/GLTFFileLoader.min.js +24 -0
  19. package/dist/plugins/loaders/GLTFFileLoader.module.js.map +1 -0
  20. package/dist/plugins/loaders/IFCXLoader.js.map +1 -0
  21. package/dist/plugins/loaders/IFCXLoader.module.js.map +1 -0
  22. package/dist/plugins/loaders/PotreeLoader.js.map +1 -0
  23. package/dist/plugins/loaders/PotreeLoader.module.js.map +1 -0
  24. package/dist/viewer-three.js +153 -249
  25. package/dist/viewer-three.js.map +1 -1
  26. package/dist/viewer-three.min.js +3 -3
  27. package/dist/viewer-three.module.js +155 -243
  28. package/dist/viewer-three.module.js.map +1 -1
  29. package/lib/Viewer/Viewer.d.ts +1 -1
  30. package/lib/Viewer/components/SelectionComponent.d.ts +3 -1
  31. package/lib/Viewer/draggers/MeasureLineDragger.d.ts +1 -7
  32. package/lib/Viewer/models/IModelImpl.d.ts +0 -4
  33. package/lib/Viewer/models/ModelImpl.d.ts +0 -4
  34. package/package.json +11 -11
  35. package/src/Viewer/Viewer.ts +1 -1
  36. package/src/Viewer/components/SelectionComponent.ts +30 -5
  37. package/src/Viewer/draggers/MeasureLineDragger.ts +226 -84
  38. package/src/Viewer/loaders/DynamicGltfLoader/DynamicGltfLoader.js +16 -14
  39. package/src/Viewer/loaders/DynamicGltfLoader/GltfStructure.js +0 -1
  40. package/src/Viewer/models/IModelImpl.ts +0 -8
  41. package/src/Viewer/models/ModelImpl.ts +0 -18
  42. package/src/index-umd.ts +1 -1
  43. package/dist/extensions/components/AxesHelperComponent.js.map +0 -1
  44. package/dist/extensions/components/AxesHelperComponent.module.js.map +0 -1
  45. package/dist/extensions/components/ExtentsHelperComponent.js.map +0 -1
  46. package/dist/extensions/components/ExtentsHelperComponent.module.js.map +0 -1
  47. package/dist/extensions/components/GridHelperComponent.js.map +0 -1
  48. package/dist/extensions/components/GridHelperComponent.module.js.map +0 -1
  49. package/dist/extensions/components/LightHelperComponent.js.map +0 -1
  50. package/dist/extensions/components/LightHelperComponent.module.js.map +0 -1
  51. package/dist/extensions/components/RoomEnvironmentComponent.js.map +0 -1
  52. package/dist/extensions/components/RoomEnvironmentComponent.module.js.map +0 -1
  53. package/dist/extensions/components/StatsPanelComponent.js.map +0 -1
  54. package/dist/extensions/components/StatsPanelComponent.module.js.map +0 -1
  55. package/dist/extensions/loaders/GLTFCloudLoader.js.map +0 -1
  56. package/dist/extensions/loaders/GLTFCloudLoader.module.js.map +0 -1
  57. package/dist/extensions/loaders/GLTFFileLoader.js.map +0 -1
  58. package/dist/extensions/loaders/GLTFFileLoader.min.js +0 -24
  59. package/dist/extensions/loaders/GLTFFileLoader.module.js.map +0 -1
  60. package/dist/extensions/loaders/IFCXLoader.js.map +0 -1
  61. package/dist/extensions/loaders/IFCXLoader.module.js.map +0 -1
  62. package/dist/extensions/loaders/PotreeLoader.js.map +0 -1
  63. package/dist/extensions/loaders/PotreeLoader.module.js.map +0 -1
  64. package/lib/Viewer/measurement/Snapper.d.ts +0 -15
  65. package/lib/Viewer/measurement/UnitConverter.d.ts +0 -63
  66. package/lib/Viewer/measurement/UnitFormatter.d.ts +0 -4
  67. package/src/Viewer/measurement/Snapper.ts +0 -208
  68. package/src/Viewer/measurement/UnitConverter.ts +0 -47
  69. package/src/Viewer/measurement/UnitFormatter.ts +0 -95
  70. /package/dist/{extensions → plugins}/components/AxesHelperComponent.js +0 -0
  71. /package/dist/{extensions → plugins}/components/AxesHelperComponent.min.js +0 -0
  72. /package/dist/{extensions → plugins}/components/AxesHelperComponent.module.js +0 -0
  73. /package/dist/{extensions → plugins}/components/ExtentsHelperComponent.js +0 -0
  74. /package/dist/{extensions → plugins}/components/ExtentsHelperComponent.min.js +0 -0
  75. /package/dist/{extensions → plugins}/components/ExtentsHelperComponent.module.js +0 -0
  76. /package/dist/{extensions → plugins}/components/GridHelperComponent.js +0 -0
  77. /package/dist/{extensions → plugins}/components/GridHelperComponent.min.js +0 -0
  78. /package/dist/{extensions → plugins}/components/GridHelperComponent.module.js +0 -0
  79. /package/dist/{extensions → plugins}/components/LightHelperComponent.js +0 -0
  80. /package/dist/{extensions → plugins}/components/LightHelperComponent.min.js +0 -0
  81. /package/dist/{extensions → plugins}/components/LightHelperComponent.module.js +0 -0
  82. /package/dist/{extensions → plugins}/components/RoomEnvironmentComponent.js +0 -0
  83. /package/dist/{extensions → plugins}/components/RoomEnvironmentComponent.min.js +0 -0
  84. /package/dist/{extensions → plugins}/components/RoomEnvironmentComponent.module.js +0 -0
  85. /package/dist/{extensions → plugins}/components/StatsPanelComponent.js +0 -0
  86. /package/dist/{extensions → plugins}/components/StatsPanelComponent.min.js +0 -0
  87. /package/dist/{extensions → plugins}/components/StatsPanelComponent.module.js +0 -0
  88. /package/dist/{extensions → plugins}/loaders/GLTFCloudLoader.js +0 -0
  89. /package/dist/{extensions → plugins}/loaders/GLTFCloudLoader.min.js +0 -0
  90. /package/dist/{extensions → plugins}/loaders/GLTFCloudLoader.module.js +0 -0
  91. /package/dist/{extensions → plugins}/loaders/GLTFFileLoader.module.js +0 -0
  92. /package/dist/{extensions → plugins}/loaders/IFCXLoader.js +0 -0
  93. /package/dist/{extensions → plugins}/loaders/IFCXLoader.min.js +0 -0
  94. /package/dist/{extensions → plugins}/loaders/IFCXLoader.module.js +0 -0
  95. /package/dist/{extensions → plugins}/loaders/PotreeLoader.js +0 -0
  96. /package/dist/{extensions → plugins}/loaders/PotreeLoader.min.js +0 -0
  97. /package/dist/{extensions → plugins}/loaders/PotreeLoader.module.js +0 -0
  98. /package/{extensions → plugins}/components/AxesHelperComponent.ts +0 -0
  99. /package/{extensions → plugins}/components/ExtentsHelperComponent.ts +0 -0
  100. /package/{extensions → plugins}/components/GridHelperComponent.ts +0 -0
  101. /package/{extensions → plugins}/components/LightHelperComponent.ts +0 -0
  102. /package/{extensions → plugins}/components/RoomEnvironmentComponent.ts +0 -0
  103. /package/{extensions → plugins}/components/StatsPanelComponent.ts +0 -0
  104. /package/{extensions → plugins}/loaders/GLTFCloudLoader.ts +0 -0
  105. /package/{extensions → plugins}/loaders/GLTFFileLoader.ts +0 -0
  106. /package/{extensions → plugins}/loaders/IFCX/IFCXCloudLoader.ts +0 -0
  107. /package/{extensions → plugins}/loaders/IFCX/IFCXFileLoader.ts +0 -0
  108. /package/{extensions → plugins}/loaders/IFCX/IFCXLoader.ts +0 -0
  109. /package/{extensions → plugins}/loaders/IFCX/index.ts +0 -0
  110. /package/{extensions → plugins}/loaders/IFCX/render.js +0 -0
  111. /package/{extensions → plugins}/loaders/Potree/PotreeFileLoader.ts +0 -0
  112. /package/{extensions → plugins}/loaders/Potree/PotreeModelImpl.ts +0 -0
  113. /package/{extensions → plugins}/loaders/Potree/index.ts +0 -0
@@ -105,7 +105,7 @@ export declare class Viewer extends EventEmitter2<ViewerEventMap & CanvasEventMa
105
105
  * object
106
106
  *
107
107
  * @param params - Loading parameters.
108
- * @param params.format - File format. Can be `gltf`, `glb` or format from an extension. Required when
108
+ * @param params.format - File format. Can be `gltf`, `glb` or format from a plugin. Required when
109
109
  * loading a file as `ArrayBuffer` or `Data URL`.
110
110
  * @param params.mode - File opening mode. Can be one of:
111
111
  *
@@ -1,10 +1,11 @@
1
- import { Object3D, Vector2 } from "three";
1
+ import { Intersection, Object3D, Raycaster, Vector2 } from "three";
2
2
  import type { IComponent } from "@inweb/viewer-core";
3
3
  import type { Viewer } from "../Viewer";
4
4
  import type { IModelImpl } from "../models/IModelImpl";
5
5
  import type { HighlighterComponent } from "./HighlighterComponent";
6
6
  export declare class SelectionComponent implements IComponent {
7
7
  protected viewer: Viewer;
8
+ protected raycaster: Raycaster;
8
9
  protected downPosition: Vector2;
9
10
  protected highlighter: HighlighterComponent;
10
11
  constructor(viewer: Viewer);
@@ -13,6 +14,7 @@ export declare class SelectionComponent implements IComponent {
13
14
  onPointerUp: (event: PointerEvent) => void;
14
15
  onDoubleClick: (event: MouseEvent) => void;
15
16
  getMousePosition(event: MouseEvent, target: Vector2): Vector2;
17
+ getPointerIntersects(mouse: Vector2, objects: Object3D[]): Array<Intersection<Object3D>>;
16
18
  select(objects: Object3D | Object3D[], model?: IModelImpl): void;
17
19
  deselect(objects: Object3D | Object3D[], model?: IModelImpl): void;
18
20
  toggleSelection(objects: Object3D | Object3D[], model?: IModelImpl): void;
@@ -4,10 +4,6 @@ export declare class MeasureLineDragger extends OrbitDragger {
4
4
  private overlay;
5
5
  private line;
6
6
  private snapper;
7
- private objects;
8
- private scale;
9
- private units;
10
- private precision;
11
7
  constructor(viewer: Viewer);
12
8
  dispose(): void;
13
9
  onPointerDown: (event: PointerEvent) => void;
@@ -15,9 +11,7 @@ export declare class MeasureLineDragger extends OrbitDragger {
15
11
  onPointerUp: (event: PointerEvent) => void;
16
12
  onPointerCancel: (event: PointerEvent) => void;
17
13
  onPointerLeave: () => void;
18
- clearOverlay: () => void;
19
14
  renderOverlay: () => void;
20
- updateObjects: () => void;
15
+ updateSnapper: () => void;
21
16
  updateSnapperCamera: () => void;
22
- updateUnits: () => void;
23
17
  }
@@ -5,10 +5,6 @@ import { IModel } from "@inweb/viewer-core";
5
5
  */
6
6
  export interface IModelImpl extends IModel {
7
7
  scene: Object3D;
8
- getUnits(): string;
9
- getUnitScale(): number;
10
- getUnitString(): string;
11
- getPrecision(): number;
12
8
  getExtents(target: Box3): Box3;
13
9
  getObjects(): Object3D[];
14
10
  getVisibleObjects(): Object3D[];
@@ -5,10 +5,6 @@ export declare class ModelImpl implements IModelImpl {
5
5
  scene: Object3D;
6
6
  constructor(scene: Object3D);
7
7
  dispose(): void;
8
- getUnits(): string;
9
- getUnitScale(): number;
10
- getUnitString(): string;
11
- getPrecision(): number;
12
8
  getExtents(target: Box3): Box3;
13
9
  getObjects(): Object3D[];
14
10
  getVisibleObjects(): Object3D[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inweb/viewer-three",
3
- "version": "26.11.0",
3
+ "version": "26.11.1",
4
4
  "description": "JavaScript library for rendering CAD and BIM files in a browser using Three.js",
5
5
  "homepage": "https://cloud.opendesign.com/docs/index.html",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -14,7 +14,7 @@
14
14
  "three.js"
15
15
  ],
16
16
  "sideEffects": [
17
- "dist/extensions/**/*"
17
+ "dist/plugins/**/*"
18
18
  ],
19
19
  "main": "dist/viewer-three.js",
20
20
  "module": "dist/viewer-three.module.js",
@@ -23,22 +23,22 @@
23
23
  "dist",
24
24
  "lib/**/*.d.ts",
25
25
  "src",
26
- "extensions"
26
+ "plugins"
27
27
  ],
28
28
  "scripts": {
29
- "build": "npm run build:viewer && npm run build:extensions",
29
+ "build": "npm run build:viewer && npm run build:plugins",
30
30
  "build:viewer": "rollup -c rollup.config.js",
31
- "build:extensions": "rollup -c rollup.extensions.config.js",
32
- "test": "npm run test:viewer && npm run test:extensions",
31
+ "build:plugins": "rollup -c rollup.plugins.config.js",
32
+ "test": "npm run test:viewer && npm run test:plugins",
33
33
  "test:viewer": "karma start karma.conf.js",
34
- "test:extensions": "karma start karma.extensions.conf.js",
34
+ "test:plugins": "karma start karma.plugins.conf.js",
35
35
  "docs": "typedoc"
36
36
  },
37
37
  "dependencies": {
38
- "@inweb/client": "~26.11.0",
39
- "@inweb/eventemitter2": "~26.11.0",
40
- "@inweb/markup": "~26.11.0",
41
- "@inweb/viewer-core": "~26.11.0"
38
+ "@inweb/client": "~26.11.1",
39
+ "@inweb/eventemitter2": "~26.11.1",
40
+ "@inweb/markup": "~26.11.1",
41
+ "@inweb/viewer-core": "~26.11.1"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/three": "^0.180.0",
@@ -388,7 +388,7 @@ export class Viewer
388
388
  * object
389
389
  *
390
390
  * @param params - Loading parameters.
391
- * @param params.format - File format. Can be `gltf`, `glb` or format from an extension. Required when
391
+ * @param params.format - File format. Can be `gltf`, `glb` or format from a plugin. Required when
392
392
  * loading a file as `ArrayBuffer` or `Data URL`.
393
393
  * @param params.mode - File opening mode. Can be one of:
394
394
  *
@@ -21,21 +21,22 @@
21
21
  // acknowledge and accept the above terms.
22
22
  ///////////////////////////////////////////////////////////////////////////////
23
23
 
24
- import { Object3D, Vector2 } from "three";
24
+ import { Intersection, Object3D, Raycaster, Vector2 } from "three";
25
25
 
26
26
  import type { IComponent } from "@inweb/viewer-core";
27
27
  import type { Viewer } from "../Viewer";
28
28
  import type { IModelImpl } from "../models/IModelImpl";
29
29
  import type { HighlighterComponent } from "./HighlighterComponent";
30
- import { Snapper } from "../measurement/Snapper";
31
30
 
32
31
  export class SelectionComponent implements IComponent {
33
32
  protected viewer: Viewer;
33
+ protected raycaster: Raycaster;
34
34
  protected downPosition: Vector2;
35
35
  protected highlighter: HighlighterComponent;
36
36
 
37
37
  constructor(viewer: Viewer) {
38
38
  this.viewer = viewer;
39
+ this.raycaster = new Raycaster();
39
40
  this.downPosition = new Vector2();
40
41
 
41
42
  this.viewer.addEventListener("pointerdown", this.onPointerDown);
@@ -63,12 +64,10 @@ export class SelectionComponent implements IComponent {
63
64
  const upPosition = this.getMousePosition(event, new Vector2());
64
65
  if (upPosition.distanceTo(this.downPosition) !== 0) return;
65
66
 
66
- const snapper = new Snapper(this.viewer.camera, this.viewer.renderer, this.viewer.canvas);
67
-
68
67
  let intersections = [];
69
68
  this.viewer.models.forEach((model) => {
70
69
  const objects = model.getVisibleObjects();
71
- const intersects = snapper.getPointerIntersects(upPosition, objects);
70
+ const intersects = this.getPointerIntersects(upPosition, objects);
72
71
  if (intersects.length > 0) intersections.push({ ...intersects[0], model });
73
72
  });
74
73
  intersections = intersections.sort((a, b) => a.distance - b.distance);
@@ -100,6 +99,32 @@ export class SelectionComponent implements IComponent {
100
99
  return target.set(event.clientX, event.clientY);
101
100
  }
102
101
 
102
+ getPointerIntersects(mouse: Vector2, objects: Object3D[]): Array<Intersection<Object3D>> {
103
+ const rect = this.viewer.canvas.getBoundingClientRect();
104
+ const x = ((mouse.x - rect.left) / rect.width) * 2 - 1;
105
+ const y = (-(mouse.y - rect.top) / rect.height) * 2 + 1;
106
+
107
+ const coords = new Vector2(x, y);
108
+ this.raycaster.setFromCamera(coords, this.viewer.camera);
109
+
110
+ this.raycaster.params = {
111
+ Mesh: {},
112
+ Line: { threshold: 0.05 },
113
+ Line2: { threshold: 0.05 },
114
+ LOD: {},
115
+ Points: { threshold: 0.01 },
116
+ Sprite: {},
117
+ };
118
+
119
+ let intersects = this.raycaster.intersectObjects(objects, false);
120
+
121
+ (this.viewer.renderer.clippingPlanes || []).forEach((plane) => {
122
+ intersects = intersects.filter((intersect) => plane.distanceToPoint(intersect.point) >= 0);
123
+ });
124
+
125
+ return intersects;
126
+ }
127
+
103
128
  select(objects: Object3D | Object3D[], model?: IModelImpl) {
104
129
  if (!model) {
105
130
  this.viewer.models.forEach((model) => this.select(objects, model));
@@ -21,24 +21,32 @@
21
21
  // acknowledge and accept the above terms.
22
22
  ///////////////////////////////////////////////////////////////////////////////
23
23
 
24
- import { Camera, MathUtils, Matrix4, Object3D, Vector2, Vector3, Vector4 } from "three";
24
+ import {
25
+ Camera,
26
+ EdgesGeometry,
27
+ Intersection,
28
+ Line3,
29
+ MathUtils,
30
+ Matrix4,
31
+ Object3D,
32
+ Plane,
33
+ Raycaster,
34
+ Vector2,
35
+ Vector3,
36
+ Vector4,
37
+ } from "three";
25
38
 
26
39
  import type { Viewer } from "../Viewer";
27
40
  import { OrbitDragger } from "./OrbitDragger";
28
- import { convertUnits } from "../measurement/UnitConverter";
29
- import { formatDistance } from "../measurement/UnitFormatter";
30
- import { Snapper } from "../measurement/Snapper";
31
41
 
32
- const _downPoint = new Vector2();
42
+ const PRECISION = 0.01;
43
+ const DESKTOP_SNAP_DISTANCE = 10;
44
+ const MOBILE_SNAP_DISTANCE = 50;
33
45
 
34
46
  export class MeasureLineDragger extends OrbitDragger {
35
47
  private overlay: MeasureOverlay;
36
48
  private line: MeasureLine;
37
- private snapper: Snapper;
38
- private objects: Object3D[];
39
- private scale = 1.0;
40
- private units = "";
41
- private precision: any = 2;
49
+ private snapper: MeasureSnapper;
42
50
 
43
51
  constructor(viewer: Viewer) {
44
52
  super(viewer);
@@ -46,15 +54,11 @@ export class MeasureLineDragger extends OrbitDragger {
46
54
  this.overlay = new MeasureOverlay(viewer.camera, viewer.canvas);
47
55
  this.overlay.attach();
48
56
 
49
- this.line = new MeasureLine(this.overlay, this.scale, this.units, this.precision);
57
+ this.line = new MeasureLine(this.overlay);
50
58
  this.overlay.addLine(this.line);
51
59
 
52
- this.snapper = new Snapper(viewer.camera, viewer.renderer, viewer.canvas);
53
-
54
- this.objects = [];
55
- this.updateObjects();
56
-
57
- this.updateUnits();
60
+ this.snapper = new MeasureSnapper(viewer.camera, viewer.canvas);
61
+ this.updateSnapper();
58
62
 
59
63
  this.viewer.canvas.addEventListener("pointerdown", this.onPointerDown);
60
64
  this.viewer.canvas.addEventListener("pointermove", this.onPointerMove);
@@ -63,12 +67,11 @@ export class MeasureLineDragger extends OrbitDragger {
63
67
  this.viewer.canvas.addEventListener("pointerleave", this.onPointerLeave);
64
68
 
65
69
  this.viewer.addEventListener("render", this.renderOverlay);
66
- this.viewer.addEventListener("hide", this.updateObjects);
67
- this.viewer.addEventListener("isolate", this.updateObjects);
68
- this.viewer.addEventListener("show", this.updateObjects);
69
- this.viewer.addEventListener("showall", this.updateObjects);
70
+ this.viewer.addEventListener("hide", this.updateSnapper);
71
+ this.viewer.addEventListener("isolate", this.updateSnapper);
72
+ this.viewer.addEventListener("show", this.updateSnapper);
73
+ this.viewer.addEventListener("showall", this.updateSnapper);
70
74
  this.viewer.addEventListener("changecameramode", this.updateSnapperCamera);
71
- this.viewer.addEventListener("optionschange", this.updateUnits);
72
75
  }
73
76
 
74
77
  override dispose() {
@@ -79,14 +82,13 @@ export class MeasureLineDragger extends OrbitDragger {
79
82
  this.viewer.canvas.removeEventListener("pointerleave", this.onPointerLeave);
80
83
 
81
84
  this.viewer.removeEventListener("render", this.renderOverlay);
82
- this.viewer.removeEventListener("hide", this.updateObjects);
83
- this.viewer.removeEventListener("isolate", this.updateObjects);
84
- this.viewer.removeEventListener("show", this.updateObjects);
85
- this.viewer.removeEventListener("showall", this.updateObjects);
85
+ this.viewer.removeEventListener("hide", this.updateSnapper);
86
+ this.viewer.removeEventListener("isolate", this.updateSnapper);
87
+ this.viewer.removeEventListener("show", this.updateSnapper);
88
+ this.viewer.removeEventListener("showall", this.updateSnapper);
86
89
  this.viewer.removeEventListener("changecameramode", this.updateSnapperCamera);
87
- this.viewer.removeEventListener("optionschange", this.updateUnits);
88
90
 
89
- this.objects.length = 0;
91
+ this.snapper.dispose();
90
92
 
91
93
  this.overlay.detach();
92
94
  this.overlay.dispose();
@@ -97,8 +99,7 @@ export class MeasureLineDragger extends OrbitDragger {
97
99
  onPointerDown = (event: PointerEvent) => {
98
100
  if (event.button !== 0) return;
99
101
 
100
- const mouse = this.snapper.getMousePosition(event, _downPoint);
101
- this.line.startPoint = this.snapper.getSnapPoint(mouse, this.objects);
102
+ this.line.startPoint = this.snapper.getSnapPoint(event);
102
103
  this.line.render();
103
104
 
104
105
  this.viewer.canvas.setPointerCapture(event.pointerId);
@@ -108,8 +109,7 @@ export class MeasureLineDragger extends OrbitDragger {
108
109
  onPointerMove = (event: PointerEvent) => {
109
110
  if (this.orbit.enabled && this.orbit.state !== -1) return;
110
111
 
111
- const mouse = this.snapper.getMousePosition(event, _downPoint);
112
- const snapPoint = this.snapper.getSnapPoint(mouse, this.objects);
112
+ const snapPoint = this.snapper.getSnapPoint(event);
113
113
  if (snapPoint && this.line.endPoint && snapPoint.equals(this.line.endPoint)) return;
114
114
 
115
115
  this.line.endPoint = snapPoint;
@@ -119,8 +119,8 @@ export class MeasureLineDragger extends OrbitDragger {
119
119
  };
120
120
 
121
121
  onPointerUp = (event: PointerEvent) => {
122
- if (this.line.startPoint && this.line.endPoint && this.line.getDistance() > 0) {
123
- this.line = new MeasureLine(this.overlay, this.scale, this.units, this.precision);
122
+ if (this.line.startPoint && this.line.endPoint && this.line.getDistance() >= PRECISION) {
123
+ this.line = new MeasureLine(this.overlay);
124
124
  this.overlay.addLine(this.line);
125
125
  } else {
126
126
  this.line.startPoint = undefined;
@@ -141,50 +141,204 @@ export class MeasureLineDragger extends OrbitDragger {
141
141
  this.line.render();
142
142
  };
143
143
 
144
- clearOverlay = () => {
145
- this.overlay.clear();
146
-
147
- this.line = new MeasureLine(this.overlay, this.scale, this.units, this.precision);
148
- this.overlay.addLine(this.line);
149
- };
150
-
151
144
  renderOverlay = () => {
152
145
  this.overlay.render();
153
146
  };
154
147
 
155
- updateObjects = () => {
156
- this.objects.length = 0;
157
- this.viewer.models.forEach((model) => {
158
- model.getVisibleObjects().forEach((object) => this.objects.push(object));
159
- });
148
+ updateSnapper = () => {
149
+ this.snapper.setFromViewer(this.viewer);
160
150
  };
161
151
 
162
152
  updateSnapperCamera = () => {
163
153
  this.snapper.camera = this.viewer.camera;
164
154
  this.overlay.camera = this.viewer.camera;
165
155
  };
156
+ }
166
157
 
167
- updateUnits = () => {
168
- const model = this.viewer.models[0];
169
- const units = this.viewer.options.rulerUnit ?? "Default";
170
- const precision = this.viewer.options.rulerPrecision ?? "Default";
158
+ const _vertex = new Vector3();
159
+ const _start = new Vector3();
160
+ const _end = new Vector3();
161
+ const _line = new Line3();
162
+ const _center = new Vector3();
163
+ const _projection = new Vector3();
171
164
 
172
- if (units === "Default") {
173
- this.scale = model.getUnitScale();
174
- this.units = model.getUnitString();
175
- } else {
176
- this.scale = convertUnits(model.getUnits(), units, 1);
177
- this.units = units;
165
+ class MeasureSnapper {
166
+ public camera: Camera;
167
+ private canvas: HTMLCanvasElement;
168
+ private objects: Object3D[];
169
+ private clippingPlanes: Plane[];
170
+ private raycaster: Raycaster;
171
+ private detectRadiusInPixels: number;
172
+ private edgesCache: WeakMap<any, EdgesGeometry>;
173
+
174
+ constructor(camera: Camera, canvas: HTMLCanvasElement) {
175
+ this.camera = camera;
176
+ this.canvas = canvas;
177
+ this.objects = [];
178
+ this.clippingPlanes = [];
179
+ this.raycaster = new Raycaster();
180
+ this.detectRadiusInPixels = this.isMobile() ? MOBILE_SNAP_DISTANCE : DESKTOP_SNAP_DISTANCE;
181
+ this.edgesCache = new WeakMap();
182
+ }
183
+
184
+ dispose() {
185
+ this.objects = [];
186
+ }
187
+
188
+ isMobile(): boolean {
189
+ if (typeof navigator === "undefined") return false;
190
+
191
+ // ===================== AI-CODE-START ======================
192
+ // Source: Claude Sonnet 4
193
+ // Date: 2025-09-09
194
+ // Reviewer: roman.mochalov@opendesign.com
195
+ // Issue: CLOUD-5799
196
+
197
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
198
+
199
+ // ===================== AI-CODE-END ======================
200
+ }
201
+
202
+ getMousePosition(event: MouseEvent, target: Vector2): Vector2 {
203
+ return target.set(event.clientX, event.clientY);
204
+ }
205
+
206
+ getPointerIntersects(mouse: Vector2): Array<Intersection<Object3D>> {
207
+ const rect = this.canvas.getBoundingClientRect();
208
+ const x = ((mouse.x - rect.left) / rect.width) * 2 - 1;
209
+ const y = (-(mouse.y - rect.top) / rect.height) * 2 + 1;
210
+
211
+ const coords = new Vector2(x, y);
212
+ this.raycaster.setFromCamera(coords, this.camera);
213
+
214
+ this.raycaster.params = {
215
+ Mesh: {},
216
+ Line: { threshold: 0.05 },
217
+ Line2: { threshold: 0.05 },
218
+ LOD: {},
219
+ Points: { threshold: 0.01 },
220
+ Sprite: {},
221
+ };
222
+
223
+ let intersects = this.raycaster.intersectObjects(this.objects, false);
224
+
225
+ this.clippingPlanes.forEach((plane) => {
226
+ intersects = intersects.filter((intersect) => plane.distanceToPoint(intersect.point) >= 0);
227
+ });
228
+
229
+ return intersects;
230
+ }
231
+
232
+ getDetectRadius(point: Vector3): number {
233
+ const camera: any = this.camera;
234
+
235
+ // ===================== AI-CODE-START ======================
236
+ // Source: Gemini 2.5 Pro
237
+ // Date: 2025-08-27
238
+ // Reviewer: roman.mochalov@opendesign.com
239
+ // Issue: CLOUD-5799
240
+ // Notes: Originally AI-generated, modified manually
241
+
242
+ if (camera.isOrthographicCamera) {
243
+ const fieldHeight = (camera.top - camera.bottom) / camera.zoom;
244
+
245
+ const canvasHeight = this.canvas.height;
246
+ const worldUnitsPerPixel = fieldHeight / canvasHeight;
247
+
248
+ return this.detectRadiusInPixels * worldUnitsPerPixel;
178
249
  }
179
250
 
180
- if (precision === "Default") {
181
- this.precision = model.getPrecision();
182
- } else {
183
- this.precision = precision;
251
+ if (camera.isPerspectiveCamera) {
252
+ const distance = camera.position.distanceTo(point);
253
+ const fieldHeight = 2 * Math.tan(MathUtils.degToRad(camera.fov * 0.5)) * distance;
254
+
255
+ const canvasHeight = this.canvas.height;
256
+ const worldUnitsPerPixel = fieldHeight / canvasHeight;
257
+
258
+ return this.detectRadiusInPixels * worldUnitsPerPixel;
184
259
  }
185
260
 
186
- this.overlay.updateLineUnits(this.scale, this.units, this.precision);
187
- };
261
+ // ===================== AI-CODE-END ======================
262
+
263
+ return 0.1;
264
+ }
265
+
266
+ getSnapPoint(event: PointerEvent): Vector3 {
267
+ const mouse = this.getMousePosition(event, new Vector2());
268
+
269
+ const intersections = this.getPointerIntersects(mouse);
270
+ if (intersections.length === 0) return undefined;
271
+
272
+ // ===================== AI-CODE-START ======================
273
+ // Source: Gemini 2.5 Pro
274
+ // Date: 2025-08-20
275
+ // Reviewer: roman.mochalov@opendesign.com
276
+ // Issue: CLOUD-5799
277
+ // Notes: Originally AI-generated, modified manually
278
+
279
+ const object: any = intersections[0].object;
280
+ const intersectionPoint = intersections[0].point;
281
+ const localPoint = object.worldToLocal(intersectionPoint.clone());
282
+
283
+ let snapPoint: Vector3;
284
+ let snapDistance = this.getDetectRadius(intersectionPoint);
285
+
286
+ const geometry = object.geometry;
287
+
288
+ const positions = geometry.attributes.position.array;
289
+ for (let i = 0; i < positions.length; i += 3) {
290
+ _vertex.set(positions[i], positions[i + 1], positions[i + 2]);
291
+ const distance = _vertex.distanceTo(localPoint);
292
+ if (distance < snapDistance) {
293
+ snapDistance = distance;
294
+ snapPoint = _vertex.clone();
295
+ }
296
+ }
297
+ if (snapPoint) return object.localToWorld(snapPoint);
298
+
299
+ let edges = this.edgesCache.get(geometry);
300
+ if (!edges) {
301
+ edges = new EdgesGeometry(geometry);
302
+ this.edgesCache.set(geometry, edges);
303
+ }
304
+
305
+ const edgePositions = edges.attributes.position.array;
306
+ for (let i = 0; i < edgePositions.length; i += 6) {
307
+ _start.set(edgePositions[i], edgePositions[i + 1], edgePositions[i + 2]);
308
+ _end.set(edgePositions[i + 3], edgePositions[i + 4], edgePositions[i + 5]);
309
+ _line.set(_start, _end);
310
+
311
+ _line.getCenter(_center);
312
+ const centerDistance = _center.distanceTo(localPoint);
313
+ if (centerDistance < snapDistance) {
314
+ snapDistance = centerDistance;
315
+ snapPoint = _center.clone();
316
+ continue;
317
+ }
318
+
319
+ _line.closestPointToPoint(localPoint, true, _projection);
320
+ const lineDistance = _projection.distanceTo(localPoint);
321
+ if (lineDistance < snapDistance) {
322
+ snapDistance = lineDistance;
323
+ snapPoint = _projection.clone();
324
+ }
325
+ }
326
+ if (snapPoint) return object.localToWorld(snapPoint);
327
+
328
+ // ===================== AI-CODE-END ======================
329
+
330
+ return intersectionPoint.clone();
331
+ }
332
+
333
+ setFromViewer(viewer: Viewer) {
334
+ this.objects.length = 0;
335
+ viewer.models.forEach((model) => {
336
+ model.getVisibleObjects().forEach((object) => this.objects.push(object));
337
+ });
338
+
339
+ this.camera = viewer.camera;
340
+ this.clippingPlanes = viewer.renderer.clippingPlanes || [];
341
+ }
188
342
  }
189
343
 
190
344
  class MeasureOverlay {
@@ -202,10 +356,6 @@ class MeasureOverlay {
202
356
  this.resizeObserver = new ResizeObserver(this.resizeContainer);
203
357
  }
204
358
 
205
- dispose() {
206
- this.clear();
207
- }
208
-
209
359
  attach() {
210
360
  this.container = document.createElement("div");
211
361
  this.container.id = "measure-container";
@@ -220,6 +370,10 @@ class MeasureOverlay {
220
370
  this.resizeObserver.observe(this.canvas);
221
371
  }
222
372
 
373
+ dispose() {
374
+ this.clear();
375
+ }
376
+
223
377
  detach() {
224
378
  this.resizeObserver.disconnect();
225
379
 
@@ -229,7 +383,7 @@ class MeasureOverlay {
229
383
 
230
384
  clear() {
231
385
  this.lines.forEach((line) => line.dispose());
232
- this.lines.length = 0;
386
+ this.lines = [];
233
387
  }
234
388
 
235
389
  render() {
@@ -249,14 +403,6 @@ class MeasureOverlay {
249
403
  this.lines = this.lines.filter((x) => x !== line);
250
404
  }
251
405
 
252
- updateLineUnits(scale: number, units: string, precision: any) {
253
- this.lines.forEach((line) => {
254
- line.scale = scale;
255
- line.units = units;
256
- line.precision = precision;
257
- });
258
- }
259
-
260
406
  resizeContainer = () => {
261
407
  const { offsetLeft, offsetTop, offsetWidth, offsetHeight } = this.canvas;
262
408
 
@@ -284,9 +430,8 @@ class MeasureLine {
284
430
  public endPoint: Vector3;
285
431
 
286
432
  public id = MathUtils.generateUUID();
287
- public scale: number;
288
- public units: string;
289
- public precision: any;
433
+ public unit = "";
434
+ public scale = 1.0;
290
435
  public size = 10.0;
291
436
  public lineWidth = 2;
292
437
 
@@ -298,11 +443,8 @@ class MeasureLine {
298
443
  font: "1rem system-ui",
299
444
  };
300
445
 
301
- constructor(overlay: MeasureOverlay, scale: number, units: string, precision: any) {
446
+ constructor(overlay: MeasureOverlay) {
302
447
  this.overlay = overlay;
303
- this.scale = scale;
304
- this.units = units;
305
- this.precision = precision;
306
448
 
307
449
  this.elementStartPoint = overlay.container.appendChild(document.createElement("div"));
308
450
  this.elementEndPoint = overlay.container.appendChild(document.createElement("div"));
@@ -369,10 +511,10 @@ class MeasureLine {
369
511
 
370
512
  const distance = this.getDistance();
371
513
 
372
- this.elementLabel.style.display = visible && distance > 0 ? "block" : "none";
514
+ this.elementLabel.style.display = visible && distance >= PRECISION ? "block" : "none";
373
515
  this.elementLabel.style.left = `${point.x}px`;
374
516
  this.elementLabel.style.top = `${point.y}px`;
375
- this.elementLabel.innerHTML = formatDistance(distance, this.units, this.precision);
517
+ this.elementLabel.innerHTML = `${distance.toFixed(2)} ${this.unit}`;
376
518
  } else {
377
519
  this.elementLabel.style.display = "none";
378
520
  }