fl-web-component 0.1.0
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 +24 -0
- package/dist/demo.html +10 -0
- package/dist/fl-web-component.common.js +316 -0
- package/dist/fl-web-component.common.js.map +1 -0
- package/dist/fl-web-component.css +1 -0
- package/dist/fl-web-component.umd.js +326 -0
- package/dist/fl-web-component.umd.js.map +1 -0
- package/dist/fl-web-component.umd.min.js +2 -0
- package/dist/fl-web-component.umd.min.js.map +1 -0
- package/package.json +47 -0
- package/packages/components/button/index.vue +22 -0
- package/packages/components/model/api/index.js +429 -0
- package/packages/components/model/api/mock/detecttree.js +58 -0
- package/packages/components/model/api/mock/getmodel-line.js +79336 -0
- package/packages/components/model/api/mock/init.js +1 -0
- package/packages/components/model/api/mock/pbstree.js +835 -0
- package/packages/components/model/api/mock/topology.json +3238 -0
- package/packages/components/model/components/TextOverTooltip/index.vue +84 -0
- package/packages/components/model/components/annotation-toolbar.vue +425 -0
- package/packages/components/model/components/check-proofing-model.vue +42 -0
- package/packages/components/model/components/clipping-type.vue +51 -0
- package/packages/components/model/components/com-dialogWrapper/Readme.md +53 -0
- package/packages/components/model/components/com-dialogWrapper/index.vue +117 -0
- package/packages/components/model/components/detect-panel.vue +327 -0
- package/packages/components/model/components/detect-tree.vue +460 -0
- package/packages/components/model/components/firstPer-panel.vue +111 -0
- package/packages/components/model/components/header-button.vue +546 -0
- package/packages/components/model/components/imageViewer/index.vue +127 -0
- package/packages/components/model/components/import-model.vue +127 -0
- package/packages/components/model/components/location-panel.vue +95 -0
- package/packages/components/model/components/measure-type.vue +59 -0
- package/packages/components/model/components/pbs-tree.vue +502 -0
- package/packages/components/model/components/proof-config.vue +80 -0
- package/packages/components/model/components/proof-for-pc.vue +123 -0
- package/packages/components/model/components/proof-history.vue +318 -0
- package/packages/components/model/components/proof-panel-detail.vue +567 -0
- package/packages/components/model/components/proof-panel.vue +770 -0
- package/packages/components/model/components/proof-project-user.vue +482 -0
- package/packages/components/model/components/proof-publish.vue +130 -0
- package/packages/components/model/components/proof-role.vue +535 -0
- package/packages/components/model/components/props-panel.vue +249 -0
- package/packages/components/model/index.vue +3413 -0
- package/packages/components/model/readme.md +31 -0
- package/packages/components/model/utils/annotation-tool.js +340 -0
- package/packages/components/model/utils/cursor.js +18 -0
- package/packages/components/model/utils/detect-v1.js +341 -0
- package/packages/components/model/utils/index.js +48 -0
- package/packages/components/model/utils/threejs/measure-angle.js +258 -0
- package/packages/components/model/utils/threejs/measure-area.js +269 -0
- package/packages/components/model/utils/threejs/measure-distance.js +207 -0
- package/packages/components/model/utils/threejs/measure-volume.js +94 -0
- package/packages/index.js +24 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
|
|
3
|
+
class CollisionSystem {
|
|
4
|
+
/**
|
|
5
|
+
* @param {Object} options - 包含初始化所需的参数对象。
|
|
6
|
+
* @param {THREE.Scene} options.scene - Three.js 场景对象,该场景包含所有需要进行碰撞检测的实体。
|
|
7
|
+
* @param {Array} options.topologyData - 拓扑数据数组,包含实体之间的连接关系等信息。
|
|
8
|
+
*/
|
|
9
|
+
constructor({ scene, topologyData }) {
|
|
10
|
+
this.scene = scene;
|
|
11
|
+
this.topologyData = topologyData;
|
|
12
|
+
this.entityMap = new Map(); // 所有实体 {id: Entity}
|
|
13
|
+
// 创建一个空数组,用于存储碰撞检测的结果,每个结果是一个包含发生碰撞的两个实体的对象
|
|
14
|
+
this.collisionResults = []; // 碰撞结果
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 主入口:初始化并运行碰撞检测
|
|
18
|
+
run() {
|
|
19
|
+
// 1. 遍历获取所有需要检测的实体
|
|
20
|
+
const allEntities = this.collectEntities();
|
|
21
|
+
|
|
22
|
+
// 2. 构建实体数据(含包围盒和连接关系)
|
|
23
|
+
this.buildEntitiesData(allEntities);
|
|
24
|
+
|
|
25
|
+
// 3. 执行碰撞检测
|
|
26
|
+
this.detectCollisions();
|
|
27
|
+
|
|
28
|
+
// 4. 展示结果
|
|
29
|
+
return this.showResults();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 收集实体(模拟C++的GetIdsUnderCurWorkSpace)
|
|
33
|
+
collectEntities() {
|
|
34
|
+
return this.scene.children[0].children.filter((obj) => {
|
|
35
|
+
return obj.name?.indexOf("MODELELEM") != -1;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 构建实体数据(核心逻辑)
|
|
40
|
+
buildEntitiesData(objects) {
|
|
41
|
+
objects.forEach((obj) => {
|
|
42
|
+
const curTopoObj = this.topologyData.find((item) => {
|
|
43
|
+
return item.ObjId === obj.name;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const connectedIds = new Set(curTopoObj.ConnectIDs);
|
|
47
|
+
|
|
48
|
+
// 创建包围盒
|
|
49
|
+
const tightBoxList = this.initTightData(obj);
|
|
50
|
+
|
|
51
|
+
const entity = {
|
|
52
|
+
id: obj.id,
|
|
53
|
+
obj: obj,
|
|
54
|
+
// tightBox: this.getTightBox(obj),
|
|
55
|
+
majorType: obj.userData.tags,
|
|
56
|
+
connectedIds: connectedIds,
|
|
57
|
+
isWeld: obj.userData.tags?.includes("PIPE_PIPEWELD"),
|
|
58
|
+
tightBoxList: tightBoxList,
|
|
59
|
+
};
|
|
60
|
+
this.entityMap.set(obj.id, entity);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 初始化紧密包围盒数据
|
|
66
|
+
* @param {Object} obj - 需要初始化紧密包围盒数据的对象
|
|
67
|
+
* @returns {Object} - 包含坐标和包围盒信息的对象
|
|
68
|
+
*/
|
|
69
|
+
initTightData(obj) {
|
|
70
|
+
const { coord, box } = obj.userData.bounding;
|
|
71
|
+
|
|
72
|
+
const maxPt = this.parseVector3(box.maxPt);
|
|
73
|
+
const minPt = this.parseVector3(box.minPt);
|
|
74
|
+
|
|
75
|
+
const m_coord = this.initCoord3D(coord);
|
|
76
|
+
const m_box = {
|
|
77
|
+
max: maxPt,
|
|
78
|
+
min: minPt,
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
m_coord,
|
|
82
|
+
m_box,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 将字符串解析为 THREE.Vector3 对象。
|
|
88
|
+
* @param {string} str - 包含三个数字的字符串,数字之间用逗号分隔。
|
|
89
|
+
* @returns {THREE.Vector3} - 解析后的 THREE.Vector3 对象。
|
|
90
|
+
*/
|
|
91
|
+
parseVector3(str) {
|
|
92
|
+
// 将字符串按逗号分割成数组,并将每个元素转换为数字,然后传递给 THREE.Vector3 构造函数
|
|
93
|
+
return new THREE.Vector3(...str.split(",").map(Number));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 碰撞检测主逻辑(双层过滤)
|
|
97
|
+
detectCollisions() {
|
|
98
|
+
const entities = Array.from(this.entityMap.values());
|
|
99
|
+
|
|
100
|
+
entities.forEach((entityA) => {
|
|
101
|
+
// 排除焊点、垫片等不需要检测的实体
|
|
102
|
+
if (this.shouldSkipEntity(entityA)) return;
|
|
103
|
+
|
|
104
|
+
entities.forEach((entityB) => {
|
|
105
|
+
//先判断两个实体是否是自己
|
|
106
|
+
if (entityA.id === entityB.id) return;
|
|
107
|
+
if (this.shouldSkipPair(entityA, entityB)) return;
|
|
108
|
+
|
|
109
|
+
// 执行包围盒相交检测
|
|
110
|
+
const obbListA = entityA.tightBoxList;
|
|
111
|
+
const obbListB = entityB.tightBoxList;
|
|
112
|
+
|
|
113
|
+
// if (entityA.tightBox.intersectsBox(entityB.tightBox)) {
|
|
114
|
+
// this.collisionResults.push({ entityA, entityB });
|
|
115
|
+
// }
|
|
116
|
+
|
|
117
|
+
if (this.collIntersect(obbListA, obbListB)) {
|
|
118
|
+
this.collisionResults.push({ entityA, entityB });
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 获取紧包围盒(优先子图段)
|
|
125
|
+
getTightBox(obj) {
|
|
126
|
+
let box = new THREE.Box3();
|
|
127
|
+
|
|
128
|
+
box.setFromObject(obj);
|
|
129
|
+
return box;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 业务规则过滤(核心逻辑)
|
|
133
|
+
shouldSkipEntity(entity) {
|
|
134
|
+
// 排除焊点、垫片等
|
|
135
|
+
return (
|
|
136
|
+
entity.isWeld ||
|
|
137
|
+
entity.obj.userData.tags.includes("GASK") ||
|
|
138
|
+
entity.obj.userData.tags.includes("FLAN")
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 判断两个实体是否应该跳过碰撞检测。
|
|
144
|
+
* @param {Object} entityA - 第一个实体对象。
|
|
145
|
+
* @param {Object} entityB - 第二个实体对象。
|
|
146
|
+
* @returns {boolean} - 如果应该跳过检测返回 true,否则返回 false。
|
|
147
|
+
*/
|
|
148
|
+
shouldSkipPair(entityA, entityB) {
|
|
149
|
+
// 已连接的实体不检测
|
|
150
|
+
if (entityA.connectedIds.has(entityB.obj.name)) return true;
|
|
151
|
+
|
|
152
|
+
// 专业类型过滤规则
|
|
153
|
+
const { majorType: aType } = entityA;
|
|
154
|
+
const { majorType: bType } = entityB;
|
|
155
|
+
|
|
156
|
+
//先判断两个实体是否是自己或者互相连接的且垫片不单独参与碰撞
|
|
157
|
+
if (aType.includes("GASK") || bType.includes("GASK")) return true;
|
|
158
|
+
|
|
159
|
+
if (bType.includes("PIPE_PIPEWELD")) return true;
|
|
160
|
+
|
|
161
|
+
if (bType === "") return true;
|
|
162
|
+
|
|
163
|
+
// 设备专业特殊规则
|
|
164
|
+
//设备不和管线等专业碰撞 但是管线和设备碰撞
|
|
165
|
+
if (
|
|
166
|
+
aType.includes("TAG_MAJOR_DEVICE") &&
|
|
167
|
+
!bType.includes("TAG_MAJOR_DEVICE")
|
|
168
|
+
)
|
|
169
|
+
return true;
|
|
170
|
+
|
|
171
|
+
// 管线专业只与设备和管线碰撞
|
|
172
|
+
//piping只能和设备以及piping专业碰撞
|
|
173
|
+
if (
|
|
174
|
+
aType.includes("TAG_MAJOR_PIPINIG") &&
|
|
175
|
+
!bType.includes("TAG_MAJOR_DEVICE") &&
|
|
176
|
+
!bType.includes("TAG_MAJOR_PIPINIG")
|
|
177
|
+
)
|
|
178
|
+
return true;
|
|
179
|
+
|
|
180
|
+
// 土建专业特殊规则
|
|
181
|
+
//土建专业只和piping和设备专业碰撞
|
|
182
|
+
if (
|
|
183
|
+
aType.includes("TAG_MAJOR_ARCH") &&
|
|
184
|
+
(bType.includes("TAG_MAJOR_ARCH") ||
|
|
185
|
+
(!bType.includes("TAG_MAJOR_PIPINIG") &&
|
|
186
|
+
!bType.includes("TAG_MAJOR_DEVICE")))
|
|
187
|
+
)
|
|
188
|
+
return true;
|
|
189
|
+
|
|
190
|
+
if (
|
|
191
|
+
bType.includes("TAG_MAJOR_ARCH") &&
|
|
192
|
+
!aType.includes("TAG_MAJOR_PIPINIG") &&
|
|
193
|
+
!aType.includes("TAG_MAJOR_DEVICE")
|
|
194
|
+
)
|
|
195
|
+
return true;
|
|
196
|
+
|
|
197
|
+
// if(aType.includes('TAG_MAJOR_HVAC') && bType.includes('TAG_MAJOR_ARCH')){
|
|
198
|
+
// console.log(entityB)
|
|
199
|
+
// console.log(bType.includes('TAG_MAJOR_ARCH'))
|
|
200
|
+
// }
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
initCoord3D(coord) {
|
|
205
|
+
const origin = this.parseVector3(coord.Origin);
|
|
206
|
+
const dirX = this.parseVector3(coord.dirX).normalize();
|
|
207
|
+
const dirY = this.parseVector3(coord.dirY).normalize();
|
|
208
|
+
const dirZ = this.parseVector3(coord.dirZ).normalize();
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
origin,
|
|
212
|
+
dirX,
|
|
213
|
+
dirY,
|
|
214
|
+
dirZ,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
globalPoint(localPoint, { origin, dirX, dirY, dirZ }) {
|
|
219
|
+
return origin
|
|
220
|
+
.clone()
|
|
221
|
+
.add(dirX.clone().multiplyScalar(localPoint.x))
|
|
222
|
+
.add(dirY.clone().multiplyScalar(localPoint.y))
|
|
223
|
+
.add(dirZ.clone().multiplyScalar(localPoint.z));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 获取包围盒的半向量数组。
|
|
228
|
+
*
|
|
229
|
+
* 此方法用于计算并返回包围盒在三个坐标轴方向上的半向量。
|
|
230
|
+
* 半向量是指从包围盒中心到其边界在各个坐标轴方向上的向量。
|
|
231
|
+
*
|
|
232
|
+
* @param {Object} param - 包含包围盒和坐标系统信息的对象。
|
|
233
|
+
* @param {Object} param.m_box - 包围盒对象,包含 max 和 min 属性,分别表示包围盒的最大点和最小点。
|
|
234
|
+
* @param {Object} param.m_coord - 坐标系统对象,包含 origin、dirX、dirY 和 dirZ 属性,分别表示原点和三个坐标轴方向。
|
|
235
|
+
* @returns {Array<THREE.Vector3>} - 包含三个半向量的数组,分别对应 X、Y 和 Z 轴方向。
|
|
236
|
+
*/
|
|
237
|
+
getHalfVectors({ m_box, m_coord }) {
|
|
238
|
+
const halfSize = new THREE.Vector3()
|
|
239
|
+
.subVectors(m_box.max, m_box.min)
|
|
240
|
+
.multiplyScalar(0.5);
|
|
241
|
+
|
|
242
|
+
return [
|
|
243
|
+
m_coord.dirX.clone().multiplyScalar(halfSize.x),
|
|
244
|
+
m_coord.dirY.clone().multiplyScalar(halfSize.y),
|
|
245
|
+
m_coord.dirZ.clone().multiplyScalar(halfSize.z),
|
|
246
|
+
];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
centerPt({ m_box, m_coord }) {
|
|
250
|
+
const center = this.getCenter(m_box);
|
|
251
|
+
return this.globalPoint(center, m_coord);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* 计算包围盒的中心点。
|
|
255
|
+
* @param {Object} m_box - 包围盒对象,包含 max 和 min 属性,分别表示包围盒的最大点和最小点。
|
|
256
|
+
* @returns {THREE.Vector3} - 包围盒的中心点向量。
|
|
257
|
+
*/
|
|
258
|
+
getCenter(m_box) {
|
|
259
|
+
// 将包围盒的最大点和最小点相加
|
|
260
|
+
// 然后将结果乘以 0.5,得到中心点的坐标
|
|
261
|
+
return new THREE.Vector3()
|
|
262
|
+
.addVectors(m_box.max, m_box.min)
|
|
263
|
+
.multiplyScalar(0.5);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* 检测两个包围盒是否相交
|
|
268
|
+
* @param {Object} box1 - 第一个包围盒对象,包含 m_box 和 m_coord 属性
|
|
269
|
+
* @param {Object} box2 - 第二个包围盒对象,包含 m_box 和 m_coord 属性
|
|
270
|
+
* @param {number} distEpsilon - 距离容差,默认为 0
|
|
271
|
+
* @returns {boolean} - 如果两个包围盒相交返回 true,否则返回 false
|
|
272
|
+
*/
|
|
273
|
+
collIntersect(box1, box2, distEpsilon = 0) {
|
|
274
|
+
const v_center = new THREE.Vector3().subVectors(
|
|
275
|
+
this.centerPt(box2),
|
|
276
|
+
this.centerPt(box1)
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const axes = [...this.getHalfVectors(box1), ...this.getHalfVectors(box2)];
|
|
280
|
+
|
|
281
|
+
const sqrDistEpsilon = distEpsilon * distEpsilon;
|
|
282
|
+
|
|
283
|
+
const isSplit = (n) => {
|
|
284
|
+
let sumProj = 0;
|
|
285
|
+
axes.forEach((axis) => {
|
|
286
|
+
sumProj += Math.abs(axis.dot(n));
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const nLengthSq = n.dot(n);
|
|
290
|
+
const epsilon = sqrDistEpsilon * nLengthSq;
|
|
291
|
+
const centerProj = Math.abs(v_center.dot(n));
|
|
292
|
+
const minus = centerProj - sumProj;
|
|
293
|
+
|
|
294
|
+
return minus >= 0 && epsilon <= minus * minus;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
if (v_center.lengthSq() > 0 && isSplit(v_center)) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
for (let i = 0; i < 6; i++) {
|
|
302
|
+
if (isSplit(axes[i])) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
showResults() {
|
|
311
|
+
const groupMap = {};
|
|
312
|
+
this.collisionResults.forEach((item) => {
|
|
313
|
+
const aId = item.entityA.id;
|
|
314
|
+
const bId = item.entityB.id;
|
|
315
|
+
const aObjectid = item.entityA.obj.name;
|
|
316
|
+
const bObjectid = item.entityB.obj.name;
|
|
317
|
+
const aCategory = item.entityA.obj.userData.category;
|
|
318
|
+
const bCategory = item.entityB.obj.userData.category;
|
|
319
|
+
if (!groupMap[aId]) {
|
|
320
|
+
groupMap[aId] = {
|
|
321
|
+
id: aId,
|
|
322
|
+
objectid: aObjectid,
|
|
323
|
+
category: aCategory,
|
|
324
|
+
detect: [],
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
groupMap[aId].detect.push({
|
|
328
|
+
id: bId,
|
|
329
|
+
objectid: bObjectid,
|
|
330
|
+
category: bCategory,
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const formatCollisionResults = Object.values(groupMap);
|
|
335
|
+
|
|
336
|
+
console.log("碰撞结果", formatCollisionResults);
|
|
337
|
+
return formatCollisionResults;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export default CollisionSystem;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Loading } from 'element-ui'
|
|
2
|
+
var loadingInstance = {}
|
|
3
|
+
export function dateFormat(dateData) {
|
|
4
|
+
var date = new Date(dateData)
|
|
5
|
+
var y = date.getFullYear()
|
|
6
|
+
var m = date.getMonth() + 1
|
|
7
|
+
m = m < 10 ? ('0' + m) : m
|
|
8
|
+
var d = date.getDate()
|
|
9
|
+
d = d < 10 ? ('0' + d) : d
|
|
10
|
+
const time = y + '-' + m + '-' + d
|
|
11
|
+
return time
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getItem(key) {
|
|
15
|
+
let item = localStorage.getItem(key)
|
|
16
|
+
if (item) {
|
|
17
|
+
return JSON.parse(item)
|
|
18
|
+
}
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function loading(tips = '拼命加载中...') {
|
|
23
|
+
loadingInstance = Loading.service({
|
|
24
|
+
lock: true,
|
|
25
|
+
fullscreen: true,
|
|
26
|
+
text: tips,
|
|
27
|
+
spinner: 'el-icon-loading',
|
|
28
|
+
background: 'rgba(255, 255, 255, 0.5)'
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function loadingClose() {
|
|
33
|
+
loadingInstance.close()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 节流 fn要防抖的函数 t是时间
|
|
37
|
+
export function debounce(fn, t) {
|
|
38
|
+
const delay = t || 500
|
|
39
|
+
let timer
|
|
40
|
+
return function() {
|
|
41
|
+
const args = arguments
|
|
42
|
+
if (timer) clearTimeout(timer)
|
|
43
|
+
timer = setTimeout(() => {
|
|
44
|
+
timer = null
|
|
45
|
+
fn.apply(this, args)
|
|
46
|
+
}, delay)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import * as THREE from 'three'
|
|
2
|
+
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
|
3
|
+
var _this = null
|
|
4
|
+
var MeasureAngle = function(renderer, scene, camera, width, height) {
|
|
5
|
+
this.renderer = renderer
|
|
6
|
+
this.scene = scene
|
|
7
|
+
this.camera = camera
|
|
8
|
+
this.pointArray = [] // 保存当前操作所添加的点
|
|
9
|
+
this.raycaster = new THREE.Raycaster()
|
|
10
|
+
this.points = [] // 保存页面中所添加的点
|
|
11
|
+
this.polyline = [] //保存页面中所添加的直线
|
|
12
|
+
this.labels = [] // 保存页面中所添加的文本
|
|
13
|
+
this.curves = [] // 保存页面所有的曲线
|
|
14
|
+
this.tempPoints = undefined
|
|
15
|
+
this.tempLine = undefined
|
|
16
|
+
this.tempLabel = undefined
|
|
17
|
+
this.tipsLabel = undefined
|
|
18
|
+
this.isCompleted = false
|
|
19
|
+
this.curveLine = undefined
|
|
20
|
+
this.timer = null
|
|
21
|
+
this.width = width
|
|
22
|
+
this.height = height
|
|
23
|
+
// this.POINT_MATERIAL = new THREE.PointsMaterial({ color: 0xff5000, size: 1, opacity: 0.6, transparent: true, depthWrite: false, depthTest: false })
|
|
24
|
+
// this.LINE_MATERIAL = new THREE.LineBasicMaterial({ color: 0xff0000, linewidth: 3, opacity: 0.8, transparent: true, side: THREE.DoubleSide, depthWrite: false, depthTest: false })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
MeasureAngle.prototype = {
|
|
28
|
+
start() {
|
|
29
|
+
_this = this
|
|
30
|
+
this.renderer.domElement.style.cursor = 'crosshair'
|
|
31
|
+
this.renderer.domElement.addEventListener('click', this.click, false)
|
|
32
|
+
this.renderer.domElement.addEventListener('mousemove', this.mousemove, false)
|
|
33
|
+
this.renderer.domElement.addEventListener('contextmenu', this.rightClick, false)
|
|
34
|
+
},
|
|
35
|
+
updateParams(width, height) {
|
|
36
|
+
this.camera.aspect = width / height
|
|
37
|
+
this.camera.updateProjectionMatrix()
|
|
38
|
+
this.renderer.setSize(width, height, true)
|
|
39
|
+
this.width = width
|
|
40
|
+
this.height = height
|
|
41
|
+
},
|
|
42
|
+
getPosition(e) {
|
|
43
|
+
const mouse = new THREE.Vector2()
|
|
44
|
+
mouse.x = (e.clientX / _this.width) * 2 - 1
|
|
45
|
+
mouse.y = -(e.clientY / _this.height) * 2 + 1
|
|
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].point
|
|
50
|
+
}
|
|
51
|
+
return null
|
|
52
|
+
},
|
|
53
|
+
createPoints(pos, config = {color: 0x009bea, size: 0.3}) {
|
|
54
|
+
const mesh = new THREE.MeshBasicMaterial({color: config.color || 0x009bea})
|
|
55
|
+
const geom = new THREE.SphereGeometry(config.size || 0.3, 28, 28)
|
|
56
|
+
const sphere = new THREE.Mesh(geom, mesh)
|
|
57
|
+
sphere.position.set(pos.x, pos.y, pos.z)
|
|
58
|
+
return sphere
|
|
59
|
+
},
|
|
60
|
+
createLine(p1, p2, config={color: 0xff0000}) {
|
|
61
|
+
const lineMaterial = new THREE.LineBasicMaterial({ color: config.color, linewidth: 10 })
|
|
62
|
+
const lineGeometry = new THREE.BufferGeometry().setFromPoints([p1, p2])
|
|
63
|
+
const line = new THREE.Line(lineGeometry, lineMaterial)
|
|
64
|
+
line.frustumCulled = false;
|
|
65
|
+
|
|
66
|
+
return line
|
|
67
|
+
},
|
|
68
|
+
createLabel(name, text, position) {
|
|
69
|
+
const div = document.createElement('div')
|
|
70
|
+
div.className = name
|
|
71
|
+
div.textContent = text
|
|
72
|
+
const divLabel = new CSS2DObject(div)
|
|
73
|
+
divLabel.position.set(position.x, position.y, position.z)
|
|
74
|
+
return divLabel
|
|
75
|
+
},
|
|
76
|
+
mousemove(e) {
|
|
77
|
+
if (_this.isCompleted || _this.pointArray.length === 0) return
|
|
78
|
+
const point = _this.getPosition(e)
|
|
79
|
+
if (point) {
|
|
80
|
+
_this.pointArray.length === 1 ? _this.pointArray.push(point) : _this.pointArray.splice(_this.pointArray.length - 1, 1, point)
|
|
81
|
+
const length = _this.pointArray.length
|
|
82
|
+
const p1 = _this.pointArray[length - 2]
|
|
83
|
+
const p2 = _this.pointArray[length - 1]
|
|
84
|
+
if (_this.tempPoints) {
|
|
85
|
+
_this.tempPoints.position.set(point.x, point.y, point.z)
|
|
86
|
+
} else {
|
|
87
|
+
const geom = _this.createLabel('circle-tag', '', point)
|
|
88
|
+
_this.tempPoints = geom
|
|
89
|
+
_this.points.push(geom)
|
|
90
|
+
_this.scene.add(geom)
|
|
91
|
+
}
|
|
92
|
+
if (_this.tempLine) {
|
|
93
|
+
_this.tempLine.geometry.setFromPoints([p1, p2])
|
|
94
|
+
} else {
|
|
95
|
+
_this.tempLine = _this.createLine(p1, p2)
|
|
96
|
+
_this.polyline.push(_this.tempLine)
|
|
97
|
+
_this.scene.add(_this.tempLine)
|
|
98
|
+
}
|
|
99
|
+
// 三点构成一个角
|
|
100
|
+
if (_this.pointArray.length === 3) {
|
|
101
|
+
const angle = _this.calculateAngle().toFixed(2) + '°'
|
|
102
|
+
const p0 = _this.pointArray[0]
|
|
103
|
+
const p1 = _this.pointArray[1]
|
|
104
|
+
const p2 = _this.pointArray[2]
|
|
105
|
+
const v1 = new THREE.Vector3((p0.x + p1.x) / 2, (p0.y + p1.y) / 2, (p0.z + p1.z) / 2)
|
|
106
|
+
const v2 = new THREE.Vector3((p1.x + p2.x) / 2, (p1.y + p2.y) / 2, (p1.z + p2.z) / 2)
|
|
107
|
+
const curve = new THREE.CatmullRomCurve3(
|
|
108
|
+
[v1, v2]
|
|
109
|
+
)
|
|
110
|
+
const points = curve.getPoints(50)
|
|
111
|
+
if (_this.tempLabel) {
|
|
112
|
+
_this.tempLabel.element.textContent = angle
|
|
113
|
+
_this.tempLabel.position.set(p1.x, p1.y, p1.z)
|
|
114
|
+
_this.curveLine.geometry.setFromPoints(points)
|
|
115
|
+
} else {
|
|
116
|
+
_this.tempLabel = _this.createLabel('measure-label', angle, p1)
|
|
117
|
+
_this.labels.push(_this.tempLabel)
|
|
118
|
+
_this.scene.add(_this.tempLabel)
|
|
119
|
+
_this.createCurve(points)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (_this.tipsLabel) {
|
|
123
|
+
_this.tipsLabel.position.set(point.x + 0.1, point.y, point.z + 0.05)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
createTipsLabel(label, position) {
|
|
128
|
+
const div = document.createElement('div')
|
|
129
|
+
div.className = 'tips-label'
|
|
130
|
+
div.textContent = label
|
|
131
|
+
div.style = 'top: -50px'
|
|
132
|
+
const tipsLabel = new CSS2DObject(div)
|
|
133
|
+
tipsLabel.position.set(position.x + 0.1, position.y, position.z + 0.05)
|
|
134
|
+
return tipsLabel
|
|
135
|
+
},
|
|
136
|
+
click(e) {
|
|
137
|
+
if (_this.isCompleted) {
|
|
138
|
+
_this.renderer.domElement.addEventListener('mousemove', _this.mousemove)
|
|
139
|
+
}
|
|
140
|
+
clearTimeout(_this.timer)
|
|
141
|
+
_this.timer = setTimeout(() => {
|
|
142
|
+
_this.isCompleted = false
|
|
143
|
+
const point = _this.getPosition(e)
|
|
144
|
+
if (point) {
|
|
145
|
+
if(_this.tipsLabel) {
|
|
146
|
+
_this.tipsLabel.position.set(point.x + 0.1, point.y, point.z + 0.05)
|
|
147
|
+
} else {
|
|
148
|
+
_this.tipsLabel = _this.createTipsLabel('左击绘制右击结束', point)
|
|
149
|
+
_this.scene.add(_this.tipsLabel)
|
|
150
|
+
}
|
|
151
|
+
if (_this.tempPoints) {
|
|
152
|
+
_this.tempPoints.position.set(point.x, point.y, point.z)
|
|
153
|
+
_this.tempPoints = undefined
|
|
154
|
+
} else {
|
|
155
|
+
const geom =_this.createLabel('circle-tag', '', point)
|
|
156
|
+
_this.points.push(geom)
|
|
157
|
+
_this.scene.add(geom)
|
|
158
|
+
}
|
|
159
|
+
_this.tempLine = undefined
|
|
160
|
+
_this.tempLabel = undefined
|
|
161
|
+
_this.pointArray.push(point)
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
},
|
|
165
|
+
rightClick(e) {
|
|
166
|
+
if(_this.tipsLabel) {
|
|
167
|
+
_this.scene.remove(_this.tipsLabel)
|
|
168
|
+
_this.tipsLabel = undefined
|
|
169
|
+
}
|
|
170
|
+
clearTimeout(_this.timer)
|
|
171
|
+
const point = _this.getPosition(e)
|
|
172
|
+
// if (point) {
|
|
173
|
+
if (_this.pointArray.length <= 3) {
|
|
174
|
+
_this.scene.remove(_this.points[_this.points.length-1])
|
|
175
|
+
_this.scene.remove(_this.points[_this.points.length-2])
|
|
176
|
+
|
|
177
|
+
if(_this.pointArray.length == 3){
|
|
178
|
+
_this.scene.remove(_this.points[_this.points.length-3])
|
|
179
|
+
|
|
180
|
+
_this.scene.remove(_this.polyline[_this.polyline.length-1])
|
|
181
|
+
_this.scene.remove(_this.polyline[_this.polyline.length-2])
|
|
182
|
+
|
|
183
|
+
_this.scene.remove(_this.curves[_this.curves.length-1])
|
|
184
|
+
|
|
185
|
+
_this.scene.remove(_this.tempLabel)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
_this.scene.remove(_this.tempPoints)
|
|
189
|
+
_this.scene.remove(_this.tempLine)
|
|
190
|
+
|
|
191
|
+
_this.isCompleted = true
|
|
192
|
+
_this.tempPoints = undefined
|
|
193
|
+
_this.tempLine = undefined
|
|
194
|
+
_this.tempLabel = undefined
|
|
195
|
+
_this.pointArray.splice(0)
|
|
196
|
+
_this.renderer.domElement.removeEventListener('mousemove', _this.mousemove)
|
|
197
|
+
// }
|
|
198
|
+
},
|
|
199
|
+
close() {
|
|
200
|
+
this.renderer.domElement.removeEventListener('mousemove', this.mousemove)
|
|
201
|
+
this.renderer.domElement.removeEventListener('click', this.click)
|
|
202
|
+
this.renderer.domElement.removeEventListener('contextmenu', this.rightClick)
|
|
203
|
+
this.remove(this.points)
|
|
204
|
+
this.remove(this.polyline)
|
|
205
|
+
this.remove(this.labels)
|
|
206
|
+
this.remove(this.curves)
|
|
207
|
+
this.pointArray.splice(0)
|
|
208
|
+
this.points.splice(0)
|
|
209
|
+
this.polyline.splice(0)
|
|
210
|
+
this.labels.splice(0)
|
|
211
|
+
this.curves.splice(0)
|
|
212
|
+
this.tempPoints = undefined
|
|
213
|
+
this.tempLabel = undefined
|
|
214
|
+
this.tempLine = undefined
|
|
215
|
+
this.scene.remove(this.tipsLabel)
|
|
216
|
+
this.tipsLabel = undefined
|
|
217
|
+
this.renderer.domElement.style.cursor = 'pointer'
|
|
218
|
+
},
|
|
219
|
+
remove(array) {
|
|
220
|
+
for (let index = 0; index < array.length; index++) {
|
|
221
|
+
const element = array[index]
|
|
222
|
+
if (element.geometry) {
|
|
223
|
+
element.geometry.dispose()
|
|
224
|
+
}
|
|
225
|
+
this.scene.remove(element)
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
calculateAngle() {
|
|
229
|
+
const p1 = _this.pointArray[1].clone()
|
|
230
|
+
const p2 = _this.pointArray[1].clone()
|
|
231
|
+
const dir0 = p1.sub(_this.pointArray[0]).clone()
|
|
232
|
+
const dir1 = p2.sub(_this.pointArray[2]).clone()
|
|
233
|
+
const angle = dir0.angleTo(dir1)
|
|
234
|
+
return angle * 180 / Math.PI
|
|
235
|
+
},
|
|
236
|
+
createCurve(points) {
|
|
237
|
+
const geom = new THREE.BufferGeometry().setFromPoints(points)
|
|
238
|
+
const material = new THREE.LineBasicMaterial({color: 0xff0000})
|
|
239
|
+
_this.curveLine = new THREE.Line(geom, material)
|
|
240
|
+
_this.curves.push(_this.curveLine)
|
|
241
|
+
_this.scene.add(_this.curveLine)
|
|
242
|
+
},
|
|
243
|
+
numberToString(num) {
|
|
244
|
+
if (num < 0.0001) {
|
|
245
|
+
return num.toString()
|
|
246
|
+
}
|
|
247
|
+
let fractionDigits = 2
|
|
248
|
+
if (num < 0.01) {
|
|
249
|
+
fractionDigits = 4
|
|
250
|
+
} else if (num < 0.1) {
|
|
251
|
+
fractionDigits = 3
|
|
252
|
+
}
|
|
253
|
+
return num.toFixed(fractionDigits)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
export default {
|
|
257
|
+
MeasureAngle
|
|
258
|
+
}
|