添加关照、全局等高线、修改图层问题
This commit is contained in:
1371
static/sdk/three/jsm/utils/BufferGeometryUtils.js
Normal file
1371
static/sdk/three/jsm/utils/BufferGeometryUtils.js
Normal file
File diff suppressed because it is too large
Load Diff
73
static/sdk/three/jsm/utils/CameraUtils.js
Normal file
73
static/sdk/three/jsm/utils/CameraUtils.js
Normal file
@ -0,0 +1,73 @@
|
||||
import {
|
||||
MathUtils,
|
||||
Quaternion,
|
||||
Vector3
|
||||
} from 'three';
|
||||
|
||||
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 a PerspectiveCamera's projectionMatrix and quaternion
|
||||
* to exactly frame the corners of an arbitrary rectangle.
|
||||
* NOTE: This function ignores the standard parameters;
|
||||
* do not call updateProjectionMatrix() after this!
|
||||
* @param {Vector3} bottomLeftCorner
|
||||
* @param {Vector3} bottomRightCorner
|
||||
* @param {Vector3} topLeftCorner
|
||||
* @param {boolean} estimateViewFrustum */
|
||||
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 };
|
93
static/sdk/three/jsm/utils/GPUStatsPanel.js
Normal file
93
static/sdk/three/jsm/utils/GPUStatsPanel.js
Normal file
@ -0,0 +1,93 @@
|
||||
import Stats from '../libs/stats.module.js';
|
||||
|
||||
// https://www.khronos.org/registry/webgl/extensions/EXT_disjoint_timer_query_webgl2/
|
||||
export class GPUStatsPanel extends Stats.Panel {
|
||||
|
||||
constructor( context, name = 'GPU MS' ) {
|
||||
|
||||
super( name, '#f90', '#210' );
|
||||
|
||||
const extension = context.getExtension( 'EXT_disjoint_timer_query_webgl2' );
|
||||
|
||||
if ( extension === null ) {
|
||||
|
||||
console.warn( 'GPUStatsPanel: disjoint_time_query extension not available.' );
|
||||
|
||||
}
|
||||
|
||||
this.context = context;
|
||||
this.extension = extension;
|
||||
this.maxTime = 30;
|
||||
this.activeQueries = 0;
|
||||
|
||||
this.startQuery = function () {
|
||||
|
||||
const gl = this.context;
|
||||
const ext = this.extension;
|
||||
|
||||
if ( ext === null ) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// create the query object
|
||||
const query = gl.createQuery();
|
||||
gl.beginQuery( ext.TIME_ELAPSED_EXT, query );
|
||||
|
||||
this.activeQueries ++;
|
||||
|
||||
const checkQuery = () => {
|
||||
|
||||
// check if the query is available and valid
|
||||
|
||||
const available = gl.getQueryParameter( query, gl.QUERY_RESULT_AVAILABLE );
|
||||
const disjoint = gl.getParameter( ext.GPU_DISJOINT_EXT );
|
||||
const ns = gl.getQueryParameter( query, gl.QUERY_RESULT );
|
||||
|
||||
const ms = ns * 1e-6;
|
||||
|
||||
if ( available ) {
|
||||
|
||||
// update the display if it is valid
|
||||
if ( ! disjoint ) {
|
||||
|
||||
this.update( ms, this.maxTime );
|
||||
|
||||
}
|
||||
|
||||
this.activeQueries --;
|
||||
|
||||
|
||||
} else if ( gl.isContextLost() === false ) {
|
||||
|
||||
// otherwise try again the next frame
|
||||
requestAnimationFrame( checkQuery );
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
requestAnimationFrame( checkQuery );
|
||||
|
||||
};
|
||||
|
||||
this.endQuery = function () {
|
||||
|
||||
// finish the query measurement
|
||||
const ext = this.extension;
|
||||
const gl = this.context;
|
||||
|
||||
if ( ext === null ) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
gl.endQuery( ext.TIME_ELAPSED_EXT );
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
639
static/sdk/three/jsm/utils/GeometryCompressionUtils.js
Normal file
639
static/sdk/three/jsm/utils/GeometryCompressionUtils.js
Normal file
@ -0,0 +1,639 @@
|
||||
/**
|
||||
* Octahedron and Quantization encodings based on work by:
|
||||
*
|
||||
* @link https://github.com/tsherif/mesh-quantization-example
|
||||
*
|
||||
*/
|
||||
|
||||
import {
|
||||
BufferAttribute,
|
||||
Matrix3,
|
||||
Matrix4,
|
||||
Vector3
|
||||
} from 'three';
|
||||
import { PackedPhongMaterial } from './PackedPhongMaterial.js';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Make the input mesh.geometry's normal attribute encoded and compressed by 3 different methods.
|
||||
* Also will change the mesh.material to `PackedPhongMaterial` which let the vertex shader program decode the normal data.
|
||||
*
|
||||
* @param {THREE.Mesh} mesh
|
||||
* @param {String} encodeMethod "DEFAULT" || "OCT1Byte" || "OCT2Byte" || "ANGLES"
|
||||
*
|
||||
*/
|
||||
function compressNormals( mesh, encodeMethod ) {
|
||||
|
||||
if ( ! mesh.geometry ) {
|
||||
|
||||
console.error( 'Mesh must contain geometry. ' );
|
||||
|
||||
}
|
||||
|
||||
const normal = mesh.geometry.attributes.normal;
|
||||
|
||||
if ( ! normal ) {
|
||||
|
||||
console.error( 'Geometry must contain normal attribute. ' );
|
||||
|
||||
}
|
||||
|
||||
if ( normal.isPacked ) return;
|
||||
|
||||
if ( normal.itemSize != 3 ) {
|
||||
|
||||
console.error( '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 ];
|
||||
|
||||
}
|
||||
|
||||
mesh.geometry.setAttribute( 'normal', new BufferAttribute( result, 3, true ) );
|
||||
mesh.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 ];
|
||||
|
||||
}
|
||||
|
||||
mesh.geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) );
|
||||
mesh.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 ];
|
||||
|
||||
}
|
||||
|
||||
mesh.geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) );
|
||||
mesh.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 ];
|
||||
|
||||
}
|
||||
|
||||
mesh.geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) );
|
||||
mesh.geometry.attributes.normal.bytes = result.length * 2;
|
||||
|
||||
} else {
|
||||
|
||||
console.error( 'Unrecognized encoding method, should be `DEFAULT` or `ANGLES` or `OCT`. ' );
|
||||
|
||||
}
|
||||
|
||||
mesh.geometry.attributes.normal.needsUpdate = true;
|
||||
mesh.geometry.attributes.normal.isPacked = true;
|
||||
mesh.geometry.attributes.normal.packingMethod = encodeMethod;
|
||||
|
||||
// modify material
|
||||
if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) {
|
||||
|
||||
mesh.material = new PackedPhongMaterial().copy( mesh.material );
|
||||
|
||||
}
|
||||
|
||||
if ( encodeMethod == 'ANGLES' ) {
|
||||
|
||||
mesh.material.defines.USE_PACKED_NORMAL = 0;
|
||||
|
||||
}
|
||||
|
||||
if ( encodeMethod == 'OCT1Byte' ) {
|
||||
|
||||
mesh.material.defines.USE_PACKED_NORMAL = 1;
|
||||
|
||||
}
|
||||
|
||||
if ( encodeMethod == 'OCT2Byte' ) {
|
||||
|
||||
mesh.material.defines.USE_PACKED_NORMAL = 1;
|
||||
|
||||
}
|
||||
|
||||
if ( encodeMethod == 'DEFAULT' ) {
|
||||
|
||||
mesh.material.defines.USE_PACKED_NORMAL = 2;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make the input mesh.geometry's position attribute encoded and compressed.
|
||||
* Also will change the mesh.material to `PackedPhongMaterial` which let the vertex shader program decode the position data.
|
||||
*
|
||||
* @param {THREE.Mesh} mesh
|
||||
*
|
||||
*/
|
||||
function compressPositions( mesh ) {
|
||||
|
||||
if ( ! mesh.geometry ) {
|
||||
|
||||
console.error( 'Mesh must contain geometry. ' );
|
||||
|
||||
}
|
||||
|
||||
const position = mesh.geometry.attributes.position;
|
||||
|
||||
if ( ! position ) {
|
||||
|
||||
console.error( 'Geometry must contain position attribute. ' );
|
||||
|
||||
}
|
||||
|
||||
if ( position.isPacked ) return;
|
||||
|
||||
if ( position.itemSize != 3 ) {
|
||||
|
||||
console.error( '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;
|
||||
const decodeMat = result.decodeMat;
|
||||
|
||||
// IMPORTANT: calculate original geometry bounding info first, before updating packed positions
|
||||
if ( mesh.geometry.boundingBox == null ) mesh.geometry.computeBoundingBox();
|
||||
if ( mesh.geometry.boundingSphere == null ) mesh.geometry.computeBoundingSphere();
|
||||
|
||||
mesh.geometry.setAttribute( 'position', new BufferAttribute( quantized, 3 ) );
|
||||
mesh.geometry.attributes.position.isPacked = true;
|
||||
mesh.geometry.attributes.position.needsUpdate = true;
|
||||
mesh.geometry.attributes.position.bytes = quantized.length * encodingBytes;
|
||||
|
||||
// modify material
|
||||
if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) {
|
||||
|
||||
mesh.material = new PackedPhongMaterial().copy( mesh.material );
|
||||
|
||||
}
|
||||
|
||||
mesh.material.defines.USE_PACKED_POSITION = 0;
|
||||
|
||||
mesh.material.uniforms.quantizeMatPos.value = decodeMat;
|
||||
mesh.material.uniforms.quantizeMatPos.needsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the input mesh.geometry's uv attribute encoded and compressed.
|
||||
* Also will change the mesh.material to `PackedPhongMaterial` which let the vertex shader program decode the uv data.
|
||||
*
|
||||
* @param {THREE.Mesh} mesh
|
||||
*
|
||||
*/
|
||||
function compressUvs( mesh ) {
|
||||
|
||||
if ( ! mesh.geometry ) {
|
||||
|
||||
console.error( 'Mesh must contain geometry property. ' );
|
||||
|
||||
}
|
||||
|
||||
const uvs = mesh.geometry.attributes.uv;
|
||||
|
||||
if ( ! uvs ) {
|
||||
|
||||
console.error( '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 ];
|
||||
|
||||
}
|
||||
|
||||
mesh.geometry.setAttribute( 'uv', new BufferAttribute( result, 2, true ) );
|
||||
mesh.geometry.attributes.uv.isPacked = true;
|
||||
mesh.geometry.attributes.uv.needsUpdate = true;
|
||||
mesh.geometry.attributes.uv.bytes = result.length * 2;
|
||||
|
||||
if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) {
|
||||
|
||||
mesh.material = new PackedPhongMaterial().copy( mesh.material );
|
||||
|
||||
}
|
||||
|
||||
mesh.material.defines.USE_PACKED_UV = 0;
|
||||
|
||||
} else {
|
||||
|
||||
// use quantized encoding method
|
||||
result = quantizedEncodeUV( array, 2 );
|
||||
|
||||
mesh.geometry.setAttribute( 'uv', new BufferAttribute( result.quantized, 2 ) );
|
||||
mesh.geometry.attributes.uv.isPacked = true;
|
||||
mesh.geometry.attributes.uv.needsUpdate = true;
|
||||
mesh.geometry.attributes.uv.bytes = result.quantized.length * 2;
|
||||
|
||||
if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) {
|
||||
|
||||
mesh.material = new PackedPhongMaterial().copy( mesh.material );
|
||||
|
||||
}
|
||||
|
||||
mesh.material.defines.USE_PACKED_UV = 1;
|
||||
|
||||
mesh.material.uniforms.quantizeMatUV.value = result.decodeMat;
|
||||
mesh.material.uniforms.quantizeMatUV.needsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 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,
|
||||
};
|
221
static/sdk/three/jsm/utils/GeometryUtils.js
Normal file
221
static/sdk/three/jsm/utils/GeometryUtils.js
Normal file
@ -0,0 +1,221 @@
|
||||
import { Vector3 } from 'three';
|
||||
|
||||
|
||||
/**
|
||||
* Generates 2D-Coordinates in a very fast way.
|
||||
*
|
||||
* Based on work by:
|
||||
* @link http://www.openprocessing.org/sketch/15493
|
||||
*
|
||||
* @param center Center of Hilbert curve.
|
||||
* @param size Total width of Hilbert curve.
|
||||
* @param iterations Number of subdivisions.
|
||||
* @param v0 Corner index -X, -Z.
|
||||
* @param v1 Corner index -X, +Z.
|
||||
* @param v2 Corner index +X, +Z.
|
||||
* @param v3 Corner index +X, -Z.
|
||||
*/
|
||||
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 in a very fast way.
|
||||
*
|
||||
* Based on work by:
|
||||
* @link https://openprocessing.org/user/5654
|
||||
*
|
||||
* @param center Center of Hilbert curve.
|
||||
* @param size Total width of Hilbert curve.
|
||||
* @param iterations Number of subdivisions.
|
||||
* @param v0 Corner index -X, +Y, -Z.
|
||||
* @param v1 Corner index -X, +Y, +Z.
|
||||
* @param v2 Corner index -X, -Y, +Z.
|
||||
* @param v3 Corner index -X, -Y, -Z.
|
||||
* @param v4 Corner index +X, -Y, -Z.
|
||||
* @param v5 Corner index +X, -Y, +Z.
|
||||
* @param v6 Corner index +X, +Y, +Z.
|
||||
* @param v7 Corner index +X, +Y, -Z.
|
||||
*/
|
||||
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)
|
||||
*
|
||||
* https://gist.github.com/nitaku/6521802
|
||||
*
|
||||
* @param size The size of a single gosper island.
|
||||
*/
|
||||
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,
|
||||
};
|
202
static/sdk/three/jsm/utils/LDrawUtils.js
Normal file
202
static/sdk/three/jsm/utils/LDrawUtils.js
Normal file
@ -0,0 +1,202 @@
|
||||
import {
|
||||
BufferAttribute,
|
||||
BufferGeometry,
|
||||
Group,
|
||||
LineSegments,
|
||||
Matrix3,
|
||||
Mesh
|
||||
} from 'three';
|
||||
|
||||
import { mergeGeometries } from './BufferGeometryUtils.js';
|
||||
|
||||
class LDrawUtils {
|
||||
|
||||
static mergeObject( object ) {
|
||||
|
||||
// Merges geometries in object by materials and returns new 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.
|
||||
|
||||
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 };
|
178
static/sdk/three/jsm/utils/PackedPhongMaterial.js
Normal file
178
static/sdk/three/jsm/utils/PackedPhongMaterial.js
Normal file
@ -0,0 +1,178 @@
|
||||
|
||||
/**
|
||||
* `PackedPhongMaterial` inherited from THREE.MeshPhongMaterial
|
||||
*
|
||||
* @param {Object} parameters
|
||||
*/
|
||||
import {
|
||||
MeshPhongMaterial,
|
||||
ShaderChunk,
|
||||
ShaderLib,
|
||||
UniformsUtils,
|
||||
} from 'three';
|
||||
|
||||
class PackedPhongMaterial extends MeshPhongMaterial {
|
||||
|
||||
constructor( parameters ) {
|
||||
|
||||
super();
|
||||
|
||||
this.defines = {};
|
||||
this.type = 'PackedPhongMaterial';
|
||||
this.uniforms = UniformsUtils.merge( [
|
||||
|
||||
ShaderLib.phong.uniforms,
|
||||
|
||||
{
|
||||
quantizeMatPos: { value: null },
|
||||
quantizeMatUV: { value: null }
|
||||
}
|
||||
|
||||
] );
|
||||
|
||||
this.vertexShader = [
|
||||
'#define PHONG',
|
||||
|
||||
'varying vec3 vViewPosition;',
|
||||
|
||||
ShaderChunk.common,
|
||||
ShaderChunk.uv_pars_vertex,
|
||||
ShaderChunk.displacementmap_pars_vertex,
|
||||
ShaderChunk.envmap_pars_vertex,
|
||||
ShaderChunk.color_pars_vertex,
|
||||
ShaderChunk.fog_pars_vertex,
|
||||
ShaderChunk.normal_pars_vertex,
|
||||
ShaderChunk.morphtarget_pars_vertex,
|
||||
ShaderChunk.skinning_pars_vertex,
|
||||
ShaderChunk.shadowmap_pars_vertex,
|
||||
ShaderChunk.logdepthbuf_pars_vertex,
|
||||
ShaderChunk.clipping_planes_pars_vertex,
|
||||
|
||||
`#ifdef USE_PACKED_NORMAL
|
||||
#if USE_PACKED_NORMAL == 0
|
||||
vec3 decodeNormal(vec3 packedNormal)
|
||||
{
|
||||
float x = packedNormal.x * 2.0 - 1.0;
|
||||
float y = packedNormal.y * 2.0 - 1.0;
|
||||
vec2 scth = vec2(sin(x * PI), cos(x * PI));
|
||||
vec2 scphi = vec2(sqrt(1.0 - y * y), y);
|
||||
return normalize( vec3(scth.y * scphi.x, scth.x * scphi.x, scphi.y) );
|
||||
}
|
||||
#endif
|
||||
|
||||
#if USE_PACKED_NORMAL == 1
|
||||
vec3 decodeNormal(vec3 packedNormal)
|
||||
{
|
||||
vec3 v = vec3(packedNormal.xy, 1.0 - abs(packedNormal.x) - abs(packedNormal.y));
|
||||
if (v.z < 0.0)
|
||||
{
|
||||
v.xy = (1.0 - abs(v.yx)) * vec2((v.x >= 0.0) ? +1.0 : -1.0, (v.y >= 0.0) ? +1.0 : -1.0);
|
||||
}
|
||||
return normalize(v);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if USE_PACKED_NORMAL == 2
|
||||
vec3 decodeNormal(vec3 packedNormal)
|
||||
{
|
||||
vec3 v = (packedNormal * 2.0) - 1.0;
|
||||
return normalize(v);
|
||||
}
|
||||
#endif
|
||||
#endif`,
|
||||
|
||||
`#ifdef USE_PACKED_POSITION
|
||||
#if USE_PACKED_POSITION == 0
|
||||
uniform mat4 quantizeMatPos;
|
||||
#endif
|
||||
#endif`,
|
||||
|
||||
`#ifdef USE_PACKED_UV
|
||||
#if USE_PACKED_UV == 1
|
||||
uniform mat3 quantizeMatUV;
|
||||
#endif
|
||||
#endif`,
|
||||
|
||||
`#ifdef USE_PACKED_UV
|
||||
#if USE_PACKED_UV == 0
|
||||
vec2 decodeUV(vec2 packedUV)
|
||||
{
|
||||
vec2 uv = (packedUV * 2.0) - 1.0;
|
||||
return uv;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if USE_PACKED_UV == 1
|
||||
vec2 decodeUV(vec2 packedUV)
|
||||
{
|
||||
vec2 uv = ( vec3(packedUV, 1.0) * quantizeMatUV ).xy;
|
||||
return uv;
|
||||
}
|
||||
#endif
|
||||
#endif`,
|
||||
|
||||
'void main() {',
|
||||
|
||||
ShaderChunk.uv_vertex,
|
||||
|
||||
`#ifdef USE_MAP
|
||||
#ifdef USE_PACKED_UV
|
||||
vMapUv = decodeUV(vMapUv);
|
||||
#endif
|
||||
#endif`,
|
||||
|
||||
ShaderChunk.color_vertex,
|
||||
ShaderChunk.morphcolor_vertex,
|
||||
|
||||
ShaderChunk.beginnormal_vertex,
|
||||
|
||||
`#ifdef USE_PACKED_NORMAL
|
||||
objectNormal = decodeNormal(objectNormal);
|
||||
#endif
|
||||
|
||||
#ifdef USE_TANGENT
|
||||
vec3 objectTangent = vec3( tangent.xyz );
|
||||
#endif
|
||||
`,
|
||||
|
||||
ShaderChunk.morphnormal_vertex,
|
||||
ShaderChunk.skinbase_vertex,
|
||||
ShaderChunk.skinnormal_vertex,
|
||||
ShaderChunk.defaultnormal_vertex,
|
||||
ShaderChunk.normal_vertex,
|
||||
|
||||
ShaderChunk.begin_vertex,
|
||||
|
||||
`#ifdef USE_PACKED_POSITION
|
||||
#if USE_PACKED_POSITION == 0
|
||||
transformed = ( vec4(transformed, 1.0) * quantizeMatPos ).xyz;
|
||||
#endif
|
||||
#endif`,
|
||||
|
||||
ShaderChunk.morphtarget_vertex,
|
||||
ShaderChunk.skinning_vertex,
|
||||
ShaderChunk.displacementmap_vertex,
|
||||
ShaderChunk.project_vertex,
|
||||
ShaderChunk.logdepthbuf_vertex,
|
||||
ShaderChunk.clipping_planes_vertex,
|
||||
|
||||
'vViewPosition = - mvPosition.xyz;',
|
||||
|
||||
ShaderChunk.worldpos_vertex,
|
||||
ShaderChunk.envmap_vertex,
|
||||
ShaderChunk.shadowmap_vertex,
|
||||
ShaderChunk.fog_vertex,
|
||||
|
||||
'}',
|
||||
].join( '\n' );
|
||||
|
||||
// Use the original MeshPhongMaterial's fragmentShader.
|
||||
this.fragmentShader = ShaderLib.phong.fragmentShader;
|
||||
|
||||
this.setValues( parameters );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { PackedPhongMaterial };
|
313
static/sdk/three/jsm/utils/SceneUtils.js
Normal file
313
static/sdk/three/jsm/utils/SceneUtils.js
Normal file
@ -0,0 +1,313 @@
|
||||
import {
|
||||
BufferAttribute,
|
||||
BufferGeometry,
|
||||
Color,
|
||||
Group,
|
||||
Matrix4,
|
||||
Mesh,
|
||||
Vector3
|
||||
} from 'three';
|
||||
|
||||
import { mergeGroups, deepCloneAttribute } from './BufferGeometryUtils.js';
|
||||
|
||||
const _color = /*@__PURE__*/new Color();
|
||||
const _matrix = /*@__PURE__*/new Matrix4();
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {InstancedMesh}
|
||||
* @param {function(int, int):int}
|
||||
*/
|
||||
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 ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 ] );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 ] );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
};
|
210
static/sdk/three/jsm/utils/ShadowMapViewer.js
Normal file
210
static/sdk/three/jsm/utils/ShadowMapViewer.js
Normal file
@ -0,0 +1,210 @@
|
||||
import {
|
||||
DoubleSide,
|
||||
LinearFilter,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
OrthographicCamera,
|
||||
PlaneGeometry,
|
||||
Scene,
|
||||
ShaderMaterial,
|
||||
Texture,
|
||||
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.
|
||||
*
|
||||
* Example usage:
|
||||
* 1) Import ShadowMapViewer into your app.
|
||||
*
|
||||
* 2) Create a shadow casting light and name it optionally:
|
||||
* let light = new DirectionalLight( 0xffffff, 1 );
|
||||
* light.castShadow = true;
|
||||
* light.name = 'Sun';
|
||||
*
|
||||
* 3) Create a shadow map viewer for that light and set its size and position optionally:
|
||||
* let shadowMapViewer = new ShadowMapViewer( light );
|
||||
* shadowMapViewer.size.set( 128, 128 ); //width, height default: 256, 256
|
||||
* shadowMapViewer.position.set( 10, 10 ); //x, y in pixel default: 0, 0 (top left corner)
|
||||
*
|
||||
* 4) Render the shadow map viewer in your render loop:
|
||||
* shadowMapViewer.render( renderer );
|
||||
*
|
||||
* 5) Optionally: Update the shadow map viewer on window resize:
|
||||
* shadowMapViewer.updateForWindowResize();
|
||||
*
|
||||
* 6) If you set the position or size members directly, you need to call shadowMapViewer.update();
|
||||
*/
|
||||
|
||||
class ShadowMapViewer {
|
||||
|
||||
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 Texture( labelCanvas );
|
||||
labelTexture.magFilter = LinearFilter;
|
||||
labelTexture.minFilter = LinearFilter;
|
||||
labelTexture.needsUpdate = true;
|
||||
|
||||
const labelMaterial = new MeshBasicMaterial( { map: labelTexture, side: DoubleSide } );
|
||||
labelMaterial.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 );
|
||||
|
||||
}
|
||||
|
||||
//- API
|
||||
// Set to false to disable displaying this shadow map
|
||||
this.enabled = true;
|
||||
|
||||
// Set the size of the displayed shadow map on the HUD
|
||||
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();
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// Set the position of the displayed shadow map on the HUD
|
||||
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 );
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
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 };
|
424
static/sdk/three/jsm/utils/SkeletonUtils.js
Normal file
424
static/sdk/three/jsm/utils/SkeletonUtils.js
Normal file
@ -0,0 +1,424 @@
|
||||
import {
|
||||
AnimationClip,
|
||||
AnimationMixer,
|
||||
Matrix4,
|
||||
Quaternion,
|
||||
QuaternionKeyframeTrack,
|
||||
SkeletonHelper,
|
||||
Vector3,
|
||||
VectorKeyframeTrack
|
||||
} from 'three';
|
||||
|
||||
|
||||
function retarget( target, source, options = {} ) {
|
||||
|
||||
const pos = new Vector3(),
|
||||
quat = new Quaternion(),
|
||||
scale = new Vector3(),
|
||||
bindBoneMatrix = new Matrix4(),
|
||||
relativeMatrix = new Matrix4(),
|
||||
globalMatrix = new Matrix4();
|
||||
|
||||
options.preserveMatrix = options.preserveMatrix !== undefined ? options.preserveMatrix : true;
|
||||
options.preservePosition = options.preservePosition !== undefined ? options.preservePosition : true;
|
||||
options.preserveHipPosition = options.preserveHipPosition !== undefined ? options.preserveHipPosition : false;
|
||||
options.useTargetMatrix = options.useTargetMatrix !== undefined ? options.useTargetMatrix : false;
|
||||
options.hip = options.hip !== undefined ? options.hip : 'hip';
|
||||
options.names = options.names || {};
|
||||
|
||||
const sourceBones = source.isObject3D ? source.skeleton.bones : getBones( source ),
|
||||
bones = target.isObject3D ? target.skeleton.bones : getBones( target );
|
||||
|
||||
let bindBones,
|
||||
bone, name, boneTo,
|
||||
bonesPosition;
|
||||
|
||||
// reset bones
|
||||
|
||||
if ( target.isObject3D ) {
|
||||
|
||||
target.skeleton.pose();
|
||||
|
||||
} else {
|
||||
|
||||
options.useTargetMatrix = true;
|
||||
options.preserveMatrix = false;
|
||||
|
||||
}
|
||||
|
||||
if ( options.preservePosition ) {
|
||||
|
||||
bonesPosition = [];
|
||||
|
||||
for ( let i = 0; i < bones.length; i ++ ) {
|
||||
|
||||
bonesPosition.push( bones[ i ].position.clone() );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( options.preserveMatrix ) {
|
||||
|
||||
// reset matrix
|
||||
|
||||
target.updateMatrixWorld();
|
||||
|
||||
target.matrixWorld.identity();
|
||||
|
||||
// reset children matrix
|
||||
|
||||
for ( let i = 0; i < target.children.length; ++ i ) {
|
||||
|
||||
target.children[ i ].updateMatrixWorld( true );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( options.offsets ) {
|
||||
|
||||
bindBones = [];
|
||||
|
||||
for ( let i = 0; i < bones.length; ++ i ) {
|
||||
|
||||
bone = bones[ i ];
|
||||
name = options.names[ bone.name ] || bone.name;
|
||||
|
||||
if ( options.offsets[ name ] ) {
|
||||
|
||||
bone.matrix.multiply( options.offsets[ name ] );
|
||||
|
||||
bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
|
||||
|
||||
bone.updateMatrixWorld();
|
||||
|
||||
}
|
||||
|
||||
bindBones.push( bone.matrixWorld.clone() );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for ( let i = 0; i < bones.length; ++ i ) {
|
||||
|
||||
bone = bones[ i ];
|
||||
name = options.names[ bone.name ] || bone.name;
|
||||
|
||||
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 ) {
|
||||
|
||||
const boneIndex = bones.indexOf( bone ),
|
||||
wBindMatrix = bindBones ? bindBones[ boneIndex ] : bindBoneMatrix.copy( target.skeleton.boneInverses[ boneIndex ] ).invert();
|
||||
|
||||
globalMatrix.multiply( wBindMatrix );
|
||||
|
||||
}
|
||||
|
||||
globalMatrix.copyPosition( relativeMatrix );
|
||||
|
||||
}
|
||||
|
||||
if ( bone.parent && bone.parent.isBone ) {
|
||||
|
||||
bone.matrix.copy( bone.parent.matrixWorld ).invert();
|
||||
bone.matrix.multiply( globalMatrix );
|
||||
|
||||
} else {
|
||||
|
||||
bone.matrix.copy( globalMatrix );
|
||||
|
||||
}
|
||||
|
||||
if ( options.preserveHipPosition && name === options.hip ) {
|
||||
|
||||
bone.matrix.setPosition( pos.set( 0, bone.position.y, 0 ) );
|
||||
|
||||
}
|
||||
|
||||
bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
|
||||
|
||||
bone.updateMatrixWorld();
|
||||
|
||||
}
|
||||
|
||||
if ( options.preservePosition ) {
|
||||
|
||||
for ( let i = 0; i < bones.length; ++ i ) {
|
||||
|
||||
bone = bones[ i ];
|
||||
name = options.names[ bone.name ] || bone.name;
|
||||
|
||||
if ( name !== options.hip ) {
|
||||
|
||||
bone.position.copy( bonesPosition[ i ] );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( options.preserveMatrix ) {
|
||||
|
||||
// restore matrix
|
||||
|
||||
target.updateMatrixWorld( true );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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();
|
||||
mixer.update( 0 );
|
||||
|
||||
source.updateMatrixWorld();
|
||||
|
||||
for ( let i = 0; i < numFrames; ++ i ) {
|
||||
|
||||
const time = i * delta;
|
||||
|
||||
retarget( target, source, options );
|
||||
|
||||
for ( let j = 0; j < bones.length; ++ j ) {
|
||||
|
||||
name = options.names[ bones[ j ].name ] || bones[ j ].name;
|
||||
|
||||
boneTo = getBoneByName( name, source.skeleton );
|
||||
|
||||
if ( boneTo ) {
|
||||
|
||||
bone = bones[ j ];
|
||||
boneData = boneDatas[ j ] = boneDatas[ j ] || { bone: bone };
|
||||
|
||||
if ( options.hip === name ) {
|
||||
|
||||
if ( ! boneData.pos ) {
|
||||
|
||||
boneData.pos = {
|
||||
times: new Float32Array( numFrames ),
|
||||
values: new Float32Array( numFrames * 3 )
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
if ( options.useFirstFramePosition ) {
|
||||
|
||||
if ( i === 0 ) {
|
||||
|
||||
positionOffset = bone.position.clone();
|
||||
|
||||
}
|
||||
|
||||
bone.position.sub( positionOffset );
|
||||
|
||||
}
|
||||
|
||||
boneData.pos.times[ i ] = time;
|
||||
|
||||
bone.position.toArray( boneData.pos.values, i * 3 );
|
||||
|
||||
}
|
||||
|
||||
if ( ! boneData.quat ) {
|
||||
|
||||
boneData.quat = {
|
||||
times: new Float32Array( numFrames ),
|
||||
values: new Float32Array( numFrames * 4 )
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
boneData.quat.times[ i ] = time;
|
||||
|
||||
bone.quaternion.toArray( boneData.quat.values, i * 4 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( i === numFrames - 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 );
|
||||
|
||||
}
|
||||
|
||||
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 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export {
|
||||
retarget,
|
||||
retargetClip,
|
||||
clone,
|
||||
};
|
160
static/sdk/three/jsm/utils/SortUtils.js
Normal file
160
static/sdk/three/jsm/utils/SortUtils.js
Normal file
@ -0,0 +1,160 @@
|
||||
// Hybrid radix sort from
|
||||
// - https://gist.github.com/sciecode/93ed864dd77c5c8803c6a86698d68dab
|
||||
// - https://github.com/mrdoob/three.js/pull/27202#issuecomment-1817640271
|
||||
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;
|
||||
|
||||
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 );
|
||||
let i = j;
|
||||
while ( i > 0 ) {
|
||||
|
||||
if ( compare( get( a[ i - 1 ] ), 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 );
|
||||
|
||||
};
|
98
static/sdk/three/jsm/utils/TextureUtils.js
Normal file
98
static/sdk/three/jsm/utils/TextureUtils.js
Normal file
@ -0,0 +1,98 @@
|
||||
import {
|
||||
PlaneGeometry,
|
||||
ShaderMaterial,
|
||||
Uniform,
|
||||
Mesh,
|
||||
PerspectiveCamera,
|
||||
Scene,
|
||||
WebGLRenderer,
|
||||
CanvasTexture,
|
||||
SRGBColorSpace
|
||||
} from 'three';
|
||||
|
||||
let _renderer;
|
||||
let fullscreenQuadGeometry;
|
||||
let fullscreenQuadMaterial;
|
||||
let fullscreenQuad;
|
||||
|
||||
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 = LinearTosRGB( 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.name = texture.name;
|
||||
|
||||
if ( _renderer ) {
|
||||
|
||||
_renderer.forceContextLoss();
|
||||
_renderer.dispose();
|
||||
_renderer = null;
|
||||
|
||||
}
|
||||
|
||||
return readableTexture;
|
||||
|
||||
}
|
165
static/sdk/three/jsm/utils/UVsDebug.js
Normal file
165
static/sdk/three/jsm/utils/UVsDebug.js
Normal file
@ -0,0 +1,165 @@
|
||||
import {
|
||||
Vector2
|
||||
} from 'three';
|
||||
|
||||
/**
|
||||
* tool for "unwrapping" and debugging three.js geometries UV mapping
|
||||
*
|
||||
* Sample usage:
|
||||
* document.body.appendChild( UVsDebug( new THREE.SphereGeometry( 10, 10, 10, 10 ) );
|
||||
*
|
||||
*/
|
||||
|
||||
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 };
|
102
static/sdk/three/jsm/utils/WorkerPool.js
Normal file
102
static/sdk/three/jsm/utils/WorkerPool.js
Normal file
@ -0,0 +1,102 @@
|
||||
/**
|
||||
* @author Deepkolos / https://github.com/deepkolos
|
||||
*/
|
||||
|
||||
export class WorkerPool {
|
||||
|
||||
constructor( pool = 4 ) {
|
||||
|
||||
this.pool = pool;
|
||||
this.queue = [];
|
||||
this.workers = [];
|
||||
this.workersResolve = [];
|
||||
this.workerStatus = 0;
|
||||
|
||||
}
|
||||
|
||||
_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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setWorkerCreator( workerCreator ) {
|
||||
|
||||
this.workerCreator = workerCreator;
|
||||
|
||||
}
|
||||
|
||||
setWorkerLimit( pool ) {
|
||||
|
||||
this.pool = pool;
|
||||
|
||||
}
|
||||
|
||||
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 } );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
||||
this.workers.forEach( ( worker ) => worker.terminate() );
|
||||
this.workersResolve.length = 0;
|
||||
this.workers.length = 0;
|
||||
this.queue.length = 0;
|
||||
this.workerStatus = 0;
|
||||
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user