three
Version:
JavaScript 3D library
452 lines (303 loc) • 9.98 kB
JavaScript
import DataMap from './DataMap.js';
import { AttributeType } from './Constants.js';
import { Uint16BufferAttribute, Uint32BufferAttribute } from '../../core/BufferAttribute.js';
/**
* Returns the wireframe version for the given geometry.
*
* @private
* @function
* @param {BufferGeometry} geometry - The geometry.
* @return {number} The version.
*/
function getWireframeVersion( geometry ) {
return ( geometry.index !== null ) ? geometry.index.version : geometry.attributes.position.version;
}
/**
* Returns the wireframe ID for the given geometry.
*
* @private
* @function
* @param {BufferGeometry} geometry - The geometry.
* @return {number} The ID.
*/
function getWireframeId( geometry ) {
return ( geometry.index !== null ) ? geometry.index.id : geometry.attributes.position.id;
}
/**
* Returns a wireframe index attribute for the given geometry.
*
* @private
* @function
* @param {BufferGeometry} geometry - The geometry.
* @return {BufferAttribute} The wireframe index attribute.
*/
function getWireframeIndex( geometry ) {
const geometryIndex = geometry.index;
const geometryPosition = geometry.attributes.position;
const count = geometryIndex !== null ? geometryIndex.array.length : ( geometryPosition.array.length / 3 ) - 1;
const IndexBufferAttribute = geometryPosition.count >= 65535 ? Uint32BufferAttribute : Uint16BufferAttribute;
const attribute = new IndexBufferAttribute( Math.ceil( count / 3 ) * 6, 1 );
const indices = attribute.array;
if ( geometryIndex !== null ) {
const array = geometryIndex.array;
for ( let i = 0, j = 0; i < count; i += 3 ) {
const a = array[ i + 0 ];
const b = array[ i + 1 ];
const c = array[ i + 2 ];
indices[ j ++ ] = a; indices[ j ++ ] = b;
indices[ j ++ ] = b; indices[ j ++ ] = c;
indices[ j ++ ] = c; indices[ j ++ ] = a;
}
} else {
for ( let i = 0, j = 0; i < count; i += 3 ) {
const a = i + 0;
const b = i + 1;
const c = i + 2;
indices[ j ++ ] = a; indices[ j ++ ] = b;
indices[ j ++ ] = b; indices[ j ++ ] = c;
indices[ j ++ ] = c; indices[ j ++ ] = a;
}
}
attribute.version = getWireframeVersion( geometry );
attribute.__id = getWireframeId( geometry );
return attribute;
}
/**
* This renderer module manages geometries.
*
* @private
* @augments DataMap
*/
class Geometries extends DataMap {
/**
* Constructs a new geometry management component.
*
* @param {Backend} backend - The renderer's backend.
* @param {Attributes} attributes - Renderer component for managing attributes.
* @param {Info} info - Renderer component for managing metrics and monitoring data.
*/
constructor( backend, attributes, info ) {
super();
/**
* The renderer's backend.
*
* @type {Backend}
*/
this.backend = backend;
/**
* Renderer component for managing attributes.
*
* @type {Attributes}
*/
this.attributes = attributes;
/**
* Renderer component for managing metrics and monitoring data.
*
* @type {Info}
*/
this.info = info;
/**
* Weak Map for managing attributes for wireframe rendering.
*
* @type {WeakMap<BufferGeometry,BufferAttribute>}
*/
this.wireframes = new WeakMap();
/**
* This Weak Map is used to make sure buffer attributes are
* updated only once per render call.
*
* @type {WeakMap<BufferAttribute,number>}
*/
this.attributeCall = new WeakMap();
/**
* Stores weak references to the geometries with attached
* `dispose` event listeners.
*
* @private
* @type {Set<WeakRef<BufferGeometry>>}
*/
this._tracked = new Set();
/**
* Removes weak references from `_tracked` when their geometry
* has been garbage collected without an explicit `dispose()`.
*
* @private
* @type {FinalizationRegistry}
*/
this._registry = new FinalizationRegistry( ( ref ) => this._tracked.delete( ref ) );
}
/**
* Returns `true` if the given geometry is initialized.
*
* @param {BufferGeometry} geometry - The geometry.
* @return {boolean} Whether if the given geometry is initialized or not.
*/
has( geometry ) {
return super.has( geometry ) && this.get( geometry ).initialized === true;
}
/**
* Prepares the geometry of the given render object for rendering.
*
* @param {RenderObject} renderObject - The render object.
*/
updateForRender( renderObject ) {
const geometry = renderObject.geometry;
if ( this.has( geometry ) === false ) this.initGeometry( geometry );
this.updateAttributes( renderObject );
}
/**
* Initializes the given geometry.
*
* @param {BufferGeometry} geometry - The geometry.
*/
initGeometry( geometry ) {
const geometryData = this.get( geometry );
geometryData.initialized = true;
this.info.memory.geometries ++;
geometryData.onDispose = () => {
this.info.memory.geometries --;
// index
const index = geometry.index;
if ( index !== null ) {
this.attributes.delete( index );
}
// geometry attributes
for ( const attribute of Object.values( geometry.attributes ) ) {
this.attributes.delete( this.backend.getBufferAttribute( attribute ) );
}
// wireframe attributes
const wireframeAttribute = this.wireframes.get( geometry );
if ( wireframeAttribute !== undefined ) {
this.attributes.delete( wireframeAttribute );
}
//
geometry.removeEventListener( 'dispose', geometryData.onDispose );
this._tracked.delete( geometryData.ref );
this._registry.unregister( geometryData.ref );
this.delete( geometry );
};
geometry.addEventListener( 'dispose', geometryData.onDispose );
// see #31798 why tracking separate remove listeners is required right now
geometryData.ref = new WeakRef( geometry );
this._tracked.add( geometryData.ref );
this._registry.register( geometry, geometryData.ref, geometryData.ref );
}
/**
* Updates the geometry attributes of the given render object.
*
* @param {RenderObject} renderObject - The render object.
*/
updateAttributes( renderObject ) {
// attributes
const attributes = renderObject.getAttributes();
for ( const attribute of attributes ) {
if ( attribute.isStorageBufferAttribute || attribute.isStorageInstancedBufferAttribute ) {
this.updateAttribute( attribute, AttributeType.STORAGE );
} else {
this.updateAttribute( attribute, AttributeType.VERTEX );
}
}
// indexes
const index = this.getIndex( renderObject );
if ( index !== null ) {
this.updateAttribute( index, AttributeType.INDEX );
}
// indirect
const indirect = renderObject.geometry.indirect;
if ( indirect !== null ) {
this.updateAttribute( indirect, AttributeType.INDIRECT );
}
}
/**
* Updates the given attribute.
*
* @param {BufferAttribute} attribute - The attribute to update.
* @param {number} type - The attribute type.
*/
updateAttribute( attribute, type ) {
const callId = this.info.render.calls;
const bufferAttribute = this.backend.getBufferAttribute( attribute );
if ( this.attributeCall.get( bufferAttribute ) !== callId ) {
this.attributes.update( bufferAttribute, type );
this.attributeCall.set( bufferAttribute, callId );
}
}
/**
* Returns the indirect buffer attribute of the given render object.
*
* @param {RenderObject} renderObject - The render object.
* @return {?BufferAttribute} The indirect attribute. `null` if no indirect drawing is used.
*/
getIndirect( renderObject ) {
return renderObject.geometry.indirect;
}
/**
* Returns the byte offset into the indirect attribute buffer of the given render object.
*
* @param {RenderObject} renderObject - The render object.
* @return {number} The byte offset into the indirect attribute buffer.
*/
getIndirectOffset( renderObject ) {
return renderObject.geometry.indirectOffset;
}
/**
* Returns the index of the given render object's geometry. This is implemented
* in a method to return a wireframe index if necessary.
*
* @param {RenderObject} renderObject - The render object.
* @return {?BufferAttribute} The index. Returns `null` for non-indexed geometries.
*/
getIndex( renderObject ) {
const { geometry, material } = renderObject;
let index = geometry.index;
if ( material.wireframe === true ) {
const wireframes = this.wireframes;
let wireframeAttribute = wireframes.get( geometry );
if ( wireframeAttribute === undefined ) {
wireframeAttribute = getWireframeIndex( geometry );
wireframes.set( geometry, wireframeAttribute );
} else if ( wireframeAttribute.version !== getWireframeVersion( geometry ) || wireframeAttribute.__id !== getWireframeId( geometry ) ) {
this.attributes.delete( wireframeAttribute );
wireframeAttribute = getWireframeIndex( geometry );
wireframes.set( geometry, wireframeAttribute );
}
index = wireframeAttribute;
}
return index;
}
/**
* Deletes the attributes that are defined via nodes and not on geometry level.
*
* @param {RenderObject} renderObject - The render object.
*/
deleteNodeAttributes( renderObject ) {
const currentAttributes = new Set( Object.values( renderObject.geometry.attributes ) );
for ( const attribute of renderObject.getAttributes() ) {
if ( currentAttributes.has( attribute ) === false ) {
this.attributes.delete( this.backend.getBufferAttribute( attribute ) );
}
}
}
/**
* Deletes the vertex state for the given render object.
*
* @param {RenderObject} renderObject - The render object.
*/
deleteVertexState( renderObject ) {
this.backend.deleteVertexState( renderObject );
}
/**
* Frees internal resources.
*/
dispose() {
for ( const ref of this._tracked ) {
const geometry = ref.deref();
if ( geometry === undefined ) continue;
const geometryData = this.get( geometry );
geometryData.onDispose();
}
this._tracked.clear();
super.dispose();
}
}
export default Geometries;