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