3d-tiles-renderer
Version:
https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification
230 lines (152 loc) • 5.45 kB
JavaScript
import { Vector3, LinearFilter, BufferAttribute, MathUtils } from 'three';
const _vec = new Vector3();
function compressAttribute( attribute, arrayType ) {
if ( attribute.isInterleavedBufferAttribute || attribute.array instanceof arrayType ) {
return attribute;
}
const signed = arrayType === Int8Array || arrayType === Int16Array || arrayType === Int32Array;
const minValue = signed ? - 1 : 0;
const array = new arrayType( attribute.count * attribute.itemSize );
const newAttribute = new BufferAttribute( array, attribute.itemSize, true );
const itemSize = attribute.itemSize;
const count = attribute.count;
for ( let i = 0; i < count; i ++ ) {
for ( let j = 0; j < itemSize; j ++ ) {
const v = MathUtils.clamp( attribute.getComponent( i, j ), minValue, 1 );
newAttribute.setComponent( i, j, v );
}
}
return newAttribute;
}
function compressPositionAttribute( mesh, arrayType = Int16Array ) {
const geometry = mesh.geometry;
const attributes = geometry.attributes;
const attribute = attributes.position;
// skip if it's already compressed to the provided level
if ( attribute.isInterleavedBufferAttribute || attribute.array instanceof arrayType ) {
return attribute;
}
// new attribute data
const array = new arrayType( attribute.count * attribute.itemSize );
const newAttribute = new BufferAttribute( array, attribute.itemSize, false );
const itemSize = attribute.itemSize;
const count = attribute.count;
// bounding box stride
// TODO: the bounding box is computed every time even if it already exists because
// it's possible that the encoded value is incorrect causing artifacts
geometry.computeBoundingBox();
const boundingBox = geometry.boundingBox;
const { min, max } = boundingBox;
// array range
const maxValue = 2 ** ( 8 * arrayType.BYTES_PER_ELEMENT - 1 ) - 1;
const minValue = - maxValue;
for ( let i = 0; i < count; i ++ ) {
for ( let j = 0; j < itemSize; j ++ ) {
const key = j === 0 ? 'x' : j === 1 ? 'y' : 'z';
const bbMinValue = min[ key ];
const bbMaxValue = max[ key ];
// scale the geometry values to the integer range
const v = MathUtils.mapLinear(
attribute.getComponent( i, j ),
bbMinValue, bbMaxValue,
minValue, maxValue,
);
newAttribute.setComponent( i, j, v );
}
}
// shift the mesh to the center of the bounds
boundingBox
.getCenter( _vec )
.multiply( mesh.scale )
.applyQuaternion( mesh.quaternion );
mesh.position.add( _vec );
// adjust the scale to accommodate the new geometry data range
mesh.scale.x *= 0.5 * ( max.x - min.x ) / maxValue;
mesh.scale.y *= 0.5 * ( max.y - min.y ) / maxValue;
mesh.scale.z *= 0.5 * ( max.z - min.z ) / maxValue;
attributes.position = newAttribute;
mesh.geometry.boundingBox = null;
mesh.geometry.boundingSphere = null;
mesh.updateMatrixWorld();
}
export class TileCompressionPlugin {
constructor( options ) {
this._options = {
// whether to generate normals if they don't already exist.
generateNormals: false,
// whether to disable use of mipmaps since they are typically not necessary
// with something like 3d tiles.
disableMipmaps: true,
// whether to compress certain attributes
compressIndex: true,
compressNormals: false,
compressUvs: false,
compressPosition: false,
// the TypedArray type to use when compressing the attributes
uvType: Int8Array,
normalType: Int8Array,
positionType: Int16Array,
...options,
};
this.name = 'TILES_COMPRESSION_PLUGIN';
this.priority = - 100;
}
processTileModel( scene, tile ) {
const {
generateNormals,
disableMipmaps,
compressIndex,
compressUvs,
compressNormals,
compressPosition,
uvType,
normalType,
positionType,
} = this._options;
scene.traverse( c => {
// handle materials
if ( c.material && disableMipmaps ) {
const material = c.material;
for ( const key in material ) {
const value = material[ key ];
if ( value && value.isTexture && value.generateMipmaps ) {
value.generateMipmaps = false;
value.minFilter = LinearFilter;
}
}
}
// handle geometry attribute compression
if ( c.geometry ) {
const geometry = c.geometry;
const attributes = geometry.attributes;
if ( compressUvs ) {
const { uv, uv1, uv2, uv3 } = attributes;
if ( uv ) attributes.uv = compressAttribute( uv, uvType );
if ( uv1 ) attributes.uv1 = compressAttribute( uv1, uvType );
if ( uv2 ) attributes.uv2 = compressAttribute( uv2, uvType );
if ( uv3 ) attributes.uv3 = compressAttribute( uv3, uvType );
}
if ( generateNormals && ! attributes.normals ) {
geometry.computeVertexNormals();
}
if ( compressNormals && attributes.normals ) {
attributes.normals = compressAttribute( attributes.normals, normalType );
}
if ( compressPosition ) {
compressPositionAttribute( c, positionType );
}
if ( compressIndex && geometry.index ) {
const vertCount = attributes.position.count;
const index = geometry.index;
const type = vertCount > 65535 ? Uint32Array : vertCount > 255 ? Uint16Array : Uint8Array;
if ( ! ( index.array instanceof type ) ) {
const array = new type( geometry.index.count );
array.set( index.array );
const attribute = new BufferAttribute( array, 1 );
geometry.setIndex( attribute );
}
}
}
} );
}
}