📄 BufferGeometryUtils.js
¶
📊 Analysis Summary¶
Metric | Count |
---|---|
🔧 Functions | 16 |
📦 Imports | 10 |
📊 Variables & Constants | 150 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 examples/jsm/utils/BufferGeometryUtils.js
📦 Imports¶
Name | Source |
---|---|
BufferAttribute |
three |
BufferGeometry |
three |
Float32BufferAttribute |
three |
InstancedBufferAttribute |
three |
InterleavedBuffer |
three |
InterleavedBufferAttribute |
three |
TriangleFanDrawMode |
three |
TriangleStripDrawMode |
three |
TrianglesDrawMode |
three |
Vector3 |
three |
Variables & Constants¶
Name | Type | Kind | Value | Exported |
---|---|---|---|---|
dstArray |
Float32Array<ArrayBuffer> |
let/var | new Float32Array( attribute.count * attribute.itemSize ) |
✗ |
_geometry |
any |
let/var | geometry.index ? geometry.toNonIndexed() : geometry |
✗ |
isIndexed |
boolean |
let/var | geometries[ 0 ].index !== null |
✗ |
attributesUsed |
Set<string> |
let/var | new Set( Object.keys( geometries[ 0 ].attributes ) ) |
✗ |
morphAttributesUsed |
Set<string> |
let/var | new Set( Object.keys( geometries[ 0 ].morphAttributes ) ) |
✗ |
attributes |
{} |
let/var | {} |
✗ |
morphAttributes |
{} |
let/var | {} |
✗ |
morphTargetsRelative |
any |
let/var | geometries[ 0 ].morphTargetsRelative |
✗ |
mergedGeometry |
any |
let/var | new BufferGeometry() |
✗ |
offset |
number |
let/var | 0 |
✗ |
geometry |
BufferGeometry |
let/var | geometries[ i ] |
✗ |
attributesCount |
number |
let/var | 0 |
✗ |
count |
any |
let/var | *not shown* |
✗ |
indexOffset |
number |
let/var | 0 |
✗ |
mergedIndex |
any[] |
let/var | [] |
✗ |
index |
any |
let/var | geometries[ i ].index |
✗ |
numMorphTargets |
any |
let/var | morphAttributes[ name ][ 0 ].length |
✗ |
morphAttributesToMerge |
any[] |
let/var | [] |
✗ |
TypedArray |
any |
let/var | *not shown* |
✗ |
itemSize |
any |
let/var | *not shown* |
✗ |
normalized |
any |
let/var | *not shown* |
✗ |
gpuType |
number |
let/var | - 1 |
✗ |
arrayLength |
number |
let/var | 0 |
✗ |
attribute |
BufferAttribute |
let/var | attributes[ i ] |
✗ |
array |
any |
let/var | new TypedArray( arrayLength ) |
✗ |
result |
any |
let/var | new BufferAttribute( array, itemSize, normalized ) |
✗ |
offset |
number |
let/var | 0 |
✗ |
attribute |
BufferAttribute |
let/var | attributes[ i ] |
✗ |
tupleOffset |
number |
let/var | offset / itemSize |
✗ |
TypedArray |
any |
let/var | *not shown* |
✗ |
arrayLength |
number |
let/var | 0 |
✗ |
stride |
number |
let/var | 0 |
✗ |
attribute |
BufferAttribute |
let/var | attributes[ i ] |
✗ |
interleavedBuffer |
any |
let/var | new InterleavedBuffer( new TypedArray( arrayLength ), stride ) |
✗ |
offset |
number |
let/var | 0 |
✗ |
res |
any[] |
let/var | [] |
✗ |
getters |
string[] |
let/var | [ 'getX', 'getY', 'getZ', 'getW' ] |
✗ |
setters |
string[] |
let/var | [ 'setX', 'setY', 'setZ', 'setW' ] |
✗ |
attribute |
BufferAttribute |
let/var | attributes[ j ] |
✗ |
itemSize |
any |
let/var | attribute.itemSize |
✗ |
count |
any |
let/var | attribute.count |
✗ |
iba |
any |
let/var | new InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, attribut... |
✗ |
cons |
any |
let/var | attribute.data.array.constructor |
✗ |
count |
any |
let/var | attribute.count |
✗ |
itemSize |
any |
let/var | attribute.itemSize |
✗ |
normalized |
any |
let/var | attribute.normalized |
✗ |
array |
any |
let/var | new cons( count * itemSize ) |
✗ |
newAttribute |
any |
let/var | *not shown* |
✗ |
attributes |
any |
let/var | geometry.attributes |
✗ |
morphTargets |
any |
let/var | geometry.morphTargets |
✗ |
attrMap |
Map<any, any> |
let/var | new Map() |
✗ |
attr |
any |
let/var | attributes[ key ] |
✗ |
attr |
any |
let/var | morphTargets[ key ] |
✗ |
mem |
number |
let/var | 0 |
✗ |
hashToIndex |
{} |
let/var | {} |
✗ |
vertexCount |
any |
let/var | indices ? indices.count : positions.count |
✗ |
nextIndex |
number |
let/var | 0 |
✗ |
tmpAttributes |
{} |
let/var | {} |
✗ |
tmpMorphAttributes |
{} |
let/var | {} |
✗ |
newIndices |
any[] |
let/var | [] |
✗ |
getters |
string[] |
let/var | [ 'getX', 'getY', 'getZ', 'getW' ] |
✗ |
setters |
string[] |
let/var | [ 'setX', 'setY', 'setZ', 'setW' ] |
✗ |
name |
string |
let/var | attributeNames[ i ] |
✗ |
attr |
any |
let/var | geometry.attributes[ name ] |
✗ |
morphAttributes |
any |
let/var | geometry.morphAttributes[ name ] |
✗ |
array |
any |
let/var | new morphAttr.array.constructor( morphAttr.count * morphAttr.itemSize ) |
✗ |
halfTolerance |
number |
let/var | tolerance * 0.5 |
✗ |
hashAdditive |
number |
let/var | halfTolerance * hashMultiplier |
✗ |
index |
any |
let/var | indices ? indices.getX( i ) : i |
✗ |
hash |
string |
let/var | '' |
✗ |
name |
string |
let/var | attributeNames[ j ] |
✗ |
itemSize |
any |
let/var | attribute.itemSize |
✗ |
name |
string |
let/var | attributeNames[ j ] |
✗ |
morphAttributes |
any |
let/var | geometry.morphAttributes[ name ] |
✗ |
itemSize |
any |
let/var | attribute.itemSize |
✗ |
newArray |
any |
let/var | tmpAttributes[ name ] |
✗ |
newMorphArrays |
any |
let/var | tmpMorphAttributes[ name ] |
✗ |
getterFunc |
string |
let/var | getters[ k ] |
✗ |
setterFunc |
string |
let/var | setters[ k ] |
✗ |
tmpAttribute |
any |
let/var | tmpAttributes[ name ] |
✗ |
tmpMorphAttribute |
any |
let/var | tmpMorphAttributes[ name ][ j ] |
✗ |
indices |
any[] |
let/var | [] |
✗ |
numberOfTriangles |
number |
let/var | index.count - 2 |
✗ |
newIndices |
any[] |
let/var | [] |
✗ |
_vA |
any |
let/var | new Vector3() |
✗ |
_vB |
any |
let/var | new Vector3() |
✗ |
_vC |
any |
let/var | new Vector3() |
✗ |
_tempA |
any |
let/var | new Vector3() |
✗ |
_tempB |
any |
let/var | new Vector3() |
✗ |
_tempC |
any |
let/var | new Vector3() |
✗ |
_morphA |
any |
let/var | new Vector3() |
✗ |
_morphB |
any |
let/var | new Vector3() |
✗ |
_morphC |
any |
let/var | new Vector3() |
✗ |
morphInfluences |
any |
let/var | object.morphTargetInfluences |
✗ |
influence |
any |
let/var | morphInfluences[ i ] |
✗ |
morph |
any |
let/var | morphAttribute[ i ] |
✗ |
geometry |
any |
let/var | object.geometry |
✗ |
material |
any |
let/var | object.material |
✗ |
a |
any |
let/var | *not shown* |
✗ |
b |
any |
let/var | *not shown* |
✗ |
c |
any |
let/var | *not shown* |
✗ |
index |
any |
let/var | geometry.index |
✗ |
positionAttribute |
any |
let/var | geometry.attributes.position |
✗ |
morphPosition |
any |
let/var | geometry.morphAttributes.position |
✗ |
morphTargetsRelative |
any |
let/var | geometry.morphTargetsRelative |
✗ |
normalAttribute |
any |
let/var | geometry.attributes.normal |
✗ |
morphNormal |
any |
let/var | geometry.morphAttributes.position |
✗ |
groups |
any |
let/var | geometry.groups |
✗ |
drawRange |
any |
let/var | geometry.drawRange |
✗ |
i |
any |
let/var | *not shown* |
✗ |
j |
any |
let/var | *not shown* |
✗ |
il |
any |
let/var | *not shown* |
✗ |
jl |
any |
let/var | *not shown* |
✗ |
group |
any |
let/var | *not shown* |
✗ |
start |
any |
let/var | *not shown* |
✗ |
end |
any |
let/var | *not shown* |
✗ |
modifiedPosition |
Float32Array<ArrayBuffer> |
let/var | new Float32Array( positionAttribute.count * positionAttribute.itemSize ) |
✗ |
modifiedNormal |
Float32Array<ArrayBuffer> |
let/var | new Float32Array( normalAttribute.count * normalAttribute.itemSize ) |
✗ |
morphedPositionAttribute |
any |
let/var | new Float32BufferAttribute( modifiedPosition, 3 ) |
✗ |
morphedNormalAttribute |
any |
let/var | new Float32BufferAttribute( modifiedNormal, 3 ) |
✗ |
groups |
any |
let/var | geometry.groups |
✗ |
indices |
any[] |
let/var | [] |
✗ |
newIndices |
any[] |
let/var | [] |
✗ |
group |
any |
let/var | groups[ i ] |
✗ |
groupStart |
any |
let/var | group.start |
✗ |
groupLength |
any |
let/var | groupStart + group.count |
✗ |
start |
number |
let/var | 0 |
✗ |
group |
any |
let/var | groups[ i ] |
✗ |
currentGroup |
any |
let/var | groups[ 0 ] |
✗ |
group |
any |
let/var | groups[ i ] |
✗ |
hashMultiplier |
number |
let/var | ( 1 + 1e-10 ) * 1e2 |
✗ |
verts |
any[] |
let/var | [ new Vector3(), new Vector3(), new Vector3() ] |
✗ |
tempVec1 |
any |
let/var | new Vector3() |
✗ |
tempVec2 |
any |
let/var | new Vector3() |
✗ |
tempNorm |
any |
let/var | new Vector3() |
✗ |
tempNorm2 |
any |
let/var | new Vector3() |
✗ |
x |
number |
let/var | ~ ~ ( v.x * hashMultiplier ) |
✗ |
y |
number |
let/var | ~ ~ ( v.y * hashMultiplier ) |
✗ |
z |
number |
let/var | ~ ~ ( v.z * hashMultiplier ) |
✗ |
resultGeometry |
any |
let/var | geometry.index ? geometry.toNonIndexed() : geometry |
✗ |
posAttr |
any |
let/var | resultGeometry.attributes.position |
✗ |
vertexMap |
{} |
let/var | {} |
✗ |
i3 |
number |
let/var | 3 * i |
✗ |
vert |
any |
let/var | verts[ n ] |
✗ |
normalArray |
Float32Array<ArrayBuffer> |
let/var | new Float32Array( posAttr.count * 3 ) |
✗ |
normAttr |
any |
let/var | new BufferAttribute( normalArray, 3, false ) |
✗ |
i3 |
number |
let/var | 3 * i |
✗ |
vert |
any |
let/var | verts[ n ] |
✗ |
otherNormals |
any |
let/var | vertexMap[ hash ] |
✗ |
otherNorm |
any |
let/var | otherNormals[ k ] |
✗ |
Functions¶
computeMikkTSpaceTangents(geometry: BufferGeometry, MikkTSpace: any, negateSign: boolean): BufferGeometry
¶
JSDoc:
/**
* Computes vertex tangents using the MikkTSpace algorithm. MikkTSpace generates the same tangents consistently,
* and is used in most modelling tools and normal map bakers. Use MikkTSpace for materials with normal maps,
* because inconsistent tangents may lead to subtle visual issues in the normal map, particularly around mirrored
* UV seams.
*
* In comparison to this method, {@link BufferGeometry#computeTangents} (a custom algorithm) generates tangents that
* probably will not match the tangents in other software. The custom algorithm is sufficient for general use with a
* custom material, and may be faster than MikkTSpace.
*
* Returns the original BufferGeometry. Indexed geometries will be de-indexed. Requires position, normal, and uv attributes.
*
* @param {BufferGeometry} geometry - The geometry to compute tangents for.
* @param {Object} MikkTSpace - Instance of `examples/jsm/libs/mikktspace.module.js`, or `mikktspace` npm package.
* Await `MikkTSpace.ready` before use.
* @param {boolean} [negateSign=true] - Whether to negate the sign component (.w) of each tangent.
* Required for normal map conventions in some formats, including glTF.
* @return {BufferGeometry} The updated geometry.
*/
Parameters:
geometry
BufferGeometry
MikkTSpace
any
negateSign
boolean
Returns: BufferGeometry
Calls:
geometry.hasAttribute
attribute.getX
attribute.getY
attribute.getZ
geometry.toNonIndexed
MikkTSpace.generateTangents
getAttributeArray
_geometry.setAttribute
geometry.copy
Internal Comments:
// MikkTSpace algorithm requires non-indexed input. (x2)
// Compute vertex tangents. (x2)
// Texture coordinate convention of glTF differs from the apparent
// default of the MikkTSpace library; .w component must be flipped.
// (x4)
Code
function computeMikkTSpaceTangents( geometry, MikkTSpace, negateSign = true ) {
if ( ! MikkTSpace || ! MikkTSpace.isReady ) {
throw new Error( 'BufferGeometryUtils: Initialized MikkTSpace library required.' );
}
if ( ! geometry.hasAttribute( 'position' ) || ! geometry.hasAttribute( 'normal' ) || ! geometry.hasAttribute( 'uv' ) ) {
throw new Error( 'BufferGeometryUtils: Tangents require "position", "normal", and "uv" attributes.' );
}
function getAttributeArray( attribute ) {
if ( attribute.normalized || attribute.isInterleavedBufferAttribute ) {
const dstArray = new Float32Array( attribute.count * attribute.itemSize );
for ( let i = 0, j = 0; i < attribute.count; i ++ ) {
dstArray[ j ++ ] = attribute.getX( i );
dstArray[ j ++ ] = attribute.getY( i );
if ( attribute.itemSize > 2 ) {
dstArray[ j ++ ] = attribute.getZ( i );
}
}
return dstArray;
}
if ( attribute.array instanceof Float32Array ) {
return attribute.array;
}
return new Float32Array( attribute.array );
}
// MikkTSpace algorithm requires non-indexed input.
const _geometry = geometry.index ? geometry.toNonIndexed() : geometry;
// Compute vertex tangents.
const tangents = MikkTSpace.generateTangents(
getAttributeArray( _geometry.attributes.position ),
getAttributeArray( _geometry.attributes.normal ),
getAttributeArray( _geometry.attributes.uv )
);
// Texture coordinate convention of glTF differs from the apparent
// default of the MikkTSpace library; .w component must be flipped.
if ( negateSign ) {
for ( let i = 3; i < tangents.length; i += 4 ) {
tangents[ i ] *= - 1;
}
}
//
_geometry.setAttribute( 'tangent', new BufferAttribute( tangents, 4 ) );
if ( geometry !== _geometry ) {
geometry.copy( _geometry );
}
return geometry;
}
getAttributeArray(attribute: any): any
¶
Parameters:
attribute
any
Returns: any
Calls:
attribute.getX
attribute.getY
attribute.getZ
Code
function getAttributeArray( attribute ) {
if ( attribute.normalized || attribute.isInterleavedBufferAttribute ) {
const dstArray = new Float32Array( attribute.count * attribute.itemSize );
for ( let i = 0, j = 0; i < attribute.count; i ++ ) {
dstArray[ j ++ ] = attribute.getX( i );
dstArray[ j ++ ] = attribute.getY( i );
if ( attribute.itemSize > 2 ) {
dstArray[ j ++ ] = attribute.getZ( i );
}
}
return dstArray;
}
if ( attribute.array instanceof Float32Array ) {
return attribute.array;
}
return new Float32Array( attribute.array );
}
mergeGeometries(geometries: BufferGeometry[], useGroups: boolean): BufferGeometry
¶
JSDoc:
/**
* Merges a set of geometries into a single instance. All geometries must have compatible attributes.
*
* @param {Array<BufferGeometry>} geometries - The geometries to merge.
* @param {boolean} [useGroups=false] - Whether to use groups or not.
* @return {?BufferGeometry} The merged geometry. Returns `null` if the merge does not succeed.
*/
Parameters:
geometries
BufferGeometry[]
useGroups
boolean
Returns: BufferGeometry
Calls:
Object.keys
console.error
attributesUsed.has
attributes[ name ].push
morphAttributesUsed.has
morphAttributes[ name ].push
mergedGeometry.addGroup
mergedIndex.push
index.getX
mergedGeometry.setIndex
mergeAttributes
mergedGeometry.setAttribute
morphAttributesToMerge.push
mergedGeometry.morphAttributes[ name ].push
Internal Comments:
// ensure that all geometries are indexed, or none
// gather attributes, exit early if they're different
// ensure geometries have the same number of attributes
// gather morph attributes, exit early if they're different
// merge indices
// merge attributes
// merge morph attributes
Code
function mergeGeometries( geometries, useGroups = false ) {
const isIndexed = geometries[ 0 ].index !== null;
const attributesUsed = new Set( Object.keys( geometries[ 0 ].attributes ) );
const morphAttributesUsed = new Set( Object.keys( geometries[ 0 ].morphAttributes ) );
const attributes = {};
const morphAttributes = {};
const morphTargetsRelative = geometries[ 0 ].morphTargetsRelative;
const mergedGeometry = new BufferGeometry();
let offset = 0;
for ( let i = 0; i < geometries.length; ++ i ) {
const geometry = geometries[ i ];
let attributesCount = 0;
// ensure that all geometries are indexed, or none
if ( isIndexed !== ( geometry.index !== null ) ) {
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.' );
return null;
}
// gather attributes, exit early if they're different
for ( const name in geometry.attributes ) {
if ( ! attributesUsed.has( name ) ) {
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.' );
return null;
}
if ( attributes[ name ] === undefined ) attributes[ name ] = [];
attributes[ name ].push( geometry.attributes[ name ] );
attributesCount ++;
}
// ensure geometries have the same number of attributes
if ( attributesCount !== attributesUsed.size ) {
console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. Make sure all geometries have the same number of attributes.' );
return null;
}
// gather morph attributes, exit early if they're different
if ( morphTargetsRelative !== geometry.morphTargetsRelative ) {
console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. .morphTargetsRelative must be consistent throughout all geometries.' );
return null;
}
for ( const name in geometry.morphAttributes ) {
if ( ! morphAttributesUsed.has( name ) ) {
console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. .morphAttributes must be consistent throughout all geometries.' );
return null;
}
if ( morphAttributes[ name ] === undefined ) morphAttributes[ name ] = [];
morphAttributes[ name ].push( geometry.morphAttributes[ name ] );
}
if ( useGroups ) {
let count;
if ( isIndexed ) {
count = geometry.index.count;
} else if ( geometry.attributes.position !== undefined ) {
count = geometry.attributes.position.count;
} else {
console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. The geometry must have either an index or a position attribute' );
return null;
}
mergedGeometry.addGroup( offset, count, i );
offset += count;
}
}
// merge indices
if ( isIndexed ) {
let indexOffset = 0;
const mergedIndex = [];
for ( let i = 0; i < geometries.length; ++ i ) {
const index = geometries[ i ].index;
for ( let j = 0; j < index.count; ++ j ) {
mergedIndex.push( index.getX( j ) + indexOffset );
}
indexOffset += geometries[ i ].attributes.position.count;
}
mergedGeometry.setIndex( mergedIndex );
}
// merge attributes
for ( const name in attributes ) {
const mergedAttribute = mergeAttributes( attributes[ name ] );
if ( ! mergedAttribute ) {
console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the ' + name + ' attribute.' );
return null;
}
mergedGeometry.setAttribute( name, mergedAttribute );
}
// merge morph attributes
for ( const name in morphAttributes ) {
const numMorphTargets = morphAttributes[ name ][ 0 ].length;
if ( numMorphTargets === 0 ) break;
mergedGeometry.morphAttributes = mergedGeometry.morphAttributes || {};
mergedGeometry.morphAttributes[ name ] = [];
for ( let i = 0; i < numMorphTargets; ++ i ) {
const morphAttributesToMerge = [];
for ( let j = 0; j < morphAttributes[ name ].length; ++ j ) {
morphAttributesToMerge.push( morphAttributes[ name ][ j ][ i ] );
}
const mergedMorphAttribute = mergeAttributes( morphAttributesToMerge );
if ( ! mergedMorphAttribute ) {
console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the ' + name + ' morphAttribute.' );
return null;
}
mergedGeometry.morphAttributes[ name ].push( mergedMorphAttribute );
}
}
return mergedGeometry;
}
mergeAttributes(attributes: BufferAttribute[]): BufferAttribute
¶
JSDoc:
/**
* Merges a set of attributes into a single instance. All attributes must have compatible properties and types.
* Instances of {@link InterleavedBufferAttribute} are not supported.
*
* @param {Array<BufferAttribute>} attributes - The attributes to merge.
* @return {?BufferAttribute} The merged attribute. Returns `null` if the merge does not succeed.
*/
Parameters:
attributes
BufferAttribute[]
Returns: BufferAttribute
Calls:
console.error
attribute.getComponent
result.setComponent
array.set
Code
function mergeAttributes( attributes ) {
let TypedArray;
let itemSize;
let normalized;
let gpuType = - 1;
let arrayLength = 0;
for ( let i = 0; i < attributes.length; ++ i ) {
const attribute = attributes[ i ];
if ( TypedArray === undefined ) TypedArray = attribute.array.constructor;
if ( TypedArray !== attribute.array.constructor ) {
console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.array must be of consistent array types across matching attributes.' );
return null;
}
if ( itemSize === undefined ) itemSize = attribute.itemSize;
if ( itemSize !== attribute.itemSize ) {
console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.itemSize must be consistent across matching attributes.' );
return null;
}
if ( normalized === undefined ) normalized = attribute.normalized;
if ( normalized !== attribute.normalized ) {
console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.normalized must be consistent across matching attributes.' );
return null;
}
if ( gpuType === - 1 ) gpuType = attribute.gpuType;
if ( gpuType !== attribute.gpuType ) {
console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.gpuType must be consistent across matching attributes.' );
return null;
}
arrayLength += attribute.count * itemSize;
}
const array = new TypedArray( arrayLength );
const result = new BufferAttribute( array, itemSize, normalized );
let offset = 0;
for ( let i = 0; i < attributes.length; ++ i ) {
const attribute = attributes[ i ];
if ( attribute.isInterleavedBufferAttribute ) {
const tupleOffset = offset / itemSize;
for ( let j = 0, l = attribute.count; j < l; j ++ ) {
for ( let c = 0; c < itemSize; c ++ ) {
const value = attribute.getComponent( j, c );
result.setComponent( j + tupleOffset, c, value );
}
}
} else {
array.set( attribute.array, offset );
}
offset += attribute.count * itemSize;
}
if ( gpuType !== undefined ) {
result.gpuType = gpuType;
}
return result;
}
deepCloneAttribute(attribute: BufferAttribute): BufferAttribute
¶
JSDoc:
/**
* Performs a deep clone of the given buffer attribute.
*
* @param {BufferAttribute} attribute - The attribute to clone.
* @return {BufferAttribute} The cloned attribute.
*/
Parameters:
attribute
BufferAttribute
Returns: BufferAttribute
Calls:
deinterleaveAttribute
new InstancedBufferAttribute().copy
new BufferAttribute().copy
Code
function deepCloneAttribute( attribute ) {
if ( attribute.isInstancedInterleavedBufferAttribute || attribute.isInterleavedBufferAttribute ) {
return deinterleaveAttribute( attribute );
}
if ( attribute.isInstancedBufferAttribute ) {
return new InstancedBufferAttribute().copy( attribute );
}
return new BufferAttribute().copy( attribute );
}
interleaveAttributes(attributes: BufferAttribute[]): InterleavedBufferAttribute[]
¶
JSDoc:
/**
* Interleaves a set of attributes and returns a new array of corresponding attributes that share a
* single {@link InterleavedBuffer} instance. All attributes must have compatible types.
*
* @param {Array<BufferAttribute>} attributes - The attributes to interleave.
* @return {Array<InterleavedBufferAttribute>} An array of interleaved attributes. If interleave does not succeed, the method returns `null`.
*/
Parameters:
attributes
BufferAttribute[]
Returns: InterleavedBufferAttribute[]
Calls:
console.error
res.push
complex_call_13531
complex_call_13555
Internal Comments:
// Interleaves the provided attributes into an InterleavedBuffer and returns (x2)
// a set of InterleavedBufferAttributes for each attribute (x2)
// calculate the length and type of the interleavedBuffer
// Create the set of buffer attributes (x2)
// Move the data for each attribute into the new interleavedBuffer
// at the appropriate offset
Code
function interleaveAttributes( attributes ) {
// Interleaves the provided attributes into an InterleavedBuffer and returns
// a set of InterleavedBufferAttributes for each attribute
let TypedArray;
let arrayLength = 0;
let stride = 0;
// calculate the length and type of the interleavedBuffer
for ( let i = 0, l = attributes.length; i < l; ++ i ) {
const attribute = attributes[ i ];
if ( TypedArray === undefined ) TypedArray = attribute.array.constructor;
if ( TypedArray !== attribute.array.constructor ) {
console.error( 'AttributeBuffers of different types cannot be interleaved' );
return null;
}
arrayLength += attribute.array.length;
stride += attribute.itemSize;
}
// Create the set of buffer attributes
const interleavedBuffer = new InterleavedBuffer( new TypedArray( arrayLength ), stride );
let offset = 0;
const res = [];
const getters = [ 'getX', 'getY', 'getZ', 'getW' ];
const setters = [ 'setX', 'setY', 'setZ', 'setW' ];
for ( let j = 0, l = attributes.length; j < l; j ++ ) {
const attribute = attributes[ j ];
const itemSize = attribute.itemSize;
const count = attribute.count;
const iba = new InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, attribute.normalized );
res.push( iba );
offset += itemSize;
// Move the data for each attribute into the new interleavedBuffer
// at the appropriate offset
for ( let c = 0; c < count; c ++ ) {
for ( let k = 0; k < itemSize; k ++ ) {
iba[ setters[ k ] ]( c, attribute[ getters[ k ] ]( c ) );
}
}
}
return res;
}
deinterleaveAttribute(attribute: InterleavedBufferAttribute): BufferAttribute
¶
JSDoc:
/**
* Returns a new, non-interleaved version of the given attribute.
*
* @param {InterleavedBufferAttribute} attribute - The interleaved attribute.
* @return {BufferAttribute} The non-interleaved attribute.
*/
Parameters:
attribute
InterleavedBufferAttribute
Returns: BufferAttribute
Calls:
newAttribute.setX
attribute.getX
newAttribute.setY
attribute.getY
newAttribute.setZ
attribute.getZ
newAttribute.setW
attribute.getW
Code
function deinterleaveAttribute( attribute ) {
const cons = attribute.data.array.constructor;
const count = attribute.count;
const itemSize = attribute.itemSize;
const normalized = attribute.normalized;
const array = new cons( count * itemSize );
let newAttribute;
if ( attribute.isInstancedInterleavedBufferAttribute ) {
newAttribute = new InstancedBufferAttribute( array, itemSize, normalized, attribute.meshPerAttribute );
} else {
newAttribute = new BufferAttribute( array, itemSize, normalized );
}
for ( let i = 0; i < count; i ++ ) {
newAttribute.setX( i, attribute.getX( i ) );
if ( itemSize >= 2 ) {
newAttribute.setY( i, attribute.getY( i ) );
}
if ( itemSize >= 3 ) {
newAttribute.setZ( i, attribute.getZ( i ) );
}
if ( itemSize >= 4 ) {
newAttribute.setW( i, attribute.getW( i ) );
}
}
return newAttribute;
}
deinterleaveGeometry(geometry: BufferGeometry): void
¶
JSDoc:
/**
* Deinterleaves all attributes on the given geometry.
*
* @param {BufferGeometry} geometry - The geometry to deinterleave.
*/
Parameters:
geometry
BufferGeometry
Returns: void
Calls:
attrMap.has
attrMap.set
deinterleaveAttribute
attrMap.get
Code
function deinterleaveGeometry( geometry ) {
const attributes = geometry.attributes;
const morphTargets = geometry.morphTargets;
const attrMap = new Map();
for ( const key in attributes ) {
const attr = attributes[ key ];
if ( attr.isInterleavedBufferAttribute ) {
if ( ! attrMap.has( attr ) ) {
attrMap.set( attr, deinterleaveAttribute( attr ) );
}
attributes[ key ] = attrMap.get( attr );
}
}
for ( const key in morphTargets ) {
const attr = morphTargets[ key ];
if ( attr.isInterleavedBufferAttribute ) {
if ( ! attrMap.has( attr ) ) {
attrMap.set( attr, deinterleaveAttribute( attr ) );
}
morphTargets[ key ] = attrMap.get( attr );
}
}
}
estimateBytesUsed(geometry: BufferGeometry): number
¶
JSDoc:
/**
* Returns the amount of bytes used by all attributes to represent the geometry.
*
* @param {BufferGeometry} geometry - The geometry.
* @return {number} The estimate bytes used.
*/
Parameters:
geometry
BufferGeometry
Returns: number
Calls:
geometry.getAttribute
geometry.getIndex
Internal Comments:
// Return the estimated memory used by this geometry in bytes (x2)
// Calculate using itemSize, count, and BYTES_PER_ELEMENT to account (x2)
// for InterleavedBufferAttributes. (x2)
Code
function estimateBytesUsed( geometry ) {
// Return the estimated memory used by this geometry in bytes
// Calculate using itemSize, count, and BYTES_PER_ELEMENT to account
// for InterleavedBufferAttributes.
let mem = 0;
for ( const name in geometry.attributes ) {
const attr = geometry.getAttribute( name );
mem += attr.count * attr.itemSize * attr.array.BYTES_PER_ELEMENT;
}
const indices = geometry.getIndex();
mem += indices ? indices.count * indices.itemSize * indices.array.BYTES_PER_ELEMENT : 0;
return mem;
}
mergeVertices(geometry: BufferGeometry, tolerance: number): BufferGeometry
¶
JSDoc:
/**
* Returns a new geometry with vertices for which all similar vertex attributes (within tolerance) are merged.
*
* @param {BufferGeometry} geometry - The geometry to merge vertices for.
* @param {number} [tolerance=1e-4] - The tolerance value.
* @return {BufferGeometry} - The new geometry with merged vertices.
*/
Parameters:
geometry
BufferGeometry
tolerance
number
Returns: BufferGeometry
Calls:
Math.max
geometry.getIndex
geometry.getAttribute
Object.keys
morphAttributes.forEach
Math.log10
Math.pow
indices.getX
complex_call_18956
newIndices.push
complex_call_19759
complex_call_19794
complex_call_19939
complex_call_19985
geometry.clone
result.setAttribute
tmpAttribute.array.slice
tmpMorphAttribute.array.slice
result.setIndex
Internal Comments:
// Generate an index buffer if the geometry doesn't have one, or optimize it (x2)
// if it's already available. (x2)
// next value for triangle indices (x2)
// attributes and new attribute arrays (x2)
// Initialize the arrays, allocating space conservatively. Extra
// space will be trimmed in the last step.
// convert the error tolerance to an amount of decimal places to truncate to (x2)
// Generate a hash for the vertex attributes at the current index 'i' (x2)
// double tilde truncates the decimal value (x3)
// Add another reference to the vertex if it's already
// used by another index
// copy data to the new index in the temporary attributes
// generate result BufferGeometry (x2)
// indices (x4)
Code
function mergeVertices( geometry, tolerance = 1e-4 ) {
tolerance = Math.max( tolerance, Number.EPSILON );
// Generate an index buffer if the geometry doesn't have one, or optimize it
// if it's already available.
const hashToIndex = {};
const indices = geometry.getIndex();
const positions = geometry.getAttribute( 'position' );
const vertexCount = indices ? indices.count : positions.count;
// next value for triangle indices
let nextIndex = 0;
// attributes and new attribute arrays
const attributeNames = Object.keys( geometry.attributes );
const tmpAttributes = {};
const tmpMorphAttributes = {};
const newIndices = [];
const getters = [ 'getX', 'getY', 'getZ', 'getW' ];
const setters = [ 'setX', 'setY', 'setZ', 'setW' ];
// Initialize the arrays, allocating space conservatively. Extra
// space will be trimmed in the last step.
for ( let i = 0, l = attributeNames.length; i < l; i ++ ) {
const name = attributeNames[ i ];
const attr = geometry.attributes[ name ];
tmpAttributes[ name ] = new attr.constructor(
new attr.array.constructor( attr.count * attr.itemSize ),
attr.itemSize,
attr.normalized
);
const morphAttributes = geometry.morphAttributes[ name ];
if ( morphAttributes ) {
if ( ! tmpMorphAttributes[ name ] ) tmpMorphAttributes[ name ] = [];
morphAttributes.forEach( ( morphAttr, i ) => {
const array = new morphAttr.array.constructor( morphAttr.count * morphAttr.itemSize );
tmpMorphAttributes[ name ][ i ] = new morphAttr.constructor( array, morphAttr.itemSize, morphAttr.normalized );
} );
}
}
// convert the error tolerance to an amount of decimal places to truncate to
const halfTolerance = tolerance * 0.5;
const exponent = Math.log10( 1 / tolerance );
const hashMultiplier = Math.pow( 10, exponent );
const hashAdditive = halfTolerance * hashMultiplier;
for ( let i = 0; i < vertexCount; i ++ ) {
const index = indices ? indices.getX( i ) : i;
// Generate a hash for the vertex attributes at the current index 'i'
let hash = '';
for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
const name = attributeNames[ j ];
const attribute = geometry.getAttribute( name );
const itemSize = attribute.itemSize;
for ( let k = 0; k < itemSize; k ++ ) {
// double tilde truncates the decimal value
hash += `${ ~ ~ ( attribute[ getters[ k ] ]( index ) * hashMultiplier + hashAdditive ) },`;
}
}
// Add another reference to the vertex if it's already
// used by another index
if ( hash in hashToIndex ) {
newIndices.push( hashToIndex[ hash ] );
} else {
// copy data to the new index in the temporary attributes
for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
const name = attributeNames[ j ];
const attribute = geometry.getAttribute( name );
const morphAttributes = geometry.morphAttributes[ name ];
const itemSize = attribute.itemSize;
const newArray = tmpAttributes[ name ];
const newMorphArrays = tmpMorphAttributes[ name ];
for ( let k = 0; k < itemSize; k ++ ) {
const getterFunc = getters[ k ];
const setterFunc = setters[ k ];
newArray[ setterFunc ]( nextIndex, attribute[ getterFunc ]( index ) );
if ( morphAttributes ) {
for ( let m = 0, ml = morphAttributes.length; m < ml; m ++ ) {
newMorphArrays[ m ][ setterFunc ]( nextIndex, morphAttributes[ m ][ getterFunc ]( index ) );
}
}
}
}
hashToIndex[ hash ] = nextIndex;
newIndices.push( nextIndex );
nextIndex ++;
}
}
// generate result BufferGeometry
const result = geometry.clone();
for ( const name in geometry.attributes ) {
const tmpAttribute = tmpAttributes[ name ];
result.setAttribute( name, new tmpAttribute.constructor(
tmpAttribute.array.slice( 0, nextIndex * tmpAttribute.itemSize ),
tmpAttribute.itemSize,
tmpAttribute.normalized,
) );
if ( ! ( name in tmpMorphAttributes ) ) continue;
for ( let j = 0; j < tmpMorphAttributes[ name ].length; j ++ ) {
const tmpMorphAttribute = tmpMorphAttributes[ name ][ j ];
result.morphAttributes[ name ][ j ] = new tmpMorphAttribute.constructor(
tmpMorphAttribute.array.slice( 0, nextIndex * tmpMorphAttribute.itemSize ),
tmpMorphAttribute.itemSize,
tmpMorphAttribute.normalized,
);
}
}
// indices
result.setIndex( newIndices );
return result;
}
toTrianglesDrawMode(geometry: BufferGeometry, drawMode: number): BufferGeometry
¶
JSDoc:
/**
* Returns a new indexed geometry based on `TrianglesDrawMode` draw mode.
* This mode corresponds to the `gl.TRIANGLES` primitive in WebGL.
*
* @param {BufferGeometry} geometry - The geometry to convert.
* @param {number} drawMode - The current draw mode.
* @return {BufferGeometry} The new geometry using `TrianglesDrawMode`.
*/
Parameters:
geometry
BufferGeometry
drawMode
number
Returns: BufferGeometry
Calls:
console.warn
geometry.getIndex
geometry.getAttribute
indices.push
geometry.setIndex
console.error
newIndices.push
index.getX
geometry.clone
newGeometry.setIndex
newGeometry.clearGroups
Internal Comments:
// generate index if not present
// (x2)
// gl.TRIANGLE_FAN
// gl.TRIANGLE_STRIP
// build final geometry (x2)
Code
function toTrianglesDrawMode( geometry, drawMode ) {
if ( drawMode === TrianglesDrawMode ) {
console.warn( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Geometry already defined as triangles.' );
return geometry;
}
if ( drawMode === TriangleFanDrawMode || drawMode === TriangleStripDrawMode ) {
let index = geometry.getIndex();
// generate index if not present
if ( index === null ) {
const indices = [];
const position = geometry.getAttribute( 'position' );
if ( position !== undefined ) {
for ( let i = 0; i < position.count; i ++ ) {
indices.push( i );
}
geometry.setIndex( indices );
index = geometry.getIndex();
} else {
console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.' );
return geometry;
}
}
//
const numberOfTriangles = index.count - 2;
const newIndices = [];
if ( drawMode === TriangleFanDrawMode ) {
// gl.TRIANGLE_FAN
for ( let i = 1; i <= numberOfTriangles; i ++ ) {
newIndices.push( index.getX( 0 ) );
newIndices.push( index.getX( i ) );
newIndices.push( index.getX( i + 1 ) );
}
} else {
// gl.TRIANGLE_STRIP
for ( let i = 0; i < numberOfTriangles; i ++ ) {
if ( i % 2 === 0 ) {
newIndices.push( index.getX( i ) );
newIndices.push( index.getX( i + 1 ) );
newIndices.push( index.getX( i + 2 ) );
} else {
newIndices.push( index.getX( i + 2 ) );
newIndices.push( index.getX( i + 1 ) );
newIndices.push( index.getX( i ) );
}
}
}
if ( ( newIndices.length / 3 ) !== numberOfTriangles ) {
console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Unable to generate correct amount of triangles.' );
}
// build final geometry
const newGeometry = geometry.clone();
newGeometry.setIndex( newIndices );
newGeometry.clearGroups();
return newGeometry;
} else {
console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Unknown draw mode:', drawMode );
return geometry;
}
}
computeMorphedAttributes(object: any): any
¶
JSDoc:
/**
* Calculates the morphed attributes of a morphed/skinned BufferGeometry.
*
* Helpful for Raytracing or Decals (i.e. a `DecalGeometry` applied to a morphed Object with a `BufferGeometry`
* will use the original `BufferGeometry`, not the morphed/skinned one, generating an incorrect result.
* Using this function to create a shadow `Object3`D the `DecalGeometry` can be correctly generated).
*
* @param {Mesh|Line|Points} object - The 3D object to compute morph attributes for.
* @return {Object} An object with original position/normal attributes and morphed ones.
*/
Parameters:
object
any
Returns: any
Calls:
_vA.fromBufferAttribute
_vB.fromBufferAttribute
_vC.fromBufferAttribute
_morphA.set
_morphB.set
_morphC.set
_tempA.fromBufferAttribute
_tempB.fromBufferAttribute
_tempC.fromBufferAttribute
_morphA.addScaledVector
_morphB.addScaledVector
_morphC.addScaledVector
_tempA.sub
_tempB.sub
_tempC.sub
_vA.add
_vB.add
_vC.add
object.applyBoneTransform
Array.isArray
Math.max
Math.min
index.getX
_calculateMorphedAttributeData
Internal Comments:
Code
function computeMorphedAttributes( object ) {
const _vA = new Vector3();
const _vB = new Vector3();
const _vC = new Vector3();
const _tempA = new Vector3();
const _tempB = new Vector3();
const _tempC = new Vector3();
const _morphA = new Vector3();
const _morphB = new Vector3();
const _morphC = new Vector3();
function _calculateMorphedAttributeData(
object,
attribute,
morphAttribute,
morphTargetsRelative,
a,
b,
c,
modifiedAttributeArray
) {
_vA.fromBufferAttribute( attribute, a );
_vB.fromBufferAttribute( attribute, b );
_vC.fromBufferAttribute( attribute, c );
const morphInfluences = object.morphTargetInfluences;
if ( morphAttribute && morphInfluences ) {
_morphA.set( 0, 0, 0 );
_morphB.set( 0, 0, 0 );
_morphC.set( 0, 0, 0 );
for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) {
const influence = morphInfluences[ i ];
const morph = morphAttribute[ i ];
if ( influence === 0 ) continue;
_tempA.fromBufferAttribute( morph, a );
_tempB.fromBufferAttribute( morph, b );
_tempC.fromBufferAttribute( morph, c );
if ( morphTargetsRelative ) {
_morphA.addScaledVector( _tempA, influence );
_morphB.addScaledVector( _tempB, influence );
_morphC.addScaledVector( _tempC, influence );
} else {
_morphA.addScaledVector( _tempA.sub( _vA ), influence );
_morphB.addScaledVector( _tempB.sub( _vB ), influence );
_morphC.addScaledVector( _tempC.sub( _vC ), influence );
}
}
_vA.add( _morphA );
_vB.add( _morphB );
_vC.add( _morphC );
}
if ( object.isSkinnedMesh ) {
object.applyBoneTransform( a, _vA );
object.applyBoneTransform( b, _vB );
object.applyBoneTransform( c, _vC );
}
modifiedAttributeArray[ a * 3 + 0 ] = _vA.x;
modifiedAttributeArray[ a * 3 + 1 ] = _vA.y;
modifiedAttributeArray[ a * 3 + 2 ] = _vA.z;
modifiedAttributeArray[ b * 3 + 0 ] = _vB.x;
modifiedAttributeArray[ b * 3 + 1 ] = _vB.y;
modifiedAttributeArray[ b * 3 + 2 ] = _vB.z;
modifiedAttributeArray[ c * 3 + 0 ] = _vC.x;
modifiedAttributeArray[ c * 3 + 1 ] = _vC.y;
modifiedAttributeArray[ c * 3 + 2 ] = _vC.z;
}
const geometry = object.geometry;
const material = object.material;
let a, b, c;
const index = geometry.index;
const positionAttribute = geometry.attributes.position;
const morphPosition = geometry.morphAttributes.position;
const morphTargetsRelative = geometry.morphTargetsRelative;
const normalAttribute = geometry.attributes.normal;
const morphNormal = geometry.morphAttributes.position;
const groups = geometry.groups;
const drawRange = geometry.drawRange;
let i, j, il, jl;
let group;
let start, end;
const modifiedPosition = new Float32Array( positionAttribute.count * positionAttribute.itemSize );
const modifiedNormal = new Float32Array( normalAttribute.count * normalAttribute.itemSize );
if ( index !== null ) {
// indexed buffer geometry
if ( Array.isArray( material ) ) {
for ( i = 0, il = groups.length; i < il; i ++ ) {
group = groups[ i ];
start = Math.max( group.start, drawRange.start );
end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) );
for ( j = start, jl = end; j < jl; j += 3 ) {
a = index.getX( j );
b = index.getX( j + 1 );
c = index.getX( j + 2 );
_calculateMorphedAttributeData(
object,
positionAttribute,
morphPosition,
morphTargetsRelative,
a, b, c,
modifiedPosition
);
_calculateMorphedAttributeData(
object,
normalAttribute,
morphNormal,
morphTargetsRelative,
a, b, c,
modifiedNormal
);
}
}
} else {
start = Math.max( 0, drawRange.start );
end = Math.min( index.count, ( drawRange.start + drawRange.count ) );
for ( i = start, il = end; i < il; i += 3 ) {
a = index.getX( i );
b = index.getX( i + 1 );
c = index.getX( i + 2 );
_calculateMorphedAttributeData(
object,
positionAttribute,
morphPosition,
morphTargetsRelative,
a, b, c,
modifiedPosition
);
_calculateMorphedAttributeData(
object,
normalAttribute,
morphNormal,
morphTargetsRelative,
a, b, c,
modifiedNormal
);
}
}
} else {
// non-indexed buffer geometry
if ( Array.isArray( material ) ) {
for ( i = 0, il = groups.length; i < il; i ++ ) {
group = groups[ i ];
start = Math.max( group.start, drawRange.start );
end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) );
for ( j = start, jl = end; j < jl; j += 3 ) {
a = j;
b = j + 1;
c = j + 2;
_calculateMorphedAttributeData(
object,
positionAttribute,
morphPosition,
morphTargetsRelative,
a, b, c,
modifiedPosition
);
_calculateMorphedAttributeData(
object,
normalAttribute,
morphNormal,
morphTargetsRelative,
a, b, c,
modifiedNormal
);
}
}
} else {
start = Math.max( 0, drawRange.start );
end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) );
for ( i = start, il = end; i < il; i += 3 ) {
a = i;
b = i + 1;
c = i + 2;
_calculateMorphedAttributeData(
object,
positionAttribute,
morphPosition,
morphTargetsRelative,
a, b, c,
modifiedPosition
);
_calculateMorphedAttributeData(
object,
normalAttribute,
morphNormal,
morphTargetsRelative,
a, b, c,
modifiedNormal
);
}
}
}
const morphedPositionAttribute = new Float32BufferAttribute( modifiedPosition, 3 );
const morphedNormalAttribute = new Float32BufferAttribute( modifiedNormal, 3 );
return {
positionAttribute: positionAttribute,
normalAttribute: normalAttribute,
morphedPositionAttribute: morphedPositionAttribute,
morphedNormalAttribute: morphedNormalAttribute
};
}
_calculateMorphedAttributeData(object: any, attribute: any, morphAttribute: any, morphTargetsRelative: any, a: any, b: any, c: any, modifiedAttributeArray: any): void
¶
Parameters:
object
any
attribute
any
morphAttribute
any
morphTargetsRelative
any
a
any
b
any
c
any
modifiedAttributeArray
any
Returns: void
Calls:
_vA.fromBufferAttribute
_vB.fromBufferAttribute
_vC.fromBufferAttribute
_morphA.set
_morphB.set
_morphC.set
_tempA.fromBufferAttribute
_tempB.fromBufferAttribute
_tempC.fromBufferAttribute
_morphA.addScaledVector
_morphB.addScaledVector
_morphC.addScaledVector
_tempA.sub
_tempB.sub
_tempC.sub
_vA.add
_vB.add
_vC.add
object.applyBoneTransform
Code
function _calculateMorphedAttributeData(
object,
attribute,
morphAttribute,
morphTargetsRelative,
a,
b,
c,
modifiedAttributeArray
) {
_vA.fromBufferAttribute( attribute, a );
_vB.fromBufferAttribute( attribute, b );
_vC.fromBufferAttribute( attribute, c );
const morphInfluences = object.morphTargetInfluences;
if ( morphAttribute && morphInfluences ) {
_morphA.set( 0, 0, 0 );
_morphB.set( 0, 0, 0 );
_morphC.set( 0, 0, 0 );
for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) {
const influence = morphInfluences[ i ];
const morph = morphAttribute[ i ];
if ( influence === 0 ) continue;
_tempA.fromBufferAttribute( morph, a );
_tempB.fromBufferAttribute( morph, b );
_tempC.fromBufferAttribute( morph, c );
if ( morphTargetsRelative ) {
_morphA.addScaledVector( _tempA, influence );
_morphB.addScaledVector( _tempB, influence );
_morphC.addScaledVector( _tempC, influence );
} else {
_morphA.addScaledVector( _tempA.sub( _vA ), influence );
_morphB.addScaledVector( _tempB.sub( _vB ), influence );
_morphC.addScaledVector( _tempC.sub( _vC ), influence );
}
}
_vA.add( _morphA );
_vB.add( _morphB );
_vC.add( _morphC );
}
if ( object.isSkinnedMesh ) {
object.applyBoneTransform( a, _vA );
object.applyBoneTransform( b, _vB );
object.applyBoneTransform( c, _vC );
}
modifiedAttributeArray[ a * 3 + 0 ] = _vA.x;
modifiedAttributeArray[ a * 3 + 1 ] = _vA.y;
modifiedAttributeArray[ a * 3 + 2 ] = _vA.z;
modifiedAttributeArray[ b * 3 + 0 ] = _vB.x;
modifiedAttributeArray[ b * 3 + 1 ] = _vB.y;
modifiedAttributeArray[ b * 3 + 2 ] = _vB.z;
modifiedAttributeArray[ c * 3 + 0 ] = _vC.x;
modifiedAttributeArray[ c * 3 + 1 ] = _vC.y;
modifiedAttributeArray[ c * 3 + 2 ] = _vC.z;
}
mergeGroups(geometry: BufferGeometry): BufferGeometry
¶
JSDoc:
/**
* Merges the {@link BufferGeometry#groups} for the given geometry.
*
* @param {BufferGeometry} geometry - The geometry to modify.
* @return {BufferGeometry} - The updated geometry
*/
Parameters:
geometry
BufferGeometry
Returns: BufferGeometry
Calls:
console.warn
groups.sort
geometry.getIndex
geometry.getAttribute
indices.push
geometry.setIndex
newIndices.push
index.getX
geometry.dispose
geometry.groups.push
Internal Comments:
// sort groups by material index (x3)
// create index for non-indexed geometries
// sort index (x2)
// update groups indices (x2)
// merge groups (x2)
Code
function mergeGroups( geometry ) {
if ( geometry.groups.length === 0 ) {
console.warn( 'THREE.BufferGeometryUtils.mergeGroups(): No groups are defined. Nothing to merge.' );
return geometry;
}
let groups = geometry.groups;
// sort groups by material index
groups = groups.sort( ( a, b ) => {
if ( a.materialIndex !== b.materialIndex ) return a.materialIndex - b.materialIndex;
return a.start - b.start;
} );
// create index for non-indexed geometries
if ( geometry.getIndex() === null ) {
const positionAttribute = geometry.getAttribute( 'position' );
const indices = [];
for ( let i = 0; i < positionAttribute.count; i += 3 ) {
indices.push( i, i + 1, i + 2 );
}
geometry.setIndex( indices );
}
// sort index
const index = geometry.getIndex();
const newIndices = [];
for ( let i = 0; i < groups.length; i ++ ) {
const group = groups[ i ];
const groupStart = group.start;
const groupLength = groupStart + group.count;
for ( let j = groupStart; j < groupLength; j ++ ) {
newIndices.push( index.getX( j ) );
}
}
geometry.dispose(); // Required to force buffer recreation
geometry.setIndex( newIndices );
// update groups indices
let start = 0;
for ( let i = 0; i < groups.length; i ++ ) {
const group = groups[ i ];
group.start = start;
start += group.count;
}
// merge groups
let currentGroup = groups[ 0 ];
geometry.groups = [ currentGroup ];
for ( let i = 1; i < groups.length; i ++ ) {
const group = groups[ i ];
if ( currentGroup.materialIndex === group.materialIndex ) {
currentGroup.count += group.count;
} else {
currentGroup = group;
geometry.groups.push( currentGroup );
}
}
return geometry;
}
toCreasedNormals(geometry: BufferGeometry, creaseAngle: number): BufferGeometry
¶
JSDoc:
/**
* Modifies the supplied geometry if it is non-indexed, otherwise creates a new,
* non-indexed geometry. Returns the geometry with smooth normals everywhere except
* faces that meet at an angle greater than the crease angle.
*
* @param {BufferGeometry} geometry - The geometry to modify.
* @param {number} [creaseAngle=Math.PI/3] - The crease angle in radians.
* @return {BufferGeometry} - The updated geometry
*/
Parameters:
geometry
BufferGeometry
creaseAngle
number
Returns: BufferGeometry
Calls:
Math.cos
geometry.toNonIndexed
verts[ 0 ].fromBufferAttribute
verts[ 1 ].fromBufferAttribute
verts[ 2 ].fromBufferAttribute
tempVec1.subVectors
tempVec2.subVectors
new Vector3().crossVectors( tempVec1, tempVec2 ).normalize
hashVertex
vertexMap[ hash ].push
tempNorm.crossVectors( tempVec1, tempVec2 ).normalize
tempNorm2.set
tempNorm.dot
tempNorm2.add
tempNorm2.normalize
normAttr.setXYZ
resultGeometry.setAttribute
Internal Comments:
// reusable vectors (x2)
// hashes a vector
// BufferGeometry.toNonIndexed() warns if the geometry is non-indexed (x2)
// and returns the original geometry (x2)
// find all the normals shared by commonly located vertices
// add the normal to the map for all vertices (x2)
// average normals from all vertices that share a common location if they are within the (x2)
// provided crease threshold (x2)
// get the face normal for this vertex (x2)
// average all normals that meet the threshold and set the normal value
Code
function toCreasedNormals( geometry, creaseAngle = Math.PI / 3 /* 60 degrees */ ) {
const creaseDot = Math.cos( creaseAngle );
const hashMultiplier = ( 1 + 1e-10 ) * 1e2;
// reusable vectors
const verts = [ new Vector3(), new Vector3(), new Vector3() ];
const tempVec1 = new Vector3();
const tempVec2 = new Vector3();
const tempNorm = new Vector3();
const tempNorm2 = new Vector3();
// hashes a vector
function hashVertex( v ) {
const x = ~ ~ ( v.x * hashMultiplier );
const y = ~ ~ ( v.y * hashMultiplier );
const z = ~ ~ ( v.z * hashMultiplier );
return `${x},${y},${z}`;
}
// BufferGeometry.toNonIndexed() warns if the geometry is non-indexed
// and returns the original geometry
const resultGeometry = geometry.index ? geometry.toNonIndexed() : geometry;
const posAttr = resultGeometry.attributes.position;
const vertexMap = {};
// find all the normals shared by commonly located vertices
for ( let i = 0, l = posAttr.count / 3; i < l; i ++ ) {
const i3 = 3 * i;
const a = verts[ 0 ].fromBufferAttribute( posAttr, i3 + 0 );
const b = verts[ 1 ].fromBufferAttribute( posAttr, i3 + 1 );
const c = verts[ 2 ].fromBufferAttribute( posAttr, i3 + 2 );
tempVec1.subVectors( c, b );
tempVec2.subVectors( a, b );
// add the normal to the map for all vertices
const normal = new Vector3().crossVectors( tempVec1, tempVec2 ).normalize();
for ( let n = 0; n < 3; n ++ ) {
const vert = verts[ n ];
const hash = hashVertex( vert );
if ( ! ( hash in vertexMap ) ) {
vertexMap[ hash ] = [];
}
vertexMap[ hash ].push( normal );
}
}
// average normals from all vertices that share a common location if they are within the
// provided crease threshold
const normalArray = new Float32Array( posAttr.count * 3 );
const normAttr = new BufferAttribute( normalArray, 3, false );
for ( let i = 0, l = posAttr.count / 3; i < l; i ++ ) {
// get the face normal for this vertex
const i3 = 3 * i;
const a = verts[ 0 ].fromBufferAttribute( posAttr, i3 + 0 );
const b = verts[ 1 ].fromBufferAttribute( posAttr, i3 + 1 );
const c = verts[ 2 ].fromBufferAttribute( posAttr, i3 + 2 );
tempVec1.subVectors( c, b );
tempVec2.subVectors( a, b );
tempNorm.crossVectors( tempVec1, tempVec2 ).normalize();
// average all normals that meet the threshold and set the normal value
for ( let n = 0; n < 3; n ++ ) {
const vert = verts[ n ];
const hash = hashVertex( vert );
const otherNormals = vertexMap[ hash ];
tempNorm2.set( 0, 0, 0 );
for ( let k = 0, lk = otherNormals.length; k < lk; k ++ ) {
const otherNorm = otherNormals[ k ];
if ( tempNorm.dot( otherNorm ) > creaseDot ) {
tempNorm2.add( otherNorm );
}
}
tempNorm2.normalize();
normAttr.setXYZ( i3 + n, tempNorm2.x, tempNorm2.y, tempNorm2.z );
}
}
resultGeometry.setAttribute( 'normal', normAttr );
return resultGeometry;
}
hashVertex(v: any): string
¶
Parameters:
v
any
Returns: string