three
Version:
JavaScript 3D library
140 lines (91 loc) • 4.43 kB
JavaScript
import {
BufferAttribute,
BufferGeometry
} from 'three';
import { MeshoptSimplifier } from '../libs/meshopt_simplifier.module.js';
import { mergeVertices } from '../utils/BufferGeometryUtils.js';
/**
* This class can be used to modify a geometry by simplifying it. A typical use
* case for such a modifier is automatic LOD generation.
*
* The implementation is based on [meshoptimizer]{@link https://github.com/zeux/meshoptimizer}.
* If you only need a simplified index buffer, use {@link MeshoptSimplifier} directly.
*
* ```js
* const modifier = new SimplifyModifier();
* geometry = await modifier.modify( geometry, count );
* ```
*
* @three_import import { SimplifyModifier } from 'three/addons/modifiers/SimplifyModifier.js';
*/
class SimplifyModifier {
/**
* Returns a new, simplified version of the given geometry. The vertex buffers
* of the result only contain vertices referenced by the simplified index.
*
* @async
* @param {BufferGeometry} geometry - The geometry to modify.
* @param {number} count - The approximate number of vertices to remove.
* @return {Promise<BufferGeometry>} A promise that resolves with the new, modified geometry.
*/
async modify( geometry, count ) {
await MeshoptSimplifier.ready;
// non-indexed geometries must be welded so the simplifier can collapse edges
if ( geometry.getIndex() === null ) geometry = mergeVertices( geometry );
const index = geometry.getIndex();
const positionAttribute = geometry.getAttribute( 'position' );
// the simplifier requires tightly packed positions
const positions = new Float32Array( positionAttribute.count * 3 );
for ( let i = 0; i < positionAttribute.count; i ++ ) {
positions[ i * 3 + 0 ] = positionAttribute.getX( i );
positions[ i * 3 + 1 ] = positionAttribute.getY( i );
positions[ i * 3 + 2 ] = positionAttribute.getZ( i );
}
const ratio = Math.min( 1, Math.max( 0, 1 - count / positionAttribute.count ) );
const targetIndexCount = Math.max( 3, Math.floor( index.count * ratio / 3 ) * 3 );
// normals and uvs, when present, contribute to the error metric so collapses
// that would distort shading or texture seams are penalized
const normalAttribute = geometry.getAttribute( 'normal' );
const uvAttribute = geometry.getAttribute( 'uv' );
const attributeStride = ( normalAttribute ? 3 : 0 ) + ( uvAttribute ? 2 : 0 );
let indices;
if ( attributeStride > 0 ) {
const vertexAttributes = new Float32Array( positionAttribute.count * attributeStride );
const attributeWeights = [];
if ( normalAttribute ) attributeWeights.push( 0.25, 0.25, 0.25 );
if ( uvAttribute ) attributeWeights.push( 0.5, 0.5 );
for ( let i = 0, o = 0; i < positionAttribute.count; i ++ ) {
if ( normalAttribute ) {
vertexAttributes[ o ++ ] = normalAttribute.getX( i );
vertexAttributes[ o ++ ] = normalAttribute.getY( i );
vertexAttributes[ o ++ ] = normalAttribute.getZ( i );
}
if ( uvAttribute ) {
vertexAttributes[ o ++ ] = uvAttribute.getX( i );
vertexAttributes[ o ++ ] = uvAttribute.getY( i );
}
}
indices = MeshoptSimplifier.simplifyWithAttributes( index.array, positions, 3, vertexAttributes, attributeStride, attributeWeights, null, targetIndexCount, 1 )[ 0 ];
} else {
indices = MeshoptSimplifier.simplify( index.array, positions, 3, targetIndexCount, 1 )[ 0 ];
}
// drop vertices that are no longer referenced and rebuild the attributes
const [ remap, unique ] = MeshoptSimplifier.compactMesh( indices ); // rewrites indices in place
const simplifiedGeometry = new BufferGeometry();
for ( const name in geometry.attributes ) {
const attribute = geometry.getAttribute( name );
const itemSize = attribute.itemSize;
const newAttribute = new BufferAttribute( new attribute.array.constructor( unique * itemSize ), itemSize, attribute.normalized );
// scatter each vertex to its new slot; 0xffffffff marks unreferenced vertices
for ( let i = 0; i < remap.length; i ++ ) {
const target = remap[ i ];
if ( target === 0xffffffff ) continue;
for ( let k = 0; k < itemSize; k ++ ) newAttribute.setComponent( target, k, attribute.getComponent( i, k ) );
}
simplifiedGeometry.setAttribute( name, newAttribute );
}
simplifiedGeometry.setIndex( new BufferAttribute( indices, 1 ) );
return simplifiedGeometry;
}
}
export { SimplifyModifier };