Skip to content

Commit ae91d85

Browse files
committedMar 18, 2025
Automatically clean shape data passed to ExtrudeGeometry
Before triangulating a shape, ExtrudeGeometry now welds any index-adjacent points within a small hardcoded distance threshold of each other. This resolves common triangulation artifacts caused by improperly formatted shape data. Also improved variable names and commenting in the affected files.
1 parent 17d0b43 commit ae91d85

File tree

2 files changed

+101
-27
lines changed

2 files changed

+101
-27
lines changed
 

‎src/extras/ShapeUtils.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class ShapeUtils {
4141
}
4242

4343
/**
44-
* Triangulates the given shape definition.
44+
* Triangulates the given shape definition using Earcut.
4545
*
4646
* @param {Array<Vector2>} contour - An array of 2D points defining the contour.
4747
* @param {Array<Array<Vector2>>} holes - An array that holds arrays of 2D points defining the holes.
@@ -58,20 +58,22 @@ class ShapeUtils {
5858

5959
//
6060

61-
let holeIndex = contour.length;
61+
let holeFirstVertIndex = contour.length;
6262

6363
holes.forEach( removeDupEndPts );
6464

6565
for ( let i = 0; i < holes.length; i ++ ) {
6666

67-
holeIndices.push( holeIndex );
68-
holeIndex += holes[ i ].length;
67+
holeIndices.push( holeFirstVertIndex );
68+
holeFirstVertIndex += holes[ i ].length;
6969
addContour( vertices, holes[ i ] );
7070

7171
}
7272

7373
//
7474

75+
//At this point we have a flat number array of X,Y values, and a list of indices in that list which correspond to the start of holes.
76+
//So eg. <contour points>,<hole 1 points>,<hole 2 points>...
7577
const triangles = Earcut.triangulate( vertices, holeIndices );
7678

7779
//
@@ -100,6 +102,10 @@ function removeDupEndPts( points ) {
100102

101103
}
102104

105+
/**Pushes each X,Y pair of the contour onto the vertices array.
106+
* @param {number[]} vertices Flat array of X,Y pairs to append to.
107+
* @param {Vector2[]} contour
108+
*/
103109
function addContour( vertices, contour ) {
104110

105111
for ( let i = 0; i < contour.length; i ++ ) {

‎src/geometries/ExtrudeGeometry.js

+91-23
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ class ExtrudeGeometry extends BufferGeometry {
5757

5858
const scope = this;
5959

60-
const verticesArray = [];
60+
const meshOutVerticesArray = [];
6161
const uvArray = [];
6262

63+
6364
for ( let i = 0, l = shapes.length; i < l; i ++ ) {
6465

6566
const shape = shapes[ i ];
@@ -69,7 +70,7 @@ class ExtrudeGeometry extends BufferGeometry {
6970

7071
// build geometry
7172

72-
this.setAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) );
73+
this.setAttribute( 'position', new Float32BufferAttribute( meshOutVerticesArray, 3 ) );
7374
this.setAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) );
7475

7576
this.computeVertexNormals();
@@ -78,7 +79,7 @@ class ExtrudeGeometry extends BufferGeometry {
7879

7980
function addShape( shape ) {
8081

81-
const placeholder = [];
82+
const uniqueShapeVertices = [];
8283

8384
// options
8485

@@ -162,14 +163,44 @@ class ExtrudeGeometry extends BufferGeometry {
162163

163164
}
164165

166+
function cleanPoints( points ) {
167+
168+
const THRESHOLD = 0.0002;
169+
const THRESHOLD_SQ = THRESHOLD * THRESHOLD;
170+
let prevPos = points[ 0 ];
171+
for ( let i = 1; i <= points.length; i ++ ) {
172+
173+
const currentIndex = i % points.length;
174+
const currentPos = points[ currentIndex ];
175+
const dx = currentPos.x - prevPos.x;
176+
const dy = currentPos.y - prevPos.y;
177+
const distSq = dx * dx + dy * dy;
178+
179+
if ( distSq <= THRESHOLD_SQ ) {
180+
181+
points.splice( currentIndex, 1 );
182+
if ( currentIndex == 0 ) break;
183+
i --;
184+
continue;
185+
186+
}
187+
188+
prevPos = currentPos;
189+
190+
}
191+
192+
}
193+
194+
cleanPoints( vertices );
195+
holes.forEach( cleanPoints );
165196

166-
const faces = ShapeUtils.triangulateShape( vertices, holes );
197+
const numHoles = holes.length;
167198

168199
/* Vertices */
169200

170201
const contour = vertices; // vertices has all points but contour has only points of circumference
171202

172-
for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
203+
for ( let h = 0; h < numHoles; h ++ ) {
173204

174205
const ahole = holes[ h ];
175206

@@ -178,6 +209,11 @@ class ExtrudeGeometry extends BufferGeometry {
178209
}
179210

180211

212+
/**
213+
* @param {Vector2} pt
214+
* @param {Vector2} vec
215+
* @param {number} size
216+
* @returns {Vector2} pt + (vec * size) */
181217
function scalePt2( pt, vec, size ) {
182218

183219
if ( ! vec ) console.error( 'THREE.ExtrudeGeometry: vec does not exist' );
@@ -186,7 +222,7 @@ class ExtrudeGeometry extends BufferGeometry {
186222

187223
}
188224

189-
const vlen = vertices.length, flen = faces.length;
225+
const vlen = vertices.length;
190226

191227

192228
// Find directions for point movement
@@ -316,6 +352,7 @@ class ExtrudeGeometry extends BufferGeometry {
316352
}
317353

318354

355+
/**contourMovements[i] refers to the bevelVec of contour vertices [i-1] -> [i] -> [i+1], wrapping around the array bounds. Which is to say, the normalized vector pointing "outside" of the contour ("left" when the differential is "forward").*/
319356
const contourMovements = [];
320357

321358
for ( let i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {
@@ -333,7 +370,7 @@ class ExtrudeGeometry extends BufferGeometry {
333370
const holesMovements = [];
334371
let oneHoleMovements, verticesMovements = contourMovements.concat();
335372

336-
for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
373+
for ( let h = 0, hl = numHoles; h < hl; h ++ ) {
337374

338375
const ahole = holes[ h ];
339376

@@ -354,52 +391,67 @@ class ExtrudeGeometry extends BufferGeometry {
354391

355392
}
356393

394+
const contractedContourVertices = [];
395+
const expandedHoleVertices = [];
357396

358397
// Loop bevelSegments, 1 for the front, 1 for the back
359398

360399
for ( let b = 0; b < bevelSegments; b ++ ) {
361400

362401
//for ( b = bevelSegments; b > 0; b -- ) {
363402

403+
/**Proportion of the way through the bevel. [0,slightly less than 1] */
364404
const t = b / bevelSegments;
405+
/**Decays smoothly from bevelThickness to almost 0.*/
365406
const z = bevelThickness * Math.cos( t * Math.PI / 2 );
407+
/**Grows smoothly from 0 to almost bevelSize, plus bevelOffset.*/
366408
const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset;
367409

368410
// contract shape
369411

370412
for ( let i = 0, il = contour.length; i < il; i ++ ) {
371413

414+
/**Contour point, beveled straight outwards bs units.*/
372415
const vert = scalePt2( contour[ i ], contourMovements[ i ], bs );
373416

374417
v( vert.x, vert.y, - z );
418+
if ( t == 0 ) contractedContourVertices.push( vert );
375419

376420
}
377421

378422
// expand holes
379423

380-
for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
424+
for ( let h = 0, hl = numHoles; h < hl; h ++ ) {
381425

382426
const ahole = holes[ h ];
383427
oneHoleMovements = holesMovements[ h ];
384-
428+
const oneHoleVertices = [];
385429
for ( let i = 0, il = ahole.length; i < il; i ++ ) {
386430

387431
const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
388432

389433
v( vert.x, vert.y, - z );
434+
if ( t == 0 ) oneHoleVertices.push( vert );
390435

391436
}
392437

438+
if ( t == 0 ) expandedHoleVertices.push( oneHoleVertices );
439+
393440
}
394441

395442
}
396443

444+
const faces = ShapeUtils.triangulateShape( contractedContourVertices, expandedHoleVertices );
445+
446+
const flen = faces.length;
447+
397448
const bs = bevelSize + bevelOffset;
398449

399450
// Back facing vertices
400451

401452
for ( let i = 0; i < vlen; i ++ ) {
402453

454+
//If beveled, move vert "outward" bevelSize + offset units.
403455
const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
404456

405457
if ( ! extrudeByPath ) {
@@ -512,7 +564,7 @@ class ExtrudeGeometry extends BufferGeometry {
512564

513565
function buildLidFaces() {
514566

515-
const start = verticesArray.length / 3;
567+
const start = meshOutVerticesArray.length / 3;
516568

517569
if ( bevelEnabled ) {
518570

@@ -562,15 +614,15 @@ class ExtrudeGeometry extends BufferGeometry {
562614

563615
}
564616

565-
scope.addGroup( start, verticesArray.length / 3 - start, 0 );
617+
scope.addGroup( start, meshOutVerticesArray.length / 3 - start, 0 );
566618

567619
}
568620

569621
// Create faces for the z-sides of the shape
570622

571623
function buildSideFaces() {
572624

573-
const start = verticesArray.length / 3;
625+
const start = meshOutVerticesArray.length / 3;
574626
let layeroffset = 0;
575627
sidewalls( contour, layeroffset );
576628
layeroffset += contour.length;
@@ -586,7 +638,7 @@ class ExtrudeGeometry extends BufferGeometry {
586638
}
587639

588640

589-
scope.addGroup( start, verticesArray.length / 3 - start, 1 );
641+
scope.addGroup( start, meshOutVerticesArray.length / 3 - start, 1 );
590642

591643

592644
}
@@ -623,28 +675,41 @@ class ExtrudeGeometry extends BufferGeometry {
623675

624676
function v( x, y, z ) {
625677

626-
placeholder.push( x );
627-
placeholder.push( y );
628-
placeholder.push( z );
678+
uniqueShapeVertices.push( x );
679+
uniqueShapeVertices.push( y );
680+
uniqueShapeVertices.push( z );
629681

630682
}
631683

632684

685+
/**Creates vertex and UV definitions in the final mesh buffers for a 3-gon face on the top of the extruded mesh.
686+
*
687+
* Uses vertex indices from the uniqueShapeVertices array.
688+
* @param {number} a
689+
* @param {number} b
690+
* @param {number} c*/
633691
function f3( a, b, c ) {
634692

635693
addVertex( a );
636694
addVertex( b );
637695
addVertex( c );
638696

639-
const nextIndex = verticesArray.length / 3;
640-
const uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 );
697+
const nextIndex = meshOutVerticesArray.length / 3;
698+
const uvs = uvgen.generateTopUV( scope, meshOutVerticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 );
641699

642700
addUV( uvs[ 0 ] );
643701
addUV( uvs[ 1 ] );
644702
addUV( uvs[ 2 ] );
645703

646704
}
647705

706+
/**Creates vertex and UV definitions in the final mesh buffers for a 4-gon face on the side of the extruded mesh.
707+
*
708+
* Uses vertex indices from the uniqueShapeVertices array.
709+
* @param {number} a
710+
* @param {number} b
711+
* @param {number} c
712+
* @param {number} d*/
648713
function f4( a, b, c, d ) {
649714

650715
addVertex( a );
@@ -656,8 +721,8 @@ class ExtrudeGeometry extends BufferGeometry {
656721
addVertex( d );
657722

658723

659-
const nextIndex = verticesArray.length / 3;
660-
const uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 );
724+
const nextIndex = meshOutVerticesArray.length / 3;
725+
const uvs = uvgen.generateSideWallUV( scope, meshOutVerticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 );
661726

662727
addUV( uvs[ 0 ] );
663728
addUV( uvs[ 1 ] );
@@ -669,11 +734,14 @@ class ExtrudeGeometry extends BufferGeometry {
669734

670735
}
671736

737+
/**Copies the specified triad of X,Y,Z values from uniqueVertices and appends them to verticesArray.
738+
* @param {number} index Index of the triad in uniqueVertices.
739+
*/
672740
function addVertex( index ) {
673741

674-
verticesArray.push( placeholder[ index * 3 + 0 ] );
675-
verticesArray.push( placeholder[ index * 3 + 1 ] );
676-
verticesArray.push( placeholder[ index * 3 + 2 ] );
742+
meshOutVerticesArray.push( uniqueShapeVertices[ index * 3 + 0 ] );
743+
meshOutVerticesArray.push( uniqueShapeVertices[ index * 3 + 1 ] );
744+
meshOutVerticesArray.push( uniqueShapeVertices[ index * 3 + 2 ] );
677745

678746
}
679747

0 commit comments

Comments
 (0)
Failed to load comments.