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.
- package/README.md +6 -0
- package/dist/fl-web-component.common.js +1044 -214
- package/dist/fl-web-component.common.js.map +1 -1
- package/dist/fl-web-component.css +1 -1
- package/package.json +1 -1
- package/packages/components/com-graphics/index.vue +73 -11
- package/src/utils/threejs/measure-angle.js +172 -41
- package/src/utils/threejs/measure-area.js +144 -46
- package/src/utils/threejs/measure-clear-distance.js +346 -0
- package/src/utils/threejs/measure-distance.js +135 -18
- package/src/utils/threejs/measure-height.js +59 -15
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|