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.
@@ -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