Buckets:
| import { Vector3 } from '../math/Vector3.js'; | |
| import { Vector2 } from '../math/Vector2.js'; | |
| import { Box3 } from '../math/Box3.js'; | |
| import { EventDispatcher } from './EventDispatcher.js'; | |
| import { BufferAttribute, Float32BufferAttribute, Uint16BufferAttribute, Uint32BufferAttribute } from './BufferAttribute.js'; | |
| import { Sphere } from '../math/Sphere.js'; | |
| import { Object3D } from './Object3D.js'; | |
| import { Matrix4 } from '../math/Matrix4.js'; | |
| import { Matrix3 } from '../math/Matrix3.js'; | |
| import { generateUUID } from '../math/MathUtils.js'; | |
| import { arrayNeedsUint32 } from '../utils.js'; | |
| let _id = 0; | |
| const _m1 = /*@__PURE__*/ new Matrix4(); | |
| const _obj = /*@__PURE__*/ new Object3D(); | |
| const _offset = /*@__PURE__*/ new Vector3(); | |
| const _box = /*@__PURE__*/ new Box3(); | |
| const _boxMorphTargets = /*@__PURE__*/ new Box3(); | |
| const _vector = /*@__PURE__*/ new Vector3(); | |
| /** | |
| * A representation of mesh, line, or point geometry. Includes vertex | |
| * positions, face indices, normals, colors, UVs, and custom attributes | |
| * within buffers, reducing the cost of passing all this data to the GPU. | |
| * | |
| * ```js | |
| * const geometry = new THREE.BufferGeometry(); | |
| * // create a simple square shape. We duplicate the top left and bottom right | |
| * // vertices because each vertex needs to appear once per triangle. | |
| * const vertices = new Float32Array( [ | |
| * -1.0, -1.0, 1.0, // v0 | |
| * 1.0, -1.0, 1.0, // v1 | |
| * 1.0, 1.0, 1.0, // v2 | |
| * | |
| * 1.0, 1.0, 1.0, // v3 | |
| * -1.0, 1.0, 1.0, // v4 | |
| * -1.0, -1.0, 1.0 // v5 | |
| * ] ); | |
| * // itemSize = 3 because there are 3 values (components) per vertex | |
| * geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); | |
| * const material = new THREE.MeshBasicMaterial( { color: 0xff0000 } ); | |
| * const mesh = new THREE.Mesh( geometry, material ); | |
| * ``` | |
| * | |
| * @augments EventDispatcher | |
| */ | |
| class BufferGeometry extends EventDispatcher { | |
| /** | |
| * Constructs a new geometry. | |
| */ | |
| constructor() { | |
| super(); | |
| /** | |
| * This flag can be used for type testing. | |
| * | |
| * @type {boolean} | |
| * @readonly | |
| * @default true | |
| */ | |
| this.isBufferGeometry = true; | |
| /** | |
| * The ID of the geometry. | |
| * | |
| * @name BufferGeometry#id | |
| * @type {number} | |
| * @readonly | |
| */ | |
| Object.defineProperty( this, 'id', { value: _id ++ } ); | |
| /** | |
| * The UUID of the geometry. | |
| * | |
| * @type {string} | |
| * @readonly | |
| */ | |
| this.uuid = generateUUID(); | |
| /** | |
| * The name of the geometry. | |
| * | |
| * @type {string} | |
| */ | |
| this.name = ''; | |
| this.type = 'BufferGeometry'; | |
| /** | |
| * Allows for vertices to be re-used across multiple triangles; this is | |
| * called using "indexed triangles". Each triangle is associated with the | |
| * indices of three vertices. This attribute therefore stores the index of | |
| * each vertex for each triangular face. If this attribute is not set, the | |
| * renderer assumes that each three contiguous positions represent a single triangle. | |
| * | |
| * @type {?BufferAttribute} | |
| * @default null | |
| */ | |
| this.index = null; | |
| /** | |
| * A (storage) buffer attribute which was generated with a compute shader and | |
| * now defines indirect draw calls. | |
| * | |
| * Can only be used with {@link WebGPURenderer} and a WebGPU backend. | |
| * | |
| * @type {?BufferAttribute} | |
| * @default null | |
| */ | |
| this.indirect = null; | |
| /** | |
| * This dictionary has as id the name of the attribute to be set and as value | |
| * the buffer attribute to set it to. Rather than accessing this property directly, | |
| * use `setAttribute()` and `getAttribute()` to access attributes of this geometry. | |
| * | |
| * @type {Object<string,(BufferAttribute|InterleavedBufferAttribute)>} | |
| */ | |
| this.attributes = {}; | |
| /** | |
| * This dictionary holds the morph targets of the geometry. | |
| * | |
| * Note: Once the geometry has been rendered, the morph attribute data cannot | |
| * be changed. You will have to call `dispose()?, and create a new geometry instance. | |
| * | |
| * @type {Object} | |
| */ | |
| this.morphAttributes = {}; | |
| /** | |
| * Used to control the morph target behavior; when set to `true`, the morph | |
| * target data is treated as relative offsets, rather than as absolute | |
| * positions/normals. | |
| * | |
| * @type {boolean} | |
| * @default false | |
| */ | |
| this.morphTargetsRelative = false; | |
| /** | |
| * Split the geometry into groups, each of which will be rendered in a | |
| * separate draw call. This allows an array of materials to be used with the geometry. | |
| * | |
| * Use `addGroup()` and `clearGroups()` to edit groups, rather than modifying this array directly. | |
| * | |
| * Every vertex and index must belong to exactly one group — groups must not share vertices or | |
| * indices, and must not leave vertices or indices unused. | |
| * | |
| * @type {Array<Object>} | |
| */ | |
| this.groups = []; | |
| /** | |
| * Bounding box for the geometry which can be calculated with `computeBoundingBox()`. | |
| * | |
| * @type {Box3} | |
| * @default null | |
| */ | |
| this.boundingBox = null; | |
| /** | |
| * Bounding sphere for the geometry which can be calculated with `computeBoundingSphere()`. | |
| * | |
| * @type {Sphere} | |
| * @default null | |
| */ | |
| this.boundingSphere = null; | |
| /** | |
| * Determines the part of the geometry to render. This should not be set directly, | |
| * instead use `setDrawRange()`. | |
| * | |
| * @type {{start:number,count:number}} | |
| */ | |
| this.drawRange = { start: 0, count: Infinity }; | |
| /** | |
| * An object that can be used to store custom data about the geometry. | |
| * It should not hold references to functions as these will not be cloned. | |
| * | |
| * @type {Object} | |
| */ | |
| this.userData = {}; | |
| } | |
| /** | |
| * Returns the index of this geometry. | |
| * | |
| * @return {?BufferAttribute} The index. Returns `null` if no index is defined. | |
| */ | |
| getIndex() { | |
| return this.index; | |
| } | |
| /** | |
| * Sets the given index to this geometry. | |
| * | |
| * @param {Array<number>|BufferAttribute} index - The index to set. | |
| * @return {BufferGeometry} A reference to this instance. | |
| */ | |
| setIndex( index ) { | |
| if ( Array.isArray( index ) ) { | |
| this.index = new ( arrayNeedsUint32( index ) ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 ); | |
| } else { | |
| this.index = index; | |
| } | |
| return this; | |
| } | |
| /** | |
| * Sets the given indirect attribute to this geometry. | |
| * | |
| * @param {BufferAttribute} indirect - The attribute holding indirect draw calls. | |
| * @return {BufferGeometry} A reference to this instance. | |
| */ | |
| setIndirect( indirect ) { | |
| this.indirect = indirect; | |
| return this; | |
| } | |
| /** | |
| * Returns the indirect attribute of this geometry. | |
| * | |
| * @return {?BufferAttribute} The indirect attribute. Returns `null` if no indirect attribute is defined. | |
| */ | |
| getIndirect() { | |
| return this.indirect; | |
| } | |
| /** | |
| * Returns the buffer attribute for the given name. | |
| * | |
| * @param {string} name - The attribute name. | |
| * @return {BufferAttribute|InterleavedBufferAttribute|undefined} The buffer attribute. | |
| * Returns `undefined` if not attribute has been found. | |
| */ | |
| getAttribute( name ) { | |
| return this.attributes[ name ]; | |
| } | |
| /** | |
| * Sets the given attribute for the given name. | |
| * | |
| * @param {string} name - The attribute name. | |
| * @param {BufferAttribute|InterleavedBufferAttribute} attribute - The attribute to set. | |
| * @return {BufferGeometry} A reference to this instance. | |
| */ | |
| setAttribute( name, attribute ) { | |
| this.attributes[ name ] = attribute; | |
| return this; | |
| } | |
| /** | |
| * Deletes the attribute for the given name. | |
| * | |
| * @param {string} name - The attribute name to delete. | |
| * @return {BufferGeometry} A reference to this instance. | |
| */ | |
| deleteAttribute( name ) { | |
| delete this.attributes[ name ]; | |
| return this; | |
| } | |
| /** | |
| * Returns `true` if this geometry has an attribute for the given name. | |
| * | |
| * @param {string} name - The attribute name. | |
| * @return {boolean} Whether this geometry has an attribute for the given name or not. | |
| */ | |
| hasAttribute( name ) { | |
| return this.attributes[ name ] !== undefined; | |
| } | |
| /** | |
| * Adds a group to this geometry. | |
| * | |
| * @param {number} start - The first element in this draw call. That is the first | |
| * vertex for non-indexed geometry, otherwise the first triangle index. | |
| * @param {number} count - Specifies how many vertices (or indices) are part of this group. | |
| * @param {number} [materialIndex=0] - The material array index to use. | |
| */ | |
| addGroup( start, count, materialIndex = 0 ) { | |
| this.groups.push( { | |
| start: start, | |
| count: count, | |
| materialIndex: materialIndex | |
| } ); | |
| } | |
| /** | |
| * Clears all groups. | |
| */ | |
| clearGroups() { | |
| this.groups = []; | |
| } | |
| /** | |
| * Sets the draw range for this geometry. | |
| * | |
| * @param {number} start - The first vertex for non-indexed geometry, otherwise the first triangle index. | |
| * @param {number} count - For non-indexed BufferGeometry, `count` is the number of vertices to render. | |
| * For indexed BufferGeometry, `count` is the number of indices to render. | |
| */ | |
| setDrawRange( start, count ) { | |
| this.drawRange.start = start; | |
| this.drawRange.count = count; | |
| } | |
| /** | |
| * Applies the given 4x4 transformation matrix to the geometry. | |
| * | |
| * @param {Matrix4} matrix - The matrix to apply. | |
| * @return {BufferGeometry} A reference to this instance. | |
| */ | |
| applyMatrix4( matrix ) { | |
| const position = this.attributes.position; | |
| if ( position !== undefined ) { | |
| position.applyMatrix4( matrix ); | |
| position.needsUpdate = true; | |
| } | |
| const normal = this.attributes.normal; | |
| if ( normal !== undefined ) { | |
| const normalMatrix = new Matrix3().getNormalMatrix( matrix ); | |
| normal.applyNormalMatrix( normalMatrix ); | |
| normal.needsUpdate = true; | |
| } | |
| const tangent = this.attributes.tangent; | |
| if ( tangent !== undefined ) { | |
| tangent.transformDirection( matrix ); | |
| tangent.needsUpdate = true; | |
| } | |
| if ( this.boundingBox !== null ) { | |
| this.computeBoundingBox(); | |
| } | |
| if ( this.boundingSphere !== null ) { | |
| this.computeBoundingSphere(); | |
| } | |
| return this; | |
| } | |
| /** | |
| * Applies the rotation represented by the Quaternion to the geometry. | |
| * | |
| * @param {Quaternion} q - The Quaternion to apply. | |
| * @return {BufferGeometry} A reference to this instance. | |
| */ | |
| applyQuaternion( q ) { | |
| _m1.makeRotationFromQuaternion( q ); | |
| this.applyMatrix4( _m1 ); | |
| return this; | |
| } | |
| /** | |
| * Rotates the geometry about the X axis. This is typically done as a one time | |
| * operation, and not during a loop. Use {@link Object3D#rotation} for typical | |
| * real-time mesh rotation. | |
| * | |
| * @param {number} angle - The angle in radians. | |
| * @return {BufferGeometry} A reference to this instance. | |
| */ | |
| rotateX( angle ) { | |
| // rotate geometry around world x-axis | |
| _m1.makeRotationX( angle ); | |
| this.applyMatrix4( _m1 ); | |
| return this; | |
| } | |
| /** | |
| * Rotates the geometry about the Y axis. This is typically done as a one time | |
| * operation, and not during a loop. Use {@link Object3D#rotation} for typical | |
| * real-time mesh rotation. | |
| * | |
| * @param {number} angle - The angle in radians. | |
| * @return {BufferGeometry} A reference to this instance. | |
| */ | |
| rotateY( angle ) { | |
| // rotate geometry around world y-axis | |
| _m1.makeRotationY( angle ); | |
| this.applyMatrix4( _m1 ); | |
| return this; | |
| } | |
| /** | |
| * Rotates the geometry about the Z axis. This is typically done as a one time | |
| * operation, and not during a loop. Use {@link Object3D#rotation} for typical | |
| * real-time mesh rotation. | |
| * | |
| * @param {number} angle - The angle in radians. | |
| * @return {BufferGeometry} A reference to this instance. | |
| */ | |
| rotateZ( angle ) { | |
| // rotate geometry around world z-axis | |
| _m1.makeRotationZ( angle ); | |
| this.applyMatrix4( _m1 ); | |
| return this; | |
| } | |
| /** | |
| * Translates the geometry. This is typically done as a one time | |
| * operation, and not during a loop. Use {@link Object3D#position} for typical | |
| * real-time mesh rotation. | |
| * | |
| * @param {number} x - The x offset. | |
| * @param {number} y - The y offset. | |
| * @param {number} z - The z offset. | |
| * @return {BufferGeometry} A reference to this instance. | |
| */ | |
| translate( x, y, z ) { | |
| // translate geometry | |
| _m1.makeTranslation( x, y, z ); | |
| this.applyMatrix4( _m1 ); | |
| return this; | |
| } | |
| /** | |
| * Scales the geometry. This is typically done as a one time | |
| * operation, and not during a loop. Use {@link Object3D#scale} for typical | |
| * real-time mesh rotation. | |
| * | |
| * @param {number} x - The x scale. | |
| * @param {number} y - The y scale. | |
| * @param {number} z - The z scale. | |
| * @return {BufferGeometry} A reference to this instance. | |
| */ | |
| scale( x, y, z ) { | |
| // scale geometry | |
| _m1.makeScale( x, y, z ); | |
| this.applyMatrix4( _m1 ); | |
| return this; | |
| } | |
| /** | |
| * Rotates the geometry to face a point in 3D space. This is typically done as a one time | |
| * operation, and not during a loop. Use {@link Object3D#lookAt} for typical | |
| * real-time mesh rotation. | |
| * | |
| * @param {Vector3} vector - The target point. | |
| * @return {BufferGeometry} A reference to this instance. | |
| */ | |
| lookAt( vector ) { | |
| _obj.lookAt( vector ); | |
| _obj.updateMatrix(); | |
| this.applyMatrix4( _obj.matrix ); | |
| return this; | |
| } | |
| /** | |
| * Center the geometry based on its bounding box. | |
| * | |
| * @return {BufferGeometry} A reference to this instance. | |
| */ | |
| center() { | |
| this.computeBoundingBox(); | |
| this.boundingBox.getCenter( _offset ).negate(); | |
| this.translate( _offset.x, _offset.y, _offset.z ); | |
| return this; | |
| } | |
| /** | |
| * Defines a geometry by creating a `position` attribute based on the given array of points. The array | |
| * can hold 2D or 3D vectors. When using two-dimensional data, the `z` coordinate for all vertices is | |
| * set to `0`. | |
| * | |
| * If the method is used with an existing `position` attribute, the vertex data are overwritten with the | |
| * data from the array. The length of the array must match the vertex count. | |
| * | |
| * @param {Array<Vector2>|Array<Vector3>} points - The points. | |
| * @return {BufferGeometry} A reference to this instance. | |
| */ | |
| setFromPoints( points ) { | |
| const positionAttribute = this.getAttribute( 'position' ); | |
| if ( positionAttribute === undefined ) { | |
| const position = []; | |
| for ( let i = 0, l = points.length; i < l; i ++ ) { | |
| const point = points[ i ]; | |
| position.push( point.x, point.y, point.z || 0 ); | |
| } | |
| this.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) ); | |
| } else { | |
| const l = Math.min( points.length, positionAttribute.count ); // make sure data do not exceed buffer size | |
| for ( let i = 0; i < l; i ++ ) { | |
| const point = points[ i ]; | |
| positionAttribute.setXYZ( i, point.x, point.y, point.z || 0 ); | |
| } | |
| if ( points.length > positionAttribute.count ) { | |
| console.warn( 'THREE.BufferGeometry: Buffer size too small for points data. Use .dispose() and create a new geometry.' ); | |
| } | |
| positionAttribute.needsUpdate = true; | |
| } | |
| return this; | |
| } | |
| /** | |
| * Computes the bounding box of the geometry, and updates the `boundingBox` member. | |
| * The bounding box is not computed by the engine; it must be computed by your app. | |
| * You may need to recompute the bounding box if the geometry vertices are modified. | |
| */ | |
| computeBoundingBox() { | |
| if ( this.boundingBox === null ) { | |
| this.boundingBox = new Box3(); | |
| } | |
| const position = this.attributes.position; | |
| const morphAttributesPosition = this.morphAttributes.position; | |
| if ( position && position.isGLBufferAttribute ) { | |
| console.error( 'THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box.', this ); | |
| this.boundingBox.set( | |
| new Vector3( - Infinity, - Infinity, - Infinity ), | |
| new Vector3( + Infinity, + Infinity, + Infinity ) | |
| ); | |
| return; | |
| } | |
| if ( position !== undefined ) { | |
| this.boundingBox.setFromBufferAttribute( position ); | |
| // process morph attributes if present | |
| if ( morphAttributesPosition ) { | |
| for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { | |
| const morphAttribute = morphAttributesPosition[ i ]; | |
| _box.setFromBufferAttribute( morphAttribute ); | |
| if ( this.morphTargetsRelative ) { | |
| _vector.addVectors( this.boundingBox.min, _box.min ); | |
| this.boundingBox.expandByPoint( _vector ); | |
| _vector.addVectors( this.boundingBox.max, _box.max ); | |
| this.boundingBox.expandByPoint( _vector ); | |
| } else { | |
| this.boundingBox.expandByPoint( _box.min ); | |
| this.boundingBox.expandByPoint( _box.max ); | |
| } | |
| } | |
| } | |
| } else { | |
| this.boundingBox.makeEmpty(); | |
| } | |
| if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) { | |
| console.error( 'THREE.BufferGeometry.computeBoundingBox(): Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this ); | |
| } | |
| } | |
| /** | |
| * Computes the bounding sphere of the geometry, and updates the `boundingSphere` member. | |
| * The engine automatically computes the bounding sphere when it is needed, e.g., for ray casting or view frustum culling. | |
| * You may need to recompute the bounding sphere if the geometry vertices are modified. | |
| */ | |
| computeBoundingSphere() { | |
| if ( this.boundingSphere === null ) { | |
| this.boundingSphere = new Sphere(); | |
| } | |
| const position = this.attributes.position; | |
| const morphAttributesPosition = this.morphAttributes.position; | |
| if ( position && position.isGLBufferAttribute ) { | |
| console.error( 'THREE.BufferGeometry.computeBoundingSphere(): GLBufferAttribute requires a manual bounding sphere.', this ); | |
| this.boundingSphere.set( new Vector3(), Infinity ); | |
| return; | |
| } | |
| if ( position ) { | |
| // first, find the center of the bounding sphere | |
| const center = this.boundingSphere.center; | |
| _box.setFromBufferAttribute( position ); | |
| // process morph attributes if present | |
| if ( morphAttributesPosition ) { | |
| for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { | |
| const morphAttribute = morphAttributesPosition[ i ]; | |
| _boxMorphTargets.setFromBufferAttribute( morphAttribute ); | |
| if ( this.morphTargetsRelative ) { | |
| _vector.addVectors( _box.min, _boxMorphTargets.min ); | |
| _box.expandByPoint( _vector ); | |
| _vector.addVectors( _box.max, _boxMorphTargets.max ); | |
| _box.expandByPoint( _vector ); | |
| } else { | |
| _box.expandByPoint( _boxMorphTargets.min ); | |
| _box.expandByPoint( _boxMorphTargets.max ); | |
| } | |
| } | |
| } | |
| _box.getCenter( center ); | |
| // second, try to find a boundingSphere with a radius smaller than the | |
| // boundingSphere of the boundingBox: sqrt(3) smaller in the best case | |
| let maxRadiusSq = 0; | |
| for ( let i = 0, il = position.count; i < il; i ++ ) { | |
| _vector.fromBufferAttribute( position, i ); | |
| maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector ) ); | |
| } | |
| // process morph attributes if present | |
| if ( morphAttributesPosition ) { | |
| for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { | |
| const morphAttribute = morphAttributesPosition[ i ]; | |
| const morphTargetsRelative = this.morphTargetsRelative; | |
| for ( let j = 0, jl = morphAttribute.count; j < jl; j ++ ) { | |
| _vector.fromBufferAttribute( morphAttribute, j ); | |
| if ( morphTargetsRelative ) { | |
| _offset.fromBufferAttribute( position, j ); | |
| _vector.add( _offset ); | |
| } | |
| maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector ) ); | |
| } | |
| } | |
| } | |
| this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); | |
| if ( isNaN( this.boundingSphere.radius ) ) { | |
| console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this ); | |
| } | |
| } | |
| } | |
| /** | |
| * Calculates and adds a tangent attribute to this geometry. | |
| * | |
| * The computation is only supported for indexed geometries and if position, normal, and uv attributes | |
| * are defined. When using a tangent space normal map, prefer the MikkTSpace algorithm provided by | |
| * {@link BufferGeometryUtils#computeMikkTSpaceTangents} instead. | |
| */ | |
| computeTangents() { | |
| const index = this.index; | |
| const attributes = this.attributes; | |
| // based on http://www.terathon.com/code/tangent.html | |
| // (per vertex tangents) | |
| if ( index === null || | |
| attributes.position === undefined || | |
| attributes.normal === undefined || | |
| attributes.uv === undefined ) { | |
| console.error( 'THREE.BufferGeometry: .computeTangents() failed. Missing required attributes (index, position, normal or uv)' ); | |
| return; | |
| } | |
| const positionAttribute = attributes.position; | |
| const normalAttribute = attributes.normal; | |
| const uvAttribute = attributes.uv; | |
| if ( this.hasAttribute( 'tangent' ) === false ) { | |
| this.setAttribute( 'tangent', new BufferAttribute( new Float32Array( 4 * positionAttribute.count ), 4 ) ); | |
| } | |
| const tangentAttribute = this.getAttribute( 'tangent' ); | |
| const tan1 = [], tan2 = []; | |
| for ( let i = 0; i < positionAttribute.count; i ++ ) { | |
| tan1[ i ] = new Vector3(); | |
| tan2[ i ] = new Vector3(); | |
| } | |
| const vA = new Vector3(), | |
| vB = new Vector3(), | |
| vC = new Vector3(), | |
| uvA = new Vector2(), | |
| uvB = new Vector2(), | |
| uvC = new Vector2(), | |
| sdir = new Vector3(), | |
| tdir = new Vector3(); | |
| function handleTriangle( a, b, c ) { | |
| vA.fromBufferAttribute( positionAttribute, a ); | |
| vB.fromBufferAttribute( positionAttribute, b ); | |
| vC.fromBufferAttribute( positionAttribute, c ); | |
| uvA.fromBufferAttribute( uvAttribute, a ); | |
| uvB.fromBufferAttribute( uvAttribute, b ); | |
| uvC.fromBufferAttribute( uvAttribute, c ); | |
| vB.sub( vA ); | |
| vC.sub( vA ); | |
| uvB.sub( uvA ); | |
| uvC.sub( uvA ); | |
| const r = 1.0 / ( uvB.x * uvC.y - uvC.x * uvB.y ); | |
| // silently ignore degenerate uv triangles having coincident or colinear vertices | |
| if ( ! isFinite( r ) ) return; | |
| sdir.copy( vB ).multiplyScalar( uvC.y ).addScaledVector( vC, - uvB.y ).multiplyScalar( r ); | |
| tdir.copy( vC ).multiplyScalar( uvB.x ).addScaledVector( vB, - uvC.x ).multiplyScalar( r ); | |
| tan1[ a ].add( sdir ); | |
| tan1[ b ].add( sdir ); | |
| tan1[ c ].add( sdir ); | |
| tan2[ a ].add( tdir ); | |
| tan2[ b ].add( tdir ); | |
| tan2[ c ].add( tdir ); | |
| } | |
| let groups = this.groups; | |
| if ( groups.length === 0 ) { | |
| groups = [ { | |
| start: 0, | |
| count: index.count | |
| } ]; | |
| } | |
| for ( let i = 0, il = groups.length; i < il; ++ i ) { | |
| const group = groups[ i ]; | |
| const start = group.start; | |
| const count = group.count; | |
| for ( let j = start, jl = start + count; j < jl; j += 3 ) { | |
| handleTriangle( | |
| index.getX( j + 0 ), | |
| index.getX( j + 1 ), | |
| index.getX( j + 2 ) | |
| ); | |
| } | |
| } | |
| const tmp = new Vector3(), tmp2 = new Vector3(); | |
| const n = new Vector3(), n2 = new Vector3(); | |
| function handleVertex( v ) { | |
| n.fromBufferAttribute( normalAttribute, v ); | |
| n2.copy( n ); | |
| const t = tan1[ v ]; | |
| // Gram-Schmidt orthogonalize | |
| tmp.copy( t ); | |
| tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize(); | |
| // Calculate handedness | |
| tmp2.crossVectors( n2, t ); | |
| const test = tmp2.dot( tan2[ v ] ); | |
| const w = ( test < 0.0 ) ? - 1.0 : 1.0; | |
| tangentAttribute.setXYZW( v, tmp.x, tmp.y, tmp.z, w ); | |
| } | |
| for ( let i = 0, il = groups.length; i < il; ++ i ) { | |
| const group = groups[ i ]; | |
| const start = group.start; | |
| const count = group.count; | |
| for ( let j = start, jl = start + count; j < jl; j += 3 ) { | |
| handleVertex( index.getX( j + 0 ) ); | |
| handleVertex( index.getX( j + 1 ) ); | |
| handleVertex( index.getX( j + 2 ) ); | |
| } | |
| } | |
| } | |
| /** | |
| * Computes vertex normals for the given vertex data. For indexed geometries, the method sets | |
| * each vertex normal to be the average of the face normals of the faces that share that vertex. | |
| * For non-indexed geometries, vertices are not shared, and the method sets each vertex normal | |
| * to be the same as the face normal. | |
| */ | |
| computeVertexNormals() { | |
| const index = this.index; | |
| const positionAttribute = this.getAttribute( 'position' ); | |
| if ( positionAttribute !== undefined ) { | |
| let normalAttribute = this.getAttribute( 'normal' ); | |
| if ( normalAttribute === undefined ) { | |
| normalAttribute = new BufferAttribute( new Float32Array( positionAttribute.count * 3 ), 3 ); | |
| this.setAttribute( 'normal', normalAttribute ); | |
| } else { | |
| // reset existing normals to zero | |
| for ( let i = 0, il = normalAttribute.count; i < il; i ++ ) { | |
| normalAttribute.setXYZ( i, 0, 0, 0 ); | |
| } | |
| } | |
| const pA = new Vector3(), pB = new Vector3(), pC = new Vector3(); | |
| const nA = new Vector3(), nB = new Vector3(), nC = new Vector3(); | |
| const cb = new Vector3(), ab = new Vector3(); | |
| // indexed elements | |
| if ( index ) { | |
| for ( let i = 0, il = index.count; i < il; i += 3 ) { | |
| const vA = index.getX( i + 0 ); | |
| const vB = index.getX( i + 1 ); | |
| const vC = index.getX( i + 2 ); | |
| pA.fromBufferAttribute( positionAttribute, vA ); | |
| pB.fromBufferAttribute( positionAttribute, vB ); | |
| pC.fromBufferAttribute( positionAttribute, vC ); | |
| cb.subVectors( pC, pB ); | |
| ab.subVectors( pA, pB ); | |
| cb.cross( ab ); | |
| nA.fromBufferAttribute( normalAttribute, vA ); | |
| nB.fromBufferAttribute( normalAttribute, vB ); | |
| nC.fromBufferAttribute( normalAttribute, vC ); | |
| nA.add( cb ); | |
| nB.add( cb ); | |
| nC.add( cb ); | |
| normalAttribute.setXYZ( vA, nA.x, nA.y, nA.z ); | |
| normalAttribute.setXYZ( vB, nB.x, nB.y, nB.z ); | |
| normalAttribute.setXYZ( vC, nC.x, nC.y, nC.z ); | |
| } | |
| } else { | |
| // non-indexed elements (unconnected triangle soup) | |
| for ( let i = 0, il = positionAttribute.count; i < il; i += 3 ) { | |
| pA.fromBufferAttribute( positionAttribute, i + 0 ); | |
| pB.fromBufferAttribute( positionAttribute, i + 1 ); | |
| pC.fromBufferAttribute( positionAttribute, i + 2 ); | |
| cb.subVectors( pC, pB ); | |
| ab.subVectors( pA, pB ); | |
| cb.cross( ab ); | |
| normalAttribute.setXYZ( i + 0, cb.x, cb.y, cb.z ); | |
| normalAttribute.setXYZ( i + 1, cb.x, cb.y, cb.z ); | |
| normalAttribute.setXYZ( i + 2, cb.x, cb.y, cb.z ); | |
| } | |
| } | |
| this.normalizeNormals(); | |
| normalAttribute.needsUpdate = true; | |
| } | |
| } | |
| /** | |
| * Ensures every normal vector in a geometry will have a magnitude of `1`. This will | |
| * correct lighting on the geometry surfaces. | |
| */ | |
| normalizeNormals() { | |
| const normals = this.attributes.normal; | |
| for ( let i = 0, il = normals.count; i < il; i ++ ) { | |
| _vector.fromBufferAttribute( normals, i ); | |
| _vector.normalize(); | |
| normals.setXYZ( i, _vector.x, _vector.y, _vector.z ); | |
| } | |
| } | |
| /** | |
| * Return a new non-index version of this indexed geometry. If the geometry | |
| * is already non-indexed, the method is a NOOP. | |
| * | |
| * @return {BufferGeometry} The non-indexed version of this indexed geometry. | |
| */ | |
| toNonIndexed() { | |
| function convertBufferAttribute( attribute, indices ) { | |
| const array = attribute.array; | |
| const itemSize = attribute.itemSize; | |
| const normalized = attribute.normalized; | |
| const array2 = new array.constructor( indices.length * itemSize ); | |
| let index = 0, index2 = 0; | |
| for ( let i = 0, l = indices.length; i < l; i ++ ) { | |
| if ( attribute.isInterleavedBufferAttribute ) { | |
| index = indices[ i ] * attribute.data.stride + attribute.offset; | |
| } else { | |
| index = indices[ i ] * itemSize; | |
| } | |
| for ( let j = 0; j < itemSize; j ++ ) { | |
| array2[ index2 ++ ] = array[ index ++ ]; | |
| } | |
| } | |
| return new BufferAttribute( array2, itemSize, normalized ); | |
| } | |
| // | |
| if ( this.index === null ) { | |
| console.warn( 'THREE.BufferGeometry.toNonIndexed(): BufferGeometry is already non-indexed.' ); | |
| return this; | |
| } | |
| const geometry2 = new BufferGeometry(); | |
| const indices = this.index.array; | |
| const attributes = this.attributes; | |
| // attributes | |
| for ( const name in attributes ) { | |
| const attribute = attributes[ name ]; | |
| const newAttribute = convertBufferAttribute( attribute, indices ); | |
| geometry2.setAttribute( name, newAttribute ); | |
| } | |
| // morph attributes | |
| const morphAttributes = this.morphAttributes; | |
| for ( const name in morphAttributes ) { | |
| const morphArray = []; | |
| const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes | |
| for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) { | |
| const attribute = morphAttribute[ i ]; | |
| const newAttribute = convertBufferAttribute( attribute, indices ); | |
| morphArray.push( newAttribute ); | |
| } | |
| geometry2.morphAttributes[ name ] = morphArray; | |
| } | |
| geometry2.morphTargetsRelative = this.morphTargetsRelative; | |
| // groups | |
| const groups = this.groups; | |
| for ( let i = 0, l = groups.length; i < l; i ++ ) { | |
| const group = groups[ i ]; | |
| geometry2.addGroup( group.start, group.count, group.materialIndex ); | |
| } | |
| return geometry2; | |
| } | |
| /** | |
| * Serializes the geometry into JSON. | |
| * | |
| * @return {Object} A JSON object representing the serialized geometry. | |
| */ | |
| toJSON() { | |
| const data = { | |
| metadata: { | |
| version: 4.6, | |
| type: 'BufferGeometry', | |
| generator: 'BufferGeometry.toJSON' | |
| } | |
| }; | |
| // standard BufferGeometry serialization | |
| data.uuid = this.uuid; | |
| data.type = this.type; | |
| if ( this.name !== '' ) data.name = this.name; | |
| if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; | |
| if ( this.parameters !== undefined ) { | |
| const parameters = this.parameters; | |
| for ( const key in parameters ) { | |
| if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; | |
| } | |
| return data; | |
| } | |
| // for simplicity the code assumes attributes are not shared across geometries, see #15811 | |
| data.data = { attributes: {} }; | |
| const index = this.index; | |
| if ( index !== null ) { | |
| data.data.index = { | |
| type: index.array.constructor.name, | |
| array: Array.prototype.slice.call( index.array ) | |
| }; | |
| } | |
| const attributes = this.attributes; | |
| for ( const key in attributes ) { | |
| const attribute = attributes[ key ]; | |
| data.data.attributes[ key ] = attribute.toJSON( data.data ); | |
| } | |
| const morphAttributes = {}; | |
| let hasMorphAttributes = false; | |
| for ( const key in this.morphAttributes ) { | |
| const attributeArray = this.morphAttributes[ key ]; | |
| const array = []; | |
| for ( let i = 0, il = attributeArray.length; i < il; i ++ ) { | |
| const attribute = attributeArray[ i ]; | |
| array.push( attribute.toJSON( data.data ) ); | |
| } | |
| if ( array.length > 0 ) { | |
| morphAttributes[ key ] = array; | |
| hasMorphAttributes = true; | |
| } | |
| } | |
| if ( hasMorphAttributes ) { | |
| data.data.morphAttributes = morphAttributes; | |
| data.data.morphTargetsRelative = this.morphTargetsRelative; | |
| } | |
| const groups = this.groups; | |
| if ( groups.length > 0 ) { | |
| data.data.groups = JSON.parse( JSON.stringify( groups ) ); | |
| } | |
| const boundingSphere = this.boundingSphere; | |
| if ( boundingSphere !== null ) { | |
| data.data.boundingSphere = { | |
| center: boundingSphere.center.toArray(), | |
| radius: boundingSphere.radius | |
| }; | |
| } | |
| return data; | |
| } | |
| /** | |
| * Returns a new geometry with copied values from this instance. | |
| * | |
| * @return {BufferGeometry} A clone of this instance. | |
| */ | |
| clone() { | |
| return new this.constructor().copy( this ); | |
| } | |
| /** | |
| * Copies the values of the given geometry to this instance. | |
| * | |
| * @param {BufferGeometry} source - The geometry to copy. | |
| * @return {BufferGeometry} A reference to this instance. | |
| */ | |
| copy( source ) { | |
| // reset | |
| this.index = null; | |
| this.attributes = {}; | |
| this.morphAttributes = {}; | |
| this.groups = []; | |
| this.boundingBox = null; | |
| this.boundingSphere = null; | |
| // used for storing cloned, shared data | |
| const data = {}; | |
| // name | |
| this.name = source.name; | |
| // index | |
| const index = source.index; | |
| if ( index !== null ) { | |
| this.setIndex( index.clone() ); | |
| } | |
| // attributes | |
| const attributes = source.attributes; | |
| for ( const name in attributes ) { | |
| const attribute = attributes[ name ]; | |
| this.setAttribute( name, attribute.clone( data ) ); | |
| } | |
| // morph attributes | |
| const morphAttributes = source.morphAttributes; | |
| for ( const name in morphAttributes ) { | |
| const array = []; | |
| const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes | |
| for ( let i = 0, l = morphAttribute.length; i < l; i ++ ) { | |
| array.push( morphAttribute[ i ].clone( data ) ); | |
| } | |
| this.morphAttributes[ name ] = array; | |
| } | |
| this.morphTargetsRelative = source.morphTargetsRelative; | |
| // groups | |
| const groups = source.groups; | |
| for ( let i = 0, l = groups.length; i < l; i ++ ) { | |
| const group = groups[ i ]; | |
| this.addGroup( group.start, group.count, group.materialIndex ); | |
| } | |
| // bounding box | |
| const boundingBox = source.boundingBox; | |
| if ( boundingBox !== null ) { | |
| this.boundingBox = boundingBox.clone(); | |
| } | |
| // bounding sphere | |
| const boundingSphere = source.boundingSphere; | |
| if ( boundingSphere !== null ) { | |
| this.boundingSphere = boundingSphere.clone(); | |
| } | |
| // draw range | |
| this.drawRange.start = source.drawRange.start; | |
| this.drawRange.count = source.drawRange.count; | |
| // user data | |
| this.userData = source.userData; | |
| return this; | |
| } | |
| /** | |
| * Frees the GPU-related resources allocated by this instance. Call this | |
| * method whenever this instance is no longer used in your app. | |
| * | |
| * @fires BufferGeometry#dispose | |
| */ | |
| dispose() { | |
| this.dispatchEvent( { type: 'dispose' } ); | |
| } | |
| } | |
| export { BufferGeometry }; | |
Xet Storage Details
- Size:
- 33.2 kB
- Xet hash:
- 03474581701c8307937f3e8cf1af2b8efdce764c3858b856518f79d5c59175ff
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.