PageSourceSearch

https://inchbyinch.ru/local/templates/inch/js/three/BufferGeometryUtils.js

js inchbyinch.ru collected 2026-10-07 12:33:19 UTC 31,554 bytes, 1,371 lines download raw bytes

vendor: 9,107 bytes, lines 1-380
1import {
2	BufferAttribute,
3	BufferGeometry,
4	Float32BufferAttribute,
5	InstancedBufferAttribute,
6	InterleavedBuffer,
7	InterleavedBufferAttribute,
8	TriangleFanDrawMode,
9	TriangleStripDrawMode,
10	TrianglesDrawMode,
11	Vector3,
12} from 'three';
13
14function computeMikkTSpaceTangents( geometry, MikkTSpace, negateSign = true ) {
15
16	if ( ! MikkTSpace || ! MikkTSpace.isReady ) {
17
18		throw new Error( 'BufferGeometryUtils: Initialized MikkTSpace library required.' );
19
20	}
21
22	if ( ! geometry.hasAttribute( 'position' ) || ! geometry.hasAttribute( 'normal' ) || ! geometry.hasAttribute( 'uv' ) ) {
23
24		throw new Error( 'BufferGeometryUtils: Tangents require "position", "normal", and "uv" attributes.' );
25
26	}
27
28	function getAttributeArray( attribute ) {
29
30		if ( attribute.normalized || attribute.isInterleavedBufferAttribute ) {
31
32			const dstArray = new Float32Array( attribute.count * attribute.itemSize );
33
34			for ( let i = 0, j = 0; i < attribute.count; i ++ ) {
35
36				dstArray[ j ++ ] = attribute.getX( i );
37				dstArray[ j ++ ] = attribute.getY( i );
38
39				if ( attribute.itemSize > 2 ) {
40
41					dstArray[ j ++ ] = attribute.getZ( i );
42
43				}
44
45			}
46
47			return dstArray;
48
49		}
50
51		if ( attribute.array instanceof Float32Array ) {
52
53			return attribute.array;
54
55		}
56
57		return new Float32Array( attribute.array );
58
59	}
60
61	// MikkTSpace algorithm requires non-indexed input.
62
63	const _geometry = geometry.index ? geometry.toNonIndexed() : geometry;
64
65	// Compute vertex tangents.
66
67	const tangents = MikkTSpace.generateTangents(
68
69		getAttributeArray( _geometry.attributes.position ),
70		getAttributeArray( _geometry.attributes.normal ),
71		getAttributeArray( _geometry.attributes.uv )
72
73	);
74
75	// Texture coordinate convention of glTF differs from the apparent
76	// default of the MikkTSpace library; .w component must be flipped.
77
78	if ( negateSign ) {
79
80		for ( let i = 3; i < tangents.length; i += 4 ) {
81
82			tangents[ i ] *= - 1;
83
84		}
85
86	}
87
88	//
89
90	_geometry.setAttribute( 'tangent', new BufferAttribute( tangents, 4 ) );
91
92	if ( geometry !== _geometry ) {
93
94		geometry.copy( _geometry );
95
96	}
97
98	return geometry;
99
100}
101
102/**
103 * @param  {Array<BufferGeometry>} geometries
104 * @param  {Boolean} useGroups
105 * @return {BufferGeometry}
106 */
107function mergeGeometries( geometries, useGroups = false ) {
108
109	const isIndexed = geometries[ 0 ].index !== null;
110
111	const attributesUsed = new Set( Object.keys( geometries[ 0 ].attributes ) );
112	const morphAttributesUsed = new Set( Object.keys( geometries[ 0 ].morphAttributes ) );
113
114	const attributes = {};
115	const morphAttributes = {};
116
117	const morphTargetsRelative = geometries[ 0 ].morphTargetsRelative;
118
119	const mergedGeometry = new BufferGeometry();
120
121	let offset = 0;
122
123	for ( let i = 0; i < geometries.length; ++ i ) {
124
125		const geometry = geometries[ i ];
126		let attributesCount = 0;
127
128		// ensure that all geometries are indexed, or none
129
130		if ( isIndexed !== ( geometry.index !== null ) ) {
131
132			console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure index attribute exists among all geometries, or in none of them.' );
133			return null;
134
135		}
136
137		// gather attributes, exit early if they're different
138
139		for ( const name in geometry.attributes ) {
140
141			if ( ! attributesUsed.has( name ) ) {
142
143				console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure "' + name + '" attribute exists among all geometries, or in none of them.' );
144				return null;
145
146			}
147
148			if ( attributes[ name ] === undefined ) attributes[ name ] = [];
149
150			attributes[ name ].push( geometry.attributes[ name ] );
151
152			attributesCount ++;
153
154		}
155
156		// ensure geometries have the same number of attributes
157
158		if ( attributesCount !== attributesUsed.size ) {
159
160			console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. Make sure all geometries have the same number of attributes.' );
161			return null;
162
163		}
164
165		// gather morph attributes, exit early if they're different
166
167		if ( morphTargetsRelative !== geometry.morphTargetsRelative ) {
168
169			console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. .morphTargetsRelative must be consistent throughout all geometries.' );
170			return null;
171
172		}
173
174		for ( const name in geometry.morphAttributes ) {
175
176			if ( ! morphAttributesUsed.has( name ) ) {
177
178				console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '.  .morphAttributes must be consistent throughout all geometries.' );
179				return null;
180
181			}
182
183			if ( morphAttributes[ name ] === undefined ) morphAttributes[ name ] = [];
184
185			morphAttributes[ name ].push( geometry.morphAttributes[ name ] );
186
187		}
188
189		if ( useGroups ) {
190
191			let count;
192
193			if ( isIndexed ) {
194
195				count = geometry.index.count;
196
197			} else if ( geometry.attributes.position !== undefined ) {
198
199				count = geometry.attributes.position.count;
200
201			} else {
202
203				console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. The geometry must have either an index or a position attribute' );
204				return null;
205
206			}
207
208			mergedGeometry.addGroup( offset, count, i );
209
210			offset += count;
211
212		}
213
214	}
215
216	// merge indices
217
218	if ( isIndexed ) {
219
220		let indexOffset = 0;
221		const mergedIndex = [];
222
223		for ( let i = 0; i < geometries.length; ++ i ) {
224
225			const index = geometries[ i ].index;
226
227			for ( let j = 0; j < index.count; ++ j ) {
228
229				mergedIndex.push( index.getX( j ) + indexOffset );
230
231			}
232
233			indexOffset += geometries[ i ].attributes.position.count;
234
235		}
236
237		mergedGeometry.setIndex( mergedIndex );
238
239	}
240
241	// merge attributes
242
243	for ( const name in attributes ) {
244
245		const mergedAttribute = mergeAttributes( attributes[ name ] );
246
247		if ( ! mergedAttribute ) {
248
249			console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the ' + name + ' attribute.' );
250			return null;
251
252		}
253
254		mergedGeometry.setAttribute( name, mergedAttribute );
255
256	}
257
258	// merge morph attributes
259
260	for ( const name in morphAttributes ) {
261
262		const numMorphTargets = morphAttributes[ name ][ 0 ].length;
263
264		if ( numMorphTargets === 0 ) break;
265
266		mergedGeometry.morphAttributes = mergedGeometry.morphAttributes || {};
267		mergedGeometry.morphAttributes[ name ] = [];
268
269		for ( let i = 0; i < numMorphTargets; ++ i ) {
270
271			const morphAttributesToMerge = [];
272
273			for ( let j = 0; j < morphAttributes[ name ].length; ++ j ) {
274
275				morphAttributesToMerge.push( morphAttributes[ name ][ j ][ i ] );
276
277			}
278
279			const mergedMorphAttribute = mergeAttributes( morphAttributesToMerge );
280
281			if ( ! mergedMorphAttribute ) {
282
283				console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the ' + name + ' morphAttribute.' );
284				return null;
285
286			}
287
288			mergedGeometry.morphAttributes[ name ].push( mergedMorphAttribute );
289
290		}
291
292	}
293
294	return mergedGeometry;
295
296}
297
298/**
299 * @param {Array<BufferAttribute>} attributes
300 * @return {BufferAttribute}
301 */
302function mergeAttributes( attributes ) {
303
304	let TypedArray;
305	let itemSize;
306	let normalized;
307	let gpuType = - 1;
308	let arrayLength = 0;
309
310	for ( let i = 0; i < attributes.length; ++ i ) {
311
312		const attribute = attributes[ i ];
313
314		if ( TypedArray === undefined ) TypedArray = attribute.array.constructor;
315		if ( TypedArray !== attribute.array.constructor ) {
316
317			console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.array must be of consistent array types across matching attributes.' );
318			return null;
319
320		}
321
322		if ( itemSize === undefined ) itemSize = attribute.itemSize;
323		if ( itemSize !== attribute.itemSize ) {
324
325			console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.itemSize must be consistent across matching attributes.' );
326			return null;
327
328		}
329
330		if ( normalized === undefined ) normalized = attribute.normalized;
331		if ( normalized !== attribute.normalized ) {
332
333			console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.normalized must be consistent across matching attributes.' );
334			return null;
335
336		}
337
338		if ( gpuType === - 1 ) gpuType = attribute.gpuType;
339		if ( gpuType !== attribute.gpuType ) {
340
341			console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.gpuType must be consistent across matching attributes.' );
342			return null;
343
344		}
345
346		arrayLength += attribute.count * itemSize;
347
348	}
349
350	const array = new TypedArray( arrayLength );
351	const result = new BufferAttribute( array, itemSize, normalized );
352	let offset = 0;
353
354	for ( let i = 0; i < attributes.length; ++ i ) {
355
356		const attribute = attributes[ i ];
357		if ( attribute.isInterleavedBufferAttribute ) {
358
359			const tupleOffset = offset / itemSize;
360			for ( let j = 0, l = attribute.count; j < l; j ++ ) {
361
362				for ( let c = 0; c < itemSize; c ++ ) {
363
364					const value = attribute.getComponent( j, c );
365					result.setComponent( j + tupleOffset, c, value );
366
367				}
368
369			}
370
371		} else {
372
373			array.set( attribute.array, offset );
374
375		}
376
377		offset += attribute.count * itemSize;
378
379	}
380
381	if ( gpuType !== undefined ) {
382
383		result.gpuType = gpuType;
384
385	}
386
387	return result;
388
389}
390
391/**
392 * @param {BufferAttribute}
393 * @return {BufferAttribute}
394 */
395export function deepCloneAttribute( attribute ) {
396
397	if ( attribute.isInstancedInterleavedBufferAttribute || attribute.isInterleavedBufferAttribute ) {
398
399		return deinterleaveAttribute( attribute );
400
401	}
402
403	if ( attribute.isInstancedBufferAttribute ) {
404
405		return new InstancedBufferAttribute().copy( attribute );
406
407	}
408
409	return new BufferAttribute().copy( attribute );
410
411}
412
413/**
414 * @param {Array<BufferAttribute>} attributes
415 * @return {Array<InterleavedBufferAttribute>}
416 */
417function interleaveAttributes( attributes ) {
418
419	// Interleaves the provided attributes into an InterleavedBuffer and returns
420	// a set of InterleavedBufferAttributes for each attribute
421	let TypedArray;
422	let arrayLength = 0;
423	let stride = 0;
424
425	// calculate the length and type of the interleavedBuffer
426	for ( let i = 0, l = attributes.length; i < l; ++ i ) {
427
428		const attribute = attributes[ i ];
429
430		if ( TypedArray === undefined ) TypedArray = attribute.array.constructor;
431		if ( TypedArray !== attribute.array.constructor ) {
432
433			console.error( 'AttributeBuffers of different types cannot be interleaved' );
434			return null;
435
436		}
437
438		arrayLength += attribute.array.length;
439		stride += attribute.itemSize;
440
441	}
442
443	// Create the set of buffer attributes
444	const interleavedBuffer = new InterleavedBuffer( new TypedArray( arrayLength ), stride );
445	let offset = 0;
446	const res = [];
447	const getters = [ 'getX', 'getY', 'getZ', 'getW' ];
448	const setters = [ 'setX', 'setY', 'setZ', 'setW' ];
449
450	for ( let j = 0, l = attributes.length; j < l; j ++ ) {
451
452		const attribute = attributes[ j ];
453		const itemSize = attribute.itemSize;
454		const count = attribute.count;
455		const iba = new InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, attribute.normalized );
456		res.push( iba );
457
458		offset += itemSize;
459
460		// Move the data for each attribute into the new interleavedBuffer
461		// at the appropriate offset
462		for ( let c = 0; c < count; c ++ ) {
463
464			for ( let k = 0; k < itemSize; k ++ ) {
465
466				iba[ setters[ k ] ]( c, attribute[ getters[ k ] ]( c ) );
467
468			}
469
470		}
471
472	}
473
474	return res;
475
476}
477
478// returns a new, non-interleaved version of the provided attribute
479export function deinterleaveAttribute( attribute ) {
480
481	const cons = attribute.data.array.constructor;
482	const count = attribute.count;
483	const itemSize = attribute.itemSize;
484	const normalized = attribute.normalized;
485
486	const array = new cons( count * itemSize );
487	let newAttribute;
488	if ( attribute.isInstancedInterleavedBufferAttribute ) {
489
490		newAttribute = new InstancedBufferAttribute( array, itemSize, normalized, attribute.meshPerAttribute );
491
492	} else {
493
494		newAttribute = new BufferAttribute( array, itemSize, normalized );
495
496	}
497
498	for ( let i = 0; i < count; i ++ ) {
499
500		newAttribute.setX( i, attribute.getX( i ) );
501
502		if ( itemSize >= 2 ) {
503
504			newAttribute.setY( i, attribute.getY( i ) );
505
506		}
507
508		if ( itemSize >= 3 ) {
509
510			newAttribute.setZ( i, attribute.getZ( i ) );
511
512		}
513
514		if ( itemSize >= 4 ) {
515
516			newAttribute.setW( i, attribute.getW( i ) );
517
518		}
519
520	}
521
522	return newAttribute;
523
524}
525
526// deinterleaves all attributes on the geometry
527export function deinterleaveGeometry( geometry ) {
528
529	const attributes = geometry.attributes;
530	const morphTargets = geometry.morphTargets;
531	const attrMap = new Map();
532
533	for ( const key in attributes ) {
534
535		const attr = attributes[ key ];
536		if ( attr.isInterleavedBufferAttribute ) {
537
538			if ( ! attrMap.has( attr ) ) {
539
540				attrMap.set( attr, deinterleaveAttribute( attr ) );
541
542			}
543
544			attributes[ key ] = attrMap.get( attr );
545
546		}
547
548	}
549
550	for ( const key in morphTargets ) {
551
552		const attr = morphTargets[ key ];
553		if ( attr.isInterleavedBufferAttribute ) {
554
555			if ( ! attrMap.has( attr ) ) {
556
557				attrMap.set( attr, deinterleaveAttribute( attr ) );
558
559			}
560
561			morphTargets[ key ] = attrMap.get( attr );
562
563		}
564
565	}
566
567}
568
569/**
570 * @param {BufferGeometry} geometry
571 * @return {number}
572 */
573function estimateBytesUsed( geometry ) {
574
575	// Return the estimated memory used by this geometry in bytes
576	// Calculate using itemSize, count, and BYTES_PER_ELEMENT to account
577	// for InterleavedBufferAttributes.
578	let mem = 0;
579	for ( const name in geometry.attributes ) {
580
581		const attr = geometry.getAttribute( name );
582		mem += attr.count * attr.itemSize * attr.array.BYTES_PER_ELEMENT;
583
584	}
585
586	const indices = geometry.getIndex();
587	mem += indices ? indices.count * indices.itemSize * indices.array.BYTES_PER_ELEMENT : 0;
588	return mem;
589
590}
591
592/**
593 * @param {BufferGeometry} geometry
594 * @param {number} tolerance
595 * @return {BufferGeometry}
596 */
597function mergeVertices( geometry, tolerance = 1e-4 ) {
598
599	tolerance = Math.max( tolerance, Number.EPSILON );
600
601	// Generate an index buffer if the geometry doesn't have one, or optimize it
602	// if it's already available.
603	const hashToIndex = {};
604	const indices = geometry.getIndex();
605	const positions = geometry.getAttribute( 'position' );
606	const vertexCount = indices ? indices.count : positions.count;
607
608	// next value for triangle indices
609	let nextIndex = 0;
610
611	// attributes and new attribute arrays
612	const attributeNames = Object.keys( geometry.attributes );
613	const tmpAttributes = {};
614	const tmpMorphAttributes = {};
615	const newIndices = [];
616	const getters = [ 'getX', 'getY', 'getZ', 'getW' ];
617	const setters = [ 'setX', 'setY', 'setZ', 'setW' ];
618
619	// Initialize the arrays, allocating space conservatively. Extra
620	// space will be trimmed in the last step.
621	for ( let i = 0, l = attributeNames.length; i < l; i ++ ) {
622
623		const name = attributeNames[ i ];
624		const attr = geometry.attributes[ name ];
625
626		tmpAttributes[ name ] = new BufferAttribute(
627			new attr.array.constructor( attr.count * attr.itemSize ),
628			attr.itemSize,
629			attr.normalized
630		);
631
632		const morphAttr = geometry.morphAttributes[ name ];
633		if ( morphAttr ) {
634
635			tmpMorphAttributes[ name ] = new BufferAttribute(
636				new morphAttr.array.constructor( morphAttr.count * morphAttr.itemSize ),
637				morphAttr.itemSize,
638				morphAttr.normalized
639			);
640
641		}
642
643	}
644
645	// convert the error tolerance to an amount of decimal places to truncate to
646	const halfTolerance = tolerance * 0.5;
647	const exponent = Math.log10( 1 / tolerance );
648	const hashMultiplier = Math.pow( 10, exponent );
649	const hashAdditive = halfTolerance * hashMultiplier;
650	for ( let i = 0; i < vertexCount; i ++ ) {
651
652		const index = indices ? indices.getX( i ) : i;
653
654		// Generate a hash for the vertex attributes at the current index 'i'
655		let hash = '';
656		for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
657
658			const name = attributeNames[ j ];
659			const attribute = geometry.getAttribute( name );
660			const itemSize = attribute.itemSize;
661
662			for ( let k = 0; k < itemSize; k ++ ) {
663
664				// double tilde truncates the decimal value
665				hash += `${ ~ ~ ( attribute[ getters[ k ] ]( index ) * hashMultiplier + hashAdditive ) },`;
666
667			}
668
669		}
670
671		// Add another reference to the vertex if it's already
672		// used by another index
673		if ( hash in hashToIndex ) {
674
675			newIndices.push( hashToIndex[ hash ] );
676
677		} else {
678
679			// copy data to the new index in the temporary attributes
680			for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
681
682				const name = attributeNames[ j ];
683				const attribute = geometry.getAttribute( name );
684				const morphAttr = geometry.morphAttributes[ name ];
685				const itemSize = attribute.itemSize;
686				const newarray = tmpAttributes[ name ];
vendor: 10,072 bytes, lines 687-1153
687				const newMorphArrays = tmpMorphAttributes[ name ];
688
689				for ( let k = 0; k < itemSize; k ++ ) {
690
691					const getterFunc = getters[ k ];
692					const setterFunc = setters[ k ];
693					newarray[ setterFunc ]( nextIndex, attribute[ getterFunc ]( index ) );
694
695					if ( morphAttr ) {
696
697						for ( let m = 0, ml = morphAttr.length; m < ml; m ++ ) {
698
699							newMorphArrays[ m ][ setterFunc ]( nextIndex, morphAttr[ m ][ getterFunc ]( index ) );
700
701						}
702
703					}
704
705				}
706
707			}
708
709			hashToIndex[ hash ] = nextIndex;
710			newIndices.push( nextIndex );
711			nextIndex ++;
712
713		}
714
715	}
716
717	// generate result BufferGeometry
718	const result = geometry.clone();
719	for ( const name in geometry.attributes ) {
720
721		const tmpAttribute = tmpAttributes[ name ];
722
723		result.setAttribute( name, new BufferAttribute(
724			tmpAttribute.array.slice( 0, nextIndex * tmpAttribute.itemSize ),
725			tmpAttribute.itemSize,
726			tmpAttribute.normalized,
727		) );
728
729		if ( ! ( name in tmpMorphAttributes ) ) continue;
730
731		for ( let j = 0; j < tmpMorphAttributes[ name ].length; j ++ ) {
732
733			const tmpMorphAttribute = tmpMorphAttributes[ name ][ j ];
734
735			result.morphAttributes[ name ][ j ] = new BufferAttribute(
736				tmpMorphAttribute.array.slice( 0, nextIndex * tmpMorphAttribute.itemSize ),
737				tmpMorphAttribute.itemSize,
738				tmpMorphAttribute.normalized,
739			);
740
741		}
742
743	}
744
745	// indices
746
747	result.setIndex( newIndices );
748
749	return result;
750
751}
752
753/**
754 * @param {BufferGeometry} geometry
755 * @param {number} drawMode
756 * @return {BufferGeometry}
757 */
758function toTrianglesDrawMode( geometry, drawMode ) {
759
760	if ( drawMode === TrianglesDrawMode ) {
761
762		console.warn( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Geometry already defined as triangles.' );
763		return geometry;
764
765	}
766
767	if ( drawMode === TriangleFanDrawMode || drawMode === TriangleStripDrawMode ) {
768
769		let index = geometry.getIndex();
770
771		// generate index if not present
772
773		if ( index === null ) {
774
775			const indices = [];
776
777			const position = geometry.getAttribute( 'position' );
778
779			if ( position !== undefined ) {
780
781				for ( let i = 0; i < position.count; i ++ ) {
782
783					indices.push( i );
784
785				}
786
787				geometry.setIndex( indices );
788				index = geometry.getIndex();
789
790			} else {
791
792				console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.' );
793				return geometry;
794
795			}
796
797		}
798
799		//
800
801		const numberOfTriangles = index.count - 2;
802		const newIndices = [];
803
804		if ( drawMode === TriangleFanDrawMode ) {
805
806			// gl.TRIANGLE_FAN
807
808			for ( let i = 1; i <= numberOfTriangles; i ++ ) {
809
810				newIndices.push( index.getX( 0 ) );
811				newIndices.push( index.getX( i ) );
812				newIndices.push( index.getX( i + 1 ) );
813
814			}
815
816		} else {
817
818			// gl.TRIANGLE_STRIP
819
820			for ( let i = 0; i < numberOfTriangles; i ++ ) {
821
822				if ( i % 2 === 0 ) {
823
824					newIndices.push( index.getX( i ) );
825					newIndices.push( index.getX( i + 1 ) );
826					newIndices.push( index.getX( i + 2 ) );
827
828				} else {
829
830					newIndices.push( index.getX( i + 2 ) );
831					newIndices.push( index.getX( i + 1 ) );
832					newIndices.push( index.getX( i ) );
833
834				}
835
836			}
837
838		}
839
840		if ( ( newIndices.length / 3 ) !== numberOfTriangles ) {
841
842			console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Unable to generate correct amount of triangles.' );
843
844		}
845
846		// build final geometry
847
848		const newGeometry = geometry.clone();
849		newGeometry.setIndex( newIndices );
850		newGeometry.clearGroups();
851
852		return newGeometry;
853
854	} else {
855
856		console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Unknown draw mode:', drawMode );
857		return geometry;
858
859	}
860
861}
862
863/**
864 * Calculates the morphed attributes of a morphed/skinned BufferGeometry.
865 * Helpful for Raytracing or Decals.
866 * @param {Mesh | Line | Points} object An instance of Mesh, Line or Points.
867 * @return {Object} An Object with original position/normal attributes and morphed ones.
868 */
869function computeMorphedAttributes( object ) {
870
871	const _vA = new Vector3();
872	const _vB = new Vector3();
873	const _vC = new Vector3();
874
875	const _tempA = new Vector3();
876	const _tempB = new Vector3();
877	const _tempC = new Vector3();
878
879	const _morphA = new Vector3();
880	const _morphB = new Vector3();
881	const _morphC = new Vector3();
882
883	function _calculateMorphedAttributeData(
884		object,
885		attribute,
886		morphAttribute,
887		morphTargetsRelative,
888		a,
889		b,
890		c,
891		modifiedAttributeArray
892	) {
893
894		_vA.fromBufferAttribute( attribute, a );
895		_vB.fromBufferAttribute( attribute, b );
896		_vC.fromBufferAttribute( attribute, c );
897
898		const morphInfluences = object.morphTargetInfluences;
899
900		if ( morphAttribute && morphInfluences ) {
901
902			_morphA.set( 0, 0, 0 );
903			_morphB.set( 0, 0, 0 );
904			_morphC.set( 0, 0, 0 );
905
906			for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) {
907
908				const influence = morphInfluences[ i ];
909				const morph = morphAttribute[ i ];
910
911				if ( influence === 0 ) continue;
912
913				_tempA.fromBufferAttribute( morph, a );
914				_tempB.fromBufferAttribute( morph, b );
915				_tempC.fromBufferAttribute( morph, c );
916
917				if ( morphTargetsRelative ) {
918
919					_morphA.addScaledVector( _tempA, influence );
920					_morphB.addScaledVector( _tempB, influence );
921					_morphC.addScaledVector( _tempC, influence );
922
923				} else {
924
925					_morphA.addScaledVector( _tempA.sub( _vA ), influence );
926					_morphB.addScaledVector( _tempB.sub( _vB ), influence );
927					_morphC.addScaledVector( _tempC.sub( _vC ), influence );
928
929				}
930
931			}
932
933			_vA.add( _morphA );
934			_vB.add( _morphB );
935			_vC.add( _morphC );
936
937		}
938
939		if ( object.isSkinnedMesh ) {
940
941			object.applyBoneTransform( a, _vA );
942			object.applyBoneTransform( b, _vB );
943			object.applyBoneTransform( c, _vC );
944
945		}
946
947		modifiedAttributeArray[ a * 3 + 0 ] = _vA.x;
948		modifiedAttributeArray[ a * 3 + 1 ] = _vA.y;
949		modifiedAttributeArray[ a * 3 + 2 ] = _vA.z;
950		modifiedAttributeArray[ b * 3 + 0 ] = _vB.x;
951		modifiedAttributeArray[ b * 3 + 1 ] = _vB.y;
952		modifiedAttributeArray[ b * 3 + 2 ] = _vB.z;
953		modifiedAttributeArray[ c * 3 + 0 ] = _vC.x;
954		modifiedAttributeArray[ c * 3 + 1 ] = _vC.y;
955		modifiedAttributeArray[ c * 3 + 2 ] = _vC.z;
956
957	}
958
959	const geometry = object.geometry;
960	const material = object.material;
961
962	let a, b, c;
963	const index = geometry.index;
964	const positionAttribute = geometry.attributes.position;
965	const morphPosition = geometry.morphAttributes.position;
966	const morphTargetsRelative = geometry.morphTargetsRelative;
967	const normalAttribute = geometry.attributes.normal;
968	const morphNormal = geometry.morphAttributes.position;
969
970	const groups = geometry.groups;
971	const drawRange = geometry.drawRange;
972	let i, j, il, jl;
973	let group;
974	let start, end;
975
976	const modifiedPosition = new Float32Array( positionAttribute.count * positionAttribute.itemSize );
977	const modifiedNormal = new Float32Array( normalAttribute.count * normalAttribute.itemSize );
978
979	if ( index !== null ) {
980
981		// indexed buffer geometry
982
983		if ( Array.isArray( material ) ) {
984
985			for ( i = 0, il = groups.length; i < il; i ++ ) {
986
987				group = groups[ i ];
988
989				start = Math.max( group.start, drawRange.start );
990				end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) );
991
992				for ( j = start, jl = end; j < jl; j += 3 ) {
993
994					a = index.getX( j );
995					b = index.getX( j + 1 );
996					c = index.getX( j + 2 );
997
998					_calculateMorphedAttributeData(
999						object,
1000						positionAttribute,
1001						morphPosition,
1002						morphTargetsRelative,
1003						a, b, c,
1004						modifiedPosition
1005					);
1006
1007					_calculateMorphedAttributeData(
1008						object,
1009						normalAttribute,
1010						morphNormal,
1011						morphTargetsRelative,
1012						a, b, c,
1013						modifiedNormal
1014					);
1015
1016				}
1017
1018			}
1019
1020		} else {
1021
1022			start = Math.max( 0, drawRange.start );
1023			end = Math.min( index.count, ( drawRange.start + drawRange.count ) );
1024
1025			for ( i = start, il = end; i < il; i += 3 ) {
1026
1027				a = index.getX( i );
1028				b = index.getX( i + 1 );
1029				c = index.getX( i + 2 );
1030
1031				_calculateMorphedAttributeData(
1032					object,
1033					positionAttribute,
1034					morphPosition,
1035					morphTargetsRelative,
1036					a, b, c,
1037					modifiedPosition
1038				);
1039
1040				_calculateMorphedAttributeData(
1041					object,
1042					normalAttribute,
1043					morphNormal,
1044					morphTargetsRelative,
1045					a, b, c,
1046					modifiedNormal
1047				);
1048
1049			}
1050
1051		}
1052
1053	} else {
1054
1055		// non-indexed buffer geometry
1056
1057		if ( Array.isArray( material ) ) {
1058
1059			for ( i = 0, il = groups.length; i < il; i ++ ) {
1060
1061				group = groups[ i ];
1062
1063				start = Math.max( group.start, drawRange.start );
1064				end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) );
1065
1066				for ( j = start, jl = end; j < jl; j += 3 ) {
1067
1068					a = j;
1069					b = j + 1;
1070					c = j + 2;
1071
1072					_calculateMorphedAttributeData(
1073						object,
1074						positionAttribute,
1075						morphPosition,
1076						morphTargetsRelative,
1077						a, b, c,
1078						modifiedPosition
1079					);
1080
1081					_calculateMorphedAttributeData(
1082						object,
1083						normalAttribute,
1084						morphNormal,
1085						morphTargetsRelative,
1086						a, b, c,
1087						modifiedNormal
1088					);
1089
1090				}
1091
1092			}
1093
1094		} else {
1095
1096			start = Math.max( 0, drawRange.start );
1097			end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) );
1098
1099			for ( i = start, il = end; i < il; i += 3 ) {
1100
1101				a = i;
1102				b = i + 1;
1103				c = i + 2;
1104
1105				_calculateMorphedAttributeData(
1106					object,
1107					positionAttribute,
1108					morphPosition,
1109					morphTargetsRelative,
1110					a, b, c,
1111					modifiedPosition
1112				);
1113
1114				_calculateMorphedAttributeData(
1115					object,
1116					normalAttribute,
1117					morphNormal,
1118					morphTargetsRelative,
1119					a, b, c,
1120					modifiedNormal
1121				);
1122
1123			}
1124
1125		}
1126
1127	}
1128
1129	const morphedPositionAttribute = new Float32BufferAttribute( modifiedPosition, 3 );
1130	const morphedNormalAttribute = new Float32BufferAttribute( modifiedNormal, 3 );
1131
1132	return {
1133
1134		positionAttribute: positionAttribute,
1135		normalAttribute: normalAttribute,
1136		morphedPositionAttribute: morphedPositionAttribute,
1137		morphedNormalAttribute: morphedNormalAttribute
1138
1139	};
1140
1141}
1142
1143function mergeGroups( geometry ) {
1144
1145	if ( geometry.groups.length === 0 ) {
1146
1147		console.warn( 'THREE.BufferGeometryUtils.mergeGroups(): No groups are defined. Nothing to merge.' );
1148		return geometry;
1149
1150	}
1151
1152	let groups = geometry.groups;
1153
vendor: 4,668 bytes, lines 1154-1354
1154	// sort groups by material index
1155
1156	groups = groups.sort( ( a, b ) => {
1157
1158		if ( a.materialIndex !== b.materialIndex ) return a.materialIndex - b.materialIndex;
1159
1160		return a.start - b.start;
1161
1162	} );
1163
1164	// create index for non-indexed geometries
1165
1166	if ( geometry.getIndex() === null ) {
1167
1168		const positionAttribute = geometry.getAttribute( 'position' );
1169		const indices = [];
1170
1171		for ( let i = 0; i < positionAttribute.count; i += 3 ) {
1172
1173			indices.push( i, i + 1, i + 2 );
1174
1175		}
1176
1177		geometry.setIndex( indices );
1178
1179	}
1180
1181	// sort index
1182
1183	const index = geometry.getIndex();
1184
1185	const newIndices = [];
1186
1187	for ( let i = 0; i < groups.length; i ++ ) {
1188
1189		const group = groups[ i ];
1190
1191		const groupStart = group.start;
1192		const groupLength = groupStart + group.count;
1193
1194		for ( let j = groupStart; j < groupLength; j ++ ) {
1195
1196			newIndices.push( index.getX( j ) );
1197
1198		}
1199
1200	}
1201
1202	geometry.dispose(); // Required to force buffer recreation
1203	geometry.setIndex( newIndices );
1204
1205	// update groups indices
1206
1207	let start = 0;
1208
1209	for ( let i = 0; i < groups.length; i ++ ) {
1210
1211		const group = groups[ i ];
1212
1213		group.start = start;
1214		start += group.count;
1215
1216	}
1217
1218	// merge groups
1219
1220	let currentGroup = groups[ 0 ];
1221
1222	geometry.groups = [ currentGroup ];
1223
1224	for ( let i = 1; i < groups.length; i ++ ) {
1225
1226		const group = groups[ i ];
1227
1228		if ( currentGroup.materialIndex === group.materialIndex ) {
1229
1230			currentGroup.count += group.count;
1231
1232		} else {
1233
1234			currentGroup = group;
1235			geometry.groups.push( currentGroup );
1236
1237		}
1238
1239	}
1240
1241	return geometry;
1242
1243}
1244
1245
1246/**
1247 * Modifies the supplied geometry if it is non-indexed, otherwise creates a new,
1248 * non-indexed geometry. Returns the geometry with smooth normals everywhere except
1249 * faces that meet at an angle greater than the crease angle.
1250 *
1251 * @param {BufferGeometry} geometry
1252 * @param {number} [creaseAngle]
1253 * @return {BufferGeometry}
1254 */
1255function toCreasedNormals( geometry, creaseAngle = Math.PI / 3 /* 60 degrees */ ) {
1256
1257	const creaseDot = Math.cos( creaseAngle );
1258	const hashMultiplier = ( 1 + 1e-10 ) * 1e2;
1259
1260	// reusable vectors
1261	const verts = [ new Vector3(), new Vector3(), new Vector3() ];
1262	const tempVec1 = new Vector3();
1263	const tempVec2 = new Vector3();
1264	const tempNorm = new Vector3();
1265	const tempNorm2 = new Vector3();
1266
1267	// hashes a vector
1268	function hashVertex( v ) {
1269
1270		const x = ~ ~ ( v.x * hashMultiplier );
1271		const y = ~ ~ ( v.y * hashMultiplier );
1272		const z = ~ ~ ( v.z * hashMultiplier );
1273		return `${x},${y},${z}`;
1274
1275	}
1276
1277	// BufferGeometry.toNonIndexed() warns if the geometry is non-indexed
1278	// and returns the original geometry
1279	const resultGeometry = geometry.index ? geometry.toNonIndexed() : geometry;
1280	const posAttr = resultGeometry.attributes.position;
1281	const vertexMap = {};
1282
1283	// find all the normals shared by commonly located vertices
1284	for ( let i = 0, l = posAttr.count / 3; i < l; i ++ ) {
1285
1286		const i3 = 3 * i;
1287		const a = verts[ 0 ].fromBufferAttribute( posAttr, i3 + 0 );
1288		const b = verts[ 1 ].fromBufferAttribute( posAttr, i3 + 1 );
1289		const c = verts[ 2 ].fromBufferAttribute( posAttr, i3 + 2 );
1290
1291		tempVec1.subVectors( c, b );
1292		tempVec2.subVectors( a, b );
1293
1294		// add the normal to the map for all vertices
1295		const normal = new Vector3().crossVectors( tempVec1, tempVec2 ).normalize();
1296		for ( let n = 0; n < 3; n ++ ) {
1297
1298			const vert = verts[ n ];
1299			const hash = hashVertex( vert );
1300			if ( ! ( hash in vertexMap ) ) {
1301
1302				vertexMap[ hash ] = [];
1303
1304			}
1305
1306			vertexMap[ hash ].push( normal );
1307
1308		}
1309
1310	}
1311
1312	// average normals from all vertices that share a common location if they are within the
1313	// provided crease threshold
1314	const normalArray = new Float32Array( posAttr.count * 3 );
1315	const normAttr = new BufferAttribute( normalArray, 3, false );
1316	for ( let i = 0, l = posAttr.count / 3; i < l; i ++ ) {
1317
1318		// get the face normal for this vertex
1319		const i3 = 3 * i;
1320		const a = verts[ 0 ].fromBufferAttribute( posAttr, i3 + 0 );
1321		const b = verts[ 1 ].fromBufferAttribute( posAttr, i3 + 1 );
1322		const c = verts[ 2 ].fromBufferAttribute( posAttr, i3 + 2 );
1323
1324		tempVec1.subVectors( c, b );
1325		tempVec2.subVectors( a, b );
1326
1327		tempNorm.crossVectors( tempVec1, tempVec2 ).normalize();
1328
1329		// average all normals that meet the threshold and set the normal value
1330		for ( let n = 0; n < 3; n ++ ) {
1331
1332			const vert = verts[ n ];
1333			const hash = hashVertex( vert );
1334			const otherNormals = vertexMap[ hash ];
1335			tempNorm2.set( 0, 0, 0 );
1336
1337			for ( let k = 0, lk = otherNormals.length; k < lk; k ++ ) {
1338
1339				const otherNorm = otherNormals[ k ];
1340				if ( tempNorm.dot( otherNorm ) > creaseDot ) {
1341
1342					tempNorm2.add( otherNorm );
1343
1344				}
1345
1346			}
1347
1348			tempNorm2.normalize();
1349			normAttr.setXYZ( i3 + n, tempNorm2.x, tempNorm2.y, tempNorm2.z );
1350
1351		}
1352
1353	}
1354
1355	resultGeometry.setAttribute( 'normal', normAttr );
1356	return resultGeometry;
1357
1358}
1359
1360export {
1361	computeMikkTSpaceTangents,
1362	mergeGeometries,
1363	mergeAttributes,
1364	interleaveAttributes,
1365	estimateBytesUsed,
1366	mergeVertices,
1367	toTrianglesDrawMode,
1368	computeMorphedAttributes,
1369	mergeGroups,
1370	toCreasedNormals
1371};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.