fl-web-component 2.0.16 → 2.0.18

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.
@@ -2,10 +2,11 @@ import * as THREE from 'three';
2
2
  import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer';
3
3
  import { Message } from 'element-ui';
4
4
  var _this = null;
5
- var MeasureArea = function (renderer, scene, camera, width, height) {
5
+ var MeasureArea = function (renderer, scene, camera, width, height, options = {}) {
6
6
  this.renderer = renderer;
7
7
  this.scene = scene;
8
8
  this.camera = camera;
9
+ this.pickRoots = options.pickRoots || null;
9
10
  this.pointArray = []; // 保存当前操作所添加的点
10
11
  this.raycaster = new THREE.Raycaster();
11
12
  this.points = []; // 保存页面中所添加的点
@@ -16,13 +17,14 @@ var MeasureArea = function (renderer, scene, camera, width, height) {
16
17
  this.tempLabel = undefined;
17
18
  this.tipsLabel = undefined;
18
19
  this.isCompleted = false;
20
+ this.hasTempPoint = false;
19
21
  this.timer = null;
20
22
  this.polygonMesh = undefined;
21
23
  this.polygons = [];
22
24
  this.width = width;
23
25
  this.height = height;
24
26
  this.firstTime = 0;
25
- this.measureName = 'measureObj'
27
+ this.measureName = 'measureObj';
26
28
  // 创建一个辅助平面来捕获鼠标事件
27
29
  this.plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
28
30
  };
@@ -43,7 +45,99 @@ MeasureArea.prototype = {
43
45
  this.width = width;
44
46
  this.height = height;
45
47
  },
46
- getPosition(e) {
48
+ getPickRoots() {
49
+ const roots = typeof _this.pickRoots === 'function' ? _this.pickRoots() : _this.pickRoots;
50
+ if (Array.isArray(roots) && roots.length > 0) {
51
+ return roots.filter(Boolean);
52
+ }
53
+ if (roots) {
54
+ return [roots];
55
+ }
56
+ return _this.scene && _this.scene.children ? _this.scene.children : [];
57
+ },
58
+ isExcludedIntersection(object) {
59
+ let current = object;
60
+ while (current) {
61
+ const userData = current.userData || {};
62
+ if (current.visible === false) return true;
63
+ if (current.name === _this.measureName || userData.isMeasureObject === true) return true;
64
+ if (userData.transformControlHelper === true || userData.outlineProxy === true) return true;
65
+ if (current.isCamera || current.isLight) return true;
66
+ if (
67
+ typeof current.type === 'string' &&
68
+ (/Helper$/i.test(current.type) ||
69
+ current.type === 'CSS2DObject' ||
70
+ current.type === 'TransformControlsRoot')
71
+ ) {
72
+ return true;
73
+ }
74
+ current = current.parent;
75
+ }
76
+ return false;
77
+ },
78
+ markMeasureObject(object) {
79
+ object.name = this.measureName;
80
+ if (!object.userData) {
81
+ object.userData = {};
82
+ }
83
+ object.userData.isMeasureObject = true;
84
+ return object;
85
+ },
86
+ disposeObject(object) {
87
+ if (!object) return;
88
+ if (object.geometry) {
89
+ object.geometry.dispose();
90
+ }
91
+ if (object.material) {
92
+ object.material.dispose();
93
+ }
94
+ this.scene.remove(object);
95
+ },
96
+ removeArrayItem(array, item) {
97
+ if (!item) return;
98
+ const index = array.indexOf(item);
99
+ if (index !== -1) {
100
+ array.splice(index, 1);
101
+ }
102
+ },
103
+ clearTempMeasure() {
104
+ if (this.hasTempPoint && this.pointArray.length > 0) {
105
+ this.pointArray.pop();
106
+ }
107
+ this.removeArrayItem(this.points, this.tempPoints);
108
+ this.removeArrayItem(this.polyline, this.tempLine);
109
+ this.disposeObject(this.tempPoints);
110
+ this.disposeObject(this.tempLine);
111
+ this.tempPoints = undefined;
112
+ this.tempLine = undefined;
113
+ this.hasTempPoint = false;
114
+ this.updateAreaMeasure();
115
+ },
116
+ updateAreaMeasure() {
117
+ if (this.pointArray.length <= 2) {
118
+ this.removeArrayItem(this.polygons, this.polygonMesh);
119
+ this.removeArrayItem(this.labels, this.tempLabel);
120
+ this.disposeObject(this.polygonMesh);
121
+ this.disposeObject(this.tempLabel);
122
+ this.polygonMesh = undefined;
123
+ this.tempLabel = undefined;
124
+ return;
125
+ }
126
+ const area = this.calculateArea(this.pointArray);
127
+ this.createPolygon(this.pointArray);
128
+ if (!this.polygonMesh) return;
129
+ this.polygonMesh.geometry.computeBoundingSphere();
130
+ const center = this.polygonMesh.geometry.boundingSphere.center;
131
+ if (this.tempLabel) {
132
+ this.tempLabel.element.textContent = this.numberToString(area);
133
+ this.tempLabel.position.set(center.x, center.y, center.z);
134
+ } else {
135
+ this.tempLabel = this.createLabel('measure-label', this.numberToString(area), center);
136
+ this.labels.push(this.tempLabel);
137
+ this.scene.add(this.tempLabel);
138
+ }
139
+ },
140
+ getPosition(e, options = {}) {
47
141
  const mouse = new THREE.Vector2();
48
142
  const elRect = this.renderer.domElement.getBoundingClientRect();
49
143
  const canvasX = e.clientX - elRect.left;
@@ -53,11 +147,17 @@ MeasureArea.prototype = {
53
147
  mouse.y = -(canvasY / elRect.height) * 2.0 + 1.0;
54
148
 
55
149
  _this.raycaster.setFromCamera(mouse, _this.camera);
56
- let intersects = _this.raycaster.intersectObjects(_this.scene.children, true);
150
+ let intersects = _this.raycaster
151
+ .intersectObjects(_this.getPickRoots(), true)
152
+ .filter(item => item && item.object && !_this.isExcludedIntersection(item.object));
57
153
  if (intersects.length > 0) {
58
154
  return { point: intersects[0].point, isModel: true };
59
155
  }
60
156
 
157
+ if (options.allowPlaneFallback === false) {
158
+ return null;
159
+ }
160
+
61
161
  // 如果没有交点,构建一个基于最后一个确认点且面向相机的平面
62
162
  if (_this.pointArray && _this.pointArray.length > 0) {
63
163
  const lastPoint =
@@ -92,7 +192,7 @@ MeasureArea.prototype = {
92
192
  });
93
193
  const lineGeometry = new THREE.BufferGeometry().setFromPoints([p1, p2]);
94
194
  const line = new THREE.Line(lineGeometry, lineMaterial);
95
- line.name = this.measureName;
195
+ this.markMeasureObject(line);
96
196
  line.renderOrder = 999;
97
197
  line.frustumCulled = false;
98
198
  return line;
@@ -102,7 +202,7 @@ MeasureArea.prototype = {
102
202
  div.className = name;
103
203
  div.textContent = text;
104
204
  const divLabel = new CSS2DObject(div);
105
- divLabel.name = this.measureName
205
+ this.markMeasureObject(divLabel);
106
206
  divLabel.position.set(position.x, position.y, position.z);
107
207
  return divLabel;
108
208
  },
@@ -111,9 +211,12 @@ MeasureArea.prototype = {
111
211
  const positionResult = _this.getPosition(e);
112
212
  if (positionResult) {
113
213
  const point = positionResult.point;
114
- _this.pointArray.length === 1
115
- ? _this.pointArray.push(point)
116
- : _this.pointArray.splice(_this.pointArray.length - 1, 1, point);
214
+ if (_this.hasTempPoint) {
215
+ _this.pointArray.splice(_this.pointArray.length - 1, 1, point);
216
+ } else {
217
+ _this.pointArray.push(point);
218
+ _this.hasTempPoint = true;
219
+ }
117
220
  const length = _this.pointArray.length;
118
221
  if (_this.tempPoints) {
119
222
  _this.tempPoints.position.set(point.x, point.y, point.z);
@@ -132,29 +235,7 @@ MeasureArea.prototype = {
132
235
  _this.polyline.push(_this.tempLine);
133
236
  _this.scene.add(_this.tempLine);
134
237
  }
135
- if (_this.pointArray.length > 2) {
136
- const area = _this.calculateArea(_this.pointArray);
137
- _this.createPolygon(_this.pointArray);
138
- if (_this.tempLabel) {
139
- _this.polygonMesh.geometry.computeBoundingSphere();
140
- _this.tempLabel.element.textContent = _this.numberToString(area); // + '㎡'
141
- _this.tempLabel.position.set(
142
- _this.polygonMesh.geometry.boundingSphere.center.x,
143
- _this.polygonMesh.geometry.boundingSphere.center.y,
144
- _this.polygonMesh.geometry.boundingSphere.center.z
145
- );
146
- } else {
147
- _this.polygonMesh.geometry.computeBoundingSphere();
148
- console.log(_this.polygonMesh.geometry);
149
- _this.tempLabel = _this.createLabel(
150
- 'measure-label',
151
- area,
152
- _this.polygonMesh.geometry.boundingSphere.center
153
- );
154
- _this.labels.push(_this.tempLabel);
155
- _this.scene.add(_this.tempLabel);
156
- }
157
- }
238
+ _this.updateAreaMeasure();
158
239
  if (_this.tipsLabel) {
159
240
  _this.tipsLabel.position.set(point.x + 0.1, point.y, point.z + 0.05);
160
241
  }
@@ -165,7 +246,7 @@ MeasureArea.prototype = {
165
246
  div.className = 'tips-label';
166
247
  div.textContent = label;
167
248
  const tipsLabel = new CSS2DObject(div);
168
- tipsLabel.name = this.measureName
249
+ this.markMeasureObject(tipsLabel);
169
250
  tipsLabel.position.set(position.x + 0.1, position.y, position.z + 0.05);
170
251
  return tipsLabel;
171
252
  },
@@ -181,13 +262,20 @@ MeasureArea.prototype = {
181
262
  clearTimeout(_this.timer);
182
263
  _this.timer = setTimeout(() => {
183
264
  _this.isCompleted = false;
184
- const positionResult = _this.getPosition(e);
265
+ const positionResult = _this.getPosition(e, { allowPlaneFallback: false });
266
+ if (!positionResult || !positionResult.isModel) {
267
+ _this.clearTempMeasure();
268
+ Message.warning('请点击模型进行测量');
269
+ return;
270
+ }
185
271
  if (positionResult) {
186
- const { point, isModel } = positionResult;
187
- if (!isModel) {
188
- Message.warning('请点击模型进行测量');
189
- return;
272
+ const { point } = positionResult;
273
+ if (_this.hasTempPoint && _this.pointArray.length > 0) {
274
+ _this.pointArray.splice(_this.pointArray.length - 1, 1, point);
275
+ } else {
276
+ _this.pointArray.push(point);
190
277
  }
278
+ _this.hasTempPoint = false;
191
279
  if (_this.tipsLabel) {
192
280
  _this.tipsLabel.position.set(point.x + 0.01, point.y, point.z + 0.05);
193
281
  } else {
@@ -202,8 +290,20 @@ MeasureArea.prototype = {
202
290
  _this.points.push(geom);
203
291
  _this.scene.add(geom);
204
292
  }
293
+ const length = _this.pointArray.length;
294
+ if (length > 1) {
295
+ const p1 = _this.pointArray[length - 2];
296
+ const p2 = _this.pointArray[length - 1];
297
+ if (_this.tempLine) {
298
+ _this.tempLine.geometry.setFromPoints([p1, p2]);
299
+ } else {
300
+ _this.tempLine = _this.createLine(p1, p2);
301
+ _this.polyline.push(_this.tempLine);
302
+ _this.scene.add(_this.tempLine);
303
+ }
304
+ }
205
305
  _this.tempLine = undefined;
206
- _this.pointArray.push(point);
306
+ _this.updateAreaMeasure();
207
307
  }
208
308
  }, 0);
209
309
  }
@@ -217,16 +317,13 @@ MeasureArea.prototype = {
217
317
  clearTimeout(_this.timer);
218
318
  const positionResult = _this.getPosition(e);
219
319
  if (positionResult) {
220
- const point = positionResult.point;
320
+ _this.clearTempMeasure();
221
321
  _this.isCompleted = true;
222
- if (_this.tempPoints) {
223
- _this.tempPoints.position.set(point.x, point.y, point.z);
224
- _this.tempPoints = undefined;
225
- }
226
322
  _this.tempLine = undefined;
227
323
  _this.tempLabel = undefined;
228
324
  _this.polygonMesh = undefined;
229
325
  _this.pointArray.splice(0);
326
+ _this.hasTempPoint = false;
230
327
  _this.renderer.domElement.removeEventListener('mousemove', _this.mousemove);
231
328
  }
232
329
  },
@@ -247,6 +344,7 @@ MeasureArea.prototype = {
247
344
  this.tempPoints = undefined;
248
345
  this.tempLabel = undefined;
249
346
  this.tempLine = undefined;
347
+ this.hasTempPoint = false;
250
348
  this.scene.remove(this.tipsLabel);
251
349
  this.tipsLabel = undefined;
252
350
  }
@@ -265,6 +363,7 @@ MeasureArea.prototype = {
265
363
  this.tempPoints = undefined;
266
364
  this.tempLabel = undefined;
267
365
  this.tempLine = undefined;
366
+ this.hasTempPoint = false;
268
367
  this.scene.remove(this.tipsLabel);
269
368
  this.tipsLabel = undefined;
270
369
  },
@@ -326,8 +425,7 @@ MeasureArea.prototype = {
326
425
  });
327
426
  const mesh = new THREE.Mesh(geom, material);
328
427
  mesh.frustumCulled = false;
329
- // mesh.name = 'polygonMesh';
330
- mesh.name = _this.measureName;
428
+ _this.markMeasureObject(mesh);
331
429
  _this.polygonMesh = mesh;
332
430
  _this.scene.add(mesh);
333
431
  _this.polygons.push(mesh);
@@ -0,0 +1,346 @@
1
+ import * as THREE from 'three';
2
+ import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer';
3
+ import { Message } from 'element-ui';
4
+
5
+ var _this = null;
6
+ var MeasureClearDistance = function (renderer, scene, camera, width, height) {
7
+ this.renderer = renderer;
8
+ this.scene = scene;
9
+ this.camera = camera;
10
+ this.pointArray = []; // 保存当前操作所添加的点
11
+ this.raycaster = new THREE.Raycaster();
12
+ this.points = []; // 保存页面中所添加的点
13
+ this.polyline = []; //保存页面中所添加的直线
14
+ this.labels = []; // 保存页面中所添加的文本
15
+ this.timer = null;
16
+ this.width = width;
17
+ this.height = height;
18
+ this.firstTime = 0;
19
+ this.measureName = 'measureObj'
20
+ this.selectedObjects = []
21
+ };
22
+
23
+ MeasureClearDistance.prototype = {
24
+ start() {
25
+ _this = this;
26
+ this.renderer.domElement.style.cursor = 'crosshair';
27
+ this.renderer.domElement.addEventListener('mouseup', this.click, false);
28
+ this.renderer.domElement.addEventListener('mousedown', this.mousedown, false);
29
+ },
30
+ updateParams(width, height) {
31
+ this.camera.aspect = width / height;
32
+ this.camera.updateProjectionMatrix();
33
+ this.renderer.setSize(width, height, true);
34
+ this.width = width;
35
+ this.height = height;
36
+ },
37
+ getPosition(e) {
38
+ const mouse = new THREE.Vector2();
39
+ const elRect = this.renderer.domElement.getBoundingClientRect();
40
+ const canvasX = e.clientX - elRect.left;
41
+ const canvasY = e.clientY - elRect.top;
42
+
43
+ mouse.x = (canvasX / elRect.width) * 2.0 - 1.0;
44
+ mouse.y = -(canvasY / elRect.height) * 2.0 + 1.0;
45
+
46
+ _this.raycaster.setFromCamera(mouse, this.camera);
47
+ let intersects = _this.raycaster.intersectObjects(_this.scene.children, true);
48
+ if (intersects.length > 0) {
49
+ return intersects[0]
50
+ }
51
+ return null
52
+ },
53
+ createLine(p1, p2, config = { color: 0xff0000 }) {
54
+ const lineMaterial = new THREE.LineBasicMaterial({
55
+ color: config.color,
56
+ linewidth: 15,
57
+ depthTest: false,
58
+ depthWrite: false,
59
+ transparent: true,
60
+ });
61
+ const lineGeometry = new THREE.BufferGeometry().setFromPoints([p1, p2]);
62
+ const line = new THREE.Line(lineGeometry, lineMaterial);
63
+ line.name = this.measureName;
64
+ line.renderOrder = 999;
65
+ line.frustumCulled = false;
66
+ return line;
67
+ },
68
+
69
+ createLabel(name, text, position) {
70
+ const div = document.createElement('div');
71
+ div.className = name;
72
+ div.textContent = text;
73
+ const divLabel = new CSS2DObject(div);
74
+ divLabel.name = this.measureName
75
+ divLabel.position.set(position.x, position.y, position.z);
76
+ return divLabel;
77
+ },
78
+ createTipsLabel(label, position) {
79
+ const div = document.createElement('div');
80
+ div.className = 'tips-label';
81
+ div.textContent = label;
82
+ const tipsLabel = new CSS2DObject(div);
83
+ tipsLabel.name = this.measureName
84
+ tipsLabel.position.set(position.x + 0.1, position.y, position.z + 0.05);
85
+ return tipsLabel;
86
+ },
87
+ mousedown() {
88
+ this.firstTime = new Date().getTime();
89
+ },
90
+ click(e) {
91
+ let lastTime = new Date().getTime();
92
+ if (lastTime - this.firstTime < 300) {
93
+ const measureObj = _this.getPosition(e);
94
+
95
+ if (measureObj) {
96
+ _this.selectedObjects.push(measureObj)
97
+
98
+ if (_this.selectedObjects.length % 2 === 0) {
99
+ _this.calculateClearDistance()
100
+ }
101
+ } else {
102
+ Message.warning('请点击模型进行测量');
103
+ }
104
+ }
105
+ },
106
+ calculateClearDistance() {
107
+ const l = this.selectedObjects.length;
108
+ // 每次都取最后两个
109
+ const obj1 = this.selectedObjects[l - 2];
110
+ const obj2 = this.selectedObjects[l - 1];
111
+ const isParallel = this.checkParallelism(obj1.object, obj1.instanceId, obj2.object, obj2.instanceId);
112
+ console.log(isParallel)
113
+ const pts1 = this.getInstanceSurfacePoints(obj1.object, obj1.instanceId, 600);
114
+ const pts2 = this.getInstanceSurfacePoints(obj2.object, obj2.instanceId, 600);
115
+ if (pts1.length === 0 || pts2.length === 0) {
116
+ return
117
+ }
118
+ const closestPair = this.findClosestPair(pts1, pts2);
119
+ const position = new THREE.Vector3((closestPair.point1.x + closestPair.point2.x) / 2, (closestPair.point1.y + closestPair.point2.y) / 2, (closestPair.point1.z + closestPair.point2.z) / 2);
120
+ // 将测量结果显示在页面中
121
+ const circleTag1 = this.createLabel('circle-tag', '', closestPair.point1);
122
+ const circleTag2 = this.createLabel('circle-tag', '', closestPair.point2);
123
+ const line = this.createLine(closestPair.point1, closestPair.point2)
124
+ const label = this.createLabel('measure-label', `${this.numberToString(closestPair.distance)}`, position)
125
+ this.points.push(circleTag1);
126
+ this.points.push(circleTag1);
127
+ this.polyline.push(line);
128
+ this.labels.push(label);
129
+ this.scene.add(circleTag1);
130
+ this.scene.add(circleTag2);
131
+ this.scene.add(line);
132
+ this.scene.add(label);
133
+ },
134
+ getInstanceSurfacePoints(instancedMesh, instanceId, maxPoints = 1000) {
135
+ const geometry = instancedMesh.geometry;
136
+ // let localPoints = this.generateGenericLocalPoints(geometry, maxPoints);
137
+ let localPoints = this.sampleGeometrySurface(geometry, maxPoints);
138
+ const worldMatrix = this.getInstanceWorldMatrix(instancedMesh, instanceId);
139
+ const worldPoints = localPoints.map(p => p.clone().applyMatrix4(worldMatrix));
140
+ return worldPoints.slice(0, maxPoints);
141
+ },
142
+ sampleGeometrySurface(geometry, maxPoints = 600) {
143
+ const positions = geometry.getAttribute('position');
144
+ if (!positions) return [];
145
+
146
+ // 获取三角形(优先使用索引)
147
+ let triangles = [];
148
+ if (geometry.index) {
149
+ const index = geometry.index.array;
150
+ for (let i = 0; i < index.length; i += 3) {
151
+ const a = new THREE.Vector3(positions.getX(index[i]), positions.getY(index[i]), positions.getZ(index[i]));
152
+ const b = new THREE.Vector3(positions.getX(index[i + 1]), positions.getY(index[i + 1]), positions.getZ(index[i + 1]));
153
+ const c = new THREE.Vector3(positions.getX(index[i + 2]), positions.getY(index[i + 2]), positions.getZ(index[i + 2]));
154
+ triangles.push([a, b, c]);
155
+ }
156
+ } else {
157
+ // 无索引时,每三个顶点组成一个三角形
158
+ for (let i = 0; i < positions.count; i += 3) {
159
+ const a = new THREE.Vector3(positions.getX(i), positions.getY(i), positions.getZ(i));
160
+ const b = new THREE.Vector3(positions.getX(i + 1), positions.getY(i + 1), positions.getZ(i + 1));
161
+ const c = new THREE.Vector3(positions.getX(i + 2), positions.getY(i + 2), positions.getZ(i + 2));
162
+ triangles.push([a, b, c]);
163
+ }
164
+ }
165
+
166
+ if (triangles.length === 0) return [];
167
+
168
+ // 计算每个三角形的面积
169
+ const areas = triangles.map(([a, b, c]) => {
170
+ const ab = new THREE.Vector3().subVectors(b, a);
171
+ const ac = new THREE.Vector3().subVectors(c, a);
172
+ return new THREE.Vector3().crossVectors(ab, ac).length() * 0.5;
173
+ });
174
+ const totalArea = areas.reduce((sum, a) => sum + a, 0);
175
+
176
+ // 按面积比例分配采样点数
177
+ const points = [];
178
+ for (let i = 0; i < triangles.length; i++) {
179
+ const [a, b, c] = triangles[i];
180
+ const area = areas[i];
181
+ let samplesForFace = Math.round((area / totalArea) * maxPoints);
182
+ if (samplesForFace < 1 && points.length < maxPoints) samplesForFace = 1; // 确保每个面至少一个点
183
+
184
+ for (let j = 0; j < samplesForFace; j++) {
185
+ // 重心坐标随机采样
186
+ const u = Math.random();
187
+ const v = Math.random() * (1 - u);
188
+ const w = 1 - u - v;
189
+ const point = new THREE.Vector3()
190
+ .addScaledVector(a, u)
191
+ .addScaledVector(b, v)
192
+ .addScaledVector(c, w);
193
+ points.push(point);
194
+ }
195
+ }
196
+
197
+ // 如果点数不够,随机补充
198
+ while (points.length < maxPoints) {
199
+ const triIdx = Math.floor(Math.random() * triangles.length);
200
+ const [a, b, c] = triangles[triIdx];
201
+ const u = Math.random();
202
+ const v = Math.random() * (1 - u);
203
+ const w = 1 - u - v;
204
+ points.push(new THREE.Vector3().addScaledVector(a, u).addScaledVector(b, v).addScaledVector(c, w));
205
+ }
206
+ return points.slice(0, maxPoints);
207
+ },
208
+ generateGenericLocalPoints(geometry, maxPoints = 1000) {
209
+ const pos = geometry.getAttribute('position');
210
+ if (!pos) return [];
211
+ const allVerts = [];
212
+ for (let i = 0; i < pos.count; i++) {
213
+ allVerts.push(new THREE.Vector3(pos.getX(i), pos.getY(i), pos.getZ(i)));
214
+ }
215
+ let sourceVerts = allVerts;
216
+ if (geometry.index) {
217
+ const indexArr = geometry.index.array;
218
+ const seen = new Set();
219
+ sourceVerts = [];
220
+ for (let i = 0; i < indexArr.length; i++) {
221
+ const v = allVerts[indexArr[i]];
222
+ const key = `${v.x.toFixed(4)},${v.y.toFixed(4)},${v.z.toFixed(4)}`;
223
+ if (!seen.has(key)) {
224
+ seen.add(key);
225
+ sourceVerts.push(v.clone());
226
+ }
227
+ }
228
+ }
229
+ if (sourceVerts.length > maxPoints) {
230
+ const step = Math.ceil(sourceVerts.length / maxPoints);
231
+ const sampled = [];
232
+ for (let i = 0; i < sourceVerts.length; i += step) sampled.push(sourceVerts[i]);
233
+ sourceVerts = sampled;
234
+ }
235
+ return sourceVerts;
236
+
237
+ },
238
+ findClosestPair(points1, points2) {
239
+ let minDist = Infinity;
240
+ const c1 = new THREE.Vector3();
241
+ const c2 = new THREE.Vector3();
242
+ for (const p1 of points1) {
243
+ for (const p2 of points2) {
244
+ const d = p1.distanceTo(p2);
245
+ if (d < minDist) {
246
+ minDist = d;
247
+ c1.copy(p1);
248
+ c2.copy(p2);
249
+ }
250
+ }
251
+ }
252
+ return { point1: c1, point2: c2, distance: minDist };
253
+ },
254
+
255
+ // 平行判断 (基于包围盒主轴方向)
256
+ checkParallelism(inst1, id1, inst2, id2) {
257
+ const mat1 = this.getInstanceWorldMatrix(inst1, id1);
258
+ const mat2 = this.getInstanceWorldMatrix(inst2, id2);
259
+ const box1 = new THREE.Box3();
260
+ const box2 = new THREE.Box3();
261
+
262
+ // 确保包围盒存在
263
+ if (!inst1.geometry.boundingBox) inst1.geometry.computeBoundingBox();
264
+ if (!inst2.geometry.boundingBox) inst2.geometry.computeBoundingBox();
265
+
266
+ box1.copy(inst1.geometry.boundingBox).applyMatrix4(mat1);
267
+ box2.copy(inst2.geometry.boundingBox).applyMatrix4(mat2);
268
+
269
+ const size1 = new THREE.Vector3(); box1.getSize(size1);
270
+ const size2 = new THREE.Vector3(); box2.getSize(size2);
271
+
272
+ const getMainDir = (s) => {
273
+ if (s.x >= s.y && s.x >= s.z) return new THREE.Vector3(1, 0, 0);
274
+ if (s.y >= s.x && s.y >= s.z) return new THREE.Vector3(0, 1, 0);
275
+ return new THREE.Vector3(0, 0, 1);
276
+ };
277
+ const d1 = getMainDir(size1);
278
+ const d2 = getMainDir(size2);
279
+
280
+ const rot1 = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().extractRotation(mat1));
281
+ const rot2 = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().extractRotation(mat2));
282
+ d1.applyQuaternion(rot1);
283
+ d2.applyQuaternion(rot2);
284
+
285
+ return Math.abs(d1.dot(d2)) > 0.95;
286
+ },
287
+ getInstanceWorldMatrix(instancedMesh, instanceId) {
288
+ const matrix = new THREE.Matrix4();
289
+ const instanceMatrix = new THREE.Matrix4();
290
+ instancedMesh.getMatrixAt(instanceId, instanceMatrix);
291
+ matrix.multiplyMatrices(instancedMesh.matrixWorld, instanceMatrix);
292
+ return matrix;
293
+ },
294
+ close(isClear) {
295
+ this.renderer.domElement.removeEventListener('mousedown', this.mousedown);
296
+ this.renderer.domElement.removeEventListener('mouseup', this.click)
297
+ if (!isClear) {
298
+ this.remove(this.points);
299
+ this.remove(this.polyline);
300
+ this.remove(this.labels);
301
+ this.pointArray.splice(0);
302
+ this.points.splice(0);
303
+ this.polyline.splice(0);
304
+ this.labels.splice(0);
305
+ this.firstTime = 0;
306
+ }
307
+ this.renderer.domElement.style.cursor = 'pointer';
308
+ },
309
+ clear() {
310
+ this.remove(this.points);
311
+ this.remove(this.polyline);
312
+ this.remove(this.labels);
313
+ this.pointArray.splice(0);
314
+ this.points.splice(0);
315
+ this.polyline.splice(0);
316
+ this.labels.splice(0);
317
+ this.firstTime = 0;
318
+ },
319
+ remove(array) {
320
+ for (let index = 0; index < array.length; index++) {
321
+ const element = array[index];
322
+ if (element.geometry) {
323
+ element.geometry.dispose();
324
+ }
325
+ this.scene.remove(element);
326
+ }
327
+ },
328
+ numberToString(num) {
329
+ if (num < 0.0001) {
330
+ return num.toString();
331
+ }
332
+ let fractionDigits = 2;
333
+ if (num < 0.01) {
334
+ fractionDigits = 4;
335
+ } else if (num < 0.1) {
336
+ fractionDigits = 3;
337
+ }
338
+ return num.toFixed(fractionDigits);
339
+ },
340
+ preventContextMenu(event) {
341
+ event.preventDefault();
342
+ },
343
+ };
344
+ export default {
345
+ MeasureClearDistance
346
+ };