UNPKG

three

Version:

JavaScript 3D library

1,224 lines (675 loc) 24.4 kB
import { Vector3 } from '../math/Vector3.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 { DirectGeometry } from './DirectGeometry.js'; import { Object3D } from './Object3D.js'; import { Matrix4 } from '../math/Matrix4.js'; import { Matrix3 } from '../math/Matrix3.js'; import { MathUtils } from '../math/MathUtils.js'; import { arrayMax } from '../utils.js'; let _bufferGeometryId = 1; // BufferGeometry uses odd numbers as Id const _m1 = new Matrix4(); const _obj = new Object3D(); const _offset = new Vector3(); const _box = new Box3(); const _boxMorphTargets = new Box3(); const _vector = new Vector3(); function BufferGeometry() { Object.defineProperty( this, 'id', { value: _bufferGeometryId += 2 } ); this.uuid = MathUtils.generateUUID(); this.name = ''; this.type = 'BufferGeometry'; this.index = null; this.attributes = {}; this.morphAttributes = {}; this.morphTargetsRelative = false; this.groups = []; this.boundingBox = null; this.boundingSphere = null; this.drawRange = { start: 0, count: Infinity }; this.userData = {}; } BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototype ), { constructor: BufferGeometry, isBufferGeometry: true, getIndex: function () { return this.index; }, setIndex: function ( index ) { if ( Array.isArray( index ) ) { this.index = new ( arrayMax( index ) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 ); } else { this.index = index; } }, getAttribute: function ( name ) { return this.attributes[ name ]; }, setAttribute: function ( name, attribute ) { this.attributes[ name ] = attribute; return this; }, deleteAttribute: function ( name ) { delete this.attributes[ name ]; return this; }, addGroup: function ( start, count, materialIndex ) { this.groups.push( { start: start, count: count, materialIndex: materialIndex !== undefined ? materialIndex : 0 } ); }, clearGroups: function () { this.groups = []; }, setDrawRange: function ( start, count ) { this.drawRange.start = start; this.drawRange.count = count; }, applyMatrix4: function ( 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; }, rotateX: function ( angle ) { // rotate geometry around world x-axis _m1.makeRotationX( angle ); this.applyMatrix4( _m1 ); return this; }, rotateY: function ( angle ) { // rotate geometry around world y-axis _m1.makeRotationY( angle ); this.applyMatrix4( _m1 ); return this; }, rotateZ: function ( angle ) { // rotate geometry around world z-axis _m1.makeRotationZ( angle ); this.applyMatrix4( _m1 ); return this; }, translate: function ( x, y, z ) { // translate geometry _m1.makeTranslation( x, y, z ); this.applyMatrix4( _m1 ); return this; }, scale: function ( x, y, z ) { // scale geometry _m1.makeScale( x, y, z ); this.applyMatrix4( _m1 ); return this; }, lookAt: function ( vector ) { _obj.lookAt( vector ); _obj.updateMatrix(); this.applyMatrix4( _obj.matrix ); return this; }, center: function () { this.computeBoundingBox(); this.boundingBox.getCenter( _offset ).negate(); this.translate( _offset.x, _offset.y, _offset.z ); return this; }, setFromObject: function ( object ) { // console.log( 'THREE.BufferGeometry.setFromObject(). Converting', object, this ); const geometry = object.geometry; if ( object.isPoints || object.isLine ) { const positions = new Float32BufferAttribute( geometry.vertices.length * 3, 3 ); const colors = new Float32BufferAttribute( geometry.colors.length * 3, 3 ); this.setAttribute( 'position', positions.copyVector3sArray( geometry.vertices ) ); this.setAttribute( 'color', colors.copyColorsArray( geometry.colors ) ); if ( geometry.lineDistances && geometry.lineDistances.length === geometry.vertices.length ) { const lineDistances = new Float32BufferAttribute( geometry.lineDistances.length, 1 ); this.setAttribute( 'lineDistance', lineDistances.copyArray( geometry.lineDistances ) ); } if ( geometry.boundingSphere !== null ) { this.boundingSphere = geometry.boundingSphere.clone(); } if ( geometry.boundingBox !== null ) { this.boundingBox = geometry.boundingBox.clone(); } } else if ( object.isMesh ) { if ( geometry && geometry.isGeometry ) { this.fromGeometry( geometry ); } } return this; }, setFromPoints: function ( points ) { 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 ) ); return this; }, updateFromObject: function ( object ) { let geometry = object.geometry; if ( object.isMesh ) { let direct = geometry.__directGeometry; if ( geometry.elementsNeedUpdate === true ) { direct = undefined; geometry.elementsNeedUpdate = false; } if ( direct === undefined ) { return this.fromGeometry( geometry ); } direct.verticesNeedUpdate = geometry.verticesNeedUpdate; direct.normalsNeedUpdate = geometry.normalsNeedUpdate; direct.colorsNeedUpdate = geometry.colorsNeedUpdate; direct.uvsNeedUpdate = geometry.uvsNeedUpdate; direct.groupsNeedUpdate = geometry.groupsNeedUpdate; geometry.verticesNeedUpdate = false; geometry.normalsNeedUpdate = false; geometry.colorsNeedUpdate = false; geometry.uvsNeedUpdate = false; geometry.groupsNeedUpdate = false; geometry = direct; } if ( geometry.verticesNeedUpdate === true ) { const attribute = this.attributes.position; if ( attribute !== undefined ) { attribute.copyVector3sArray( geometry.vertices ); attribute.needsUpdate = true; } geometry.verticesNeedUpdate = false; } if ( geometry.normalsNeedUpdate === true ) { const attribute = this.attributes.normal; if ( attribute !== undefined ) { attribute.copyVector3sArray( geometry.normals ); attribute.needsUpdate = true; } geometry.normalsNeedUpdate = false; } if ( geometry.colorsNeedUpdate === true ) { const attribute = this.attributes.color; if ( attribute !== undefined ) { attribute.copyColorsArray( geometry.colors ); attribute.needsUpdate = true; } geometry.colorsNeedUpdate = false; } if ( geometry.uvsNeedUpdate ) { const attribute = this.attributes.uv; if ( attribute !== undefined ) { attribute.copyVector2sArray( geometry.uvs ); attribute.needsUpdate = true; } geometry.uvsNeedUpdate = false; } if ( geometry.lineDistancesNeedUpdate ) { const attribute = this.attributes.lineDistance; if ( attribute !== undefined ) { attribute.copyArray( geometry.lineDistances ); attribute.needsUpdate = true; } geometry.lineDistancesNeedUpdate = false; } if ( geometry.groupsNeedUpdate ) { geometry.computeGroups( object.geometry ); this.groups = geometry.groups; geometry.groupsNeedUpdate = false; } return this; }, fromGeometry: function ( geometry ) { geometry.__directGeometry = new DirectGeometry().fromGeometry( geometry ); return this.fromDirectGeometry( geometry.__directGeometry ); }, fromDirectGeometry: function ( geometry ) { const positions = new Float32Array( geometry.vertices.length * 3 ); this.setAttribute( 'position', new BufferAttribute( positions, 3 ).copyVector3sArray( geometry.vertices ) ); if ( geometry.normals.length > 0 ) { const normals = new Float32Array( geometry.normals.length * 3 ); this.setAttribute( 'normal', new BufferAttribute( normals, 3 ).copyVector3sArray( geometry.normals ) ); } if ( geometry.colors.length > 0 ) { const colors = new Float32Array( geometry.colors.length * 3 ); this.setAttribute( 'color', new BufferAttribute( colors, 3 ).copyColorsArray( geometry.colors ) ); } if ( geometry.uvs.length > 0 ) { const uvs = new Float32Array( geometry.uvs.length * 2 ); this.setAttribute( 'uv', new BufferAttribute( uvs, 2 ).copyVector2sArray( geometry.uvs ) ); } if ( geometry.uvs2.length > 0 ) { const uvs2 = new Float32Array( geometry.uvs2.length * 2 ); this.setAttribute( 'uv2', new BufferAttribute( uvs2, 2 ).copyVector2sArray( geometry.uvs2 ) ); } // groups this.groups = geometry.groups; // morphs for ( const name in geometry.morphTargets ) { const array = []; const morphTargets = geometry.morphTargets[ name ]; for ( let i = 0, l = morphTargets.length; i < l; i ++ ) { const morphTarget = morphTargets[ i ]; const attribute = new Float32BufferAttribute( morphTarget.data.length * 3, 3 ); attribute.name = morphTarget.name; array.push( attribute.copyVector3sArray( morphTarget.data ) ); } this.morphAttributes[ name ] = array; } // skinning if ( geometry.skinIndices.length > 0 ) { const skinIndices = new Float32BufferAttribute( geometry.skinIndices.length * 4, 4 ); this.setAttribute( 'skinIndex', skinIndices.copyVector4sArray( geometry.skinIndices ) ); } if ( geometry.skinWeights.length > 0 ) { const skinWeights = new Float32BufferAttribute( geometry.skinWeights.length * 4, 4 ); this.setAttribute( 'skinWeight', skinWeights.copyVector4sArray( geometry.skinWeights ) ); } // if ( geometry.boundingSphere !== null ) { this.boundingSphere = geometry.boundingSphere.clone(); } if ( geometry.boundingBox !== null ) { this.boundingBox = geometry.boundingBox.clone(); } return this; }, computeBoundingBox: function () { if ( this.boundingBox === null ) { this.boundingBox = new Box3(); } const position = this.attributes.position; const morphAttributesPosition = this.morphAttributes.position; 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 ); } }, computeBoundingSphere: function () { if ( this.boundingSphere === null ) { this.boundingSphere = new Sphere(); } const position = this.attributes.position; const morphAttributesPosition = this.morphAttributes.position; 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 ); } } }, computeFaceNormals: function () { // backwards compatibility }, computeVertexNormals: function () { 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; } }, merge: function ( geometry, offset ) { if ( ! ( geometry && geometry.isBufferGeometry ) ) { console.error( 'THREE.BufferGeometry.merge(): geometry not an instance of THREE.BufferGeometry.', geometry ); return; } if ( offset === undefined ) { offset = 0; console.warn( 'THREE.BufferGeometry.merge(): Overwriting original geometry, starting at offset=0. ' + 'Use BufferGeometryUtils.mergeBufferGeometries() for lossless merge.' ); } const attributes = this.attributes; for ( const key in attributes ) { if ( geometry.attributes[ key ] === undefined ) continue; const attribute1 = attributes[ key ]; const attributeArray1 = attribute1.array; const attribute2 = geometry.attributes[ key ]; const attributeArray2 = attribute2.array; const attributeOffset = attribute2.itemSize * offset; const length = Math.min( attributeArray2.length, attributeArray1.length - attributeOffset ); for ( let i = 0, j = attributeOffset; i < length; i ++, j ++ ) { attributeArray1[ j ] = attributeArray2[ i ]; } } return this; }, normalizeNormals: function () { 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 ); } }, toNonIndexed: function () { 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 ++ ) { 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(): Geometry 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; }, toJSON: function () { const data = { metadata: { version: 4.5, 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; } 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 ]; const attributeData = attribute.toJSON( data.data ); if ( attribute.name !== '' ) attributeData.name = attribute.name; data.data.attributes[ key ] = attributeData; } 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 ]; const attributeData = attribute.toJSON( data.data ); if ( attribute.name !== '' ) attributeData.name = attribute.name; array.push( attributeData ); } 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; }, clone: function () { /* // Handle primitives const parameters = this.parameters; if ( parameters !== undefined ) { const values = []; for ( const key in parameters ) { values.push( parameters[ key ] ); } const geometry = Object.create( this.constructor.prototype ); this.constructor.apply( geometry, values ); return geometry; } return new this.constructor().copy( this ); */ return new BufferGeometry().copy( this ); }, copy: function ( 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( data ) ); } // 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; }, dispose: function () { this.dispatchEvent( { type: 'dispose' } ); } } ); export { BufferGeometry };