build-dxf 0.0.9 → 0.0.11
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 +21 -3
- package/package.json +16 -21
- package/src/App.vue.d.ts +2 -0
- package/src/build.d.ts +3 -0
- package/src/build.js +2023 -0
- package/src/components/Card.vue.d.ts +9 -0
- package/src/index.css +1 -0
- package/src/index.js +5 -1758
- package/src/index2.js +7367 -0
- package/src/main.d.ts +0 -0
- package/src/pages/Dxf.vue.d.ts +2 -0
- package/src/router/index.d.ts +2 -0
- package/src/utils/ComponentManager/Component.d.ts +17 -0
- package/src/utils/ComponentManager/ComponentManager.d.ts +47 -0
- package/src/utils/ComponentManager/EventDispatcher.d.ts +4 -0
- package/src/utils/ComponentManager/index.d.ts +4 -0
- package/src/utils/ComponentManager/uuid.d.ts +1 -0
- package/src/utils/DxfSystem/components/Dxf.d.ts +150 -0
- package/src/utils/DxfSystem/components/LineAnalysis.d.ts +85 -0
- package/src/utils/DxfSystem/components/Variable.d.ts +33 -0
- package/src/utils/DxfSystem/index.d.ts +16 -0
- package/src/utils/DxfSystem/plugin/ModelDataPlugin/components/DetailsPoint.d.ts +39 -0
- package/src/utils/DxfSystem/plugin/ModelDataPlugin/components/DxfLineModel.d.ts +10 -0
- package/src/utils/DxfSystem/plugin/ModelDataPlugin/components/WhiteModel.d.ts +21 -0
- package/src/utils/DxfSystem/plugin/ModelDataPlugin/components/index.d.ts +3 -0
- package/src/utils/DxfSystem/plugin/ModelDataPlugin/index.d.ts +4 -0
- package/src/utils/DxfSystem/plugin/RenderPlugin/components/DetailsPointRender.d.ts +48 -0
- package/src/utils/DxfSystem/plugin/RenderPlugin/components/DomContainer.d.ts +8 -0
- package/src/utils/DxfSystem/plugin/RenderPlugin/components/DomEventRegister.d.ts +23 -0
- package/src/utils/DxfSystem/plugin/RenderPlugin/components/ModelDataRender.d.ts +13 -0
- package/src/utils/DxfSystem/plugin/RenderPlugin/components/OriginalLineRender.d.ts +16 -0
- package/src/utils/DxfSystem/plugin/RenderPlugin/components/Renderer.d.ts +82 -0
- package/src/utils/DxfSystem/plugin/RenderPlugin/components/index.d.ts +6 -0
- package/src/utils/DxfSystem/plugin/RenderPlugin/index.d.ts +5 -0
- package/src/utils/DxfSystem/plugin/RenderPlugin/pages/Dxf.vue.d.ts +21 -0
- package/src/utils/DxfSystem/plugin/index.d.ts +1 -0
- package/src/utils/Lines.d.ts +11 -0
- package/src/utils/PointVirtualGrid/index.d.ts +70 -0
- package/src/utils/Quadtree/Box2.d.ts +89 -0
- package/src/utils/Quadtree/LineSegment.d.ts +29 -0
- package/src/utils/Quadtree/Point.d.ts +113 -0
- package/src/utils/Quadtree/Quadtree.d.ts +60 -0
- package/src/utils/Quadtree/Rectangle.d.ts +40 -0
- package/src/utils/drawLinePathToPng.d.ts +7 -0
- package/src/utils/selectLocalFile.d.ts +7 -0
- package/src/index.d.ts +0 -525
- package/src/plugins/ModelDataPlugin/index.d.ts +0 -596
- package/src/plugins/ModelDataPlugin/index.js +0 -498
- package/src/plugins/RenderPlugin/index.d.ts +0 -762
- package/src/plugins/RenderPlugin/index.js +0 -19348
package/src/build.js
ADDED
|
@@ -0,0 +1,2023 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { EventDispatcher as EventDispatcher$1 } from "three";
|
|
3
|
+
import ClipperLib from "clipper-lib";
|
|
4
|
+
import Drawing from "dxf-writer";
|
|
5
|
+
import { OBJExporter } from "three/examples/jsm/exporters/OBJExporter.js";
|
|
6
|
+
function uuid() {
|
|
7
|
+
return "xxxx-xxxx-4xxx-yxxx-xxxx".replace(/[xy]/g, function(c) {
|
|
8
|
+
var r = Math.random() * 16 | 0, v = c == "x" ? r : r & 3 | 8;
|
|
9
|
+
return v.toString(16);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
class EventDispatcher extends EventDispatcher$1 {
|
|
13
|
+
uuid = uuid();
|
|
14
|
+
}
|
|
15
|
+
class Component extends EventDispatcher {
|
|
16
|
+
parent;
|
|
17
|
+
constructor(...arg) {
|
|
18
|
+
super();
|
|
19
|
+
this.addEventListener("addFromParent", (e) => {
|
|
20
|
+
this.parent = e.parent;
|
|
21
|
+
this.onAddFromParent(e.parent);
|
|
22
|
+
});
|
|
23
|
+
this.addEventListener("removeFromParent", (e) => {
|
|
24
|
+
this.parent = void 0;
|
|
25
|
+
this.onRemoveFromParent(e.parent);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
onAddFromParent(parent) {
|
|
29
|
+
}
|
|
30
|
+
onRemoveFromParent(parent) {
|
|
31
|
+
}
|
|
32
|
+
destroy() {
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
class ComponentManager extends EventDispatcher {
|
|
36
|
+
static EventType = {
|
|
37
|
+
ADD_COMPONENT: "addComponent"
|
|
38
|
+
};
|
|
39
|
+
components = [];
|
|
40
|
+
/**
|
|
41
|
+
* 添加组件
|
|
42
|
+
* @param component
|
|
43
|
+
*/
|
|
44
|
+
addComponent(component) {
|
|
45
|
+
if (component) {
|
|
46
|
+
this.components.push(component);
|
|
47
|
+
this.dispatchEvent({
|
|
48
|
+
type: "addComponent",
|
|
49
|
+
component
|
|
50
|
+
});
|
|
51
|
+
component.dispatchEvent({
|
|
52
|
+
type: "addFromParent",
|
|
53
|
+
parent: this
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 移除组件
|
|
60
|
+
* @param component
|
|
61
|
+
*/
|
|
62
|
+
removeComponent(component) {
|
|
63
|
+
if (component instanceof Component) {
|
|
64
|
+
const index2 = this.components.indexOf(component);
|
|
65
|
+
if (index2 > -1) {
|
|
66
|
+
this.components.splice(index2, 1);
|
|
67
|
+
this.dispatchEvent({
|
|
68
|
+
type: "removeComponent",
|
|
69
|
+
component
|
|
70
|
+
});
|
|
71
|
+
component.dispatchEvent({
|
|
72
|
+
type: "removeFromParent",
|
|
73
|
+
parent: this
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 查找符合条件的第一个组件
|
|
80
|
+
* @param callBack
|
|
81
|
+
*/
|
|
82
|
+
findComponent(predicate) {
|
|
83
|
+
for (let i = 0; i < this.components.length; i++) {
|
|
84
|
+
const component = this.components[i];
|
|
85
|
+
if (predicate(component, i)) return component;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 查找所有符合条件的组件
|
|
90
|
+
* @param callBack
|
|
91
|
+
*/
|
|
92
|
+
findComponents(predicate) {
|
|
93
|
+
return this.components.find(predicate);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
*
|
|
97
|
+
* @param type
|
|
98
|
+
*/
|
|
99
|
+
findComponentByType(type) {
|
|
100
|
+
const component = this.findComponent((c) => c instanceof type);
|
|
101
|
+
return component ? component : null;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
*
|
|
105
|
+
* @param type
|
|
106
|
+
*/
|
|
107
|
+
findComponentByName(name) {
|
|
108
|
+
const component = this.findComponent((c) => c.constructor.name === name);
|
|
109
|
+
return component ? component : null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
class Point {
|
|
113
|
+
x;
|
|
114
|
+
y;
|
|
115
|
+
get X() {
|
|
116
|
+
return this.x;
|
|
117
|
+
}
|
|
118
|
+
get Y() {
|
|
119
|
+
return this.y;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
*
|
|
123
|
+
* @param x
|
|
124
|
+
* @param y
|
|
125
|
+
*/
|
|
126
|
+
constructor(x = 0, y = 0) {
|
|
127
|
+
this.x = x;
|
|
128
|
+
this.y = y;
|
|
129
|
+
}
|
|
130
|
+
set(x, y) {
|
|
131
|
+
this.x = x;
|
|
132
|
+
this.y = y;
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
setX(x) {
|
|
136
|
+
this.x = x;
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
setY(y) {
|
|
140
|
+
this.y = y;
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
*
|
|
145
|
+
* @param point
|
|
146
|
+
* @returns
|
|
147
|
+
*/
|
|
148
|
+
equal(point) {
|
|
149
|
+
return point.x === this.x && point.y === this.y;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
*
|
|
153
|
+
* @param arr
|
|
154
|
+
*/
|
|
155
|
+
setByArray(arr) {
|
|
156
|
+
this.x = arr[0];
|
|
157
|
+
this.y = arr[1];
|
|
158
|
+
return this;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
*
|
|
162
|
+
*/
|
|
163
|
+
toArray() {
|
|
164
|
+
return [this.x, this.y];
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* multiplyScalar
|
|
168
|
+
* @param scalar
|
|
169
|
+
*/
|
|
170
|
+
mutiplyScalar(scalar) {
|
|
171
|
+
this.x *= scalar;
|
|
172
|
+
this.y *= scalar;
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
multiplyScalar(scalar) {
|
|
176
|
+
this.x *= scalar;
|
|
177
|
+
this.y *= scalar;
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
*
|
|
182
|
+
* @param scalar
|
|
183
|
+
* @returns
|
|
184
|
+
*/
|
|
185
|
+
divisionScalar(scalar) {
|
|
186
|
+
this.x /= scalar;
|
|
187
|
+
this.y /= scalar;
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* 减法
|
|
192
|
+
* @description 将当前点的坐标减去指定点的坐标
|
|
193
|
+
* @param point
|
|
194
|
+
* @returns
|
|
195
|
+
*/
|
|
196
|
+
division(point) {
|
|
197
|
+
this.x -= point.x;
|
|
198
|
+
this.y -= point.y;
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 加法
|
|
203
|
+
* @description 将当前点的坐标加上指定点的坐标
|
|
204
|
+
* @param point
|
|
205
|
+
* @returns
|
|
206
|
+
*/
|
|
207
|
+
add(point) {
|
|
208
|
+
this.x += point.x;
|
|
209
|
+
this.y += point.y;
|
|
210
|
+
return this;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* 保留小数位数
|
|
214
|
+
* @param count
|
|
215
|
+
*/
|
|
216
|
+
fixed(count) {
|
|
217
|
+
this.x = Number(this.x.toFixed(count));
|
|
218
|
+
this.y = Number(this.y.toFixed(count));
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* 归一化
|
|
223
|
+
* @description 将当前点的坐标归一化为单位向量
|
|
224
|
+
* @returns
|
|
225
|
+
*/
|
|
226
|
+
normalize() {
|
|
227
|
+
const length = Math.sqrt(this.x * this.x + this.y * this.y);
|
|
228
|
+
if (length === 0) return this;
|
|
229
|
+
this.x /= length;
|
|
230
|
+
this.y /= length;
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* 获取单位法向量
|
|
235
|
+
* @description 计算当前点到指定点的方向向量,并返回逆时针旋转90度后的单位法向量
|
|
236
|
+
* @param point
|
|
237
|
+
* @returns
|
|
238
|
+
*/
|
|
239
|
+
normal(point) {
|
|
240
|
+
const dx = this.x - point.x;
|
|
241
|
+
const dy = this.y - point.y;
|
|
242
|
+
const length = Math.sqrt(dx * dx + dy * dy);
|
|
243
|
+
const nx = -dy / length;
|
|
244
|
+
const ny = dx / length;
|
|
245
|
+
return new Point(nx, ny);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* 获取由传入的点到该点的单位方向向量
|
|
249
|
+
* @description 计算当前点到指定点的方向向量,并返回单位方向
|
|
250
|
+
* @param point
|
|
251
|
+
* @returns
|
|
252
|
+
*/
|
|
253
|
+
direction(point) {
|
|
254
|
+
const dx = this.x - point.x;
|
|
255
|
+
const dy = this.y - point.y;
|
|
256
|
+
const length = Math.sqrt(dx * dx + dy * dy);
|
|
257
|
+
if (length === 0) return new Point(0, 0);
|
|
258
|
+
return new Point(dx / length, dy / length);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* 计算模长
|
|
262
|
+
* @returns
|
|
263
|
+
*/
|
|
264
|
+
magnitude() {
|
|
265
|
+
return Math.sqrt(this.x * this.x + this.y * this.y);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* 计算点点积
|
|
269
|
+
* @param point
|
|
270
|
+
* @returns
|
|
271
|
+
*/
|
|
272
|
+
dot(point) {
|
|
273
|
+
return this.x * point.x + this.y * point.y;
|
|
274
|
+
}
|
|
275
|
+
/** 计算两个向量夹角
|
|
276
|
+
* @description 公式:a · b = |a| × |b| × cosθ
|
|
277
|
+
* @param point
|
|
278
|
+
* @returns
|
|
279
|
+
*/
|
|
280
|
+
angleBetween(point) {
|
|
281
|
+
const dotProduct = this.dot(point);
|
|
282
|
+
const magnitude1 = this.magnitude();
|
|
283
|
+
const magnitude2 = point.magnitude();
|
|
284
|
+
if (magnitude1 === 0 || magnitude2 === 0) return 0;
|
|
285
|
+
const cosTheta = dotProduct / (magnitude1 * magnitude2);
|
|
286
|
+
const clampedCosTheta = Math.max(-1, Math.min(1, cosTheta));
|
|
287
|
+
return Math.acos(clampedCosTheta);
|
|
288
|
+
}
|
|
289
|
+
/** 获取向量长度
|
|
290
|
+
*/
|
|
291
|
+
length() {
|
|
292
|
+
const magnitude = Math.sqrt(this.x * this.x + this.y * this.y);
|
|
293
|
+
if (Math.abs(magnitude - Math.round(magnitude)) < 1e-9) {
|
|
294
|
+
return Math.round(magnitude);
|
|
295
|
+
}
|
|
296
|
+
return magnitude;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* 获取两个点长度
|
|
300
|
+
* @param point
|
|
301
|
+
*/
|
|
302
|
+
distance(point) {
|
|
303
|
+
return Math.sqrt(
|
|
304
|
+
(this.x - point.x) * (this.x - point.x) + (this.y - point.y) * (this.y - point.y)
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* 克隆
|
|
309
|
+
* @returns
|
|
310
|
+
*/
|
|
311
|
+
clone() {
|
|
312
|
+
return new Point(this.x, this.y);
|
|
313
|
+
}
|
|
314
|
+
static from(arr) {
|
|
315
|
+
if (Array.isArray(arr)) {
|
|
316
|
+
return new Point(arr[0], arr[1]);
|
|
317
|
+
} else if ("x" in arr && "y" in arr) {
|
|
318
|
+
return new Point(arr.x, arr.y);
|
|
319
|
+
} else if ("X" in arr && "Y" in arr) {
|
|
320
|
+
return new Point(arr.X, arr.Y);
|
|
321
|
+
}
|
|
322
|
+
return this.zero();
|
|
323
|
+
}
|
|
324
|
+
static zero() {
|
|
325
|
+
return new Point(0, 0);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
class Box2 {
|
|
329
|
+
minX = 0;
|
|
330
|
+
maxX = 0;
|
|
331
|
+
minY = 0;
|
|
332
|
+
maxY = 0;
|
|
333
|
+
get points() {
|
|
334
|
+
return [
|
|
335
|
+
new Point(this.minX, this.minY),
|
|
336
|
+
new Point(this.maxX, this.minY),
|
|
337
|
+
new Point(this.maxX, this.maxY),
|
|
338
|
+
new Point(this.minX, this.maxY)
|
|
339
|
+
];
|
|
340
|
+
}
|
|
341
|
+
get width() {
|
|
342
|
+
return this.maxX - this.minX;
|
|
343
|
+
}
|
|
344
|
+
get height() {
|
|
345
|
+
return this.maxY - this.minY;
|
|
346
|
+
}
|
|
347
|
+
get center() {
|
|
348
|
+
return new Point(
|
|
349
|
+
this.minX + (this.maxX - this.minX) * 0.5,
|
|
350
|
+
this.minY + (this.maxY - this.minY) * 0.5
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
constructor(minX = 0, maxX = 0, minY = 0, maxY = 0) {
|
|
354
|
+
this.minX = minX;
|
|
355
|
+
this.maxX = maxX;
|
|
356
|
+
this.minY = minY;
|
|
357
|
+
this.maxY = maxY;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
*
|
|
361
|
+
* @param z
|
|
362
|
+
* @returns
|
|
363
|
+
*/
|
|
364
|
+
getPaths3D(z = 0) {
|
|
365
|
+
const points = this.points, list = [];
|
|
366
|
+
points.forEach((p, i) => {
|
|
367
|
+
const nextP = points[(i + 1) % points.length];
|
|
368
|
+
list.push(p.x, p.y, z);
|
|
369
|
+
list.push(nextP.x, nextP.y, z);
|
|
370
|
+
});
|
|
371
|
+
return list;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* 判断线段是与包围盒相交
|
|
375
|
+
* @description Liang-Barsky算法的变种
|
|
376
|
+
* @param line
|
|
377
|
+
*/
|
|
378
|
+
intersectLineSegment(line) {
|
|
379
|
+
const p1 = line.points[0];
|
|
380
|
+
const p2 = line.points[1];
|
|
381
|
+
const dx = p2.x - p1.x;
|
|
382
|
+
const dy = p2.y - p1.y;
|
|
383
|
+
if (dx === 0 && dy === 0) {
|
|
384
|
+
return this.minX <= p1.x && p1.x <= this.maxX && this.minY <= p1.y && p1.y <= this.maxY;
|
|
385
|
+
}
|
|
386
|
+
let tNear = Number.NEGATIVE_INFINITY;
|
|
387
|
+
let tFar = Number.POSITIVE_INFINITY;
|
|
388
|
+
if (dx !== 0) {
|
|
389
|
+
const tx1 = (this.minX - p1.x) / dx;
|
|
390
|
+
const tx2 = (this.maxX - p1.x) / dx;
|
|
391
|
+
tNear = Math.max(tNear, Math.min(tx1, tx2));
|
|
392
|
+
tFar = Math.min(tFar, Math.max(tx1, tx2));
|
|
393
|
+
} else if (p1.x < this.minX || p1.x > this.maxX) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
if (dy !== 0) {
|
|
397
|
+
const ty1 = (this.minY - p1.y) / dy;
|
|
398
|
+
const ty2 = (this.maxY - p1.y) / dy;
|
|
399
|
+
tNear = Math.max(tNear, Math.min(ty1, ty2));
|
|
400
|
+
tFar = Math.min(tFar, Math.max(ty1, ty2));
|
|
401
|
+
} else if (p1.y < this.minY || p1.y > this.maxY) {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
return tNear <= tFar && tNear <= 1 && tFar >= 0;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* 判断线段是在包围盒内
|
|
408
|
+
* @param line
|
|
409
|
+
*/
|
|
410
|
+
containsLineSegment(line) {
|
|
411
|
+
const [p1, p2] = line.points;
|
|
412
|
+
return this.minX <= p1.x && p1.x <= this.maxX && this.minY <= p1.y && p1.y <= this.maxY && this.minX <= p2.x && p2.x <= this.maxX && this.minY <= p2.y && p2.y <= this.maxY;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* 判断矩形与包围盒相交
|
|
416
|
+
* @param rectangle
|
|
417
|
+
*/
|
|
418
|
+
intersectRectangle(rectangle) {
|
|
419
|
+
const isPointInBox = (p) => this.minX <= p.x && p.x <= this.maxX && this.minY <= p.y && p.y <= this.maxY;
|
|
420
|
+
const isPointInRect = (point) => {
|
|
421
|
+
let sign = 0;
|
|
422
|
+
for (let i = 0; i < 4; i++) {
|
|
423
|
+
const p1 = rectangle.points[i];
|
|
424
|
+
const p2 = rectangle.points[(i + 1) % 4];
|
|
425
|
+
const edge = { x: p2.x - p1.x, y: p2.y - p1.y };
|
|
426
|
+
const toPoint = { x: point.x - p1.x, y: point.y - p1.y };
|
|
427
|
+
const cross = edge.x * toPoint.y - edge.y * toPoint.x;
|
|
428
|
+
if (cross === 0) {
|
|
429
|
+
const t = edge.x !== 0 ? (point.x - p1.x) / edge.x : (point.y - p1.y) / edge.y;
|
|
430
|
+
if (t >= 0 && t <= 1) return true;
|
|
431
|
+
} else {
|
|
432
|
+
const currentSign = cross > 0 ? 1 : -1;
|
|
433
|
+
if (sign === 0) sign = currentSign;
|
|
434
|
+
if (sign !== currentSign) return false;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return true;
|
|
438
|
+
};
|
|
439
|
+
const doIntersect = (l1p1, l1p2, l2p1, l2p2) => {
|
|
440
|
+
const orientation = (p, q, r) => {
|
|
441
|
+
const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
|
|
442
|
+
if (val === 0) return 0;
|
|
443
|
+
return val > 0 ? 1 : 2;
|
|
444
|
+
};
|
|
445
|
+
const onSegment = (p, q, r) => {
|
|
446
|
+
return Math.min(p.x, r.x) <= q.x && q.x <= Math.max(p.x, r.x) && Math.min(p.y, r.y) <= q.y && q.y <= Math.max(p.y, r.y);
|
|
447
|
+
};
|
|
448
|
+
const o1 = orientation(l1p1, l1p2, l2p1);
|
|
449
|
+
const o2 = orientation(l1p1, l1p2, l2p2);
|
|
450
|
+
const o3 = orientation(l2p1, l2p2, l1p1);
|
|
451
|
+
const o4 = orientation(l2p1, l2p2, l1p2);
|
|
452
|
+
if (o1 !== o2 && o3 !== o4) return true;
|
|
453
|
+
if (o1 === 0 && onSegment(l1p1, l2p1, l1p2)) return true;
|
|
454
|
+
if (o2 === 0 && onSegment(l1p1, l2p2, l1p2)) return true;
|
|
455
|
+
if (o3 === 0 && onSegment(l2p1, l1p1, l2p2)) return true;
|
|
456
|
+
if (o4 === 0 && onSegment(l2p1, l1p2, l2p2)) return true;
|
|
457
|
+
return false;
|
|
458
|
+
};
|
|
459
|
+
const boxPoints = this.points;
|
|
460
|
+
for (let i = 0; i < 4; i++) {
|
|
461
|
+
const bp1 = boxPoints[i];
|
|
462
|
+
const bp2 = boxPoints[(i + 1) % 4];
|
|
463
|
+
for (let j = 0; j < 4; j++) {
|
|
464
|
+
const rp1 = rectangle.points[j];
|
|
465
|
+
const rp2 = rectangle.points[(j + 1) % 4];
|
|
466
|
+
if (doIntersect(bp1, bp2, rp1, rp2)) return true;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
for (let p of rectangle.points) {
|
|
470
|
+
if (isPointInBox(p)) return true;
|
|
471
|
+
}
|
|
472
|
+
for (let p of boxPoints) {
|
|
473
|
+
if (isPointInRect(p)) return true;
|
|
474
|
+
}
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* 判断矩形是在包围盒内
|
|
479
|
+
* @param rectangle
|
|
480
|
+
*/
|
|
481
|
+
containsRectangle(rectangle) {
|
|
482
|
+
return rectangle.points.every(
|
|
483
|
+
(p) => this.minX <= p.x && p.x <= this.maxX && this.minY <= p.y && p.y <= this.maxY
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* 判断包围盒与包围盒相交
|
|
488
|
+
* @param box
|
|
489
|
+
*/
|
|
490
|
+
intersectBox(box) {
|
|
491
|
+
return !(this.maxX < box.minX || this.minX > box.maxX || this.maxY < box.minY || this.minY > box.maxY);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* 判断包围盒是在包围盒内
|
|
495
|
+
* @param box
|
|
496
|
+
*/
|
|
497
|
+
containsBox(box) {
|
|
498
|
+
return this.minX <= box.minX && box.maxX <= this.maxX && this.minY <= box.minY && box.maxY <= this.maxY;
|
|
499
|
+
}
|
|
500
|
+
/** 判断点是在包围盒内
|
|
501
|
+
* @param point
|
|
502
|
+
*/
|
|
503
|
+
containsPoint(point) {
|
|
504
|
+
return point.x >= this.minX && point.x <= this.maxX && point.y >= this.minY && point.y <= this.maxY;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
*
|
|
508
|
+
* @param minX
|
|
509
|
+
* @param minY
|
|
510
|
+
* @param maxX
|
|
511
|
+
* @param maxY
|
|
512
|
+
* @returns
|
|
513
|
+
*/
|
|
514
|
+
set(minX, minY, maxX, maxY) {
|
|
515
|
+
this.minX = minX;
|
|
516
|
+
this.minY = minY;
|
|
517
|
+
this.maxX = maxX;
|
|
518
|
+
this.maxY = maxY;
|
|
519
|
+
return this;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
*
|
|
523
|
+
* @param maxWidth
|
|
524
|
+
* @param maxHeight
|
|
525
|
+
* @param mode
|
|
526
|
+
* @returns
|
|
527
|
+
*/
|
|
528
|
+
scaleSize(maxWidth, maxHeight, mode = "min") {
|
|
529
|
+
const scaleX = maxWidth / this.width;
|
|
530
|
+
const scaleY = maxHeight / this.height;
|
|
531
|
+
return Math[mode](scaleX, scaleY);
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
*
|
|
535
|
+
* @param scalar
|
|
536
|
+
* @returns
|
|
537
|
+
*/
|
|
538
|
+
multiplyScalar(scalar) {
|
|
539
|
+
this.minX *= scalar;
|
|
540
|
+
this.minY *= scalar;
|
|
541
|
+
this.maxX *= scalar;
|
|
542
|
+
this.maxY *= scalar;
|
|
543
|
+
return this;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
*
|
|
547
|
+
* @returns
|
|
548
|
+
*/
|
|
549
|
+
clone() {
|
|
550
|
+
return new Box2(this.minX, this.minY, this.maxX, this.maxY);
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
*
|
|
554
|
+
* @param points
|
|
555
|
+
* @returns
|
|
556
|
+
*/
|
|
557
|
+
static fromByPoints(...points) {
|
|
558
|
+
const xList = [], yList = [];
|
|
559
|
+
points.forEach((p) => {
|
|
560
|
+
xList.push(p.x);
|
|
561
|
+
yList.push(p.y);
|
|
562
|
+
});
|
|
563
|
+
return new Box2(
|
|
564
|
+
Math.min(...xList),
|
|
565
|
+
Math.max(...xList),
|
|
566
|
+
Math.min(...yList),
|
|
567
|
+
Math.max(...yList)
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
class LineSegment {
|
|
572
|
+
points = [new Point(), new Point()];
|
|
573
|
+
userData;
|
|
574
|
+
get center() {
|
|
575
|
+
return new Point(
|
|
576
|
+
this.points[0].x + (this.points[1].x - this.points[0].x) * 0.5,
|
|
577
|
+
this.points[0].y + (this.points[1].y - this.points[0].y) * 0.5
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
get start() {
|
|
581
|
+
return this.points[0];
|
|
582
|
+
}
|
|
583
|
+
get end() {
|
|
584
|
+
return this.points[1];
|
|
585
|
+
}
|
|
586
|
+
constructor(p1 = new Point(), p2 = new Point()) {
|
|
587
|
+
this.points = [p1, p2];
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* 计算线段的长度
|
|
591
|
+
* @returns 线段的长度
|
|
592
|
+
*/
|
|
593
|
+
getLength() {
|
|
594
|
+
return this.points[0].distance(this.points[1]);
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* 获取方向
|
|
598
|
+
* @returns
|
|
599
|
+
*/
|
|
600
|
+
direction() {
|
|
601
|
+
return this.points[1].direction(this.points[0]);
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* 计算一条线段在另一条直线上的投影,并裁剪超出目标线段的部分
|
|
605
|
+
* @param line 要投影的线段
|
|
606
|
+
* @returns 投影并裁剪后的线段
|
|
607
|
+
*/
|
|
608
|
+
projectLineSegment(line) {
|
|
609
|
+
if (line.points.length !== 2 || this.points.length !== 2) {
|
|
610
|
+
throw new Error("每条线段必须由两个点定义");
|
|
611
|
+
}
|
|
612
|
+
const [p1, p2] = line.points;
|
|
613
|
+
const [q1, q2] = this.points;
|
|
614
|
+
const dir = new Point(q2.x - q1.x, q2.y - q1.y);
|
|
615
|
+
if (dir.x === 0 && dir.y === 0) {
|
|
616
|
+
throw new Error("投影目标线段的两个点不能重合");
|
|
617
|
+
}
|
|
618
|
+
const projectPoint = (point) => {
|
|
619
|
+
const pq = new Point(point.x - q1.x, point.y - q1.y);
|
|
620
|
+
const dirLengthSquared = dir.x * dir.x + dir.y * dir.y;
|
|
621
|
+
const dotProduct = pq.x * dir.x + pq.y * dir.y;
|
|
622
|
+
const t = dotProduct / dirLengthSquared;
|
|
623
|
+
const projX = q1.x + t * dir.x;
|
|
624
|
+
const projY = q1.y + t * dir.y;
|
|
625
|
+
return new Point(projX, projY);
|
|
626
|
+
};
|
|
627
|
+
let projP1 = projectPoint(p1);
|
|
628
|
+
let projP2 = projectPoint(p2);
|
|
629
|
+
const getT = (point) => {
|
|
630
|
+
const pq = new Point(point.x - q1.x, point.y - q1.y);
|
|
631
|
+
const dirLengthSquared = dir.x * dir.x + dir.y * dir.y;
|
|
632
|
+
return (pq.x * dir.x + pq.y * dir.y) / dirLengthSquared;
|
|
633
|
+
};
|
|
634
|
+
let t1 = getT(projP1);
|
|
635
|
+
let t2 = getT(projP2);
|
|
636
|
+
const clampPoint = (t) => {
|
|
637
|
+
const clampedT = Math.max(0, Math.min(1, t));
|
|
638
|
+
const x = q1.x + clampedT * dir.x;
|
|
639
|
+
const y = q1.y + clampedT * dir.y;
|
|
640
|
+
return new Point(x, y);
|
|
641
|
+
};
|
|
642
|
+
if (t1 < 0 || t1 > 1) {
|
|
643
|
+
projP1 = clampPoint(t1);
|
|
644
|
+
}
|
|
645
|
+
if (t2 < 0 || t2 > 1) {
|
|
646
|
+
projP2 = clampPoint(t2);
|
|
647
|
+
}
|
|
648
|
+
if (projP1.x === projP2.x && projP1.y === projP2.y) {
|
|
649
|
+
return new LineSegment(projP1, projP1);
|
|
650
|
+
}
|
|
651
|
+
return new LineSegment(projP1, projP2);
|
|
652
|
+
}
|
|
653
|
+
clone() {
|
|
654
|
+
return new LineSegment(
|
|
655
|
+
this.points[0].clone(),
|
|
656
|
+
this.points[1].clone()
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
const units = {
|
|
661
|
+
Unitless: 1,
|
|
662
|
+
// 无单位,1米 = 1(无单位)
|
|
663
|
+
Inches: 39.37007874015748,
|
|
664
|
+
// 英寸,1米 = 39.37007874015748英寸
|
|
665
|
+
Feet: 3.280839895013123,
|
|
666
|
+
// 英尺,1米 = 3.280839895013123英尺
|
|
667
|
+
Miles: 6213711922373339e-19,
|
|
668
|
+
// 英里,1米 = 0.0006213711922373339英里
|
|
669
|
+
Millimeters: 1e3,
|
|
670
|
+
// 毫米,1米 = 1000毫米
|
|
671
|
+
Centimeters: 100,
|
|
672
|
+
// 厘米,1米 = 100厘米
|
|
673
|
+
Meters: 1,
|
|
674
|
+
// 米,1米 = 1米
|
|
675
|
+
Kilometers: 1e-3,
|
|
676
|
+
// 千米,1米 = 0.001千米
|
|
677
|
+
Microinches: 3937007874015748e-8,
|
|
678
|
+
// 微英寸,1米 = 39370078.74015748微英寸
|
|
679
|
+
Mils: 39370.07874015748,
|
|
680
|
+
// 密耳,1米 = 39370.07874015748密耳
|
|
681
|
+
Yards: 1.0936132983377078,
|
|
682
|
+
// 码,1米 = 1.0936132983377078码
|
|
683
|
+
Angstroms: 1e10,
|
|
684
|
+
// 埃,1米 = 10^10埃
|
|
685
|
+
Nanometers: 1e9,
|
|
686
|
+
// 纳米,1米 = 10^9纳米
|
|
687
|
+
Microns: 1e6,
|
|
688
|
+
// 微米,1米 = 10^6微米
|
|
689
|
+
Decimeters: 10,
|
|
690
|
+
// 分米,1米 = 10分米
|
|
691
|
+
Decameters: 0.1,
|
|
692
|
+
// 十米,1米 = 0.1十米
|
|
693
|
+
Hectometers: 0.01,
|
|
694
|
+
// 百米,1米 = 0.01百米
|
|
695
|
+
Gigameters: 1e-9,
|
|
696
|
+
// 吉米,1米 = 10^-9吉米
|
|
697
|
+
"Astronomical units": 6684587122268445e-27,
|
|
698
|
+
// 天文单位,1米 = 0.000000000006684587122268445天文单位
|
|
699
|
+
"Light years": 10570008340246154e-32,
|
|
700
|
+
// 光年,1米 ≈ 0.00000000000000010570008340246154光年
|
|
701
|
+
Parsecs: 3240779289666404e-32
|
|
702
|
+
// 秒差距,1米 ≈ 0.00000000000000003240779289666404秒差距
|
|
703
|
+
};
|
|
704
|
+
class Dxf extends Component {
|
|
705
|
+
static name = "Dxf";
|
|
706
|
+
width = 0.04;
|
|
707
|
+
scale = 1;
|
|
708
|
+
originalData = [];
|
|
709
|
+
data = [];
|
|
710
|
+
originalBox = new Box2(0, 0, 0, 0);
|
|
711
|
+
box = new Box2(0, 0, 0, 0);
|
|
712
|
+
pointsGroups = [];
|
|
713
|
+
wallsGroup = [];
|
|
714
|
+
doors = [];
|
|
715
|
+
lineSegments = [];
|
|
716
|
+
originalZAverage = 0;
|
|
717
|
+
static EndType = {
|
|
718
|
+
etOpenSquare: 0,
|
|
719
|
+
etOpenRound: 1,
|
|
720
|
+
etOpenButt: 2
|
|
721
|
+
// etClosedLine: 3,
|
|
722
|
+
// etClosedPolygon: 4
|
|
723
|
+
};
|
|
724
|
+
static JoinType = {
|
|
725
|
+
jtSquare: 0,
|
|
726
|
+
jtRound: 1,
|
|
727
|
+
jtMiter: 2
|
|
728
|
+
};
|
|
729
|
+
/** 原始数据组
|
|
730
|
+
*/
|
|
731
|
+
get lines() {
|
|
732
|
+
return this.lineSegments;
|
|
733
|
+
}
|
|
734
|
+
/**初始化
|
|
735
|
+
* @param data 点云数据
|
|
736
|
+
* @param width 墙体宽度
|
|
737
|
+
* @param scale 缩放比例
|
|
738
|
+
*/
|
|
739
|
+
constructor(width = 0.04, scale = 1) {
|
|
740
|
+
super();
|
|
741
|
+
this.width = width;
|
|
742
|
+
this.scale = scale;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* 设置
|
|
746
|
+
* @param data
|
|
747
|
+
* @param width
|
|
748
|
+
* @param scale
|
|
749
|
+
*/
|
|
750
|
+
async set(data, width = this.width, scale = this.scale) {
|
|
751
|
+
if (typeof data === "string") {
|
|
752
|
+
if (typeof global !== "undefined") {
|
|
753
|
+
const packageName = "fs";
|
|
754
|
+
const { default: fs } = await import(
|
|
755
|
+
/* @vite-ignore */
|
|
756
|
+
packageName
|
|
757
|
+
);
|
|
758
|
+
const buffer = fs.readFileSync(data);
|
|
759
|
+
const json = JSON.parse(buffer.toString("utf-8"));
|
|
760
|
+
return this.set(json, width, scale);
|
|
761
|
+
} else {
|
|
762
|
+
throw new Error("非node环境不允许使用路径");
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
this.scale = scale;
|
|
766
|
+
this.width = width;
|
|
767
|
+
this.originalData = data;
|
|
768
|
+
const zList = [];
|
|
769
|
+
this.data = data.map(({ start, end, insetionArr, isDoor = false }, index2) => {
|
|
770
|
+
zList.push(start.z ?? 0, end.z ?? 0);
|
|
771
|
+
const lineSegment = new LineSegment(
|
|
772
|
+
Point.from(start).mutiplyScalar(scale),
|
|
773
|
+
Point.from(end).mutiplyScalar(scale)
|
|
774
|
+
);
|
|
775
|
+
lineSegment.userData = { isDoor };
|
|
776
|
+
this.lineSegments.push(lineSegment);
|
|
777
|
+
return [
|
|
778
|
+
lineSegment.points[0],
|
|
779
|
+
lineSegment.points[1],
|
|
780
|
+
(insetionArr ?? []).map((i) => i.index),
|
|
781
|
+
isDoor,
|
|
782
|
+
index2
|
|
783
|
+
];
|
|
784
|
+
});
|
|
785
|
+
this.originalZAverage = zList.reduce((count, num) => count + num, 0) / zList.length;
|
|
786
|
+
this.computedOriginalSize(data, this.originalBox);
|
|
787
|
+
this.dispatchEvent({
|
|
788
|
+
type: "setDta",
|
|
789
|
+
originalData: this.originalData,
|
|
790
|
+
data: this.data
|
|
791
|
+
});
|
|
792
|
+
this.createGroups();
|
|
793
|
+
this.computedSize();
|
|
794
|
+
this.dispatchEvent({
|
|
795
|
+
type: "createGroup",
|
|
796
|
+
groups: this.pointsGroups
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
/** 创建分组
|
|
800
|
+
* @description 根据相交数组insetionArr, 把相交的线段,划分到一组内,方便后续路径查找合并使用
|
|
801
|
+
* @returns
|
|
802
|
+
*/
|
|
803
|
+
createGroups() {
|
|
804
|
+
const groups = [], visited = /* @__PURE__ */ new Set(), doorSet = /* @__PURE__ */ new Set(), doorVisitedRecord = /* @__PURE__ */ new Map();
|
|
805
|
+
const dfs = (index2, group, preIndex = -1) => {
|
|
806
|
+
const [start, end, insetionArr, isDoor] = this.data[index2];
|
|
807
|
+
visited.add(index2);
|
|
808
|
+
if (isDoor) {
|
|
809
|
+
if (!doorVisitedRecord.has(index2)) doorVisitedRecord.set(index2, []);
|
|
810
|
+
doorVisitedRecord.get(index2)?.push(preIndex);
|
|
811
|
+
return doorSet.add(this.data[index2]);
|
|
812
|
+
}
|
|
813
|
+
group.push([start, end]);
|
|
814
|
+
insetionArr.forEach((i) => {
|
|
815
|
+
if (!visited.has(i)) {
|
|
816
|
+
dfs(i, group, index2);
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
};
|
|
820
|
+
this.data.forEach((_, index2) => {
|
|
821
|
+
if (!visited.has(index2)) {
|
|
822
|
+
const group = [];
|
|
823
|
+
dfs(index2, group);
|
|
824
|
+
groups.push(group);
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
this.doors = [...doorSet];
|
|
828
|
+
this.pointsGroups = groups;
|
|
829
|
+
return groups;
|
|
830
|
+
}
|
|
831
|
+
/** 计算当前墙体数据的边界框
|
|
832
|
+
* @description 根据分组数据pointsGroups,计算包围盒, pointsGroups数据为缩放后数据。
|
|
833
|
+
* @description 可通过box属性查看计算结果。
|
|
834
|
+
* @returns
|
|
835
|
+
*/
|
|
836
|
+
computedSize() {
|
|
837
|
+
const xArr = this.pointsGroups.flatMap((points) => points.flatMap((p) => [p[0].x, p[1].x]));
|
|
838
|
+
const yArr = this.pointsGroups.flatMap((points) => points.flatMap((p) => [p[0].y, p[1].y]));
|
|
839
|
+
const minX = Math.min(...xArr);
|
|
840
|
+
const minY = Math.min(...yArr);
|
|
841
|
+
const maxX = Math.max(...xArr);
|
|
842
|
+
const maxY = Math.max(...yArr);
|
|
843
|
+
this.box.set(minX, minY, maxX, maxY);
|
|
844
|
+
return this.box;
|
|
845
|
+
}
|
|
846
|
+
/** 线路拓扑
|
|
847
|
+
* @description 处理线路拓扑,使线路有序链接,形成长路径
|
|
848
|
+
* @param lines
|
|
849
|
+
*/
|
|
850
|
+
lineTopology(lines) {
|
|
851
|
+
const visited = [];
|
|
852
|
+
function dfs(index2, linePath) {
|
|
853
|
+
const [_0, a2] = lines[index2];
|
|
854
|
+
visited[index2] = true;
|
|
855
|
+
linePath.push(a2);
|
|
856
|
+
for (let i = 0; i < lines.length; i++) {
|
|
857
|
+
const [b1, _1] = lines[i];
|
|
858
|
+
if (!visited[i]) {
|
|
859
|
+
if (Math.abs(a2.x - b1.x) < 1e-6 && Math.abs(a2.y - b1.y) < 1e-6) {
|
|
860
|
+
return dfs(i, linePath);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
const linePaths = [];
|
|
866
|
+
for (let i = 0; i < lines.length; i++) {
|
|
867
|
+
if (!visited[i]) {
|
|
868
|
+
const linePath = [lines[i][0]];
|
|
869
|
+
dfs(i, linePath);
|
|
870
|
+
linePaths.push(linePath);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
return linePaths;
|
|
874
|
+
}
|
|
875
|
+
/** etOpenRound 去除毛刺
|
|
876
|
+
* @description 检查连续的短线段数量,去除合并后产生的毛刺
|
|
877
|
+
*/
|
|
878
|
+
squareRemoveBurr() {
|
|
879
|
+
this.wallsGroup = this.wallsGroup.map((lines) => {
|
|
880
|
+
if (lines.length < 3) return lines;
|
|
881
|
+
const filterLines = [lines[0]];
|
|
882
|
+
for (let i = 1; i < lines.length; i++) {
|
|
883
|
+
const prev = lines[i - 1];
|
|
884
|
+
const curr = lines[i];
|
|
885
|
+
const len = prev.distance(curr);
|
|
886
|
+
if (len < this.width * 0.5) {
|
|
887
|
+
let count = 0;
|
|
888
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
889
|
+
const prev2 = lines[j - 1];
|
|
890
|
+
const curr2 = lines[j];
|
|
891
|
+
const len2 = prev2.distance(curr2);
|
|
892
|
+
if (len2 < this.width * 0.8) count++;
|
|
893
|
+
else break;
|
|
894
|
+
}
|
|
895
|
+
if (count === 0 && i + count === lines.length - 1) ;
|
|
896
|
+
else if (i == 1 && count === 1) ;
|
|
897
|
+
else if (count === 3) {
|
|
898
|
+
filterLines.push(lines[i + 1]);
|
|
899
|
+
i += count;
|
|
900
|
+
} else if (count === 5) {
|
|
901
|
+
filterLines.push(lines[i + 2]);
|
|
902
|
+
i += count;
|
|
903
|
+
} else {
|
|
904
|
+
filterLines.push(curr);
|
|
905
|
+
}
|
|
906
|
+
} else {
|
|
907
|
+
filterLines.push(curr);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
return filterLines;
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
/** 线偏移
|
|
914
|
+
* @description 使用 ClipperLib 对每个点组进行线偏移处理,生成具有指定宽度的墙体路径
|
|
915
|
+
*/
|
|
916
|
+
lineOffset(endType = Dxf.EndType.etOpenSquare, joinType = Dxf.JoinType.jtMiter, scale = 1e4) {
|
|
917
|
+
const solutions = new ClipperLib.Paths();
|
|
918
|
+
const offset = new ClipperLib.ClipperOffset(20, 0.25);
|
|
919
|
+
this.pointsGroups.forEach((points) => {
|
|
920
|
+
const linePaths = this.lineTopology(points).map((linePath) => linePath.map((p) => p.clone().mutiplyScalar(scale)));
|
|
921
|
+
offset.AddPaths(linePaths, joinType, endType);
|
|
922
|
+
});
|
|
923
|
+
offset.Execute(solutions, this.width / 2 * scale);
|
|
924
|
+
offset.Execute(solutions, this.width / 2 * scale);
|
|
925
|
+
this.wallsGroup = solutions.map((ps) => ps.map((p) => Point.from(p).divisionScalar(scale)));
|
|
926
|
+
if (endType == Dxf.EndType.etOpenSquare) this.squareRemoveBurr();
|
|
927
|
+
this.dispatchEvent({
|
|
928
|
+
type: "lineOffset",
|
|
929
|
+
wallsGroup: this.wallsGroup
|
|
930
|
+
});
|
|
931
|
+
return this.wallsGroup;
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* 将点云结构转换为Float32Array
|
|
935
|
+
*/
|
|
936
|
+
to3DArray(scale) {
|
|
937
|
+
const array = [];
|
|
938
|
+
this.wallsGroup.forEach((points) => {
|
|
939
|
+
for (let i = 0; i < points.length; i++) {
|
|
940
|
+
const point1 = points[i];
|
|
941
|
+
const nextIndex = i === points.length - 1 ? 0 : i + 1;
|
|
942
|
+
const point2 = points[nextIndex];
|
|
943
|
+
array.push(point1.X * scale, point1.Y * scale, this.originalZAverage, point2.X * scale, point2.Y * scale, this.originalZAverage);
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
return new Float32Array(array);
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* 将点云结构转换为string
|
|
950
|
+
*/
|
|
951
|
+
toDxfString(unit = "Millimeters") {
|
|
952
|
+
const d = new Drawing();
|
|
953
|
+
d.setUnits("Millimeters");
|
|
954
|
+
const s = units[unit];
|
|
955
|
+
this.wallsGroup.forEach((points) => {
|
|
956
|
+
for (let i = 0; i < points.length; i++) {
|
|
957
|
+
const point1 = points[i];
|
|
958
|
+
const nextIndex = i === points.length - 1 ? 0 : i + 1;
|
|
959
|
+
const point2 = points[nextIndex];
|
|
960
|
+
d.drawLine(point1.X * s, point1.Y * s, point2.X * s, point2.Y * s);
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
return d.toDxfString();
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* 将点云结构转换为DXF格式
|
|
967
|
+
* @returns
|
|
968
|
+
*/
|
|
969
|
+
toDxfBlob(unit = "Millimeters") {
|
|
970
|
+
const blob = new Blob([this.toDxfString(unit)]);
|
|
971
|
+
return blob;
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* 下载
|
|
975
|
+
* @param filename
|
|
976
|
+
*/
|
|
977
|
+
async download(filename, unit = "Millimeters") {
|
|
978
|
+
if (typeof window !== "undefined") {
|
|
979
|
+
const blob = this.toDxfBlob(unit);
|
|
980
|
+
const a = document.createElement("a");
|
|
981
|
+
a.href = URL.createObjectURL(blob);
|
|
982
|
+
a.download = filename + ".dxf";
|
|
983
|
+
a.click();
|
|
984
|
+
} else if (typeof global !== "undefined") {
|
|
985
|
+
const packageName = "fs";
|
|
986
|
+
const { default: fs } = await import(
|
|
987
|
+
/* @vite-ignore */
|
|
988
|
+
packageName
|
|
989
|
+
);
|
|
990
|
+
fs.writeFileSync(filename, this.toDxfString(unit));
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* 计算原始数据的边界框
|
|
995
|
+
* @description 计算所有线段的起点和终点的最小最大值,形成一个边界框
|
|
996
|
+
* @returns
|
|
997
|
+
*/
|
|
998
|
+
computedOriginalSize(data, originalBox = new Box2(0, 0, 0, 0)) {
|
|
999
|
+
const xArr = data.flatMap((item) => [item.start.x, item.end.x]);
|
|
1000
|
+
const yArr = data.flatMap((item) => [item.start.y, item.end.y]);
|
|
1001
|
+
const minX = Math.min(...xArr);
|
|
1002
|
+
const minY = Math.min(...yArr);
|
|
1003
|
+
const maxX = Math.max(...xArr);
|
|
1004
|
+
const maxY = Math.max(...yArr);
|
|
1005
|
+
originalBox.set(minX, minY, maxX, maxY);
|
|
1006
|
+
return originalBox;
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* 创建数据
|
|
1010
|
+
* @param pointsGroups
|
|
1011
|
+
* @returns
|
|
1012
|
+
*/
|
|
1013
|
+
static createData(pointsGroups, sealed = true) {
|
|
1014
|
+
let count = 0;
|
|
1015
|
+
const data = pointsGroups.flatMap((points) => {
|
|
1016
|
+
const lines = points.map((point, index2) => {
|
|
1017
|
+
const nextIndex = index2 === points.length - 1 ? 0 : index2 + 1;
|
|
1018
|
+
const nextPoint = points[nextIndex];
|
|
1019
|
+
return {
|
|
1020
|
+
start: { x: point.x, y: point.y },
|
|
1021
|
+
end: { x: nextPoint.x, y: nextPoint.y },
|
|
1022
|
+
insetionArr: [
|
|
1023
|
+
{
|
|
1024
|
+
index: nextIndex + count
|
|
1025
|
+
}
|
|
1026
|
+
]
|
|
1027
|
+
};
|
|
1028
|
+
});
|
|
1029
|
+
count += points.length;
|
|
1030
|
+
if (!sealed) {
|
|
1031
|
+
lines.pop();
|
|
1032
|
+
lines[lines.length - 1].insetionArr.length = 0;
|
|
1033
|
+
count--;
|
|
1034
|
+
}
|
|
1035
|
+
return lines;
|
|
1036
|
+
});
|
|
1037
|
+
return data;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
class Variable extends Component {
|
|
1041
|
+
static name = "Variable";
|
|
1042
|
+
originalLineVisible = true;
|
|
1043
|
+
dxfVisible = true;
|
|
1044
|
+
whiteModelVisible = true;
|
|
1045
|
+
isLook = false;
|
|
1046
|
+
currentWheel = 0;
|
|
1047
|
+
pointerMove = { x: 0, y: 0 };
|
|
1048
|
+
currentKeyUp = "";
|
|
1049
|
+
set(key, value) {
|
|
1050
|
+
if (key in this) {
|
|
1051
|
+
const oldValue = this[key];
|
|
1052
|
+
this[key] = value;
|
|
1053
|
+
this.dispatchEvent({
|
|
1054
|
+
type: key,
|
|
1055
|
+
value,
|
|
1056
|
+
oldValue
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
get(key) {
|
|
1061
|
+
if (key in this) return this[key];
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
class Rectangle {
|
|
1065
|
+
points;
|
|
1066
|
+
get path() {
|
|
1067
|
+
return this.points.flatMap((p, i) => {
|
|
1068
|
+
const np = this.points[(i + 1) % this.points.length];
|
|
1069
|
+
return [p.x, p.y, 0, np.x, np.y, 0];
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
constructor(points) {
|
|
1073
|
+
if (points.length !== 4) {
|
|
1074
|
+
throw new Error("Rectangle must be defined by exactly 4 points");
|
|
1075
|
+
}
|
|
1076
|
+
this.points = points;
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* 判断线段是否与矩形相交
|
|
1080
|
+
* @param line 线段
|
|
1081
|
+
* @returns 是否与矩形相交
|
|
1082
|
+
*/
|
|
1083
|
+
intersectLineSegment(line) {
|
|
1084
|
+
if (line.points.length !== 2) {
|
|
1085
|
+
throw new Error("LineSegment must have exactly 2 points");
|
|
1086
|
+
}
|
|
1087
|
+
const [p1, p2] = line.points;
|
|
1088
|
+
const doIntersect = (l1p1, l1p2, l2p1, l2p2) => {
|
|
1089
|
+
const orientation = (p, q, r) => {
|
|
1090
|
+
const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
|
|
1091
|
+
if (val === 0) return 0;
|
|
1092
|
+
return val > 0 ? 1 : 2;
|
|
1093
|
+
};
|
|
1094
|
+
const onSegment = (p, q, r) => {
|
|
1095
|
+
return Math.min(p.x, r.x) <= q.x && q.x <= Math.max(p.x, r.x) && Math.min(p.y, r.y) <= q.y && q.y <= Math.max(p.y, r.y);
|
|
1096
|
+
};
|
|
1097
|
+
const o1 = orientation(l1p1, l1p2, l2p1);
|
|
1098
|
+
const o2 = orientation(l1p1, l1p2, l2p2);
|
|
1099
|
+
const o3 = orientation(l2p1, l2p2, l1p1);
|
|
1100
|
+
const o4 = orientation(l2p1, l2p2, l1p2);
|
|
1101
|
+
if (o1 !== o2 && o3 !== o4) return true;
|
|
1102
|
+
if (o1 === 0 && onSegment(l1p1, l2p1, l1p2)) return true;
|
|
1103
|
+
if (o2 === 0 && onSegment(l1p1, l2p2, l1p2)) return true;
|
|
1104
|
+
if (o3 === 0 && onSegment(l2p1, l1p1, l2p2)) return true;
|
|
1105
|
+
if (o4 === 0 && onSegment(l2p1, l1p2, l2p2)) return true;
|
|
1106
|
+
return false;
|
|
1107
|
+
};
|
|
1108
|
+
for (let i = 0; i < 4; i++) {
|
|
1109
|
+
const rectP1 = this.points[i];
|
|
1110
|
+
const rectP2 = this.points[(i + 1) % 4];
|
|
1111
|
+
if (doIntersect(p1, p2, rectP1, rectP2)) {
|
|
1112
|
+
return true;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
if (this.containsLineSegment(line)) {
|
|
1116
|
+
return true;
|
|
1117
|
+
}
|
|
1118
|
+
return false;
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* 判断线段是否完全位于矩形内部
|
|
1122
|
+
* @param line 线段
|
|
1123
|
+
* @returns 是否完全在矩形内部
|
|
1124
|
+
*/
|
|
1125
|
+
containsLineSegment(line) {
|
|
1126
|
+
if (line.points.length !== 2) {
|
|
1127
|
+
throw new Error("LineSegment must have exactly 2 points");
|
|
1128
|
+
}
|
|
1129
|
+
const isPointInRectangle = (point) => {
|
|
1130
|
+
let sign = 0;
|
|
1131
|
+
for (let i = 0; i < 4; i++) {
|
|
1132
|
+
const p1 = this.points[i];
|
|
1133
|
+
const p2 = this.points[(i + 1) % 4];
|
|
1134
|
+
const edge = { x: p2.x - p1.x, y: p2.y - p1.y };
|
|
1135
|
+
const toPoint = { x: point.x - p1.x, y: point.y - p1.y };
|
|
1136
|
+
const cross = edge.x * toPoint.y - edge.y * toPoint.x;
|
|
1137
|
+
if (cross === 0) {
|
|
1138
|
+
const t = edge.x !== 0 ? (point.x - p1.x) / edge.x : (point.y - p1.y) / edge.y;
|
|
1139
|
+
if (t >= 0 && t <= 1) return true;
|
|
1140
|
+
} else {
|
|
1141
|
+
const currentSign = cross > 0 ? 1 : -1;
|
|
1142
|
+
if (sign === 0) sign = currentSign;
|
|
1143
|
+
if (sign !== currentSign) return false;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return true;
|
|
1147
|
+
};
|
|
1148
|
+
return isPointInRectangle(line.points[0]) && isPointInRectangle(line.points[1]);
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* 判断点是否完全位于矩形内部
|
|
1152
|
+
* @param point
|
|
1153
|
+
*/
|
|
1154
|
+
containsPoint(point) {
|
|
1155
|
+
let positiveCount = 0;
|
|
1156
|
+
let negativeCount = 0;
|
|
1157
|
+
for (let i = 0; i < 4; i++) {
|
|
1158
|
+
const p1 = this.points[i];
|
|
1159
|
+
const p2 = this.points[(i + 1) % 4];
|
|
1160
|
+
const cross = (p2.x - p1.x) * (point.y - p1.y) - (p2.y - p1.y) * (point.x - p1.x);
|
|
1161
|
+
if (cross > 0) positiveCount++;
|
|
1162
|
+
else if (cross < 0) negativeCount++;
|
|
1163
|
+
else return false;
|
|
1164
|
+
}
|
|
1165
|
+
return positiveCount === 4 || negativeCount === 4;
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
*
|
|
1169
|
+
* @returns
|
|
1170
|
+
*/
|
|
1171
|
+
toBox() {
|
|
1172
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
1173
|
+
this.points.forEach((p) => {
|
|
1174
|
+
maxX = Math.max(p.x, maxX);
|
|
1175
|
+
minX = Math.min(p.x, minX);
|
|
1176
|
+
maxY = Math.max(p.x, maxY);
|
|
1177
|
+
minY = Math.min(p.x, minY);
|
|
1178
|
+
});
|
|
1179
|
+
return new Box2(minX, maxX, minY, maxY);
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
*
|
|
1183
|
+
* @param line
|
|
1184
|
+
* @param width
|
|
1185
|
+
* @returns
|
|
1186
|
+
*/
|
|
1187
|
+
static fromByLineSegment(line, width = 0.1, horizontal = false, hScale = 0.5) {
|
|
1188
|
+
const p1 = line.points[0], p2 = line.points[1], normal = p2.normal(p1), pDirect = horizontal ? p2.direction(p1).mutiplyScalar(width * hScale) : Point.zero(), nDirect = horizontal ? p1.direction(p2).mutiplyScalar(width * hScale) : Point.zero();
|
|
1189
|
+
const offsetX = normal.x * width * 0.5;
|
|
1190
|
+
const offsetY = normal.y * width * 0.5;
|
|
1191
|
+
return new Rectangle([
|
|
1192
|
+
new Point(p1.x + offsetX, p1.y + offsetY).add(nDirect),
|
|
1193
|
+
new Point(p2.x + offsetX, p2.y + offsetY).add(pDirect),
|
|
1194
|
+
new Point(p2.x - offsetX, p2.y - offsetY).add(pDirect),
|
|
1195
|
+
new Point(p1.x - offsetX, p1.y - offsetY).add(nDirect)
|
|
1196
|
+
]);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
class Quadtree {
|
|
1200
|
+
bounds;
|
|
1201
|
+
// 包围盒
|
|
1202
|
+
capacity;
|
|
1203
|
+
// 节点容量
|
|
1204
|
+
maxDepth;
|
|
1205
|
+
// 最大深度
|
|
1206
|
+
depth;
|
|
1207
|
+
// 当前深度
|
|
1208
|
+
isLeaf = true;
|
|
1209
|
+
// 是否为叶子节点
|
|
1210
|
+
children = null;
|
|
1211
|
+
// 子节点数组
|
|
1212
|
+
nodes = [];
|
|
1213
|
+
// 存储的节点
|
|
1214
|
+
color = [Math.random(), Math.random(), Math.random()];
|
|
1215
|
+
// 颜色
|
|
1216
|
+
constructor(bounds, capacity = 8, maxDepth = 10, depth = 1) {
|
|
1217
|
+
this.bounds = bounds;
|
|
1218
|
+
this.capacity = capacity;
|
|
1219
|
+
this.depth = depth;
|
|
1220
|
+
this.maxDepth = maxDepth;
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* 插入线段节点
|
|
1224
|
+
* @param node 线段节点
|
|
1225
|
+
*/
|
|
1226
|
+
insert(node) {
|
|
1227
|
+
if (!this.isLeaf) {
|
|
1228
|
+
const quadrant = this.getQuadrant(node.line);
|
|
1229
|
+
if (quadrant !== -1) {
|
|
1230
|
+
this.children[quadrant].insert(node);
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
this.nodes.push(node);
|
|
1235
|
+
if (this.isLeaf && this.nodes.length > this.capacity && this.depth < this.maxDepth) {
|
|
1236
|
+
this.subdivide();
|
|
1237
|
+
const nodes = this.nodes;
|
|
1238
|
+
this.nodes = [];
|
|
1239
|
+
for (const n of nodes) {
|
|
1240
|
+
const quadrant = this.getQuadrant(n.line);
|
|
1241
|
+
if (quadrant !== -1) {
|
|
1242
|
+
this.children[quadrant].insert(n);
|
|
1243
|
+
} else {
|
|
1244
|
+
this.nodes.push(n);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* 获取线段所属的象限
|
|
1251
|
+
* @param line 线段
|
|
1252
|
+
* @returns 象限索引(0:西北,1:东北,2:西南,3:东南)或-1(跨多个象限)
|
|
1253
|
+
*/
|
|
1254
|
+
getQuadrant(line) {
|
|
1255
|
+
const intersectsNW = this.children[0].bounds.intersectLineSegment(line);
|
|
1256
|
+
const intersectsNE = this.children[1].bounds.intersectLineSegment(line);
|
|
1257
|
+
const intersectsSW = this.children[2].bounds.intersectLineSegment(line);
|
|
1258
|
+
const intersectsSE = this.children[3].bounds.intersectLineSegment(line);
|
|
1259
|
+
let count = 0;
|
|
1260
|
+
let quadrant = -1;
|
|
1261
|
+
if (intersectsNW) {
|
|
1262
|
+
count++;
|
|
1263
|
+
quadrant = 0;
|
|
1264
|
+
}
|
|
1265
|
+
if (intersectsNE) {
|
|
1266
|
+
count++;
|
|
1267
|
+
quadrant = 1;
|
|
1268
|
+
}
|
|
1269
|
+
if (intersectsSW) {
|
|
1270
|
+
count++;
|
|
1271
|
+
quadrant = 2;
|
|
1272
|
+
}
|
|
1273
|
+
if (intersectsSE) {
|
|
1274
|
+
count++;
|
|
1275
|
+
quadrant = 3;
|
|
1276
|
+
}
|
|
1277
|
+
if (count === 1) return quadrant;
|
|
1278
|
+
return -1;
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* 细分当前节点为四个子节点
|
|
1282
|
+
*/
|
|
1283
|
+
subdivide() {
|
|
1284
|
+
if (!this.isLeaf) return;
|
|
1285
|
+
this.isLeaf = false;
|
|
1286
|
+
this.children = [];
|
|
1287
|
+
const midX = (this.bounds.minX + this.bounds.maxX) / 2;
|
|
1288
|
+
const midY = (this.bounds.minY + this.bounds.maxY) / 2;
|
|
1289
|
+
this.children[0] = new Quadtree(
|
|
1290
|
+
new Box2(this.bounds.minX, midX, this.bounds.minY, midY),
|
|
1291
|
+
this.capacity,
|
|
1292
|
+
this.maxDepth,
|
|
1293
|
+
this.depth + 1
|
|
1294
|
+
);
|
|
1295
|
+
this.children[1] = new Quadtree(
|
|
1296
|
+
new Box2(midX, this.bounds.maxX, this.bounds.minY, midY),
|
|
1297
|
+
this.capacity,
|
|
1298
|
+
this.maxDepth,
|
|
1299
|
+
this.depth + 1
|
|
1300
|
+
);
|
|
1301
|
+
this.children[2] = new Quadtree(
|
|
1302
|
+
new Box2(this.bounds.minX, midX, midY, this.bounds.maxY),
|
|
1303
|
+
this.capacity,
|
|
1304
|
+
this.maxDepth,
|
|
1305
|
+
this.depth + 1
|
|
1306
|
+
);
|
|
1307
|
+
this.children[3] = new Quadtree(
|
|
1308
|
+
new Box2(midX, this.bounds.maxX, midY, this.bounds.maxY),
|
|
1309
|
+
this.capacity,
|
|
1310
|
+
this.maxDepth,
|
|
1311
|
+
this.depth + 1
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* 查询与包围盒相交的线段节点
|
|
1316
|
+
* @param box2 包围盒
|
|
1317
|
+
* @returns 相交的节点数组
|
|
1318
|
+
*/
|
|
1319
|
+
queryBox(box2) {
|
|
1320
|
+
const result = [];
|
|
1321
|
+
if (!this.bounds.intersectBox(box2)) {
|
|
1322
|
+
return result;
|
|
1323
|
+
}
|
|
1324
|
+
for (const node of this.nodes) {
|
|
1325
|
+
if (box2.intersectLineSegment(node.line)) {
|
|
1326
|
+
result.push(node);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
if (!this.isLeaf) {
|
|
1330
|
+
for (const child of this.children) {
|
|
1331
|
+
result.push(...child.queryBox(box2));
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
return result;
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* 查询与圆形区域相交的线段节点
|
|
1338
|
+
* @param pos 圆心
|
|
1339
|
+
* @param radius 半径
|
|
1340
|
+
* @returns 相交的节点数组
|
|
1341
|
+
*/
|
|
1342
|
+
queryCircle(pos, radius) {
|
|
1343
|
+
const result = [];
|
|
1344
|
+
const circleBox = new Box2(
|
|
1345
|
+
pos.x - radius,
|
|
1346
|
+
pos.x + radius,
|
|
1347
|
+
pos.y - radius,
|
|
1348
|
+
pos.y + radius
|
|
1349
|
+
);
|
|
1350
|
+
if (!this.bounds.intersectBox(circleBox)) {
|
|
1351
|
+
return result;
|
|
1352
|
+
}
|
|
1353
|
+
for (const node of this.nodes) {
|
|
1354
|
+
const [p1, p2] = node.line.points;
|
|
1355
|
+
const dx = p2.x - p1.x;
|
|
1356
|
+
const dy = p2.y - p1.y;
|
|
1357
|
+
const l2 = dx * dx + dy * dy;
|
|
1358
|
+
let t = ((pos.x - p1.x) * dx + (pos.y - p1.y) * dy) / l2;
|
|
1359
|
+
t = Math.max(0, Math.min(1, t));
|
|
1360
|
+
const closestX = p1.x + t * dx;
|
|
1361
|
+
const closestY = p1.y + t * dy;
|
|
1362
|
+
const distance = pos.distance(new Point(closestX, closestY));
|
|
1363
|
+
if (distance <= radius) {
|
|
1364
|
+
result.push(node);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
if (!this.isLeaf) {
|
|
1368
|
+
for (const child of this.children) {
|
|
1369
|
+
result.push(...child.queryCircle(pos, radius));
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
return result;
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* 查询与矩形相交的线段节点
|
|
1376
|
+
* @param rectangle 矩形
|
|
1377
|
+
* @returns 相交的节点数组
|
|
1378
|
+
*/
|
|
1379
|
+
queryRect(rectangle) {
|
|
1380
|
+
const result = [];
|
|
1381
|
+
if (!this.bounds.intersectRectangle(rectangle)) {
|
|
1382
|
+
return result;
|
|
1383
|
+
}
|
|
1384
|
+
for (const node of this.nodes) {
|
|
1385
|
+
if (rectangle.intersectLineSegment(node.line)) {
|
|
1386
|
+
result.push(node);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
if (!this.isLeaf) {
|
|
1390
|
+
for (const child of this.children) {
|
|
1391
|
+
result.push(...child.queryRect(rectangle));
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
return result;
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* 包围盒转换为数组
|
|
1398
|
+
* @param array
|
|
1399
|
+
* @param colors
|
|
1400
|
+
* @returns
|
|
1401
|
+
*/
|
|
1402
|
+
boundsToArray(array = [], colors, recursion = true) {
|
|
1403
|
+
if (!this.isLeaf && recursion) {
|
|
1404
|
+
this.children?.forEach((child) => child.boundsToArray(array, colors));
|
|
1405
|
+
}
|
|
1406
|
+
array.push(...this.bounds.points.flatMap((p, i, array2) => {
|
|
1407
|
+
const np = array2[(i + 1) % array2.length];
|
|
1408
|
+
colors?.push(...this.color);
|
|
1409
|
+
colors?.push(...this.color);
|
|
1410
|
+
return [p.x, p.y, 0, np.x, np.y, 0];
|
|
1411
|
+
}));
|
|
1412
|
+
return array;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
class PointVirtualGrid {
|
|
1416
|
+
map = /* @__PURE__ */ new Map();
|
|
1417
|
+
gridSize;
|
|
1418
|
+
constructor(gridSize = 2) {
|
|
1419
|
+
this.gridSize = gridSize;
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* 插入
|
|
1423
|
+
* @param point
|
|
1424
|
+
* @param userData
|
|
1425
|
+
*/
|
|
1426
|
+
insert(point, userData) {
|
|
1427
|
+
if (!point || isNaN(point.x) || isNaN(point.y)) {
|
|
1428
|
+
throw new Error("无效的点坐标");
|
|
1429
|
+
}
|
|
1430
|
+
const id = this.getGridId(point);
|
|
1431
|
+
if (!this.map.has(id)) this.map.set(id, /* @__PURE__ */ new Set());
|
|
1432
|
+
const set = this.map.get(id);
|
|
1433
|
+
set?.add({ point, userData });
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* 批量加入
|
|
1437
|
+
* @param points
|
|
1438
|
+
*/
|
|
1439
|
+
insertBatch(points) {
|
|
1440
|
+
for (const { point, userData } of points) {
|
|
1441
|
+
this.insert(point, userData);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* 获取通过坐标,获取唯一网格索引
|
|
1446
|
+
* @param point
|
|
1447
|
+
* @returns
|
|
1448
|
+
*/
|
|
1449
|
+
getGridId(point) {
|
|
1450
|
+
const i = Math.ceil(point.x / this.gridSize), j = Math.ceil(point.y / this.gridSize);
|
|
1451
|
+
return `${i}.${j}`;
|
|
1452
|
+
}
|
|
1453
|
+
/**
|
|
1454
|
+
*
|
|
1455
|
+
* @param gridId
|
|
1456
|
+
* @returns
|
|
1457
|
+
*/
|
|
1458
|
+
decodeGridId(gridId) {
|
|
1459
|
+
const [i, j] = gridId.split(".").map(Number);
|
|
1460
|
+
return new Point(i, j);
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* 查询与矩形相交的点
|
|
1464
|
+
* @param rectangle 矩形
|
|
1465
|
+
* @returns 相交的节点数组
|
|
1466
|
+
*/
|
|
1467
|
+
queryRect(rectangle) {
|
|
1468
|
+
const box2 = rectangle.toBox();
|
|
1469
|
+
const minI = Math.ceil(box2.minX / this.gridSize), maxI = Math.ceil(box2.maxX / this.gridSize), minJ = Math.ceil(box2.minY / this.gridSize), maxJ = Math.ceil(box2.maxY / this.gridSize);
|
|
1470
|
+
for (let i = minI; i <= maxI; i++) {
|
|
1471
|
+
for (let j = minJ; j <= maxJ; j++) {
|
|
1472
|
+
const id = `${i}.${j}`;
|
|
1473
|
+
if (!this.map.has(id)) continue;
|
|
1474
|
+
const set = this.map.get(id);
|
|
1475
|
+
set?.forEach((item) => {
|
|
1476
|
+
if (rectangle.containsPoint(item.point)) ;
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* 查询与圆形区域相交的点
|
|
1483
|
+
* @param pos 圆心
|
|
1484
|
+
* @param radius 半径
|
|
1485
|
+
* @returns 相交的节点数组
|
|
1486
|
+
*/
|
|
1487
|
+
queryCircle(pos, radius) {
|
|
1488
|
+
const box2 = new Box2(pos.x - radius, pos.x + radius, pos.y - radius, pos.y + radius);
|
|
1489
|
+
const minI = Math.ceil(box2.minX / this.gridSize), maxI = Math.ceil(box2.maxX / this.gridSize), minJ = Math.ceil(box2.minY / this.gridSize), maxJ = Math.ceil(box2.maxY / this.gridSize), list = [];
|
|
1490
|
+
for (let i = minI; i <= maxI; i++) {
|
|
1491
|
+
for (let j = minJ; j <= maxJ; j++) {
|
|
1492
|
+
const id = `${i}.${j}`;
|
|
1493
|
+
if (!this.map.has(id)) continue;
|
|
1494
|
+
const set = this.map.get(id);
|
|
1495
|
+
set?.forEach((item) => {
|
|
1496
|
+
if (pos.distance(item.point) <= radius) list.push(item);
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
return list;
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* 查询与包围盒相交的点
|
|
1504
|
+
* @param box2 包围盒
|
|
1505
|
+
* @returns 相交的节点数组
|
|
1506
|
+
*/
|
|
1507
|
+
queryBox(box2) {
|
|
1508
|
+
const minI = Math.ceil(box2.minX / this.gridSize), maxI = Math.ceil(box2.maxX / this.gridSize), minJ = Math.ceil(box2.minY / this.gridSize), maxJ = Math.ceil(box2.maxY / this.gridSize), list = [];
|
|
1509
|
+
for (let i = minI; i <= maxI; i++) {
|
|
1510
|
+
for (let j = minJ; j <= maxJ; j++) {
|
|
1511
|
+
const id = `${i}.${j}`;
|
|
1512
|
+
if (!this.map.has(id)) continue;
|
|
1513
|
+
const set = this.map.get(id);
|
|
1514
|
+
set?.forEach((item) => {
|
|
1515
|
+
if (box2.containsPoint(item.point)) list.push(item);
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
return list;
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* 查找相同点
|
|
1523
|
+
* @param point
|
|
1524
|
+
*/
|
|
1525
|
+
queryPoint(point) {
|
|
1526
|
+
const id = this.getGridId(point), list = [];
|
|
1527
|
+
if (this.map.has(id)) {
|
|
1528
|
+
const set = this.map.get(id);
|
|
1529
|
+
set?.forEach((item) => {
|
|
1530
|
+
if (point.equal(item.point)) list.push(item);
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
return list;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
class LineAnalysis extends Component {
|
|
1537
|
+
static name = "LineAnalysis";
|
|
1538
|
+
Dxf = null;
|
|
1539
|
+
Variable = null;
|
|
1540
|
+
lineSegmentList = [];
|
|
1541
|
+
container = new THREE.Group();
|
|
1542
|
+
// 误差角度
|
|
1543
|
+
errorAngle = 4;
|
|
1544
|
+
width = 0.4;
|
|
1545
|
+
/**
|
|
1546
|
+
*
|
|
1547
|
+
* @param parent
|
|
1548
|
+
*/
|
|
1549
|
+
onAddFromParent(parent) {
|
|
1550
|
+
this.Dxf = parent.findComponentByType(Dxf);
|
|
1551
|
+
this.Variable = this.parent?.findComponentByType(Variable);
|
|
1552
|
+
console.log(this.Dxf);
|
|
1553
|
+
this.Dxf.addEventListener("setDta", this.lineAnalysis.bind(this));
|
|
1554
|
+
this.Dxf.addEventListener("lineOffset", this.duplicatePointFiltering.bind(this));
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* 去除路径上重复的点
|
|
1558
|
+
* @description 判断方向向量,一个连续的方向上,只应该出现两个点
|
|
1559
|
+
*/
|
|
1560
|
+
duplicatePointFiltering() {
|
|
1561
|
+
const dxf = this.Dxf;
|
|
1562
|
+
dxf.wallsGroup = dxf.wallsGroup.map((points) => {
|
|
1563
|
+
const filterPoints = [];
|
|
1564
|
+
points.forEach((point, index2) => {
|
|
1565
|
+
if (index2 < 1 || points.length - 1 === index2) return filterPoints.push(point);
|
|
1566
|
+
const preP = points[index2 - 1], nextP = points[index2 + 1], direct1 = point.direction(preP), direct2 = nextP.direction(point), angle = direct1.angleBetween(direct2) / (Math.PI / 180);
|
|
1567
|
+
if (angle > 0.02 && angle < 180 - 0.02) filterPoints.push(point);
|
|
1568
|
+
});
|
|
1569
|
+
points = [filterPoints[0]];
|
|
1570
|
+
for (let i = 1; i < filterPoints.length; i++) {
|
|
1571
|
+
const point = filterPoints[i];
|
|
1572
|
+
const nextP = filterPoints[i - 1];
|
|
1573
|
+
if (nextP.distance(point) < dxf.width * 0.5) ;
|
|
1574
|
+
points.push(point);
|
|
1575
|
+
}
|
|
1576
|
+
return points;
|
|
1577
|
+
});
|
|
1578
|
+
dxf.wallsGroup = dxf.wallsGroup.filter((i) => i.length >= 4);
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
*
|
|
1582
|
+
* @param p1
|
|
1583
|
+
* @param p2
|
|
1584
|
+
* @param width
|
|
1585
|
+
* @returns
|
|
1586
|
+
*/
|
|
1587
|
+
expandLineSegment(p1, p2, width = 0.1) {
|
|
1588
|
+
const normal = p2.normal(p1);
|
|
1589
|
+
const pDirect = p2.direction(p1).mutiplyScalar(width * 0.5);
|
|
1590
|
+
const nDirect = p1.direction(p2).mutiplyScalar(width * 0.5);
|
|
1591
|
+
const offsetX = normal.x * width * 0.5;
|
|
1592
|
+
const offsetY = normal.y * width * 0.5;
|
|
1593
|
+
return {
|
|
1594
|
+
points: [
|
|
1595
|
+
// 第一条线
|
|
1596
|
+
new Point(p1.x + offsetX, p1.y + offsetY).add(nDirect),
|
|
1597
|
+
new Point(p2.x + offsetX, p2.y + offsetY).add(pDirect),
|
|
1598
|
+
// 第二条线
|
|
1599
|
+
new Point(p1.x - offsetX, p1.y - offsetY).add(nDirect),
|
|
1600
|
+
new Point(p2.x - offsetX, p2.y - offsetY).add(pDirect)
|
|
1601
|
+
],
|
|
1602
|
+
indices: [0, 1, 1, 3, 3, 2, 2, 0],
|
|
1603
|
+
rectIndices: [0, 1, 3, 2, 0]
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
appendLineSegmentList = [];
|
|
1607
|
+
/**
|
|
1608
|
+
* 追加数据
|
|
1609
|
+
* @param p1
|
|
1610
|
+
* @param p2
|
|
1611
|
+
*/
|
|
1612
|
+
addData(p1, p2) {
|
|
1613
|
+
const dxf = this.Dxf;
|
|
1614
|
+
dxf.data.push([p1.clone(), p2.clone(), [], false, dxf.data.length]);
|
|
1615
|
+
this.appendLineSegmentList.push(new LineSegment(p1.clone(), p2.clone()));
|
|
1616
|
+
}
|
|
1617
|
+
/** 结果分析创建矩形
|
|
1618
|
+
* @param result
|
|
1619
|
+
*/
|
|
1620
|
+
createRectangle(result) {
|
|
1621
|
+
const dxf = this.Dxf;
|
|
1622
|
+
const project0 = result.project, project1 = result.project2;
|
|
1623
|
+
this.addData(project0.points[0], project1.points[0]);
|
|
1624
|
+
this.addData(project0.points[1], project1.points[1]);
|
|
1625
|
+
const leftHeight = project0.points[0].distance(project1.points[0]), rightHeight = project0.points[1].distance(project1.points[1]), count = Math.ceil(Math.max(leftHeight, rightHeight) / dxf.width), leftFragment = leftHeight / count, rightFragment = rightHeight / count, leftDirection = project1.points[0].direction(project0.points[0]), rightDirection = project1.points[1].direction(project0.points[1]), leftP = project0.points[0].clone(), rightP = project0.points[1].clone(), direction = rightP.direction(leftP);
|
|
1626
|
+
direction.multiplyScalar(dxf.width * 0.5);
|
|
1627
|
+
const _leftP = leftP.clone().add(direction), _rightP = rightP.clone().add(direction.multiplyScalar(-1)), d1 = leftP.direction(rightP), d2 = _leftP.direction(_rightP);
|
|
1628
|
+
if (d1.x > 0 && d2.x < 0 || d1.x < 0 && d2.x > 0 || d1.y > 0 && d2.y < 0 || d1.y < 0 && d2.y > 0) return;
|
|
1629
|
+
leftP.set(_leftP.x, _leftP.y);
|
|
1630
|
+
rightP.set(_rightP.x, _rightP.y);
|
|
1631
|
+
for (let i = 1; i < count; i++) {
|
|
1632
|
+
const left = leftDirection.clone().multiplyScalar(leftFragment * i), right = rightDirection.clone().multiplyScalar(rightFragment * i), p1 = leftP.clone().add(left), p2 = rightP.clone().add(right);
|
|
1633
|
+
this.addData(p1, p2);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
pointVirtualGrid = new PointVirtualGrid();
|
|
1637
|
+
/**
|
|
1638
|
+
* 构建点的虚拟网格索引
|
|
1639
|
+
*/
|
|
1640
|
+
buildVirtualGrid() {
|
|
1641
|
+
const dxf = this.Dxf;
|
|
1642
|
+
const pointVirtualGrid = new PointVirtualGrid();
|
|
1643
|
+
dxf.originalData.forEach((d, index2) => {
|
|
1644
|
+
const [p1, p2] = [Point.from(d.start), Point.from(d.end)];
|
|
1645
|
+
pointVirtualGrid.insert(p1, { index: index2, type: "start" });
|
|
1646
|
+
pointVirtualGrid.insert(p2, { index: index2, type: "end" });
|
|
1647
|
+
});
|
|
1648
|
+
this.pointVirtualGrid = pointVirtualGrid;
|
|
1649
|
+
}
|
|
1650
|
+
quadtree;
|
|
1651
|
+
/**
|
|
1652
|
+
* 构建线段四叉树,快速查找,
|
|
1653
|
+
*/
|
|
1654
|
+
buildQuadtree() {
|
|
1655
|
+
const dxf = this.Dxf;
|
|
1656
|
+
const lineSegmentList = [];
|
|
1657
|
+
this.quadtree = new Quadtree(dxf.originalBox, 2);
|
|
1658
|
+
dxf.lineSegments.forEach((lineSegment) => {
|
|
1659
|
+
if (lineSegment.userData?.isDoor) return;
|
|
1660
|
+
this.quadtree?.insert({
|
|
1661
|
+
line: lineSegment,
|
|
1662
|
+
userData: lineSegmentList.length
|
|
1663
|
+
});
|
|
1664
|
+
lineSegmentList.push(lineSegment);
|
|
1665
|
+
});
|
|
1666
|
+
this.lineSegmentList = lineSegmentList;
|
|
1667
|
+
}
|
|
1668
|
+
resultList = [];
|
|
1669
|
+
/** 线段分析
|
|
1670
|
+
* @description 判断两条线段距离是否较短且趋近平行,然后查找两条线段的重合部分的投影线,以此判断两根线是否需要合并
|
|
1671
|
+
* @param data
|
|
1672
|
+
*/
|
|
1673
|
+
lineAnalysis() {
|
|
1674
|
+
this.buildQuadtree();
|
|
1675
|
+
this.buildVirtualGrid();
|
|
1676
|
+
const quadtree = this.quadtree;
|
|
1677
|
+
const lineSegmentList = this.lineSegmentList;
|
|
1678
|
+
const visited = /* @__PURE__ */ new Set(), resultList = [];
|
|
1679
|
+
lineSegmentList.forEach((_0, i) => {
|
|
1680
|
+
const sourceLineSegment = lineSegmentList[i], rectangle = Rectangle.fromByLineSegment(sourceLineSegment, this.width * 2, false, -0.01), ids = quadtree.queryRect(rectangle).map((i2) => i2.userData).filter((index2) => index2 !== i);
|
|
1681
|
+
ids.forEach((id) => {
|
|
1682
|
+
try {
|
|
1683
|
+
if (visited.has(`${i}-${id}`) || visited.has(`${id}-${i}`)) return;
|
|
1684
|
+
const res = this.projectionAnalysis(id, i, sourceLineSegment, lineSegmentList);
|
|
1685
|
+
if (res) resultList.push(res);
|
|
1686
|
+
visited.add(`${i}-${id}`);
|
|
1687
|
+
} catch (error) {
|
|
1688
|
+
}
|
|
1689
|
+
});
|
|
1690
|
+
});
|
|
1691
|
+
this.appendLineSegmentList.length = 0;
|
|
1692
|
+
resultList.forEach(this.createRectangle.bind(this));
|
|
1693
|
+
this.resultList = [];
|
|
1694
|
+
}
|
|
1695
|
+
/** 线段投影分析
|
|
1696
|
+
* @param index
|
|
1697
|
+
* @param sourceLineSegment
|
|
1698
|
+
* @param lineSegmentList
|
|
1699
|
+
* @returns
|
|
1700
|
+
*/
|
|
1701
|
+
projectionAnalysis(index2, sourceIndex, sourceLineSegment, lineSegmentList) {
|
|
1702
|
+
const temLineSegment = lineSegmentList[index2], direct = sourceLineSegment.direction(), temDirect = temLineSegment.direction(), angle = direct.angleBetween(temDirect) / (Math.PI / 180);
|
|
1703
|
+
if (angle < this.errorAngle || angle > 180 - this.errorAngle) {
|
|
1704
|
+
let data;
|
|
1705
|
+
const p1 = temLineSegment.projectLineSegment(sourceLineSegment), p2 = sourceLineSegment.projectLineSegment(temLineSegment), d1 = p1.direction(), d2 = p2.direction();
|
|
1706
|
+
if (d1.x > 0 && d2.x < 0 || d1.x < 0 && d2.x > 0 || d1.y > 0 && d2.y < 0 || d1.y < 0 && d2.y > 0) {
|
|
1707
|
+
p1.points = [p1.points[1], p1.points[0]];
|
|
1708
|
+
}
|
|
1709
|
+
if (p1.getLength() > p2.getLength()) {
|
|
1710
|
+
data = {
|
|
1711
|
+
target: temLineSegment,
|
|
1712
|
+
targetIndex: index2,
|
|
1713
|
+
source: sourceLineSegment,
|
|
1714
|
+
sourceIndex,
|
|
1715
|
+
project: p1,
|
|
1716
|
+
project2: p2
|
|
1717
|
+
};
|
|
1718
|
+
} else {
|
|
1719
|
+
data = {
|
|
1720
|
+
target: sourceLineSegment,
|
|
1721
|
+
targetIndex: sourceIndex,
|
|
1722
|
+
source: temLineSegment,
|
|
1723
|
+
sourceIndex: index2,
|
|
1724
|
+
project: p2,
|
|
1725
|
+
project2: p1
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
if (!data || data.project.getLength() < 0.01) return;
|
|
1729
|
+
return data;
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
class DxfSystem extends ComponentManager {
|
|
1734
|
+
Dxf;
|
|
1735
|
+
Variable;
|
|
1736
|
+
wallWidth;
|
|
1737
|
+
environment;
|
|
1738
|
+
/** 构造函数
|
|
1739
|
+
* @param wallWidth 输出墙壁厚度,该墙壁厚度不受缩放影响
|
|
1740
|
+
* @param scale 原始数据缩放比例
|
|
1741
|
+
*/
|
|
1742
|
+
constructor(wallWidth = 0.1, scale = 1) {
|
|
1743
|
+
super();
|
|
1744
|
+
this.environment = typeof window !== "undefined" ? "browser" : typeof global !== "undefined" ? "node" : "unknown";
|
|
1745
|
+
this.wallWidth = wallWidth;
|
|
1746
|
+
this.Dxf = new Dxf(this.wallWidth, scale);
|
|
1747
|
+
this.Variable = new Variable();
|
|
1748
|
+
this.addComponent(this.Variable);
|
|
1749
|
+
this.addComponent(this.Dxf);
|
|
1750
|
+
this.addComponent(new LineAnalysis());
|
|
1751
|
+
}
|
|
1752
|
+
usePlugin(plugin) {
|
|
1753
|
+
if (typeof plugin === "function") plugin.call(this, this);
|
|
1754
|
+
return this;
|
|
1755
|
+
}
|
|
1756
|
+
destroy() {
|
|
1757
|
+
this.components.forEach((com) => {
|
|
1758
|
+
this.removeComponent(com);
|
|
1759
|
+
com.destroy();
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
const exporter = new OBJExporter();
|
|
1764
|
+
function lineSqueezing(p1, p2, width = 0.1) {
|
|
1765
|
+
const normal = p2.normal(p1);
|
|
1766
|
+
const pDirect = p2.direction(p1).mutiplyScalar(width * 0.5);
|
|
1767
|
+
const nDirect = p1.direction(p2).mutiplyScalar(width * 0.5);
|
|
1768
|
+
const offsetX = normal.x * width * 0.5;
|
|
1769
|
+
const offsetY = normal.y * width * 0.5;
|
|
1770
|
+
return {
|
|
1771
|
+
points: [
|
|
1772
|
+
// 第一条线
|
|
1773
|
+
new Point(p1.x + offsetX, p1.y + offsetY).add(nDirect),
|
|
1774
|
+
new Point(p2.x + offsetX, p2.y + offsetY).add(pDirect),
|
|
1775
|
+
// 第二条线
|
|
1776
|
+
new Point(p1.x - offsetX, p1.y - offsetY).add(nDirect),
|
|
1777
|
+
new Point(p2.x - offsetX, p2.y - offsetY).add(pDirect)
|
|
1778
|
+
],
|
|
1779
|
+
indices: [0, 1, 1, 3, 3, 2, 2, 0],
|
|
1780
|
+
rectIndices: [0, 1, 3, 2, 0]
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
class WhiteModel extends Component {
|
|
1784
|
+
static name = "WhiteModel";
|
|
1785
|
+
Dxf = null;
|
|
1786
|
+
Variable = null;
|
|
1787
|
+
// dxf数据白模
|
|
1788
|
+
whiteModelGroup = new THREE.Group();
|
|
1789
|
+
// 原始数据白模
|
|
1790
|
+
originalWhiteMode = new THREE.Group();
|
|
1791
|
+
onAddFromParent(parent) {
|
|
1792
|
+
this.Dxf = parent.findComponentByName("Dxf");
|
|
1793
|
+
this.Variable = parent.findComponentByName("Variable");
|
|
1794
|
+
this.originalWhiteMode.visible = false;
|
|
1795
|
+
this.Dxf?.addEventListener("lineOffset", () => {
|
|
1796
|
+
this.updateModel();
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
updateModel() {
|
|
1800
|
+
this.Variable?.set("whiteModelVisible", false);
|
|
1801
|
+
const dxf = this.Dxf;
|
|
1802
|
+
this.originalWhiteMode.clear();
|
|
1803
|
+
this.whiteModelGroup.clear();
|
|
1804
|
+
this.whiteModelGroup.position.z = dxf.originalZAverage;
|
|
1805
|
+
this.originalWhiteMode.position.z = dxf.originalZAverage;
|
|
1806
|
+
dxf.wallsGroup.forEach((points) => {
|
|
1807
|
+
const shape = new THREE.Shape();
|
|
1808
|
+
points.forEach((p, i) => i === 0 ? shape.moveTo(p.x / dxf.scale, p.y / dxf.scale) : shape.lineTo(p.x / dxf.scale, p.y / dxf.scale));
|
|
1809
|
+
const geometry = new THREE.ExtrudeGeometry(shape, {
|
|
1810
|
+
depth: 2.8,
|
|
1811
|
+
bevelSize: 0
|
|
1812
|
+
});
|
|
1813
|
+
const mesh = new THREE.Mesh(geometry);
|
|
1814
|
+
mesh.material = new THREE.MeshStandardMaterial({ color: 16777215, transparent: true, opacity: 0.8 });
|
|
1815
|
+
this.whiteModelGroup.add(mesh);
|
|
1816
|
+
this.whiteModelGroup.add(
|
|
1817
|
+
new THREE.LineSegments(new THREE.EdgesGeometry(geometry), new THREE.LineBasicMaterial({ color: 6710886 }))
|
|
1818
|
+
);
|
|
1819
|
+
});
|
|
1820
|
+
const walls = dxf.originalData.map(({ start, end, insetionArr }) => {
|
|
1821
|
+
const startVec3 = new Point(start.x, start.y).mutiplyScalar(dxf.scale), endVec3 = new Point(end.x, end.y).mutiplyScalar(dxf.scale), { points, indices, rectIndices } = lineSqueezing(startVec3, endVec3, dxf.width);
|
|
1822
|
+
return {
|
|
1823
|
+
points,
|
|
1824
|
+
indices,
|
|
1825
|
+
rectIndices,
|
|
1826
|
+
insetions: (insetionArr ?? []).map((insetion) => insetion.index)
|
|
1827
|
+
};
|
|
1828
|
+
});
|
|
1829
|
+
walls.forEach((wall) => {
|
|
1830
|
+
const shape = new THREE.Shape();
|
|
1831
|
+
wall.rectIndices.forEach((index2, i) => {
|
|
1832
|
+
const p = wall.points[index2];
|
|
1833
|
+
if (i === 0) shape.moveTo(p.x, p.y);
|
|
1834
|
+
else shape.lineTo(p.x, p.y);
|
|
1835
|
+
});
|
|
1836
|
+
const geometry = new THREE.ExtrudeGeometry(shape, {
|
|
1837
|
+
depth: 2.8,
|
|
1838
|
+
bevelSize: 0
|
|
1839
|
+
});
|
|
1840
|
+
if (geometry.attributes.position.array.filter((num) => Number.isNaN(num)).length) return;
|
|
1841
|
+
const mesh = new THREE.Mesh(geometry);
|
|
1842
|
+
this.originalWhiteMode?.add(mesh);
|
|
1843
|
+
});
|
|
1844
|
+
this.dispatchEvent({
|
|
1845
|
+
type: "updateModel",
|
|
1846
|
+
originalWhiteMode: this.originalWhiteMode,
|
|
1847
|
+
whiteModelGroup: this.whiteModelGroup
|
|
1848
|
+
});
|
|
1849
|
+
}
|
|
1850
|
+
toOBJ() {
|
|
1851
|
+
return new Promise((resolve) => {
|
|
1852
|
+
resolve(exporter.parse(this.whiteModelGroup));
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
async toBlob() {
|
|
1856
|
+
const buffer = await this.toOBJ();
|
|
1857
|
+
if (buffer) {
|
|
1858
|
+
return new Blob([buffer], { type: "application/octet-stream" });
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
async download(filename) {
|
|
1862
|
+
if (typeof window !== "undefined") {
|
|
1863
|
+
const blob = await this.toBlob();
|
|
1864
|
+
if (!blob) return;
|
|
1865
|
+
const a = document.createElement("a");
|
|
1866
|
+
a.href = URL.createObjectURL(blob);
|
|
1867
|
+
a.download = filename;
|
|
1868
|
+
a.click();
|
|
1869
|
+
} else if (typeof global !== "undefined") {
|
|
1870
|
+
const buffer = await this.toOBJ();
|
|
1871
|
+
if (buffer) {
|
|
1872
|
+
const packageName = "fs";
|
|
1873
|
+
const { default: fs } = await import(
|
|
1874
|
+
/* @vite-ignore */
|
|
1875
|
+
packageName
|
|
1876
|
+
);
|
|
1877
|
+
fs.writeFileSync(filename, buffer);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
class DetailsPoint extends Component {
|
|
1883
|
+
static name = "DetailsPoint";
|
|
1884
|
+
Dxf = null;
|
|
1885
|
+
WhiteModel = null;
|
|
1886
|
+
Variable = null;
|
|
1887
|
+
desPoints = [];
|
|
1888
|
+
raylines = [];
|
|
1889
|
+
data = [];
|
|
1890
|
+
onAddFromParent(parent) {
|
|
1891
|
+
this.Dxf = parent.findComponentByName("Dxf");
|
|
1892
|
+
this.Variable = parent.findComponentByName("Variable");
|
|
1893
|
+
this.Dxf?.addEventListener("setDta", () => {
|
|
1894
|
+
this.updateModel();
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* 设置值
|
|
1899
|
+
* @param data
|
|
1900
|
+
*/
|
|
1901
|
+
async set(data) {
|
|
1902
|
+
if (typeof data === "string") {
|
|
1903
|
+
if (typeof global !== "undefined") {
|
|
1904
|
+
const packageName = "fs";
|
|
1905
|
+
const { default: fs } = await import(
|
|
1906
|
+
/* @vite-ignore */
|
|
1907
|
+
packageName
|
|
1908
|
+
);
|
|
1909
|
+
const buffer = fs.readFileSync(data);
|
|
1910
|
+
const json = JSON.parse(buffer.toString("utf-8"));
|
|
1911
|
+
this.set(json);
|
|
1912
|
+
return;
|
|
1913
|
+
} else {
|
|
1914
|
+
throw new Error("非node环境不允许使用路径");
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
this.data = data;
|
|
1918
|
+
this.updateModel();
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* 设置射线辅助
|
|
1922
|
+
*/
|
|
1923
|
+
racasterHelper(position, direction, far) {
|
|
1924
|
+
this.raylines.push([
|
|
1925
|
+
position.clone(),
|
|
1926
|
+
position.clone().add(direction.clone().multiplyScalar(far))
|
|
1927
|
+
]);
|
|
1928
|
+
direction.z = 0;
|
|
1929
|
+
this.raylines.push([
|
|
1930
|
+
position.clone(),
|
|
1931
|
+
position.clone().add(direction.clone().multiplyScalar(far))
|
|
1932
|
+
]);
|
|
1933
|
+
}
|
|
1934
|
+
_timer = null;
|
|
1935
|
+
/**
|
|
1936
|
+
* 更新模型
|
|
1937
|
+
*/
|
|
1938
|
+
updateModel() {
|
|
1939
|
+
if (this._timer) clearTimeout(this._timer);
|
|
1940
|
+
this._timer = setTimeout(() => {
|
|
1941
|
+
this._timer = null;
|
|
1942
|
+
const whiteModel = this.parent?.findComponentByName("WhiteModel");
|
|
1943
|
+
this.raylines.length = 0;
|
|
1944
|
+
this.desPoints.length = 0;
|
|
1945
|
+
this.data.forEach((item) => {
|
|
1946
|
+
const position = new THREE.Vector3(
|
|
1947
|
+
item.position.x,
|
|
1948
|
+
item.position.y,
|
|
1949
|
+
item.position.z
|
|
1950
|
+
);
|
|
1951
|
+
const direction = new THREE.Vector3(
|
|
1952
|
+
item.direction.x,
|
|
1953
|
+
item.direction.y,
|
|
1954
|
+
item.direction.z
|
|
1955
|
+
);
|
|
1956
|
+
const far = 100;
|
|
1957
|
+
this.racasterHelper(position, direction, far);
|
|
1958
|
+
direction.z = 0;
|
|
1959
|
+
const raycaster = new THREE.Raycaster(position, direction, 0, far);
|
|
1960
|
+
const list = raycaster.intersectObject(whiteModel.originalWhiteMode);
|
|
1961
|
+
if (list.length) {
|
|
1962
|
+
const { point } = list[0];
|
|
1963
|
+
this.desPoints.push({
|
|
1964
|
+
message: item.desc,
|
|
1965
|
+
position,
|
|
1966
|
+
intersection: point
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
});
|
|
1970
|
+
this.dispatchEvent({
|
|
1971
|
+
type: "handleSuccess",
|
|
1972
|
+
desPoints: this.desPoints
|
|
1973
|
+
});
|
|
1974
|
+
}, 50);
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
class DxfLineModel extends Component {
|
|
1978
|
+
static name = "DxfLineModel";
|
|
1979
|
+
dxfLineModel = new THREE.LineSegments();
|
|
1980
|
+
dxfDoorsLineModel = new THREE.LineSegments();
|
|
1981
|
+
dxfModelGroup = new THREE.Group();
|
|
1982
|
+
onAddFromParent(parent) {
|
|
1983
|
+
const dxf = parent.findComponentByName("Dxf");
|
|
1984
|
+
this.dxfModelGroup.add(this.dxfLineModel);
|
|
1985
|
+
this.dxfModelGroup.add(this.dxfDoorsLineModel);
|
|
1986
|
+
this.dxfDoorsLineModel.material = new THREE.LineBasicMaterial({ color: 16776960, vertexColors: true });
|
|
1987
|
+
dxf?.addEventListener("lineOffset", () => this.updateMode());
|
|
1988
|
+
}
|
|
1989
|
+
updateMode() {
|
|
1990
|
+
const dxf = this.parent?.findComponentByName("Dxf");
|
|
1991
|
+
this.dxfLineModel.clear();
|
|
1992
|
+
const dxfArray = dxf.to3DArray(1 / dxf.scale);
|
|
1993
|
+
this.dxfLineModel.geometry = new THREE.BufferGeometry().setAttribute("position", new THREE.BufferAttribute(dxfArray, 3, true));
|
|
1994
|
+
const doorsArray = new Float32Array(dxf.doors.flatMap(([p1, p2]) => [p1.x, p1.y, dxf.originalZAverage, p2.x, p2.y, dxf.originalZAverage])).map((n) => n / dxf.scale);
|
|
1995
|
+
const doorsColorArray = new Float32Array(dxf.doors.flatMap(() => [1, 0, 0, 0, 1, 0]));
|
|
1996
|
+
this.dxfDoorsLineModel.geometry = new THREE.BufferGeometry().setAttribute("position", new THREE.BufferAttribute(doorsArray, 3, true)).setAttribute("color", new THREE.BufferAttribute(doorsColorArray, 3));
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
2000
|
+
__proto__: null,
|
|
2001
|
+
DetailsPoint,
|
|
2002
|
+
DxfLineModel,
|
|
2003
|
+
WhiteModel
|
|
2004
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
2005
|
+
function ModelDataPlugin(dxfSystem) {
|
|
2006
|
+
dxfSystem.addComponent(new DxfLineModel());
|
|
2007
|
+
dxfSystem.addComponent(new WhiteModel());
|
|
2008
|
+
dxfSystem.addComponent(new DetailsPoint());
|
|
2009
|
+
}
|
|
2010
|
+
function loadRenderPlugin() {
|
|
2011
|
+
return import("./index2.js");
|
|
2012
|
+
}
|
|
2013
|
+
export {
|
|
2014
|
+
Box2 as B,
|
|
2015
|
+
Component as C,
|
|
2016
|
+
DxfSystem as D,
|
|
2017
|
+
ModelDataPlugin as M,
|
|
2018
|
+
Point as P,
|
|
2019
|
+
Variable as V,
|
|
2020
|
+
DetailsPoint as a,
|
|
2021
|
+
index as i,
|
|
2022
|
+
loadRenderPlugin as l
|
|
2023
|
+
};
|