cyclecad 3.10.0 → 3.10.2
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/app/index.html +75 -1
- package/app/js/fusion-help.json +373 -96
- package/app/js/killer-features.js +18 -6
- package/app/js/modules/ai-copilot.js +548 -41
- package/app/js/vendor/three-bvh-csg.js +3891 -0
- package/app/tests/ai-copilot-tests.html +230 -0
- package/docs/AI-COPILOT-TUTORIAL.md +150 -0
- package/docs/AI-COPILOT.md +99 -0
- package/docs/API-REFERENCE.md +17 -0
- package/package.json +1 -1
|
@@ -0,0 +1,3891 @@
|
|
|
1
|
+
import { BufferAttribute, Vector3, Ray, Vector2, Vector4, Mesh, Matrix4, Line3, Plane, Triangle, DoubleSide, Matrix3, BufferGeometry, Group, Color, MeshPhongMaterial, MathUtils, LineSegments, LineBasicMaterial, InstancedMesh, SphereGeometry, MeshBasicMaterial } from 'https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js';
|
|
2
|
+
import { MeshBVH, ExtendedTriangle } from 'https://cdn.jsdelivr.net/npm/three-mesh-bvh@0.7.8/build/index.module.js';
|
|
3
|
+
|
|
4
|
+
const HASH_WIDTH = 1e-6;
|
|
5
|
+
const HASH_HALF_WIDTH = HASH_WIDTH * 0.5;
|
|
6
|
+
const HASH_MULTIPLIER = Math.pow( 10, - Math.log10( HASH_WIDTH ) );
|
|
7
|
+
const HASH_ADDITION = HASH_HALF_WIDTH * HASH_MULTIPLIER;
|
|
8
|
+
function hashNumber( v ) {
|
|
9
|
+
|
|
10
|
+
return ~ ~ ( v * HASH_MULTIPLIER + HASH_ADDITION );
|
|
11
|
+
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function hashVertex2( v ) {
|
|
15
|
+
|
|
16
|
+
return `${ hashNumber( v.x ) },${ hashNumber( v.y ) }`;
|
|
17
|
+
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function hashVertex3( v ) {
|
|
21
|
+
|
|
22
|
+
return `${ hashNumber( v.x ) },${ hashNumber( v.y ) },${ hashNumber( v.z ) }`;
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function hashVertex4( v ) {
|
|
27
|
+
|
|
28
|
+
return `${ hashNumber( v.x ) },${ hashNumber( v.y ) },${ hashNumber( v.z ) },${ hashNumber( v.w ) }`;
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function hashRay( r ) {
|
|
33
|
+
|
|
34
|
+
return `${ hashVertex3( r.origin ) }-${ hashVertex3( r.direction ) }`;
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function toNormalizedRay( v0, v1, target ) {
|
|
39
|
+
|
|
40
|
+
// get a normalized direction
|
|
41
|
+
target
|
|
42
|
+
.direction
|
|
43
|
+
.subVectors( v1, v0 )
|
|
44
|
+
.normalize();
|
|
45
|
+
|
|
46
|
+
// project the origin onto the perpendicular plane that
|
|
47
|
+
// passes through 0, 0, 0
|
|
48
|
+
const scalar = v0.dot( target.direction );
|
|
49
|
+
target.
|
|
50
|
+
origin
|
|
51
|
+
.copy( v0 )
|
|
52
|
+
.addScaledVector( target.direction, - scalar );
|
|
53
|
+
|
|
54
|
+
return target;
|
|
55
|
+
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function areSharedArrayBuffersSupported() {
|
|
59
|
+
|
|
60
|
+
return typeof SharedArrayBuffer !== 'undefined';
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function convertToSharedArrayBuffer( array ) {
|
|
65
|
+
|
|
66
|
+
if ( array.buffer instanceof SharedArrayBuffer ) {
|
|
67
|
+
|
|
68
|
+
return array;
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const cons = array.constructor;
|
|
73
|
+
const buffer = array.buffer;
|
|
74
|
+
const sharedBuffer = new SharedArrayBuffer( buffer.byteLength );
|
|
75
|
+
|
|
76
|
+
const uintArray = new Uint8Array( buffer );
|
|
77
|
+
const sharedUintArray = new Uint8Array( sharedBuffer );
|
|
78
|
+
sharedUintArray.set( uintArray, 0 );
|
|
79
|
+
|
|
80
|
+
return new cons( sharedBuffer );
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getIndexArray( vertexCount, BufferConstructor = ArrayBuffer ) {
|
|
85
|
+
|
|
86
|
+
if ( vertexCount > 65535 ) {
|
|
87
|
+
|
|
88
|
+
return new Uint32Array( new BufferConstructor( 4 * vertexCount ) );
|
|
89
|
+
|
|
90
|
+
} else {
|
|
91
|
+
|
|
92
|
+
return new Uint16Array( new BufferConstructor( 2 * vertexCount ) );
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function ensureIndex( geo, options ) {
|
|
99
|
+
|
|
100
|
+
if ( ! geo.index ) {
|
|
101
|
+
|
|
102
|
+
const vertexCount = geo.attributes.position.count;
|
|
103
|
+
const BufferConstructor = options.useSharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer;
|
|
104
|
+
const index = getIndexArray( vertexCount, BufferConstructor );
|
|
105
|
+
geo.setIndex( new BufferAttribute( index, 1 ) );
|
|
106
|
+
|
|
107
|
+
for ( let i = 0; i < vertexCount; i ++ ) {
|
|
108
|
+
|
|
109
|
+
index[ i ] = i;
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getVertexCount( geo ) {
|
|
118
|
+
|
|
119
|
+
return geo.index ? geo.index.count : geo.attributes.position.count;
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getTriCount( geo ) {
|
|
124
|
+
|
|
125
|
+
return getVertexCount( geo ) / 3;
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const DEGENERATE_EPSILON = 1e-8;
|
|
130
|
+
const _tempVec = new Vector3();
|
|
131
|
+
|
|
132
|
+
function toTriIndex( v ) {
|
|
133
|
+
|
|
134
|
+
return ~ ~ ( v / 3 );
|
|
135
|
+
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function toEdgeIndex( v ) {
|
|
139
|
+
|
|
140
|
+
return v % 3;
|
|
141
|
+
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function sortEdgeFunc( a, b ) {
|
|
145
|
+
|
|
146
|
+
return a.start - b.start;
|
|
147
|
+
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getProjectedDistance( ray, vec ) {
|
|
151
|
+
|
|
152
|
+
return _tempVec.subVectors( vec, ray.origin ).dot( ray.direction );
|
|
153
|
+
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function hasOverlaps( arr ) {
|
|
157
|
+
|
|
158
|
+
arr = [ ...arr ].sort( sortEdgeFunc );
|
|
159
|
+
for ( let i = 0, l = arr.length; i < l - 1; i ++ ) {
|
|
160
|
+
|
|
161
|
+
const info0 = arr[ i ];
|
|
162
|
+
const info1 = arr[ i + 1 ];
|
|
163
|
+
|
|
164
|
+
if ( info1.start < info0.end && Math.abs( info1.start - info0.end ) > 1e-5 ) {
|
|
165
|
+
|
|
166
|
+
return true;
|
|
167
|
+
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return false;
|
|
173
|
+
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getEdgeSetLength( arr ) {
|
|
177
|
+
|
|
178
|
+
let tot = 0;
|
|
179
|
+
arr.forEach( ( { start, end } ) => tot += end - start );
|
|
180
|
+
return tot;
|
|
181
|
+
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function matchEdges( forward, reverse, disjointConnectivityMap, eps = DEGENERATE_EPSILON ) {
|
|
185
|
+
|
|
186
|
+
forward.sort( sortEdgeFunc );
|
|
187
|
+
reverse.sort( sortEdgeFunc );
|
|
188
|
+
|
|
189
|
+
for ( let i = 0; i < forward.length; i ++ ) {
|
|
190
|
+
|
|
191
|
+
const e0 = forward[ i ];
|
|
192
|
+
for ( let o = 0; o < reverse.length; o ++ ) {
|
|
193
|
+
|
|
194
|
+
const e1 = reverse[ o ];
|
|
195
|
+
if ( e1.start > e0.end ) {
|
|
196
|
+
|
|
197
|
+
// e2 is completely after e1
|
|
198
|
+
// break;
|
|
199
|
+
|
|
200
|
+
// NOTE: there are cases where there are overlaps due to precision issues or
|
|
201
|
+
// thin / degenerate triangles. Assuming the sibling side has the same issues
|
|
202
|
+
// we let the matching work here. Long term we should remove the degenerate
|
|
203
|
+
// triangles before this.
|
|
204
|
+
|
|
205
|
+
} else if ( e0.end < e1.start || e1.end < e0.start ) {
|
|
206
|
+
|
|
207
|
+
// e1 is completely before e2
|
|
208
|
+
continue;
|
|
209
|
+
|
|
210
|
+
} else if ( e0.start <= e1.start && e0.end >= e1.end ) {
|
|
211
|
+
|
|
212
|
+
// e1 is larger than and e2 is completely within e1
|
|
213
|
+
if ( ! areDistancesDegenerate( e1.end, e0.end ) ) {
|
|
214
|
+
|
|
215
|
+
forward.splice( i + 1, 0, {
|
|
216
|
+
start: e1.end,
|
|
217
|
+
end: e0.end,
|
|
218
|
+
index: e0.index,
|
|
219
|
+
} );
|
|
220
|
+
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
e0.end = e1.start;
|
|
224
|
+
|
|
225
|
+
e1.start = 0;
|
|
226
|
+
e1.end = 0;
|
|
227
|
+
|
|
228
|
+
} else if ( e0.start >= e1.start && e0.end <= e1.end ) {
|
|
229
|
+
|
|
230
|
+
// e2 is larger than and e1 is completely within e2
|
|
231
|
+
if ( ! areDistancesDegenerate( e0.end, e1.end ) ) {
|
|
232
|
+
|
|
233
|
+
reverse.splice( o + 1, 0, {
|
|
234
|
+
start: e0.end,
|
|
235
|
+
end: e1.end,
|
|
236
|
+
index: e1.index,
|
|
237
|
+
} );
|
|
238
|
+
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
e1.end = e0.start;
|
|
242
|
+
|
|
243
|
+
e0.start = 0;
|
|
244
|
+
e0.end = 0;
|
|
245
|
+
|
|
246
|
+
} else if ( e0.start <= e1.start && e0.end <= e1.end ) {
|
|
247
|
+
|
|
248
|
+
// e1 overlaps e2 at the beginning
|
|
249
|
+
const tmp = e0.end;
|
|
250
|
+
e0.end = e1.start;
|
|
251
|
+
e1.start = tmp;
|
|
252
|
+
|
|
253
|
+
} else if ( e0.start >= e1.start && e0.end >= e1.end ) {
|
|
254
|
+
|
|
255
|
+
// e1 overlaps e2 at the end
|
|
256
|
+
const tmp = e1.end;
|
|
257
|
+
e1.end = e0.start;
|
|
258
|
+
e0.start = tmp;
|
|
259
|
+
|
|
260
|
+
} else {
|
|
261
|
+
|
|
262
|
+
throw new Error();
|
|
263
|
+
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Add the connectivity information
|
|
267
|
+
if ( ! disjointConnectivityMap.has( e0.index ) ) {
|
|
268
|
+
|
|
269
|
+
disjointConnectivityMap.set( e0.index, [] );
|
|
270
|
+
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if ( ! disjointConnectivityMap.has( e1.index ) ) {
|
|
274
|
+
|
|
275
|
+
disjointConnectivityMap.set( e1.index, [] );
|
|
276
|
+
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
disjointConnectivityMap
|
|
280
|
+
.get( e0.index )
|
|
281
|
+
.push( e1.index );
|
|
282
|
+
|
|
283
|
+
disjointConnectivityMap
|
|
284
|
+
.get( e1.index )
|
|
285
|
+
.push( e0.index );
|
|
286
|
+
|
|
287
|
+
if ( isEdgeDegenerate( e1 ) ) {
|
|
288
|
+
|
|
289
|
+
reverse.splice( o, 1 );
|
|
290
|
+
o --;
|
|
291
|
+
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if ( isEdgeDegenerate( e0 ) ) {
|
|
295
|
+
|
|
296
|
+
// and if we have to remove the current original edge then exit this loop
|
|
297
|
+
// so we can work on the next one
|
|
298
|
+
forward.splice( i, 1 );
|
|
299
|
+
i --;
|
|
300
|
+
break;
|
|
301
|
+
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
cleanUpEdgeSet( forward );
|
|
309
|
+
cleanUpEdgeSet( reverse );
|
|
310
|
+
|
|
311
|
+
function cleanUpEdgeSet( arr ) {
|
|
312
|
+
|
|
313
|
+
for ( let i = 0; i < arr.length; i ++ ) {
|
|
314
|
+
|
|
315
|
+
if ( isEdgeDegenerate( arr[ i ] ) ) {
|
|
316
|
+
|
|
317
|
+
arr.splice( i, 1 );
|
|
318
|
+
i --;
|
|
319
|
+
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function areDistancesDegenerate( start, end ) {
|
|
327
|
+
|
|
328
|
+
return Math.abs( end - start ) < eps;
|
|
329
|
+
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function isEdgeDegenerate( e ) {
|
|
333
|
+
|
|
334
|
+
return Math.abs( e.end - e.start ) < eps;
|
|
335
|
+
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const DIST_EPSILON = 1e-5;
|
|
341
|
+
const ANGLE_EPSILON = 1e-4;
|
|
342
|
+
|
|
343
|
+
class RaySet {
|
|
344
|
+
|
|
345
|
+
constructor() {
|
|
346
|
+
|
|
347
|
+
this._rays = [];
|
|
348
|
+
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
addRay( ray ) {
|
|
352
|
+
|
|
353
|
+
this._rays.push( ray );
|
|
354
|
+
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
findClosestRay( ray ) {
|
|
358
|
+
|
|
359
|
+
const rays = this._rays;
|
|
360
|
+
const inv = ray.clone();
|
|
361
|
+
inv.direction.multiplyScalar( - 1 );
|
|
362
|
+
|
|
363
|
+
let bestScore = Infinity;
|
|
364
|
+
let bestRay = null;
|
|
365
|
+
for ( let i = 0, l = rays.length; i < l; i ++ ) {
|
|
366
|
+
|
|
367
|
+
const r = rays[ i ];
|
|
368
|
+
if ( skipRay( r, ray ) && skipRay( r, inv ) ) {
|
|
369
|
+
|
|
370
|
+
continue;
|
|
371
|
+
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const rayScore = scoreRays( r, ray );
|
|
375
|
+
const invScore = scoreRays( r, inv );
|
|
376
|
+
const score = Math.min( rayScore, invScore );
|
|
377
|
+
if ( score < bestScore ) {
|
|
378
|
+
|
|
379
|
+
bestScore = score;
|
|
380
|
+
bestRay = r;
|
|
381
|
+
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return bestRay;
|
|
387
|
+
|
|
388
|
+
function skipRay( r0, r1 ) {
|
|
389
|
+
|
|
390
|
+
const distOutOfThreshold = r0.origin.distanceTo( r1.origin ) > DIST_EPSILON;
|
|
391
|
+
const angleOutOfThreshold = r0.direction.angleTo( r1.direction ) > ANGLE_EPSILON;
|
|
392
|
+
return angleOutOfThreshold || distOutOfThreshold;
|
|
393
|
+
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function scoreRays( r0, r1 ) {
|
|
397
|
+
|
|
398
|
+
const originDistance = r0.origin.distanceTo( r1.origin );
|
|
399
|
+
const angleDistance = r0.direction.angleTo( r1.direction );
|
|
400
|
+
return originDistance / DIST_EPSILON + angleDistance / ANGLE_EPSILON;
|
|
401
|
+
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const _v0 = new Vector3();
|
|
409
|
+
const _v1 = new Vector3();
|
|
410
|
+
const _ray$2 = new Ray();
|
|
411
|
+
|
|
412
|
+
function computeDisjointEdges(
|
|
413
|
+
geometry,
|
|
414
|
+
unmatchedSet,
|
|
415
|
+
eps,
|
|
416
|
+
) {
|
|
417
|
+
|
|
418
|
+
const attributes = geometry.attributes;
|
|
419
|
+
const indexAttr = geometry.index;
|
|
420
|
+
const posAttr = attributes.position;
|
|
421
|
+
|
|
422
|
+
const disjointConnectivityMap = new Map();
|
|
423
|
+
const fragmentMap = new Map();
|
|
424
|
+
const edges = Array.from( unmatchedSet );
|
|
425
|
+
const rays = new RaySet();
|
|
426
|
+
|
|
427
|
+
for ( let i = 0, l = edges.length; i < l; i ++ ) {
|
|
428
|
+
|
|
429
|
+
// get the triangle edge
|
|
430
|
+
const index = edges[ i ];
|
|
431
|
+
const triIndex = toTriIndex( index );
|
|
432
|
+
const edgeIndex = toEdgeIndex( index );
|
|
433
|
+
|
|
434
|
+
let i0 = 3 * triIndex + edgeIndex;
|
|
435
|
+
let i1 = 3 * triIndex + ( edgeIndex + 1 ) % 3;
|
|
436
|
+
if ( indexAttr ) {
|
|
437
|
+
|
|
438
|
+
i0 = indexAttr.getX( i0 );
|
|
439
|
+
i1 = indexAttr.getX( i1 );
|
|
440
|
+
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
_v0.fromBufferAttribute( posAttr, i0 );
|
|
444
|
+
_v1.fromBufferAttribute( posAttr, i1 );
|
|
445
|
+
|
|
446
|
+
// get the ray corresponding to the edge
|
|
447
|
+
toNormalizedRay( _v0, _v1, _ray$2 );
|
|
448
|
+
|
|
449
|
+
// find the shared ray with other edges
|
|
450
|
+
let info;
|
|
451
|
+
let commonRay = rays.findClosestRay( _ray$2 );
|
|
452
|
+
if ( commonRay === null ) {
|
|
453
|
+
|
|
454
|
+
commonRay = _ray$2.clone();
|
|
455
|
+
rays.addRay( commonRay );
|
|
456
|
+
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if ( ! fragmentMap.has( commonRay ) ) {
|
|
460
|
+
|
|
461
|
+
fragmentMap.set( commonRay, {
|
|
462
|
+
|
|
463
|
+
forward: [],
|
|
464
|
+
reverse: [],
|
|
465
|
+
ray: commonRay,
|
|
466
|
+
|
|
467
|
+
} );
|
|
468
|
+
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
info = fragmentMap.get( commonRay );
|
|
472
|
+
|
|
473
|
+
// store the stride of edge endpoints along the ray
|
|
474
|
+
let start = getProjectedDistance( commonRay, _v0 );
|
|
475
|
+
let end = getProjectedDistance( commonRay, _v1 );
|
|
476
|
+
if ( start > end ) {
|
|
477
|
+
|
|
478
|
+
[ start, end ] = [ end, start ];
|
|
479
|
+
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if ( _ray$2.direction.dot( commonRay.direction ) < 0 ) {
|
|
483
|
+
|
|
484
|
+
info.reverse.push( { start, end, index } );
|
|
485
|
+
|
|
486
|
+
} else {
|
|
487
|
+
|
|
488
|
+
info.forward.push( { start, end, index } );
|
|
489
|
+
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// match the found sibling edges
|
|
495
|
+
fragmentMap.forEach( ( { forward, reverse }, ray ) => {
|
|
496
|
+
|
|
497
|
+
matchEdges( forward, reverse, disjointConnectivityMap, eps );
|
|
498
|
+
|
|
499
|
+
if ( forward.length === 0 && reverse.length === 0 ) {
|
|
500
|
+
|
|
501
|
+
fragmentMap.delete( ray );
|
|
502
|
+
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
} );
|
|
506
|
+
|
|
507
|
+
return {
|
|
508
|
+
disjointConnectivityMap,
|
|
509
|
+
fragmentMap,
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const _vec2$1 = new Vector2();
|
|
515
|
+
const _vec3$1 = new Vector3();
|
|
516
|
+
const _vec4 = new Vector4();
|
|
517
|
+
const _hashes = [ '', '', '' ];
|
|
518
|
+
|
|
519
|
+
class HalfEdgeMap {
|
|
520
|
+
|
|
521
|
+
constructor( geometry = null ) {
|
|
522
|
+
|
|
523
|
+
// result data
|
|
524
|
+
this.data = null;
|
|
525
|
+
this.disjointConnections = null;
|
|
526
|
+
this.unmatchedDisjointEdges = null;
|
|
527
|
+
this.unmatchedEdges = - 1;
|
|
528
|
+
this.matchedEdges = - 1;
|
|
529
|
+
|
|
530
|
+
// options
|
|
531
|
+
this.useDrawRange = true;
|
|
532
|
+
this.useAllAttributes = false;
|
|
533
|
+
this.matchDisjointEdges = false;
|
|
534
|
+
this.degenerateEpsilon = 1e-8;
|
|
535
|
+
|
|
536
|
+
if ( geometry ) {
|
|
537
|
+
|
|
538
|
+
this.updateFrom( geometry );
|
|
539
|
+
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
getSiblingTriangleIndex( triIndex, edgeIndex ) {
|
|
545
|
+
|
|
546
|
+
const otherIndex = this.data[ triIndex * 3 + edgeIndex ];
|
|
547
|
+
return otherIndex === - 1 ? - 1 : ~ ~ ( otherIndex / 3 );
|
|
548
|
+
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
getSiblingEdgeIndex( triIndex, edgeIndex ) {
|
|
552
|
+
|
|
553
|
+
const otherIndex = this.data[ triIndex * 3 + edgeIndex ];
|
|
554
|
+
return otherIndex === - 1 ? - 1 : ( otherIndex % 3 );
|
|
555
|
+
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
getDisjointSiblingTriangleIndices( triIndex, edgeIndex ) {
|
|
559
|
+
|
|
560
|
+
const index = triIndex * 3 + edgeIndex;
|
|
561
|
+
const arr = this.disjointConnections.get( index );
|
|
562
|
+
return arr ? arr.map( i => ~ ~ ( i / 3 ) ) : [];
|
|
563
|
+
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
getDisjointSiblingEdgeIndices( triIndex, edgeIndex ) {
|
|
567
|
+
|
|
568
|
+
const index = triIndex * 3 + edgeIndex;
|
|
569
|
+
const arr = this.disjointConnections.get( index );
|
|
570
|
+
return arr ? arr.map( i => i % 3 ) : [];
|
|
571
|
+
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
isFullyConnected() {
|
|
575
|
+
|
|
576
|
+
return this.unmatchedEdges === 0;
|
|
577
|
+
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
updateFrom( geometry ) {
|
|
581
|
+
|
|
582
|
+
const { useAllAttributes, useDrawRange, matchDisjointEdges, degenerateEpsilon } = this;
|
|
583
|
+
const hashFunction = useAllAttributes ? hashAllAttributes : hashPositionAttribute;
|
|
584
|
+
|
|
585
|
+
// runs on the assumption that there is a 1 : 1 match of edges
|
|
586
|
+
const map = new Map();
|
|
587
|
+
|
|
588
|
+
// attributes
|
|
589
|
+
const { attributes } = geometry;
|
|
590
|
+
const attrKeys = useAllAttributes ? Object.keys( attributes ) : null;
|
|
591
|
+
const indexAttr = geometry.index;
|
|
592
|
+
const posAttr = attributes.position;
|
|
593
|
+
|
|
594
|
+
// get the potential number of triangles
|
|
595
|
+
let triCount = getTriCount( geometry );
|
|
596
|
+
const maxTriCount = triCount;
|
|
597
|
+
|
|
598
|
+
// get the real number of triangles from the based on the draw range
|
|
599
|
+
let offset = 0;
|
|
600
|
+
if ( useDrawRange ) {
|
|
601
|
+
|
|
602
|
+
offset = geometry.drawRange.start;
|
|
603
|
+
if ( geometry.drawRange.count !== Infinity ) {
|
|
604
|
+
|
|
605
|
+
triCount = ~ ~ ( geometry.drawRange.count / 3 );
|
|
606
|
+
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// initialize the connectivity buffer - 1 means no connectivity
|
|
612
|
+
let data = this.data;
|
|
613
|
+
if ( ! data || data.length < 3 * maxTriCount ) {
|
|
614
|
+
|
|
615
|
+
data = new Int32Array( 3 * maxTriCount );
|
|
616
|
+
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
data.fill( - 1 );
|
|
620
|
+
|
|
621
|
+
// iterate over all triangles
|
|
622
|
+
let matchedEdges = 0;
|
|
623
|
+
let unmatchedSet = new Set();
|
|
624
|
+
for ( let i = offset, l = triCount * 3 + offset; i < l; i += 3 ) {
|
|
625
|
+
|
|
626
|
+
const i3 = i;
|
|
627
|
+
for ( let e = 0; e < 3; e ++ ) {
|
|
628
|
+
|
|
629
|
+
let i0 = i3 + e;
|
|
630
|
+
if ( indexAttr ) {
|
|
631
|
+
|
|
632
|
+
i0 = indexAttr.getX( i0 );
|
|
633
|
+
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
_hashes[ e ] = hashFunction( i0 );
|
|
637
|
+
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
for ( let e = 0; e < 3; e ++ ) {
|
|
641
|
+
|
|
642
|
+
const nextE = ( e + 1 ) % 3;
|
|
643
|
+
const vh0 = _hashes[ e ];
|
|
644
|
+
const vh1 = _hashes[ nextE ];
|
|
645
|
+
|
|
646
|
+
const reverseHash = `${ vh1 }_${ vh0 }`;
|
|
647
|
+
if ( map.has( reverseHash ) ) {
|
|
648
|
+
|
|
649
|
+
// create a reference between the two triangles and clear the hash
|
|
650
|
+
const index = i3 + e;
|
|
651
|
+
const otherIndex = map.get( reverseHash );
|
|
652
|
+
data[ index ] = otherIndex;
|
|
653
|
+
data[ otherIndex ] = index;
|
|
654
|
+
map.delete( reverseHash );
|
|
655
|
+
matchedEdges += 2;
|
|
656
|
+
unmatchedSet.delete( otherIndex );
|
|
657
|
+
|
|
658
|
+
} else {
|
|
659
|
+
|
|
660
|
+
// save the triangle and triangle edge index captured in one value
|
|
661
|
+
// triIndex = ~ ~ ( i0 / 3 );
|
|
662
|
+
// edgeIndex = i0 % 3;
|
|
663
|
+
const hash = `${ vh0 }_${ vh1 }`;
|
|
664
|
+
const index = i3 + e;
|
|
665
|
+
map.set( hash, index );
|
|
666
|
+
unmatchedSet.add( index );
|
|
667
|
+
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if ( matchDisjointEdges ) {
|
|
675
|
+
|
|
676
|
+
const {
|
|
677
|
+
fragmentMap,
|
|
678
|
+
disjointConnectivityMap,
|
|
679
|
+
} = computeDisjointEdges( geometry, unmatchedSet, degenerateEpsilon );
|
|
680
|
+
|
|
681
|
+
unmatchedSet.clear();
|
|
682
|
+
fragmentMap.forEach( ( { forward, reverse } ) => {
|
|
683
|
+
|
|
684
|
+
forward.forEach( ( { index } ) => unmatchedSet.add( index ) );
|
|
685
|
+
reverse.forEach( ( { index } ) => unmatchedSet.add( index ) );
|
|
686
|
+
|
|
687
|
+
} );
|
|
688
|
+
|
|
689
|
+
this.unmatchedDisjointEdges = fragmentMap;
|
|
690
|
+
this.disjointConnections = disjointConnectivityMap;
|
|
691
|
+
matchedEdges = triCount * 3 - unmatchedSet.size;
|
|
692
|
+
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
this.matchedEdges = matchedEdges;
|
|
696
|
+
this.unmatchedEdges = unmatchedSet.size;
|
|
697
|
+
this.data = data;
|
|
698
|
+
|
|
699
|
+
function hashPositionAttribute( i ) {
|
|
700
|
+
|
|
701
|
+
_vec3$1.fromBufferAttribute( posAttr, i );
|
|
702
|
+
return hashVertex3( _vec3$1 );
|
|
703
|
+
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function hashAllAttributes( i ) {
|
|
707
|
+
|
|
708
|
+
let result = '';
|
|
709
|
+
for ( let k = 0, l = attrKeys.length; k < l; k ++ ) {
|
|
710
|
+
|
|
711
|
+
const attr = attributes[ attrKeys[ k ] ];
|
|
712
|
+
let str;
|
|
713
|
+
switch ( attr.itemSize ) {
|
|
714
|
+
|
|
715
|
+
case 1:
|
|
716
|
+
str = hashNumber( attr.getX( i ) );
|
|
717
|
+
break;
|
|
718
|
+
case 2:
|
|
719
|
+
str = hashVertex2( _vec2$1.fromBufferAttribute( attr, i ) );
|
|
720
|
+
break;
|
|
721
|
+
case 3:
|
|
722
|
+
str = hashVertex3( _vec3$1.fromBufferAttribute( attr, i ) );
|
|
723
|
+
break;
|
|
724
|
+
case 4:
|
|
725
|
+
str = hashVertex4( _vec4.fromBufferAttribute( attr, i ) );
|
|
726
|
+
break;
|
|
727
|
+
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if ( result !== '' ) {
|
|
731
|
+
|
|
732
|
+
result += '|';
|
|
733
|
+
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
result += str;
|
|
737
|
+
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
return result;
|
|
741
|
+
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
class Brush extends Mesh {
|
|
749
|
+
|
|
750
|
+
constructor( ...args ) {
|
|
751
|
+
|
|
752
|
+
super( ...args );
|
|
753
|
+
|
|
754
|
+
this.isBrush = true;
|
|
755
|
+
this._previousMatrix = new Matrix4();
|
|
756
|
+
this._previousMatrix.elements.fill( 0 );
|
|
757
|
+
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
markUpdated() {
|
|
761
|
+
|
|
762
|
+
this._previousMatrix.copy( this.matrix );
|
|
763
|
+
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
isDirty() {
|
|
767
|
+
|
|
768
|
+
const { matrix, _previousMatrix } = this;
|
|
769
|
+
const el1 = matrix.elements;
|
|
770
|
+
const el2 = _previousMatrix.elements;
|
|
771
|
+
for ( let i = 0; i < 16; i ++ ) {
|
|
772
|
+
|
|
773
|
+
if ( el1[ i ] !== el2[ i ] ) {
|
|
774
|
+
|
|
775
|
+
return true;
|
|
776
|
+
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
return false;
|
|
782
|
+
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
prepareGeometry() {
|
|
786
|
+
|
|
787
|
+
// generate shared array buffers
|
|
788
|
+
const geometry = this.geometry;
|
|
789
|
+
const attributes = geometry.attributes;
|
|
790
|
+
const useSharedArrayBuffer = areSharedArrayBuffersSupported();
|
|
791
|
+
if ( useSharedArrayBuffer ) {
|
|
792
|
+
|
|
793
|
+
for ( const key in attributes ) {
|
|
794
|
+
|
|
795
|
+
const attribute = attributes[ key ];
|
|
796
|
+
if ( attribute.isInterleavedBufferAttribute ) {
|
|
797
|
+
|
|
798
|
+
throw new Error( 'Brush: InterleavedBufferAttributes are not supported.' );
|
|
799
|
+
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
attribute.array = convertToSharedArrayBuffer( attribute.array );
|
|
803
|
+
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// generate bounds tree
|
|
809
|
+
if ( ! geometry.boundsTree ) {
|
|
810
|
+
|
|
811
|
+
ensureIndex( geometry, { useSharedArrayBuffer } );
|
|
812
|
+
geometry.boundsTree = new MeshBVH( geometry, { maxLeafTris: 3, indirect: true, useSharedArrayBuffer } );
|
|
813
|
+
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// generate half edges
|
|
817
|
+
if ( ! geometry.halfEdges ) {
|
|
818
|
+
|
|
819
|
+
geometry.halfEdges = new HalfEdgeMap( geometry );
|
|
820
|
+
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// save group indices for materials
|
|
824
|
+
if ( ! geometry.groupIndices ) {
|
|
825
|
+
|
|
826
|
+
const triCount = getTriCount( geometry );
|
|
827
|
+
const array = new Uint16Array( triCount );
|
|
828
|
+
const groups = geometry.groups;
|
|
829
|
+
for ( let i = 0, l = groups.length; i < l; i ++ ) {
|
|
830
|
+
|
|
831
|
+
const { start, count } = groups[ i ];
|
|
832
|
+
for ( let g = start / 3, lg = ( start + count ) / 3; g < lg; g ++ ) {
|
|
833
|
+
|
|
834
|
+
array[ g ] = i;
|
|
835
|
+
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
geometry.groupIndices = array;
|
|
841
|
+
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
disposeCacheData() {
|
|
847
|
+
|
|
848
|
+
const { geometry } = this;
|
|
849
|
+
geometry.halfEdges = null;
|
|
850
|
+
geometry.boundsTree = null;
|
|
851
|
+
geometry.groupIndices = null;
|
|
852
|
+
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
const EPSILON$1 = 1e-14;
|
|
858
|
+
const _AB = new Vector3();
|
|
859
|
+
const _AC = new Vector3();
|
|
860
|
+
const _CB = new Vector3();
|
|
861
|
+
|
|
862
|
+
function isTriDegenerate( tri, eps = EPSILON$1 ) {
|
|
863
|
+
|
|
864
|
+
// compute angles to determine whether they're degenerate
|
|
865
|
+
_AB.subVectors( tri.b, tri.a );
|
|
866
|
+
_AC.subVectors( tri.c, tri.a );
|
|
867
|
+
_CB.subVectors( tri.b, tri.c );
|
|
868
|
+
|
|
869
|
+
const angle1 = _AB.angleTo( _AC ); // AB v AC
|
|
870
|
+
const angle2 = _AB.angleTo( _CB ); // AB v BC
|
|
871
|
+
const angle3 = Math.PI - angle1 - angle2; // 180deg - angle1 - angle2
|
|
872
|
+
|
|
873
|
+
return Math.abs( angle1 ) < eps ||
|
|
874
|
+
Math.abs( angle2 ) < eps ||
|
|
875
|
+
Math.abs( angle3 ) < eps ||
|
|
876
|
+
tri.a.distanceToSquared( tri.b ) < eps ||
|
|
877
|
+
tri.a.distanceToSquared( tri.c ) < eps ||
|
|
878
|
+
tri.b.distanceToSquared( tri.c ) < eps;
|
|
879
|
+
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// NOTE: these epsilons likely should all be the same since they're used to measure the
|
|
883
|
+
// distance from a point to a plane which needs to be done consistently
|
|
884
|
+
const EPSILON = 1e-10;
|
|
885
|
+
const COPLANAR_EPSILON = 1e-10;
|
|
886
|
+
const PARALLEL_EPSILON = 1e-10;
|
|
887
|
+
const _edge$2 = new Line3();
|
|
888
|
+
const _foundEdge = new Line3();
|
|
889
|
+
const _vec$1 = new Vector3();
|
|
890
|
+
const _triangleNormal = new Vector3();
|
|
891
|
+
const _planeNormal = new Vector3();
|
|
892
|
+
const _plane$1 = new Plane();
|
|
893
|
+
const _splittingTriangle = new ExtendedTriangle();
|
|
894
|
+
|
|
895
|
+
// A pool of triangles to avoid unnecessary triangle creation
|
|
896
|
+
class TrianglePool {
|
|
897
|
+
|
|
898
|
+
constructor() {
|
|
899
|
+
|
|
900
|
+
this._pool = [];
|
|
901
|
+
this._index = 0;
|
|
902
|
+
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
getTriangle() {
|
|
906
|
+
|
|
907
|
+
if ( this._index >= this._pool.length ) {
|
|
908
|
+
|
|
909
|
+
this._pool.push( new Triangle() );
|
|
910
|
+
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
return this._pool[ this._index ++ ];
|
|
914
|
+
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
clear() {
|
|
918
|
+
|
|
919
|
+
this._index = 0;
|
|
920
|
+
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
reset() {
|
|
924
|
+
|
|
925
|
+
this._pool.length = 0;
|
|
926
|
+
this._index = 0;
|
|
927
|
+
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// Utility class for splitting triangles
|
|
933
|
+
class TriangleSplitter {
|
|
934
|
+
|
|
935
|
+
constructor() {
|
|
936
|
+
|
|
937
|
+
this.trianglePool = new TrianglePool();
|
|
938
|
+
this.triangles = [];
|
|
939
|
+
this.normal = new Vector3();
|
|
940
|
+
this.coplanarTriangleUsed = false;
|
|
941
|
+
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// initialize the class with a triangle
|
|
945
|
+
initialize( tri ) {
|
|
946
|
+
|
|
947
|
+
this.reset();
|
|
948
|
+
|
|
949
|
+
const { triangles, trianglePool, normal } = this;
|
|
950
|
+
if ( Array.isArray( tri ) ) {
|
|
951
|
+
|
|
952
|
+
for ( let i = 0, l = tri.length; i < l; i ++ ) {
|
|
953
|
+
|
|
954
|
+
const t = tri[ i ];
|
|
955
|
+
if ( i === 0 ) {
|
|
956
|
+
|
|
957
|
+
t.getNormal( normal );
|
|
958
|
+
|
|
959
|
+
} else if ( Math.abs( 1.0 - t.getNormal( _vec$1 ).dot( normal ) ) > EPSILON ) {
|
|
960
|
+
|
|
961
|
+
throw new Error( 'Triangle Splitter: Cannot initialize with triangles that have different normals.' );
|
|
962
|
+
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
const poolTri = trianglePool.getTriangle();
|
|
966
|
+
poolTri.copy( t );
|
|
967
|
+
triangles.push( poolTri );
|
|
968
|
+
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
} else {
|
|
972
|
+
|
|
973
|
+
tri.getNormal( normal );
|
|
974
|
+
|
|
975
|
+
const poolTri = trianglePool.getTriangle();
|
|
976
|
+
poolTri.copy( tri );
|
|
977
|
+
triangles.push( poolTri );
|
|
978
|
+
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// Split the current set of triangles by passing a single triangle in. If the triangle is
|
|
984
|
+
// coplanar it will attempt to split by the triangle edge planes
|
|
985
|
+
splitByTriangle( triangle ) {
|
|
986
|
+
|
|
987
|
+
const { normal, triangles } = this;
|
|
988
|
+
triangle.getNormal( _triangleNormal ).normalize();
|
|
989
|
+
|
|
990
|
+
if ( Math.abs( 1.0 - Math.abs( _triangleNormal.dot( normal ) ) ) < PARALLEL_EPSILON ) {
|
|
991
|
+
|
|
992
|
+
this.coplanarTriangleUsed = true;
|
|
993
|
+
|
|
994
|
+
for ( let i = 0, l = triangles.length; i < l; i ++ ) {
|
|
995
|
+
|
|
996
|
+
const t = triangles[ i ];
|
|
997
|
+
t.coplanarCount = 0;
|
|
998
|
+
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// if the triangle is coplanar then split by the edge planes
|
|
1002
|
+
const arr = [ triangle.a, triangle.b, triangle.c ];
|
|
1003
|
+
for ( let i = 0; i < 3; i ++ ) {
|
|
1004
|
+
|
|
1005
|
+
const nexti = ( i + 1 ) % 3;
|
|
1006
|
+
|
|
1007
|
+
const v0 = arr[ i ];
|
|
1008
|
+
const v1 = arr[ nexti ];
|
|
1009
|
+
|
|
1010
|
+
// plane positive direction is toward triangle center
|
|
1011
|
+
_vec$1.subVectors( v1, v0 ).normalize();
|
|
1012
|
+
_planeNormal.crossVectors( _triangleNormal, _vec$1 );
|
|
1013
|
+
_plane$1.setFromNormalAndCoplanarPoint( _planeNormal, v0 );
|
|
1014
|
+
|
|
1015
|
+
this.splitByPlane( _plane$1, triangle );
|
|
1016
|
+
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
} else {
|
|
1020
|
+
|
|
1021
|
+
// otherwise split by the triangle plane
|
|
1022
|
+
triangle.getPlane( _plane$1 );
|
|
1023
|
+
this.splitByPlane( _plane$1, triangle );
|
|
1024
|
+
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// Split the triangles by the given plan. If a triangle is provided then we ensure we
|
|
1030
|
+
// intersect the triangle before splitting the plane
|
|
1031
|
+
splitByPlane( plane, clippingTriangle ) {
|
|
1032
|
+
|
|
1033
|
+
const { triangles, trianglePool } = this;
|
|
1034
|
+
|
|
1035
|
+
// init our triangle to check for intersection
|
|
1036
|
+
_splittingTriangle.copy( clippingTriangle );
|
|
1037
|
+
_splittingTriangle.needsUpdate = true;
|
|
1038
|
+
|
|
1039
|
+
// try to split every triangle in the class
|
|
1040
|
+
for ( let i = 0, l = triangles.length; i < l; i ++ ) {
|
|
1041
|
+
|
|
1042
|
+
const tri = triangles[ i ];
|
|
1043
|
+
|
|
1044
|
+
// skip the triangle if we don't intersect with it
|
|
1045
|
+
if ( ! _splittingTriangle.intersectsTriangle( tri, _edge$2, true ) ) {
|
|
1046
|
+
|
|
1047
|
+
continue;
|
|
1048
|
+
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
const { a, b, c } = tri;
|
|
1052
|
+
let intersects = 0;
|
|
1053
|
+
let vertexSplitEnd = - 1;
|
|
1054
|
+
let coplanarEdge = false;
|
|
1055
|
+
let posSideVerts = [];
|
|
1056
|
+
let negSideVerts = [];
|
|
1057
|
+
const arr = [ a, b, c ];
|
|
1058
|
+
for ( let t = 0; t < 3; t ++ ) {
|
|
1059
|
+
|
|
1060
|
+
// get the triangle edge
|
|
1061
|
+
const tNext = ( t + 1 ) % 3;
|
|
1062
|
+
_edge$2.start.copy( arr[ t ] );
|
|
1063
|
+
_edge$2.end.copy( arr[ tNext ] );
|
|
1064
|
+
|
|
1065
|
+
// track if the start point sits on the plane or if it's on the positive side of it
|
|
1066
|
+
// so we can use that information to determine whether to split later.
|
|
1067
|
+
const startDist = plane.distanceToPoint( _edge$2.start );
|
|
1068
|
+
const endDist = plane.distanceToPoint( _edge$2.end );
|
|
1069
|
+
if ( Math.abs( startDist ) < COPLANAR_EPSILON && Math.abs( endDist ) < COPLANAR_EPSILON ) {
|
|
1070
|
+
|
|
1071
|
+
coplanarEdge = true;
|
|
1072
|
+
break;
|
|
1073
|
+
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
if ( startDist > 0 ) {
|
|
1077
|
+
|
|
1078
|
+
posSideVerts.push( t );
|
|
1079
|
+
|
|
1080
|
+
} else {
|
|
1081
|
+
|
|
1082
|
+
negSideVerts.push( t );
|
|
1083
|
+
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// we only don't consider this an intersection if the start points hits the plane
|
|
1087
|
+
if ( Math.abs( startDist ) < COPLANAR_EPSILON ) {
|
|
1088
|
+
|
|
1089
|
+
continue;
|
|
1090
|
+
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// double check the end point since the "intersectLine" function sometimes does not
|
|
1094
|
+
// return it as an intersection (see issue #28)
|
|
1095
|
+
// Because we ignore the start point intersection above we have to make sure we check the end
|
|
1096
|
+
// point intersection here.
|
|
1097
|
+
let didIntersect = ! ! plane.intersectLine( _edge$2, _vec$1 );
|
|
1098
|
+
if ( ! didIntersect && Math.abs( endDist ) < COPLANAR_EPSILON ) {
|
|
1099
|
+
|
|
1100
|
+
_vec$1.copy( _edge$2.end );
|
|
1101
|
+
didIntersect = true;
|
|
1102
|
+
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// check if we intersect the plane (ignoring the start point so we don't double count)
|
|
1106
|
+
if ( didIntersect && ! ( _vec$1.distanceTo( _edge$2.start ) < EPSILON ) ) {
|
|
1107
|
+
|
|
1108
|
+
// if we intersect at the end point then we track that point as one that we
|
|
1109
|
+
// have to split down the middle
|
|
1110
|
+
if ( _vec$1.distanceTo( _edge$2.end ) < EPSILON ) {
|
|
1111
|
+
|
|
1112
|
+
vertexSplitEnd = t;
|
|
1113
|
+
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// track the split edge
|
|
1117
|
+
if ( intersects === 0 ) {
|
|
1118
|
+
|
|
1119
|
+
_foundEdge.start.copy( _vec$1 );
|
|
1120
|
+
|
|
1121
|
+
} else {
|
|
1122
|
+
|
|
1123
|
+
_foundEdge.end.copy( _vec$1 );
|
|
1124
|
+
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
intersects ++;
|
|
1128
|
+
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// skip splitting if:
|
|
1134
|
+
// - we have two points on the plane then the plane intersects the triangle exactly on an edge
|
|
1135
|
+
// - the plane does not intersect on 2 points
|
|
1136
|
+
// - the intersection edge is too small
|
|
1137
|
+
// - we're not along a coplanar edge
|
|
1138
|
+
if ( ! coplanarEdge && intersects === 2 && _foundEdge.distance() > COPLANAR_EPSILON ) {
|
|
1139
|
+
|
|
1140
|
+
if ( vertexSplitEnd !== - 1 ) {
|
|
1141
|
+
|
|
1142
|
+
vertexSplitEnd = ( vertexSplitEnd + 1 ) % 3;
|
|
1143
|
+
|
|
1144
|
+
// we're splitting along a vertex
|
|
1145
|
+
let otherVert1 = 0;
|
|
1146
|
+
if ( otherVert1 === vertexSplitEnd ) {
|
|
1147
|
+
|
|
1148
|
+
otherVert1 = ( otherVert1 + 1 ) % 3;
|
|
1149
|
+
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
let otherVert2 = otherVert1 + 1;
|
|
1153
|
+
if ( otherVert2 === vertexSplitEnd ) {
|
|
1154
|
+
|
|
1155
|
+
otherVert2 = ( otherVert2 + 1 ) % 3;
|
|
1156
|
+
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
const nextTri = trianglePool.getTriangle();
|
|
1160
|
+
nextTri.a.copy( arr[ otherVert2 ] );
|
|
1161
|
+
nextTri.b.copy( _foundEdge.end );
|
|
1162
|
+
nextTri.c.copy( _foundEdge.start );
|
|
1163
|
+
|
|
1164
|
+
if ( ! isTriDegenerate( nextTri ) ) {
|
|
1165
|
+
|
|
1166
|
+
triangles.push( nextTri );
|
|
1167
|
+
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
tri.a.copy( arr[ otherVert1 ] );
|
|
1171
|
+
tri.b.copy( _foundEdge.start );
|
|
1172
|
+
tri.c.copy( _foundEdge.end );
|
|
1173
|
+
|
|
1174
|
+
// finish off the adjusted triangle
|
|
1175
|
+
if ( isTriDegenerate( tri ) ) {
|
|
1176
|
+
|
|
1177
|
+
triangles.splice( i, 1 );
|
|
1178
|
+
i --;
|
|
1179
|
+
l --;
|
|
1180
|
+
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
} else {
|
|
1184
|
+
|
|
1185
|
+
// we're splitting with a quad and a triangle
|
|
1186
|
+
// TODO: what happens when we find that about the pos and negative
|
|
1187
|
+
// sides have only a single vertex?
|
|
1188
|
+
const singleVert =
|
|
1189
|
+
posSideVerts.length >= 2 ?
|
|
1190
|
+
negSideVerts[ 0 ] :
|
|
1191
|
+
posSideVerts[ 0 ];
|
|
1192
|
+
|
|
1193
|
+
// swap the direction of the intersection edge depending on which
|
|
1194
|
+
// side of the plane the single vertex is on to align with the
|
|
1195
|
+
// correct winding order.
|
|
1196
|
+
if ( singleVert === 0 ) {
|
|
1197
|
+
|
|
1198
|
+
let tmp = _foundEdge.start;
|
|
1199
|
+
_foundEdge.start = _foundEdge.end;
|
|
1200
|
+
_foundEdge.end = tmp;
|
|
1201
|
+
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const nextVert1 = ( singleVert + 1 ) % 3;
|
|
1205
|
+
const nextVert2 = ( singleVert + 2 ) % 3;
|
|
1206
|
+
|
|
1207
|
+
const nextTri1 = trianglePool.getTriangle();
|
|
1208
|
+
const nextTri2 = trianglePool.getTriangle();
|
|
1209
|
+
|
|
1210
|
+
// choose the triangle that has the larger areas (shortest split distance)
|
|
1211
|
+
if ( arr[ nextVert1 ].distanceToSquared( _foundEdge.start ) < arr[ nextVert2 ].distanceToSquared( _foundEdge.end ) ) {
|
|
1212
|
+
|
|
1213
|
+
nextTri1.a.copy( arr[ nextVert1 ] );
|
|
1214
|
+
nextTri1.b.copy( _foundEdge.start );
|
|
1215
|
+
nextTri1.c.copy( _foundEdge.end );
|
|
1216
|
+
|
|
1217
|
+
nextTri2.a.copy( arr[ nextVert1 ] );
|
|
1218
|
+
nextTri2.b.copy( arr[ nextVert2 ] );
|
|
1219
|
+
nextTri2.c.copy( _foundEdge.start );
|
|
1220
|
+
|
|
1221
|
+
} else {
|
|
1222
|
+
|
|
1223
|
+
nextTri1.a.copy( arr[ nextVert2 ] );
|
|
1224
|
+
nextTri1.b.copy( _foundEdge.start );
|
|
1225
|
+
nextTri1.c.copy( _foundEdge.end );
|
|
1226
|
+
|
|
1227
|
+
nextTri2.a.copy( arr[ nextVert1 ] );
|
|
1228
|
+
nextTri2.b.copy( arr[ nextVert2 ] );
|
|
1229
|
+
nextTri2.c.copy( _foundEdge.end );
|
|
1230
|
+
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
tri.a.copy( arr[ singleVert ] );
|
|
1234
|
+
tri.b.copy( _foundEdge.end );
|
|
1235
|
+
tri.c.copy( _foundEdge.start );
|
|
1236
|
+
|
|
1237
|
+
// don't add degenerate triangles to the list
|
|
1238
|
+
if ( ! isTriDegenerate( nextTri1 ) ) {
|
|
1239
|
+
|
|
1240
|
+
triangles.push( nextTri1 );
|
|
1241
|
+
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
if ( ! isTriDegenerate( nextTri2 ) ) {
|
|
1245
|
+
|
|
1246
|
+
triangles.push( nextTri2 );
|
|
1247
|
+
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// finish off the adjusted triangle
|
|
1251
|
+
if ( isTriDegenerate( tri ) ) {
|
|
1252
|
+
|
|
1253
|
+
triangles.splice( i, 1 );
|
|
1254
|
+
i --;
|
|
1255
|
+
l --;
|
|
1256
|
+
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
} else if ( intersects === 3 ) {
|
|
1262
|
+
|
|
1263
|
+
console.warn( 'TriangleClipper: Coplanar clip not handled' );
|
|
1264
|
+
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
reset() {
|
|
1272
|
+
|
|
1273
|
+
this.triangles.length = 0;
|
|
1274
|
+
this.trianglePool.clear();
|
|
1275
|
+
this.coplanarTriangleUsed = false;
|
|
1276
|
+
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
function ceilToFourByteStride( byteLength ) {
|
|
1282
|
+
|
|
1283
|
+
byteLength = ~ ~ byteLength;
|
|
1284
|
+
return byteLength + 4 - byteLength % 4;
|
|
1285
|
+
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// Make a new array wrapper class that more easily affords expansion when reaching it's max capacity
|
|
1289
|
+
class TypeBackedArray {
|
|
1290
|
+
|
|
1291
|
+
constructor( type, initialSize = 500 ) {
|
|
1292
|
+
|
|
1293
|
+
|
|
1294
|
+
this.expansionFactor = 1.5;
|
|
1295
|
+
this.type = type;
|
|
1296
|
+
this.length = 0;
|
|
1297
|
+
this.array = null;
|
|
1298
|
+
|
|
1299
|
+
this.setSize( initialSize );
|
|
1300
|
+
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
setType( type ) {
|
|
1304
|
+
|
|
1305
|
+
if ( this.length !== 0 ) {
|
|
1306
|
+
|
|
1307
|
+
throw new Error( 'TypeBackedArray: Cannot change the type while there is used data in the buffer.' );
|
|
1308
|
+
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
const buffer = this.array.buffer;
|
|
1312
|
+
this.array = new type( buffer );
|
|
1313
|
+
this.type = type;
|
|
1314
|
+
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
setSize( size ) {
|
|
1318
|
+
|
|
1319
|
+
if ( this.array && size === this.array.length ) {
|
|
1320
|
+
|
|
1321
|
+
return;
|
|
1322
|
+
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
// ceil to the nearest 4 bytes so we can replace the array with any type using the same buffer
|
|
1326
|
+
const type = this.type;
|
|
1327
|
+
const bufferType = areSharedArrayBuffersSupported() ? SharedArrayBuffer : ArrayBuffer;
|
|
1328
|
+
const newArray = new type( new bufferType( ceilToFourByteStride( size * type.BYTES_PER_ELEMENT ) ) );
|
|
1329
|
+
if ( this.array ) {
|
|
1330
|
+
|
|
1331
|
+
newArray.set( this.array, 0 );
|
|
1332
|
+
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
this.array = newArray;
|
|
1336
|
+
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
expand() {
|
|
1340
|
+
|
|
1341
|
+
const { array, expansionFactor } = this;
|
|
1342
|
+
this.setSize( array.length * expansionFactor );
|
|
1343
|
+
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
push( ...args ) {
|
|
1347
|
+
|
|
1348
|
+
let { array, length } = this;
|
|
1349
|
+
if ( length + args.length > array.length ) {
|
|
1350
|
+
|
|
1351
|
+
this.expand();
|
|
1352
|
+
array = this.array;
|
|
1353
|
+
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
for ( let i = 0, l = args.length; i < l; i ++ ) {
|
|
1357
|
+
|
|
1358
|
+
array[ length + i ] = args[ i ];
|
|
1359
|
+
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
this.length += args.length;
|
|
1363
|
+
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
clear() {
|
|
1367
|
+
|
|
1368
|
+
this.length = 0;
|
|
1369
|
+
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// Utility class for for tracking attribute data in type-backed arrays for a set
|
|
1375
|
+
// of groups. The set of attributes is kept for each group and are expected to be the
|
|
1376
|
+
// same buffer type.
|
|
1377
|
+
class TypedAttributeData {
|
|
1378
|
+
|
|
1379
|
+
constructor() {
|
|
1380
|
+
|
|
1381
|
+
this.groupAttributes = [ {} ];
|
|
1382
|
+
this.groupCount = 0;
|
|
1383
|
+
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// returns the buffer type for the given attribute
|
|
1387
|
+
getType( name ) {
|
|
1388
|
+
|
|
1389
|
+
return this.groupAttributes[ 0 ][ name ].type;
|
|
1390
|
+
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
getItemSize( name ) {
|
|
1394
|
+
|
|
1395
|
+
return this.groupAttributes[ 0 ][ name ].itemSize;
|
|
1396
|
+
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
getNormalized( name ) {
|
|
1400
|
+
|
|
1401
|
+
return this.groupAttributes[ 0 ][ name ].normalized;
|
|
1402
|
+
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
getCount( index ) {
|
|
1406
|
+
|
|
1407
|
+
if ( this.groupCount <= index ) {
|
|
1408
|
+
|
|
1409
|
+
return 0;
|
|
1410
|
+
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
const pos = this.getGroupAttrArray( 'position', index );
|
|
1414
|
+
return pos.length / pos.itemSize;
|
|
1415
|
+
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// returns the total length required for all groups for the given attribute
|
|
1419
|
+
getTotalLength( name ) {
|
|
1420
|
+
|
|
1421
|
+
const { groupCount, groupAttributes } = this;
|
|
1422
|
+
|
|
1423
|
+
let length = 0;
|
|
1424
|
+
for ( let i = 0; i < groupCount; i ++ ) {
|
|
1425
|
+
|
|
1426
|
+
const attrSet = groupAttributes[ i ];
|
|
1427
|
+
length += attrSet[ name ].length;
|
|
1428
|
+
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
return length;
|
|
1432
|
+
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
getGroupAttrSet( index = 0 ) {
|
|
1436
|
+
|
|
1437
|
+
// TODO: can this be abstracted?
|
|
1438
|
+
// Return the exiting group set if necessary
|
|
1439
|
+
const { groupAttributes } = this;
|
|
1440
|
+
if ( groupAttributes[ index ] ) {
|
|
1441
|
+
|
|
1442
|
+
this.groupCount = Math.max( this.groupCount, index + 1 );
|
|
1443
|
+
return groupAttributes[ index ];
|
|
1444
|
+
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
// add any new group sets required
|
|
1448
|
+
const refAttrSet = groupAttributes[ 0 ];
|
|
1449
|
+
this.groupCount = Math.max( this.groupCount, index + 1 );
|
|
1450
|
+
while ( index >= groupAttributes.length ) {
|
|
1451
|
+
|
|
1452
|
+
const newAttrSet = {};
|
|
1453
|
+
groupAttributes.push( newAttrSet );
|
|
1454
|
+
for ( const key in refAttrSet ) {
|
|
1455
|
+
|
|
1456
|
+
const refAttr = refAttrSet[ key ];
|
|
1457
|
+
const newAttr = new TypeBackedArray( refAttr.type );
|
|
1458
|
+
newAttr.itemSize = refAttr.itemSize;
|
|
1459
|
+
newAttr.normalized = refAttr.normalized;
|
|
1460
|
+
newAttrSet[ key ] = newAttr;
|
|
1461
|
+
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
return groupAttributes[ index ];
|
|
1467
|
+
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
// Get the raw array for the group set of data
|
|
1471
|
+
getGroupAttrArray( name, index = 0 ) {
|
|
1472
|
+
|
|
1473
|
+
// throw an error if we've never
|
|
1474
|
+
const { groupAttributes } = this;
|
|
1475
|
+
const referenceAttrSet = groupAttributes[ 0 ];
|
|
1476
|
+
const referenceAttr = referenceAttrSet[ name ];
|
|
1477
|
+
if ( ! referenceAttr ) {
|
|
1478
|
+
|
|
1479
|
+
throw new Error( `TypedAttributeData: Attribute with "${ name }" has not been initialized` );
|
|
1480
|
+
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
return this.getGroupAttrSet( index )[ name ];
|
|
1484
|
+
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
// initializes an attribute array with the given name, type, and size
|
|
1488
|
+
initializeArray( name, type, itemSize, normalized ) {
|
|
1489
|
+
|
|
1490
|
+
const { groupAttributes } = this;
|
|
1491
|
+
const referenceAttrSet = groupAttributes[ 0 ];
|
|
1492
|
+
const referenceAttr = referenceAttrSet[ name ];
|
|
1493
|
+
if ( referenceAttr ) {
|
|
1494
|
+
|
|
1495
|
+
if ( referenceAttr.type !== type ) {
|
|
1496
|
+
|
|
1497
|
+
for ( let i = 0, l = groupAttributes.length; i < l; i ++ ) {
|
|
1498
|
+
|
|
1499
|
+
const arr = groupAttributes[ i ][ name ];
|
|
1500
|
+
arr.setType( type );
|
|
1501
|
+
arr.itemSize = itemSize;
|
|
1502
|
+
arr.normalized = normalized;
|
|
1503
|
+
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
} else {
|
|
1509
|
+
|
|
1510
|
+
for ( let i = 0, l = groupAttributes.length; i < l; i ++ ) {
|
|
1511
|
+
|
|
1512
|
+
const arr = new TypeBackedArray( type );
|
|
1513
|
+
arr.itemSize = itemSize;
|
|
1514
|
+
arr.normalized = normalized;
|
|
1515
|
+
groupAttributes[ i ][ name ] = arr;
|
|
1516
|
+
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
// Clear all the data
|
|
1524
|
+
clear() {
|
|
1525
|
+
|
|
1526
|
+
this.groupCount = 0;
|
|
1527
|
+
|
|
1528
|
+
const { groupAttributes } = this;
|
|
1529
|
+
groupAttributes.forEach( attrSet => {
|
|
1530
|
+
|
|
1531
|
+
for ( const key in attrSet ) {
|
|
1532
|
+
|
|
1533
|
+
attrSet[ key ].clear();
|
|
1534
|
+
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
|
|
1538
|
+
} );
|
|
1539
|
+
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// Remove the given key
|
|
1543
|
+
delete( key ) {
|
|
1544
|
+
|
|
1545
|
+
this.groupAttributes.forEach( attrSet => {
|
|
1546
|
+
|
|
1547
|
+
delete attrSet[ key ];
|
|
1548
|
+
|
|
1549
|
+
} );
|
|
1550
|
+
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// Reset the datasets completely
|
|
1554
|
+
reset() {
|
|
1555
|
+
|
|
1556
|
+
this.groupAttributes = [];
|
|
1557
|
+
this.groupCount = 0;
|
|
1558
|
+
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
class IntersectionMap {
|
|
1564
|
+
|
|
1565
|
+
constructor() {
|
|
1566
|
+
|
|
1567
|
+
this.intersectionSet = {};
|
|
1568
|
+
this.ids = [];
|
|
1569
|
+
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
add( id, intersectionId ) {
|
|
1573
|
+
|
|
1574
|
+
const { intersectionSet, ids } = this;
|
|
1575
|
+
if ( ! intersectionSet[ id ] ) {
|
|
1576
|
+
|
|
1577
|
+
intersectionSet[ id ] = [];
|
|
1578
|
+
ids.push( id );
|
|
1579
|
+
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
intersectionSet[ id ].push( intersectionId );
|
|
1583
|
+
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
const ADDITION = 0;
|
|
1589
|
+
const SUBTRACTION = 1;
|
|
1590
|
+
const REVERSE_SUBTRACTION = 2;
|
|
1591
|
+
const INTERSECTION = 3;
|
|
1592
|
+
const DIFFERENCE = 4;
|
|
1593
|
+
|
|
1594
|
+
// guaranteed non manifold results
|
|
1595
|
+
const HOLLOW_SUBTRACTION = 5;
|
|
1596
|
+
const HOLLOW_INTERSECTION = 6;
|
|
1597
|
+
|
|
1598
|
+
const _ray$1 = new Ray();
|
|
1599
|
+
const _matrix$2 = new Matrix4();
|
|
1600
|
+
const _tri$2 = new Triangle();
|
|
1601
|
+
const _vec3 = new Vector3();
|
|
1602
|
+
const _vec4a = new Vector4();
|
|
1603
|
+
const _vec4b = new Vector4();
|
|
1604
|
+
const _vec4c = new Vector4();
|
|
1605
|
+
const _vec4_0 = new Vector4();
|
|
1606
|
+
const _vec4_1 = new Vector4();
|
|
1607
|
+
const _vec4_2 = new Vector4();
|
|
1608
|
+
const _edge$1 = new Line3();
|
|
1609
|
+
const _normal$1 = new Vector3();
|
|
1610
|
+
const JITTER_EPSILON = 1e-8;
|
|
1611
|
+
const OFFSET_EPSILON = 1e-15;
|
|
1612
|
+
|
|
1613
|
+
const BACK_SIDE = - 1;
|
|
1614
|
+
const FRONT_SIDE = 1;
|
|
1615
|
+
const COPLANAR_OPPOSITE = - 2;
|
|
1616
|
+
const COPLANAR_ALIGNED = 2;
|
|
1617
|
+
|
|
1618
|
+
const INVERT_TRI = 0;
|
|
1619
|
+
const ADD_TRI = 1;
|
|
1620
|
+
const SKIP_TRI = 2;
|
|
1621
|
+
|
|
1622
|
+
const FLOATING_COPLANAR_EPSILON = 1e-14;
|
|
1623
|
+
|
|
1624
|
+
let _debugContext = null;
|
|
1625
|
+
function setDebugContext( debugData ) {
|
|
1626
|
+
|
|
1627
|
+
_debugContext = debugData;
|
|
1628
|
+
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
function getHitSide( tri, bvh ) {
|
|
1632
|
+
|
|
1633
|
+
tri.getMidpoint( _ray$1.origin );
|
|
1634
|
+
tri.getNormal( _ray$1.direction );
|
|
1635
|
+
|
|
1636
|
+
const hit = bvh.raycastFirst( _ray$1, DoubleSide );
|
|
1637
|
+
const hitBackSide = Boolean( hit && _ray$1.direction.dot( hit.face.normal ) > 0 );
|
|
1638
|
+
return hitBackSide ? BACK_SIDE : FRONT_SIDE;
|
|
1639
|
+
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
function getHitSideWithCoplanarCheck( tri, bvh ) {
|
|
1643
|
+
|
|
1644
|
+
// random function that returns [ - 0.5, 0.5 ];
|
|
1645
|
+
function rand() {
|
|
1646
|
+
|
|
1647
|
+
return Math.random() - 0.5;
|
|
1648
|
+
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
// get the ray the check the triangle for
|
|
1652
|
+
tri.getNormal( _normal$1 );
|
|
1653
|
+
_ray$1.direction.copy( _normal$1 );
|
|
1654
|
+
tri.getMidpoint( _ray$1.origin );
|
|
1655
|
+
|
|
1656
|
+
const total = 3;
|
|
1657
|
+
let count = 0;
|
|
1658
|
+
let minDistance = Infinity;
|
|
1659
|
+
for ( let i = 0; i < total; i ++ ) {
|
|
1660
|
+
|
|
1661
|
+
// jitter the ray slightly
|
|
1662
|
+
_ray$1.direction.x += rand() * JITTER_EPSILON;
|
|
1663
|
+
_ray$1.direction.y += rand() * JITTER_EPSILON;
|
|
1664
|
+
_ray$1.direction.z += rand() * JITTER_EPSILON;
|
|
1665
|
+
|
|
1666
|
+
// and invert it so we can account for floating point error by checking both directions
|
|
1667
|
+
// to catch coplanar distances
|
|
1668
|
+
_ray$1.direction.multiplyScalar( - 1 );
|
|
1669
|
+
|
|
1670
|
+
// check if the ray hit the backside
|
|
1671
|
+
const hit = bvh.raycastFirst( _ray$1, DoubleSide );
|
|
1672
|
+
let hitBackSide = Boolean( hit && _ray$1.direction.dot( hit.face.normal ) > 0 );
|
|
1673
|
+
if ( hitBackSide ) {
|
|
1674
|
+
|
|
1675
|
+
count ++;
|
|
1676
|
+
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
if ( hit !== null ) {
|
|
1680
|
+
|
|
1681
|
+
minDistance = Math.min( minDistance, hit.distance );
|
|
1682
|
+
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
// if we're right up against another face then we're coplanar
|
|
1686
|
+
if ( minDistance <= OFFSET_EPSILON ) {
|
|
1687
|
+
|
|
1688
|
+
return hit.face.normal.dot( _normal$1 ) > 0 ? COPLANAR_ALIGNED : COPLANAR_OPPOSITE;
|
|
1689
|
+
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
// if our current casts meet our requirements then early out
|
|
1693
|
+
if ( count / total > 0.5 || ( i - count + 1 ) / total > 0.5 ) {
|
|
1694
|
+
|
|
1695
|
+
break;
|
|
1696
|
+
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
return count / total > 0.5 ? BACK_SIDE : FRONT_SIDE;
|
|
1702
|
+
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
// returns the intersected triangles and returns objects mapping triangle indices to
|
|
1706
|
+
// the other triangles intersected
|
|
1707
|
+
function collectIntersectingTriangles( a, b ) {
|
|
1708
|
+
|
|
1709
|
+
const aIntersections = new IntersectionMap();
|
|
1710
|
+
const bIntersections = new IntersectionMap();
|
|
1711
|
+
|
|
1712
|
+
_matrix$2
|
|
1713
|
+
.copy( a.matrixWorld )
|
|
1714
|
+
.invert()
|
|
1715
|
+
.multiply( b.matrixWorld );
|
|
1716
|
+
|
|
1717
|
+
a.geometry.boundsTree.bvhcast( b.geometry.boundsTree, _matrix$2, {
|
|
1718
|
+
|
|
1719
|
+
intersectsTriangles( triangleA, triangleB, ia, ib ) {
|
|
1720
|
+
|
|
1721
|
+
if ( ! isTriDegenerate( triangleA ) && ! isTriDegenerate( triangleB ) ) {
|
|
1722
|
+
|
|
1723
|
+
// due to floating point error it's possible that we can have two overlapping, coplanar triangles
|
|
1724
|
+
// that are a _tiny_ fraction of a value away from each other. If we find that case then check the
|
|
1725
|
+
// distance between triangles and if it's small enough consider them intersecting.
|
|
1726
|
+
let intersected = triangleA.intersectsTriangle( triangleB, _edge$1, true );
|
|
1727
|
+
if ( ! intersected ) {
|
|
1728
|
+
|
|
1729
|
+
const pa = triangleA.plane;
|
|
1730
|
+
const pb = triangleB.plane;
|
|
1731
|
+
const na = pa.normal;
|
|
1732
|
+
const nb = pb.normal;
|
|
1733
|
+
|
|
1734
|
+
if ( na.dot( nb ) === 1 && Math.abs( pa.constant - pb.constant ) < FLOATING_COPLANAR_EPSILON ) {
|
|
1735
|
+
|
|
1736
|
+
intersected = true;
|
|
1737
|
+
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
if ( intersected ) {
|
|
1743
|
+
|
|
1744
|
+
let va = a.geometry.boundsTree.resolveTriangleIndex( ia );
|
|
1745
|
+
let vb = b.geometry.boundsTree.resolveTriangleIndex( ib );
|
|
1746
|
+
aIntersections.add( va, vb );
|
|
1747
|
+
bIntersections.add( vb, va );
|
|
1748
|
+
|
|
1749
|
+
if ( _debugContext ) {
|
|
1750
|
+
|
|
1751
|
+
_debugContext.addEdge( _edge$1 );
|
|
1752
|
+
_debugContext.addIntersectingTriangles( ia, triangleA, ib, triangleB );
|
|
1753
|
+
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
return false;
|
|
1761
|
+
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
} );
|
|
1765
|
+
|
|
1766
|
+
return { aIntersections, bIntersections };
|
|
1767
|
+
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// Add the barycentric interpolated values fro the triangle into the new attribute data
|
|
1771
|
+
function appendAttributeFromTriangle(
|
|
1772
|
+
triIndex,
|
|
1773
|
+
baryCoordTri,
|
|
1774
|
+
geometry,
|
|
1775
|
+
matrixWorld,
|
|
1776
|
+
normalMatrix,
|
|
1777
|
+
attributeData,
|
|
1778
|
+
invert = false,
|
|
1779
|
+
) {
|
|
1780
|
+
|
|
1781
|
+
const attributes = geometry.attributes;
|
|
1782
|
+
const indexAttr = geometry.index;
|
|
1783
|
+
const i3 = triIndex * 3;
|
|
1784
|
+
const i0 = indexAttr.getX( i3 + 0 );
|
|
1785
|
+
const i1 = indexAttr.getX( i3 + 1 );
|
|
1786
|
+
const i2 = indexAttr.getX( i3 + 2 );
|
|
1787
|
+
|
|
1788
|
+
for ( const key in attributeData ) {
|
|
1789
|
+
|
|
1790
|
+
// check if the key we're asking for is in the geometry at all
|
|
1791
|
+
const attr = attributes[ key ];
|
|
1792
|
+
const arr = attributeData[ key ];
|
|
1793
|
+
if ( ! ( key in attributes ) ) {
|
|
1794
|
+
|
|
1795
|
+
throw new Error( `CSG Operations: Attribute ${ key } not available on geometry.` );
|
|
1796
|
+
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
// handle normals and positions specially because they require transforming
|
|
1800
|
+
// TODO: handle tangents
|
|
1801
|
+
const itemSize = attr.itemSize;
|
|
1802
|
+
if ( key === 'position' ) {
|
|
1803
|
+
|
|
1804
|
+
_tri$2.a.fromBufferAttribute( attr, i0 ).applyMatrix4( matrixWorld );
|
|
1805
|
+
_tri$2.b.fromBufferAttribute( attr, i1 ).applyMatrix4( matrixWorld );
|
|
1806
|
+
_tri$2.c.fromBufferAttribute( attr, i2 ).applyMatrix4( matrixWorld );
|
|
1807
|
+
|
|
1808
|
+
pushBarycoordInterpolatedValues( _tri$2.a, _tri$2.b, _tri$2.c, baryCoordTri, 3, arr, invert );
|
|
1809
|
+
|
|
1810
|
+
} else if ( key === 'normal' ) {
|
|
1811
|
+
|
|
1812
|
+
_tri$2.a.fromBufferAttribute( attr, i0 ).applyNormalMatrix( normalMatrix );
|
|
1813
|
+
_tri$2.b.fromBufferAttribute( attr, i1 ).applyNormalMatrix( normalMatrix );
|
|
1814
|
+
_tri$2.c.fromBufferAttribute( attr, i2 ).applyNormalMatrix( normalMatrix );
|
|
1815
|
+
|
|
1816
|
+
if ( invert ) {
|
|
1817
|
+
|
|
1818
|
+
_tri$2.a.multiplyScalar( - 1 );
|
|
1819
|
+
_tri$2.b.multiplyScalar( - 1 );
|
|
1820
|
+
_tri$2.c.multiplyScalar( - 1 );
|
|
1821
|
+
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
pushBarycoordInterpolatedValues( _tri$2.a, _tri$2.b, _tri$2.c, baryCoordTri, 3, arr, invert, true );
|
|
1825
|
+
|
|
1826
|
+
} else {
|
|
1827
|
+
|
|
1828
|
+
_vec4a.fromBufferAttribute( attr, i0 );
|
|
1829
|
+
_vec4b.fromBufferAttribute( attr, i1 );
|
|
1830
|
+
_vec4c.fromBufferAttribute( attr, i2 );
|
|
1831
|
+
|
|
1832
|
+
pushBarycoordInterpolatedValues( _vec4a, _vec4b, _vec4c, baryCoordTri, itemSize, arr, invert );
|
|
1833
|
+
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
// Append all the values of the attributes for the triangle onto the new attribute arrays
|
|
1841
|
+
function appendAttributesFromIndices(
|
|
1842
|
+
i0,
|
|
1843
|
+
i1,
|
|
1844
|
+
i2,
|
|
1845
|
+
attributes,
|
|
1846
|
+
matrixWorld,
|
|
1847
|
+
normalMatrix,
|
|
1848
|
+
attributeData,
|
|
1849
|
+
invert = false,
|
|
1850
|
+
) {
|
|
1851
|
+
|
|
1852
|
+
appendAttributeFromIndex( i0, attributes, matrixWorld, normalMatrix, attributeData, invert );
|
|
1853
|
+
appendAttributeFromIndex( invert ? i2 : i1, attributes, matrixWorld, normalMatrix, attributeData, invert );
|
|
1854
|
+
appendAttributeFromIndex( invert ? i1 : i2, attributes, matrixWorld, normalMatrix, attributeData, invert );
|
|
1855
|
+
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
// Returns the triangle to add when performing an operation
|
|
1859
|
+
function getOperationAction( operation, hitSide, invert = false ) {
|
|
1860
|
+
|
|
1861
|
+
switch ( operation ) {
|
|
1862
|
+
|
|
1863
|
+
case ADDITION:
|
|
1864
|
+
|
|
1865
|
+
if ( hitSide === FRONT_SIDE || ( hitSide === COPLANAR_ALIGNED && ! invert ) ) {
|
|
1866
|
+
|
|
1867
|
+
return ADD_TRI;
|
|
1868
|
+
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
break;
|
|
1872
|
+
case SUBTRACTION:
|
|
1873
|
+
|
|
1874
|
+
if ( invert ) {
|
|
1875
|
+
|
|
1876
|
+
if ( hitSide === BACK_SIDE ) {
|
|
1877
|
+
|
|
1878
|
+
return INVERT_TRI;
|
|
1879
|
+
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
} else {
|
|
1883
|
+
|
|
1884
|
+
if ( hitSide === FRONT_SIDE || hitSide === COPLANAR_OPPOSITE ) {
|
|
1885
|
+
|
|
1886
|
+
return ADD_TRI;
|
|
1887
|
+
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
break;
|
|
1893
|
+
case REVERSE_SUBTRACTION:
|
|
1894
|
+
|
|
1895
|
+
if ( invert ) {
|
|
1896
|
+
|
|
1897
|
+
if ( hitSide === FRONT_SIDE || hitSide === COPLANAR_OPPOSITE ) {
|
|
1898
|
+
|
|
1899
|
+
return ADD_TRI;
|
|
1900
|
+
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
} else {
|
|
1904
|
+
|
|
1905
|
+
if ( hitSide === BACK_SIDE ) {
|
|
1906
|
+
|
|
1907
|
+
return INVERT_TRI;
|
|
1908
|
+
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
break;
|
|
1914
|
+
case DIFFERENCE:
|
|
1915
|
+
|
|
1916
|
+
if ( hitSide === BACK_SIDE ) {
|
|
1917
|
+
|
|
1918
|
+
return INVERT_TRI;
|
|
1919
|
+
|
|
1920
|
+
} else if ( hitSide === FRONT_SIDE ) {
|
|
1921
|
+
|
|
1922
|
+
return ADD_TRI;
|
|
1923
|
+
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
break;
|
|
1927
|
+
case INTERSECTION:
|
|
1928
|
+
if ( hitSide === BACK_SIDE || ( hitSide === COPLANAR_ALIGNED && ! invert ) ) {
|
|
1929
|
+
|
|
1930
|
+
return ADD_TRI;
|
|
1931
|
+
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
break;
|
|
1935
|
+
|
|
1936
|
+
case HOLLOW_SUBTRACTION:
|
|
1937
|
+
if ( ! invert && ( hitSide === FRONT_SIDE || hitSide === COPLANAR_OPPOSITE ) ) {
|
|
1938
|
+
|
|
1939
|
+
return ADD_TRI;
|
|
1940
|
+
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
break;
|
|
1944
|
+
case HOLLOW_INTERSECTION:
|
|
1945
|
+
if ( ! invert && ( hitSide === BACK_SIDE || hitSide === COPLANAR_ALIGNED ) ) {
|
|
1946
|
+
|
|
1947
|
+
return ADD_TRI;
|
|
1948
|
+
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
break;
|
|
1952
|
+
default:
|
|
1953
|
+
throw new Error( `Unrecognized CSG operation enum "${ operation }".` );
|
|
1954
|
+
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
return SKIP_TRI;
|
|
1958
|
+
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// takes a set of barycentric values in the form of a triangle, a set of vectors, number of components,
|
|
1962
|
+
// and whether to invert the result and pushes the new values onto the provided attribute array
|
|
1963
|
+
function pushBarycoordInterpolatedValues( v0, v1, v2, baryCoordTri, itemSize, attrArr, invert = false, normalize = false ) {
|
|
1964
|
+
|
|
1965
|
+
// adds the appropriate number of values for the vector onto the array
|
|
1966
|
+
const addValues = v => {
|
|
1967
|
+
|
|
1968
|
+
attrArr.push( v.x );
|
|
1969
|
+
if ( itemSize > 1 ) attrArr.push( v.y );
|
|
1970
|
+
if ( itemSize > 2 ) attrArr.push( v.z );
|
|
1971
|
+
if ( itemSize > 3 ) attrArr.push( v.w );
|
|
1972
|
+
|
|
1973
|
+
};
|
|
1974
|
+
|
|
1975
|
+
// barycentric interpolate the first component
|
|
1976
|
+
_vec4_0.set( 0, 0, 0, 0 )
|
|
1977
|
+
.addScaledVector( v0, baryCoordTri.a.x )
|
|
1978
|
+
.addScaledVector( v1, baryCoordTri.a.y )
|
|
1979
|
+
.addScaledVector( v2, baryCoordTri.a.z );
|
|
1980
|
+
|
|
1981
|
+
_vec4_1.set( 0, 0, 0, 0 )
|
|
1982
|
+
.addScaledVector( v0, baryCoordTri.b.x )
|
|
1983
|
+
.addScaledVector( v1, baryCoordTri.b.y )
|
|
1984
|
+
.addScaledVector( v2, baryCoordTri.b.z );
|
|
1985
|
+
|
|
1986
|
+
_vec4_2.set( 0, 0, 0, 0 )
|
|
1987
|
+
.addScaledVector( v0, baryCoordTri.c.x )
|
|
1988
|
+
.addScaledVector( v1, baryCoordTri.c.y )
|
|
1989
|
+
.addScaledVector( v2, baryCoordTri.c.z );
|
|
1990
|
+
|
|
1991
|
+
if ( normalize ) {
|
|
1992
|
+
|
|
1993
|
+
_vec4_0.normalize();
|
|
1994
|
+
_vec4_1.normalize();
|
|
1995
|
+
_vec4_2.normalize();
|
|
1996
|
+
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
// if the face is inverted then add the values in an inverted order
|
|
2000
|
+
addValues( _vec4_0 );
|
|
2001
|
+
|
|
2002
|
+
if ( invert ) {
|
|
2003
|
+
|
|
2004
|
+
addValues( _vec4_2 );
|
|
2005
|
+
addValues( _vec4_1 );
|
|
2006
|
+
|
|
2007
|
+
} else {
|
|
2008
|
+
|
|
2009
|
+
addValues( _vec4_1 );
|
|
2010
|
+
addValues( _vec4_2 );
|
|
2011
|
+
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
// Adds the values for the given vertex index onto the new attribute arrays
|
|
2017
|
+
function appendAttributeFromIndex(
|
|
2018
|
+
index,
|
|
2019
|
+
attributes,
|
|
2020
|
+
matrixWorld,
|
|
2021
|
+
normalMatrix,
|
|
2022
|
+
attributeData,
|
|
2023
|
+
invert = false,
|
|
2024
|
+
) {
|
|
2025
|
+
|
|
2026
|
+
for ( const key in attributeData ) {
|
|
2027
|
+
|
|
2028
|
+
// check if the key we're asking for is in the geometry at all
|
|
2029
|
+
const attr = attributes[ key ];
|
|
2030
|
+
const arr = attributeData[ key ];
|
|
2031
|
+
if ( ! ( key in attributes ) ) {
|
|
2032
|
+
|
|
2033
|
+
throw new Error( `CSG Operations: Attribute ${ key } no available on geometry.` );
|
|
2034
|
+
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
// specially handle the position and normal attributes because they require transforms
|
|
2038
|
+
// TODO: handle tangents
|
|
2039
|
+
const itemSize = attr.itemSize;
|
|
2040
|
+
if ( key === 'position' ) {
|
|
2041
|
+
|
|
2042
|
+
_vec3.fromBufferAttribute( attr, index ).applyMatrix4( matrixWorld );
|
|
2043
|
+
arr.push( _vec3.x, _vec3.y, _vec3.z );
|
|
2044
|
+
|
|
2045
|
+
} else if ( key === 'normal' ) {
|
|
2046
|
+
|
|
2047
|
+
_vec3.fromBufferAttribute( attr, index ).applyNormalMatrix( normalMatrix );
|
|
2048
|
+
if ( invert ) {
|
|
2049
|
+
|
|
2050
|
+
_vec3.multiplyScalar( - 1 );
|
|
2051
|
+
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
arr.push( _vec3.x, _vec3.y, _vec3.z );
|
|
2055
|
+
|
|
2056
|
+
} else {
|
|
2057
|
+
|
|
2058
|
+
arr.push( attr.getX( index ) );
|
|
2059
|
+
if ( itemSize > 1 ) arr.push( attr.getY( index ) );
|
|
2060
|
+
if ( itemSize > 2 ) arr.push( attr.getZ( index ) );
|
|
2061
|
+
if ( itemSize > 3 ) arr.push( attr.getW( index ) );
|
|
2062
|
+
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
class TriangleIntersectData {
|
|
2070
|
+
|
|
2071
|
+
constructor( tri ) {
|
|
2072
|
+
|
|
2073
|
+
this.triangle = new Triangle().copy( tri );
|
|
2074
|
+
this.intersects = {};
|
|
2075
|
+
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
addTriangle( index, tri ) {
|
|
2079
|
+
|
|
2080
|
+
this.intersects[ index ] = new Triangle().copy( tri );
|
|
2081
|
+
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
getIntersectArray() {
|
|
2085
|
+
|
|
2086
|
+
const array = [];
|
|
2087
|
+
const { intersects } = this;
|
|
2088
|
+
for ( const key in intersects ) {
|
|
2089
|
+
|
|
2090
|
+
array.push( intersects[ key ] );
|
|
2091
|
+
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
return array;
|
|
2095
|
+
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
class TriangleIntersectionSets {
|
|
2101
|
+
|
|
2102
|
+
constructor() {
|
|
2103
|
+
|
|
2104
|
+
this.data = {};
|
|
2105
|
+
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
addTriangleIntersection( ia, triA, ib, triB ) {
|
|
2109
|
+
|
|
2110
|
+
const { data } = this;
|
|
2111
|
+
if ( ! data[ ia ] ) {
|
|
2112
|
+
|
|
2113
|
+
data[ ia ] = new TriangleIntersectData( triA );
|
|
2114
|
+
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
data[ ia ].addTriangle( ib, triB );
|
|
2118
|
+
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
getTrianglesAsArray( id = null ) {
|
|
2122
|
+
|
|
2123
|
+
const { data } = this;
|
|
2124
|
+
const arr = [];
|
|
2125
|
+
|
|
2126
|
+
if ( id !== null ) {
|
|
2127
|
+
|
|
2128
|
+
if ( id in data ) {
|
|
2129
|
+
|
|
2130
|
+
arr.push( data[ id ].triangle );
|
|
2131
|
+
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
} else {
|
|
2135
|
+
|
|
2136
|
+
for ( const key in data ) {
|
|
2137
|
+
|
|
2138
|
+
arr.push( data[ key ].triangle );
|
|
2139
|
+
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
return arr;
|
|
2145
|
+
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
getTriangleIndices() {
|
|
2149
|
+
|
|
2150
|
+
return Object.keys( this.data ).map( i => parseInt( i ) );
|
|
2151
|
+
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
getIntersectionIndices( id ) {
|
|
2155
|
+
|
|
2156
|
+
const { data } = this;
|
|
2157
|
+
if ( ! data[ id ] ) {
|
|
2158
|
+
|
|
2159
|
+
return [];
|
|
2160
|
+
|
|
2161
|
+
} else {
|
|
2162
|
+
|
|
2163
|
+
return Object.keys( data[ id ].intersects ).map( i => parseInt( i ) );
|
|
2164
|
+
|
|
2165
|
+
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
getIntersectionsAsArray( id = null, id2 = null ) {
|
|
2171
|
+
|
|
2172
|
+
const { data } = this;
|
|
2173
|
+
const triSet = new Set();
|
|
2174
|
+
const arr = [];
|
|
2175
|
+
|
|
2176
|
+
const addTriangles = key => {
|
|
2177
|
+
|
|
2178
|
+
if ( ! data[ key ] ) return;
|
|
2179
|
+
|
|
2180
|
+
if ( id2 !== null ) {
|
|
2181
|
+
|
|
2182
|
+
if ( data[ key ].intersects[ id2 ] ) {
|
|
2183
|
+
|
|
2184
|
+
arr.push( data[ key ].intersects[ id2 ] );
|
|
2185
|
+
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
} else {
|
|
2189
|
+
|
|
2190
|
+
const intersects = data[ key ].intersects;
|
|
2191
|
+
for ( const key2 in intersects ) {
|
|
2192
|
+
|
|
2193
|
+
if ( ! triSet.has( key2 ) ) {
|
|
2194
|
+
|
|
2195
|
+
triSet.add( key2 );
|
|
2196
|
+
arr.push( intersects[ key2 ] );
|
|
2197
|
+
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
};
|
|
2205
|
+
|
|
2206
|
+
if ( id !== null ) {
|
|
2207
|
+
|
|
2208
|
+
addTriangles( id );
|
|
2209
|
+
|
|
2210
|
+
} else {
|
|
2211
|
+
|
|
2212
|
+
for ( const key in data ) {
|
|
2213
|
+
|
|
2214
|
+
addTriangles( key );
|
|
2215
|
+
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
return arr;
|
|
2221
|
+
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
reset() {
|
|
2225
|
+
|
|
2226
|
+
this.data = {};
|
|
2227
|
+
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
class OperationDebugData {
|
|
2233
|
+
|
|
2234
|
+
constructor() {
|
|
2235
|
+
|
|
2236
|
+
this.enabled = false;
|
|
2237
|
+
this.triangleIntersectsA = new TriangleIntersectionSets();
|
|
2238
|
+
this.triangleIntersectsB = new TriangleIntersectionSets();
|
|
2239
|
+
this.intersectionEdges = [];
|
|
2240
|
+
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
addIntersectingTriangles( ia, triA, ib, triB ) {
|
|
2244
|
+
|
|
2245
|
+
const { triangleIntersectsA, triangleIntersectsB } = this;
|
|
2246
|
+
triangleIntersectsA.addTriangleIntersection( ia, triA, ib, triB );
|
|
2247
|
+
triangleIntersectsB.addTriangleIntersection( ib, triB, ia, triA );
|
|
2248
|
+
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
addEdge( edge ) {
|
|
2252
|
+
|
|
2253
|
+
this.intersectionEdges.push( edge.clone() );
|
|
2254
|
+
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
reset() {
|
|
2258
|
+
|
|
2259
|
+
this.triangleIntersectsA.reset();
|
|
2260
|
+
this.triangleIntersectsB.reset();
|
|
2261
|
+
this.intersectionEdges = [];
|
|
2262
|
+
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
init() {
|
|
2266
|
+
|
|
2267
|
+
if ( this.enabled ) {
|
|
2268
|
+
|
|
2269
|
+
this.reset();
|
|
2270
|
+
setDebugContext( this );
|
|
2271
|
+
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
complete() {
|
|
2277
|
+
|
|
2278
|
+
if ( this.enabled ) {
|
|
2279
|
+
|
|
2280
|
+
setDebugContext( null );
|
|
2281
|
+
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
const _matrix$1 = new Matrix4();
|
|
2289
|
+
const _normalMatrix = new Matrix3();
|
|
2290
|
+
const _triA = new Triangle();
|
|
2291
|
+
const _triB = new Triangle();
|
|
2292
|
+
const _tri$1 = new Triangle();
|
|
2293
|
+
const _barycoordTri = new Triangle();
|
|
2294
|
+
const _attr = [];
|
|
2295
|
+
const _actions = [];
|
|
2296
|
+
|
|
2297
|
+
function getFirstIdFromSet( set ) {
|
|
2298
|
+
|
|
2299
|
+
for ( const id of set ) return id;
|
|
2300
|
+
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
// runs the given operation against a and b using the splitter and appending data to the
|
|
2304
|
+
// attributeData object.
|
|
2305
|
+
function performOperation(
|
|
2306
|
+
a,
|
|
2307
|
+
b,
|
|
2308
|
+
operations,
|
|
2309
|
+
splitter,
|
|
2310
|
+
attributeData,
|
|
2311
|
+
options = {},
|
|
2312
|
+
) {
|
|
2313
|
+
|
|
2314
|
+
const { useGroups = true } = options;
|
|
2315
|
+
const { aIntersections, bIntersections } = collectIntersectingTriangles( a, b );
|
|
2316
|
+
|
|
2317
|
+
const resultGroups = [];
|
|
2318
|
+
let resultMaterials = null;
|
|
2319
|
+
|
|
2320
|
+
let groupOffset;
|
|
2321
|
+
groupOffset = useGroups ? 0 : - 1;
|
|
2322
|
+
performSplitTriangleOperations( a, b, aIntersections, operations, false, splitter, attributeData, groupOffset );
|
|
2323
|
+
performWholeTriangleOperations( a, b, aIntersections, operations, false, attributeData, groupOffset );
|
|
2324
|
+
|
|
2325
|
+
// find whether the set of operations contains a non-hollow operations. If it does then we need
|
|
2326
|
+
// to perform the second set of triangle additions
|
|
2327
|
+
const nonHollow = operations
|
|
2328
|
+
.findIndex( op => op !== HOLLOW_INTERSECTION && op !== HOLLOW_SUBTRACTION ) !== - 1;
|
|
2329
|
+
|
|
2330
|
+
if ( nonHollow ) {
|
|
2331
|
+
|
|
2332
|
+
groupOffset = useGroups ? a.geometry.groups.length || 1 : - 1;
|
|
2333
|
+
performSplitTriangleOperations( b, a, bIntersections, operations, true, splitter, attributeData, groupOffset );
|
|
2334
|
+
performWholeTriangleOperations( b, a, bIntersections, operations, true, attributeData, groupOffset );
|
|
2335
|
+
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
_attr.length = 0;
|
|
2339
|
+
_actions.length = 0;
|
|
2340
|
+
|
|
2341
|
+
return {
|
|
2342
|
+
groups: resultGroups,
|
|
2343
|
+
materials: resultMaterials
|
|
2344
|
+
};
|
|
2345
|
+
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
// perform triangle splitting and CSG operations on the set of split triangles
|
|
2349
|
+
function performSplitTriangleOperations(
|
|
2350
|
+
a,
|
|
2351
|
+
b,
|
|
2352
|
+
intersectionMap,
|
|
2353
|
+
operations,
|
|
2354
|
+
invert,
|
|
2355
|
+
splitter,
|
|
2356
|
+
attributeData,
|
|
2357
|
+
groupOffset = 0,
|
|
2358
|
+
) {
|
|
2359
|
+
|
|
2360
|
+
const invertedGeometry = a.matrixWorld.determinant() < 0;
|
|
2361
|
+
|
|
2362
|
+
// transforms into the local frame of matrix b
|
|
2363
|
+
_matrix$1
|
|
2364
|
+
.copy( b.matrixWorld )
|
|
2365
|
+
.invert()
|
|
2366
|
+
.multiply( a.matrixWorld );
|
|
2367
|
+
|
|
2368
|
+
_normalMatrix
|
|
2369
|
+
.getNormalMatrix( a.matrixWorld )
|
|
2370
|
+
.multiplyScalar( invertedGeometry ? - 1 : 1 );
|
|
2371
|
+
|
|
2372
|
+
const groupIndices = a.geometry.groupIndices;
|
|
2373
|
+
const aIndex = a.geometry.index;
|
|
2374
|
+
const aPosition = a.geometry.attributes.position;
|
|
2375
|
+
|
|
2376
|
+
const bBVH = b.geometry.boundsTree;
|
|
2377
|
+
const bIndex = b.geometry.index;
|
|
2378
|
+
const bPosition = b.geometry.attributes.position;
|
|
2379
|
+
const splitIds = intersectionMap.ids;
|
|
2380
|
+
const intersectionSet = intersectionMap.intersectionSet;
|
|
2381
|
+
|
|
2382
|
+
// iterate over all split triangle indices
|
|
2383
|
+
for ( let i = 0, l = splitIds.length; i < l; i ++ ) {
|
|
2384
|
+
|
|
2385
|
+
const ia = splitIds[ i ];
|
|
2386
|
+
const groupIndex = groupOffset === - 1 ? 0 : groupIndices[ ia ] + groupOffset;
|
|
2387
|
+
|
|
2388
|
+
// get the triangle in the geometry B local frame
|
|
2389
|
+
const ia3 = 3 * ia;
|
|
2390
|
+
const ia0 = aIndex.getX( ia3 + 0 );
|
|
2391
|
+
const ia1 = aIndex.getX( ia3 + 1 );
|
|
2392
|
+
const ia2 = aIndex.getX( ia3 + 2 );
|
|
2393
|
+
_triA.a.fromBufferAttribute( aPosition, ia0 ).applyMatrix4( _matrix$1 );
|
|
2394
|
+
_triA.b.fromBufferAttribute( aPosition, ia1 ).applyMatrix4( _matrix$1 );
|
|
2395
|
+
_triA.c.fromBufferAttribute( aPosition, ia2 ).applyMatrix4( _matrix$1 );
|
|
2396
|
+
|
|
2397
|
+
// initialize the splitter with the triangle from geometry A
|
|
2398
|
+
splitter.reset();
|
|
2399
|
+
splitter.initialize( _triA );
|
|
2400
|
+
|
|
2401
|
+
// split the triangle with the intersecting triangles from B
|
|
2402
|
+
const intersectingIndices = intersectionSet[ ia ];
|
|
2403
|
+
for ( let ib = 0, l = intersectingIndices.length; ib < l; ib ++ ) {
|
|
2404
|
+
|
|
2405
|
+
const ib3 = 3 * intersectingIndices[ ib ];
|
|
2406
|
+
const ib0 = bIndex.getX( ib3 + 0 );
|
|
2407
|
+
const ib1 = bIndex.getX( ib3 + 1 );
|
|
2408
|
+
const ib2 = bIndex.getX( ib3 + 2 );
|
|
2409
|
+
_triB.a.fromBufferAttribute( bPosition, ib0 );
|
|
2410
|
+
_triB.b.fromBufferAttribute( bPosition, ib1 );
|
|
2411
|
+
_triB.c.fromBufferAttribute( bPosition, ib2 );
|
|
2412
|
+
splitter.splitByTriangle( _triB );
|
|
2413
|
+
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
// for all triangles in the split result
|
|
2417
|
+
const triangles = splitter.triangles;
|
|
2418
|
+
for ( let ib = 0, l = triangles.length; ib < l; ib ++ ) {
|
|
2419
|
+
|
|
2420
|
+
// get the barycentric coordinates of the clipped triangle to add
|
|
2421
|
+
const clippedTri = triangles[ ib ];
|
|
2422
|
+
|
|
2423
|
+
// try to use the side derived from the clipping but if it turns out to be
|
|
2424
|
+
// uncertain then fall back to the raycasting approach
|
|
2425
|
+
const hitSide = splitter.coplanarTriangleUsed ?
|
|
2426
|
+
getHitSideWithCoplanarCheck( clippedTri, bBVH ) :
|
|
2427
|
+
getHitSide( clippedTri, bBVH );
|
|
2428
|
+
|
|
2429
|
+
_attr.length = 0;
|
|
2430
|
+
_actions.length = 0;
|
|
2431
|
+
for ( let o = 0, lo = operations.length; o < lo; o ++ ) {
|
|
2432
|
+
|
|
2433
|
+
const op = getOperationAction( operations[ o ], hitSide, invert );
|
|
2434
|
+
if ( op !== SKIP_TRI ) {
|
|
2435
|
+
|
|
2436
|
+
_actions.push( op );
|
|
2437
|
+
_attr.push( attributeData[ o ].getGroupAttrSet( groupIndex ) );
|
|
2438
|
+
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
if ( _attr.length !== 0 ) {
|
|
2444
|
+
|
|
2445
|
+
_triA.getBarycoord( clippedTri.a, _barycoordTri.a );
|
|
2446
|
+
_triA.getBarycoord( clippedTri.b, _barycoordTri.b );
|
|
2447
|
+
_triA.getBarycoord( clippedTri.c, _barycoordTri.c );
|
|
2448
|
+
|
|
2449
|
+
for ( let k = 0, lk = _attr.length; k < lk; k ++ ) {
|
|
2450
|
+
|
|
2451
|
+
const attrSet = _attr[ k ];
|
|
2452
|
+
const action = _actions[ k ];
|
|
2453
|
+
const invertTri = action === INVERT_TRI;
|
|
2454
|
+
appendAttributeFromTriangle( ia, _barycoordTri, a.geometry, a.matrixWorld, _normalMatrix, attrSet, invertedGeometry !== invertTri );
|
|
2455
|
+
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
return splitIds.length;
|
|
2465
|
+
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
// perform CSG operations on the set of whole triangles using a half edge structure
|
|
2469
|
+
// at the moment this isn't always faster due to overhead of building the half edge structure
|
|
2470
|
+
// and degraded connectivity due to split triangles.
|
|
2471
|
+
|
|
2472
|
+
function performWholeTriangleOperations(
|
|
2473
|
+
a,
|
|
2474
|
+
b,
|
|
2475
|
+
splitTriSet,
|
|
2476
|
+
operations,
|
|
2477
|
+
invert,
|
|
2478
|
+
attributeData,
|
|
2479
|
+
groupOffset = 0,
|
|
2480
|
+
) {
|
|
2481
|
+
|
|
2482
|
+
const invertedGeometry = a.matrixWorld.determinant() < 0;
|
|
2483
|
+
|
|
2484
|
+
// matrix for transforming into the local frame of geometry b
|
|
2485
|
+
_matrix$1
|
|
2486
|
+
.copy( b.matrixWorld )
|
|
2487
|
+
.invert()
|
|
2488
|
+
.multiply( a.matrixWorld );
|
|
2489
|
+
|
|
2490
|
+
_normalMatrix
|
|
2491
|
+
.getNormalMatrix( a.matrixWorld )
|
|
2492
|
+
.multiplyScalar( invertedGeometry ? - 1 : 1 );
|
|
2493
|
+
|
|
2494
|
+
const bBVH = b.geometry.boundsTree;
|
|
2495
|
+
const groupIndices = a.geometry.groupIndices;
|
|
2496
|
+
const aIndex = a.geometry.index;
|
|
2497
|
+
const aAttributes = a.geometry.attributes;
|
|
2498
|
+
const aPosition = aAttributes.position;
|
|
2499
|
+
|
|
2500
|
+
const stack = [];
|
|
2501
|
+
const halfEdges = a.geometry.halfEdges;
|
|
2502
|
+
const traverseSet = new Set();
|
|
2503
|
+
const triCount = getTriCount( a.geometry );
|
|
2504
|
+
for ( let i = 0, l = triCount; i < l; i ++ ) {
|
|
2505
|
+
|
|
2506
|
+
if ( ! ( i in splitTriSet.intersectionSet ) ) {
|
|
2507
|
+
|
|
2508
|
+
traverseSet.add( i );
|
|
2509
|
+
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
while ( traverseSet.size > 0 ) {
|
|
2515
|
+
|
|
2516
|
+
const id = getFirstIdFromSet( traverseSet );
|
|
2517
|
+
traverseSet.delete( id );
|
|
2518
|
+
|
|
2519
|
+
stack.push( id );
|
|
2520
|
+
|
|
2521
|
+
// get the vertex indices
|
|
2522
|
+
const i3 = 3 * id;
|
|
2523
|
+
const i0 = aIndex.getX( i3 + 0 );
|
|
2524
|
+
const i1 = aIndex.getX( i3 + 1 );
|
|
2525
|
+
const i2 = aIndex.getX( i3 + 2 );
|
|
2526
|
+
|
|
2527
|
+
// get the vertex position in the frame of geometry b so we can
|
|
2528
|
+
// perform hit testing
|
|
2529
|
+
_tri$1.a.fromBufferAttribute( aPosition, i0 ).applyMatrix4( _matrix$1 );
|
|
2530
|
+
_tri$1.b.fromBufferAttribute( aPosition, i1 ).applyMatrix4( _matrix$1 );
|
|
2531
|
+
_tri$1.c.fromBufferAttribute( aPosition, i2 ).applyMatrix4( _matrix$1 );
|
|
2532
|
+
|
|
2533
|
+
// get the side and decide if we need to cull the triangle based on the operation
|
|
2534
|
+
const hitSide = getHitSide( _tri$1, bBVH );
|
|
2535
|
+
|
|
2536
|
+
_actions.length = 0;
|
|
2537
|
+
_attr.length = 0;
|
|
2538
|
+
for ( let o = 0, lo = operations.length; o < lo; o ++ ) {
|
|
2539
|
+
|
|
2540
|
+
const op = getOperationAction( operations[ o ], hitSide, invert );
|
|
2541
|
+
if ( op !== SKIP_TRI ) {
|
|
2542
|
+
|
|
2543
|
+
_actions.push( op );
|
|
2544
|
+
_attr.push( attributeData[ o ] );
|
|
2545
|
+
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
while ( stack.length > 0 ) {
|
|
2551
|
+
|
|
2552
|
+
const currId = stack.pop();
|
|
2553
|
+
for ( let i = 0; i < 3; i ++ ) {
|
|
2554
|
+
|
|
2555
|
+
const sid = halfEdges.getSiblingTriangleIndex( currId, i );
|
|
2556
|
+
if ( sid !== - 1 && traverseSet.has( sid ) ) {
|
|
2557
|
+
|
|
2558
|
+
stack.push( sid );
|
|
2559
|
+
traverseSet.delete( sid );
|
|
2560
|
+
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
if ( _attr.length !== 0 ) {
|
|
2566
|
+
|
|
2567
|
+
const i3 = 3 * currId;
|
|
2568
|
+
const i0 = aIndex.getX( i3 + 0 );
|
|
2569
|
+
const i1 = aIndex.getX( i3 + 1 );
|
|
2570
|
+
const i2 = aIndex.getX( i3 + 2 );
|
|
2571
|
+
const groupIndex = groupOffset === - 1 ? 0 : groupIndices[ currId ] + groupOffset;
|
|
2572
|
+
|
|
2573
|
+
_tri$1.a.fromBufferAttribute( aPosition, i0 );
|
|
2574
|
+
_tri$1.b.fromBufferAttribute( aPosition, i1 );
|
|
2575
|
+
_tri$1.c.fromBufferAttribute( aPosition, i2 );
|
|
2576
|
+
if ( ! isTriDegenerate( _tri$1 ) ) {
|
|
2577
|
+
|
|
2578
|
+
for ( let k = 0, lk = _attr.length; k < lk; k ++ ) {
|
|
2579
|
+
|
|
2580
|
+
const action = _actions[ k ];
|
|
2581
|
+
const attrSet = _attr[ k ].getGroupAttrSet( groupIndex );
|
|
2582
|
+
const invertTri = action === INVERT_TRI;
|
|
2583
|
+
appendAttributesFromIndices( i0, i1, i2, aAttributes, a.matrixWorld, _normalMatrix, attrSet, invertTri !== invertedGeometry );
|
|
2584
|
+
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
// merges groups with common material indices in place
|
|
2598
|
+
function joinGroups( groups ) {
|
|
2599
|
+
|
|
2600
|
+
for ( let i = 0; i < groups.length - 1; i ++ ) {
|
|
2601
|
+
|
|
2602
|
+
const group = groups[ i ];
|
|
2603
|
+
const nextGroup = groups[ i + 1 ];
|
|
2604
|
+
if ( group.materialIndex === nextGroup.materialIndex ) {
|
|
2605
|
+
|
|
2606
|
+
const start = group.start;
|
|
2607
|
+
const end = nextGroup.start + nextGroup.count;
|
|
2608
|
+
nextGroup.start = start;
|
|
2609
|
+
nextGroup.count = end - start;
|
|
2610
|
+
|
|
2611
|
+
groups.splice( i, 1 );
|
|
2612
|
+
i --;
|
|
2613
|
+
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
// initialize the target geometry and attribute data to be based on
|
|
2621
|
+
// the given reference geometry
|
|
2622
|
+
function prepareAttributesData( referenceGeometry, targetGeometry, attributeData, relevantAttributes ) {
|
|
2623
|
+
|
|
2624
|
+
attributeData.clear();
|
|
2625
|
+
|
|
2626
|
+
// initialize and clear unused data from the attribute buffers and vice versa
|
|
2627
|
+
const aAttributes = referenceGeometry.attributes;
|
|
2628
|
+
for ( let i = 0, l = relevantAttributes.length; i < l; i ++ ) {
|
|
2629
|
+
|
|
2630
|
+
const key = relevantAttributes[ i ];
|
|
2631
|
+
const aAttr = aAttributes[ key ];
|
|
2632
|
+
attributeData.initializeArray( key, aAttr.array.constructor, aAttr.itemSize, aAttr.normalized );
|
|
2633
|
+
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
for ( const key in attributeData.attributes ) {
|
|
2637
|
+
|
|
2638
|
+
if ( ! relevantAttributes.includes( key ) ) {
|
|
2639
|
+
|
|
2640
|
+
attributeData.delete( key );
|
|
2641
|
+
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
for ( const key in targetGeometry.attributes ) {
|
|
2647
|
+
|
|
2648
|
+
if ( ! relevantAttributes.includes( key ) ) {
|
|
2649
|
+
|
|
2650
|
+
targetGeometry.deleteAttribute( key );
|
|
2651
|
+
targetGeometry.dispose();
|
|
2652
|
+
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
// Assigns the given tracked attribute data to the geometry and returns whether the
|
|
2660
|
+
// geometry needs to be disposed of.
|
|
2661
|
+
function assignBufferData( geometry, attributeData, groupOrder ) {
|
|
2662
|
+
|
|
2663
|
+
let needsDisposal = false;
|
|
2664
|
+
let drawRange = - 1;
|
|
2665
|
+
|
|
2666
|
+
// set the data
|
|
2667
|
+
const attributes = geometry.attributes;
|
|
2668
|
+
const referenceAttrSet = attributeData.groupAttributes[ 0 ];
|
|
2669
|
+
for ( const key in referenceAttrSet ) {
|
|
2670
|
+
|
|
2671
|
+
const requiredLength = attributeData.getTotalLength( key );
|
|
2672
|
+
const type = attributeData.getType( key );
|
|
2673
|
+
const itemSize = attributeData.getItemSize( key );
|
|
2674
|
+
const normalized = attributeData.getNormalized( key );
|
|
2675
|
+
let geoAttr = attributes[ key ];
|
|
2676
|
+
if ( ! geoAttr || geoAttr.array.length < requiredLength ) {
|
|
2677
|
+
|
|
2678
|
+
// create the attribute if it doesn't exist yet
|
|
2679
|
+
geoAttr = new BufferAttribute( new type( requiredLength ), itemSize, normalized );
|
|
2680
|
+
geometry.setAttribute( key, geoAttr );
|
|
2681
|
+
needsDisposal = true;
|
|
2682
|
+
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
// assign the data to the geometry attribute buffers in the provided order
|
|
2686
|
+
// of the groups list
|
|
2687
|
+
let offset = 0;
|
|
2688
|
+
for ( let i = 0, l = Math.min( groupOrder.length, attributeData.groupCount ); i < l; i ++ ) {
|
|
2689
|
+
|
|
2690
|
+
const index = groupOrder[ i ].index;
|
|
2691
|
+
const { array, type, length } = attributeData.groupAttributes[ index ][ key ];
|
|
2692
|
+
const trimmedArray = new type( array.buffer, 0, length );
|
|
2693
|
+
geoAttr.array.set( trimmedArray, offset );
|
|
2694
|
+
offset += trimmedArray.length;
|
|
2695
|
+
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
geoAttr.needsUpdate = true;
|
|
2699
|
+
drawRange = requiredLength / geoAttr.itemSize;
|
|
2700
|
+
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
// remove or update the index appropriately
|
|
2704
|
+
if ( geometry.index ) {
|
|
2705
|
+
|
|
2706
|
+
const indexArray = geometry.index.array;
|
|
2707
|
+
if ( indexArray.length < drawRange ) {
|
|
2708
|
+
|
|
2709
|
+
geometry.index = null;
|
|
2710
|
+
needsDisposal = true;
|
|
2711
|
+
|
|
2712
|
+
} else {
|
|
2713
|
+
|
|
2714
|
+
for ( let i = 0, l = indexArray.length; i < l; i ++ ) {
|
|
2715
|
+
|
|
2716
|
+
indexArray[ i ] = i;
|
|
2717
|
+
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
// initialize the groups
|
|
2725
|
+
let groupOffset = 0;
|
|
2726
|
+
geometry.clearGroups();
|
|
2727
|
+
for ( let i = 0, l = Math.min( groupOrder.length, attributeData.groupCount ); i < l; i ++ ) {
|
|
2728
|
+
|
|
2729
|
+
const { index, materialIndex } = groupOrder[ i ];
|
|
2730
|
+
const vertCount = attributeData.getCount( index );
|
|
2731
|
+
if ( vertCount !== 0 ) {
|
|
2732
|
+
|
|
2733
|
+
geometry.addGroup( groupOffset, vertCount, materialIndex );
|
|
2734
|
+
groupOffset += vertCount;
|
|
2735
|
+
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
// update the draw range
|
|
2741
|
+
geometry.setDrawRange( 0, drawRange );
|
|
2742
|
+
|
|
2743
|
+
// remove the bounds tree if it exists because its now out of date
|
|
2744
|
+
// TODO: can we have this dispose in the same way that a brush does?
|
|
2745
|
+
// TODO: why are half edges and group indices not removed here?
|
|
2746
|
+
geometry.boundsTree = null;
|
|
2747
|
+
|
|
2748
|
+
if ( needsDisposal ) {
|
|
2749
|
+
|
|
2750
|
+
geometry.dispose();
|
|
2751
|
+
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
// Returns the list of materials used for the given set of groups
|
|
2757
|
+
function getMaterialList( groups, materials ) {
|
|
2758
|
+
|
|
2759
|
+
let result = materials;
|
|
2760
|
+
if ( ! Array.isArray( materials ) ) {
|
|
2761
|
+
|
|
2762
|
+
result = [];
|
|
2763
|
+
groups.forEach( g => {
|
|
2764
|
+
|
|
2765
|
+
result[ g.materialIndex ] = materials;
|
|
2766
|
+
|
|
2767
|
+
} );
|
|
2768
|
+
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
return result;
|
|
2772
|
+
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
// Utility class for performing CSG operations
|
|
2776
|
+
class Evaluator {
|
|
2777
|
+
|
|
2778
|
+
constructor() {
|
|
2779
|
+
|
|
2780
|
+
this.triangleSplitter = new TriangleSplitter();
|
|
2781
|
+
this.attributeData = [];
|
|
2782
|
+
this.attributes = [ 'position', 'uv', 'normal' ];
|
|
2783
|
+
this.useGroups = true;
|
|
2784
|
+
this.consolidateGroups = true;
|
|
2785
|
+
this.debug = new OperationDebugData();
|
|
2786
|
+
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
getGroupRanges( geometry ) {
|
|
2790
|
+
|
|
2791
|
+
return ! this.useGroups || geometry.groups.length === 0 ?
|
|
2792
|
+
[ { start: 0, count: Infinity, materialIndex: 0 } ] :
|
|
2793
|
+
geometry.groups.map( group => ( { ...group } ) );
|
|
2794
|
+
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
evaluate( a, b, operations, targetBrushes = new Brush() ) {
|
|
2798
|
+
|
|
2799
|
+
let wasArray = true;
|
|
2800
|
+
if ( ! Array.isArray( operations ) ) {
|
|
2801
|
+
|
|
2802
|
+
operations = [ operations ];
|
|
2803
|
+
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
if ( ! Array.isArray( targetBrushes ) ) {
|
|
2807
|
+
|
|
2808
|
+
targetBrushes = [ targetBrushes ];
|
|
2809
|
+
wasArray = false;
|
|
2810
|
+
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
if ( targetBrushes.length !== operations.length ) {
|
|
2814
|
+
|
|
2815
|
+
throw new Error( 'Evaluator: operations and target array passed as different sizes.' );
|
|
2816
|
+
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
a.prepareGeometry();
|
|
2820
|
+
b.prepareGeometry();
|
|
2821
|
+
|
|
2822
|
+
const {
|
|
2823
|
+
triangleSplitter,
|
|
2824
|
+
attributeData,
|
|
2825
|
+
attributes,
|
|
2826
|
+
useGroups,
|
|
2827
|
+
consolidateGroups,
|
|
2828
|
+
debug,
|
|
2829
|
+
} = this;
|
|
2830
|
+
|
|
2831
|
+
// expand the attribute data array to the necessary size
|
|
2832
|
+
while ( attributeData.length < targetBrushes.length ) {
|
|
2833
|
+
|
|
2834
|
+
attributeData.push( new TypedAttributeData() );
|
|
2835
|
+
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
// prepare the attribute data buffer information
|
|
2839
|
+
targetBrushes.forEach( ( brush, i ) => {
|
|
2840
|
+
|
|
2841
|
+
prepareAttributesData( a.geometry, brush.geometry, attributeData[ i ], attributes );
|
|
2842
|
+
|
|
2843
|
+
} );
|
|
2844
|
+
|
|
2845
|
+
// run the operation to fill the list of attribute data
|
|
2846
|
+
debug.init();
|
|
2847
|
+
performOperation( a, b, operations, triangleSplitter, attributeData, { useGroups } );
|
|
2848
|
+
debug.complete();
|
|
2849
|
+
|
|
2850
|
+
// get the materials and group ranges
|
|
2851
|
+
const aGroups = this.getGroupRanges( a.geometry );
|
|
2852
|
+
const aMaterials = getMaterialList( aGroups, a.material );
|
|
2853
|
+
|
|
2854
|
+
const bGroups = this.getGroupRanges( b.geometry );
|
|
2855
|
+
const bMaterials = getMaterialList( bGroups, b.material );
|
|
2856
|
+
bGroups.forEach( g => g.materialIndex += aMaterials.length );
|
|
2857
|
+
|
|
2858
|
+
let groups = [ ...aGroups, ...bGroups ]
|
|
2859
|
+
.map( ( group, index ) => ( { ...group, index } ) );
|
|
2860
|
+
|
|
2861
|
+
// generate the minimum set of materials needed for the list of groups and adjust the groups
|
|
2862
|
+
// if they're needed
|
|
2863
|
+
if ( useGroups ) {
|
|
2864
|
+
|
|
2865
|
+
const allMaterials = [ ...aMaterials, ...bMaterials ];
|
|
2866
|
+
if ( consolidateGroups ) {
|
|
2867
|
+
|
|
2868
|
+
groups = groups
|
|
2869
|
+
.map( group => {
|
|
2870
|
+
|
|
2871
|
+
const mat = allMaterials[ group.materialIndex ];
|
|
2872
|
+
group.materialIndex = allMaterials.indexOf( mat );
|
|
2873
|
+
return group;
|
|
2874
|
+
|
|
2875
|
+
} )
|
|
2876
|
+
.sort( ( a, b ) => {
|
|
2877
|
+
|
|
2878
|
+
return a.materialIndex - b.materialIndex;
|
|
2879
|
+
|
|
2880
|
+
} );
|
|
2881
|
+
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
// create a map from old to new index and remove materials that aren't used
|
|
2885
|
+
const finalMaterials = [];
|
|
2886
|
+
for ( let i = 0, l = allMaterials.length; i < l; i ++ ) {
|
|
2887
|
+
|
|
2888
|
+
let foundGroup = false;
|
|
2889
|
+
for ( let g = 0, lg = groups.length; g < lg; g ++ ) {
|
|
2890
|
+
|
|
2891
|
+
const group = groups[ g ];
|
|
2892
|
+
if ( group.materialIndex === i ) {
|
|
2893
|
+
|
|
2894
|
+
foundGroup = true;
|
|
2895
|
+
group.materialIndex = finalMaterials.length;
|
|
2896
|
+
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
if ( foundGroup ) {
|
|
2902
|
+
|
|
2903
|
+
finalMaterials.push( allMaterials[ i ] );
|
|
2904
|
+
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
targetBrushes.forEach( tb => {
|
|
2910
|
+
|
|
2911
|
+
tb.material = finalMaterials;
|
|
2912
|
+
|
|
2913
|
+
} );
|
|
2914
|
+
|
|
2915
|
+
} else {
|
|
2916
|
+
|
|
2917
|
+
groups = [ { start: 0, count: Infinity, index: 0, materialIndex: 0 } ];
|
|
2918
|
+
targetBrushes.forEach( tb => {
|
|
2919
|
+
|
|
2920
|
+
tb.material = aMaterials[ 0 ];
|
|
2921
|
+
|
|
2922
|
+
} );
|
|
2923
|
+
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
// apply groups and attribute data to the geometry
|
|
2927
|
+
targetBrushes.forEach( ( brush, i ) => {
|
|
2928
|
+
|
|
2929
|
+
const targetGeometry = brush.geometry;
|
|
2930
|
+
assignBufferData( targetGeometry, attributeData[ i ], groups );
|
|
2931
|
+
if ( consolidateGroups ) {
|
|
2932
|
+
|
|
2933
|
+
joinGroups( targetGeometry.groups );
|
|
2934
|
+
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
} );
|
|
2938
|
+
|
|
2939
|
+
return wasArray ? targetBrushes : targetBrushes[ 0 ];
|
|
2940
|
+
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
// TODO: fix
|
|
2944
|
+
evaluateHierarchy( root, target = new Brush() ) {
|
|
2945
|
+
|
|
2946
|
+
root.updateMatrixWorld( true );
|
|
2947
|
+
|
|
2948
|
+
const flatTraverse = ( obj, cb ) => {
|
|
2949
|
+
|
|
2950
|
+
const children = obj.children;
|
|
2951
|
+
for ( let i = 0, l = children.length; i < l; i ++ ) {
|
|
2952
|
+
|
|
2953
|
+
const child = children[ i ];
|
|
2954
|
+
if ( child.isOperationGroup ) {
|
|
2955
|
+
|
|
2956
|
+
flatTraverse( child, cb );
|
|
2957
|
+
|
|
2958
|
+
} else {
|
|
2959
|
+
|
|
2960
|
+
cb( child );
|
|
2961
|
+
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
};
|
|
2967
|
+
|
|
2968
|
+
|
|
2969
|
+
const traverse = brush => {
|
|
2970
|
+
|
|
2971
|
+
const children = brush.children;
|
|
2972
|
+
let didChange = false;
|
|
2973
|
+
for ( let i = 0, l = children.length; i < l; i ++ ) {
|
|
2974
|
+
|
|
2975
|
+
const child = children[ i ];
|
|
2976
|
+
didChange = traverse( child ) || didChange;
|
|
2977
|
+
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
const isDirty = brush.isDirty();
|
|
2981
|
+
if ( isDirty ) {
|
|
2982
|
+
|
|
2983
|
+
brush.markUpdated();
|
|
2984
|
+
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
if ( didChange && ! brush.isOperationGroup ) {
|
|
2988
|
+
|
|
2989
|
+
let result;
|
|
2990
|
+
flatTraverse( brush, child => {
|
|
2991
|
+
|
|
2992
|
+
if ( ! result ) {
|
|
2993
|
+
|
|
2994
|
+
result = this.evaluate( brush, child, child.operation );
|
|
2995
|
+
|
|
2996
|
+
} else {
|
|
2997
|
+
|
|
2998
|
+
result = this.evaluate( result, child, child.operation );
|
|
2999
|
+
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
} );
|
|
3003
|
+
|
|
3004
|
+
brush._cachedGeometry = result.geometry;
|
|
3005
|
+
brush._cachedMaterials = result.material;
|
|
3006
|
+
return true;
|
|
3007
|
+
|
|
3008
|
+
} else {
|
|
3009
|
+
|
|
3010
|
+
return didChange || isDirty;
|
|
3011
|
+
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
};
|
|
3015
|
+
|
|
3016
|
+
traverse( root );
|
|
3017
|
+
|
|
3018
|
+
target.geometry = root._cachedGeometry;
|
|
3019
|
+
target.material = root._cachedMaterials;
|
|
3020
|
+
|
|
3021
|
+
return target;
|
|
3022
|
+
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
reset() {
|
|
3026
|
+
|
|
3027
|
+
this.triangleSplitter.reset();
|
|
3028
|
+
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
class Operation extends Brush {
|
|
3034
|
+
|
|
3035
|
+
constructor( ...args ) {
|
|
3036
|
+
|
|
3037
|
+
super( ...args );
|
|
3038
|
+
|
|
3039
|
+
this.isOperation = true;
|
|
3040
|
+
this.operation = ADDITION;
|
|
3041
|
+
|
|
3042
|
+
this._cachedGeometry = new BufferGeometry();
|
|
3043
|
+
this._cachedMaterials = null;
|
|
3044
|
+
this._previousOperation = null;
|
|
3045
|
+
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
markUpdated() {
|
|
3049
|
+
|
|
3050
|
+
super.markUpdated();
|
|
3051
|
+
this._previousOperation = this.operation;
|
|
3052
|
+
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
isDirty() {
|
|
3056
|
+
|
|
3057
|
+
return this.operation !== this._previousOperation || super.isDirty();
|
|
3058
|
+
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
insertBefore( brush ) {
|
|
3062
|
+
|
|
3063
|
+
const parent = this.parent;
|
|
3064
|
+
const index = parent.children.indexOf( this );
|
|
3065
|
+
parent.children.splice( index, 0, brush );
|
|
3066
|
+
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
insertAfter( brush ) {
|
|
3070
|
+
|
|
3071
|
+
const parent = this.parent;
|
|
3072
|
+
const index = parent.children.indexOf( this );
|
|
3073
|
+
parent.children.splice( index + 1, 0, brush );
|
|
3074
|
+
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
class OperationGroup extends Group {
|
|
3080
|
+
|
|
3081
|
+
constructor() {
|
|
3082
|
+
|
|
3083
|
+
super();
|
|
3084
|
+
this.isOperationGroup = true;
|
|
3085
|
+
this._previousMatrix = new Matrix4();
|
|
3086
|
+
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
markUpdated() {
|
|
3090
|
+
|
|
3091
|
+
this._previousMatrix.copy( this.matrix );
|
|
3092
|
+
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
isDirty() {
|
|
3096
|
+
|
|
3097
|
+
const { matrix, _previousMatrix } = this;
|
|
3098
|
+
const el1 = matrix.elements;
|
|
3099
|
+
const el2 = _previousMatrix.elements;
|
|
3100
|
+
for ( let i = 0; i < 16; i ++ ) {
|
|
3101
|
+
|
|
3102
|
+
if ( el1[ i ] !== el2[ i ] ) {
|
|
3103
|
+
|
|
3104
|
+
return true;
|
|
3105
|
+
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
return false;
|
|
3111
|
+
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
function addWorldPosition( shader ) {
|
|
3117
|
+
|
|
3118
|
+
if ( /varying\s+vec3\s+wPosition/.test( shader.vertexShader ) ) return;
|
|
3119
|
+
|
|
3120
|
+
shader.vertexShader = `
|
|
3121
|
+
varying vec3 wPosition;
|
|
3122
|
+
${shader.vertexShader}
|
|
3123
|
+
`.replace(
|
|
3124
|
+
/#include <displacementmap_vertex>/,
|
|
3125
|
+
v =>
|
|
3126
|
+
`${v}
|
|
3127
|
+
wPosition = (modelMatrix * vec4( transformed, 1.0 )).xyz;
|
|
3128
|
+
`,
|
|
3129
|
+
);
|
|
3130
|
+
|
|
3131
|
+
shader.fragmentShader = `
|
|
3132
|
+
varying vec3 wPosition;
|
|
3133
|
+
${shader.fragmentShader}
|
|
3134
|
+
`;
|
|
3135
|
+
|
|
3136
|
+
return shader;
|
|
3137
|
+
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
function csgGridShaderMixin( shader ) {
|
|
3141
|
+
|
|
3142
|
+
shader.uniforms = {
|
|
3143
|
+
...shader.uniforms,
|
|
3144
|
+
checkerboardColor: { value: new Color( 0x111111 ) }
|
|
3145
|
+
};
|
|
3146
|
+
|
|
3147
|
+
addWorldPosition( shader );
|
|
3148
|
+
|
|
3149
|
+
shader.defines = { CSG_GRID: 1 };
|
|
3150
|
+
|
|
3151
|
+
shader.fragmentShader = shader.fragmentShader.replace(
|
|
3152
|
+
/#include <common>/,
|
|
3153
|
+
v =>
|
|
3154
|
+
/* glsl */`
|
|
3155
|
+
${v}
|
|
3156
|
+
|
|
3157
|
+
uniform vec3 checkerboardColor;
|
|
3158
|
+
float getCheckerboard( vec2 p, float scale ) {
|
|
3159
|
+
|
|
3160
|
+
p /= scale;
|
|
3161
|
+
p += vec2( 0.5 );
|
|
3162
|
+
|
|
3163
|
+
vec2 line = mod( p, 2.0 ) - vec2( 1.0 );
|
|
3164
|
+
line = abs( line );
|
|
3165
|
+
|
|
3166
|
+
vec2 pWidth = fwidth( line );
|
|
3167
|
+
vec2 value = smoothstep( 0.5 - pWidth / 2.0, 0.5 + pWidth / 2.0, line );
|
|
3168
|
+
float result = value.x * value.y + ( 1.0 - value.x ) * ( 1.0 - value.y );
|
|
3169
|
+
|
|
3170
|
+
return result;
|
|
3171
|
+
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
float getGrid( vec2 p, float scale, float thickness ) {
|
|
3175
|
+
|
|
3176
|
+
p /= 0.5 * scale;
|
|
3177
|
+
|
|
3178
|
+
vec2 stride = mod( p, 2.0 ) - vec2( 1.0 );
|
|
3179
|
+
stride = abs( stride );
|
|
3180
|
+
|
|
3181
|
+
vec2 pWidth = fwidth( p );
|
|
3182
|
+
vec2 line = smoothstep( 1.0 - pWidth / 2.0, 1.0 + pWidth / 2.0, stride + thickness * pWidth );
|
|
3183
|
+
|
|
3184
|
+
return max( line.x, line.y );
|
|
3185
|
+
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
vec3 getFaceColor( vec2 p, vec3 color ) {
|
|
3189
|
+
|
|
3190
|
+
float checkLarge = getCheckerboard( p, 1.0 );
|
|
3191
|
+
float checkSmall = abs( getCheckerboard( p, 0.1 ) );
|
|
3192
|
+
float lines = getGrid( p, 10.0, 1.0 );
|
|
3193
|
+
|
|
3194
|
+
vec3 checkColor = mix(
|
|
3195
|
+
vec3( 0.7 ) * color,
|
|
3196
|
+
vec3( 1.0 ) * color,
|
|
3197
|
+
checkSmall * 0.4 + checkLarge * 0.6
|
|
3198
|
+
);
|
|
3199
|
+
|
|
3200
|
+
vec3 gridColor = vec3( 1.0 );
|
|
3201
|
+
|
|
3202
|
+
return mix( checkColor, gridColor, lines );
|
|
3203
|
+
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
float angleBetween( vec3 a, vec3 b ) {
|
|
3207
|
+
|
|
3208
|
+
return acos( abs( dot( a, b ) ) );
|
|
3209
|
+
|
|
3210
|
+
}
|
|
3211
|
+
|
|
3212
|
+
vec3 planeProject( vec3 norm, vec3 other ) {
|
|
3213
|
+
|
|
3214
|
+
float d = dot( norm, other );
|
|
3215
|
+
return normalize( other - norm * d );
|
|
3216
|
+
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
vec3 getBlendFactors( vec3 norm ) {
|
|
3220
|
+
|
|
3221
|
+
vec3 xVec = vec3( 1.0, 0.0, 0.0 );
|
|
3222
|
+
vec3 yVec = vec3( 0.0, 1.0, 0.0 );
|
|
3223
|
+
vec3 zVec = vec3( 0.0, 0.0, 1.0 );
|
|
3224
|
+
|
|
3225
|
+
vec3 projX = planeProject( xVec, norm );
|
|
3226
|
+
vec3 projY = planeProject( yVec, norm );
|
|
3227
|
+
vec3 projZ = planeProject( zVec, norm );
|
|
3228
|
+
|
|
3229
|
+
float xAngle = max(
|
|
3230
|
+
angleBetween( xVec, projY ),
|
|
3231
|
+
angleBetween( xVec, projZ )
|
|
3232
|
+
);
|
|
3233
|
+
|
|
3234
|
+
float yAngle = max(
|
|
3235
|
+
angleBetween( yVec, projX ),
|
|
3236
|
+
angleBetween( yVec, projZ )
|
|
3237
|
+
);
|
|
3238
|
+
|
|
3239
|
+
float zAngle = max(
|
|
3240
|
+
angleBetween( zVec, projX ),
|
|
3241
|
+
angleBetween( zVec, projY )
|
|
3242
|
+
);
|
|
3243
|
+
|
|
3244
|
+
return vec3( xAngle, yAngle, zAngle ) / ( 0.5 * PI );
|
|
3245
|
+
|
|
3246
|
+
}
|
|
3247
|
+
` ).replace(
|
|
3248
|
+
/#include <normal_fragment_maps>/,
|
|
3249
|
+
v =>
|
|
3250
|
+
/* glsl */`${v}
|
|
3251
|
+
#if CSG_GRID
|
|
3252
|
+
{
|
|
3253
|
+
|
|
3254
|
+
vec3 worldNormal = inverseTransformDirection( normal, viewMatrix );
|
|
3255
|
+
|
|
3256
|
+
float yCont = abs( dot( vec3( 0.0, 1.0, 0.0 ), worldNormal ) );
|
|
3257
|
+
float zCont = abs( dot( vec3( 0.0, 0.0, 1.0 ), worldNormal ) );
|
|
3258
|
+
float xCont = abs( dot( vec3( 1.0, 0.0, 0.0 ), worldNormal ) );
|
|
3259
|
+
|
|
3260
|
+
vec3 factors = getBlendFactors( worldNormal );
|
|
3261
|
+
factors = smoothstep( vec3( 0.475 ), vec3( 0.525 ), vec3( 1.0 ) - factors );
|
|
3262
|
+
|
|
3263
|
+
float weight = factors.x + factors.y + factors.z;
|
|
3264
|
+
factors /= weight;
|
|
3265
|
+
|
|
3266
|
+
vec3 color =
|
|
3267
|
+
getFaceColor( wPosition.yz, diffuseColor.rgb ) * factors.x +
|
|
3268
|
+
getFaceColor( wPosition.xz, diffuseColor.rgb ) * factors.y +
|
|
3269
|
+
getFaceColor( wPosition.xy, diffuseColor.rgb ) * factors.z;
|
|
3270
|
+
|
|
3271
|
+
diffuseColor.rgb = color;
|
|
3272
|
+
|
|
3273
|
+
}
|
|
3274
|
+
#endif
|
|
3275
|
+
`,
|
|
3276
|
+
);
|
|
3277
|
+
|
|
3278
|
+
return shader;
|
|
3279
|
+
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
class GridMaterial extends MeshPhongMaterial {
|
|
3283
|
+
|
|
3284
|
+
get enableGrid() {
|
|
3285
|
+
|
|
3286
|
+
return Boolean( this._enableGrid );
|
|
3287
|
+
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
set enableGrid( v ) {
|
|
3291
|
+
|
|
3292
|
+
if ( this._enableGrid !== v ) {
|
|
3293
|
+
|
|
3294
|
+
this._enableGrid = v;
|
|
3295
|
+
this.needsUpdate = true;
|
|
3296
|
+
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
constructor( ...args ) {
|
|
3302
|
+
|
|
3303
|
+
super( ...args );
|
|
3304
|
+
this.enableGrid = true;
|
|
3305
|
+
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
onBeforeCompile( shader ) {
|
|
3309
|
+
|
|
3310
|
+
csgGridShaderMixin( shader );
|
|
3311
|
+
shader.defines.CSG_GRID = Number( this.enableGrid );
|
|
3312
|
+
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
customProgramCacheKey() {
|
|
3316
|
+
|
|
3317
|
+
return this.enableGrid.toString();
|
|
3318
|
+
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
function getTriangleDefinitions( ...triangles ) {
|
|
3324
|
+
|
|
3325
|
+
function getVectorDefinition( v ) {
|
|
3326
|
+
|
|
3327
|
+
return /* js */`new THREE.Vector3( ${ v.x }, ${ v.y }, ${ v.z } )`;
|
|
3328
|
+
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
return triangles.map( t => {
|
|
3332
|
+
|
|
3333
|
+
return /* js */`
|
|
3334
|
+
new THREE.Triangle(
|
|
3335
|
+
${ getVectorDefinition( t.a ) },
|
|
3336
|
+
${ getVectorDefinition( t.b ) },
|
|
3337
|
+
${ getVectorDefinition( t.c ) },
|
|
3338
|
+
)
|
|
3339
|
+
`.trim();
|
|
3340
|
+
|
|
3341
|
+
} );
|
|
3342
|
+
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
function logTriangleDefinitions( ...triangles ) {
|
|
3346
|
+
|
|
3347
|
+
console.log( getTriangleDefinitions( ...triangles ).join( ',\n' ) );
|
|
3348
|
+
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
function generateRandomTriangleColors( geometry ) {
|
|
3352
|
+
|
|
3353
|
+
const position = geometry.attributes.position;
|
|
3354
|
+
const array = new Float32Array( position.count * 3 );
|
|
3355
|
+
|
|
3356
|
+
const color = new Color();
|
|
3357
|
+
for ( let i = 0, l = array.length; i < l; i += 9 ) {
|
|
3358
|
+
|
|
3359
|
+
color.setHSL(
|
|
3360
|
+
Math.random(),
|
|
3361
|
+
MathUtils.lerp( 0.5, 1.0, Math.random() ),
|
|
3362
|
+
MathUtils.lerp( 0.5, 0.75, Math.random() ),
|
|
3363
|
+
);
|
|
3364
|
+
|
|
3365
|
+
array[ i + 0 ] = color.r;
|
|
3366
|
+
array[ i + 1 ] = color.g;
|
|
3367
|
+
array[ i + 2 ] = color.b;
|
|
3368
|
+
|
|
3369
|
+
array[ i + 3 ] = color.r;
|
|
3370
|
+
array[ i + 4 ] = color.g;
|
|
3371
|
+
array[ i + 5 ] = color.b;
|
|
3372
|
+
|
|
3373
|
+
array[ i + 6 ] = color.r;
|
|
3374
|
+
array[ i + 7 ] = color.g;
|
|
3375
|
+
array[ i + 8 ] = color.b;
|
|
3376
|
+
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
geometry.setAttribute( 'color', new BufferAttribute( array, 3 ) );
|
|
3380
|
+
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3383
|
+
class TriangleSetHelper extends Group {
|
|
3384
|
+
|
|
3385
|
+
get color() {
|
|
3386
|
+
|
|
3387
|
+
return this._mesh.material.color;
|
|
3388
|
+
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
get side() {
|
|
3392
|
+
|
|
3393
|
+
return this._mesh.material.side;
|
|
3394
|
+
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
set side( v ) {
|
|
3398
|
+
|
|
3399
|
+
this._mesh.material.side = v;
|
|
3400
|
+
|
|
3401
|
+
}
|
|
3402
|
+
|
|
3403
|
+
constructor( triangles = [] ) {
|
|
3404
|
+
|
|
3405
|
+
super();
|
|
3406
|
+
|
|
3407
|
+
const geometry = new BufferGeometry();
|
|
3408
|
+
const lineGeom = new BufferGeometry();
|
|
3409
|
+
this._mesh = new Mesh( geometry, new MeshPhongMaterial( {
|
|
3410
|
+
flatShading: true,
|
|
3411
|
+
transparent: true,
|
|
3412
|
+
opacity: 0.25,
|
|
3413
|
+
depthWrite: false,
|
|
3414
|
+
} ) );
|
|
3415
|
+
this._lines = new LineSegments( lineGeom, new LineBasicMaterial() );
|
|
3416
|
+
this._mesh.material.color = this._lines.material.color;
|
|
3417
|
+
|
|
3418
|
+
this._lines.frustumCulled = false;
|
|
3419
|
+
this._mesh.frustumCulled = false;
|
|
3420
|
+
|
|
3421
|
+
this.add( this._lines, this._mesh );
|
|
3422
|
+
|
|
3423
|
+
this.setTriangles( triangles );
|
|
3424
|
+
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3427
|
+
setTriangles( triangles ) {
|
|
3428
|
+
|
|
3429
|
+
const triPositions = new Float32Array( 3 * 3 * triangles.length );
|
|
3430
|
+
const linePositions = new Float32Array( 6 * 3 * triangles.length );
|
|
3431
|
+
for ( let i = 0, l = triangles.length; i < l; i ++ ) {
|
|
3432
|
+
|
|
3433
|
+
const i9 = 9 * i;
|
|
3434
|
+
const i18 = 18 * i;
|
|
3435
|
+
const tri = triangles[ i ];
|
|
3436
|
+
|
|
3437
|
+
tri.a.toArray( triPositions, i9 + 0 );
|
|
3438
|
+
tri.b.toArray( triPositions, i9 + 3 );
|
|
3439
|
+
tri.c.toArray( triPositions, i9 + 6 );
|
|
3440
|
+
|
|
3441
|
+
|
|
3442
|
+
tri.a.toArray( linePositions, i18 + 0 );
|
|
3443
|
+
tri.b.toArray( linePositions, i18 + 3 );
|
|
3444
|
+
|
|
3445
|
+
tri.b.toArray( linePositions, i18 + 6 );
|
|
3446
|
+
tri.c.toArray( linePositions, i18 + 9 );
|
|
3447
|
+
|
|
3448
|
+
tri.c.toArray( linePositions, i18 + 12 );
|
|
3449
|
+
tri.a.toArray( linePositions, i18 + 15 );
|
|
3450
|
+
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
this._mesh.geometry.dispose();
|
|
3454
|
+
this._mesh.geometry.setAttribute( 'position', new BufferAttribute( triPositions, 3 ) );
|
|
3455
|
+
|
|
3456
|
+
this._lines.geometry.dispose();
|
|
3457
|
+
this._lines.geometry.setAttribute( 'position', new BufferAttribute( linePositions, 3 ) );
|
|
3458
|
+
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
class EdgesHelper extends LineSegments {
|
|
3464
|
+
|
|
3465
|
+
get color() {
|
|
3466
|
+
|
|
3467
|
+
return this.material.color;
|
|
3468
|
+
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
constructor( edges = [] ) {
|
|
3472
|
+
|
|
3473
|
+
super();
|
|
3474
|
+
this.frustumCulled = false;
|
|
3475
|
+
this.setEdges( edges );
|
|
3476
|
+
|
|
3477
|
+
}
|
|
3478
|
+
|
|
3479
|
+
setEdges( edges ) {
|
|
3480
|
+
|
|
3481
|
+
const { geometry } = this;
|
|
3482
|
+
const points = edges.flatMap( e => [ e.start, e.end ] );
|
|
3483
|
+
geometry.dispose();
|
|
3484
|
+
geometry.setFromPoints( points );
|
|
3485
|
+
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
const _matrix = new Matrix4();
|
|
3491
|
+
class PointsHelper extends InstancedMesh {
|
|
3492
|
+
|
|
3493
|
+
get color() {
|
|
3494
|
+
|
|
3495
|
+
return this.material.color;
|
|
3496
|
+
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
constructor( count = 1000, points = [] ) {
|
|
3500
|
+
|
|
3501
|
+
super( new SphereGeometry( 0.025 ), new MeshBasicMaterial(), count );
|
|
3502
|
+
this.frustumCulled = false;
|
|
3503
|
+
this.setPoints( points );
|
|
3504
|
+
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
setPoints( points ) {
|
|
3508
|
+
|
|
3509
|
+
for ( let i = 0, l = points.length; i < l; i ++ ) {
|
|
3510
|
+
|
|
3511
|
+
const point = points[ i ];
|
|
3512
|
+
_matrix.makeTranslation( point.x, point.y, point.z );
|
|
3513
|
+
this.setMatrixAt( i, _matrix );
|
|
3514
|
+
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
this.count = points.length;
|
|
3518
|
+
|
|
3519
|
+
}
|
|
3520
|
+
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
const vertKeys = [ 'a', 'b', 'c' ];
|
|
3524
|
+
const _tri1 = new Triangle();
|
|
3525
|
+
const _tri2 = new Triangle();
|
|
3526
|
+
const _center = new Vector3();
|
|
3527
|
+
const _center2 = new Vector3();
|
|
3528
|
+
const _projected = new Vector3();
|
|
3529
|
+
const _projected2 = new Vector3();
|
|
3530
|
+
const _projectedDir = new Vector3();
|
|
3531
|
+
const _projectedDir2 = new Vector3();
|
|
3532
|
+
const _edgeDir = new Vector3();
|
|
3533
|
+
const _edgeDir2 = new Vector3();
|
|
3534
|
+
const _vec = new Vector3();
|
|
3535
|
+
const _vec2 = new Vector3();
|
|
3536
|
+
const _finalPoint = new Vector3();
|
|
3537
|
+
const _finalPoint2 = new Vector3();
|
|
3538
|
+
const _plane = new Plane();
|
|
3539
|
+
const _plane2 = new Plane();
|
|
3540
|
+
const _centerPoint = new Vector3();
|
|
3541
|
+
const _ray = new Ray();
|
|
3542
|
+
const _edge = new Line3();
|
|
3543
|
+
|
|
3544
|
+
function getTriangle( geometry, triIndex, target ) {
|
|
3545
|
+
|
|
3546
|
+
const i3 = 3 * triIndex;
|
|
3547
|
+
let i0 = i3 + 0;
|
|
3548
|
+
let i1 = i3 + 1;
|
|
3549
|
+
let i2 = i3 + 2;
|
|
3550
|
+
|
|
3551
|
+
const indexAttr = geometry.index;
|
|
3552
|
+
const posAttr = geometry.attributes.position;
|
|
3553
|
+
if ( indexAttr ) {
|
|
3554
|
+
|
|
3555
|
+
i0 = indexAttr.getX( i0 );
|
|
3556
|
+
i1 = indexAttr.getX( i1 );
|
|
3557
|
+
i2 = indexAttr.getX( i2 );
|
|
3558
|
+
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
target.a.fromBufferAttribute( posAttr, i0 );
|
|
3562
|
+
target.b.fromBufferAttribute( posAttr, i1 );
|
|
3563
|
+
target.c.fromBufferAttribute( posAttr, i2 );
|
|
3564
|
+
|
|
3565
|
+
return target;
|
|
3566
|
+
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
function getOverlapEdge( tri1, e1, tri2, e2, target ) {
|
|
3570
|
+
|
|
3571
|
+
// get the two edges
|
|
3572
|
+
const nextE_0 = ( e1 + 1 ) % 3;
|
|
3573
|
+
const v0_1 = tri1[ vertKeys[ e1 ] ];
|
|
3574
|
+
const v1_1 = tri1[ vertKeys[ nextE_0 ] ];
|
|
3575
|
+
|
|
3576
|
+
const nextE_1 = ( e2 + 1 ) % 3;
|
|
3577
|
+
const v0_2 = tri2[ vertKeys[ e2 ] ];
|
|
3578
|
+
const v1_2 = tri2[ vertKeys[ nextE_1 ] ];
|
|
3579
|
+
|
|
3580
|
+
// get the ray defined by the edges
|
|
3581
|
+
toNormalizedRay( v0_1, v1_1, _ray );
|
|
3582
|
+
|
|
3583
|
+
// get the min and max stride across the rays
|
|
3584
|
+
let d0_1 = _vec.subVectors( v0_1, _ray.origin ).dot( _ray.direction );
|
|
3585
|
+
let d1_1 = _vec.subVectors( v1_1, _ray.origin ).dot( _ray.direction );
|
|
3586
|
+
if ( d0_1 > d1_1 ) [ d0_1, d1_1 ] = [ d1_1, d0_1 ];
|
|
3587
|
+
|
|
3588
|
+
let d0_2 = _vec.subVectors( v0_2, _ray.origin ).dot( _ray.direction );
|
|
3589
|
+
let d1_2 = _vec.subVectors( v1_2, _ray.origin ).dot( _ray.direction );
|
|
3590
|
+
if ( d0_2 > d1_2 ) [ d0_2, d1_2 ] = [ d1_2, d0_2 ];
|
|
3591
|
+
|
|
3592
|
+
// get the range of overlap
|
|
3593
|
+
const final_0 = Math.max( d0_1, d0_2 );
|
|
3594
|
+
const final_1 = Math.min( d1_1, d1_2 );
|
|
3595
|
+
_ray.at( final_0, target.start );
|
|
3596
|
+
_ray.at( final_1, target.end );
|
|
3597
|
+
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
|
|
3601
|
+
class HalfEdgeHelper extends EdgesHelper {
|
|
3602
|
+
|
|
3603
|
+
constructor( geometry = null, halfEdges = null ) {
|
|
3604
|
+
|
|
3605
|
+
super();
|
|
3606
|
+
this.straightEdges = false;
|
|
3607
|
+
this.displayDisconnectedEdges = false;
|
|
3608
|
+
|
|
3609
|
+
if ( geometry && halfEdges ) {
|
|
3610
|
+
|
|
3611
|
+
this.setHalfEdges( geometry, halfEdges );
|
|
3612
|
+
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3617
|
+
setHalfEdges( geometry, halfEdges ) {
|
|
3618
|
+
|
|
3619
|
+
const { straightEdges, displayDisconnectedEdges } = this;
|
|
3620
|
+
const edges = [];
|
|
3621
|
+
const offset = geometry.drawRange.start;
|
|
3622
|
+
let triCount = getTriCount( geometry );
|
|
3623
|
+
if ( geometry.drawRange.count !== Infinity ) {
|
|
3624
|
+
|
|
3625
|
+
triCount = ~ ~ ( geometry.drawRange.count / 3 );
|
|
3626
|
+
|
|
3627
|
+
}
|
|
3628
|
+
|
|
3629
|
+
if ( displayDisconnectedEdges ) {
|
|
3630
|
+
|
|
3631
|
+
if ( halfEdges.unmatchedDisjointEdges ) {
|
|
3632
|
+
|
|
3633
|
+
halfEdges
|
|
3634
|
+
.unmatchedDisjointEdges
|
|
3635
|
+
.forEach( ( { forward, reverse, ray } ) => {
|
|
3636
|
+
|
|
3637
|
+
[ ...forward, ...reverse ]
|
|
3638
|
+
.forEach( ( { start, end } ) => {
|
|
3639
|
+
|
|
3640
|
+
const edge = new Line3();
|
|
3641
|
+
ray.at( start, edge.start );
|
|
3642
|
+
ray.at( end, edge.end );
|
|
3643
|
+
edges.push( edge );
|
|
3644
|
+
|
|
3645
|
+
} );
|
|
3646
|
+
|
|
3647
|
+
} );
|
|
3648
|
+
|
|
3649
|
+
} else {
|
|
3650
|
+
|
|
3651
|
+
for ( let triIndex = offset; triIndex < triCount; triIndex ++ ) {
|
|
3652
|
+
|
|
3653
|
+
getTriangle( geometry, triIndex, _tri1 );
|
|
3654
|
+
for ( let e = 0; e < 3; e ++ ) {
|
|
3655
|
+
|
|
3656
|
+
const otherTriIndex = halfEdges.getSiblingTriangleIndex( triIndex, e );
|
|
3657
|
+
if ( otherTriIndex === - 1 ) {
|
|
3658
|
+
|
|
3659
|
+
const nextE = ( e + 1 ) % 3;
|
|
3660
|
+
const v0 = _tri1[ vertKeys[ e ] ];
|
|
3661
|
+
const v1 = _tri1[ vertKeys[ nextE ] ];
|
|
3662
|
+
const edge = new Line3();
|
|
3663
|
+
edge.start.copy( v0 );
|
|
3664
|
+
edge.end.copy( v1 );
|
|
3665
|
+
edges.push( edge );
|
|
3666
|
+
|
|
3667
|
+
}
|
|
3668
|
+
|
|
3669
|
+
}
|
|
3670
|
+
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
}
|
|
3674
|
+
|
|
3675
|
+
} else {
|
|
3676
|
+
|
|
3677
|
+
for ( let triIndex = offset; triIndex < triCount; triIndex ++ ) {
|
|
3678
|
+
|
|
3679
|
+
getTriangle( geometry, triIndex, _tri1 );
|
|
3680
|
+
for ( let e = 0; e < 3; e ++ ) {
|
|
3681
|
+
|
|
3682
|
+
const otherTriIndex = halfEdges.getSiblingTriangleIndex( triIndex, e );
|
|
3683
|
+
if ( otherTriIndex === - 1 ) {
|
|
3684
|
+
|
|
3685
|
+
continue;
|
|
3686
|
+
|
|
3687
|
+
}
|
|
3688
|
+
|
|
3689
|
+
// get other triangle
|
|
3690
|
+
getTriangle( geometry, otherTriIndex, _tri2 );
|
|
3691
|
+
|
|
3692
|
+
// get edge centers
|
|
3693
|
+
const nextE = ( e + 1 ) % 3;
|
|
3694
|
+
const v0 = _tri1[ vertKeys[ e ] ];
|
|
3695
|
+
const v1 = _tri1[ vertKeys[ nextE ] ];
|
|
3696
|
+
_centerPoint.lerpVectors( v0, v1, 0.5 );
|
|
3697
|
+
addConnectionEdge( _tri1, _tri2, _centerPoint );
|
|
3698
|
+
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3701
|
+
if ( halfEdges.disjointConnections ) {
|
|
3702
|
+
|
|
3703
|
+
for ( let e = 0; e < 3; e ++ ) {
|
|
3704
|
+
|
|
3705
|
+
const disjointTriIndices = halfEdges.getDisjointSiblingTriangleIndices( triIndex, e );
|
|
3706
|
+
const disjointEdgeIndices = halfEdges.getDisjointSiblingEdgeIndices( triIndex, e );
|
|
3707
|
+
|
|
3708
|
+
for ( let i = 0; i < disjointTriIndices.length; i ++ ) {
|
|
3709
|
+
|
|
3710
|
+
const ti = disjointTriIndices[ i ];
|
|
3711
|
+
const ei = disjointEdgeIndices[ i ];
|
|
3712
|
+
|
|
3713
|
+
// get other triangle
|
|
3714
|
+
getTriangle( geometry, ti, _tri2 );
|
|
3715
|
+
|
|
3716
|
+
getOverlapEdge( _tri1, e, _tri2, ei, _edge );
|
|
3717
|
+
|
|
3718
|
+
_centerPoint.lerpVectors( _edge.start, _edge.end, 0.5 );
|
|
3719
|
+
addConnectionEdge( _tri1, _tri2, _centerPoint );
|
|
3720
|
+
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
}
|
|
3724
|
+
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3729
|
+
}
|
|
3730
|
+
|
|
3731
|
+
super.setEdges( edges );
|
|
3732
|
+
|
|
3733
|
+
function addConnectionEdge( tri1, tri2, centerPoint ) {
|
|
3734
|
+
|
|
3735
|
+
tri1.getMidpoint( _center );
|
|
3736
|
+
tri2.getMidpoint( _center2 );
|
|
3737
|
+
|
|
3738
|
+
tri1.getPlane( _plane );
|
|
3739
|
+
tri2.getPlane( _plane2 );
|
|
3740
|
+
|
|
3741
|
+
const edge = new Line3();
|
|
3742
|
+
edge.start.copy( _center );
|
|
3743
|
+
|
|
3744
|
+
if ( straightEdges ) {
|
|
3745
|
+
|
|
3746
|
+
// get the projected centers
|
|
3747
|
+
_plane.projectPoint( _center2, _projected );
|
|
3748
|
+
_plane2.projectPoint( _center, _projected2 );
|
|
3749
|
+
|
|
3750
|
+
// get the directions so we can flip them if needed
|
|
3751
|
+
_projectedDir.subVectors( _projected, _center );
|
|
3752
|
+
_projectedDir2.subVectors( _projected2, _center2 );
|
|
3753
|
+
|
|
3754
|
+
// get the directions so we can flip them if needed
|
|
3755
|
+
_edgeDir.subVectors( centerPoint, _center );
|
|
3756
|
+
_edgeDir2.subVectors( centerPoint, _center2 );
|
|
3757
|
+
|
|
3758
|
+
if ( _projectedDir.dot( _edgeDir ) < 0 ) {
|
|
3759
|
+
|
|
3760
|
+
_projectedDir.multiplyScalar( - 1 );
|
|
3761
|
+
|
|
3762
|
+
}
|
|
3763
|
+
|
|
3764
|
+
if ( _projectedDir2.dot( _edgeDir2 ) < 0 ) {
|
|
3765
|
+
|
|
3766
|
+
_projectedDir2.multiplyScalar( - 1 );
|
|
3767
|
+
|
|
3768
|
+
}
|
|
3769
|
+
|
|
3770
|
+
// find the new points after inversion
|
|
3771
|
+
_vec.addVectors( _center, _projectedDir );
|
|
3772
|
+
_vec2.addVectors( _center2, _projectedDir2 );
|
|
3773
|
+
|
|
3774
|
+
// project the points onto the triangle edge. This would be better
|
|
3775
|
+
// if we clipped instead of chose the closest point
|
|
3776
|
+
tri1.closestPointToPoint( _vec, _finalPoint );
|
|
3777
|
+
tri2.closestPointToPoint( _vec2, _finalPoint2 );
|
|
3778
|
+
|
|
3779
|
+
edge.end.lerpVectors( _finalPoint, _finalPoint2, 0.5 );
|
|
3780
|
+
|
|
3781
|
+
} else {
|
|
3782
|
+
|
|
3783
|
+
edge.end.copy( centerPoint );
|
|
3784
|
+
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3787
|
+
edges.push( edge );
|
|
3788
|
+
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
}
|
|
3794
|
+
|
|
3795
|
+
// https://stackoverflow.com/questions/1406029/how-to-calculate-the-volume-of-a-3d-mesh-object-the-surface-of-which-is-made-up
|
|
3796
|
+
const _tri = new Triangle();
|
|
3797
|
+
const _normal = new Vector3();
|
|
3798
|
+
const _relPoint = new Vector3();
|
|
3799
|
+
function computeMeshVolume( mesh ) {
|
|
3800
|
+
|
|
3801
|
+
// grab the matrix and the geometry
|
|
3802
|
+
let geometry;
|
|
3803
|
+
let matrix;
|
|
3804
|
+
if ( mesh.isBufferGeometry ) {
|
|
3805
|
+
|
|
3806
|
+
geometry = mesh;
|
|
3807
|
+
matrix = null;
|
|
3808
|
+
|
|
3809
|
+
} else {
|
|
3810
|
+
|
|
3811
|
+
geometry = mesh.geometry;
|
|
3812
|
+
matrix = Math.abs( mesh.matrixWorld.determinant() - 1.0 ) < 1e-15 ? null : mesh.matrixWorld;
|
|
3813
|
+
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3816
|
+
// determine the number of relevant draw range elements to use
|
|
3817
|
+
const index = geometry.index;
|
|
3818
|
+
const pos = geometry.attributes.position;
|
|
3819
|
+
const drawRange = geometry.drawRange;
|
|
3820
|
+
const triCount = Math.min( getTriCount( geometry ), drawRange.count / 3 );
|
|
3821
|
+
|
|
3822
|
+
// get a point relative to the position of the geometry to avoid floating point error
|
|
3823
|
+
_tri.setFromAttributeAndIndices( pos, 0, 1, 2 );
|
|
3824
|
+
applyMatrix4ToTri( _tri, matrix );
|
|
3825
|
+
_tri.getNormal( _normal );
|
|
3826
|
+
_tri.getMidpoint( _relPoint ).add( _normal );
|
|
3827
|
+
|
|
3828
|
+
// iterate over all triangles
|
|
3829
|
+
let volume = 0;
|
|
3830
|
+
const startIndex = drawRange.start / 3;
|
|
3831
|
+
for ( let i = startIndex, l = startIndex + triCount; i < l; i ++ ) {
|
|
3832
|
+
|
|
3833
|
+
let i0 = 3 * i + 0;
|
|
3834
|
+
let i1 = 3 * i + 1;
|
|
3835
|
+
let i2 = 3 * i + 2;
|
|
3836
|
+
if ( index ) {
|
|
3837
|
+
|
|
3838
|
+
i0 = index.getX( i0 );
|
|
3839
|
+
i1 = index.getX( i1 );
|
|
3840
|
+
i2 = index.getX( i2 );
|
|
3841
|
+
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3844
|
+
// get the triangle
|
|
3845
|
+
_tri.setFromAttributeAndIndices( pos, i0, i1, i2 );
|
|
3846
|
+
applyMatrix4ToTri( _tri, matrix );
|
|
3847
|
+
subVectorFromTri( _tri, _relPoint );
|
|
3848
|
+
|
|
3849
|
+
// add the signed volume
|
|
3850
|
+
volume += signedVolumeOfTriangle( _tri.a, _tri.b, _tri.c );
|
|
3851
|
+
|
|
3852
|
+
}
|
|
3853
|
+
|
|
3854
|
+
return Math.abs( volume );
|
|
3855
|
+
|
|
3856
|
+
}
|
|
3857
|
+
|
|
3858
|
+
function signedVolumeOfTriangle( p1, p2, p3 ) {
|
|
3859
|
+
|
|
3860
|
+
const v321 = p3.x * p2.y * p1.z;
|
|
3861
|
+
const v231 = p2.x * p3.y * p1.z;
|
|
3862
|
+
const v312 = p3.x * p1.y * p2.z;
|
|
3863
|
+
const v132 = p1.x * p3.y * p2.z;
|
|
3864
|
+
const v213 = p2.x * p1.y * p3.z;
|
|
3865
|
+
const v123 = p1.x * p2.y * p3.z;
|
|
3866
|
+
return ( 1 / 6 ) * ( - v321 + v231 + v312 - v132 - v213 + v123 );
|
|
3867
|
+
|
|
3868
|
+
}
|
|
3869
|
+
|
|
3870
|
+
function subVectorFromTri( tri, pos ) {
|
|
3871
|
+
|
|
3872
|
+
tri.a.sub( pos );
|
|
3873
|
+
tri.b.sub( pos );
|
|
3874
|
+
tri.c.sub( pos );
|
|
3875
|
+
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
function applyMatrix4ToTri( tri, mat = null ) {
|
|
3879
|
+
|
|
3880
|
+
if ( mat !== null ) {
|
|
3881
|
+
|
|
3882
|
+
tri.a.applyMatrix4( mat );
|
|
3883
|
+
tri.b.applyMatrix4( mat );
|
|
3884
|
+
tri.c.applyMatrix4( mat );
|
|
3885
|
+
|
|
3886
|
+
}
|
|
3887
|
+
|
|
3888
|
+
}
|
|
3889
|
+
|
|
3890
|
+
export { ADDITION, Brush, DIFFERENCE, EdgesHelper, Evaluator, GridMaterial, HOLLOW_INTERSECTION, HOLLOW_SUBTRACTION, HalfEdgeHelper, HalfEdgeMap, INTERSECTION, Operation, OperationGroup, PointsHelper, REVERSE_SUBTRACTION, SUBTRACTION, TriangleSetHelper, TriangleSplitter, computeMeshVolume, generateRandomTriangleColors, getTriangleDefinitions, logTriangleDefinitions };
|
|
3891
|
+
//# sourceMappingURL=index.module.js.map
|