3d-tiles-renderer
Version:
https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification
496 lines (300 loc) • 9.84 kB
JavaScript
import {
Vector2,
Vector3,
Vector4,
Matrix2,
Matrix3,
Matrix4,
} from 'three';
// returns the field in the object with a resolved default
export function getField( object, key, def ) {
return object && key in object ? object[ key ] : def;
}
// checks the structural metadata type
export function isNumericType( type ) {
return type !== 'BOOLEAN' && type !== 'STRING' && type !== 'ENUM';
}
// check if the class property type is a float component type value
export function isFloatComponentType( type ) {
return /^FLOAT/.test( type );
}
// check if the class property type is a vector type
export function isVectorType( type ) {
return /^VEC/.test( type );
}
// check if the class property type is a matrix type
export function isMatrixType( type ) {
return /^MAT/.test( type );
}
// returns a value from the given buffer of the given type
export function readDataFromBufferToType( buffer, offset, type, target = null ) {
if ( isMatrixType( type ) ) {
return target.fromArray( buffer, offset );
} else if ( isVectorType( type ) ) {
return target.fromArray( buffer, offset );
} else {
return buffer[ offset ];
}
}
// gets a new instance of the given structural metadata type
export function getTypeInstance( property ) {
const { type, componentType } = property;
switch ( type ) {
case 'SCALAR': return componentType === 'INT64' ? 0n : 0;
case 'VEC2': return new Vector2();
case 'VEC3': return new Vector3();
case 'VEC4': return new Vector4();
case 'MAT2': return new Matrix2();
case 'MAT3': return new Matrix3();
case 'MAT4': return new Matrix4();
case 'BOOLEAN': return false;
case 'STRING': return '';
// the final value for enums is a string but are represented as integers
// during intermediate steps
case 'ENUM': return 0;
}
}
// returns false if the given value is not of "type"
export function isTypeInstance( type, value ) {
if ( value === null || value === undefined ) {
return false;
}
switch ( type ) {
case 'SCALAR': return typeof value === 'number' || typeof value === 'bigint';
case 'VEC2': return value.isVector2;
case 'VEC3': return value.isVector3;
case 'VEC4': return value.isVector4;
case 'MAT2': return value.isMatrix2;
case 'MAT3': return value.isMatrix3;
case 'MAT4': return value.isMatrix4;
case 'BOOLEAN': return typeof value === 'boolean';
case 'STRING': return typeof value === 'string';
case 'ENUM': return typeof value === 'number' || typeof value === 'bigint';
}
throw new Error( 'ClassProperty: invalid type.' );
}
// gets a new numeric array constructor from the given structural metadata type
export function getArrayConstructorFromComponentType( componentType, type = null ) {
switch ( componentType ) {
case 'INT8': return Int8Array;
case 'INT16': return Int16Array;
case 'INT32': return Int32Array;
case 'INT64': return BigInt64Array;
case 'UINT8': return Uint8Array;
case 'UINT16': return Uint16Array;
case 'UINT32': return Uint32Array;
case 'UINT64': return BigUint64Array;
case 'FLOAT32': return Float32Array;
case 'FLOAT64': return Float64Array;
}
switch ( type ) {
case 'BOOLEAN': return Uint8Array;
case 'STRING': return Uint8Array;
}
throw new Error( 'ClassProperty: invalid type.' );
}
// resolve a full default value for the given property including arrays
export function resolveDefault( property, target = null ) {
const array = property.array;
if ( array ) {
target = target && Array.isArray( target ) ? target : [];
target.length = property.count;
for ( let i = 0, l = target.length; i < l; i ++ ) {
target[ i ] = resolveDefaultElement( property, target[ i ] );
}
} else {
target = resolveDefaultElement( property, target );
}
return target;
}
// gets the default value of the given type
export function resolveDefaultElement( property, target = null ) {
const defaultValue = property.default;
const type = property.type;
target = target || getTypeInstance( property );
if ( defaultValue === null ) {
switch ( type ) {
case 'SCALAR': return 0;
case 'VEC2': return target.set( 0, 0 );
case 'VEC3': return target.set( 0, 0, 0 );
case 'VEC4': return target.set( 0, 0, 0, 0 );
case 'MAT2': return target.identity();
case 'MAT3': return target.identity();
case 'MAT4': return target.identity();
case 'BOOLEAN': return false;
case 'STRING': return '';
case 'ENUM': return '';
}
throw new Error( 'ClassProperty: invalid type.' );
} else {
if ( isMatrixType( type ) ) {
target.fromArray( defaultValue );
} else if ( isVectorType( type ) ) {
target.fromArray( defaultValue );
} else {
return defaultValue;
}
}
}
// check for of instances of "no data" in the given target value and adjust them to the
// default value.
export function resolveNoData( property, target ) {
if ( property.noData === null ) {
return target;
}
const noData = property.noData;
const type = property.type;
if ( Array.isArray( target ) ) {
for ( let i = 0, l = target.length; i < l; i ++ ) {
target[ i ] = performResolution( target[ i ] );
}
} else {
target = performResolution( target );
}
return target;
// replace the value with a default if no data is encountered
function performResolution( target ) {
if ( isNoDataEqual( target ) ) {
target = resolveDefaultElement( property, target );
}
return target;
}
// checks if the given value is equal to the no data value
function isNoDataEqual( value ) {
if ( isMatrixType( type ) ) {
const elements = value.elements;
for ( let i = 0, l = noData.length; i < l; i ++ ) {
if ( noData[ i ] !== elements[ i ] ) {
return false;
}
}
return true;
} else if ( isVectorType( type ) ) {
for ( let i = 0, l = noData.length; i < l; i ++ ) {
if ( noData[ i ] !== value.getComponent( i ) ) {
return false;
}
}
return true;
} else {
return noData === value;
}
}
}
export function normalizeValue( componentType, v ) {
// formulas defined here but normalizing 64 bit ints will result in precision loss:
// https://github.com/CesiumGS/3d-tiles/tree/main/specification/Metadata/#normalized-values
switch ( componentType ) {
case 'INT8': return Math.max( v / 127.0, - 1.0 );
case 'INT16': return Math.max( v, 32767.0, - 1.0 );
case 'INT32': return Math.max( v / 2147483647.0, - 1.0 );
case 'INT64': return Math.max( Number( v ) / 9223372036854775807.0, - 1.0 ); // eslint-disable-line no-loss-of-precision
case 'UINT8': return v / 255.0;
case 'UINT16': return v / 65535.0;
case 'UINT32': return v / 4294967295.0;
case 'UINT64': return Number( v ) / 18446744073709551615.0; // eslint-disable-line no-loss-of-precision
}
}
// scales the value based on property settings
// the provided target value is normalized, scaled, and then offset if numeric
export function adjustValueScaleOffset( property, target ) {
const {
type,
componentType,
scale,
offset,
normalized,
} = property;
if ( Array.isArray( target ) ) {
for ( let i = 0, l = target.length; i < l; i ++ ) {
target[ i ] = adjustFromType( target[ i ] );
}
} else {
target = adjustFromType( target );
}
return target;
function adjustFromType( value ) {
if ( isMatrixType( type ) ) {
value = adjustMatrix( value );
} else if ( isVectorType( type ) ) {
value = adjustVector( value );
} else {
value = adjustScalar( value );
}
return value;
}
function adjustVector( value ) {
value.x = adjustScalar( value.x );
value.y = adjustScalar( value.y );
if ( 'z' in value ) value.z = adjustScalar( value.z );
if ( 'w' in value ) value.w = adjustScalar( value.w );
return value;
}
function adjustMatrix( value ) {
const elements = value.elements;
for ( let i = 0, l = elements.length; i < l; i ++ ) {
elements[ i ] = adjustScalar( elements[ i ] );
}
return value;
}
function adjustScalar( value ) {
if ( normalized ) {
value = normalizeValue( componentType, value );
}
if ( normalized || isFloatComponentType( componentType ) ) {
value = value * scale + offset;
}
return value;
}
}
// Shape the given target object based on the provided property. If overrideCount is
// provided then it will be used to specify the array length.
export function initializeFromProperty( property, target, overrideCount = null ) {
if ( property.array ) {
if ( ! Array.isArray( target ) ) {
target = new Array( property.count || 0 );
}
target.length = overrideCount !== null ? overrideCount : property.count;
for ( let i = 0, l = target.length; i < l; i ++ ) {
if ( ! isTypeInstance( property.type, target[ i ] ) ) {
target[ i ] = getTypeInstance( property );
}
}
} else {
if ( ! isTypeInstance( property.type, target ) ) {
target = getTypeInstance( property );
}
}
return target;
}
// Shape the "target" object based on the provided set of properties
export function initializeFromClass( properties, target ) {
// remove unused fields
for ( const key in target ) {
if ( ! ( key in properties ) ) {
delete target[ key ];
}
}
// add and adjust any fields required by the set of properties
for ( const key in properties ) {
const prop = properties[ key ];
target[ key ] = initializeFromProperty( prop, target[ key ] );
}
}
// Returns the number of components required for the given type
export function typeToComponentCount( type ) {
switch ( type ) {
case 'ENUM': return 1;
case 'SCALAR': return 1;
case 'VEC2': return 2;
case 'VEC3': return 3;
case 'VEC4': return 4;
case 'MAT2': return 4;
case 'MAT3': return 9;
case 'MAT4': return 16;
// unused
case 'BOOLEAN': return - 1;
case 'STRING': return - 1;
default: return - 1;
}
}