main repo

This commit is contained in:
Basilosaurusrex
2025-11-24 18:09:40 +01:00
parent b636ee5e70
commit f027651f9b
34146 changed files with 4436636 additions and 0 deletions

File diff suppressed because it is too large Load Diff

82
node_modules/three/examples/jsm/utils/CameraUtils.js generated vendored Normal file
View File

@@ -0,0 +1,82 @@
import {
MathUtils,
Quaternion,
Vector3
} from 'three';
/**
* @module CameraUtils
* @three_import import * as CameraUtils from 'three/addons/utils/CameraUtils.js';
*/
const _va = /*@__PURE__*/ new Vector3(), // from pe to pa
_vb = /*@__PURE__*/ new Vector3(), // from pe to pb
_vc = /*@__PURE__*/ new Vector3(), // from pe to pc
_vr = /*@__PURE__*/ new Vector3(), // right axis of screen
_vu = /*@__PURE__*/ new Vector3(), // up axis of screen
_vn = /*@__PURE__*/ new Vector3(), // normal vector of screen
_vec = /*@__PURE__*/ new Vector3(), // temporary vector
_quat = /*@__PURE__*/ new Quaternion(); // temporary quaternion
/**
* Set projection matrix and the orientation of a perspective camera
* to exactly frame the corners of an arbitrary rectangle.
* NOTE: This function ignores the standard parameters;
* do not call `updateProjectionMatrix()` after this.
*
* @param {PerspectiveCamera} camera - The camera.
* @param {Vector3} bottomLeftCorner - The bottom-left corner point.
* @param {Vector3} bottomRightCorner - The bottom-right corner point.
* @param {Vector3} topLeftCorner - The top-left corner point.
* @param {boolean} [estimateViewFrustum=false] - If set to `true`, the function tries to estimate the camera's FOV.
*/
function frameCorners( camera, bottomLeftCorner, bottomRightCorner, topLeftCorner, estimateViewFrustum = false ) {
const pa = bottomLeftCorner, pb = bottomRightCorner, pc = topLeftCorner;
const pe = camera.position; // eye position
const n = camera.near; // distance of near clipping plane
const f = camera.far; //distance of far clipping plane
_vr.copy( pb ).sub( pa ).normalize();
_vu.copy( pc ).sub( pa ).normalize();
_vn.crossVectors( _vr, _vu ).normalize();
_va.copy( pa ).sub( pe ); // from pe to pa
_vb.copy( pb ).sub( pe ); // from pe to pb
_vc.copy( pc ).sub( pe ); // from pe to pc
const d = - _va.dot( _vn ); // distance from eye to screen
const l = _vr.dot( _va ) * n / d; // distance to left screen edge
const r = _vr.dot( _vb ) * n / d; // distance to right screen edge
const b = _vu.dot( _va ) * n / d; // distance to bottom screen edge
const t = _vu.dot( _vc ) * n / d; // distance to top screen edge
// Set the camera rotation to match the focal plane to the corners' plane
_quat.setFromUnitVectors( _vec.set( 0, 1, 0 ), _vu );
camera.quaternion.setFromUnitVectors( _vec.set( 0, 0, 1 ).applyQuaternion( _quat ), _vn ).multiply( _quat );
// Set the off-axis projection matrix to match the corners
camera.projectionMatrix.set( 2.0 * n / ( r - l ), 0.0,
( r + l ) / ( r - l ), 0.0, 0.0,
2.0 * n / ( t - b ),
( t + b ) / ( t - b ), 0.0, 0.0, 0.0,
( f + n ) / ( n - f ),
2.0 * f * n / ( n - f ), 0.0, 0.0, - 1.0, 0.0 );
camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert();
// FoV estimation to fix frustum culling
if ( estimateViewFrustum ) {
// Set fieldOfView to a conservative estimate
// to make frustum tall/wide enough to encompass it
camera.fov =
MathUtils.RAD2DEG / Math.min( 1.0, camera.aspect ) *
Math.atan( ( _vec.copy( pb ).sub( pa ).length() +
( _vec.copy( pc ).sub( pa ).length() ) ) / _va.length() );
}
}
export { frameCorners };

View File

@@ -0,0 +1,547 @@
import {
BufferAttribute,
Matrix3,
Matrix4,
Vector3
} from 'three';
/**
* @module GeometryCompressionUtils
* @three_import import * as GeometryCompressionUtils from 'three/addons/utils/GeometryCompressionUtils.js';
*/
// Octahedron and Quantization encodings based on work by: https://github.com/tsherif/mesh-quantization-example
/**
* Compressed the given geometry's `normal` attribute by the selected encode method.
*
* @param {BufferGeometry} geometry - The geometry whose normals should be compressed.
* @param {('DEFAULT'|'OCT1Byte'|'OCT2Byte'|'ANGLES')} encodeMethod - The compression method.
*/
function compressNormals( geometry, encodeMethod ) {
const normal = geometry.attributes.normal;
if ( ! normal ) {
console.error( 'THREE.GeometryCompressionUtils.compressNormals(): Geometry must contain normal attribute.' );
}
if ( normal.isPacked ) return;
if ( normal.itemSize != 3 ) {
console.error( 'THREE.GeometryCompressionUtils.compressNormals(): normal.itemSize is not 3, which cannot be encoded.' );
}
const array = normal.array;
const count = normal.count;
let result;
if ( encodeMethod == 'DEFAULT' ) {
// TODO: Add 1 byte to the result, making the encoded length to be 4 bytes.
result = new Uint8Array( count * 3 );
for ( let idx = 0; idx < array.length; idx += 3 ) {
const encoded = defaultEncode( array[ idx ], array[ idx + 1 ], array[ idx + 2 ], 1 );
result[ idx + 0 ] = encoded[ 0 ];
result[ idx + 1 ] = encoded[ 1 ];
result[ idx + 2 ] = encoded[ 2 ];
}
geometry.setAttribute( 'normal', new BufferAttribute( result, 3, true ) );
geometry.attributes.normal.bytes = result.length * 1;
} else if ( encodeMethod == 'OCT1Byte' ) {
// It is not recommended to use 1-byte octahedron normals encoding unless you want to extremely reduce the memory usage
// As it makes vertex data not aligned to a 4 byte boundary which may harm some WebGL implementations and sometimes the normal distortion is visible
// Please refer to @zeux 's comments in https://github.com/mrdoob/three.js/pull/18208
result = new Int8Array( count * 2 );
for ( let idx = 0; idx < array.length; idx += 3 ) {
const encoded = octEncodeBest( array[ idx ], array[ idx + 1 ], array[ idx + 2 ], 1 );
result[ idx / 3 * 2 + 0 ] = encoded[ 0 ];
result[ idx / 3 * 2 + 1 ] = encoded[ 1 ];
}
geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) );
geometry.attributes.normal.bytes = result.length * 1;
} else if ( encodeMethod == 'OCT2Byte' ) {
result = new Int16Array( count * 2 );
for ( let idx = 0; idx < array.length; idx += 3 ) {
const encoded = octEncodeBest( array[ idx ], array[ idx + 1 ], array[ idx + 2 ], 2 );
result[ idx / 3 * 2 + 0 ] = encoded[ 0 ];
result[ idx / 3 * 2 + 1 ] = encoded[ 1 ];
}
geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) );
geometry.attributes.normal.bytes = result.length * 2;
} else if ( encodeMethod == 'ANGLES' ) {
result = new Uint16Array( count * 2 );
for ( let idx = 0; idx < array.length; idx += 3 ) {
const encoded = anglesEncode( array[ idx ], array[ idx + 1 ], array[ idx + 2 ] );
result[ idx / 3 * 2 + 0 ] = encoded[ 0 ];
result[ idx / 3 * 2 + 1 ] = encoded[ 1 ];
}
geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) );
geometry.attributes.normal.bytes = result.length * 2;
} else {
console.error( 'Unrecognized encoding method, should be `DEFAULT` or `ANGLES` or `OCT`. ' );
}
geometry.attributes.normal.needsUpdate = true;
geometry.attributes.normal.isPacked = true;
geometry.attributes.normal.packingMethod = encodeMethod;
}
/**
* Compressed the given geometry's `position` attribute.
*
* @param {BufferGeometry} geometry - The geometry whose position values should be compressed.
*/
function compressPositions( geometry ) {
const position = geometry.attributes.position;
if ( ! position ) {
console.error( 'THREE.GeometryCompressionUtils.compressPositions(): Geometry must contain position attribute.' );
}
if ( position.isPacked ) return;
if ( position.itemSize != 3 ) {
console.error( 'THREE.GeometryCompressionUtils.compressPositions(): position.itemSize is not 3, which cannot be packed.' );
}
const array = position.array;
const encodingBytes = 2;
const result = quantizedEncode( array, encodingBytes );
const quantized = result.quantized;
// IMPORTANT: calculate original geometry bounding info first, before updating packed positions
if ( geometry.boundingBox == null ) geometry.computeBoundingBox();
if ( geometry.boundingSphere == null ) geometry.computeBoundingSphere();
geometry.setAttribute( 'position', new BufferAttribute( quantized, 3 ) );
geometry.attributes.position.isPacked = true;
geometry.attributes.position.needsUpdate = true;
geometry.attributes.position.bytes = quantized.length * encodingBytes;
}
/**
* Compressed the given geometry's `uv` attribute.
*
* @param {BufferGeometry} geometry - The geometry whose texture coordinates should be compressed.
*/
function compressUvs( geometry ) {
const uvs = geometry.attributes.uv;
if ( ! uvs ) {
console.error( 'THREE.GeometryCompressionUtils.compressUvs(): Geometry must contain uv attribute.' );
}
if ( uvs.isPacked ) return;
const range = { min: Infinity, max: - Infinity };
const array = uvs.array;
for ( let i = 0; i < array.length; i ++ ) {
range.min = Math.min( range.min, array[ i ] );
range.max = Math.max( range.max, array[ i ] );
}
let result;
if ( range.min >= - 1.0 && range.max <= 1.0 ) {
// use default encoding method
result = new Uint16Array( array.length );
for ( let i = 0; i < array.length; i += 2 ) {
const encoded = defaultEncode( array[ i ], array[ i + 1 ], 0, 2 );
result[ i ] = encoded[ 0 ];
result[ i + 1 ] = encoded[ 1 ];
}
geometry.setAttribute( 'uv', new BufferAttribute( result, 2, true ) );
geometry.attributes.uv.isPacked = true;
geometry.attributes.uv.needsUpdate = true;
geometry.attributes.uv.bytes = result.length * 2;
} else {
// use quantized encoding method
result = quantizedEncodeUV( array, 2 );
geometry.setAttribute( 'uv', new BufferAttribute( result.quantized, 2 ) );
geometry.attributes.uv.isPacked = true;
geometry.attributes.uv.needsUpdate = true;
geometry.attributes.uv.bytes = result.quantized.length * 2;
}
}
// Encoding functions
function defaultEncode( x, y, z, bytes ) {
if ( bytes == 1 ) {
const tmpx = Math.round( ( x + 1 ) * 0.5 * 255 );
const tmpy = Math.round( ( y + 1 ) * 0.5 * 255 );
const tmpz = Math.round( ( z + 1 ) * 0.5 * 255 );
return new Uint8Array( [ tmpx, tmpy, tmpz ] );
} else if ( bytes == 2 ) {
const tmpx = Math.round( ( x + 1 ) * 0.5 * 65535 );
const tmpy = Math.round( ( y + 1 ) * 0.5 * 65535 );
const tmpz = Math.round( ( z + 1 ) * 0.5 * 65535 );
return new Uint16Array( [ tmpx, tmpy, tmpz ] );
} else {
console.error( 'number of bytes must be 1 or 2' );
}
}
// for `Angles` encoding
function anglesEncode( x, y, z ) {
const normal0 = parseInt( 0.5 * ( 1.0 + Math.atan2( y, x ) / Math.PI ) * 65535 );
const normal1 = parseInt( 0.5 * ( 1.0 + z ) * 65535 );
return new Uint16Array( [ normal0, normal1 ] );
}
// for `Octahedron` encoding
function octEncodeBest( x, y, z, bytes ) {
let oct, dec, best, currentCos, bestCos;
// Test various combinations of ceil and floor
// to minimize rounding errors
best = oct = octEncodeVec3( x, y, z, 'floor', 'floor' );
dec = octDecodeVec2( oct );
bestCos = dot( x, y, z, dec );
oct = octEncodeVec3( x, y, z, 'ceil', 'floor' );
dec = octDecodeVec2( oct );
currentCos = dot( x, y, z, dec );
if ( currentCos > bestCos ) {
best = oct;
bestCos = currentCos;
}
oct = octEncodeVec3( x, y, z, 'floor', 'ceil' );
dec = octDecodeVec2( oct );
currentCos = dot( x, y, z, dec );
if ( currentCos > bestCos ) {
best = oct;
bestCos = currentCos;
}
oct = octEncodeVec3( x, y, z, 'ceil', 'ceil' );
dec = octDecodeVec2( oct );
currentCos = dot( x, y, z, dec );
if ( currentCos > bestCos ) {
best = oct;
}
return best;
function octEncodeVec3( x0, y0, z0, xfunc, yfunc ) {
let x = x0 / ( Math.abs( x0 ) + Math.abs( y0 ) + Math.abs( z0 ) );
let y = y0 / ( Math.abs( x0 ) + Math.abs( y0 ) + Math.abs( z0 ) );
if ( z < 0 ) {
const tempx = ( 1 - Math.abs( y ) ) * ( x >= 0 ? 1 : - 1 );
const tempy = ( 1 - Math.abs( x ) ) * ( y >= 0 ? 1 : - 1 );
x = tempx;
y = tempy;
let diff = 1 - Math.abs( x ) - Math.abs( y );
if ( diff > 0 ) {
diff += 0.001;
x += x > 0 ? diff / 2 : - diff / 2;
y += y > 0 ? diff / 2 : - diff / 2;
}
}
if ( bytes == 1 ) {
return new Int8Array( [
Math[ xfunc ]( x * 127.5 + ( x < 0 ? 1 : 0 ) ),
Math[ yfunc ]( y * 127.5 + ( y < 0 ? 1 : 0 ) )
] );
}
if ( bytes == 2 ) {
return new Int16Array( [
Math[ xfunc ]( x * 32767.5 + ( x < 0 ? 1 : 0 ) ),
Math[ yfunc ]( y * 32767.5 + ( y < 0 ? 1 : 0 ) )
] );
}
}
function octDecodeVec2( oct ) {
let x = oct[ 0 ];
let y = oct[ 1 ];
if ( bytes == 1 ) {
x /= x < 0 ? 127 : 128;
y /= y < 0 ? 127 : 128;
} else if ( bytes == 2 ) {
x /= x < 0 ? 32767 : 32768;
y /= y < 0 ? 32767 : 32768;
}
const z = 1 - Math.abs( x ) - Math.abs( y );
if ( z < 0 ) {
const tmpx = x;
x = ( 1 - Math.abs( y ) ) * ( x >= 0 ? 1 : - 1 );
y = ( 1 - Math.abs( tmpx ) ) * ( y >= 0 ? 1 : - 1 );
}
const length = Math.sqrt( x * x + y * y + z * z );
return [
x / length,
y / length,
z / length
];
}
function dot( x, y, z, vec3 ) {
return x * vec3[ 0 ] + y * vec3[ 1 ] + z * vec3[ 2 ];
}
}
function quantizedEncode( array, bytes ) {
let quantized, segments;
if ( bytes == 1 ) {
quantized = new Uint8Array( array.length );
segments = 255;
} else if ( bytes == 2 ) {
quantized = new Uint16Array( array.length );
segments = 65535;
} else {
console.error( 'number of bytes error! ' );
}
const decodeMat = new Matrix4();
const min = new Float32Array( 3 );
const max = new Float32Array( 3 );
min[ 0 ] = min[ 1 ] = min[ 2 ] = Number.MAX_VALUE;
max[ 0 ] = max[ 1 ] = max[ 2 ] = - Number.MAX_VALUE;
for ( let i = 0; i < array.length; i += 3 ) {
min[ 0 ] = Math.min( min[ 0 ], array[ i + 0 ] );
min[ 1 ] = Math.min( min[ 1 ], array[ i + 1 ] );
min[ 2 ] = Math.min( min[ 2 ], array[ i + 2 ] );
max[ 0 ] = Math.max( max[ 0 ], array[ i + 0 ] );
max[ 1 ] = Math.max( max[ 1 ], array[ i + 1 ] );
max[ 2 ] = Math.max( max[ 2 ], array[ i + 2 ] );
}
decodeMat.scale( new Vector3(
( max[ 0 ] - min[ 0 ] ) / segments,
( max[ 1 ] - min[ 1 ] ) / segments,
( max[ 2 ] - min[ 2 ] ) / segments
) );
decodeMat.elements[ 12 ] = min[ 0 ];
decodeMat.elements[ 13 ] = min[ 1 ];
decodeMat.elements[ 14 ] = min[ 2 ];
decodeMat.transpose();
const multiplier = new Float32Array( [
max[ 0 ] !== min[ 0 ] ? segments / ( max[ 0 ] - min[ 0 ] ) : 0,
max[ 1 ] !== min[ 1 ] ? segments / ( max[ 1 ] - min[ 1 ] ) : 0,
max[ 2 ] !== min[ 2 ] ? segments / ( max[ 2 ] - min[ 2 ] ) : 0
] );
for ( let i = 0; i < array.length; i += 3 ) {
quantized[ i + 0 ] = Math.floor( ( array[ i + 0 ] - min[ 0 ] ) * multiplier[ 0 ] );
quantized[ i + 1 ] = Math.floor( ( array[ i + 1 ] - min[ 1 ] ) * multiplier[ 1 ] );
quantized[ i + 2 ] = Math.floor( ( array[ i + 2 ] - min[ 2 ] ) * multiplier[ 2 ] );
}
return {
quantized: quantized,
decodeMat: decodeMat
};
}
function quantizedEncodeUV( array, bytes ) {
let quantized, segments;
if ( bytes == 1 ) {
quantized = new Uint8Array( array.length );
segments = 255;
} else if ( bytes == 2 ) {
quantized = new Uint16Array( array.length );
segments = 65535;
} else {
console.error( 'number of bytes error! ' );
}
const decodeMat = new Matrix3();
const min = new Float32Array( 2 );
const max = new Float32Array( 2 );
min[ 0 ] = min[ 1 ] = Number.MAX_VALUE;
max[ 0 ] = max[ 1 ] = - Number.MAX_VALUE;
for ( let i = 0; i < array.length; i += 2 ) {
min[ 0 ] = Math.min( min[ 0 ], array[ i + 0 ] );
min[ 1 ] = Math.min( min[ 1 ], array[ i + 1 ] );
max[ 0 ] = Math.max( max[ 0 ], array[ i + 0 ] );
max[ 1 ] = Math.max( max[ 1 ], array[ i + 1 ] );
}
decodeMat.scale(
( max[ 0 ] - min[ 0 ] ) / segments,
( max[ 1 ] - min[ 1 ] ) / segments
);
decodeMat.elements[ 6 ] = min[ 0 ];
decodeMat.elements[ 7 ] = min[ 1 ];
decodeMat.transpose();
const multiplier = new Float32Array( [
max[ 0 ] !== min[ 0 ] ? segments / ( max[ 0 ] - min[ 0 ] ) : 0,
max[ 1 ] !== min[ 1 ] ? segments / ( max[ 1 ] - min[ 1 ] ) : 0
] );
for ( let i = 0; i < array.length; i += 2 ) {
quantized[ i + 0 ] = Math.floor( ( array[ i + 0 ] - min[ 0 ] ) * multiplier[ 0 ] );
quantized[ i + 1 ] = Math.floor( ( array[ i + 1 ] - min[ 1 ] ) * multiplier[ 1 ] );
}
return {
quantized: quantized,
decodeMat: decodeMat
};
}
export {
compressNormals,
compressPositions,
compressUvs,
};

226
node_modules/three/examples/jsm/utils/GeometryUtils.js generated vendored Normal file
View File

@@ -0,0 +1,226 @@
import { Vector3 } from 'three';
/**
* @module GeometryUtils
* @three_import import * as GeometryUtils from 'three/addons/utils/GeometryUtils.js';
*/
/**
* Generates 2D-Coordinates along a Hilbert curve.
*
* Based on work by: {@link http://www.openprocessing.org/sketch/15493}
*
* @param {Vector3} [center] - Center of Hilbert curve.
* @param {number} [size=10] - Total width of Hilbert curve.
* @param {number} [iterations=10] - Number of subdivisions.
* @param {number} [v0=0] - Corner index -X, -Z.
* @param {number} [v1=1] - Corner index -X, +Z.
* @param {number} [v2=2] - Corner index +X, +Z.
* @param {number} [v3=3] - Corner index +X, -Z.
* @returns {Array<Vector3>} The Hilbert curve points.
*/
function hilbert2D( center = new Vector3( 0, 0, 0 ), size = 10, iterations = 1, v0 = 0, v1 = 1, v2 = 2, v3 = 3 ) {
const half = size / 2;
const vec_s = [
new Vector3( center.x - half, center.y, center.z - half ),
new Vector3( center.x - half, center.y, center.z + half ),
new Vector3( center.x + half, center.y, center.z + half ),
new Vector3( center.x + half, center.y, center.z - half )
];
const vec = [
vec_s[ v0 ],
vec_s[ v1 ],
vec_s[ v2 ],
vec_s[ v3 ]
];
// Recurse iterations
if ( 0 <= -- iterations ) {
return [
...hilbert2D( vec[ 0 ], half, iterations, v0, v3, v2, v1 ),
...hilbert2D( vec[ 1 ], half, iterations, v0, v1, v2, v3 ),
...hilbert2D( vec[ 2 ], half, iterations, v0, v1, v2, v3 ),
...hilbert2D( vec[ 3 ], half, iterations, v2, v1, v0, v3 )
];
}
// Return complete Hilbert Curve.
return vec;
}
/**
* Generates 3D-Coordinates along a Hilbert curve.
*
* Based on work by: {@link https://openprocessing.org/user/5654}
*
* @param {Vector3} [center] - Center of Hilbert curve.
* @param {number} [size=10] - Total width of Hilbert curve.
* @param {number} [iterations=1] - Number of subdivisions.
* @param {number} [v0=0] - Corner index -X, +Y, -Z.
* @param {number} [v1=1] - Corner index -X, +Y, +Z.
* @param {number} [v2=2] - Corner index -X, -Y, +Z.
* @param {number} [v3=3] - Corner index -X, -Y, -Z.
* @param {number} [v4=4] - Corner index +X, -Y, -Z.
* @param {number} [v5=5] - Corner index +X, -Y, +Z.
* @param {number} [v6=6] - Corner index +X, +Y, +Z.
* @param {number} [v7=7] - Corner index +X, +Y, -Z.
* @returns {Array<Vector3>} - The Hilbert curve points.
*/
function hilbert3D( center = new Vector3( 0, 0, 0 ), size = 10, iterations = 1, v0 = 0, v1 = 1, v2 = 2, v3 = 3, v4 = 4, v5 = 5, v6 = 6, v7 = 7 ) {
// Default Vars
const half = size / 2;
const vec_s = [
new Vector3( center.x - half, center.y + half, center.z - half ),
new Vector3( center.x - half, center.y + half, center.z + half ),
new Vector3( center.x - half, center.y - half, center.z + half ),
new Vector3( center.x - half, center.y - half, center.z - half ),
new Vector3( center.x + half, center.y - half, center.z - half ),
new Vector3( center.x + half, center.y - half, center.z + half ),
new Vector3( center.x + half, center.y + half, center.z + half ),
new Vector3( center.x + half, center.y + half, center.z - half )
];
const vec = [
vec_s[ v0 ],
vec_s[ v1 ],
vec_s[ v2 ],
vec_s[ v3 ],
vec_s[ v4 ],
vec_s[ v5 ],
vec_s[ v6 ],
vec_s[ v7 ]
];
// Recurse iterations
if ( -- iterations >= 0 ) {
return [
...hilbert3D( vec[ 0 ], half, iterations, v0, v3, v4, v7, v6, v5, v2, v1 ),
...hilbert3D( vec[ 1 ], half, iterations, v0, v7, v6, v1, v2, v5, v4, v3 ),
...hilbert3D( vec[ 2 ], half, iterations, v0, v7, v6, v1, v2, v5, v4, v3 ),
...hilbert3D( vec[ 3 ], half, iterations, v2, v3, v0, v1, v6, v7, v4, v5 ),
...hilbert3D( vec[ 4 ], half, iterations, v2, v3, v0, v1, v6, v7, v4, v5 ),
...hilbert3D( vec[ 5 ], half, iterations, v4, v3, v2, v5, v6, v1, v0, v7 ),
...hilbert3D( vec[ 6 ], half, iterations, v4, v3, v2, v5, v6, v1, v0, v7 ),
...hilbert3D( vec[ 7 ], half, iterations, v6, v5, v2, v1, v0, v3, v4, v7 )
];
}
// Return complete Hilbert Curve.
return vec;
}
/**
* Generates a Gosper curve (lying in the XY plane).
*
* Reference: {@link https://gist.github.com/nitaku/6521802}
*
* @param {number} [size=1] - The size of a single gosper island.
* @return {Array<number>} The gosper island points.
*/
function gosper( size = 1 ) {
function fractalize( config ) {
let output;
let input = config.axiom;
for ( let i = 0, il = config.steps; 0 <= il ? i < il : i > il; 0 <= il ? i ++ : i -- ) {
output = '';
for ( let j = 0, jl = input.length; j < jl; j ++ ) {
const char = input[ j ];
if ( char in config.rules ) {
output += config.rules[ char ];
} else {
output += char;
}
}
input = output;
}
return output;
}
function toPoints( config ) {
let currX = 0, currY = 0;
let angle = 0;
const path = [ 0, 0, 0 ];
const fractal = config.fractal;
for ( let i = 0, l = fractal.length; i < l; i ++ ) {
const char = fractal[ i ];
if ( char === '+' ) {
angle += config.angle;
} else if ( char === '-' ) {
angle -= config.angle;
} else if ( char === 'F' ) {
currX += config.size * Math.cos( angle );
currY += - config.size * Math.sin( angle );
path.push( currX, currY, 0 );
}
}
return path;
}
//
const gosper = fractalize( {
axiom: 'A',
steps: 4,
rules: {
A: 'A+BF++BF-FA--FAFA-BF+',
B: '-FA+BFBF++BF+FA--FA-B'
}
} );
const points = toPoints( {
fractal: gosper,
size: size,
angle: Math.PI / 3 // 60 degrees
} );
return points;
}
export {
hilbert2D,
hilbert3D,
gosper,
};

211
node_modules/three/examples/jsm/utils/LDrawUtils.js generated vendored Normal file
View File

@@ -0,0 +1,211 @@
import {
BufferAttribute,
BufferGeometry,
Group,
LineSegments,
Matrix3,
Mesh
} from 'three';
import { mergeGeometries } from './BufferGeometryUtils.js';
/**
* Utility class for LDraw models.
*
* @three_import import { LDrawUtils } from 'three/addons/utils/LDrawUtils.js';
*/
class LDrawUtils {
/**
* Merges geometries in the given object by materials and returns a new group object.
* Use on not indexed geometries. The object buffers reference the old object ones.
* Special treatment is done to the conditional lines generated by LDrawLoader.
*
* @param {Object3D} object - The object to merge.
* @returns {Group} The merged object.
*/
static mergeObject( object ) {
function extractGroup( geometry, group, elementSize, isConditionalLine ) {
// Extracts a group from a geometry as a new geometry (with attribute buffers referencing original buffers)
const newGeometry = new BufferGeometry();
const originalPositions = geometry.getAttribute( 'position' ).array;
const originalNormals = elementSize === 3 ? geometry.getAttribute( 'normal' ).array : null;
const numVertsGroup = Math.min( group.count, Math.floor( originalPositions.length / 3 ) - group.start );
const vertStart = group.start * 3;
const vertEnd = ( group.start + numVertsGroup ) * 3;
const positions = originalPositions.subarray( vertStart, vertEnd );
const normals = originalNormals !== null ? originalNormals.subarray( vertStart, vertEnd ) : null;
newGeometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) );
if ( normals !== null ) newGeometry.setAttribute( 'normal', new BufferAttribute( normals, 3 ) );
if ( isConditionalLine ) {
const controlArray0 = geometry.getAttribute( 'control0' ).array.subarray( vertStart, vertEnd );
const controlArray1 = geometry.getAttribute( 'control1' ).array.subarray( vertStart, vertEnd );
const directionArray = geometry.getAttribute( 'direction' ).array.subarray( vertStart, vertEnd );
newGeometry.setAttribute( 'control0', new BufferAttribute( controlArray0, 3, false ) );
newGeometry.setAttribute( 'control1', new BufferAttribute( controlArray1, 3, false ) );
newGeometry.setAttribute( 'direction', new BufferAttribute( directionArray, 3, false ) );
}
return newGeometry;
}
function addGeometry( mat, geometry, geometries ) {
const geoms = geometries[ mat.uuid ];
if ( ! geoms ) {
geometries[ mat.uuid ] = {
mat: mat,
arr: [ geometry ]
};
} else {
geoms.arr.push( geometry );
}
}
function permuteAttribute( attribute, elemSize ) {
// Permutes first two vertices of each attribute element
if ( ! attribute ) return;
const verts = attribute.array;
const numVerts = Math.floor( verts.length / 3 );
let offset = 0;
for ( let i = 0; i < numVerts; i ++ ) {
const x = verts[ offset ];
const y = verts[ offset + 1 ];
const z = verts[ offset + 2 ];
verts[ offset ] = verts[ offset + 3 ];
verts[ offset + 1 ] = verts[ offset + 4 ];
verts[ offset + 2 ] = verts[ offset + 5 ];
verts[ offset + 3 ] = x;
verts[ offset + 4 ] = y;
verts[ offset + 5 ] = z;
offset += elemSize * 3;
}
}
// Traverse the object hierarchy collecting geometries and transforming them to world space
const meshGeometries = {};
const linesGeometries = {};
const condLinesGeometries = {};
object.updateMatrixWorld( true );
const normalMatrix = new Matrix3();
object.traverse( c => {
if ( c.isMesh | c.isLineSegments ) {
const elemSize = c.isMesh ? 3 : 2;
const geometry = c.geometry.clone();
const matrixIsInverted = c.matrixWorld.determinant() < 0;
if ( matrixIsInverted ) {
permuteAttribute( geometry.attributes.position, elemSize );
permuteAttribute( geometry.attributes.normal, elemSize );
}
geometry.applyMatrix4( c.matrixWorld );
if ( c.isConditionalLine ) {
geometry.attributes.control0.applyMatrix4( c.matrixWorld );
geometry.attributes.control1.applyMatrix4( c.matrixWorld );
normalMatrix.getNormalMatrix( c.matrixWorld );
geometry.attributes.direction.applyNormalMatrix( normalMatrix );
}
const geometries = c.isMesh ? meshGeometries : ( c.isConditionalLine ? condLinesGeometries : linesGeometries );
if ( Array.isArray( c.material ) ) {
for ( const groupIndex in geometry.groups ) {
const group = geometry.groups[ groupIndex ];
const mat = c.material[ group.materialIndex ];
const newGeometry = extractGroup( geometry, group, elemSize, c.isConditionalLine );
addGeometry( mat, newGeometry, geometries );
}
} else {
addGeometry( c.material, geometry, geometries );
}
}
} );
// Create object with merged geometries
const mergedObject = new Group();
const meshMaterialsIds = Object.keys( meshGeometries );
for ( const meshMaterialsId of meshMaterialsIds ) {
const meshGeometry = meshGeometries[ meshMaterialsId ];
const mergedGeometry = mergeGeometries( meshGeometry.arr );
mergedObject.add( new Mesh( mergedGeometry, meshGeometry.mat ) );
}
const linesMaterialsIds = Object.keys( linesGeometries );
for ( const linesMaterialsId of linesMaterialsIds ) {
const lineGeometry = linesGeometries[ linesMaterialsId ];
const mergedGeometry = mergeGeometries( lineGeometry.arr );
mergedObject.add( new LineSegments( mergedGeometry, lineGeometry.mat ) );
}
const condLinesMaterialsIds = Object.keys( condLinesGeometries );
for ( const condLinesMaterialsId of condLinesMaterialsIds ) {
const condLineGeometry = condLinesGeometries[ condLinesMaterialsId ];
const mergedGeometry = mergeGeometries( condLineGeometry.arr );
const condLines = new LineSegments( mergedGeometry, condLineGeometry.mat );
condLines.isConditionalLine = true;
mergedObject.add( condLines );
}
mergedObject.userData.constructionStep = 0;
mergedObject.userData.numConstructionSteps = 1;
return mergedObject;
}
}
export { LDrawUtils };

458
node_modules/three/examples/jsm/utils/SceneOptimizer.js generated vendored Normal file
View File

@@ -0,0 +1,458 @@
import * as THREE from 'three';
/**
* This class can be used to optimized scenes by converting
* individual meshes into {@link BatchedMesh}. This component
* is an experimental attempt to implement auto-batching in three.js.
*
* @three_import import { SceneOptimizer } from 'three/addons/utils/SceneOptimizer.js';
*/
class SceneOptimizer {
/**
* Constructs a new scene optimizer.
*
* @param {Scene} scene - The scene to optimize.
* @param {SceneOptimizer~Options} options - The configuration options.
*/
constructor( scene, options = {} ) {
this.scene = scene;
this.debug = options.debug || false;
}
_bufferToHash( buffer ) {
let hash = 0;
if ( buffer.byteLength !== 0 ) {
let uintArray;
if ( buffer.buffer ) {
uintArray = new Uint8Array(
buffer.buffer,
buffer.byteOffset,
buffer.byteLength
);
} else {
uintArray = new Uint8Array( buffer );
}
for ( let i = 0; i < buffer.byteLength; i ++ ) {
const byte = uintArray[ i ];
hash = ( hash << 5 ) - hash + byte;
hash |= 0;
}
}
return hash;
}
_getMaterialPropertiesHash( material ) {
const mapProps = [
'map',
'alphaMap',
'aoMap',
'bumpMap',
'displacementMap',
'emissiveMap',
'envMap',
'lightMap',
'metalnessMap',
'normalMap',
'roughnessMap',
];
const mapHash = mapProps
.map( ( prop ) => {
const map = material[ prop ];
if ( ! map ) return 0;
return `${map.uuid}_${map.offset.x}_${map.offset.y}_${map.repeat.x}_${map.repeat.y}_${map.rotation}`;
} )
.join( '|' );
const physicalProps = [
'transparent',
'opacity',
'alphaTest',
'alphaToCoverage',
'side',
'vertexColors',
'visible',
'blending',
'wireframe',
'flatShading',
'premultipliedAlpha',
'dithering',
'toneMapped',
'depthTest',
'depthWrite',
'metalness',
'roughness',
'clearcoat',
'clearcoatRoughness',
'sheen',
'sheenRoughness',
'transmission',
'thickness',
'attenuationDistance',
'ior',
'iridescence',
'iridescenceIOR',
'iridescenceThicknessRange',
'reflectivity',
]
.map( ( prop ) => {
if ( typeof material[ prop ] === 'undefined' ) return 0;
if ( material[ prop ] === null ) return 0;
return material[ prop ].toString();
} )
.join( '|' );
const emissiveHash = material.emissive ? material.emissive.getHexString() : 0;
const attenuationHash = material.attenuationColor
? material.attenuationColor.getHexString()
: 0;
const sheenColorHash = material.sheenColor
? material.sheenColor.getHexString()
: 0;
return [
material.type,
physicalProps,
mapHash,
emissiveHash,
attenuationHash,
sheenColorHash,
].join( '_' );
}
_getAttributesSignature( geometry ) {
return Object.keys( geometry.attributes )
.sort()
.map( ( name ) => {
const attribute = geometry.attributes[ name ];
return `${name}_${attribute.itemSize}_${attribute.normalized}`;
} )
.join( '|' );
}
_getGeometryHash( geometry ) {
const indexHash = geometry.index
? this._bufferToHash( geometry.index.array )
: 'noIndex';
const positionHash = this._bufferToHash( geometry.attributes.position.array );
const attributesSignature = this._getAttributesSignature( geometry );
return `${indexHash}_${positionHash}_${attributesSignature}`;
}
_getBatchKey( materialProps, attributesSignature ) {
return `${materialProps}_${attributesSignature}`;
}
_analyzeModel() {
const batchGroups = new Map();
const singleGroups = new Map();
const uniqueGeometries = new Set();
this.scene.updateMatrixWorld( true );
this.scene.traverse( ( node ) => {
if ( ! node.isMesh ) return;
const materialProps = this._getMaterialPropertiesHash( node.material );
const attributesSignature = this._getAttributesSignature( node.geometry );
const batchKey = this._getBatchKey( materialProps, attributesSignature );
const geometryHash = this._getGeometryHash( node.geometry );
uniqueGeometries.add( geometryHash );
if ( ! batchGroups.has( batchKey ) ) {
batchGroups.set( batchKey, {
meshes: [],
geometryStats: new Map(),
totalInstances: 0,
materialProps: node.material.clone(),
} );
}
const group = batchGroups.get( batchKey );
group.meshes.push( node );
group.totalInstances ++;
if ( ! group.geometryStats.has( geometryHash ) ) {
group.geometryStats.set( geometryHash, {
count: 0,
vertices: node.geometry.attributes.position.count,
indices: node.geometry.index ? node.geometry.index.count : 0,
geometry: node.geometry,
} );
}
group.geometryStats.get( geometryHash ).count ++;
} );
// Move single instance groups to singleGroups
for ( const [ batchKey, group ] of batchGroups ) {
if ( group.totalInstances === 1 ) {
singleGroups.set( batchKey, group );
batchGroups.delete( batchKey );
}
}
return { batchGroups, singleGroups, uniqueGeometries: uniqueGeometries.size };
}
_createBatchedMeshes( batchGroups ) {
const meshesToRemove = new Set();
for ( const [ , group ] of batchGroups ) {
const maxGeometries = group.totalInstances;
const maxVertices = Array.from( group.geometryStats.values() ).reduce(
( sum, stats ) => sum + stats.vertices,
0
);
const maxIndices = Array.from( group.geometryStats.values() ).reduce(
( sum, stats ) => sum + stats.indices,
0
);
const batchedMaterial = new group.materialProps.constructor( group.materialProps );
if ( batchedMaterial.color !== undefined ) {
// Reset color to white, color will be set per instance
batchedMaterial.color.set( 1, 1, 1 );
}
const batchedMesh = new THREE.BatchedMesh(
maxGeometries,
maxVertices,
maxIndices,
batchedMaterial
);
const referenceMesh = group.meshes[ 0 ];
batchedMesh.name = `${referenceMesh.name}_batch`;
const geometryIds = new Map();
const inverseParentMatrix = new THREE.Matrix4();
if ( referenceMesh.parent ) {
referenceMesh.parent.updateWorldMatrix( true, false );
inverseParentMatrix.copy( referenceMesh.parent.matrixWorld ).invert();
}
for ( const mesh of group.meshes ) {
const geometryHash = this._getGeometryHash( mesh.geometry );
if ( ! geometryIds.has( geometryHash ) ) {
geometryIds.set( geometryHash, batchedMesh.addGeometry( mesh.geometry ) );
}
const geometryId = geometryIds.get( geometryHash );
const instanceId = batchedMesh.addInstance( geometryId );
const localMatrix = new THREE.Matrix4();
mesh.updateWorldMatrix( true, false );
localMatrix.copy( mesh.matrixWorld );
if ( referenceMesh.parent ) {
localMatrix.premultiply( inverseParentMatrix );
}
batchedMesh.setMatrixAt( instanceId, localMatrix );
batchedMesh.setColorAt( instanceId, mesh.material.color );
meshesToRemove.add( mesh );
}
if ( referenceMesh.parent ) {
referenceMesh.parent.add( batchedMesh );
}
}
return meshesToRemove;
}
/**
* Removes empty nodes from all descendants of the given 3D object.
*
* @param {Object3D} object - The 3D object to process.
*/
removeEmptyNodes( object ) {
const children = [ ...object.children ];
for ( const child of children ) {
this.removeEmptyNodes( child );
if ( ( child instanceof THREE.Group || child.constructor === THREE.Object3D )
&& child.children.length === 0 ) {
object.remove( child );
}
}
}
/**
* Removes the given array of meshes from the scene.
*
* @param {Set<Mesh>} meshesToRemove - The meshes to remove.
*/
disposeMeshes( meshesToRemove ) {
meshesToRemove.forEach( ( mesh ) => {
if ( mesh.parent ) {
mesh.parent.remove( mesh );
}
if ( mesh.geometry ) mesh.geometry.dispose();
if ( mesh.material ) {
if ( Array.isArray( mesh.material ) ) {
mesh.material.forEach( ( m ) => m.dispose() );
} else {
mesh.material.dispose();
}
}
} );
}
_logDebugInfo( stats ) {
console.group( 'Scene Optimization Results' );
console.log( `Original meshes: ${stats.originalMeshes}` );
console.log( `Batched into: ${stats.batchedMeshes} BatchedMesh` );
console.log( `Single meshes: ${stats.singleMeshes} Mesh` );
console.log( `Total draw calls: ${stats.drawCalls}` );
console.log( `Reduction Ratio: ${stats.reductionRatio}% fewer draw calls` );
console.groupEnd();
}
/**
* Performs the auto-baching by identifying groups of meshes in the scene
* that can be represented as a single {@link BatchedMesh}. The method modifies
* the scene by adding instances of `BatchedMesh` and removing the now redundant
* individual meshes.
*
* @return {Scene} The optimized scene.
*/
toBatchedMesh() {
const { batchGroups, singleGroups, uniqueGeometries } = this._analyzeModel();
const meshesToRemove = this._createBatchedMeshes( batchGroups );
this.disposeMeshes( meshesToRemove );
this.removeEmptyNodes( this.scene );
if ( this.debug ) {
const totalOriginalMeshes = meshesToRemove.size + singleGroups.size;
const totalFinalMeshes = batchGroups.size + singleGroups.size;
const stats = {
originalMeshes: totalOriginalMeshes,
batchedMeshes: batchGroups.size,
singleMeshes: singleGroups.size,
drawCalls: totalFinalMeshes,
uniqueGeometries: uniqueGeometries,
reductionRatio: ( ( 1 - totalFinalMeshes / totalOriginalMeshes ) * 100 ).toFixed( 1 ),
};
this._logDebugInfo( stats );
}
return this.scene;
}
/**
* Performs the auto-instancing by identifying groups of meshes in the scene
* that can be represented as a single {@link InstancedMesh}. The method modifies
* the scene by adding instances of `InstancedMesh` and removing the now redundant
* individual meshes.
*
* This method is not yet implemented.
*
* @abstract
* @return {Scene} The optimized scene.
*/
toInstancingMesh() {
throw new Error( 'InstancedMesh optimization not implemented yet' );
}
}
/**
* Constructor options of `SceneOptimizer`.
*
* @typedef {Object} SceneOptimizer~Options
* @property {boolean} [debug=false] - Whether to enable debug mode or not.
**/
export { SceneOptimizer };

363
node_modules/three/examples/jsm/utils/SceneUtils.js generated vendored Normal file
View File

@@ -0,0 +1,363 @@
import {
BufferAttribute,
BufferGeometry,
Color,
Group,
Matrix4,
Mesh,
Vector3
} from 'three';
import { mergeGroups, deepCloneAttribute } from './BufferGeometryUtils.js';
/**
* @module SceneUtils
* @three_import import * as SceneUtils from 'three/addons/utils/SceneUtils.js';
*/
const _color = /*@__PURE__*/new Color();
const _matrix = /*@__PURE__*/new Matrix4();
/**
* This function creates a mesh for each instance of the given instanced mesh and
* adds it to a group. Each mesh will honor the current 3D transformation of its
* corresponding instance.
*
* @param {InstancedMesh} instancedMesh - The instanced mesh.
* @return {Group} A group of meshes.
*/
function createMeshesFromInstancedMesh( instancedMesh ) {
const group = new Group();
const count = instancedMesh.count;
const geometry = instancedMesh.geometry;
const material = instancedMesh.material;
for ( let i = 0; i < count; i ++ ) {
const mesh = new Mesh( geometry, material );
instancedMesh.getMatrixAt( i, mesh.matrix );
mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
group.add( mesh );
}
group.copy( instancedMesh );
group.updateMatrixWorld(); // ensure correct world matrices of meshes
return group;
}
/**
* This function creates a mesh for each geometry-group of the given multi-material mesh and
* adds it to a group.
*
* @param {Mesh} mesh - The multi-material mesh.
* @return {Group} A group of meshes.
*/
function createMeshesFromMultiMaterialMesh( mesh ) {
if ( Array.isArray( mesh.material ) === false ) {
console.warn( 'THREE.SceneUtils.createMeshesFromMultiMaterialMesh(): The given mesh has no multiple materials.' );
return mesh;
}
const object = new Group();
object.copy( mesh );
// merge groups (which automatically sorts them)
const geometry = mergeGroups( mesh.geometry );
const index = geometry.index;
const groups = geometry.groups;
const attributeNames = Object.keys( geometry.attributes );
// create a mesh for each group by extracting the buffer data into a new geometry
for ( let i = 0; i < groups.length; i ++ ) {
const group = groups[ i ];
const start = group.start;
const end = start + group.count;
const newGeometry = new BufferGeometry();
const newMaterial = mesh.material[ group.materialIndex ];
// process all buffer attributes
for ( let j = 0; j < attributeNames.length; j ++ ) {
const name = attributeNames[ j ];
const attribute = geometry.attributes[ name ];
const itemSize = attribute.itemSize;
const newLength = group.count * itemSize;
const type = attribute.array.constructor;
const newArray = new type( newLength );
const newAttribute = new BufferAttribute( newArray, itemSize );
for ( let k = start, n = 0; k < end; k ++, n ++ ) {
const ind = index.getX( k );
if ( itemSize >= 1 ) newAttribute.setX( n, attribute.getX( ind ) );
if ( itemSize >= 2 ) newAttribute.setY( n, attribute.getY( ind ) );
if ( itemSize >= 3 ) newAttribute.setZ( n, attribute.getZ( ind ) );
if ( itemSize >= 4 ) newAttribute.setW( n, attribute.getW( ind ) );
}
newGeometry.setAttribute( name, newAttribute );
}
const newMesh = new Mesh( newGeometry, newMaterial );
object.add( newMesh );
}
return object;
}
/**
* This function represents an alternative way to create 3D objects with multiple materials.
* Normally, {@link BufferGeometry#groups} are used which might introduce issues e.g. when
* exporting the object to a 3D format. This function accepts a geometry and an array of
* materials and creates for each material a mesh that is added to a group.
*
* @param {BufferGeometry} geometry - The geometry.
* @param {Array<Material>} materials - An array of materials.
* @return {Group} A group representing a multi-material object.
*/
function createMultiMaterialObject( geometry, materials ) {
const group = new Group();
for ( let i = 0, l = materials.length; i < l; i ++ ) {
group.add( new Mesh( geometry, materials[ i ] ) );
}
return group;
}
/**
* Executes a reducer function for each vertex of the given 3D object.
* `reduceVertices()` returns a single value: the function's accumulated result.
*
* @param {Object3D} object - The 3D object that should be processed. It must have a
* geometry with a `position` attribute.
* @param {function(number,Vector3):number} func - The reducer function. First argument
* is the current value, second argument the current vertex.
* @param {any} initialValue - The initial value.
* @return {any} The result.
*/
function reduceVertices( object, func, initialValue ) {
let value = initialValue;
const vertex = new Vector3();
object.updateWorldMatrix( true, true );
object.traverseVisible( ( child ) => {
const { geometry } = child;
if ( geometry !== undefined ) {
const { position } = geometry.attributes;
if ( position !== undefined ) {
for ( let i = 0, l = position.count; i < l; i ++ ) {
if ( child.isMesh ) {
child.getVertexPosition( i, vertex );
} else {
vertex.fromBufferAttribute( position, i );
}
if ( ! child.isSkinnedMesh ) {
vertex.applyMatrix4( child.matrixWorld );
}
value = func( value, vertex );
}
}
}
} );
return value;
}
/**
* Sorts the instances of the given instanced mesh.
*
* @param {InstancedMesh} mesh - The instanced mesh to sort.
* @param {function(number, number):number} compareFn - A custom compare function for the sort.
*/
function sortInstancedMesh( mesh, compareFn ) {
// store copy of instanced attributes for lookups
const instanceMatrixRef = deepCloneAttribute( mesh.instanceMatrix );
const instanceColorRef = mesh.instanceColor ? deepCloneAttribute( mesh.instanceColor ) : null;
const attributeRefs = new Map();
for ( const name in mesh.geometry.attributes ) {
const attribute = mesh.geometry.attributes[ name ];
if ( attribute.isInstancedBufferAttribute ) {
attributeRefs.set( attribute, deepCloneAttribute( attribute ) );
}
}
// compute sort order
const tokens = [];
for ( let i = 0; i < mesh.count; i ++ ) tokens.push( i );
tokens.sort( compareFn );
// apply sort order
for ( let i = 0; i < tokens.length; i ++ ) {
const refIndex = tokens[ i ];
_matrix.fromArray( instanceMatrixRef.array, refIndex * mesh.instanceMatrix.itemSize );
_matrix.toArray( mesh.instanceMatrix.array, i * mesh.instanceMatrix.itemSize );
if ( mesh.instanceColor ) {
_color.fromArray( instanceColorRef.array, refIndex * mesh.instanceColor.itemSize );
_color.toArray( mesh.instanceColor.array, i * mesh.instanceColor.itemSize );
}
for ( const name in mesh.geometry.attributes ) {
const attribute = mesh.geometry.attributes[ name ];
if ( attribute.isInstancedBufferAttribute ) {
const attributeRef = attributeRefs.get( attribute );
attribute.setX( i, attributeRef.getX( refIndex ) );
if ( attribute.itemSize > 1 ) attribute.setY( i, attributeRef.getY( refIndex ) );
if ( attribute.itemSize > 2 ) attribute.setZ( i, attributeRef.getZ( refIndex ) );
if ( attribute.itemSize > 3 ) attribute.setW( i, attributeRef.getW( refIndex ) );
}
}
}
}
/**
* Generator based alternative to {@link Object3D#traverse}.
*
* @param {Object3D} object - Object to traverse.
* @yields {Object3D} Objects that passed the filter condition.
*/
function* traverseGenerator( object ) {
yield object;
const children = object.children;
for ( let i = 0, l = children.length; i < l; i ++ ) {
yield* traverseGenerator( children[ i ] );
}
}
/**
* Generator based alternative to {@link Object3D#traverseVisible}.
*
* @param {Object3D} object Object to traverse.
* @yields {Object3D} Objects that passed the filter condition.
*/
function* traverseVisibleGenerator( object ) {
if ( object.visible === false ) return;
yield object;
const children = object.children;
for ( let i = 0, l = children.length; i < l; i ++ ) {
yield* traverseVisibleGenerator( children[ i ] );
}
}
/**
* Generator based alternative to {@link Object3D#traverseAncestors}.
*
* @param {Object3D} object Object to traverse.
* @yields {Object3D} Objects that passed the filter condition.
*/
function* traverseAncestorsGenerator( object ) {
const parent = object.parent;
if ( parent !== null ) {
yield parent;
yield* traverseAncestorsGenerator( parent );
}
}
export {
createMeshesFromInstancedMesh,
createMeshesFromMultiMaterialMesh,
createMultiMaterialObject,
reduceVertices,
sortInstancedMesh,
traverseGenerator,
traverseVisibleGenerator,
traverseAncestorsGenerator
};

View File

@@ -0,0 +1,230 @@
import {
DoubleSide,
CanvasTexture,
Mesh,
MeshBasicMaterial,
OrthographicCamera,
PlaneGeometry,
Scene,
ShaderMaterial,
UniformsUtils
} from 'three';
import { UnpackDepthRGBAShader } from '../shaders/UnpackDepthRGBAShader.js';
/**
* This is a helper for visualising a given light's shadow map.
* It works for shadow casting lights: DirectionalLight and SpotLight.
* It renders out the shadow map and displays it on a HUD.
*
* This module can only be used with {@link WebGLRenderer}. When using {@link WebGPURenderer},
* import the class from `ShadowMapViewerGPU.js`.
*
* ```js
* const lightShadowMapViewer = new ShadowMapViewer( light );
* lightShadowMapViewer.position.x = 10;
* lightShadowMapViewer.position.y = SCREEN_HEIGHT - ( SHADOW_MAP_HEIGHT / 4 ) - 10;
* lightShadowMapViewer.size.width = SHADOW_MAP_WIDTH / 4;
* lightShadowMapViewer.size.height = SHADOW_MAP_HEIGHT / 4;
* lightShadowMapViewer.update();
* ```
*
* @three_import import { ShadowMapViewer } from 'three/addons/utils/ShadowMapViewer.js';
*/
class ShadowMapViewer {
/**
* Constructs a new shadow map viewer.
*
* @param {Light} light - The shadow casting light.
*/
constructor( light ) {
//- Internals
const scope = this;
const doRenderLabel = ( light.name !== undefined && light.name !== '' );
let userAutoClearSetting;
//Holds the initial position and dimension of the HUD
const frame = {
x: 10,
y: 10,
width: 256,
height: 256
};
const camera = new OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, 1, 10 );
camera.position.set( 0, 0, 2 );
const scene = new Scene();
//HUD for shadow map
const shader = UnpackDepthRGBAShader;
const uniforms = UniformsUtils.clone( shader.uniforms );
const material = new ShaderMaterial( {
uniforms: uniforms,
vertexShader: shader.vertexShader,
fragmentShader: shader.fragmentShader
} );
const plane = new PlaneGeometry( frame.width, frame.height );
const mesh = new Mesh( plane, material );
scene.add( mesh );
//Label for light's name
let labelCanvas, labelMesh;
if ( doRenderLabel ) {
labelCanvas = document.createElement( 'canvas' );
const context = labelCanvas.getContext( '2d' );
context.font = 'Bold 20px Arial';
const labelWidth = context.measureText( light.name ).width;
labelCanvas.width = labelWidth;
labelCanvas.height = 25; //25 to account for g, p, etc.
context.font = 'Bold 20px Arial';
context.fillStyle = 'rgba( 255, 0, 0, 1 )';
context.fillText( light.name, 0, 20 );
const labelTexture = new CanvasTexture( labelCanvas );
const labelMaterial = new MeshBasicMaterial( { map: labelTexture, side: DoubleSide, transparent: true } );
const labelPlane = new PlaneGeometry( labelCanvas.width, labelCanvas.height );
labelMesh = new Mesh( labelPlane, labelMaterial );
scene.add( labelMesh );
}
function resetPosition() {
scope.position.set( scope.position.x, scope.position.y );
}
/**
* Whether to display the shadow map viewer or not.
*
* @type {boolean}
* @default true
*/
this.enabled = true;
/**
* The size of the viewer. When changing this property, make sure
* to call {@link ShadowMapViewer#update}.
*
* @type {{width:number,height:number}}
* @default true
*/
this.size = {
width: frame.width,
height: frame.height,
set: function ( width, height ) {
this.width = width;
this.height = height;
mesh.scale.set( this.width / frame.width, this.height / frame.height, 1 );
//Reset the position as it is off when we scale stuff
resetPosition();
}
};
/**
* The position of the viewer. When changing this property, make sure
* to call {@link ShadowMapViewer#update}.
*
* @type {{x:number,y:number, set:function(number,number)}}
* @default true
*/
this.position = {
x: frame.x,
y: frame.y,
set: function ( x, y ) {
this.x = x;
this.y = y;
const width = scope.size.width;
const height = scope.size.height;
mesh.position.set( - window.innerWidth / 2 + width / 2 + this.x, window.innerHeight / 2 - height / 2 - this.y, 0 );
if ( doRenderLabel ) labelMesh.position.set( mesh.position.x, mesh.position.y - scope.size.height / 2 + labelCanvas.height / 2, 0 );
}
};
/**
* Renders the viewer. This method must be called in the app's animation loop.
*
* @param {WebGLRenderer} renderer - The renderer.
*/
this.render = function ( renderer ) {
if ( this.enabled ) {
//Because a light's .shadowMap is only initialised after the first render pass
//we have to make sure the correct map is sent into the shader, otherwise we
//always end up with the scene's first added shadow casting light's shadowMap
//in the shader
//See: https://github.com/mrdoob/three.js/issues/5932
uniforms.tDiffuse.value = light.shadow.map.texture;
userAutoClearSetting = renderer.autoClear;
renderer.autoClear = false; // To allow render overlay
renderer.clearDepth();
renderer.render( scene, camera );
renderer.autoClear = userAutoClearSetting; //Restore user's setting
}
};
/**
* Resizes the viewer. This method should be called whenever the app's
* window is resized.
*/
this.updateForWindowResize = function () {
if ( this.enabled ) {
camera.left = window.innerWidth / - 2;
camera.right = window.innerWidth / 2;
camera.top = window.innerHeight / 2;
camera.bottom = window.innerHeight / - 2;
camera.updateProjectionMatrix();
this.update();
}
};
/**
* Updates the viewer.
*/
this.update = function () {
this.position.set( this.position.x, this.position.y );
this.size.set( this.size.width, this.size.height );
};
//Force an update to set position/size
this.update();
}
}
export { ShadowMapViewer };

View File

@@ -0,0 +1,233 @@
import {
DoubleSide,
CanvasTexture,
Mesh,
MeshBasicMaterial,
NodeMaterial,
OrthographicCamera,
PlaneGeometry,
Scene,
DepthTexture,
Vector2
} from 'three';
import { uv, uniform, textureLoad } from 'three/tsl';
/**
* This is a helper for visualising a given light's shadow map.
* It works for shadow casting lights: DirectionalLight and SpotLight.
* It renders out the shadow map and displays it on a HUD.
*
* This module can only be used with {@link WebGPURenderer}. When using {@link WebGLRenderer},
* import the class from `ShadowMapViewer.js`.
*
* ```js
* const lightShadowMapViewer = new ShadowMapViewer( light );
* lightShadowMapViewer.position.x = 10;
* lightShadowMapViewer.position.y = SCREEN_HEIGHT - ( SHADOW_MAP_HEIGHT / 4 ) - 10;
* lightShadowMapViewer.size.width = SHADOW_MAP_WIDTH / 4;
* lightShadowMapViewer.size.height = SHADOW_MAP_HEIGHT / 4;
* lightShadowMapViewer.update();
* ```
*
* @three_import import { ShadowMapViewer } from 'three/addons/utils/ShadowMapViewerGPU.js';
*/
class ShadowMapViewer {
/**
* Constructs a new shadow map viewer.
*
* @param {Light} light - The shadow casting light.
*/
constructor( light ) {
//- Internals
const scope = this;
const doRenderLabel = ( light.name !== undefined && light.name !== '' );
let currentAutoClear;
//Holds the initial position and dimension of the HUD
const frame = {
x: 10,
y: 10,
width: 256,
height: 256
};
const camera = new OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, 1, 10 );
camera.position.set( 0, 0, 2 );
const scene = new Scene();
//HUD for shadow map
const material = new NodeMaterial();
const textureDimension = uniform( new Vector2() );
const shadowMapUniform = textureLoad( new DepthTexture(), uv().flipY().mul( textureDimension ) );
material.fragmentNode = shadowMapUniform.x.oneMinus();
const plane = new PlaneGeometry( frame.width, frame.height );
const mesh = new Mesh( plane, material );
scene.add( mesh );
//Label for light's name
let labelCanvas, labelMesh;
if ( doRenderLabel ) {
labelCanvas = document.createElement( 'canvas' );
const context = labelCanvas.getContext( '2d' );
context.font = 'Bold 20px Arial';
const labelWidth = context.measureText( light.name ).width;
labelCanvas.width = labelWidth;
labelCanvas.height = 25; //25 to account for g, p, etc.
context.font = 'Bold 20px Arial';
context.fillStyle = 'rgba( 255, 0, 0, 1 )';
context.fillText( light.name, 0, 20 );
const labelTexture = new CanvasTexture( labelCanvas );
const labelMaterial = new MeshBasicMaterial( { map: labelTexture, side: DoubleSide, transparent: true } );
const labelPlane = new PlaneGeometry( labelCanvas.width, labelCanvas.height );
labelMesh = new Mesh( labelPlane, labelMaterial );
scene.add( labelMesh );
}
function resetPosition() {
scope.position.set( scope.position.x, scope.position.y );
}
/**
* Whether to display the shadow map viewer or not.
*
* @type {boolean}
* @default true
*/
this.enabled = true;
/**
* The size of the viewer. When changing this property, make sure
* to call {@link ShadowMapViewer#update}.
*
* @type {{width:number,height:number}}
* @default true
*/
this.size = {
width: frame.width,
height: frame.height,
set: function ( width, height ) {
this.width = width;
this.height = height;
mesh.scale.set( this.width / frame.width, this.height / frame.height, 1 );
//Reset the position as it is off when we scale stuff
resetPosition();
}
};
/**
* The position of the viewer. When changing this property, make sure
* to call {@link ShadowMapViewer#update}.
*
* @type {{width:number,height:number}}
* @default true
*/
this.position = {
x: frame.x,
y: frame.y,
set: function ( x, y ) {
this.x = x;
this.y = y;
const width = scope.size.width;
const height = scope.size.height;
mesh.position.set( - window.innerWidth / 2 + width / 2 + this.x, window.innerHeight / 2 - height / 2 - this.y, 0 );
if ( doRenderLabel ) labelMesh.position.set( mesh.position.x, mesh.position.y - scope.size.height / 2 + labelCanvas.height / 2, 0 );
}
};
/**
* Renders the viewer. This method must be called in the app's animation loop.
*
* @param {WebGPURenderer} renderer - The renderer.
*/
this.render = function ( renderer ) {
if ( this.enabled ) {
//Because a light's .shadowMap is only initialised after the first render pass
//we have to make sure the correct map is sent into the shader, otherwise we
//always end up with the scene's first added shadow casting light's shadowMap
//in the shader
//See: https://github.com/mrdoob/three.js/issues/5932
const depthTexture = light.shadow.map.depthTexture;
shadowMapUniform.value = depthTexture;
textureDimension.value.set( depthTexture.width, depthTexture.height );
currentAutoClear = renderer.autoClear;
renderer.autoClear = false; // To allow render overlay
renderer.clearDepth();
renderer.render( scene, camera );
renderer.autoClear = currentAutoClear;
}
};
/**
* Resizes the viewer. This method should be called whenever the app's
* window is resized.
*/
this.updateForWindowResize = function () {
if ( this.enabled ) {
camera.left = window.innerWidth / - 2;
camera.right = window.innerWidth / 2;
camera.top = window.innerHeight / 2;
camera.bottom = window.innerHeight / - 2;
camera.updateProjectionMatrix();
this.update();
}
};
/**
* Updates the viewer.
*/
this.update = function () {
this.position.set( this.position.x, this.position.y );
this.size.set( this.size.width, this.size.height );
};
//Force an update to set position/size
this.update();
}
}
export { ShadowMapViewer };

490
node_modules/three/examples/jsm/utils/SkeletonUtils.js generated vendored Normal file
View File

@@ -0,0 +1,490 @@
import {
AnimationClip,
AnimationMixer,
Matrix4,
Quaternion,
QuaternionKeyframeTrack,
SkeletonHelper,
Vector3,
VectorKeyframeTrack
} from 'three';
/**
* @module SkeletonUtils
* @three_import import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js';
*/
function getBoneName( bone, options ) {
if ( options.getBoneName !== undefined ) {
return options.getBoneName( bone );
}
return options.names[ bone.name ];
}
/**
* Retargets the skeleton from the given source 3D object to the
* target 3D object.
*
* @param {Object3D} target - The target 3D object.
* @param {Object3D} source - The source 3D object.
* @param {module:SkeletonUtils~RetargetOptions} options - The options.
*/
function retarget( target, source, options = {} ) {
const quat = new Quaternion(),
scale = new Vector3(),
relativeMatrix = new Matrix4(),
globalMatrix = new Matrix4();
options.preserveBoneMatrix = options.preserveBoneMatrix !== undefined ? options.preserveBoneMatrix : true;
options.preserveBonePositions = options.preserveBonePositions !== undefined ? options.preserveBonePositions : true;
options.useTargetMatrix = options.useTargetMatrix !== undefined ? options.useTargetMatrix : false;
options.hip = options.hip !== undefined ? options.hip : 'hip';
options.hipInfluence = options.hipInfluence !== undefined ? options.hipInfluence : new Vector3( 1, 1, 1 );
options.scale = options.scale !== undefined ? options.scale : 1;
options.names = options.names || {};
const sourceBones = source.isObject3D ? source.skeleton.bones : getBones( source ),
bones = target.isObject3D ? target.skeleton.bones : getBones( target );
let bone, name, boneTo,
bonesPosition;
// reset bones
if ( target.isObject3D ) {
target.skeleton.pose();
} else {
options.useTargetMatrix = true;
options.preserveBoneMatrix = false;
}
if ( options.preserveBonePositions ) {
bonesPosition = [];
for ( let i = 0; i < bones.length; i ++ ) {
bonesPosition.push( bones[ i ].position.clone() );
}
}
if ( options.preserveBoneMatrix ) {
// reset matrix
target.updateMatrixWorld();
target.matrixWorld.identity();
// reset children matrix
for ( let i = 0; i < target.children.length; ++ i ) {
target.children[ i ].updateMatrixWorld( true );
}
}
for ( let i = 0; i < bones.length; ++ i ) {
bone = bones[ i ];
name = getBoneName( bone, options );
boneTo = getBoneByName( name, sourceBones );
globalMatrix.copy( bone.matrixWorld );
if ( boneTo ) {
boneTo.updateMatrixWorld();
if ( options.useTargetMatrix ) {
relativeMatrix.copy( boneTo.matrixWorld );
} else {
relativeMatrix.copy( target.matrixWorld ).invert();
relativeMatrix.multiply( boneTo.matrixWorld );
}
// ignore scale to extract rotation
scale.setFromMatrixScale( relativeMatrix );
relativeMatrix.scale( scale.set( 1 / scale.x, 1 / scale.y, 1 / scale.z ) );
// apply to global matrix
globalMatrix.makeRotationFromQuaternion( quat.setFromRotationMatrix( relativeMatrix ) );
if ( target.isObject3D ) {
if ( options.localOffsets ) {
if ( options.localOffsets[ bone.name ] ) {
globalMatrix.multiply( options.localOffsets[ bone.name ] );
}
}
}
globalMatrix.copyPosition( relativeMatrix );
}
if ( name === options.hip ) {
globalMatrix.elements[ 12 ] *= options.scale * options.hipInfluence.x;
globalMatrix.elements[ 13 ] *= options.scale * options.hipInfluence.y;
globalMatrix.elements[ 14 ] *= options.scale * options.hipInfluence.z;
if ( options.hipPosition !== undefined ) {
globalMatrix.elements[ 12 ] += options.hipPosition.x * options.scale;
globalMatrix.elements[ 13 ] += options.hipPosition.y * options.scale;
globalMatrix.elements[ 14 ] += options.hipPosition.z * options.scale;
}
}
if ( bone.parent ) {
bone.matrix.copy( bone.parent.matrixWorld ).invert();
bone.matrix.multiply( globalMatrix );
} else {
bone.matrix.copy( globalMatrix );
}
bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
bone.updateMatrixWorld();
}
if ( options.preserveBonePositions ) {
for ( let i = 0; i < bones.length; ++ i ) {
bone = bones[ i ];
name = getBoneName( bone, options ) || bone.name;
if ( name !== options.hip ) {
bone.position.copy( bonesPosition[ i ] );
}
}
}
if ( options.preserveBoneMatrix ) {
// restore matrix
target.updateMatrixWorld( true );
}
}
/**
* Retargets the animation clip of the source object to the
* target 3D object.
*
* @param {Object3D} target - The target 3D object.
* @param {Object3D} source - The source 3D object.
* @param {AnimationClip} clip - The animation clip.
* @param {module:SkeletonUtils~RetargetOptions} options - The options.
* @return {AnimationClip} The retargeted animation clip.
*/
function retargetClip( target, source, clip, options = {} ) {
options.useFirstFramePosition = options.useFirstFramePosition !== undefined ? options.useFirstFramePosition : false;
// Calculate the fps from the source clip based on the track with the most frames, unless fps is already provided.
options.fps = options.fps !== undefined ? options.fps : ( Math.max( ...clip.tracks.map( track => track.times.length ) ) / clip.duration );
options.names = options.names || [];
if ( ! source.isObject3D ) {
source = getHelperFromSkeleton( source );
}
const numFrames = Math.round( clip.duration * ( options.fps / 1000 ) * 1000 ),
delta = clip.duration / ( numFrames - 1 ),
convertedTracks = [],
mixer = new AnimationMixer( source ),
bones = getBones( target.skeleton ),
boneDatas = [];
let positionOffset,
bone, boneTo, boneData,
name;
mixer.clipAction( clip ).play();
// trim
let start = 0, end = numFrames;
if ( options.trim !== undefined ) {
start = Math.round( options.trim[ 0 ] * options.fps );
end = Math.min( Math.round( options.trim[ 1 ] * options.fps ), numFrames ) - start;
mixer.update( options.trim[ 0 ] );
} else {
mixer.update( 0 );
}
source.updateMatrixWorld();
//
for ( let frame = 0; frame < end; ++ frame ) {
const time = frame * delta;
retarget( target, source, options );
for ( let j = 0; j < bones.length; ++ j ) {
bone = bones[ j ];
name = getBoneName( bone, options ) || bone.name;
boneTo = getBoneByName( name, source.skeleton );
if ( boneTo ) {
boneData = boneDatas[ j ] = boneDatas[ j ] || { bone: bone };
if ( options.hip === name ) {
if ( ! boneData.pos ) {
boneData.pos = {
times: new Float32Array( end ),
values: new Float32Array( end * 3 )
};
}
if ( options.useFirstFramePosition ) {
if ( frame === 0 ) {
positionOffset = bone.position.clone();
}
bone.position.sub( positionOffset );
}
boneData.pos.times[ frame ] = time;
bone.position.toArray( boneData.pos.values, frame * 3 );
}
if ( ! boneData.quat ) {
boneData.quat = {
times: new Float32Array( end ),
values: new Float32Array( end * 4 )
};
}
boneData.quat.times[ frame ] = time;
bone.quaternion.toArray( boneData.quat.values, frame * 4 );
}
}
if ( frame === end - 2 ) {
// last mixer update before final loop iteration
// make sure we do not go over or equal to clip duration
mixer.update( delta - 0.0000001 );
} else {
mixer.update( delta );
}
source.updateMatrixWorld();
}
for ( let i = 0; i < boneDatas.length; ++ i ) {
boneData = boneDatas[ i ];
if ( boneData ) {
if ( boneData.pos ) {
convertedTracks.push( new VectorKeyframeTrack(
'.bones[' + boneData.bone.name + '].position',
boneData.pos.times,
boneData.pos.values
) );
}
convertedTracks.push( new QuaternionKeyframeTrack(
'.bones[' + boneData.bone.name + '].quaternion',
boneData.quat.times,
boneData.quat.values
) );
}
}
mixer.uncacheAction( clip );
return new AnimationClip( clip.name, - 1, convertedTracks );
}
/**
* Clones the given 3D object and its descendants, ensuring that any `SkinnedMesh` instances are
* correctly associated with their bones. Bones are also cloned, and must be descendants of the
* object passed to this method. Other data, like geometries and materials, are reused by reference.
*
* @param {Object3D} source - The 3D object to clone.
* @return {Object3D} The cloned 3D object.
*/
function clone( source ) {
const sourceLookup = new Map();
const cloneLookup = new Map();
const clone = source.clone();
parallelTraverse( source, clone, function ( sourceNode, clonedNode ) {
sourceLookup.set( clonedNode, sourceNode );
cloneLookup.set( sourceNode, clonedNode );
} );
clone.traverse( function ( node ) {
if ( ! node.isSkinnedMesh ) return;
const clonedMesh = node;
const sourceMesh = sourceLookup.get( node );
const sourceBones = sourceMesh.skeleton.bones;
clonedMesh.skeleton = sourceMesh.skeleton.clone();
clonedMesh.bindMatrix.copy( sourceMesh.bindMatrix );
clonedMesh.skeleton.bones = sourceBones.map( function ( bone ) {
return cloneLookup.get( bone );
} );
clonedMesh.bind( clonedMesh.skeleton, clonedMesh.bindMatrix );
} );
return clone;
}
// internal helper
function getBoneByName( name, skeleton ) {
for ( let i = 0, bones = getBones( skeleton ); i < bones.length; i ++ ) {
if ( name === bones[ i ].name )
return bones[ i ];
}
}
function getBones( skeleton ) {
return Array.isArray( skeleton ) ? skeleton : skeleton.bones;
}
function getHelperFromSkeleton( skeleton ) {
const source = new SkeletonHelper( skeleton.bones[ 0 ] );
source.skeleton = skeleton;
return source;
}
function parallelTraverse( a, b, callback ) {
callback( a, b );
for ( let i = 0; i < a.children.length; i ++ ) {
parallelTraverse( a.children[ i ], b.children[ i ], callback );
}
}
/**
* Retarget options of `SkeletonUtils`.
*
* @typedef {Object} module:SkeletonUtils~RetargetOptions
* @property {boolean} [useFirstFramePosition=false] - Whether to use the position of the first frame or not.
* @property {number} [fps] - The FPS of the clip.
* @property {Object<string,string>} [names] - A dictionary for mapping target to source bone names.
* @property {function(string):string} [getBoneName] - A function for mapping bone names. Alternative to `names`.
* @property {Array<number>} [trim] - Whether to trim the clip or not. If set the array should hold two values for the start and end.
* @property {boolean} [preserveBoneMatrix=true] - Whether to preserve bone matrices or not.
* @property {boolean} [preserveBonePositions=true] - Whether to preserve bone positions or not.
* @property {boolean} [useTargetMatrix=false] - Whether to use the target matrix or not.
* @property {string} [hip='hip'] - The name of the source's hip bone.
* @property {Vector3} [hipInfluence=(1,1,1)] - The hip influence.
* @property {number} [scale=1] - The scale.
**/
export {
retarget,
retargetClip,
clone,
};

175
node_modules/three/examples/jsm/utils/SortUtils.js generated vendored Normal file
View File

@@ -0,0 +1,175 @@
/**
* @module SortUtils
* @three_import import * as SortUtils from 'three/addons/utils/SortUtils.js';
*/
const POWER = 3;
const BIT_MAX = 32;
const BIN_BITS = 1 << POWER;
const BIN_SIZE = 1 << BIN_BITS;
const BIN_MAX = BIN_SIZE - 1;
const ITERATIONS = BIT_MAX / BIN_BITS;
const bins = new Array( ITERATIONS );
const bins_buffer = new ArrayBuffer( ( ITERATIONS + 1 ) * BIN_SIZE * 4 );
let c = 0;
for ( let i = 0; i < ( ITERATIONS + 1 ); i ++ ) {
bins[ i ] = new Uint32Array( bins_buffer, c, BIN_SIZE );
c += BIN_SIZE * 4;
}
const defaultGet = ( el ) => el;
/**
* Hybrid radix sort from.
*
* - {@link https://gist.github.com/sciecode/93ed864dd77c5c8803c6a86698d68dab}
* - {@link https://github.com/mrdoob/three.js/pull/27202#issuecomment-1817640271}
*
* Expects unsigned 32b integer values.
*
* @function
* @param {Array<Object>} arr - The array to sort.
* @param {Object} opt - The options
*/
export const radixSort = ( arr, opt ) => {
const len = arr.length;
const options = opt || {};
const aux = options.aux || new arr.constructor( len );
const get = options.get || defaultGet;
const data = [ arr, aux ];
let compare, accumulate, recurse;
if ( options.reversed ) {
compare = ( a, b ) => a < b;
accumulate = ( bin ) => {
for ( let j = BIN_SIZE - 2; j >= 0; j -- )
bin[ j ] += bin[ j + 1 ];
};
recurse = ( cache, depth, start ) => {
let prev = 0;
for ( let j = BIN_MAX; j >= 0; j -- ) {
const cur = cache[ j ], diff = cur - prev;
if ( diff != 0 ) {
if ( diff > 32 )
radixSortBlock( depth + 1, start + prev, diff );
else
insertionSortBlock( depth + 1, start + prev, diff );
prev = cur;
}
}
};
} else {
compare = ( a, b ) => a > b;
accumulate = ( bin ) => {
for ( let j = 1; j < BIN_SIZE; j ++ )
bin[ j ] += bin[ j - 1 ];
};
recurse = ( cache, depth, start ) => {
let prev = 0;
for ( let j = 0; j < BIN_SIZE; j ++ ) {
const cur = cache[ j ], diff = cur - prev;
if ( diff != 0 ) {
if ( diff > 32 )
radixSortBlock( depth + 1, start + prev, diff );
else
insertionSortBlock( depth + 1, start + prev, diff );
prev = cur;
}
}
};
}
const insertionSortBlock = ( depth, start, len ) => {
const a = data[ depth & 1 ];
const b = data[ ( depth + 1 ) & 1 ];
for ( let j = start + 1; j < start + len; j ++ ) {
const p = a[ j ], t = get( p ) >>> 0;
let i = j;
while ( i > start ) {
if ( compare( get( a[ i - 1 ] ) >>> 0, t ) )
a[ i ] = a[ -- i ];
else
break;
}
a[ i ] = p;
}
if ( ( depth & 1 ) == 1 ) {
for ( let i = start; i < start + len; i ++ )
b[ i ] = a[ i ];
}
};
const radixSortBlock = ( depth, start, len ) => {
const a = data[ depth & 1 ];
const b = data[ ( depth + 1 ) & 1 ];
const shift = ( 3 - depth ) << POWER;
const end = start + len;
const cache = bins[ depth ];
const bin = bins[ depth + 1 ];
bin.fill( 0 );
for ( let j = start; j < end; j ++ )
bin[ ( get( a[ j ] ) >>> shift ) & BIN_MAX ] ++;
accumulate( bin );
cache.set( bin );
for ( let j = end - 1; j >= start; j -- )
b[ start + -- bin[ ( get( a[ j ] ) >>> shift ) & BIN_MAX ] ] = a[ j ];
if ( depth == ITERATIONS - 1 ) return;
recurse( cache, depth, start );
};
radixSortBlock( 0, 0, len );
};

173
node_modules/three/examples/jsm/utils/UVsDebug.js generated vendored Normal file
View File

@@ -0,0 +1,173 @@
import {
Vector2
} from 'three';
/**
* @module UVsDebug
* @three_import import { UVsDebug } from 'three/addons/utils/UVsDebug.js';
*/
/**
* Function for "unwrapping" and debugging three.js geometries UV mapping.
*
* ```js
* document.body.appendChild( UVsDebug( new THREE.SphereGeometry() ) );
* ```
*
* @param {BufferGeometry} geometry - The geometry whose uv coordinates should be inspected.
* @param {number} [size=1024] - The size of the debug canvas.
* @return {HTMLCanvasElement} A canvas element with visualized uv coordinates.
*/
function UVsDebug( geometry, size = 1024 ) {
// handles wrapping of uv.x > 1 only
const abc = 'abc';
const a = new Vector2();
const b = new Vector2();
const uvs = [
new Vector2(),
new Vector2(),
new Vector2()
];
const face = [];
const canvas = document.createElement( 'canvas' );
const width = size; // power of 2 required for wrapping
const height = size;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext( '2d' );
ctx.lineWidth = 1;
ctx.strokeStyle = 'rgb( 63, 63, 63 )';
ctx.textAlign = 'center';
// paint background white
ctx.fillStyle = 'rgb( 255, 255, 255 )';
ctx.fillRect( 0, 0, width, height );
const index = geometry.index;
const uvAttribute = geometry.attributes.uv;
if ( index ) {
// indexed geometry
for ( let i = 0, il = index.count; i < il; i += 3 ) {
face[ 0 ] = index.getX( i );
face[ 1 ] = index.getX( i + 1 );
face[ 2 ] = index.getX( i + 2 );
uvs[ 0 ].fromBufferAttribute( uvAttribute, face[ 0 ] );
uvs[ 1 ].fromBufferAttribute( uvAttribute, face[ 1 ] );
uvs[ 2 ].fromBufferAttribute( uvAttribute, face[ 2 ] );
processFace( face, uvs, i / 3 );
}
} else {
// non-indexed geometry
for ( let i = 0, il = uvAttribute.count; i < il; i += 3 ) {
face[ 0 ] = i;
face[ 1 ] = i + 1;
face[ 2 ] = i + 2;
uvs[ 0 ].fromBufferAttribute( uvAttribute, face[ 0 ] );
uvs[ 1 ].fromBufferAttribute( uvAttribute, face[ 1 ] );
uvs[ 2 ].fromBufferAttribute( uvAttribute, face[ 2 ] );
processFace( face, uvs, i / 3 );
}
}
return canvas;
function processFace( face, uvs, index ) {
// draw contour of face
ctx.beginPath();
a.set( 0, 0 );
for ( let j = 0, jl = uvs.length; j < jl; j ++ ) {
const uv = uvs[ j ];
a.x += uv.x;
a.y += uv.y;
if ( j === 0 ) {
ctx.moveTo( uv.x * ( width - 2 ) + 0.5, ( 1 - uv.y ) * ( height - 2 ) + 0.5 );
} else {
ctx.lineTo( uv.x * ( width - 2 ) + 0.5, ( 1 - uv.y ) * ( height - 2 ) + 0.5 );
}
}
ctx.closePath();
ctx.stroke();
// calculate center of face
a.divideScalar( uvs.length );
// label the face number
ctx.font = '18px Arial';
ctx.fillStyle = 'rgb( 63, 63, 63 )';
ctx.fillText( index, a.x * width, ( 1 - a.y ) * height );
if ( a.x > 0.95 ) {
// wrap x // 0.95 is arbitrary
ctx.fillText( index, ( a.x % 1 ) * width, ( 1 - a.y ) * height );
}
//
ctx.font = '12px Arial';
ctx.fillStyle = 'rgb( 191, 191, 191 )';
// label uv edge orders
for ( let j = 0, jl = uvs.length; j < jl; j ++ ) {
const uv = uvs[ j ];
b.addVectors( a, uv ).divideScalar( 2 );
const vnum = face[ j ];
ctx.fillText( abc[ j ] + vnum, b.x * width, ( 1 - b.y ) * height );
if ( b.x > 0.95 ) {
// wrap x
ctx.fillText( abc[ j ] + vnum, ( b.x % 1 ) * width, ( 1 - b.y ) * height );
}
}
}
}
export { UVsDebug };

View File

@@ -0,0 +1,115 @@
import {
PlaneGeometry,
ShaderMaterial,
Uniform,
Mesh,
PerspectiveCamera,
Scene,
WebGLRenderer,
CanvasTexture,
SRGBColorSpace
} from 'three';
/**
* @module WebGLTextureUtils
* @three_import import * as WebGLTextureUtils from 'three/addons/utils/WebGLTextureUtils.js';
*/
let _renderer;
let fullscreenQuadGeometry;
let fullscreenQuadMaterial;
let fullscreenQuad;
/**
* Returns an uncompressed version of the given compressed texture.
*
* This module can only be used with {@link WebGLRenderer}. When using {@link WebGPURenderer},
* import the function from {@link WebGPUTextureUtils}.
*
* @param {CompressedTexture} texture - The compressed texture.
* @param {number} [maxTextureSize=Infinity] - The maximum size of the uncompressed texture.
* @param {?WebGLRenderer} [renderer=null] - A reference to a renderer.
* @return {CanvasTexture} The uncompressed texture.
*/
export function decompress( texture, maxTextureSize = Infinity, renderer = null ) {
if ( ! fullscreenQuadGeometry ) fullscreenQuadGeometry = new PlaneGeometry( 2, 2, 1, 1 );
if ( ! fullscreenQuadMaterial ) fullscreenQuadMaterial = new ShaderMaterial( {
uniforms: { blitTexture: new Uniform( texture ) },
vertexShader: `
varying vec2 vUv;
void main(){
vUv = uv;
gl_Position = vec4(position.xy * 1.0,0.,.999999);
}`,
fragmentShader: `
uniform sampler2D blitTexture;
varying vec2 vUv;
void main(){
gl_FragColor = vec4(vUv.xy, 0, 1);
#ifdef IS_SRGB
gl_FragColor = sRGBTransferOETF( texture2D( blitTexture, vUv) );
#else
gl_FragColor = texture2D( blitTexture, vUv);
#endif
}`
} );
fullscreenQuadMaterial.uniforms.blitTexture.value = texture;
fullscreenQuadMaterial.defines.IS_SRGB = texture.colorSpace == SRGBColorSpace;
fullscreenQuadMaterial.needsUpdate = true;
if ( ! fullscreenQuad ) {
fullscreenQuad = new Mesh( fullscreenQuadGeometry, fullscreenQuadMaterial );
fullscreenQuad.frustumCulled = false;
}
const _camera = new PerspectiveCamera();
const _scene = new Scene();
_scene.add( fullscreenQuad );
if ( renderer === null ) {
renderer = _renderer = new WebGLRenderer( { antialias: false } );
}
const width = Math.min( texture.image.width, maxTextureSize );
const height = Math.min( texture.image.height, maxTextureSize );
renderer.setSize( width, height );
renderer.clear();
renderer.render( _scene, _camera );
const canvas = document.createElement( 'canvas' );
const context = canvas.getContext( '2d' );
canvas.width = width;
canvas.height = height;
context.drawImage( renderer.domElement, 0, 0, width, height );
const readableTexture = new CanvasTexture( canvas );
readableTexture.minFilter = texture.minFilter;
readableTexture.magFilter = texture.magFilter;
readableTexture.wrapS = texture.wrapS;
readableTexture.wrapT = texture.wrapT;
readableTexture.colorSpace = texture.colorSpace;
readableTexture.name = texture.name;
if ( _renderer ) {
_renderer.forceContextLoss();
_renderer.dispose();
_renderer = null;
}
return readableTexture;
}

View File

@@ -0,0 +1,81 @@
import {
QuadMesh,
NodeMaterial,
WebGPURenderer,
CanvasTexture
} from 'three';
import { texture, uv } from 'three/tsl';
/**
* @module WebGPUTextureUtils
* @three_import import * as WebGPUTextureUtils from 'three/addons/utils/WebGPUTextureUtils.js';
*/
let _renderer;
const _quadMesh = /*@__PURE__*/ new QuadMesh();
/**
* Returns an uncompressed version of the given compressed texture.
*
* This module can only be used with {@link WebGPURenderer}. When using {@link WebGLRenderer},
* import the function from {@link WebGLTextureUtils}.
*
* @async
* @param {CompressedTexture} blitTexture - The compressed texture.
* @param {number} [maxTextureSize=Infinity] - The maximum size of the uncompressed texture.
* @param {?WebGPURenderer} [renderer=null] - A reference to a renderer.
* @return {Promise<CanvasTexture>} A Promise that resolved with the uncompressed texture.
*/
export async function decompress( blitTexture, maxTextureSize = Infinity, renderer = null ) {
if ( renderer === null ) {
renderer = _renderer = new WebGPURenderer();
await renderer.init();
}
const material = new NodeMaterial();
material.fragmentNode = texture( blitTexture, uv().flipY() );
const width = Math.min( blitTexture.image.width, maxTextureSize );
const height = Math.min( blitTexture.image.height, maxTextureSize );
const currentOutputColorSpace = renderer.outputColorSpace;
renderer.setSize( width, height );
renderer.outputColorSpace = blitTexture.colorSpace;
_quadMesh.material = material;
_quadMesh.render( renderer );
renderer.outputColorSpace = currentOutputColorSpace;
const canvas = document.createElement( 'canvas' );
const context = canvas.getContext( '2d' );
canvas.width = width;
canvas.height = height;
context.drawImage( renderer.domElement, 0, 0, width, height );
const readableTexture = new CanvasTexture( canvas );
readableTexture.minFilter = blitTexture.minFilter;
readableTexture.magFilter = blitTexture.magFilter;
readableTexture.wrapS = blitTexture.wrapS;
readableTexture.wrapT = blitTexture.wrapT;
readableTexture.colorSpace = blitTexture.colorSpace;
readableTexture.name = blitTexture.name;
if ( _renderer !== null ) {
_renderer.dispose();
_renderer = null;
}
return readableTexture;
}

167
node_modules/three/examples/jsm/utils/WorkerPool.js generated vendored Normal file
View File

@@ -0,0 +1,167 @@
/**
* A simple pool for managing Web Workers.
*
* @three_import import { WorkerPool } from 'three/addons/utils/WorkerPool.js';
*/
export class WorkerPool {
/**
* Constructs a new Worker pool.
*
* @param {number} [pool=4] - The size of the pool.
*/
constructor( pool = 4 ) {
/**
* The size of the pool.
*
* @type {number}
* @default 4
*/
this.pool = pool;
/**
* A message queue.
*
* @type {Array<Object>}
*/
this.queue = [];
/**
* An array of Workers.
*
* @type {Array<Worker>}
*/
this.workers = [];
/**
* An array with resolve functions for messages.
*
* @type {Array<Function>}
*/
this.workersResolve = [];
/**
* The current worker status.
*
* @type {number}
*/
this.workerStatus = 0;
/**
* A factory function for creating workers.
*
* @type {?Function}
*/
this.workerCreator = null;
}
_initWorker( workerId ) {
if ( ! this.workers[ workerId ] ) {
const worker = this.workerCreator();
worker.addEventListener( 'message', this._onMessage.bind( this, workerId ) );
this.workers[ workerId ] = worker;
}
}
_getIdleWorker() {
for ( let i = 0; i < this.pool; i ++ )
if ( ! ( this.workerStatus & ( 1 << i ) ) ) return i;
return - 1;
}
_onMessage( workerId, msg ) {
const resolve = this.workersResolve[ workerId ];
resolve && resolve( msg );
if ( this.queue.length ) {
const { resolve, msg, transfer } = this.queue.shift();
this.workersResolve[ workerId ] = resolve;
this.workers[ workerId ].postMessage( msg, transfer );
} else {
this.workerStatus ^= 1 << workerId;
}
}
/**
* Sets a function that is responsible for creating Workers.
*
* @param {Function} workerCreator - The worker creator function.
*/
setWorkerCreator( workerCreator ) {
this.workerCreator = workerCreator;
}
/**
* Sets the Worker limit
*
* @param {number} pool - The size of the pool.
*/
setWorkerLimit( pool ) {
this.pool = pool;
}
/**
* Post a message to an idle Worker. If no Worker is available,
* the message is pushed into a message queue for later processing.
*
* @param {Object} msg - The message.
* @param {Array<ArrayBuffer>} transfer - An array with array buffers for data transfer.
* @return {Promise} A Promise that resolves when the message has been processed.
*/
postMessage( msg, transfer ) {
return new Promise( ( resolve ) => {
const workerId = this._getIdleWorker();
if ( workerId !== - 1 ) {
this._initWorker( workerId );
this.workerStatus |= 1 << workerId;
this.workersResolve[ workerId ] = resolve;
this.workers[ workerId ].postMessage( msg, transfer );
} else {
this.queue.push( { resolve, msg, transfer } );
}
} );
}
/**
* Terminates all Workers of this pool. Call this method whenever this
* Worker pool is no longer used in your app.
*/
dispose() {
this.workers.forEach( ( worker ) => worker.terminate() );
this.workersResolve.length = 0;
this.workers.length = 0;
this.queue.length = 0;
this.workerStatus = 0;
}
}