diff options
| author | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-07 23:09:40 +0900 |
|---|---|---|
| committer | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-07 23:09:40 +0900 |
| commit | bc2821adb11a30244fc4663f4fafb756877d9508 (patch) | |
| tree | fea8d30d2eb1e67f895097dbf7ec4d152361243e /public/bluebey-studio/vendor/three/examples/jsm | |
| parent | 0d50c5ede0812ba7b67b775c9cd0bf52d7b65e02 (diff) | |
bluebey-studio: public/ の外へ移動し非公開化
Diffstat (limited to 'public/bluebey-studio/vendor/three/examples/jsm')
7 files changed, 0 insertions, 14938 deletions
diff --git a/public/bluebey-studio/vendor/three/examples/jsm/controls/OrbitControls.js b/public/bluebey-studio/vendor/three/examples/jsm/controls/OrbitControls.js deleted file mode 100644 index 3b49ec2..0000000 --- a/public/bluebey-studio/vendor/three/examples/jsm/controls/OrbitControls.js +++ /dev/null @@ -1,1972 +0,0 @@ -import { - Controls, - MOUSE, - Quaternion, - Spherical, - TOUCH, - Vector2, - Vector3, - Plane, - Ray, - MathUtils -} from 'three'; - -/** - * Fires when the camera has been transformed by the controls. - * - * @event OrbitControls#change - * @type {Object} - */ -const _changeEvent = { type: 'change' }; - -/** - * Fires when an interaction was initiated. - * - * @event OrbitControls#start - * @type {Object} - */ -const _startEvent = { type: 'start' }; - -/** - * Fires when an interaction has finished. - * - * @event OrbitControls#end - * @type {Object} - */ -const _endEvent = { type: 'end' }; - -const _ray = new Ray(); -const _plane = new Plane(); -const _TILT_LIMIT = Math.cos( 70 * MathUtils.DEG2RAD ); - -const _v = new Vector3(); -const _twoPI = 2 * Math.PI; - -const _STATE = { - NONE: - 1, - ROTATE: 0, - DOLLY: 1, - PAN: 2, - TOUCH_ROTATE: 3, - TOUCH_PAN: 4, - TOUCH_DOLLY_PAN: 5, - TOUCH_DOLLY_ROTATE: 6 -}; -const _EPS = 0.000001; - - -/** - * Orbit controls allow the camera to orbit around a target. - * - * OrbitControls performs orbiting, dollying (zooming), and panning. Unlike {@link TrackballControls}, - * it maintains the "up" direction `object.up` (+Y by default). - * - * - Orbit: Left mouse / touch: one-finger move. - * - Zoom: Middle mouse, or mousewheel / touch: two-finger spread or squish. - * - Pan: Right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move. - * - * ```js - * const controls = new OrbitControls( camera, renderer.domElement ); - * - * // controls.update() must be called after any manual changes to the camera's transform - * camera.position.set( 0, 20, 100 ); - * controls.update(); - * - * function animate() { - * - * // required if controls.enableDamping or controls.autoRotate are set to true - * controls.update(); - * - * renderer.render( scene, camera ); - * - * } - * ``` - * - * @augments Controls - * @three_import import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - */ -class OrbitControls extends Controls { - - /** - * Constructs a new controls instance. - * - * @param {Object3D} object - The object that is managed by the controls. - * @param {?HTMLElement} domElement - The HTML element used for event listeners. - */ - constructor( object, domElement = null ) { - - super( object, domElement ); - - this.state = _STATE.NONE; - - /** - * The focus point of the controls, the `object` orbits around this. - * It can be updated manually at any point to change the focus of the controls. - * - * @type {Vector3} - */ - this.target = new Vector3(); - - /** - * The focus point of the `minTargetRadius` and `maxTargetRadius` limits. - * It can be updated manually at any point to change the center of interest - * for the `target`. - * - * @type {Vector3} - */ - this.cursor = new Vector3(); - - /** - * How far you can dolly in (perspective camera only). - * - * @type {number} - * @default 0 - */ - this.minDistance = 0; - - /** - * How far you can dolly out (perspective camera only). - * - * @type {number} - * @default Infinity - */ - this.maxDistance = Infinity; - - /** - * How far you can zoom in (orthographic camera only). - * - * @type {number} - * @default 0 - */ - this.minZoom = 0; - - /** - * How far you can zoom out (orthographic camera only). - * - * @type {number} - * @default Infinity - */ - this.maxZoom = Infinity; - - /** - * How close you can get the target to the 3D `cursor`. - * - * @type {number} - * @default 0 - */ - this.minTargetRadius = 0; - - /** - * How far you can move the target from the 3D `cursor`. - * - * @type {number} - * @default Infinity - */ - this.maxTargetRadius = Infinity; - - /** - * How far you can orbit vertically, lower limit. Range is `[0, Math.PI]` radians. - * - * @type {number} - * @default 0 - */ - this.minPolarAngle = 0; - - /** - * How far you can orbit vertically, upper limit. Range is `[0, Math.PI]` radians. - * - * @type {number} - * @default Math.PI - */ - this.maxPolarAngle = Math.PI; - - /** - * How far you can orbit horizontally, lower limit. If set, the interval `[ min, max ]` - * must be a sub-interval of `[ - 2 PI, 2 PI ]`, with `( max - min < 2 PI )`. - * - * @type {number} - * @default -Infinity - */ - this.minAzimuthAngle = - Infinity; - - /** - * How far you can orbit horizontally, upper limit. If set, the interval `[ min, max ]` - * must be a sub-interval of `[ - 2 PI, 2 PI ]`, with `( max - min < 2 PI )`. - * - * @type {number} - * @default -Infinity - */ - this.maxAzimuthAngle = Infinity; - - /** - * Set to `true` to enable damping (inertia), which can be used to give a sense of weight - * to the controls. Note that if this is enabled, you must call `update()` in your animation - * loop. - * - * @type {boolean} - * @default false - */ - this.enableDamping = false; - - /** - * The damping inertia used if `enableDamping` is set to `true`. - * - * Note that for this to work, you must call `update()` in your animation loop. - * - * @type {number} - * @default 0.05 - */ - this.dampingFactor = 0.05; - - /** - * Enable or disable zooming (dollying) of the camera. - * - * @type {boolean} - * @default true - */ - this.enableZoom = true; - - /** - * Speed of zooming / dollying. - * - * @type {number} - * @default 1 - */ - this.zoomSpeed = 1.0; - - /** - * Enable or disable horizontal and vertical rotation of the camera. - * - * Note that it is possible to disable a single axis by setting the min and max of the - * `minPolarAngle` or `minAzimuthAngle` to the same value, which will cause the vertical - * or horizontal rotation to be fixed at that value. - * - * @type {boolean} - * @default true - */ - this.enableRotate = true; - - /** - * Speed of rotation. - * - * @type {number} - * @default 1 - */ - this.rotateSpeed = 1.0; - - /** - * How fast to rotate the camera when the keyboard is used. - * - * @type {number} - * @default 1 - */ - this.keyRotateSpeed = 1.0; - - /** - * Enable or disable camera panning. - * - * @type {boolean} - * @default true - */ - this.enablePan = true; - - /** - * Speed of panning. - * - * @type {number} - * @default 1 - */ - this.panSpeed = 1.0; - - /** - * Defines how the camera's position is translated when panning. If `true`, the camera pans - * in screen space. Otherwise, the camera pans in the plane orthogonal to the camera's up - * direction. - * - * @type {boolean} - * @default true - */ - this.screenSpacePanning = true; - - /** - * How fast to pan the camera when the keyboard is used in - * pixels per keypress. - * - * @type {number} - * @default 7 - */ - this.keyPanSpeed = 7.0; - - /** - * Setting this property to `true` allows to zoom to the cursor's position. - * - * @type {boolean} - * @default false - */ - this.zoomToCursor = false; - - /** - * Set to true to automatically rotate around the target - * - * Note that if this is enabled, you must call `update()` in your animation loop. - * If you want the auto-rotate speed to be independent of the frame rate (the refresh - * rate of the display), you must pass the time `deltaTime`, in seconds, to `update()`. - * - * @type {boolean} - * @default false - */ - this.autoRotate = false; - - /** - * How fast to rotate around the target if `autoRotate` is `true`. The default equates to 30 seconds - * per orbit at 60fps. - * - * Note that if `autoRotate` is enabled, you must call `update()` in your animation loop. - * - * @type {number} - * @default 2 - */ - this.autoRotateSpeed = 2.0; - - /** - * This object contains references to the keycodes for controlling camera panning. - * - * ```js - * controls.keys = { - * LEFT: 'ArrowLeft', //left arrow - * UP: 'ArrowUp', // up arrow - * RIGHT: 'ArrowRight', // right arrow - * BOTTOM: 'ArrowDown' // down arrow - * } - * ``` - * @type {Object} - */ - this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' }; - - /** - * This object contains references to the mouse actions used by the controls. - * - * ```js - * controls.mouseButtons = { - * LEFT: THREE.MOUSE.ROTATE, - * MIDDLE: THREE.MOUSE.DOLLY, - * RIGHT: THREE.MOUSE.PAN - * } - * ``` - * @type {Object} - */ - this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; - - /** - * This object contains references to the touch actions used by the controls. - * - * ```js - * controls.mouseButtons = { - * ONE: THREE.TOUCH.ROTATE, - * TWO: THREE.TOUCH.DOLLY_PAN - * } - * ``` - * @type {Object} - */ - this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN }; - - /** - * Used internally by `saveState()` and `reset()`. - * - * @type {Vector3} - */ - this.target0 = this.target.clone(); - - /** - * Used internally by `saveState()` and `reset()`. - * - * @type {Vector3} - */ - this.position0 = this.object.position.clone(); - - /** - * Used internally by `saveState()` and `reset()`. - * - * @type {number} - */ - this.zoom0 = this.object.zoom; - - this._cursorStyle = 'auto'; - - // the target DOM element for key events - this._domElementKeyEvents = null; - - // internals - - this._lastPosition = new Vector3(); - this._lastQuaternion = new Quaternion(); - this._lastTargetPosition = new Vector3(); - - // so camera.up is the orbit axis - this._quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); - this._quatInverse = this._quat.clone().invert(); - - // current position in spherical coordinates - this._spherical = new Spherical(); - this._sphericalDelta = new Spherical(); - - this._scale = 1; - this._panOffset = new Vector3(); - - this._rotateStart = new Vector2(); - this._rotateEnd = new Vector2(); - this._rotateDelta = new Vector2(); - - this._panStart = new Vector2(); - this._panEnd = new Vector2(); - this._panDelta = new Vector2(); - - this._dollyStart = new Vector2(); - this._dollyEnd = new Vector2(); - this._dollyDelta = new Vector2(); - - this._dollyDirection = new Vector3(); - this._mouse = new Vector2(); - this._performCursorZoom = false; - - this._pointers = []; - this._pointerPositions = {}; - - this._controlActive = false; - - // event listeners - - this._onPointerMove = onPointerMove.bind( this ); - this._onPointerDown = onPointerDown.bind( this ); - this._onPointerUp = onPointerUp.bind( this ); - this._onContextMenu = onContextMenu.bind( this ); - this._onMouseWheel = onMouseWheel.bind( this ); - this._onKeyDown = onKeyDown.bind( this ); - - this._onTouchStart = onTouchStart.bind( this ); - this._onTouchMove = onTouchMove.bind( this ); - - this._onMouseDown = onMouseDown.bind( this ); - this._onMouseMove = onMouseMove.bind( this ); - - this._interceptControlDown = interceptControlDown.bind( this ); - this._interceptControlUp = interceptControlUp.bind( this ); - - // - - if ( this.domElement !== null ) { - - this.connect( this.domElement ); - - } - - this.update(); - - } - - /** - * Defines the visual representation of the cursor. - * - * @type {('auto'|'grab')} - * @default 'auto' - */ - set cursorStyle( type ) { - - this._cursorStyle = type; - - if ( type === 'grab' ) { - - this.domElement.style.cursor = 'grab'; - - } else { - - this.domElement.style.cursor = 'auto'; - - } - - } - - get cursorStyle() { - - return this._cursorStyle; - - } - - connect( element ) { - - super.connect( element ); - - this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); - this.domElement.addEventListener( 'pointercancel', this._onPointerUp ); - - this.domElement.addEventListener( 'contextmenu', this._onContextMenu ); - this.domElement.addEventListener( 'wheel', this._onMouseWheel, { passive: false } ); - - const document = this.domElement.getRootNode(); // offscreen canvas compatibility - document.addEventListener( 'keydown', this._interceptControlDown, { passive: true, capture: true } ); - - this.domElement.style.touchAction = 'none'; // Disable touch scroll - - } - - disconnect() { - - this.state = _STATE.NONE; - - this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); - this.domElement.ownerDocument.removeEventListener( 'pointermove', this._onPointerMove ); - this.domElement.ownerDocument.removeEventListener( 'pointerup', this._onPointerUp ); - this.domElement.removeEventListener( 'pointercancel', this._onPointerUp ); - - this.domElement.removeEventListener( 'wheel', this._onMouseWheel ); - this.domElement.removeEventListener( 'contextmenu', this._onContextMenu ); - - this.stopListenToKeyEvents(); - - const document = this.domElement.getRootNode(); // offscreen canvas compatibility - document.removeEventListener( 'keydown', this._interceptControlDown, { capture: true } ); - document.removeEventListener( 'keyup', this._interceptControlUp, { capture: true } ); - - this._controlActive = false; - - this._pointers.length = 0; - this._pointerPositions = {}; - - this.domElement.style.touchAction = ''; // Restore touch scroll - this.domElement.style.cursor = 'auto'; - - } - - dispose() { - - this.disconnect(); - - } - - /** - * Get the current vertical rotation, in radians. - * - * @return {number} The current vertical rotation, in radians. - */ - getPolarAngle() { - - return this._spherical.phi; - - } - - /** - * Get the current horizontal rotation, in radians. - * - * @return {number} The current horizontal rotation, in radians. - */ - getAzimuthalAngle() { - - return this._spherical.theta; - - } - - /** - * Returns the distance from the camera to the target. - * - * @return {number} The distance from the camera to the target. - */ - getDistance() { - - return this.object.position.distanceTo( this.target ); - - } - - /** - * Adds key event listeners to the given DOM element. - * `window` is a recommended argument for using this method. - * - * @param {HTMLElement} domElement - The DOM element - */ - listenToKeyEvents( domElement ) { - - domElement.addEventListener( 'keydown', this._onKeyDown ); - this._domElementKeyEvents = domElement; - - } - - /** - * Removes the key event listener previously defined with `listenToKeyEvents()`. - */ - stopListenToKeyEvents() { - - if ( this._domElementKeyEvents !== null ) { - - this._domElementKeyEvents.removeEventListener( 'keydown', this._onKeyDown ); - this._domElementKeyEvents = null; - - } - - } - - /** - * Save the current state of the controls. This can later be recovered with `reset()`. - */ - saveState() { - - this.target0.copy( this.target ); - this.position0.copy( this.object.position ); - this.zoom0 = this.object.zoom; - - } - - /** - * Reset the controls to their state from either the last time the `saveState()` - * was called, or the initial state. - */ - reset() { - - this.target.copy( this.target0 ); - this.object.position.copy( this.position0 ); - this.object.zoom = this.zoom0; - - this.object.updateProjectionMatrix(); - this.dispatchEvent( _changeEvent ); - - this.update(); - - this.state = _STATE.NONE; - - } - - /** - * Programmatically pan the camera. - * - * @param {number} deltaX - The horizontal pan amount in pixels. - * @param {number} deltaY - The vertical pan amount in pixels. - */ - pan( deltaX, deltaY ) { - - this._pan( deltaX, deltaY ); - this.update(); - - } - - /** - * Programmatically dolly in (zoom in for perspective camera). - * - * @param {number} dollyScale - The dolly scale factor. - */ - dollyIn( dollyScale ) { - - this._dollyIn( dollyScale ); - this.update(); - - } - - /** - * Programmatically dolly out (zoom out for perspective camera). - * - * @param {number} dollyScale - The dolly scale factor. - */ - dollyOut( dollyScale ) { - - this._dollyOut( dollyScale ); - this.update(); - - } - - /** - * Programmatically rotate the camera left (around the vertical axis). - * - * @param {number} angle - The rotation angle in radians. - */ - rotateLeft( angle ) { - - this._rotateLeft( angle ); - this.update(); - - } - - /** - * Programmatically rotate the camera up (around the horizontal axis). - * - * @param {number} angle - The rotation angle in radians. - */ - rotateUp( angle ) { - - this._rotateUp( angle ); - this.update(); - - } - - update( deltaTime = null ) { - - const position = this.object.position; - - _v.copy( position ).sub( this.target ); - - // rotate offset to "y-axis-is-up" space - _v.applyQuaternion( this._quat ); - - // angle from z-axis around y-axis - this._spherical.setFromVector3( _v ); - - if ( this.autoRotate && this.state === _STATE.NONE ) { - - this._rotateLeft( this._getAutoRotationAngle( deltaTime ) ); - - } - - if ( this.enableDamping ) { - - this._spherical.theta += this._sphericalDelta.theta * this.dampingFactor; - this._spherical.phi += this._sphericalDelta.phi * this.dampingFactor; - - } else { - - this._spherical.theta += this._sphericalDelta.theta; - this._spherical.phi += this._sphericalDelta.phi; - - } - - // restrict theta to be between desired limits - - let min = this.minAzimuthAngle; - let max = this.maxAzimuthAngle; - - if ( isFinite( min ) && isFinite( max ) ) { - - if ( min < - Math.PI ) min += _twoPI; else if ( min > Math.PI ) min -= _twoPI; - - if ( max < - Math.PI ) max += _twoPI; else if ( max > Math.PI ) max -= _twoPI; - - if ( min <= max ) { - - this._spherical.theta = Math.max( min, Math.min( max, this._spherical.theta ) ); - - } else { - - this._spherical.theta = ( this._spherical.theta > ( min + max ) / 2 ) ? - Math.max( min, this._spherical.theta ) : - Math.min( max, this._spherical.theta ); - - } - - } - - // restrict phi to be between desired limits - this._spherical.phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, this._spherical.phi ) ); - - this._spherical.makeSafe(); - - - // move target to panned location - - if ( this.enableDamping === true ) { - - this.target.addScaledVector( this._panOffset, this.dampingFactor ); - - } else { - - this.target.add( this._panOffset ); - - } - - // Limit the target distance from the cursor to create a sphere around the center of interest - this.target.sub( this.cursor ); - this.target.clampLength( this.minTargetRadius, this.maxTargetRadius ); - this.target.add( this.cursor ); - - let zoomChanged = false; - // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera - // we adjust zoom later in these cases - if ( this.zoomToCursor && this._performCursorZoom || this.object.isOrthographicCamera ) { - - this._spherical.radius = this._clampDistance( this._spherical.radius ); - - } else { - - const prevRadius = this._spherical.radius; - this._spherical.radius = this._clampDistance( this._spherical.radius * this._scale ); - zoomChanged = prevRadius != this._spherical.radius; - - } - - _v.setFromSpherical( this._spherical ); - - // rotate offset back to "camera-up-vector-is-up" space - _v.applyQuaternion( this._quatInverse ); - - position.copy( this.target ).add( _v ); - - this.object.lookAt( this.target ); - - if ( this.enableDamping === true ) { - - this._sphericalDelta.theta *= ( 1 - this.dampingFactor ); - this._sphericalDelta.phi *= ( 1 - this.dampingFactor ); - - this._panOffset.multiplyScalar( 1 - this.dampingFactor ); - - } else { - - this._sphericalDelta.set( 0, 0, 0 ); - - this._panOffset.set( 0, 0, 0 ); - - } - - // adjust camera position - if ( this.zoomToCursor && this._performCursorZoom ) { - - let newRadius = null; - if ( this.object.isPerspectiveCamera ) { - - // move the camera down the pointer ray - // this method avoids floating point error - const prevRadius = _v.length(); - newRadius = this._clampDistance( prevRadius * this._scale ); - - const radiusDelta = prevRadius - newRadius; - this.object.position.addScaledVector( this._dollyDirection, radiusDelta ); - this.object.updateMatrixWorld(); - - zoomChanged = !! radiusDelta; - - } else if ( this.object.isOrthographicCamera ) { - - // adjust the ortho camera position based on zoom changes - const mouseBefore = new Vector3( this._mouse.x, this._mouse.y, 0 ); - mouseBefore.unproject( this.object ); - - const prevZoom = this.object.zoom; - this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) ); - this.object.updateProjectionMatrix(); - - zoomChanged = prevZoom !== this.object.zoom; - - const mouseAfter = new Vector3( this._mouse.x, this._mouse.y, 0 ); - mouseAfter.unproject( this.object ); - - this.object.position.sub( mouseAfter ).add( mouseBefore ); - this.object.updateMatrixWorld(); - - newRadius = _v.length(); - - } else { - - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' ); - this.zoomToCursor = false; - - } - - // handle the placement of the target - if ( newRadius !== null ) { - - if ( this.screenSpacePanning ) { - - // position the orbit target in front of the new camera position - this.target.set( 0, 0, - 1 ) - .transformDirection( this.object.matrix ) - .multiplyScalar( newRadius ) - .add( this.object.position ); - - } else { - - // get the ray and translation plane to compute target - _ray.origin.copy( this.object.position ); - _ray.direction.set( 0, 0, - 1 ).transformDirection( this.object.matrix ); - - // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid - // extremely large values - if ( Math.abs( this.object.up.dot( _ray.direction ) ) < _TILT_LIMIT ) { - - this.object.lookAt( this.target ); - - } else { - - _plane.setFromNormalAndCoplanarPoint( this.object.up, this.target ); - _ray.intersectPlane( _plane, this.target ); - - } - - } - - } - - } else if ( this.object.isOrthographicCamera ) { - - const prevZoom = this.object.zoom; - this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) ); - - if ( prevZoom !== this.object.zoom ) { - - this.object.updateProjectionMatrix(); - zoomChanged = true; - - } - - } - - this._scale = 1; - this._performCursorZoom = false; - - // update condition is: - // min(camera displacement, camera rotation in radians)^2 > EPS - // using small-angle approximation cos(x/2) = 1 - x^2 / 8 - - if ( zoomChanged || - this._lastPosition.distanceToSquared( this.object.position ) > _EPS || - 8 * ( 1 - this._lastQuaternion.dot( this.object.quaternion ) ) > _EPS || - this._lastTargetPosition.distanceToSquared( this.target ) > _EPS ) { - - this.dispatchEvent( _changeEvent ); - - this._lastPosition.copy( this.object.position ); - this._lastQuaternion.copy( this.object.quaternion ); - this._lastTargetPosition.copy( this.target ); - - return true; - - } - - return false; - - } - - _getAutoRotationAngle( deltaTime ) { - - if ( deltaTime !== null ) { - - return ( _twoPI / 60 * this.autoRotateSpeed ) * deltaTime; - - } else { - - return _twoPI / 60 / 60 * this.autoRotateSpeed; - - } - - } - - _getZoomScale( delta ) { - - const normalizedDelta = Math.abs( delta * 0.01 ); - return Math.pow( 0.95, this.zoomSpeed * normalizedDelta ); - - } - - _rotateLeft( angle ) { - - this._sphericalDelta.theta -= angle; - - } - - _rotateUp( angle ) { - - this._sphericalDelta.phi -= angle; - - } - - _panLeft( distance, objectMatrix ) { - - _v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix - _v.multiplyScalar( - distance ); - - this._panOffset.add( _v ); - - } - - _panUp( distance, objectMatrix ) { - - if ( this.screenSpacePanning === true ) { - - _v.setFromMatrixColumn( objectMatrix, 1 ); - - } else { - - _v.setFromMatrixColumn( objectMatrix, 0 ); - _v.crossVectors( this.object.up, _v ); - - } - - _v.multiplyScalar( distance ); - - this._panOffset.add( _v ); - - } - - // deltaX and deltaY are in pixels; right and down are positive - _pan( deltaX, deltaY ) { - - const element = this.domElement; - - if ( this.object.isPerspectiveCamera ) { - - // perspective - const position = this.object.position; - _v.copy( position ).sub( this.target ); - let targetDistance = _v.length(); - - // half of the fov is center to top of screen - targetDistance *= Math.tan( ( this.object.fov / 2 ) * Math.PI / 180.0 ); - - // we use only clientHeight here so aspect ratio does not distort speed - this._panLeft( 2 * deltaX * targetDistance / element.clientHeight, this.object.matrix ); - this._panUp( 2 * deltaY * targetDistance / element.clientHeight, this.object.matrix ); - - } else if ( this.object.isOrthographicCamera ) { - - // orthographic - this._panLeft( deltaX * ( this.object.right - this.object.left ) / this.object.zoom / element.clientWidth, this.object.matrix ); - this._panUp( deltaY * ( this.object.top - this.object.bottom ) / this.object.zoom / element.clientHeight, this.object.matrix ); - - } else { - - // camera neither orthographic nor perspective - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); - this.enablePan = false; - - } - - } - - _dollyOut( dollyScale ) { - - if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) { - - this._scale /= dollyScale; - - } else { - - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); - this.enableZoom = false; - - } - - } - - _dollyIn( dollyScale ) { - - if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) { - - this._scale *= dollyScale; - - } else { - - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); - this.enableZoom = false; - - } - - } - - _updateZoomParameters( x, y ) { - - if ( ! this.zoomToCursor ) { - - return; - - } - - this._performCursorZoom = true; - - const rect = this.domElement.getBoundingClientRect(); - const dx = x - rect.left; - const dy = y - rect.top; - const w = rect.width; - const h = rect.height; - - this._mouse.x = ( dx / w ) * 2 - 1; - this._mouse.y = - ( dy / h ) * 2 + 1; - - this._dollyDirection.set( this._mouse.x, this._mouse.y, 1 ).unproject( this.object ).sub( this.object.position ).normalize(); - - } - - _clampDistance( dist ) { - - return Math.max( this.minDistance, Math.min( this.maxDistance, dist ) ); - - } - - // - // event callbacks - update the object state - // - - _handleMouseDownRotate( event ) { - - this._rotateStart.set( event.clientX, event.clientY ); - - } - - _handleMouseDownDolly( event ) { - - this._updateZoomParameters( event.clientX, event.clientX ); - this._dollyStart.set( event.clientX, event.clientY ); - - } - - _handleMouseDownPan( event ) { - - this._panStart.set( event.clientX, event.clientY ); - - } - - _handleMouseMoveRotate( event ) { - - this._rotateEnd.set( event.clientX, event.clientY ); - - this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed ); - - const element = this.domElement; - - this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height - - this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight ); - - this._rotateStart.copy( this._rotateEnd ); - - this.update(); - - } - - _handleMouseMoveDolly( event ) { - - this._dollyEnd.set( event.clientX, event.clientY ); - - this._dollyDelta.subVectors( this._dollyEnd, this._dollyStart ); - - if ( this._dollyDelta.y > 0 ) { - - this._dollyOut( this._getZoomScale( this._dollyDelta.y ) ); - - } else if ( this._dollyDelta.y < 0 ) { - - this._dollyIn( this._getZoomScale( this._dollyDelta.y ) ); - - } - - this._dollyStart.copy( this._dollyEnd ); - - this.update(); - - } - - _handleMouseMovePan( event ) { - - this._panEnd.set( event.clientX, event.clientY ); - - this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed ); - - this._pan( this._panDelta.x, this._panDelta.y ); - - this._panStart.copy( this._panEnd ); - - this.update(); - - } - - _handleMouseWheel( event ) { - - this._updateZoomParameters( event.clientX, event.clientY ); - - if ( event.deltaY < 0 ) { - - this._dollyIn( this._getZoomScale( event.deltaY ) ); - - } else if ( event.deltaY > 0 ) { - - this._dollyOut( this._getZoomScale( event.deltaY ) ); - - } - - this.update(); - - } - - _handleKeyDown( event ) { - - let needsUpdate = false; - - switch ( event.code ) { - - case this.keys.UP: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - if ( this.enableRotate ) { - - this._rotateUp( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); - - } - - } else { - - if ( this.enablePan ) { - - this._pan( 0, this.keyPanSpeed ); - - } - - } - - needsUpdate = true; - break; - - case this.keys.BOTTOM: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - if ( this.enableRotate ) { - - this._rotateUp( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); - - } - - } else { - - if ( this.enablePan ) { - - this._pan( 0, - this.keyPanSpeed ); - - } - - } - - needsUpdate = true; - break; - - case this.keys.LEFT: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - if ( this.enableRotate ) { - - this._rotateLeft( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); - - } - - } else { - - if ( this.enablePan ) { - - this._pan( this.keyPanSpeed, 0 ); - - } - - } - - needsUpdate = true; - break; - - case this.keys.RIGHT: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - if ( this.enableRotate ) { - - this._rotateLeft( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); - - } - - } else { - - if ( this.enablePan ) { - - this._pan( - this.keyPanSpeed, 0 ); - - } - - } - - needsUpdate = true; - break; - - } - - if ( needsUpdate ) { - - // prevent the browser from scrolling on cursor keys - event.preventDefault(); - - this.update(); - - } - - - } - - _handleTouchStartRotate( event ) { - - if ( this._pointers.length === 1 ) { - - this._rotateStart.set( event.pageX, event.pageY ); - - } else { - - const position = this._getSecondPointerPosition( event ); - - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); - - this._rotateStart.set( x, y ); - - } - - } - - _handleTouchStartPan( event ) { - - if ( this._pointers.length === 1 ) { - - this._panStart.set( event.pageX, event.pageY ); - - } else { - - const position = this._getSecondPointerPosition( event ); - - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); - - this._panStart.set( x, y ); - - } - - } - - _handleTouchStartDolly( event ) { - - const position = this._getSecondPointerPosition( event ); - - const dx = event.pageX - position.x; - const dy = event.pageY - position.y; - - const distance = Math.sqrt( dx * dx + dy * dy ); - - this._dollyStart.set( 0, distance ); - - } - - _handleTouchStartDollyPan( event ) { - - if ( this.enableZoom ) this._handleTouchStartDolly( event ); - - if ( this.enablePan ) this._handleTouchStartPan( event ); - - } - - _handleTouchStartDollyRotate( event ) { - - if ( this.enableZoom ) this._handleTouchStartDolly( event ); - - if ( this.enableRotate ) this._handleTouchStartRotate( event ); - - } - - _handleTouchMoveRotate( event ) { - - if ( this._pointers.length == 1 ) { - - this._rotateEnd.set( event.pageX, event.pageY ); - - } else { - - const position = this._getSecondPointerPosition( event ); - - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); - - this._rotateEnd.set( x, y ); - - } - - this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed ); - - const element = this.domElement; - - this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height - - this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight ); - - this._rotateStart.copy( this._rotateEnd ); - - } - - _handleTouchMovePan( event ) { - - if ( this._pointers.length === 1 ) { - - this._panEnd.set( event.pageX, event.pageY ); - - } else { - - const position = this._getSecondPointerPosition( event ); - - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); - - this._panEnd.set( x, y ); - - } - - this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed ); - - this._pan( this._panDelta.x, this._panDelta.y ); - - this._panStart.copy( this._panEnd ); - - } - - _handleTouchMoveDolly( event ) { - - const position = this._getSecondPointerPosition( event ); - - const dx = event.pageX - position.x; - const dy = event.pageY - position.y; - - const distance = Math.sqrt( dx * dx + dy * dy ); - - this._dollyEnd.set( 0, distance ); - - this._dollyDelta.set( 0, Math.pow( this._dollyEnd.y / this._dollyStart.y, this.zoomSpeed ) ); - - this._dollyOut( this._dollyDelta.y ); - - this._dollyStart.copy( this._dollyEnd ); - - const centerX = ( event.pageX + position.x ) * 0.5; - const centerY = ( event.pageY + position.y ) * 0.5; - - this._updateZoomParameters( centerX, centerY ); - - } - - _handleTouchMoveDollyPan( event ) { - - if ( this.enableZoom ) this._handleTouchMoveDolly( event ); - - if ( this.enablePan ) this._handleTouchMovePan( event ); - - } - - _handleTouchMoveDollyRotate( event ) { - - if ( this.enableZoom ) this._handleTouchMoveDolly( event ); - - if ( this.enableRotate ) this._handleTouchMoveRotate( event ); - - } - - // pointers - - _addPointer( event ) { - - this._pointers.push( event.pointerId ); - - } - - _removePointer( event ) { - - delete this._pointerPositions[ event.pointerId ]; - - for ( let i = 0; i < this._pointers.length; i ++ ) { - - if ( this._pointers[ i ] == event.pointerId ) { - - this._pointers.splice( i, 1 ); - return; - - } - - } - - } - - _isTrackingPointer( event ) { - - for ( let i = 0; i < this._pointers.length; i ++ ) { - - if ( this._pointers[ i ] == event.pointerId ) return true; - - } - - return false; - - } - - _trackPointer( event ) { - - let position = this._pointerPositions[ event.pointerId ]; - - if ( position === undefined ) { - - position = new Vector2(); - this._pointerPositions[ event.pointerId ] = position; - - } - - position.set( event.pageX, event.pageY ); - - } - - _getSecondPointerPosition( event ) { - - const pointerId = ( event.pointerId === this._pointers[ 0 ] ) ? this._pointers[ 1 ] : this._pointers[ 0 ]; - - return this._pointerPositions[ pointerId ]; - - } - - // - - _customWheelEvent( event ) { - - const mode = event.deltaMode; - - // minimal wheel event altered to meet delta-zoom demand - const newEvent = { - clientX: event.clientX, - clientY: event.clientY, - deltaY: event.deltaY, - }; - - switch ( mode ) { - - case 1: // LINE_MODE - newEvent.deltaY *= 16; - break; - - case 2: // PAGE_MODE - newEvent.deltaY *= 100; - break; - - } - - // detect if event was triggered by pinching - if ( event.ctrlKey && ! this._controlActive ) { - - newEvent.deltaY *= 10; - - } - - return newEvent; - - } - -} - -function onPointerDown( event ) { - - if ( this.enabled === false ) return; - - if ( this._pointers.length === 0 ) { - - this.domElement.setPointerCapture( event.pointerId ); - - this.domElement.ownerDocument.addEventListener( 'pointermove', this._onPointerMove ); - this.domElement.ownerDocument.addEventListener( 'pointerup', this._onPointerUp ); - - } - - // - - if ( this._isTrackingPointer( event ) ) return; - - // - - this._addPointer( event ); - - if ( event.pointerType === 'touch' ) { - - this._onTouchStart( event ); - - } else { - - this._onMouseDown( event ); - - } - - if ( this._cursorStyle === 'grab' ) { - - this.domElement.style.cursor = 'grabbing'; - - } - -} - -function onPointerMove( event ) { - - if ( this.enabled === false ) return; - - if ( event.pointerType === 'touch' ) { - - this._onTouchMove( event ); - - } else { - - this._onMouseMove( event ); - - } - -} - -function onPointerUp( event ) { - - this._removePointer( event ); - - switch ( this._pointers.length ) { - - case 0: - - this.domElement.releasePointerCapture( event.pointerId ); - - this.domElement.ownerDocument.removeEventListener( 'pointermove', this._onPointerMove ); - this.domElement.ownerDocument.removeEventListener( 'pointerup', this._onPointerUp ); - - this.dispatchEvent( _endEvent ); - - this.state = _STATE.NONE; - - if ( this._cursorStyle === 'grab' ) { - - this.domElement.style.cursor = 'grab'; - - } - - break; - - case 1: - - const pointerId = this._pointers[ 0 ]; - const position = this._pointerPositions[ pointerId ]; - - // minimal placeholder event - allows state correction on pointer-up - this._onTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } ); - - break; - - } - -} - -function onMouseDown( event ) { - - let mouseAction; - - switch ( event.button ) { - - case 0: - - mouseAction = this.mouseButtons.LEFT; - break; - - case 1: - - mouseAction = this.mouseButtons.MIDDLE; - break; - - case 2: - - mouseAction = this.mouseButtons.RIGHT; - break; - - default: - - mouseAction = - 1; - - } - - switch ( mouseAction ) { - - case MOUSE.DOLLY: - - if ( this.enableZoom === false ) return; - - this._handleMouseDownDolly( event ); - - this.state = _STATE.DOLLY; - - break; - - case MOUSE.ROTATE: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - if ( this.enablePan === false ) return; - - this._handleMouseDownPan( event ); - - this.state = _STATE.PAN; - - } else { - - if ( this.enableRotate === false ) return; - - this._handleMouseDownRotate( event ); - - this.state = _STATE.ROTATE; - - } - - break; - - case MOUSE.PAN: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - if ( this.enableRotate === false ) return; - - this._handleMouseDownRotate( event ); - - this.state = _STATE.ROTATE; - - } else { - - if ( this.enablePan === false ) return; - - this._handleMouseDownPan( event ); - - this.state = _STATE.PAN; - - } - - break; - - default: - - this.state = _STATE.NONE; - - } - - if ( this.state !== _STATE.NONE ) { - - this.dispatchEvent( _startEvent ); - - } - -} - -function onMouseMove( event ) { - - switch ( this.state ) { - - case _STATE.ROTATE: - - if ( this.enableRotate === false ) return; - - this._handleMouseMoveRotate( event ); - - break; - - case _STATE.DOLLY: - - if ( this.enableZoom === false ) return; - - this._handleMouseMoveDolly( event ); - - break; - - case _STATE.PAN: - - if ( this.enablePan === false ) return; - - this._handleMouseMovePan( event ); - - break; - - } - -} - -function onMouseWheel( event ) { - - if ( this.enabled === false || this.enableZoom === false || this.state !== _STATE.NONE ) return; - - event.preventDefault(); - - this.dispatchEvent( _startEvent ); - - this._handleMouseWheel( this._customWheelEvent( event ) ); - - this.dispatchEvent( _endEvent ); - -} - -function onKeyDown( event ) { - - if ( this.enabled === false ) return; - - this._handleKeyDown( event ); - -} - -function onTouchStart( event ) { - - this._trackPointer( event ); - - switch ( this._pointers.length ) { - - case 1: - - switch ( this.touches.ONE ) { - - case TOUCH.ROTATE: - - if ( this.enableRotate === false ) return; - - this._handleTouchStartRotate( event ); - - this.state = _STATE.TOUCH_ROTATE; - - break; - - case TOUCH.PAN: - - if ( this.enablePan === false ) return; - - this._handleTouchStartPan( event ); - - this.state = _STATE.TOUCH_PAN; - - break; - - default: - - this.state = _STATE.NONE; - - } - - break; - - case 2: - - switch ( this.touches.TWO ) { - - case TOUCH.DOLLY_PAN: - - if ( this.enableZoom === false && this.enablePan === false ) return; - - this._handleTouchStartDollyPan( event ); - - this.state = _STATE.TOUCH_DOLLY_PAN; - - break; - - case TOUCH.DOLLY_ROTATE: - - if ( this.enableZoom === false && this.enableRotate === false ) return; - - this._handleTouchStartDollyRotate( event ); - - this.state = _STATE.TOUCH_DOLLY_ROTATE; - - break; - - default: - - this.state = _STATE.NONE; - - } - - break; - - default: - - this.state = _STATE.NONE; - - } - - if ( this.state !== _STATE.NONE ) { - - this.dispatchEvent( _startEvent ); - - } - -} - -function onTouchMove( event ) { - - this._trackPointer( event ); - - switch ( this.state ) { - - case _STATE.TOUCH_ROTATE: - - if ( this.enableRotate === false ) return; - - this._handleTouchMoveRotate( event ); - - this.update(); - - break; - - case _STATE.TOUCH_PAN: - - if ( this.enablePan === false ) return; - - this._handleTouchMovePan( event ); - - this.update(); - - break; - - case _STATE.TOUCH_DOLLY_PAN: - - if ( this.enableZoom === false && this.enablePan === false ) return; - - this._handleTouchMoveDollyPan( event ); - - this.update(); - - break; - - case _STATE.TOUCH_DOLLY_ROTATE: - - if ( this.enableZoom === false && this.enableRotate === false ) return; - - this._handleTouchMoveDollyRotate( event ); - - this.update(); - - break; - - default: - - this.state = _STATE.NONE; - - } - -} - -function onContextMenu( event ) { - - if ( this.enabled === false ) return; - - event.preventDefault(); - -} - -function interceptControlDown( event ) { - - if ( event.key === 'Control' ) { - - this._controlActive = true; - - const document = this.domElement.getRootNode(); // offscreen canvas compatibility - - document.addEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } ); - - } - -} - -function interceptControlUp( event ) { - - if ( event.key === 'Control' ) { - - this._controlActive = false; - - const document = this.domElement.getRootNode(); // offscreen canvas compatibility - - document.removeEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } ); - - } - -} - -export { OrbitControls }; diff --git a/public/bluebey-studio/vendor/three/examples/jsm/controls/TransformControls.js b/public/bluebey-studio/vendor/three/examples/jsm/controls/TransformControls.js deleted file mode 100644 index 88fc5e2..0000000 --- a/public/bluebey-studio/vendor/three/examples/jsm/controls/TransformControls.js +++ /dev/null @@ -1,2003 +0,0 @@ -import { - BoxGeometry, - BufferGeometry, - Controls, - CylinderGeometry, - DoubleSide, - Euler, - Float32BufferAttribute, - Line, - LineBasicMaterial, - Matrix4, - Mesh, - MeshBasicMaterial, - Object3D, - OctahedronGeometry, - PlaneGeometry, - Quaternion, - Raycaster, - SphereGeometry, - TorusGeometry, - Vector3 -} from 'three'; - -const _raycaster = new Raycaster(); - -const _tempVector = new Vector3(); -const _tempVector2 = new Vector3(); -const _tempQuaternion = new Quaternion(); -const _unit = { - X: new Vector3( 1, 0, 0 ), - Y: new Vector3( 0, 1, 0 ), - Z: new Vector3( 0, 0, 1 ) -}; - -/** - * Fires if any type of change (object or property change) is performed. Property changes - * are separate events you can add event listeners to. The event type is "propertyname-changed". - * - * @event TransformControls#change - * @type {Object} - */ -const _changeEvent = { type: 'change' }; - -/** - * Fires if a pointer (mouse/touch) becomes active. - * - * @event TransformControls#mouseDown - * @type {Object} - */ -const _mouseDownEvent = { type: 'mouseDown', mode: null }; - -/** - * Fires if a pointer (mouse/touch) is no longer active. - * - * @event TransformControls#mouseUp - * @type {Object} - */ -const _mouseUpEvent = { type: 'mouseUp', mode: null }; - -/** - * Fires if the controlled 3D object is changed. - * - * @event TransformControls#objectChange - * @type {Object} - */ -const _objectChangeEvent = { type: 'objectChange' }; - -/** - * This class can be used to transform objects in 3D space by adapting a similar interaction model - * of DCC tools like Blender. Unlike other controls, it is not intended to transform the scene's camera. - * - * `TransformControls` expects that its attached 3D object is part of the scene graph. - * - * @augments Controls - * @three_import import { TransformControls } from 'three/addons/controls/TransformControls.js'; - */ -class TransformControls extends Controls { - - /** - * Constructs a new controls instance. - * - * @param {Camera} camera - The camera of the rendered scene. - * @param {?HTMLElement} domElement - The HTML element used for event listeners. - */ - constructor( camera, domElement = null ) { - - super( undefined, domElement ); - - const root = new TransformControlsRoot( this ); - this._root = root; - - const gizmo = new TransformControlsGizmo(); - this._gizmo = gizmo; - root.add( gizmo ); - - const plane = new TransformControlsPlane(); - this._plane = plane; - root.add( plane ); - - const scope = this; - - // Defined getter, setter and store for a property - function defineProperty( propName, defaultValue ) { - - let propValue = defaultValue; - - Object.defineProperty( scope, propName, { - - get: function () { - - return propValue !== undefined ? propValue : defaultValue; - - }, - - set: function ( value ) { - - if ( propValue !== value ) { - - propValue = value; - plane[ propName ] = value; - gizmo[ propName ] = value; - - scope.dispatchEvent( { type: propName + '-changed', value: value } ); - scope.dispatchEvent( _changeEvent ); - - } - - } - - } ); - - scope[ propName ] = defaultValue; - plane[ propName ] = defaultValue; - gizmo[ propName ] = defaultValue; - - } - - // Define properties with getters/setter - // Setting the defined property will automatically trigger change event - // Defined properties are passed down to gizmo and plane - - /** - * The camera of the rendered scene. - * - * @name TransformControls#camera - * @type {Camera} - */ - defineProperty( 'camera', camera ); - defineProperty( 'object', undefined ); - defineProperty( 'enabled', true ); - - /** - * The current transformation axis. - * - * @name TransformControls#axis - * @type {string} - */ - defineProperty( 'axis', null ); - - /** - * The current transformation axis. - * - * @name TransformControls#mode - * @type {('translate'|'rotate'|'scale')} - * @default 'translate' - */ - defineProperty( 'mode', 'translate' ); - - /** - * By default, 3D objects are continuously translated. If you set this property to a numeric - * value (world units), you can define in which steps the 3D object should be translated. - * - * @name TransformControls#translationSnap - * @type {?number} - * @default null - */ - defineProperty( 'translationSnap', null ); - - /** - * By default, 3D objects are continuously rotated. If you set this property to a numeric - * value (radians), you can define in which steps the 3D object should be rotated. - * - * @name TransformControls#rotationSnap - * @type {?number} - * @default null - */ - defineProperty( 'rotationSnap', null ); - - /** - * By default, 3D objects are continuously scaled. If you set this property to a numeric - * value, you can define in which steps the 3D object should be scaled. - * - * @name TransformControls#scaleSnap - * @type {?number} - * @default null - */ - defineProperty( 'scaleSnap', null ); - - /** - * Defines in which coordinate space transformations should be performed. - * - * @name TransformControls#space - * @type {('world'|'local')} - * @default 'world' - */ - defineProperty( 'space', 'world' ); - - /** - * The size of the helper UI (axes/planes). - * - * @name TransformControls#size - * @type {number} - * @default 1 - */ - defineProperty( 'size', 1 ); - - /** - * The viewport rectangle, in logical (CSS) pixels with the origin at the lower-left - * of the canvas. Set this when the renderer uses a sub-canvas viewport so pointer - * coordinates map to the correct region. If `null`, the full canvas is used. - * - * @name TransformControls#viewport - * @type {?Vector4} - * @default null - */ - this.viewport = null; - - /** - * Whether dragging is currently performed or not. - * - * @name TransformControls#dragging - * @type {boolean} - * @readonly - * @default false - */ - defineProperty( 'dragging', false ); - - /** - * Whether the x-axis helper should be visible or not. - * - * @name TransformControls#showX - * @type {boolean} - * @default true - */ - defineProperty( 'showX', true ); - - /** - * Whether the y-axis helper should be visible or not. - * - * @name TransformControls#showY - * @type {boolean} - * @default true - */ - defineProperty( 'showY', true ); - - /** - * Whether the z-axis helper should be visible or not. - * - * @name TransformControls#showZ - * @type {boolean} - * @default true - */ - defineProperty( 'showZ', true ); - - /** - * Whether the xy-plane helper should be visible or not. - * - * @name TransformControls#showXY - * @type {boolean} - * @default true - */ - defineProperty( 'showXY', true ); - - /** - * Whether the yz-plane helper should be visible or not. - * - * @name TransformControls#showYZ - * @type {boolean} - * @default true - */ - defineProperty( 'showYZ', true ); - - /** - * Whether the xz-axis helper should be visible or not. - * - * @name TransformControls#showXZ - * @type {boolean} - * @default true - */ - defineProperty( 'showXZ', true ); - - /** - * Whether the xyze rotation helper should be visible or not. - * - * @name TransformControls#showXYZE - * @type {boolean} - * @default true - */ - defineProperty( 'showXYZE', true ); - - /** - * Whether the e rotation helper should be visible or not. - * - * @name TransformControls#showE - * @type {boolean} - * @default true - */ - defineProperty( 'showE', true ); - - /** - * The minimum allowed X position during translation. - * - * @name TransformControls#minX - * @type {number} - * @default -Infinity - */ - defineProperty( 'minX', - Infinity ); - - /** - * The maximum allowed X position during translation. - * - * @name TransformControls#maxX - * @type {number} - * @default Infinity - */ - defineProperty( 'maxX', Infinity ); - - /** - * The minimum allowed y position during translation. - * - * @name TransformControls#minY - * @type {number} - * @default -Infinity - */ - defineProperty( 'minY', - Infinity ); - - /** - * The maximum allowed Y position during translation. - * - * @name TransformControls#maxY - * @type {number} - * @default Infinity - */ - defineProperty( 'maxY', Infinity ); - - /** - * The minimum allowed z position during translation. - * - * @name TransformControls#minZ - * @type {number} - * @default -Infinity - */ - defineProperty( 'minZ', - Infinity ); - - /** - * The maximum allowed Z position during translation. - * - * @name TransformControls#maxZ - * @type {number} - * @default Infinity - */ - defineProperty( 'maxZ', Infinity ); - - // Reusable utility variables - - const worldPosition = new Vector3(); - const worldPositionStart = new Vector3(); - const worldQuaternion = new Quaternion(); - const worldQuaternionStart = new Quaternion(); - const cameraPosition = new Vector3(); - const cameraQuaternion = new Quaternion(); - const pointStart = new Vector3(); - const pointEnd = new Vector3(); - const rotationAxis = new Vector3(); - const rotationAngle = 0; - const eye = new Vector3(); - - // TODO: remove properties unused in plane and gizmo - - defineProperty( 'worldPosition', worldPosition ); - defineProperty( 'worldPositionStart', worldPositionStart ); - defineProperty( 'worldQuaternion', worldQuaternion ); - defineProperty( 'worldQuaternionStart', worldQuaternionStart ); - defineProperty( 'cameraPosition', cameraPosition ); - defineProperty( 'cameraQuaternion', cameraQuaternion ); - defineProperty( 'pointStart', pointStart ); - defineProperty( 'pointEnd', pointEnd ); - defineProperty( 'rotationAxis', rotationAxis ); - defineProperty( 'rotationAngle', rotationAngle ); - defineProperty( 'eye', eye ); - - this._offset = new Vector3(); - this._startNorm = new Vector3(); - this._endNorm = new Vector3(); - this._cameraScale = new Vector3(); - - this._parentPosition = new Vector3(); - this._parentQuaternion = new Quaternion(); - this._parentQuaternionInv = new Quaternion(); - this._parentScale = new Vector3(); - - this._worldScaleStart = new Vector3(); - this._worldQuaternionInv = new Quaternion(); - this._worldScale = new Vector3(); - - this._positionStart = new Vector3(); - this._quaternionStart = new Quaternion(); - this._scaleStart = new Vector3(); - - this._getPointer = getPointer.bind( this ); - this._onPointerDown = onPointerDown.bind( this ); - this._onPointerHover = onPointerHover.bind( this ); - this._onPointerMove = onPointerMove.bind( this ); - this._onPointerUp = onPointerUp.bind( this ); - - if ( domElement !== null ) { - - this.connect( domElement ); - - } - - } - - connect( element ) { - - super.connect( element ); - - this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); - this.domElement.addEventListener( 'pointermove', this._onPointerHover ); - this.domElement.addEventListener( 'pointerup', this._onPointerUp ); - - this.domElement.style.touchAction = 'none'; // Disable touch scroll - - } - - disconnect() { - - this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); - this.domElement.removeEventListener( 'pointermove', this._onPointerHover ); - this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); - this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); - - this.domElement.style.touchAction = ''; // Restore touch scroll - - } - - /** - * Returns the visual representation of the controls. Add the helper to your scene to - * visually transform the attached 3D object. - * - * @return {TransformControlsRoot} The helper. - */ - getHelper() { - - return this._root; - - } - - pointerHover( pointer ) { - - if ( this.object === undefined || this.dragging === true ) return; - - if ( pointer !== null ) _raycaster.setFromCamera( pointer, this.camera ); - - const intersect = intersectObjectWithRay( this._gizmo.picker[ this.mode ], _raycaster ); - - if ( intersect ) { - - this.axis = intersect.object.name; - - } else { - - this.axis = null; - - } - - } - - pointerDown( pointer ) { - - if ( this.object === undefined || this.dragging === true || ( pointer != null && pointer.button !== 0 ) ) return; - - if ( this.axis !== null ) { - - if ( pointer !== null ) _raycaster.setFromCamera( pointer, this.camera ); - - const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true ); - - if ( planeIntersect ) { - - this.object.updateMatrixWorld(); - this.object.parent.updateMatrixWorld(); - - this._positionStart.copy( this.object.position ); - this._quaternionStart.copy( this.object.quaternion ); - this._scaleStart.copy( this.object.scale ); - - this.object.matrixWorld.decompose( this.worldPositionStart, this.worldQuaternionStart, this._worldScaleStart ); - - this.pointStart.copy( planeIntersect.point ).sub( this.worldPositionStart ); - - } - - this.dragging = true; - _mouseDownEvent.mode = this.mode; - this.dispatchEvent( _mouseDownEvent ); - - } - - } - - pointerMove( pointer ) { - - const axis = this.axis; - const mode = this.mode; - const object = this.object; - let space = this.space; - - if ( mode === 'scale' ) { - - space = 'local'; - - } else if ( axis === 'E' || axis === 'XYZE' || axis === 'XYZ' ) { - - space = 'world'; - - } - - if ( object === undefined || axis === null || this.dragging === false || ( pointer !== null && pointer.button !== - 1 ) ) return; - - if ( pointer !== null ) _raycaster.setFromCamera( pointer, this.camera ); - - const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true ); - - if ( ! planeIntersect ) return; - - this.pointEnd.copy( planeIntersect.point ).sub( this.worldPositionStart ); - - if ( mode === 'translate' ) { - - // Apply translate - - this._offset.copy( this.pointEnd ).sub( this.pointStart ); - - if ( space === 'local' && axis !== 'XYZ' ) { - - this._offset.applyQuaternion( this._worldQuaternionInv ); - - } - - if ( axis.indexOf( 'X' ) === - 1 ) this._offset.x = 0; - if ( axis.indexOf( 'Y' ) === - 1 ) this._offset.y = 0; - if ( axis.indexOf( 'Z' ) === - 1 ) this._offset.z = 0; - - if ( space === 'local' && axis !== 'XYZ' ) { - - this._offset.applyQuaternion( this._quaternionStart ).divide( this._parentScale ); - - } else { - - this._offset.applyQuaternion( this._parentQuaternionInv ).divide( this._parentScale ); - - } - - object.position.copy( this._offset ).add( this._positionStart ); - - // Apply translation snap - - if ( this.translationSnap ) { - - if ( space === 'local' ) { - - object.position.applyQuaternion( _tempQuaternion.copy( this._quaternionStart ).invert() ); - - if ( axis.search( 'X' ) !== - 1 ) { - - object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap; - - } - - if ( axis.search( 'Y' ) !== - 1 ) { - - object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap; - - } - - if ( axis.search( 'Z' ) !== - 1 ) { - - object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap; - - } - - object.position.applyQuaternion( this._quaternionStart ); - - } - - if ( space === 'world' ) { - - object.getWorldPosition( _tempVector ); - - if ( axis.search( 'X' ) !== - 1 ) { - - _tempVector.x = Math.round( _tempVector.x / this.translationSnap ) * this.translationSnap; - - } - - if ( axis.search( 'Y' ) !== - 1 ) { - - _tempVector.y = Math.round( _tempVector.y / this.translationSnap ) * this.translationSnap; - - } - - if ( axis.search( 'Z' ) !== - 1 ) { - - _tempVector.z = Math.round( _tempVector.z / this.translationSnap ) * this.translationSnap; - - } - - object.position.copy( object.parent.worldToLocal( _tempVector ) ); - - } - - } - - object.position.x = Math.max( this.minX, Math.min( this.maxX, object.position.x ) ); - object.position.y = Math.max( this.minY, Math.min( this.maxY, object.position.y ) ); - object.position.z = Math.max( this.minZ, Math.min( this.maxZ, object.position.z ) ); - - } else if ( mode === 'scale' ) { - - if ( axis.search( 'XYZ' ) !== - 1 ) { - - let d = this.pointEnd.length() / this.pointStart.length(); - - if ( this.pointEnd.dot( this.pointStart ) < 0 ) d *= - 1; - - _tempVector2.set( d, d, d ); - - } else { - - _tempVector.copy( this.pointStart ); - _tempVector2.copy( this.pointEnd ); - - _tempVector.applyQuaternion( this._worldQuaternionInv ); - _tempVector2.applyQuaternion( this._worldQuaternionInv ); - - _tempVector2.divide( _tempVector ); - - if ( axis.search( 'X' ) === - 1 ) { - - _tempVector2.x = 1; - - } - - if ( axis.search( 'Y' ) === - 1 ) { - - _tempVector2.y = 1; - - } - - if ( axis.search( 'Z' ) === - 1 ) { - - _tempVector2.z = 1; - - } - - } - - // Apply scale - - object.scale.copy( this._scaleStart ).multiply( _tempVector2 ); - - if ( this.scaleSnap ) { - - if ( axis.search( 'X' ) !== - 1 ) { - - object.scale.x = Math.round( object.scale.x / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; - - } - - if ( axis.search( 'Y' ) !== - 1 ) { - - object.scale.y = Math.round( object.scale.y / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; - - } - - if ( axis.search( 'Z' ) !== - 1 ) { - - object.scale.z = Math.round( object.scale.z / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; - - } - - } - - } else if ( mode === 'rotate' ) { - - this._offset.copy( this.pointEnd ).sub( this.pointStart ); - - const ROTATION_SPEED = 20 / this.worldPosition.distanceTo( _tempVector.setFromMatrixPosition( this.camera.matrixWorld ) ); - - let _inPlaneRotation = false; - - if ( axis === 'XYZE' ) { - - this.rotationAxis.copy( this._offset ).cross( this.eye ).normalize(); - this.rotationAngle = this._offset.dot( _tempVector.copy( this.rotationAxis ).cross( this.eye ) ) * ROTATION_SPEED; - - } else if ( axis === 'X' || axis === 'Y' || axis === 'Z' ) { - - this.rotationAxis.copy( _unit[ axis ] ); - - _tempVector.copy( _unit[ axis ] ); - - if ( space === 'local' ) { - - _tempVector.applyQuaternion( this.worldQuaternion ); - - } - - _tempVector.cross( this.eye ); - - // When _tempVector is 0 after cross with this.eye the vectors are parallel and should use in-plane rotation logic. - if ( _tempVector.length() === 0 ) { - - _inPlaneRotation = true; - - } else { - - this.rotationAngle = this._offset.dot( _tempVector.normalize() ) * ROTATION_SPEED; - - } - - - } - - if ( axis === 'E' || _inPlaneRotation ) { - - this.rotationAxis.copy( this.eye ); - this.rotationAngle = this.pointEnd.angleTo( this.pointStart ); - - this._startNorm.copy( this.pointStart ).normalize(); - this._endNorm.copy( this.pointEnd ).normalize(); - - this.rotationAngle *= ( this._endNorm.cross( this._startNorm ).dot( this.eye ) < 0 ? 1 : - 1 ); - - } - - // Apply rotation snap - - if ( this.rotationSnap ) this.rotationAngle = Math.round( this.rotationAngle / this.rotationSnap ) * this.rotationSnap; - - // Apply rotate - if ( space === 'local' && axis !== 'E' && axis !== 'XYZE' ) { - - object.quaternion.copy( this._quaternionStart ); - object.quaternion.multiply( _tempQuaternion.setFromAxisAngle( this.rotationAxis, this.rotationAngle ) ).normalize(); - - } else { - - this.rotationAxis.applyQuaternion( this._parentQuaternionInv ); - object.quaternion.copy( _tempQuaternion.setFromAxisAngle( this.rotationAxis, this.rotationAngle ) ); - object.quaternion.multiply( this._quaternionStart ).normalize(); - - } - - } - - this.dispatchEvent( _changeEvent ); - this.dispatchEvent( _objectChangeEvent ); - - } - - pointerUp( pointer ) { - - if ( pointer !== null && pointer.button !== 0 ) return; - - if ( this.dragging && ( this.axis !== null ) ) { - - _mouseUpEvent.mode = this.mode; - this.dispatchEvent( _mouseUpEvent ); - - } - - this.dragging = false; - this.axis = null; - - } - - dispose() { - - this.disconnect(); - - this._root.dispose(); - - } - - /** - * Sets the 3D object that should be transformed and ensures the controls UI is visible. - * - * @param {Object3D} object - The 3D object that should be transformed. - * @return {TransformControls} A reference to this controls. - */ - attach( object ) { - - this.object = object; - this._root.visible = true; - - return this; - - } - - /** - * Removes the current 3D object from the controls and makes the helper UI invisible. - * - * @return {TransformControls} A reference to this controls. - */ - detach() { - - this.object = undefined; - this.axis = null; - - this._root.visible = false; - - return this; - - } - - /** - * Resets the object's position, rotation and scale to when the current transform began. - */ - reset() { - - if ( ! this.enabled ) return; - - if ( this.dragging ) { - - this.object.position.copy( this._positionStart ); - this.object.quaternion.copy( this._quaternionStart ); - this.object.scale.copy( this._scaleStart ); - - this.dispatchEvent( _changeEvent ); - this.dispatchEvent( _objectChangeEvent ); - - this.pointStart.copy( this.pointEnd ); - - } - - } - - /** - * Returns the raycaster that is used for user interaction. This object is shared between all - * instances of `TransformControls`. - * - * @returns {Raycaster} The internal raycaster. - */ - getRaycaster() { - - return _raycaster; - - } - - /** - * Returns the transformation mode. - * - * @returns {'translate'|'rotate'|'scale'} The transformation mode. - */ - getMode() { - - return this.mode; - - } - - /** - * Sets the given transformation mode. - * - * @param {'translate'|'rotate'|'scale'} mode - The transformation mode to set. - */ - setMode( mode ) { - - this.mode = mode; - - } - - /** - * Sets the translation snap. - * - * @param {?number} translationSnap - The translation snap to set. - */ - setTranslationSnap( translationSnap ) { - - this.translationSnap = translationSnap; - - } - - /** - * Sets the rotation snap. - * - * @param {?number} rotationSnap - The rotation snap to set. - */ - setRotationSnap( rotationSnap ) { - - this.rotationSnap = rotationSnap; - - } - - /** - * Sets the scale snap. - * - * @param {?number} scaleSnap - The scale snap to set. - */ - setScaleSnap( scaleSnap ) { - - this.scaleSnap = scaleSnap; - - } - - /** - * Sets the size of the helper UI. - * - * @param {number} size - The size to set. - */ - setSize( size ) { - - this.size = size; - - } - - /** - * Sets the coordinate space in which transformations are applied. - * - * @param {'world'|'local'} space - The space to set. - */ - setSpace( space ) { - - this.space = space; - - } - - /** - * Sets the colors of the control's gizmo. - * - * @param {number|Color|string} xAxis - The x-axis color. - * @param {number|Color|string} yAxis - The y-axis color. - * @param {number|Color|string} zAxis - The z-axis color. - * @param {number|Color|string} active - The color for active elements. - */ - setColors( xAxis, yAxis, zAxis, active ) { - - const materialLib = this._gizmo.materialLib; - - materialLib.xAxis.color.set( xAxis ); - materialLib.yAxis.color.set( yAxis ); - materialLib.zAxis.color.set( zAxis ); - materialLib.active.color.set( active ); - materialLib.xAxisTransparent.color.set( xAxis ); - materialLib.yAxisTransparent.color.set( yAxis ); - materialLib.zAxisTransparent.color.set( zAxis ); - materialLib.activeTransparent.color.set( active ); - - // update color caches - - if ( materialLib.xAxis._color ) materialLib.xAxis._color.set( xAxis ); - if ( materialLib.yAxis._color ) materialLib.yAxis._color.set( yAxis ); - if ( materialLib.zAxis._color ) materialLib.zAxis._color.set( zAxis ); - if ( materialLib.active._color ) materialLib.active._color.set( active ); - if ( materialLib.xAxisTransparent._color ) materialLib.xAxisTransparent._color.set( xAxis ); - if ( materialLib.yAxisTransparent._color ) materialLib.yAxisTransparent._color.set( yAxis ); - if ( materialLib.zAxisTransparent._color ) materialLib.zAxisTransparent._color.set( zAxis ); - if ( materialLib.activeTransparent._color ) materialLib.activeTransparent._color.set( active ); - - } - -} - -// mouse / touch event handlers - -function getPointer( event ) { - - if ( this.domElement.ownerDocument.pointerLockElement ) { - - return { - x: 0, - y: 0, - button: event.button - }; - - } else { - - const rect = this.domElement.getBoundingClientRect(); - const viewport = this.viewport; - - let originX, originY, regionWidth, regionHeight; - - if ( viewport !== null ) { - - originX = viewport.x; - originY = rect.height - viewport.y - viewport.w; - regionWidth = viewport.z; - regionHeight = viewport.w; - - } else { - - originX = 0; - originY = 0; - regionWidth = rect.width; - regionHeight = rect.height; - - } - - return { - x: ( event.clientX - rect.left - originX ) / regionWidth * 2 - 1, - y: - ( event.clientY - rect.top - originY ) / regionHeight * 2 + 1, - button: event.button - }; - - } - -} - -function onPointerHover( event ) { - - if ( ! this.enabled ) return; - - switch ( event.pointerType ) { - - case 'mouse': - case 'pen': - this.pointerHover( this._getPointer( event ) ); - break; - - } - -} - -function onPointerDown( event ) { - - if ( ! this.enabled ) return; - - if ( ! document.pointerLockElement ) { - - this.domElement.setPointerCapture( event.pointerId ); - - } - - this.domElement.addEventListener( 'pointermove', this._onPointerMove ); - - this.pointerHover( this._getPointer( event ) ); - this.pointerDown( this._getPointer( event ) ); - -} - -function onPointerMove( event ) { - - if ( ! this.enabled ) return; - - this.pointerMove( this._getPointer( event ) ); - -} - -function onPointerUp( event ) { - - if ( ! this.enabled ) return; - - this.domElement.releasePointerCapture( event.pointerId ); - - this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); - - this.pointerUp( this._getPointer( event ) ); - -} - -function intersectObjectWithRay( object, raycaster, includeInvisible ) { - - const allIntersections = raycaster.intersectObject( object, true ); - - for ( let i = 0; i < allIntersections.length; i ++ ) { - - if ( allIntersections[ i ].object.visible || includeInvisible ) { - - return allIntersections[ i ]; - - } - - } - - return false; - -} - -// - -// Reusable utility variables - -const _tempEuler = new Euler(); -const _alignVector = new Vector3( 0, 1, 0 ); -const _zeroVector = new Vector3( 0, 0, 0 ); -const _lookAtMatrix = new Matrix4(); -const _tempQuaternion2 = new Quaternion(); -const _identityQuaternion = new Quaternion(); -const _dirVector = new Vector3(); -const _tempMatrix = new Matrix4(); - -const _unitX = new Vector3( 1, 0, 0 ); -const _unitY = new Vector3( 0, 1, 0 ); -const _unitZ = new Vector3( 0, 0, 1 ); - -const _v1 = new Vector3(); -const _v2 = new Vector3(); -const _v3 = new Vector3(); - -class TransformControlsRoot extends Object3D { - - constructor( controls ) { - - super(); - - this.isTransformControlsRoot = true; - - this.controls = controls; - this.visible = false; - - } - - // updateMatrixWorld updates key transformation variables - updateMatrixWorld( force ) { - - const controls = this.controls; - - if ( controls.object !== undefined ) { - - controls.object.updateMatrixWorld(); - - if ( controls.object.parent === null ) { - - console.error( 'TransformControls: The attached 3D object must be a part of the scene graph.' ); - - } else { - - controls.object.parent.matrixWorld.decompose( controls._parentPosition, controls._parentQuaternion, controls._parentScale ); - - } - - controls.object.matrixWorld.decompose( controls.worldPosition, controls.worldQuaternion, controls._worldScale ); - - controls._parentQuaternionInv.copy( controls._parentQuaternion ).invert(); - controls._worldQuaternionInv.copy( controls.worldQuaternion ).invert(); - - } - - controls.camera.updateMatrixWorld(); - controls.camera.matrixWorld.decompose( controls.cameraPosition, controls.cameraQuaternion, controls._cameraScale ); - - if ( controls.camera.isOrthographicCamera ) { - - controls.camera.getWorldDirection( controls.eye ).negate(); - - } else { - - controls.eye.copy( controls.cameraPosition ).sub( controls.worldPosition ).normalize(); - - } - - // Cancel out the parent's transform so the gizmo stays world-aligned. - - if ( this.parent ) { - - _tempMatrix.copy( this.parent.matrixWorld ).invert(); - _tempMatrix.decompose( this.position, this.quaternion, this.scale ); - - } - - super.updateMatrixWorld( force ); - - } - - dispose() { - - this.traverse( function ( child ) { - - if ( child.geometry ) child.geometry.dispose(); - if ( child.material ) child.material.dispose(); - - } ); - - } - -} - -class TransformControlsGizmo extends Object3D { - - constructor() { - - super(); - - this.isTransformControlsGizmo = true; - - this.type = 'TransformControlsGizmo'; - - // shared materials - - const gizmoMaterial = new MeshBasicMaterial( { - depthTest: false, - depthWrite: false, - fog: false, - toneMapped: false, - transparent: true - } ); - - const gizmoLineMaterial = new LineBasicMaterial( { - depthTest: false, - depthWrite: false, - fog: false, - toneMapped: false, - transparent: true - } ); - - // Make unique material for each axis/color - - const matInvisible = gizmoMaterial.clone(); - matInvisible.opacity = 0.15; - - const matHelper = gizmoLineMaterial.clone(); - matHelper.opacity = 0.5; - - const matRed = gizmoMaterial.clone(); - matRed.color.setHex( 0xff0000 ); - - const matGreen = gizmoMaterial.clone(); - matGreen.color.setHex( 0x00ff00 ); - - const matBlue = gizmoMaterial.clone(); - matBlue.color.setHex( 0x0000ff ); - - const matRedTransparent = gizmoMaterial.clone(); - matRedTransparent.color.setHex( 0xff0000 ); - matRedTransparent.opacity = 0.5; - - const matGreenTransparent = gizmoMaterial.clone(); - matGreenTransparent.color.setHex( 0x00ff00 ); - matGreenTransparent.opacity = 0.5; - - const matBlueTransparent = gizmoMaterial.clone(); - matBlueTransparent.color.setHex( 0x0000ff ); - matBlueTransparent.opacity = 0.5; - - const matWhiteTransparent = gizmoMaterial.clone(); - matWhiteTransparent.opacity = 0.25; - - const matYellowTransparent = gizmoMaterial.clone(); - matYellowTransparent.color.setHex( 0xffff00 ); - matYellowTransparent.opacity = 0.25; - - const matYellow = gizmoMaterial.clone(); - matYellow.color.setHex( 0xffff00 ); - - const matGray = gizmoMaterial.clone(); - matGray.color.setHex( 0x787878 ); - - // materials in the below property are configurable via setColors() - - this.materialLib = { - xAxis: matRed, - yAxis: matGreen, - zAxis: matBlue, - active: matYellow, - xAxisTransparent: matRedTransparent, - yAxisTransparent: matGreenTransparent, - zAxisTransparent: matBlueTransparent, - activeTransparent: matYellowTransparent - }; - - // reusable geometry - - const arrowGeometry = new CylinderGeometry( 0, 0.04, 0.1, 12 ); - arrowGeometry.translate( 0, 0.05, 0 ); - - const scaleHandleGeometry = new BoxGeometry( 0.08, 0.08, 0.08 ); - scaleHandleGeometry.translate( 0, 0.04, 0 ); - - const lineGeometry = new BufferGeometry(); - lineGeometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 1, 0, 0 ], 3 ) ); - - const lineGeometry2 = new CylinderGeometry( 0.0075, 0.0075, 0.5, 3 ); - lineGeometry2.translate( 0, 0.25, 0 ); - - function CircleGeometry( radius, arc ) { - - const geometry = new TorusGeometry( radius, 0.0075, 3, 64, arc * Math.PI * 2 ); - geometry.rotateY( Math.PI / 2 ); - geometry.rotateX( Math.PI / 2 ); - return geometry; - - } - - // Special geometry for transform helper. If scaled with position vector it spans from [0,0,0] to position - - function TranslateHelperGeometry() { - - const geometry = new BufferGeometry(); - - geometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 1, 1, 1 ], 3 ) ); - - return geometry; - - } - - // Gizmo definitions - custom hierarchy definitions for setupGizmo() function - - const gizmoTranslate = { - X: [ - [ new Mesh( arrowGeometry, matRed ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], - [ new Mesh( arrowGeometry, matRed ), [ - 0.5, 0, 0 ], [ 0, 0, Math.PI / 2 ]], - [ new Mesh( lineGeometry2, matRed ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]] - ], - Y: [ - [ new Mesh( arrowGeometry, matGreen ), [ 0, 0.5, 0 ]], - [ new Mesh( arrowGeometry, matGreen ), [ 0, - 0.5, 0 ], [ Math.PI, 0, 0 ]], - [ new Mesh( lineGeometry2, matGreen ) ] - ], - Z: [ - [ new Mesh( arrowGeometry, matBlue ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]], - [ new Mesh( arrowGeometry, matBlue ), [ 0, 0, - 0.5 ], [ - Math.PI / 2, 0, 0 ]], - [ new Mesh( lineGeometry2, matBlue ), null, [ Math.PI / 2, 0, 0 ]] - ], - XYZ: [ - [ new Mesh( new OctahedronGeometry( 0.1, 0 ), matWhiteTransparent ), [ 0, 0, 0 ]] - ], - XY: [ - [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent ), [ 0.15, 0.15, 0 ]] - ], - YZ: [ - [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]] - ], - XZ: [ - [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]] - ] - }; - - const pickerTranslate = { - X: [ - [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0.3, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], - [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ - 0.3, 0, 0 ], [ 0, 0, Math.PI / 2 ]] - ], - Y: [ - [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0.3, 0 ]], - [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, - 0.3, 0 ], [ 0, 0, Math.PI ]] - ], - Z: [ - [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, 0.3 ], [ Math.PI / 2, 0, 0 ]], - [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, - 0.3 ], [ - Math.PI / 2, 0, 0 ]] - ], - XYZ: [ - [ new Mesh( new OctahedronGeometry( 0.2, 0 ), matInvisible ) ] - ], - XY: [ - [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0.15, 0 ]] - ], - YZ: [ - [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]] - ], - XZ: [ - [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]] - ] - }; - - const helperTranslate = { - START: [ - [ new Mesh( new OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ] - ], - END: [ - [ new Mesh( new OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ] - ], - DELTA: [ - [ new Line( TranslateHelperGeometry(), matHelper ), null, null, null, 'helper' ] - ], - X: [ - [ new Line( lineGeometry, matHelper ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] - ], - Y: [ - [ new Line( lineGeometry, matHelper ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ] - ], - Z: [ - [ new Line( lineGeometry, matHelper ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ] - ] - }; - - const gizmoRotate = { - XYZE: [ - [ new Mesh( CircleGeometry( 0.5, 1 ), matGray ), null, [ 0, Math.PI / 2, 0 ]] - ], - X: [ - [ new Mesh( CircleGeometry( 0.5, 0.5 ), matRed ) ] - ], - Y: [ - [ new Mesh( CircleGeometry( 0.5, 0.5 ), matGreen ), null, [ 0, 0, - Math.PI / 2 ]] - ], - Z: [ - [ new Mesh( CircleGeometry( 0.5, 0.5 ), matBlue ), null, [ 0, Math.PI / 2, 0 ]] - ], - E: [ - [ new Mesh( CircleGeometry( 0.75, 1 ), matYellowTransparent ), null, [ 0, Math.PI / 2, 0 ]] - ] - }; - - const helperRotate = { - AXIS: [ - [ new Line( lineGeometry, matHelper ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] - ] - }; - - const pickerRotate = { - XYZE: [ - [ new Mesh( new SphereGeometry( 0.25, 10, 8 ), matInvisible ) ] - ], - X: [ - [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, - Math.PI / 2, - Math.PI / 2 ]], - ], - Y: [ - [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]], - ], - Z: [ - [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], - ], - E: [ - [ new Mesh( new TorusGeometry( 0.75, 0.1, 2, 24 ), matInvisible ) ] - ] - }; - - const gizmoScale = { - X: [ - [ new Mesh( scaleHandleGeometry, matRed ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], - [ new Mesh( lineGeometry2, matRed ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], - [ new Mesh( scaleHandleGeometry, matRed ), [ - 0.5, 0, 0 ], [ 0, 0, Math.PI / 2 ]], - ], - Y: [ - [ new Mesh( scaleHandleGeometry, matGreen ), [ 0, 0.5, 0 ]], - [ new Mesh( lineGeometry2, matGreen ) ], - [ new Mesh( scaleHandleGeometry, matGreen ), [ 0, - 0.5, 0 ], [ 0, 0, Math.PI ]], - ], - Z: [ - [ new Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]], - [ new Mesh( lineGeometry2, matBlue ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]], - [ new Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, - 0.5 ], [ - Math.PI / 2, 0, 0 ]] - ], - XY: [ - [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent ), [ 0.15, 0.15, 0 ]] - ], - YZ: [ - [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]] - ], - XZ: [ - [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]] - ], - XYZ: [ - [ new Mesh( new BoxGeometry( 0.1, 0.1, 0.1 ), matWhiteTransparent ) ], - ] - }; - - const pickerScale = { - X: [ - [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0.3, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], - [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ - 0.3, 0, 0 ], [ 0, 0, Math.PI / 2 ]] - ], - Y: [ - [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0.3, 0 ]], - [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, - 0.3, 0 ], [ 0, 0, Math.PI ]] - ], - Z: [ - [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, 0.3 ], [ Math.PI / 2, 0, 0 ]], - [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, - 0.3 ], [ - Math.PI / 2, 0, 0 ]] - ], - XY: [ - [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0.15, 0 ]], - ], - YZ: [ - [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]], - ], - XZ: [ - [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]], - ], - XYZ: [ - [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 0, 0 ]], - ] - }; - - const helperScale = { - X: [ - [ new Line( lineGeometry, matHelper ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] - ], - Y: [ - [ new Line( lineGeometry, matHelper ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ] - ], - Z: [ - [ new Line( lineGeometry, matHelper ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ] - ] - }; - - // Creates an Object3D with gizmos described in custom hierarchy definition. - - function setupGizmo( gizmoMap ) { - - const gizmo = new Object3D(); - - for ( const name in gizmoMap ) { - - for ( let i = gizmoMap[ name ].length; i --; ) { - - const object = gizmoMap[ name ][ i ][ 0 ].clone(); - const position = gizmoMap[ name ][ i ][ 1 ]; - const rotation = gizmoMap[ name ][ i ][ 2 ]; - const scale = gizmoMap[ name ][ i ][ 3 ]; - const tag = gizmoMap[ name ][ i ][ 4 ]; - - // name and tag properties are essential for picking and updating logic. - object.name = name; - object.tag = tag; - - if ( position ) { - - object.position.set( position[ 0 ], position[ 1 ], position[ 2 ] ); - - } - - if ( rotation ) { - - object.rotation.set( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ] ); - - } - - if ( scale ) { - - object.scale.set( scale[ 0 ], scale[ 1 ], scale[ 2 ] ); - - } - - object.updateMatrix(); - - const tempGeometry = object.geometry.clone(); - tempGeometry.applyMatrix4( object.matrix ); - object.geometry = tempGeometry; - object.renderOrder = Infinity; - - object.position.set( 0, 0, 0 ); - object.rotation.set( 0, 0, 0 ); - object.scale.set( 1, 1, 1 ); - - gizmo.add( object ); - - } - - } - - return gizmo; - - } - - // Gizmo creation - - this.gizmo = {}; - this.picker = {}; - this.helper = {}; - - this.add( this.gizmo[ 'translate' ] = setupGizmo( gizmoTranslate ) ); - this.add( this.gizmo[ 'rotate' ] = setupGizmo( gizmoRotate ) ); - this.add( this.gizmo[ 'scale' ] = setupGizmo( gizmoScale ) ); - this.add( this.picker[ 'translate' ] = setupGizmo( pickerTranslate ) ); - this.add( this.picker[ 'rotate' ] = setupGizmo( pickerRotate ) ); - this.add( this.picker[ 'scale' ] = setupGizmo( pickerScale ) ); - this.add( this.helper[ 'translate' ] = setupGizmo( helperTranslate ) ); - this.add( this.helper[ 'rotate' ] = setupGizmo( helperRotate ) ); - this.add( this.helper[ 'scale' ] = setupGizmo( helperScale ) ); - - // Pickers should be hidden always - - this.picker[ 'translate' ].visible = false; - this.picker[ 'rotate' ].visible = false; - this.picker[ 'scale' ].visible = false; - - } - - // updateMatrixWorld will update transformations and appearance of individual handles - - updateMatrixWorld( force ) { - - const space = ( this.mode === 'scale' ) ? 'local' : this.space; // scale always oriented to local rotation - - const quaternion = ( space === 'local' ) ? this.worldQuaternion : _identityQuaternion; - - // Show only gizmos for current transform mode - - this.gizmo[ 'translate' ].visible = this.mode === 'translate'; - this.gizmo[ 'rotate' ].visible = this.mode === 'rotate'; - this.gizmo[ 'scale' ].visible = this.mode === 'scale'; - - this.helper[ 'translate' ].visible = this.mode === 'translate'; - this.helper[ 'rotate' ].visible = this.mode === 'rotate'; - this.helper[ 'scale' ].visible = this.mode === 'scale'; - - - let handles = []; - handles = handles.concat( this.picker[ this.mode ].children ); - handles = handles.concat( this.gizmo[ this.mode ].children ); - handles = handles.concat( this.helper[ this.mode ].children ); - - for ( let i = 0; i < handles.length; i ++ ) { - - const handle = handles[ i ]; - - // hide aligned to camera - - handle.visible = true; - handle.rotation.set( 0, 0, 0 ); - handle.position.copy( this.worldPosition ); - - let factor; - - if ( this.camera.isOrthographicCamera ) { - - factor = ( this.camera.top - this.camera.bottom ) / this.camera.zoom; - - } else { - - factor = this.worldPosition.distanceTo( this.cameraPosition ) * Math.min( 1.9 * Math.tan( Math.PI * this.camera.fov / 360 ) / this.camera.zoom, 7 ); - - } - - handle.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 4 ); - - // TODO: simplify helpers and consider decoupling from gizmo - - if ( handle.tag === 'helper' ) { - - handle.visible = false; - - if ( handle.name === 'AXIS' ) { - - handle.visible = !! this.axis; - - if ( this.axis === 'X' ) { - - _tempQuaternion.setFromEuler( _tempEuler.set( 0, 0, 0 ) ); - handle.quaternion.copy( quaternion ).multiply( _tempQuaternion ); - - if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) { - - handle.visible = false; - - } - - } - - if ( this.axis === 'Y' ) { - - _tempQuaternion.setFromEuler( _tempEuler.set( 0, 0, Math.PI / 2 ) ); - handle.quaternion.copy( quaternion ).multiply( _tempQuaternion ); - - if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) { - - handle.visible = false; - - } - - } - - if ( this.axis === 'Z' ) { - - _tempQuaternion.setFromEuler( _tempEuler.set( 0, Math.PI / 2, 0 ) ); - handle.quaternion.copy( quaternion ).multiply( _tempQuaternion ); - - if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) { - - handle.visible = false; - - } - - } - - if ( this.axis === 'XYZE' ) { - - _tempQuaternion.setFromEuler( _tempEuler.set( 0, Math.PI / 2, 0 ) ); - _alignVector.copy( this.rotationAxis ); - handle.quaternion.setFromRotationMatrix( _lookAtMatrix.lookAt( _zeroVector, _alignVector, _unitY ) ); - handle.quaternion.multiply( _tempQuaternion ); - handle.visible = this.dragging; - - } - - if ( this.axis === 'E' ) { - - handle.visible = false; - - } - - - } else if ( handle.name === 'START' ) { - - handle.position.copy( this.worldPositionStart ); - handle.visible = this.dragging; - - } else if ( handle.name === 'END' ) { - - handle.position.copy( this.worldPosition ); - handle.visible = this.dragging; - - } else if ( handle.name === 'DELTA' ) { - - handle.position.copy( this.worldPositionStart ); - handle.quaternion.copy( this.worldQuaternionStart ); - _tempVector.set( 1e-10, 1e-10, 1e-10 ).add( this.worldPositionStart ).sub( this.worldPosition ).multiplyScalar( - 1 ); - _tempVector.applyQuaternion( this.worldQuaternionStart.clone().invert() ); - handle.scale.copy( _tempVector ); - handle.visible = this.dragging; - - } else { - - handle.quaternion.copy( quaternion ); - - if ( this.dragging ) { - - handle.position.copy( this.worldPositionStart ); - - } else { - - handle.position.copy( this.worldPosition ); - - } - - if ( this.axis ) { - - handle.visible = this.axis.search( handle.name ) !== - 1; - - } - - } - - // If updating helper, skip rest of the loop - continue; - - } - - // Align handles to current local or world rotation - - handle.quaternion.copy( quaternion ); - - if ( this.mode === 'translate' || this.mode === 'scale' ) { - - // Hide translate and scale axis facing the camera - - const AXIS_HIDE_THRESHOLD = 0.99; - const PLANE_HIDE_THRESHOLD = 0.2; - - if ( handle.name === 'X' ) { - - if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_THRESHOLD ) { - - handle.scale.set( 1e-10, 1e-10, 1e-10 ); - handle.visible = false; - - } - - } - - if ( handle.name === 'Y' ) { - - if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_THRESHOLD ) { - - handle.scale.set( 1e-10, 1e-10, 1e-10 ); - handle.visible = false; - - } - - } - - if ( handle.name === 'Z' ) { - - if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_THRESHOLD ) { - - handle.scale.set( 1e-10, 1e-10, 1e-10 ); - handle.visible = false; - - } - - } - - if ( handle.name === 'XY' ) { - - if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_THRESHOLD ) { - - handle.scale.set( 1e-10, 1e-10, 1e-10 ); - handle.visible = false; - - } - - } - - if ( handle.name === 'YZ' ) { - - if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_THRESHOLD ) { - - handle.scale.set( 1e-10, 1e-10, 1e-10 ); - handle.visible = false; - - } - - } - - if ( handle.name === 'XZ' ) { - - if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_THRESHOLD ) { - - handle.scale.set( 1e-10, 1e-10, 1e-10 ); - handle.visible = false; - - } - - } - - } else if ( this.mode === 'rotate' ) { - - // Align handles to current local or world rotation - - _tempQuaternion2.copy( quaternion ); - _alignVector.copy( this.eye ).applyQuaternion( _tempQuaternion.copy( quaternion ).invert() ); - - if ( handle.name.search( 'E' ) !== - 1 ) { - - handle.quaternion.setFromRotationMatrix( _lookAtMatrix.lookAt( this.eye, _zeroVector, _unitY ) ); - - } - - if ( handle.name === 'X' ) { - - _tempQuaternion.setFromAxisAngle( _unitX, Math.atan2( - _alignVector.y, _alignVector.z ) ); - _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion ); - handle.quaternion.copy( _tempQuaternion ); - - } - - if ( handle.name === 'Y' ) { - - _tempQuaternion.setFromAxisAngle( _unitY, Math.atan2( _alignVector.x, _alignVector.z ) ); - _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion ); - handle.quaternion.copy( _tempQuaternion ); - - } - - if ( handle.name === 'Z' ) { - - _tempQuaternion.setFromAxisAngle( _unitZ, Math.atan2( _alignVector.y, _alignVector.x ) ); - _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion ); - handle.quaternion.copy( _tempQuaternion ); - - } - - } - - // Hide disabled axes - handle.visible = handle.visible && ( handle.name.indexOf( 'X' ) === - 1 || this.showX ); - handle.visible = handle.visible && ( handle.name.indexOf( 'Y' ) === - 1 || this.showY ); - handle.visible = handle.visible && ( handle.name.indexOf( 'Z' ) === - 1 || this.showZ ); - handle.visible = handle.visible && ( handle.name.indexOf( 'E' ) === - 1 || ( this.showX && this.showY && this.showZ ) ); - - // Hide disabled plane helpers - handle.visible = handle.visible && ( handle.name.indexOf( 'XY' ) === - 1 || this.showXY ); - handle.visible = handle.visible && ( handle.name.indexOf( 'YZ' ) === - 1 || this.showYZ ); - handle.visible = handle.visible && ( handle.name.indexOf( 'XZ' ) === - 1 || this.showXZ ); - - // Hide disabled rotation helpers - handle.visible = handle.visible && ( handle.name !== 'E' || this.showE ); - handle.visible = handle.visible && ( handle.name !== 'XYZE' || this.showXYZE ); - - // highlight selected axis - - handle.material._color = handle.material._color || handle.material.color.clone(); - handle.material._opacity = handle.material._opacity || handle.material.opacity; - - handle.material.color.copy( handle.material._color ); - handle.material.opacity = handle.material._opacity; - - if ( this.enabled && this.axis ) { - - if ( handle.name === this.axis ) { - - handle.material.color.copy( this.materialLib.active.color ); - handle.material.opacity = 1.0; - - } else if ( this.axis.split( '' ).some( function ( a ) { - - return handle.name === a; - - } ) ) { - - handle.material.color.copy( this.materialLib.active.color ); - handle.material.opacity = 1.0; - - } - - } - - } - - super.updateMatrixWorld( force ); - - } - -} - -// - -class TransformControlsPlane extends Mesh { - - constructor() { - - super( - new PlaneGeometry( 100000, 100000, 2, 2 ), - new MeshBasicMaterial( { visible: false, wireframe: true, side: DoubleSide, transparent: true, opacity: 0.1, toneMapped: false } ) - ); - - this.isTransformControlsPlane = true; - - this.type = 'TransformControlsPlane'; - - } - - updateMatrixWorld( force ) { - - let space = this.space; - - this.position.copy( this.worldPosition ); - - if ( this.mode === 'scale' ) space = 'local'; // scale always oriented to local rotation - - _v1.copy( _unitX ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); - _v2.copy( _unitY ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); - _v3.copy( _unitZ ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); - - // Align the plane for current transform mode, axis and space. - - _alignVector.copy( _v2 ); - - switch ( this.mode ) { - - case 'translate': - case 'scale': - switch ( this.axis ) { - - case 'X': - _alignVector.copy( this.eye ).cross( _v1 ); - _dirVector.copy( _v1 ).cross( _alignVector ); - break; - case 'Y': - _alignVector.copy( this.eye ).cross( _v2 ); - _dirVector.copy( _v2 ).cross( _alignVector ); - break; - case 'Z': - _alignVector.copy( this.eye ).cross( _v3 ); - _dirVector.copy( _v3 ).cross( _alignVector ); - break; - case 'XY': - _dirVector.copy( _v3 ); - break; - case 'YZ': - _dirVector.copy( _v1 ); - break; - case 'XZ': - _alignVector.copy( _v3 ); - _dirVector.copy( _v2 ); - break; - case 'XYZ': - case 'E': - _dirVector.set( 0, 0, 0 ); - break; - - } - - break; - case 'rotate': - default: - // special case for rotate - _dirVector.set( 0, 0, 0 ); - - } - - if ( _dirVector.length() === 0 ) { - - // If in rotate mode, make the plane parallel to camera - this.quaternion.copy( this.cameraQuaternion ); - - } else { - - _tempMatrix.lookAt( _tempVector.set( 0, 0, 0 ), _dirVector, _alignVector ); - - this.quaternion.setFromRotationMatrix( _tempMatrix ); - - } - - super.updateMatrixWorld( force ); - - } - -} - -export { TransformControls, TransformControlsGizmo, TransformControlsPlane }; diff --git a/public/bluebey-studio/vendor/three/examples/jsm/environments/RoomEnvironment.js b/public/bluebey-studio/vendor/three/examples/jsm/environments/RoomEnvironment.js deleted file mode 100644 index 49867cd..0000000 --- a/public/bluebey-studio/vendor/three/examples/jsm/environments/RoomEnvironment.js +++ /dev/null @@ -1,185 +0,0 @@ -import { - BackSide, - BoxGeometry, - InstancedMesh, - Mesh, - MeshLambertMaterial, - MeshStandardMaterial, - PointLight, - Scene, - Object3D, -} from 'three'; - -/** - * This class represents a scene with a basic room setup that can be used as - * input for {@link PMREMGenerator#fromScene}. The resulting PMREM represents the room's - * lighting and can be used for Image Based Lighting by assigning it to {@link Scene#environment} - * or directly as an environment map to PBR materials. - * - * The implementation is based on the [EnvironmentScene](https://github.com/google/model-viewer/blob/master/packages/model-viewer/src/three-components/EnvironmentScene.ts) - * component from the `model-viewer` project. - * - * ```js - * const environment = new RoomEnvironment(); - * const pmremGenerator = new THREE.PMREMGenerator( renderer ); - * - * const envMap = pmremGenerator.fromScene( environment ).texture; - * scene.environment = envMap; - * ``` - * - * @augments Scene - * @three_import import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js'; - */ -class RoomEnvironment extends Scene { - - constructor() { - - super(); - - this.name = 'RoomEnvironment'; - this.position.y = - 3.5; - - const geometry = new BoxGeometry(); - geometry.deleteAttribute( 'uv' ); - - const roomMaterial = new MeshStandardMaterial( { side: BackSide } ); - const boxMaterial = new MeshStandardMaterial(); - - const mainLight = new PointLight( 0xffffff, 900, 28, 2 ); - mainLight.position.set( 0.418, 16.199, 0.300 ); - this.add( mainLight ); - - const room = new Mesh( geometry, roomMaterial ); - room.position.set( - 0.757, 13.219, 0.717 ); - room.scale.set( 31.713, 28.305, 28.591 ); - this.add( room ); - - const boxes = new InstancedMesh( geometry, boxMaterial, 6 ); - const transform = new Object3D(); - - // box1 - transform.position.set( - 10.906, 2.009, 1.846 ); - transform.rotation.set( 0, - 0.195, 0 ); - transform.scale.set( 2.328, 7.905, 4.651 ); - transform.updateMatrix(); - boxes.setMatrixAt( 0, transform.matrix ); - - // box2 - transform.position.set( - 5.607, - 0.754, - 0.758 ); - transform.rotation.set( 0, 0.994, 0 ); - transform.scale.set( 1.970, 1.534, 3.955 ); - transform.updateMatrix(); - boxes.setMatrixAt( 1, transform.matrix ); - - // box3 - transform.position.set( 6.167, 0.857, 7.803 ); - transform.rotation.set( 0, 0.561, 0 ); - transform.scale.set( 3.927, 6.285, 3.687 ); - transform.updateMatrix(); - boxes.setMatrixAt( 2, transform.matrix ); - - // box4 - transform.position.set( - 2.017, 0.018, 6.124 ); - transform.rotation.set( 0, 0.333, 0 ); - transform.scale.set( 2.002, 4.566, 2.064 ); - transform.updateMatrix(); - boxes.setMatrixAt( 3, transform.matrix ); - - // box5 - transform.position.set( 2.291, - 0.756, - 2.621 ); - transform.rotation.set( 0, - 0.286, 0 ); - transform.scale.set( 1.546, 1.552, 1.496 ); - transform.updateMatrix(); - boxes.setMatrixAt( 4, transform.matrix ); - - // box6 - transform.position.set( - 2.193, - 0.369, - 5.547 ); - transform.rotation.set( 0, 0.516, 0 ); - transform.scale.set( 3.875, 3.487, 2.986 ); - transform.updateMatrix(); - boxes.setMatrixAt( 5, transform.matrix ); - - this.add( boxes ); - - - // -x right - const light1 = new Mesh( geometry, createAreaLightMaterial( 50 ) ); - light1.position.set( - 16.116, 14.37, 8.208 ); - light1.scale.set( 0.1, 2.428, 2.739 ); - this.add( light1 ); - - // -x left - const light2 = new Mesh( geometry, createAreaLightMaterial( 50 ) ); - light2.position.set( - 16.109, 18.021, - 8.207 ); - light2.scale.set( 0.1, 2.425, 2.751 ); - this.add( light2 ); - - // +x - const light3 = new Mesh( geometry, createAreaLightMaterial( 17 ) ); - light3.position.set( 14.904, 12.198, - 1.832 ); - light3.scale.set( 0.15, 4.265, 6.331 ); - this.add( light3 ); - - // +z - const light4 = new Mesh( geometry, createAreaLightMaterial( 43 ) ); - light4.position.set( - 0.462, 8.89, 14.520 ); - light4.scale.set( 4.38, 5.441, 0.088 ); - this.add( light4 ); - - // -z - const light5 = new Mesh( geometry, createAreaLightMaterial( 20 ) ); - light5.position.set( 3.235, 11.486, - 12.541 ); - light5.scale.set( 2.5, 2.0, 0.1 ); - this.add( light5 ); - - // +y - const light6 = new Mesh( geometry, createAreaLightMaterial( 100 ) ); - light6.position.set( 0.0, 20.0, 0.0 ); - light6.scale.set( 1.0, 0.1, 1.0 ); - this.add( light6 ); - - } - - /** - * Frees internal resources. This method should be called - * when the environment is no longer required. - */ - dispose() { - - const resources = new Set(); - - this.traverse( ( object ) => { - - if ( object.isMesh ) { - - resources.add( object.geometry ); - resources.add( object.material ); - - } - - } ); - - for ( const resource of resources ) { - - resource.dispose(); - - } - - } - -} - -function createAreaLightMaterial( intensity ) { - - // create an emissive-only material. see #31348 - const material = new MeshLambertMaterial( { - color: 0x000000, - emissive: 0xffffff, - emissiveIntensity: intensity - } ); - - return material; - -} - -export { RoomEnvironment }; diff --git a/public/bluebey-studio/vendor/three/examples/jsm/exporters/GLTFExporter.js b/public/bluebey-studio/vendor/three/examples/jsm/exporters/GLTFExporter.js deleted file mode 100644 index dc439d7..0000000 --- a/public/bluebey-studio/vendor/three/examples/jsm/exporters/GLTFExporter.js +++ /dev/null @@ -1,3856 +0,0 @@ -import { - BufferAttribute, - ClampToEdgeWrapping, - Color, - DoubleSide, - InterpolateDiscrete, - InterpolateLinear, - NoColorSpace, - LinearFilter, - LinearMipmapLinearFilter, - LinearMipmapNearestFilter, - MathUtils, - Matrix4, - MirroredRepeatWrapping, - NearestFilter, - NearestMipmapLinearFilter, - NearestMipmapNearestFilter, - PropertyBinding, - RGBAFormat, - RepeatWrapping, - Scene, - SRGBColorSpace, - TextureSource, - CompressedTexture, - Vector3, - Quaternion, - REVISION, - ImageUtils -} from 'three'; - -/** - * The KHR_mesh_quantization extension allows these extra attribute component types - * - * @see https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md#extending-mesh-attributes - */ -const KHR_mesh_quantization_ExtraAttrTypes = { - POSITION: [ - 'byte', - 'byte normalized', - 'unsigned byte', - 'unsigned byte normalized', - 'short', - 'short normalized', - 'unsigned short', - 'unsigned short normalized', - ], - NORMAL: [ - 'byte normalized', - 'short normalized', - ], - TANGENT: [ - 'byte normalized', - 'short normalized', - ], - TEXCOORD: [ - 'byte', - 'byte normalized', - 'unsigned byte', - 'short', - 'short normalized', - 'unsigned short', - ], -}; - -/** - * An exporter for `glTF` 2.0. - * - * glTF (GL Transmission Format) is an [open format specification](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0) - * for efficient delivery and loading of 3D content. Assets may be provided either in JSON (.gltf) - * or binary (.glb) format. External files store textures (.jpg, .png) and additional binary - * data (.bin). A glTF asset may deliver one or more scenes, including meshes, materials, - * textures, skins, skeletons, morph targets, animations, lights, and/or cameras. - * - * GLTFExporter supports the [glTF 2.0 extensions](https://github.com/KhronosGroup/glTF/tree/master/extensions/): - * - * - KHR_lights_punctual - * - KHR_materials_clearcoat - * - KHR_materials_dispersion - * - KHR_materials_emissive_strength - * - KHR_materials_ior - * - KHR_materials_iridescence - * - KHR_materials_specular - * - KHR_materials_sheen - * - KHR_materials_transmission - * - KHR_materials_unlit - * - KHR_materials_volume - * - KHR_mesh_quantization - * - KHR_texture_transform - * - EXT_materials_bump - * - EXT_mesh_gpu_instancing - * - EXT_texture_webp - * - * The following glTF 2.0 extension is supported by an external user plugin: - * - * - [KHR_materials_variants](https://github.com/takahirox/three-gltf-extensions) - * - * ```js - * const exporter = new GLTFExporter(); - * const data = await exporter.parseAsync( scene, options ); - * ``` - * - * @three_import import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js'; - */ -class GLTFExporter { - - /** - * Constructs a new glTF exporter. - */ - constructor() { - - /** - * A reference to a texture utils module. - * - * @type {?(WebGLTextureUtils|WebGPUTextureUtils)} - * @default null - */ - this.textureUtils = null; - - this.pluginCallbacks = []; - - this.register( function ( writer ) { - - return new GLTFLightExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsUnlitExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsTransmissionExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsVolumeExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsIorExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsSpecularExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsClearcoatExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsDispersionExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsIridescenceExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsSheenExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsAnisotropyExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsEmissiveStrengthExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsBumpExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMeshGpuInstancing( writer ); - - } ); - - } - - /** - * Registers a plugin callback. This API is internally used to implement the various - * glTF extensions but can also used by third-party code to add additional logic - * to the exporter. - * - * @param {function(writer:GLTFWriter)} callback - The callback function to register. - * @return {GLTFExporter} A reference to this exporter. - */ - register( callback ) { - - if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { - - this.pluginCallbacks.push( callback ); - - } - - return this; - - } - - /** - * Unregisters a plugin callback. - * - * @param {Function} callback - The callback function to unregister. - * @return {GLTFExporter} A reference to this exporter. - */ - unregister( callback ) { - - if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { - - this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); - - } - - return this; - - } - - /** - * Sets the texture utils for this exporter. Only relevant when compressed textures have to be exported. - * - * Depending on whether you use {@link WebGLRenderer} or {@link WebGPURenderer}, you must inject the - * corresponding texture utils {@link module:WebGLTextureUtils} or {@link module:WebGPUTextureUtils}. - * - * @param {WebGLTextureUtils|WebGPUTextureUtils} utils - The texture utils. - * @return {GLTFExporter} A reference to this exporter. - */ - setTextureUtils( utils ) { - - this.textureUtils = utils; - - return this; - - } - - /** - * Parses the given scenes and generates the glTF output. - * - * @param {Scene|Array<Scene>} input - A scene or an array of scenes. - * @param {GLTFExporter~OnDone} onDone - A callback function that is executed when the export has finished. - * @param {GLTFExporter~OnError} onError - A callback function that is executed when an error happens. - * @param {GLTFExporter~Options} options - options - */ - parse( input, onDone, onError, options ) { - - const writer = new GLTFWriter(); - const plugins = []; - - for ( let i = 0, il = this.pluginCallbacks.length; i < il; i ++ ) { - - plugins.push( this.pluginCallbacks[ i ]( writer ) ); - - } - - writer.setPlugins( plugins ); - writer.setTextureUtils( this.textureUtils ); - writer.writeAsync( input, onDone, options ).catch( onError ); - - } - - /** - * Async version of {@link GLTFExporter#parse}. - * - * @param {Scene|Array<Scene>} input - A scene or an array of scenes. - * @param {GLTFExporter~Options} options - options. - * @return {Promise<ArrayBuffer|string>} A Promise that resolved with the exported glTF data. - */ - parseAsync( input, options ) { - - const scope = this; - - return new Promise( function ( resolve, reject ) { - - scope.parse( input, resolve, reject, options ); - - } ); - - } - -} - -//------------------------------------------------------------------------------ -// Constants -//------------------------------------------------------------------------------ - -const WEBGL_CONSTANTS = { - POINTS: 0x0000, - LINES: 0x0001, - LINE_LOOP: 0x0002, - LINE_STRIP: 0x0003, - TRIANGLES: 0x0004, - TRIANGLE_STRIP: 0x0005, - TRIANGLE_FAN: 0x0006, - - BYTE: 0x1400, - UNSIGNED_BYTE: 0x1401, - SHORT: 0x1402, - UNSIGNED_SHORT: 0x1403, - INT: 0x1404, - UNSIGNED_INT: 0x1405, - FLOAT: 0x1406, - - ARRAY_BUFFER: 0x8892, - ELEMENT_ARRAY_BUFFER: 0x8893, - - NEAREST: 0x2600, - LINEAR: 0x2601, - NEAREST_MIPMAP_NEAREST: 0x2700, - LINEAR_MIPMAP_NEAREST: 0x2701, - NEAREST_MIPMAP_LINEAR: 0x2702, - LINEAR_MIPMAP_LINEAR: 0x2703, - - CLAMP_TO_EDGE: 33071, - MIRRORED_REPEAT: 33648, - REPEAT: 10497 -}; - -const KHR_MESH_QUANTIZATION = 'KHR_mesh_quantization'; - -const THREE_TO_WEBGL = {}; - -THREE_TO_WEBGL[ NearestFilter ] = WEBGL_CONSTANTS.NEAREST; -THREE_TO_WEBGL[ NearestMipmapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST; -THREE_TO_WEBGL[ NearestMipmapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR; -THREE_TO_WEBGL[ LinearFilter ] = WEBGL_CONSTANTS.LINEAR; -THREE_TO_WEBGL[ LinearMipmapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST; -THREE_TO_WEBGL[ LinearMipmapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR; - -THREE_TO_WEBGL[ ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE; -THREE_TO_WEBGL[ RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT; -THREE_TO_WEBGL[ MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT; - -const PATH_PROPERTIES = { - scale: 'scale', - position: 'translation', - quaternion: 'rotation', - morphTargetInfluences: 'weights' -}; - -const DEFAULT_SPECULAR_COLOR = new Color(); - -// GLB constants -// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification - -const GLB_HEADER_BYTES = 12; -const GLB_HEADER_MAGIC = 0x46546C67; -const GLB_VERSION = 2; - -const GLB_CHUNK_PREFIX_BYTES = 8; -const GLB_CHUNK_TYPE_JSON = 0x4E4F534A; -const GLB_CHUNK_TYPE_BIN = 0x004E4942; - -//------------------------------------------------------------------------------ -// Utility functions -//------------------------------------------------------------------------------ - -/** - * Compare two arrays - * - * @private - * @param {Array} array1 Array 1 to compare - * @param {Array} array2 Array 2 to compare - * @return {boolean} Returns true if both arrays are equal - */ -function equalArray( array1, array2 ) { - - return ( array1.length === array2.length ) && array1.every( function ( element, index ) { - - return element === array2[ index ]; - - } ); - -} - -/** - * Converts a string to an ArrayBuffer. - * - * @private - * @param {string} text - * @return {ArrayBuffer} - */ -function stringToArrayBuffer( text ) { - - return new TextEncoder().encode( text ).buffer; - -} - -/** - * Is identity matrix - * - * @private - * @param {Matrix4} matrix - * @returns {boolean} Returns true, if parameter is identity matrix - */ -function isIdentityMatrix( matrix ) { - - return equalArray( matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ); - -} - -/** - * Get the min and max vectors from the given attribute - * - * @private - * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count - * @param {number} start Start index - * @param {number} count Range to cover - * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components) - */ -function getMinMax( attribute, start, count ) { - - const output = { - - min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ), - max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY ) - - }; - - for ( let i = start; i < start + count; i ++ ) { - - for ( let a = 0; a < attribute.itemSize; a ++ ) { - - let value; - - if ( attribute.itemSize > 4 ) { - - // no support for interleaved data for itemSize > 4 - - value = attribute.array[ i * attribute.itemSize + a ]; - - } else { - - if ( a === 0 ) value = attribute.getX( i ); - else if ( a === 1 ) value = attribute.getY( i ); - else if ( a === 2 ) value = attribute.getZ( i ); - else if ( a === 3 ) value = attribute.getW( i ); - - if ( attribute.normalized === true ) { - - value = MathUtils.normalize( value, attribute.array ); - - } - - } - - output.min[ a ] = Math.min( output.min[ a ], value ); - output.max[ a ] = Math.max( output.max[ a ], value ); - - } - - } - - return output; - -} - -/** - * Get the required size + padding for a buffer, rounded to the next 4-byte boundary. - * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment - * - * @private - * @param {number} bufferSize The size the original buffer. Should be an integer. - * @returns {number} new buffer size with required padding as an integer. - * - */ -function getPaddedBufferSize( bufferSize ) { - - return Math.ceil( bufferSize / 4 ) * 4; - -} - -/** - * Returns a buffer aligned to 4-byte boundary. - * - * @private - * @param {ArrayBuffer} arrayBuffer Buffer to pad - * @param {number} [paddingByte=0] Should be an integer - * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer - */ -function getPaddedArrayBuffer( arrayBuffer, paddingByte = 0 ) { - - const paddedLength = getPaddedBufferSize( arrayBuffer.byteLength ); - - if ( paddedLength !== arrayBuffer.byteLength ) { - - const array = new Uint8Array( paddedLength ); - array.set( new Uint8Array( arrayBuffer ) ); - - if ( paddingByte !== 0 ) { - - for ( let i = arrayBuffer.byteLength; i < paddedLength; i ++ ) { - - array[ i ] = paddingByte; - - } - - } - - return array.buffer; - - } - - return arrayBuffer; - -} - -function getCanvas() { - - if ( typeof document === 'undefined' && typeof OffscreenCanvas !== 'undefined' ) { - - return new OffscreenCanvas( 1, 1 ); - - } - - return document.createElement( 'canvas' ); - -} - -function getToBlobPromise( canvas, mimeType ) { - - if ( typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas ) { - - let quality; - - // Blink's implementation of convertToBlob seems to default to a quality level of 100% - // Use the Blink default quality levels of toBlob instead so that file sizes are comparable. - if ( mimeType === 'image/jpeg' ) { - - quality = 0.92; - - } else if ( mimeType === 'image/webp' ) { - - quality = 0.8; - - } - - return canvas.convertToBlob( { - - type: mimeType, - quality: quality - - } ); - - } else { - - // HTMLCanvasElement code path - - return new Promise( ( resolve ) => canvas.toBlob( resolve, mimeType ) ); - - } - -} - -/** - * Writer - * - * @private - */ -class GLTFWriter { - - constructor() { - - this.plugins = []; - - this.options = {}; - this.pending = []; - this.buffers = []; - - this.byteOffset = 0; - this.buffers = []; - this.nodeMap = new Map(); - this.skins = []; - - this.extensionsUsed = {}; - this.extensionsRequired = {}; - - this.uids = new Map(); - this.uid = 0; - - this.json = { - asset: { - version: '2.0', - generator: 'THREE.GLTFExporter r' + REVISION - } - }; - - this.cache = { - meshes: new Map(), - attributes: new Map(), - attributesNormalized: new Map(), - materials: new Map(), - textures: new Map(), - images: new Map(), - normalMaps: new Map() - }; - - this.textureUtils = null; - - } - - setPlugins( plugins ) { - - this.plugins = plugins; - - } - - setTextureUtils( utils ) { - - this.textureUtils = utils; - - } - - /** - * Parse scenes and generate GLTF output - * - * @param {Scene|Array<Scene>} input Scene or Array of THREE.Scenes - * @param {Function} onDone Callback on completed - * @param {Object} options options - */ - async writeAsync( input, onDone, options = {} ) { - - this.options = Object.assign( { - // default options - binary: false, - trs: false, - onlyVisible: true, - maxTextureSize: Infinity, - animations: [], - includeCustomExtensions: false, - copyright: null - }, options ); - - if ( this.options.animations.length > 0 ) { - - // Only TRS properties, and not matrices, may be targeted by animation. - this.options.trs = true; - - } - - await this.processInputAsync( input ); - - await Promise.all( this.pending ); - - const writer = this; - const buffers = writer.buffers; - const json = writer.json; - options = writer.options; - - const extensionsUsed = writer.extensionsUsed; - const extensionsRequired = writer.extensionsRequired; - - // Merge buffers. - const blob = new Blob( buffers, { type: 'application/octet-stream' } ); - - // Declare extensions. - const extensionsUsedList = Object.keys( extensionsUsed ); - const extensionsRequiredList = Object.keys( extensionsRequired ); - - if ( extensionsUsedList.length > 0 ) json.extensionsUsed = extensionsUsedList; - if ( extensionsRequiredList.length > 0 ) json.extensionsRequired = extensionsRequiredList; - - // Update bytelength of the single buffer. - if ( json.buffers && json.buffers.length > 0 ) json.buffers[ 0 ].byteLength = blob.size; - - if ( options.copyright ) json.asset.copyright = options.copyright; - - if ( options.binary === true ) { - - // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification - - const reader = new FileReader(); - reader.readAsArrayBuffer( blob ); - reader.onloadend = function () { - - // Binary chunk. - const binaryChunk = getPaddedArrayBuffer( reader.result ); - const binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); - binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true ); - binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true ); - - // JSON chunk. - const jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( json ) ), 0x20 ); - const jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); - jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true ); - jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true ); - - // GLB header. - const header = new ArrayBuffer( GLB_HEADER_BYTES ); - const headerView = new DataView( header ); - headerView.setUint32( 0, GLB_HEADER_MAGIC, true ); - headerView.setUint32( 4, GLB_VERSION, true ); - const totalByteLength = GLB_HEADER_BYTES - + jsonChunkPrefix.byteLength + jsonChunk.byteLength - + binaryChunkPrefix.byteLength + binaryChunk.byteLength; - headerView.setUint32( 8, totalByteLength, true ); - - const glbBlob = new Blob( [ - header, - jsonChunkPrefix, - jsonChunk, - binaryChunkPrefix, - binaryChunk - ], { type: 'application/octet-stream' } ); - - const glbReader = new FileReader(); - glbReader.readAsArrayBuffer( glbBlob ); - glbReader.onloadend = function () { - - onDone( glbReader.result ); - - }; - - }; - - } else { - - if ( json.buffers && json.buffers.length > 0 ) { - - const reader = new FileReader(); - reader.readAsDataURL( blob ); - reader.onloadend = function () { - - const base64data = reader.result; - json.buffers[ 0 ].uri = base64data; - onDone( json ); - - }; - - } else { - - onDone( json ); - - } - - } - - - } - - /** - * Serializes a userData. - * - * @param {THREE.Object3D|THREE.Material|THREE.BufferGeometry|THREE.AnimationClip} object - * @param {Object} objectDef - */ - serializeUserData( object, objectDef ) { - - if ( Object.keys( object.userData ).length === 0 ) return; - - const options = this.options; - const extensionsUsed = this.extensionsUsed; - - try { - - const json = JSON.parse( JSON.stringify( object.userData ) ); - - if ( options.includeCustomExtensions && json.gltfExtensions ) { - - if ( objectDef.extensions === undefined ) objectDef.extensions = {}; - - for ( const extensionName in json.gltfExtensions ) { - - objectDef.extensions[ extensionName ] = json.gltfExtensions[ extensionName ]; - extensionsUsed[ extensionName ] = true; - - } - - delete json.gltfExtensions; - - } - - if ( Object.keys( json ).length > 0 ) objectDef.extras = json; - - } catch ( error ) { - - console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' + - 'won\'t be serialized because of JSON.stringify error - ' + error.message ); - - } - - } - - /** - * Returns ids for buffer attributes. - * - * @param {Object} attribute - * @param {boolean} [isRelativeCopy=false] - * @return {number} An integer - */ - getUID( attribute, isRelativeCopy = false ) { - - if ( this.uids.has( attribute ) === false ) { - - const uids = new Map(); - - uids.set( true, this.uid ++ ); - uids.set( false, this.uid ++ ); - - this.uids.set( attribute, uids ); - - } - - const uids = this.uids.get( attribute ); - - return uids.get( isRelativeCopy ); - - } - - /** - * Checks if normal attribute values are normalized. - * - * @param {BufferAttribute} normal - * @returns {boolean} - */ - isNormalizedNormalAttribute( normal ) { - - const cache = this.cache; - - if ( cache.attributesNormalized.has( normal ) ) return false; - - const v = new Vector3(); - - for ( let i = 0, il = normal.count; i < il; i ++ ) { - - // 0.0005 is from glTF-validator - if ( Math.abs( v.fromBufferAttribute( normal, i ).length() - 1.0 ) > 0.0005 ) return false; - - } - - return true; - - } - - /** - * Creates normalized normal buffer attribute. - * - * @param {BufferAttribute} normal - * @returns {BufferAttribute} - * - */ - createNormalizedNormalAttribute( normal ) { - - const cache = this.cache; - - if ( cache.attributesNormalized.has( normal ) ) return cache.attributesNormalized.get( normal ); - - const attribute = normal.clone(); - const v = new Vector3(); - - for ( let i = 0, il = attribute.count; i < il; i ++ ) { - - v.fromBufferAttribute( attribute, i ); - - if ( v.x === 0 && v.y === 0 && v.z === 0 ) { - - // if values can't be normalized set (1, 0, 0) - v.setX( 1.0 ); - - } else { - - v.normalize(); - - } - - attribute.setXYZ( i, v.x, v.y, v.z ); - - } - - cache.attributesNormalized.set( normal, attribute ); - - return attribute; - - } - - /** - * Applies a texture transform, if present, to the map definition. Requires - * the KHR_texture_transform extension. - * - * @param {Object} mapDef - * @param {THREE.Texture} texture - */ - applyTextureTransform( mapDef, texture ) { - - let didTransform = false; - const transformDef = {}; - - if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) { - - transformDef.offset = texture.offset.toArray(); - didTransform = true; - - } - - if ( texture.rotation !== 0 ) { - - transformDef.rotation = texture.rotation; - didTransform = true; - - } - - if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) { - - transformDef.scale = texture.repeat.toArray(); - didTransform = true; - - } - - if ( didTransform ) { - - mapDef.extensions = mapDef.extensions || {}; - mapDef.extensions[ 'KHR_texture_transform' ] = transformDef; - this.extensionsUsed[ 'KHR_texture_transform' ] = true; - - } - - } - - async buildMetalRoughTextureAsync( metalnessMap, roughnessMap ) { - - if ( metalnessMap === roughnessMap ) return metalnessMap; - - function getEncodingConversion( map ) { - - if ( map.colorSpace === SRGBColorSpace ) { - - return function SRGBToLinear( c ) { - - return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); - - }; - - } - - return function LinearToLinear( c ) { - - return c; - - }; - - } - - if ( metalnessMap instanceof CompressedTexture ) { - - metalnessMap = await this.decompressTextureAsync( metalnessMap ); - - } - - if ( roughnessMap instanceof CompressedTexture ) { - - roughnessMap = await this.decompressTextureAsync( roughnessMap ); - - } - - const metalness = metalnessMap ? metalnessMap.image : null; - const roughness = roughnessMap ? roughnessMap.image : null; - - const width = Math.max( metalness ? metalness.width : 0, roughness ? roughness.width : 0 ); - const height = Math.max( metalness ? metalness.height : 0, roughness ? roughness.height : 0 ); - - const canvas = getCanvas(); - canvas.width = width; - canvas.height = height; - - const context = canvas.getContext( '2d', { - willReadFrequently: true, - } ); - context.fillStyle = '#00ffff'; - context.fillRect( 0, 0, width, height ); - - const composite = context.getImageData( 0, 0, width, height ); - - if ( metalness ) { - - context.drawImage( metalness, 0, 0, width, height ); - - const convert = getEncodingConversion( metalnessMap ); - const data = context.getImageData( 0, 0, width, height ).data; - - for ( let i = 2; i < data.length; i += 4 ) { - - composite.data[ i ] = convert( data[ i ] / 256 ) * 256; - - } - - } - - if ( roughness ) { - - context.drawImage( roughness, 0, 0, width, height ); - - const convert = getEncodingConversion( roughnessMap ); - const data = context.getImageData( 0, 0, width, height ).data; - - for ( let i = 1; i < data.length; i += 4 ) { - - composite.data[ i ] = convert( data[ i ] / 256 ) * 256; - - } - - } - - context.putImageData( composite, 0, 0 ); - - // - - const reference = metalnessMap || roughnessMap; - - const texture = reference.clone(); - - texture.source = new TextureSource( canvas ); - texture.colorSpace = NoColorSpace; - texture.channel = ( metalnessMap || roughnessMap ).channel; - - if ( metalnessMap && roughnessMap && metalnessMap.channel !== roughnessMap.channel ) { - - console.warn( 'THREE.GLTFExporter: UV channels for metalnessMap and roughnessMap textures must match.' ); - - } - - console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' ); - - return texture; - - } - - - /** - * Builds a copy of the given normal map with the red and/or green channels - * inverted (`color = 255 - color`). This is used to bake the sign of - * `material.normalScale` and the tangent-space convention into the texture, - * since glTF only supports OpenGL-style normal maps with a univariate, - * positive scale. - * - * @param {THREE.Texture} normalMap The source normal map. - * @param {boolean} flipX Whether to invert the red channel (normal X). - * @param {boolean} flipY Whether to invert the green channel (normal Y). - * @return {Promise<THREE.Texture>} The derived normal map texture. - */ - async buildNormalMapTextureAsync( normalMap, flipX, flipY ) { - - if ( normalMap instanceof CompressedTexture ) { - - normalMap = await this.decompressTextureAsync( normalMap ); - - } - - const image = normalMap.image; - - const canvas = getCanvas(); - canvas.width = image.width; - canvas.height = image.height; - - const context = canvas.getContext( '2d', { - willReadFrequently: true, - } ); - - context.drawImage( image, 0, 0, canvas.width, canvas.height ); - - const imageData = context.getImageData( 0, 0, canvas.width, canvas.height ); - const data = imageData.data; - - for ( let i = 0; i < data.length; i += 4 ) { - - if ( flipX ) data[ i + 0 ] = 255 - data[ i + 0 ]; - if ( flipY ) data[ i + 1 ] = 255 - data[ i + 1 ]; - - } - - context.putImageData( imageData, 0, 0 ); - - const texture = normalMap.clone(); - texture.source = new TextureSource( canvas ); - - return texture; - - } - - async decompressTextureAsync( texture, maxTextureSize = Infinity ) { - - if ( this.textureUtils === null ) { - - throw new Error( 'THREE.GLTFExporter: setTextureUtils() must be called to process compressed textures.' ); - - } - - return await this.textureUtils.decompress( texture, maxTextureSize ); - - } - - /** - * Process a buffer to append to the default one. - * @param {ArrayBuffer} buffer - * @return {0} - */ - processBuffer( buffer ) { - - const json = this.json; - const buffers = this.buffers; - - if ( ! json.buffers ) json.buffers = [ { byteLength: 0 } ]; - - // All buffers are merged before export. - buffers.push( buffer ); - - return 0; - - } - - /** - * Process and generate a BufferView - * @param {BufferAttribute} attribute - * @param {number} componentType - * @param {number} start - * @param {number} count - * @param {number} [target] Target usage of the BufferView - * @return {Object} - */ - processBufferView( attribute, componentType, start, count, target ) { - - const json = this.json; - - if ( ! json.bufferViews ) json.bufferViews = []; - - // Create a new dataview and dump the attribute's array into it - - let componentSize; - - switch ( componentType ) { - - case WEBGL_CONSTANTS.BYTE: - case WEBGL_CONSTANTS.UNSIGNED_BYTE: - - componentSize = 1; - - break; - - case WEBGL_CONSTANTS.SHORT: - case WEBGL_CONSTANTS.UNSIGNED_SHORT: - - componentSize = 2; - - break; - - default: - - componentSize = 4; - - } - - let byteStride = attribute.itemSize * componentSize; - - if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { - - // Each element of a vertex attribute MUST be aligned to 4-byte boundaries - // inside a bufferView - byteStride = Math.ceil( byteStride / 4 ) * 4; - - } - - const byteLength = getPaddedBufferSize( count * byteStride ); - const dataView = new DataView( new ArrayBuffer( byteLength ) ); - let offset = 0; - - for ( let i = start; i < start + count; i ++ ) { - - for ( let a = 0; a < attribute.itemSize; a ++ ) { - - let value; - - if ( attribute.itemSize > 4 ) { - - // no support for interleaved data for itemSize > 4 - - value = attribute.array[ i * attribute.itemSize + a ]; - - } else { - - if ( a === 0 ) value = attribute.getX( i ); - else if ( a === 1 ) value = attribute.getY( i ); - else if ( a === 2 ) value = attribute.getZ( i ); - else if ( a === 3 ) value = attribute.getW( i ); - - if ( attribute.normalized === true ) { - - value = MathUtils.normalize( value, attribute.array ); - - } - - } - - if ( componentType === WEBGL_CONSTANTS.FLOAT ) { - - dataView.setFloat32( offset, value, true ); - - } else if ( componentType === WEBGL_CONSTANTS.INT ) { - - dataView.setInt32( offset, value, true ); - - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) { - - dataView.setUint32( offset, value, true ); - - } else if ( componentType === WEBGL_CONSTANTS.SHORT ) { - - dataView.setInt16( offset, value, true ); - - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { - - dataView.setUint16( offset, value, true ); - - } else if ( componentType === WEBGL_CONSTANTS.BYTE ) { - - dataView.setInt8( offset, value ); - - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { - - dataView.setUint8( offset, value ); - - } - - offset += componentSize; - - } - - if ( ( offset % byteStride ) !== 0 ) { - - offset += byteStride - ( offset % byteStride ); - - } - - } - - const bufferViewDef = { - - buffer: this.processBuffer( dataView.buffer ), - byteOffset: this.byteOffset, - byteLength: byteLength - - }; - - if ( target !== undefined ) bufferViewDef.target = target; - - if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { - - // Only define byteStride for vertex attributes. - bufferViewDef.byteStride = byteStride; - - } - - this.byteOffset += byteLength; - - json.bufferViews.push( bufferViewDef ); - - // @TODO Merge bufferViews where possible. - const output = { - - id: json.bufferViews.length - 1, - byteLength: 0 - - }; - - return output; - - } - - /** - * Process and generate a BufferView from an image Blob. - * @param {Blob} blob - * @return {Promise<number>} An integer - */ - processBufferViewImage( blob ) { - - const writer = this; - const json = writer.json; - - if ( ! json.bufferViews ) json.bufferViews = []; - - return new Promise( function ( resolve ) { - - const reader = new FileReader(); - reader.readAsArrayBuffer( blob ); - reader.onloadend = function () { - - const buffer = getPaddedArrayBuffer( reader.result ); - - const bufferViewDef = { - buffer: writer.processBuffer( buffer ), - byteOffset: writer.byteOffset, - byteLength: buffer.byteLength - }; - - writer.byteOffset += buffer.byteLength; - resolve( json.bufferViews.push( bufferViewDef ) - 1 ); - - }; - - } ); - - } - - /** - * Process attribute to generate an accessor - * @param {BufferAttribute} attribute Attribute to process - * @param {?BufferGeometry} [geometry] Geometry used for truncated draw range - * @param {number} [start=0] - * @param {number} [count=Infinity] - * @return {?number} Index of the processed accessor on the "accessors" array - */ - processAccessor( attribute, geometry, start, count ) { - - const json = this.json; - - const types = { - - 1: 'SCALAR', - 2: 'VEC2', - 3: 'VEC3', - 4: 'VEC4', - 9: 'MAT3', - 16: 'MAT4' - - }; - - let componentType; - - // Detect the component type of the attribute array - if ( attribute.array.constructor === Float32Array ) { - - componentType = WEBGL_CONSTANTS.FLOAT; - - } else if ( attribute.array.constructor === Int32Array ) { - - componentType = WEBGL_CONSTANTS.INT; - - } else if ( attribute.array.constructor === Uint32Array ) { - - componentType = WEBGL_CONSTANTS.UNSIGNED_INT; - - } else if ( attribute.array.constructor === Int16Array ) { - - componentType = WEBGL_CONSTANTS.SHORT; - - } else if ( attribute.array.constructor === Uint16Array ) { - - componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT; - - } else if ( attribute.array.constructor === Int8Array ) { - - componentType = WEBGL_CONSTANTS.BYTE; - - } else if ( attribute.array.constructor === Uint8Array ) { - - componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE; - - } else { - - throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type: ' + attribute.array.constructor.name ); - - } - - if ( start === undefined ) start = 0; - if ( count === undefined || count === Infinity ) count = attribute.count; - - // Skip creating an accessor if the attribute doesn't have data to export - if ( count === 0 ) return null; - - const minMax = getMinMax( attribute, start, count ); - let bufferViewTarget; - - // If geometry isn't provided, don't infer the target usage of the bufferView. For - // animation samplers, target must not be set. - if ( geometry !== undefined ) { - - bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER; - - } - - const bufferView = this.processBufferView( attribute, componentType, start, count, bufferViewTarget ); - - const accessorDef = { - - bufferView: bufferView.id, - byteOffset: bufferView.byteOffset, - componentType: componentType, - count: count, - max: minMax.max, - min: minMax.min, - type: types[ attribute.itemSize ] - - }; - - if ( attribute.normalized === true ) accessorDef.normalized = true; - if ( ! json.accessors ) json.accessors = []; - - return json.accessors.push( accessorDef ) - 1; - - } - - /** - * Process image - * @param {Image} image to process - * @param {number} format Identifier of the format (RGBAFormat) - * @param {boolean} flipY before writing out the image - * @param {string} mimeType export format - * @return {number} Index of the processed texture in the "images" array - */ - processImage( image, format, flipY, mimeType = 'image/png' ) { - - if ( image !== null ) { - - const writer = this; - const cache = writer.cache; - const json = writer.json; - const options = writer.options; - const pending = writer.pending; - - if ( ! cache.images.has( image ) ) cache.images.set( image, {} ); - - const cachedImages = cache.images.get( image ); - - const key = mimeType + ':flipY/' + flipY.toString(); - - if ( cachedImages[ key ] !== undefined ) return cachedImages[ key ]; - - if ( ! json.images ) json.images = []; - - const imageDef = { mimeType: mimeType }; - - const canvas = getCanvas(); - - canvas.width = Math.min( image.width, options.maxTextureSize ); - canvas.height = Math.min( image.height, options.maxTextureSize ); - - const ctx = canvas.getContext( '2d', { - willReadFrequently: true, - } ); - - if ( flipY === true ) { - - ctx.translate( 0, canvas.height ); - ctx.scale( 1, - 1 ); - - } - - if ( image.data !== undefined ) { // THREE.DataTexture - - if ( format !== RGBAFormat ) { - - console.error( 'GLTFExporter: Only RGBAFormat is supported.', format ); - - } - - if ( image.width > options.maxTextureSize || image.height > options.maxTextureSize ) { - - console.warn( 'GLTFExporter: Image size is bigger than maxTextureSize', image ); - - } - - const data = new Uint8ClampedArray( image.height * image.width * 4 ); - - for ( let i = 0; i < data.length; i += 4 ) { - - data[ i + 0 ] = image.data[ i + 0 ]; - data[ i + 1 ] = image.data[ i + 1 ]; - data[ i + 2 ] = image.data[ i + 2 ]; - data[ i + 3 ] = image.data[ i + 3 ]; - - } - - ctx.putImageData( new ImageData( data, image.width, image.height ), 0, 0 ); - - } else { - - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) || - ( typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas ) ) { - - ctx.drawImage( image, 0, 0, canvas.width, canvas.height ); - - } else { - - throw new Error( 'THREE.GLTFExporter: Invalid image type. Use HTMLImageElement, HTMLCanvasElement, ImageBitmap or OffscreenCanvas.' ); - - } - - } - - if ( options.binary === true ) { - - pending.push( - - getToBlobPromise( canvas, mimeType ) - .then( blob => writer.processBufferViewImage( blob ) ) - .then( bufferViewIndex => { - - imageDef.bufferView = bufferViewIndex; - - } ) - - ); - - } else { - - imageDef.uri = ImageUtils.getDataURL( canvas, mimeType ); - - } - - const index = json.images.push( imageDef ) - 1; - cachedImages[ key ] = index; - return index; - - } else { - - throw new Error( 'THREE.GLTFExporter: No valid image data found. Unable to process texture.' ); - - } - - } - - /** - * Process sampler - * @param {Texture} map Texture to process - * @return {number} Index of the processed texture in the "samplers" array - */ - processSampler( map ) { - - const json = this.json; - - if ( ! json.samplers ) json.samplers = []; - - const samplerDef = { - magFilter: THREE_TO_WEBGL[ map.magFilter ], - minFilter: THREE_TO_WEBGL[ map.minFilter ], - wrapS: THREE_TO_WEBGL[ map.wrapS ], - wrapT: THREE_TO_WEBGL[ map.wrapT ] - }; - - return json.samplers.push( samplerDef ) - 1; - - } - - /** - * Process texture - * @param {Texture} map Map to process - * @return {Promise<number>} Index of the processed texture in the "textures" array - */ - async processTextureAsync( map ) { - - const writer = this; - const options = writer.options; - const cache = this.cache; - const json = this.json; - - if ( cache.textures.has( map ) ) return cache.textures.get( map ); - - if ( ! json.textures ) json.textures = []; - - // make non-readable textures (e.g. CompressedTexture) readable by blitting them into a new texture - if ( map instanceof CompressedTexture ) { - - map = await this.decompressTextureAsync( map, options.maxTextureSize ); - - } - - const mimeType = map.userData.mimeType; - - const imageIndex = this.processImage( map.image, map.format, map.flipY, mimeType ); - - const textureDef = { - sampler: this.processSampler( map ) - }; - - if ( mimeType === 'image/webp' ) { - - textureDef.extensions = textureDef.extensions || {}; - textureDef.extensions[ 'EXT_texture_webp' ] = { - source: imageIndex - }; - - this.extensionsUsed[ 'EXT_texture_webp' ] = true; - this.extensionsRequired[ 'EXT_texture_webp' ] = true; - - } else { - - textureDef.source = imageIndex; - - } - - if ( map.name ) textureDef.name = map.name; - - await this._invokeAllAsync( async function ( ext ) { - - ext.writeTexture && await ext.writeTexture( map, textureDef ); - - } ); - - const index = json.textures.push( textureDef ) - 1; - cache.textures.set( map, index ); - return index; - - } - - /** - * Process material - * @param {THREE.Material} material Material to process - * @param {THREE.BufferGeometry} [geometry] Geometry the material is used with. - * @return {Promise<?number>} Index of the processed material in the "materials" array - */ - async processMaterialAsync( material, geometry ) { - - const cache = this.cache; - const json = this.json; - - // Whether the geometry provides explicit tangents. The exported normal map depends on - // this, so it is part of the material cache key. - const hasTangent = geometry !== undefined && geometry.hasAttribute( 'tangent' ); - const cacheKey = material.normalMap ? material.uuid + ':' + hasTangent : material.uuid; - - if ( cache.materials.has( cacheKey ) ) return cache.materials.get( cacheKey ); - - if ( material.isShaderMaterial ) { - - console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' ); - return null; - - } - - if ( ! json.materials ) json.materials = []; - - // @QUESTION Should we avoid including any attribute that has the default value? - const materialDef = { pbrMetallicRoughness: {} }; - - if ( material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true ) { - - console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' ); - - } - - // pbrMetallicRoughness.baseColorFactor - const color = material.color.toArray().concat( [ material.opacity ] ); - - if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) { - - materialDef.pbrMetallicRoughness.baseColorFactor = color; - - } - - if ( material.isMeshStandardMaterial ) { - - materialDef.pbrMetallicRoughness.metallicFactor = material.metalness; - materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness; - - } else { - - materialDef.pbrMetallicRoughness.metallicFactor = 0; - materialDef.pbrMetallicRoughness.roughnessFactor = 1; - - } - - // pbrMetallicRoughness.metallicRoughnessTexture - if ( material.metalnessMap || material.roughnessMap ) { - - const metalRoughTexture = await this.buildMetalRoughTextureAsync( material.metalnessMap, material.roughnessMap ); - - const metalRoughMapDef = { - index: await this.processTextureAsync( metalRoughTexture ), - texCoord: metalRoughTexture.channel - }; - this.applyTextureTransform( metalRoughMapDef, metalRoughTexture ); - materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef; - - } - - // pbrMetallicRoughness.baseColorTexture - if ( material.map ) { - - const baseColorMapDef = { - index: await this.processTextureAsync( material.map ), - texCoord: material.map.channel - }; - this.applyTextureTransform( baseColorMapDef, material.map ); - materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef; - - } - - if ( material.emissive ) { - - const emissive = material.emissive; - const maxEmissiveComponent = Math.max( emissive.r, emissive.g, emissive.b ); - - if ( maxEmissiveComponent > 0 ) { - - materialDef.emissiveFactor = material.emissive.toArray(); - - } - - // emissiveTexture - if ( material.emissiveMap ) { - - const emissiveMapDef = { - index: await this.processTextureAsync( material.emissiveMap ), - texCoord: material.emissiveMap.channel - }; - this.applyTextureTransform( emissiveMapDef, material.emissiveMap ); - materialDef.emissiveTexture = emissiveMapDef; - - } - - } - - // normalTexture - if ( material.normalMap ) { - - const normalScale = material.normalScale; - - // glTF only supports OpenGL-style normal maps with a univariate, positive scale. - // A negative `normalScale` component is baked into the texture by inverting the - // corresponding channel. Meshes without explicit tangents use the opposite - // green-channel convention, so the green channel is inverted in that case too. - // - // The no-tangent green flip is the counterpart of GLTFLoader, which negates - // `normalScale.y` on import for the same case. - const flipX = normalScale.x < 0; - const flipY = hasTangent ? normalScale.y < 0 : normalScale.y > 0; - - let normalMap = material.normalMap; - - if ( flipX || flipY ) { - - if ( cache.normalMaps.has( material.normalMap ) === false ) cache.normalMaps.set( material.normalMap, {} ); - - const cachedVariants = cache.normalMaps.get( material.normalMap ); - const cacheKey = `${flipX}:${flipY}`; - - if ( cachedVariants[ cacheKey ] === undefined ) { - - cachedVariants[ cacheKey ] = await this.buildNormalMapTextureAsync( material.normalMap, flipX, flipY ); - - } - - normalMap = cachedVariants[ cacheKey ]; - - } - - const normalMapDef = { - index: await this.processTextureAsync( normalMap ), - texCoord: material.normalMap.channel - }; - - if ( Math.abs( normalScale.x ) !== 1 ) { - - // glTF normal scale is univariate. The magnitude of `x` is used; the sign of - // both components has already been baked into the texture above. - normalMapDef.scale = Math.abs( normalScale.x ); - - } - - this.applyTextureTransform( normalMapDef, material.normalMap ); - materialDef.normalTexture = normalMapDef; - - } - - // occlusionTexture - if ( material.aoMap ) { - - const occlusionMapDef = { - index: await this.processTextureAsync( material.aoMap ), - texCoord: material.aoMap.channel - }; - - if ( material.aoMapIntensity !== 1.0 ) { - - occlusionMapDef.strength = material.aoMapIntensity; - - } - - this.applyTextureTransform( occlusionMapDef, material.aoMap ); - materialDef.occlusionTexture = occlusionMapDef; - - } - - // alphaMode - if ( material.transparent ) { - - materialDef.alphaMode = 'BLEND'; - - } else { - - if ( material.alphaTest > 0.0 ) { - - materialDef.alphaMode = 'MASK'; - materialDef.alphaCutoff = material.alphaTest; - - } - - } - - // doubleSided - if ( material.side === DoubleSide ) materialDef.doubleSided = true; - if ( material.name !== '' ) materialDef.name = material.name; - - this.serializeUserData( material, materialDef ); - - await this._invokeAllAsync( async function ( ext ) { - - ext.writeMaterialAsync && await ext.writeMaterialAsync( material, materialDef ); - - } ); - - const index = json.materials.push( materialDef ) - 1; - cache.materials.set( cacheKey, index ); - return index; - - } - - /** - * Process mesh - * @param {THREE.Mesh} mesh Mesh to process - * @return {Promise<?number>} Index of the processed mesh in the "meshes" array - */ - async processMeshAsync( mesh ) { - - const cache = this.cache; - const json = this.json; - - const meshCacheKeyParts = [ mesh.geometry.uuid ]; - - if ( Array.isArray( mesh.material ) ) { - - for ( let i = 0, l = mesh.material.length; i < l; i ++ ) { - - meshCacheKeyParts.push( mesh.material[ i ].uuid ); - - } - - } else { - - meshCacheKeyParts.push( mesh.material.uuid ); - - } - - const meshCacheKey = meshCacheKeyParts.join( ':' ); - - if ( cache.meshes.has( meshCacheKey ) ) return cache.meshes.get( meshCacheKey ); - - const geometry = mesh.geometry; - - let mode; - - // Use the correct mode - if ( mesh.isLineSegments ) { - - mode = WEBGL_CONSTANTS.LINES; - - } else if ( mesh.isLineLoop ) { - - mode = WEBGL_CONSTANTS.LINE_LOOP; - - } else if ( mesh.isLine ) { - - mode = WEBGL_CONSTANTS.LINE_STRIP; - - } else if ( mesh.isPoints ) { - - mode = WEBGL_CONSTANTS.POINTS; - - } else { - - mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES; - - } - - const meshDef = {}; - const attributes = {}; - const primitives = []; - const targets = []; - - // Conversion between attributes names in threejs and gltf spec - const nameConversion = { - uv: 'TEXCOORD_0', - uv1: 'TEXCOORD_1', - uv2: 'TEXCOORD_2', - uv3: 'TEXCOORD_3', - color: 'COLOR_0', - skinWeight: 'WEIGHTS_0', - skinIndex: 'JOINTS_0' - }; - - const originalNormal = geometry.getAttribute( 'normal' ); - - if ( originalNormal !== undefined && ! this.isNormalizedNormalAttribute( originalNormal ) ) { - - console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' ); - - geometry.setAttribute( 'normal', this.createNormalizedNormalAttribute( originalNormal ) ); - - } - - // @QUESTION Detect if .vertexColors = true? - // For every attribute create an accessor - let modifiedAttribute = null; - - for ( let attributeName in geometry.attributes ) { - - // Ignore morph target attributes, which are exported later. - if ( attributeName.slice( 0, 5 ) === 'morph' ) continue; - - const attribute = geometry.attributes[ attributeName ]; - attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase(); - - // Prefix all geometry attributes except the ones specifically - // listed in the spec; non-spec attributes are considered custom. - const validVertexAttributes = - /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/; - - if ( ! validVertexAttributes.test( attributeName ) && ! attributeName.startsWith( '_' ) ) attributeName = '_' + attributeName; - - if ( cache.attributes.has( this.getUID( attribute ) ) ) { - - attributes[ attributeName ] = cache.attributes.get( this.getUID( attribute ) ); - continue; - - } - - // Enforce glTF vertex attribute requirements: - // - JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT - // - Only custom attributes may be INT or UNSIGNED_INT - modifiedAttribute = null; - const array = attribute.array; - - if ( attributeName === 'JOINTS_0' && - ! ( array instanceof Uint16Array ) && - ! ( array instanceof Uint8Array ) ) { - - console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); - modifiedAttribute = GLTFExporter.Utils.toTypedBufferAttribute( attribute, Uint16Array ); - - } else if ( ( array instanceof Uint32Array || array instanceof Int32Array ) && ! attributeName.startsWith( '_' ) ) { - - console.warn( `GLTFExporter: Attribute "${ attributeName }" converted to type FLOAT.` ); - modifiedAttribute = GLTFExporter.Utils.toTypedBufferAttribute( attribute, Float32Array ); - - } - - const accessor = this.processAccessor( modifiedAttribute || attribute, geometry ); - - if ( accessor !== null ) { - - if ( ! attributeName.startsWith( '_' ) ) { - - this.detectMeshQuantization( attributeName, attribute ); - - } - - attributes[ attributeName ] = accessor; - cache.attributes.set( this.getUID( attribute ), accessor ); - - } - - } - - if ( originalNormal !== undefined ) geometry.setAttribute( 'normal', originalNormal ); - - // Skip if no exportable attributes found - if ( Object.keys( attributes ).length === 0 ) return null; - - // Morph targets - if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) { - - const weights = []; - const targetNames = []; - const reverseDictionary = {}; - - if ( mesh.morphTargetDictionary !== undefined ) { - - for ( const key in mesh.morphTargetDictionary ) { - - reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key; - - } - - } - - for ( let i = 0; i < mesh.morphTargetInfluences.length; ++ i ) { - - const target = {}; - let warned = false; - - for ( const attributeName in geometry.morphAttributes ) { - - // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. - // Three.js doesn't support TANGENT yet. - - if ( attributeName !== 'position' && attributeName !== 'normal' ) { - - if ( ! warned ) { - - console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' ); - warned = true; - - } - - continue; - - } - - const attribute = geometry.morphAttributes[ attributeName ][ i ]; - const gltfAttributeName = attributeName.toUpperCase(); - - // Three.js morph attribute has absolute values while the one of glTF has relative values. - // - // glTF 2.0 Specification: - // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets - - const baseAttribute = geometry.attributes[ attributeName ]; - - if ( cache.attributes.has( this.getUID( attribute, true ) ) ) { - - target[ gltfAttributeName ] = cache.attributes.get( this.getUID( attribute, true ) ); - continue; - - } - - // Clones attribute not to override - const relativeAttribute = attribute.clone(); - - if ( ! geometry.morphTargetsRelative ) { - - for ( let j = 0, jl = attribute.count; j < jl; j ++ ) { - - for ( let a = 0; a < attribute.itemSize; a ++ ) { - - if ( a === 0 ) relativeAttribute.setX( j, attribute.getX( j ) - baseAttribute.getX( j ) ); - if ( a === 1 ) relativeAttribute.setY( j, attribute.getY( j ) - baseAttribute.getY( j ) ); - if ( a === 2 ) relativeAttribute.setZ( j, attribute.getZ( j ) - baseAttribute.getZ( j ) ); - if ( a === 3 ) relativeAttribute.setW( j, attribute.getW( j ) - baseAttribute.getW( j ) ); - - } - - } - - } - - target[ gltfAttributeName ] = this.processAccessor( relativeAttribute, geometry ); - cache.attributes.set( this.getUID( baseAttribute, true ), target[ gltfAttributeName ] ); - - } - - targets.push( target ); - - weights.push( mesh.morphTargetInfluences[ i ] ); - - if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] ); - - } - - meshDef.weights = weights; - - if ( targetNames.length > 0 ) { - - meshDef.extras = {}; - meshDef.extras.targetNames = targetNames; - - } - - } - - const isMultiMaterial = Array.isArray( mesh.material ); - - if ( isMultiMaterial && geometry.groups.length === 0 ) return null; - - let didForceIndices = false; - - if ( isMultiMaterial && geometry.index === null ) { - - const indices = []; - - for ( let i = 0, il = geometry.attributes.position.count; i < il; i ++ ) { - - indices[ i ] = i; - - } - - geometry.setIndex( indices ); - - didForceIndices = true; - - } - - const materials = isMultiMaterial ? mesh.material : [ mesh.material ]; - const groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ]; - - for ( let i = 0, il = groups.length; i < il; i ++ ) { - - const primitive = { - mode: mode, - attributes: attributes, - }; - - this.serializeUserData( geometry, primitive ); - - if ( targets.length > 0 ) primitive.targets = targets; - - if ( geometry.index !== null ) { - - let cacheKey = this.getUID( geometry.index ); - - if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) { - - cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count; - - } - - if ( cache.attributes.has( cacheKey ) ) { - - primitive.indices = cache.attributes.get( cacheKey ); - - } else { - - primitive.indices = this.processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count ); - cache.attributes.set( cacheKey, primitive.indices ); - - } - - if ( primitive.indices === null ) delete primitive.indices; - - } - - const material = await this.processMaterialAsync( materials[ groups[ i ].materialIndex ], geometry ); - - if ( material !== null ) primitive.material = material; - - primitives.push( primitive ); - - } - - if ( didForceIndices === true ) { - - geometry.setIndex( null ); - - } - - meshDef.primitives = primitives; - - if ( ! json.meshes ) json.meshes = []; - - await this._invokeAllAsync( function ( ext ) { - - ext.writeMesh && ext.writeMesh( mesh, meshDef ); - - } ); - - const index = json.meshes.push( meshDef ) - 1; - cache.meshes.set( meshCacheKey, index ); - return index; - - } - - /** - * If a vertex attribute with a - * [non-standard data type](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview) - * is used, it is checked whether it is a valid data type according to the - * [KHR_mesh_quantization](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md) - * extension. - * In this case the extension is automatically added to the list of used extensions. - * - * @param {string} attributeName - * @param {THREE.BufferAttribute} attribute - */ - detectMeshQuantization( attributeName, attribute ) { - - if ( this.extensionsUsed[ KHR_MESH_QUANTIZATION ] ) return; - - let attrType = undefined; - - switch ( attribute.array.constructor ) { - - case Int8Array: - - attrType = 'byte'; - - break; - - case Uint8Array: - - attrType = 'unsigned byte'; - - break; - - case Int16Array: - - attrType = 'short'; - - break; - - case Uint16Array: - - attrType = 'unsigned short'; - - break; - - default: - - return; - - } - - if ( attribute.normalized ) attrType += ' normalized'; - - const attrNamePrefix = attributeName.split( '_', 1 )[ 0 ]; - - if ( KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ] && KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ].includes( attrType ) ) { - - this.extensionsUsed[ KHR_MESH_QUANTIZATION ] = true; - this.extensionsRequired[ KHR_MESH_QUANTIZATION ] = true; - - } - - } - - /** - * Process camera - * @param {THREE.Camera} camera Camera to process - * @return {number} Index of the processed mesh in the "camera" array - */ - processCamera( camera ) { - - const json = this.json; - - if ( ! json.cameras ) json.cameras = []; - - const isOrtho = camera.isOrthographicCamera; - - const cameraDef = { - type: isOrtho ? 'orthographic' : 'perspective' - }; - - if ( isOrtho ) { - - cameraDef.orthographic = { - xmag: camera.right * 2, - ymag: camera.top * 2, - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near - }; - - } else { - - cameraDef.perspective = { - aspectRatio: camera.aspect, - yfov: MathUtils.degToRad( camera.fov ), - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near - }; - - } - - // Question: Is saving "type" as name intentional? - if ( camera.name !== '' ) cameraDef.name = camera.type; - - return json.cameras.push( cameraDef ) - 1; - - } - - /** - * Creates glTF animation entry from AnimationClip object. - * - * Status: - * - Only properties listed in PATH_PROPERTIES may be animated. - * - * @param {THREE.AnimationClip} clip - * @param {THREE.Object3D} root - * @return {?number} - */ - processAnimation( clip, root ) { - - const json = this.json; - const nodeMap = this.nodeMap; - - if ( ! json.animations ) json.animations = []; - - clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root ); - - const tracks = clip.tracks; - const channels = []; - const samplers = []; - - for ( let i = 0; i < tracks.length; ++ i ) { - - const track = tracks[ i ]; - const trackBinding = PropertyBinding.parseTrackName( track.name ); - let trackNode = PropertyBinding.findNode( root, trackBinding.nodeName ); - const trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ]; - - if ( trackBinding.objectName === 'bones' ) { - - if ( trackNode.isSkinnedMesh === true ) { - - trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex ); - - } else { - - trackNode = undefined; - - } - - } - - if ( ! trackNode || ! trackProperty ) { - - console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name ); - continue; - - } - - const inputItemSize = 1; - let outputItemSize = track.values.length / track.times.length; - - if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) { - - outputItemSize /= trackNode.morphTargetInfluences.length; - - } - - let interpolation; - - // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE - - // Detecting glTF cubic spline interpolant by checking factory method's special property - // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return - // valid value from .getInterpolation(). - if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) { - - interpolation = 'CUBICSPLINE'; - - // itemSize of CUBICSPLINE keyframe is 9 - // (VEC3 * 3: inTangent, splineVertex, and outTangent) - // but needs to be stored as VEC3 so dividing by 3 here. - outputItemSize /= 3; - - } else if ( track.getInterpolation() === InterpolateDiscrete ) { - - interpolation = 'STEP'; - - } else { - - interpolation = 'LINEAR'; - - } - - samplers.push( { - input: this.processAccessor( new BufferAttribute( track.times, inputItemSize ) ), - output: this.processAccessor( new BufferAttribute( track.values, outputItemSize ) ), - interpolation: interpolation - } ); - - channels.push( { - sampler: samplers.length - 1, - target: { - node: nodeMap.get( trackNode ), - path: trackProperty - } - } ); - - } - - const animationDef = { - name: clip.name || 'clip_' + json.animations.length, - samplers: samplers, - channels: channels - }; - - this.serializeUserData( clip, animationDef ); - - json.animations.push( animationDef ); - - return json.animations.length - 1; - - } - - /** - * @param {THREE.Object3D} object - * @return {?number} - */ - processSkin( object ) { - - const json = this.json; - const nodeMap = this.nodeMap; - - const node = json.nodes[ nodeMap.get( object ) ]; - - const skeleton = object.skeleton; - - if ( skeleton === undefined ) return null; - - const rootJoint = object.skeleton.bones[ 0 ]; - - if ( rootJoint === undefined ) return null; - - const joints = []; - const inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 ); - const temporaryBoneInverse = new Matrix4(); - - for ( let i = 0; i < skeleton.bones.length; ++ i ) { - - joints.push( nodeMap.get( skeleton.bones[ i ] ) ); - temporaryBoneInverse.copy( skeleton.boneInverses[ i ] ); - temporaryBoneInverse.multiply( object.bindMatrix ).toArray( inverseBindMatrices, i * 16 ); - - } - - if ( json.skins === undefined ) json.skins = []; - - json.skins.push( { - inverseBindMatrices: this.processAccessor( new BufferAttribute( inverseBindMatrices, 16 ) ), - joints: joints, - skeleton: nodeMap.get( rootJoint ) - } ); - - const skinIndex = node.skin = json.skins.length - 1; - - return skinIndex; - - } - - /** - * Process Object3D node - * @param {THREE.Object3D} object Object3D to processNodeAsync - * @return {Promise<number>} Index of the node in the nodes list - */ - async processNodeAsync( object ) { - - const json = this.json; - const options = this.options; - const nodeMap = this.nodeMap; - - if ( ! json.nodes ) json.nodes = []; - - // Handle pivot by creating a container node - if ( object.pivot !== null ) { - - return await this._processNodeWithPivotAsync( object ); - - } - - const nodeDef = {}; - - if ( options.trs ) { - - const rotation = object.quaternion.toArray(); - const position = object.position.toArray(); - const scale = object.scale.toArray(); - - if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { - - nodeDef.rotation = rotation; - - } - - if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { - - nodeDef.translation = position; - - } - - if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { - - nodeDef.scale = scale; - - } - - } else { - - if ( object.matrixAutoUpdate ) { - - object.updateMatrix(); - - } - - if ( isIdentityMatrix( object.matrix ) === false ) { - - nodeDef.matrix = object.matrix.elements; - - } - - } - - // We don't export empty strings name because it represents no-name in Three.js. - if ( object.name !== '' ) nodeDef.name = String( object.name ); - - this.serializeUserData( object, nodeDef ); - - if ( object.isMesh || object.isLine || object.isPoints ) { - - const meshIndex = await this.processMeshAsync( object ); - - if ( meshIndex !== null ) nodeDef.mesh = meshIndex; - - } else if ( object.isCamera ) { - - nodeDef.camera = this.processCamera( object ); - - } - - if ( object.isSkinnedMesh ) this.skins.push( object ); - - const nodeIndex = json.nodes.push( nodeDef ) - 1; - nodeMap.set( object, nodeIndex ); - - if ( object.children.length > 0 ) { - - const children = []; - - for ( let i = 0, l = object.children.length; i < l; i ++ ) { - - const child = object.children[ i ]; - - if ( child.visible || options.onlyVisible === false ) { - - const childNodeIndex = await this.processNodeAsync( child ); - - if ( childNodeIndex !== null ) children.push( childNodeIndex ); - - } - - } - - if ( children.length > 0 ) nodeDef.children = children; - - } - - await this._invokeAllAsync( function ( ext ) { - - ext.writeNode && ext.writeNode( object, nodeDef ); - - } ); - - return nodeIndex; - - } - - /** - * Process Object3D node with pivot using container approach - * @param {THREE.Object3D} object Object3D with pivot - * @return {Promise<number>} Index of the container node - */ - async _processNodeWithPivotAsync( object ) { - - const json = this.json; - const options = this.options; - const nodeMap = this.nodeMap; - - const pivot = object.pivot; - - // Container node: holds position + pivot offset, rotation, scale - // Animations will target this node - const containerDef = {}; - - const rotation = object.quaternion.toArray(); - const position = [ - object.position.x + pivot.x, - object.position.y + pivot.y, - object.position.z + pivot.z - ]; - const scale = object.scale.toArray(); - - if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { - - containerDef.rotation = rotation; - - } - - if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { - - containerDef.translation = position; - - } - - if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { - - containerDef.scale = scale; - - } - - // Store pivot in extras for round-trip reconstruction - containerDef.extras = { pivot: pivot.toArray() }; - - if ( object.name !== '' ) containerDef.name = String( object.name ); - - this.serializeUserData( object, containerDef ); - - const containerIndex = json.nodes.push( containerDef ) - 1; - - // Map original object to container so animations target it - nodeMap.set( object, containerIndex ); - - // Child node: holds mesh with -pivot offset - const childDef = {}; - - const childPosition = [ - pivot.x, - pivot.y, - pivot.z ]; - - if ( ! equalArray( childPosition, [ 0, 0, 0 ] ) ) { - - childDef.translation = childPosition; - - } - - if ( object.isMesh || object.isLine || object.isPoints ) { - - const meshIndex = await this.processMeshAsync( object ); - - if ( meshIndex !== null ) childDef.mesh = meshIndex; - - } else if ( object.isCamera ) { - - childDef.camera = this.processCamera( object ); - - } - - if ( object.isSkinnedMesh ) this.skins.push( object ); - - const childIndex = json.nodes.push( childDef ) - 1; - - // Build children array for container - const containerChildren = [ childIndex ]; - - // Process object's children as children of the child node - if ( object.children.length > 0 ) { - - const grandchildren = []; - - for ( let i = 0, l = object.children.length; i < l; i ++ ) { - - const child = object.children[ i ]; - - if ( child.visible || options.onlyVisible === false ) { - - const childNodeIndex = await this.processNodeAsync( child ); - - if ( childNodeIndex !== null ) grandchildren.push( childNodeIndex ); - - } - - } - - if ( grandchildren.length > 0 ) childDef.children = grandchildren; - - } - - containerDef.children = containerChildren; - - await this._invokeAllAsync( function ( ext ) { - - ext.writeNode && ext.writeNode( object, containerDef ); - - } ); - - return containerIndex; - - } - - /** - * Process Scene - * @param {Scene} scene Scene to process - */ - async processSceneAsync( scene ) { - - const json = this.json; - const options = this.options; - - if ( ! json.scenes ) { - - json.scenes = []; - json.scene = 0; - - } - - const sceneDef = {}; - - if ( scene.name !== '' ) sceneDef.name = scene.name; - - json.scenes.push( sceneDef ); - - const nodes = []; - - for ( let i = 0, l = scene.children.length; i < l; i ++ ) { - - const child = scene.children[ i ]; - - if ( child.visible || options.onlyVisible === false ) { - - const nodeIndex = await this.processNodeAsync( child ); - - if ( nodeIndex !== null ) nodes.push( nodeIndex ); - - } - - } - - if ( nodes.length > 0 ) sceneDef.nodes = nodes; - - this.serializeUserData( scene, sceneDef ); - - } - - /** - * Creates a Scene to hold a list of objects and parse it - * @param {Array<THREE.Object3D>} objects List of objects to process - */ - async processObjectsAsync( objects ) { - - const scene = new Scene(); - scene.name = 'AuxScene'; - - for ( let i = 0; i < objects.length; i ++ ) { - - // We push directly to children instead of calling `add` to prevent - // modify the .parent and break its original scene and hierarchy - scene.children.push( objects[ i ] ); - - } - - await this.processSceneAsync( scene ); - - } - - /** - * @param {THREE.Object3D|Array<THREE.Object3D>} input - */ - async processInputAsync( input ) { - - const options = this.options; - - input = input instanceof Array ? input : [ input ]; - - await this._invokeAllAsync( function ( ext ) { - - ext.beforeParse && ext.beforeParse( input ); - - } ); - - const objectsWithoutScene = []; - - for ( let i = 0; i < input.length; i ++ ) { - - if ( input[ i ] instanceof Scene ) { - - await this.processSceneAsync( input[ i ] ); - - } else { - - objectsWithoutScene.push( input[ i ] ); - - } - - } - - if ( objectsWithoutScene.length > 0 ) { - - await this.processObjectsAsync( objectsWithoutScene ); - - } - - for ( let i = 0; i < this.skins.length; ++ i ) { - - this.processSkin( this.skins[ i ] ); - - } - - // animations - - if ( input.length === 1 ) { - - // default: single input, flat animations array - - for ( let i = 0; i < options.animations.length; ++ i ) { - - this.processAnimation( options.animations[ i ], input[ 0 ] ); - - } - - } else { - - // multi-input with multi-dimensional animations array - - for ( let i = 0; i < input.length; i ++ ) { - - const animations = options.animations[ i ] || []; - - for ( let j = 0; j < animations.length; ++ j ) { - - this.processAnimation( animations[ j ], input[ i ] ); - - } - - } - - } - - await this._invokeAllAsync( function ( ext ) { - - ext.afterParse && ext.afterParse( input ); - - } ); - - } - - async _invokeAllAsync( func ) { - - for ( let i = 0, il = this.plugins.length; i < il; i ++ ) { - - await func( this.plugins[ i ] ); - - } - - } - -} - -/** - * Punctual Lights Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual - * - * @private - */ -class GLTFLightExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_lights_punctual'; - - } - - writeNode( light, nodeDef ) { - - if ( ! light.isLight ) return; - - if ( ! light.isDirectionalLight && ! light.isPointLight && ! light.isSpotLight ) { - - console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light ); - return; - - } - - const writer = this.writer; - const json = writer.json; - const extensionsUsed = writer.extensionsUsed; - - const lightDef = {}; - - if ( light.name ) lightDef.name = light.name; - - lightDef.color = light.color.toArray(); - - lightDef.intensity = light.intensity; - - if ( light.isDirectionalLight ) { - - lightDef.type = 'directional'; - - } else if ( light.isPointLight ) { - - lightDef.type = 'point'; - - if ( light.distance > 0 ) lightDef.range = light.distance; - - } else if ( light.isSpotLight ) { - - lightDef.type = 'spot'; - - if ( light.distance > 0 ) lightDef.range = light.distance; - - lightDef.spot = {}; - lightDef.spot.innerConeAngle = ( 1.0 - light.penumbra ) * light.angle; - lightDef.spot.outerConeAngle = light.angle; - - } - - if ( light.decay !== undefined && light.decay !== 2 ) { - - console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' - + 'and expects light.decay=2.' ); - - } - - if ( light.target - && ( light.target.parent !== light - || light.target.position.x !== 0 - || light.target.position.y !== 0 - || light.target.position.z !== - 1 ) ) { - - console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, ' - + 'make light.target a child of the light with position 0,0,-1.' ); - - } - - if ( ! extensionsUsed[ this.name ] ) { - - json.extensions = json.extensions || {}; - json.extensions[ this.name ] = { lights: [] }; - extensionsUsed[ this.name ] = true; - - } - - const lights = json.extensions[ this.name ].lights; - lights.push( lightDef ); - - nodeDef.extensions = nodeDef.extensions || {}; - nodeDef.extensions[ this.name ] = { light: lights.length - 1 }; - - } - -} - -/** - * Unlit Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit - * - * @private - */ -class GLTFMaterialsUnlitExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_unlit'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshBasicMaterial ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = {}; - - extensionsUsed[ this.name ] = true; - - materialDef.pbrMetallicRoughness.metallicFactor = 0.0; - materialDef.pbrMetallicRoughness.roughnessFactor = 0.9; - - } - -} - -/** - * Clearcoat Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat - * - * @private - */ -class GLTFMaterialsClearcoatExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_clearcoat'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || material.clearcoat === 0 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - extensionDef.clearcoatFactor = material.clearcoat; - - if ( material.clearcoatMap ) { - - const clearcoatMapDef = { - index: await writer.processTextureAsync( material.clearcoatMap ), - texCoord: material.clearcoatMap.channel - }; - writer.applyTextureTransform( clearcoatMapDef, material.clearcoatMap ); - extensionDef.clearcoatTexture = clearcoatMapDef; - - } - - extensionDef.clearcoatRoughnessFactor = material.clearcoatRoughness; - - if ( material.clearcoatRoughnessMap ) { - - const clearcoatRoughnessMapDef = { - index: await writer.processTextureAsync( material.clearcoatRoughnessMap ), - texCoord: material.clearcoatRoughnessMap.channel - }; - writer.applyTextureTransform( clearcoatRoughnessMapDef, material.clearcoatRoughnessMap ); - extensionDef.clearcoatRoughnessTexture = clearcoatRoughnessMapDef; - - } - - if ( material.clearcoatNormalMap ) { - - const clearcoatNormalMapDef = { - index: await writer.processTextureAsync( material.clearcoatNormalMap ), - texCoord: material.clearcoatNormalMap.channel - }; - - if ( material.clearcoatNormalScale.x !== 1 ) clearcoatNormalMapDef.scale = material.clearcoatNormalScale.x; - - writer.applyTextureTransform( clearcoatNormalMapDef, material.clearcoatNormalMap ); - extensionDef.clearcoatNormalTexture = clearcoatNormalMapDef; - - } - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - - } - -} - -/** - * Materials dispersion Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_dispersion - * - * @private - */ -class GLTFMaterialsDispersionExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_dispersion'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || material.dispersion === 0 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - extensionDef.dispersion = material.dispersion; - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * Iridescence Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence - * - * @private - */ -class GLTFMaterialsIridescenceExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_iridescence'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || material.iridescence === 0 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - extensionDef.iridescenceFactor = material.iridescence; - - if ( material.iridescenceMap ) { - - const iridescenceMapDef = { - index: await writer.processTextureAsync( material.iridescenceMap ), - texCoord: material.iridescenceMap.channel - }; - writer.applyTextureTransform( iridescenceMapDef, material.iridescenceMap ); - extensionDef.iridescenceTexture = iridescenceMapDef; - - } - - extensionDef.iridescenceIor = material.iridescenceIOR; - extensionDef.iridescenceThicknessMinimum = material.iridescenceThicknessRange[ 0 ]; - extensionDef.iridescenceThicknessMaximum = material.iridescenceThicknessRange[ 1 ]; - - if ( material.iridescenceThicknessMap ) { - - const iridescenceThicknessMapDef = { - index: await writer.processTextureAsync( material.iridescenceThicknessMap ), - texCoord: material.iridescenceThicknessMap.channel - }; - writer.applyTextureTransform( iridescenceThicknessMapDef, material.iridescenceThicknessMap ); - extensionDef.iridescenceThicknessTexture = iridescenceThicknessMapDef; - - } - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * Transmission Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission - * - * @private - */ -class GLTFMaterialsTransmissionExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_transmission'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - extensionDef.transmissionFactor = material.transmission; - - if ( material.transmissionMap ) { - - const transmissionMapDef = { - index: await writer.processTextureAsync( material.transmissionMap ), - texCoord: material.transmissionMap.channel - }; - writer.applyTextureTransform( transmissionMapDef, material.transmissionMap ); - extensionDef.transmissionTexture = transmissionMapDef; - - } - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * Materials Volume Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume - * - * @private - */ -class GLTFMaterialsVolumeExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_volume'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - extensionDef.thicknessFactor = material.thickness; - - if ( material.thicknessMap ) { - - const thicknessMapDef = { - index: await writer.processTextureAsync( material.thicknessMap ), - texCoord: material.thicknessMap.channel - }; - writer.applyTextureTransform( thicknessMapDef, material.thicknessMap ); - extensionDef.thicknessTexture = thicknessMapDef; - - } - - if ( material.attenuationDistance !== Infinity ) { - - extensionDef.attenuationDistance = material.attenuationDistance; - - } - - extensionDef.attenuationColor = material.attenuationColor.toArray(); - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * Materials ior Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior - * - * @private - */ -class GLTFMaterialsIorExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_ior'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || material.ior === 1.5 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - extensionDef.ior = material.ior; - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * Materials specular Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular - * - * @private - */ -class GLTFMaterialsSpecularExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_specular'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || ( material.specularIntensity === 1.0 && - material.specularColor.equals( DEFAULT_SPECULAR_COLOR ) && - ! material.specularIntensityMap && ! material.specularColorMap ) ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - if ( material.specularIntensityMap ) { - - const specularIntensityMapDef = { - index: await writer.processTextureAsync( material.specularIntensityMap ), - texCoord: material.specularIntensityMap.channel - }; - writer.applyTextureTransform( specularIntensityMapDef, material.specularIntensityMap ); - extensionDef.specularTexture = specularIntensityMapDef; - - } - - if ( material.specularColorMap ) { - - const specularColorMapDef = { - index: await writer.processTextureAsync( material.specularColorMap ), - texCoord: material.specularColorMap.channel - }; - writer.applyTextureTransform( specularColorMapDef, material.specularColorMap ); - extensionDef.specularColorTexture = specularColorMapDef; - - } - - extensionDef.specularFactor = material.specularIntensity; - extensionDef.specularColorFactor = material.specularColor.toArray(); - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * Sheen Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen - * - * @private - */ -class GLTFMaterialsSheenExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_sheen'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || material.sheen == 0.0 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - if ( material.sheenRoughnessMap ) { - - const sheenRoughnessMapDef = { - index: await writer.processTextureAsync( material.sheenRoughnessMap ), - texCoord: material.sheenRoughnessMap.channel - }; - writer.applyTextureTransform( sheenRoughnessMapDef, material.sheenRoughnessMap ); - extensionDef.sheenRoughnessTexture = sheenRoughnessMapDef; - - } - - if ( material.sheenColorMap ) { - - const sheenColorMapDef = { - index: await writer.processTextureAsync( material.sheenColorMap ), - texCoord: material.sheenColorMap.channel - }; - writer.applyTextureTransform( sheenColorMapDef, material.sheenColorMap ); - extensionDef.sheenColorTexture = sheenColorMapDef; - - } - - extensionDef.sheenRoughnessFactor = material.sheenRoughness; - extensionDef.sheenColorFactor = material.sheenColor.toArray(); - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * Anisotropy Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_anisotropy - * - * @private - */ -class GLTFMaterialsAnisotropyExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_anisotropy'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || material.anisotropy == 0.0 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - if ( material.anisotropyMap ) { - - const anisotropyMapDef = { index: await writer.processTextureAsync( material.anisotropyMap ) }; - writer.applyTextureTransform( anisotropyMapDef, material.anisotropyMap ); - extensionDef.anisotropyTexture = anisotropyMapDef; - - } - - extensionDef.anisotropyStrength = material.anisotropy; - extensionDef.anisotropyRotation = material.anisotropyRotation; - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * Materials Emissive Strength Extension - * - * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md - * - * @private - */ -class GLTFMaterialsEmissiveStrengthExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_emissive_strength'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshStandardMaterial || material.emissiveIntensity === 1.0 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - extensionDef.emissiveStrength = material.emissiveIntensity; - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - - -/** - * Materials bump Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/EXT_materials_bump - * - * @private - */ -class GLTFMaterialsBumpExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'EXT_materials_bump'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshStandardMaterial || ( - material.bumpScale === 1 && - ! material.bumpMap ) ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - if ( material.bumpMap ) { - - const bumpMapDef = { - index: await writer.processTextureAsync( material.bumpMap ), - texCoord: material.bumpMap.channel - }; - writer.applyTextureTransform( bumpMapDef, material.bumpMap ); - extensionDef.bumpTexture = bumpMapDef; - - } - - extensionDef.bumpFactor = material.bumpScale; - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * GPU Instancing Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing - * - * @private - */ -class GLTFMeshGpuInstancing { - - constructor( writer ) { - - this.writer = writer; - this.name = 'EXT_mesh_gpu_instancing'; - - } - - writeNode( object, nodeDef ) { - - if ( ! object.isInstancedMesh ) return; - - const writer = this.writer; - - const mesh = object; - - const translationAttr = new Float32Array( mesh.count * 3 ); - const rotationAttr = new Float32Array( mesh.count * 4 ); - const scaleAttr = new Float32Array( mesh.count * 3 ); - - const matrix = new Matrix4(); - const position = new Vector3(); - const quaternion = new Quaternion(); - const scale = new Vector3(); - - for ( let i = 0; i < mesh.count; i ++ ) { - - mesh.getMatrixAt( i, matrix ); - matrix.decompose( position, quaternion, scale ); - - position.toArray( translationAttr, i * 3 ); - quaternion.toArray( rotationAttr, i * 4 ); - scale.toArray( scaleAttr, i * 3 ); - - } - - const attributes = { - TRANSLATION: writer.processAccessor( new BufferAttribute( translationAttr, 3 ) ), - ROTATION: writer.processAccessor( new BufferAttribute( rotationAttr, 4 ) ), - SCALE: writer.processAccessor( new BufferAttribute( scaleAttr, 3 ) ), - }; - - if ( mesh.instanceColor ) - attributes._COLOR_0 = writer.processAccessor( mesh.instanceColor ); - - nodeDef.extensions = nodeDef.extensions || {}; - nodeDef.extensions[ this.name ] = { attributes }; - - writer.extensionsUsed[ this.name ] = true; - writer.extensionsRequired[ this.name ] = true; - - } - -} - -/** - * Static utility functions - * - * @private - */ -GLTFExporter.Utils = { - - insertKeyframe: function ( track, time ) { - - const tolerance = 0.001; // 1ms - const valueSize = track.getValueSize(); - - const times = new track.TimeBufferType( track.times.length + 1 ); - const values = new track.ValueBufferType( track.values.length + valueSize ); - const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); - - let index; - - if ( track.times.length === 0 ) { - - times[ 0 ] = time; - - for ( let i = 0; i < valueSize; i ++ ) { - - values[ i ] = 0; - - } - - index = 0; - - } else if ( time < track.times[ 0 ] ) { - - if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; - - times[ 0 ] = time; - times.set( track.times, 1 ); - - values.set( interpolant.evaluate( time ), 0 ); - values.set( track.values, valueSize ); - - index = 0; - - } else if ( time > track.times[ track.times.length - 1 ] ) { - - if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { - - return track.times.length - 1; - - } - - times[ times.length - 1 ] = time; - times.set( track.times, 0 ); - - values.set( track.values, 0 ); - values.set( interpolant.evaluate( time ), track.values.length ); - - index = times.length - 1; - - } else { - - for ( let i = 0; i < track.times.length; i ++ ) { - - if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; - - if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { - - times.set( track.times.slice( 0, i + 1 ), 0 ); - times[ i + 1 ] = time; - times.set( track.times.slice( i + 1 ), i + 2 ); - - values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); - values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); - values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); - - index = i + 1; - - break; - - } - - } - - } - - track.times = times; - track.values = values; - - return index; - - }, - - mergeMorphTargetTracks: function ( clip, root ) { - - const tracks = []; - const mergedTracks = {}; - const sourceTracks = clip.tracks; - - for ( let i = 0; i < sourceTracks.length; ++ i ) { - - let sourceTrack = sourceTracks[ i ]; - const sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); - const sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); - - if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { - - // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. - tracks.push( sourceTrack ); - continue; - - } - - if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete - && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { - - if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { - - // This should never happen, because glTF morph target animations - // affect all targets already. - throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); - - } - - console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); - - sourceTrack = sourceTrack.clone(); - sourceTrack.setInterpolation( InterpolateLinear ); - - } - - const targetCount = sourceTrackNode.morphTargetInfluences.length; - const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; - - if ( targetIndex === undefined ) { - - throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); - - } - - let mergedTrack; - - // If this is the first time we've seen this object, create a new - // track to store merged keyframe data for each morph target. - if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { - - mergedTrack = sourceTrack.clone(); - - const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); - - for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - - values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; - - } - - // We need to take into consideration the intended target node - // of our original un-merged morphTarget animation. - mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; - mergedTrack.values = values; - - mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; - tracks.push( mergedTrack ); - - continue; - - } - - const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); - - mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; - - // For every existing keyframe of the merged track, write a (possibly - // interpolated) value from the source track. - for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - - mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); - - } - - // For every existing keyframe of the source track, write a (possibly - // new) keyframe to the merged track. Values from the previous loop may - // be written again, but keyframes are de-duplicated. - for ( let j = 0; j < sourceTrack.times.length; j ++ ) { - - const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); - mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; - - } - - } - - clip.tracks = tracks; - - return clip; - - }, - - toTypedBufferAttribute: function ( srcAttribute, TypedArray ) { - - const dstAttribute = new BufferAttribute( new TypedArray( srcAttribute.count * srcAttribute.itemSize ), srcAttribute.itemSize, false ); - - if ( ! srcAttribute.normalized && ! srcAttribute.isInterleavedBufferAttribute ) { - - dstAttribute.array.set( srcAttribute.array ); - - return dstAttribute; - - } - - for ( let i = 0, il = srcAttribute.count; i < il; i ++ ) { - - for ( let j = 0; j < srcAttribute.itemSize; j ++ ) { - - dstAttribute.setComponent( i, j, srcAttribute.getComponent( i, j ) ); - - } - - } - - return dstAttribute; - - } - -}; - -/** - * Export options of `GLTFExporter`. - * - * @typedef {Object} GLTFExporter~Options - * @property {boolean} [trs=false] - Export position, rotation and scale instead of matrix per node. - * @property {boolean} [onlyVisible=true] - Export only visible 3D objects. - * @property {boolean} [binary=false] - Export in binary (.glb) format, returning an ArrayBuffer. - * @property {number} [maxTextureSize=Infinity] - Restricts the image maximum size (both width and height) to the given value. - * @property {Array<AnimationClip>|Array<Array<AnimationClip>>} [animations=[]] - List of animations to be included in the export. When exporting a single 3D object or scene, this is a flat list of clips. - * When exporting an array of multiple scenes, this must be a nested array with one list of clips per scene, matched to the input by index. - * @property {boolean} [includeCustomExtensions=false] - Export custom glTF extensions defined on an object's `userData.gltfExtensions` property. - * @property {string} [copyright=null] - Export with a copyright notice embedded in the glTF. - **/ - -/** - * onDone callback of `GLTFExporter`. - * - * @callback GLTFExporter~OnDone - * @param {ArrayBuffer|string} result - The generated .gltf (JSON) or .glb (binary). - */ - -/** - * onError callback of `GLTFExporter`. - * - * @callback GLTFExporter~OnError - * @param {Error} error - The error object. - */ - -export { GLTFExporter }; diff --git a/public/bluebey-studio/vendor/three/examples/jsm/loaders/GLTFLoader.js b/public/bluebey-studio/vendor/three/examples/jsm/loaders/GLTFLoader.js deleted file mode 100644 index cd95b20..0000000 --- a/public/bluebey-studio/vendor/three/examples/jsm/loaders/GLTFLoader.js +++ /dev/null @@ -1,4925 +0,0 @@ -import { - AnimationClip, - Bone, - Box3, - BufferAttribute, - BufferGeometry, - ClampToEdgeWrapping, - Color, - ColorManagement, - DirectionalLight, - DoubleSide, - FileLoader, - FrontSide, - Group, - ImageBitmapLoader, - InstancedMesh, - InterleavedBuffer, - InterleavedBufferAttribute, - Interpolant, - InterpolateDiscrete, - InterpolateLinear, - Line, - LineBasicMaterial, - LineLoop, - LineSegments, - LinearFilter, - LinearMipmapLinearFilter, - LinearMipmapNearestFilter, - LinearSRGBColorSpace, - Loader, - LoaderUtils, - Material, - MathUtils, - Matrix4, - Mesh, - MeshBasicMaterial, - MeshPhysicalMaterial, - MeshStandardMaterial, - MirroredRepeatWrapping, - NearestFilter, - NearestMipmapLinearFilter, - NearestMipmapNearestFilter, - NumberKeyframeTrack, - Object3D, - OrthographicCamera, - PerspectiveCamera, - PointLight, - Points, - PointsMaterial, - PropertyBinding, - Quaternion, - QuaternionKeyframeTrack, - RepeatWrapping, - Skeleton, - SkinnedMesh, - Sphere, - SpotLight, - Texture, - TextureLoader, - TriangleFanDrawMode, - TriangleStripDrawMode, - Vector2, - Vector3, - VectorKeyframeTrack, - SRGBColorSpace, - InstancedBufferAttribute -} from 'three'; -import { toTrianglesDrawMode } from '../utils/BufferGeometryUtils.js'; -import { clone } from '../utils/SkeletonUtils.js'; - -/** - * A loader for the glTF 2.0 format. - * - * [glTF](https://www.khronos.org/gltf/) (GL Transmission Format) is an [open format specification]{@link https://github.com/KhronosGroup/glTF/tree/main/specification/2.0) - * for efficient delivery and loading of 3D content. Assets may be provided either in JSON (.gltf) or binary (.glb) - * format. External files store textures (.jpg, .png) and additional binary data (.bin). A glTF asset may deliver - * one or more scenes, including meshes, materials, textures, skins, skeletons, morph targets, animations, lights, - * and/or cameras. - * - * `GLTFLoader` uses {@link ImageBitmapLoader} whenever possible. Be advised that image bitmaps are not - * automatically GC-collected when they are no longer referenced, and they require special handling during - * the disposal process. - * - * `GLTFLoader` supports the following glTF 2.0 extensions: - * - KHR_draco_mesh_compression - * - KHR_lights_punctual - * - KHR_materials_anisotropy - * - KHR_materials_clearcoat - * - KHR_materials_dispersion - * - KHR_materials_emissive_strength - * - KHR_materials_ior - * - KHR_materials_specular - * - KHR_materials_transmission - * - KHR_materials_iridescence - * - KHR_materials_unlit - * - KHR_materials_volume - * - KHR_mesh_quantization - * - KHR_meshopt_compression - * - KHR_texture_basisu - * - KHR_texture_transform - * - EXT_materials_bump - * - EXT_meshopt_compression - * - EXT_mesh_gpu_instancing - * - EXT_texture_avif - * - EXT_texture_webp - * - * The following glTF 2.0 extensions are supported by separately registered plugins: - * - KHR_gaussian_splatting - * - [KHR_materials_variants](https://github.com/takahirox/three-gltf-extensions) - * - [MSFT_texture_dds](https://github.com/takahirox/three-gltf-extensions) - * - [KHR_animation_pointer](https://github.com/needle-tools/three-animation-pointer) - * - [NEEDLE_progressive](https://github.com/needle-tools/gltf-progressive) - * - * ```js - * const loader = new GLTFLoader(); - * - * // Optional: Provide a DRACOLoader instance to decode compressed mesh data - * const dracoLoader = new DRACOLoader(); - * dracoLoader.setDecoderPath( '/examples/jsm/libs/draco/' ); - * loader.setDRACOLoader( dracoLoader ); - * - * const gltf = await loader.loadAsync( 'models/gltf/duck/duck.gltf' ); - * scene.add( gltf.scene ); - * ``` - * - * @augments Loader - * @three_import import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; - */ -class GLTFLoader extends Loader { - - /** - * Constructs a new glTF loader. - * - * @param {LoadingManager} [manager] - The loading manager. - */ - constructor( manager ) { - - super( manager ); - - this.dracoLoader = null; - this.ktx2Loader = null; - this.meshoptDecoder = null; - - this.pluginCallbacks = []; - - this.register( function ( parser ) { - - return new GLTFMaterialsClearcoatExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsDispersionExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFTextureBasisUExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFTextureWebPExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFTextureAVIFExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsSheenExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsTransmissionExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsVolumeExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsIorExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsEmissiveStrengthExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsSpecularExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsIridescenceExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsAnisotropyExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMaterialsBumpExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFLightsExtension( parser ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMeshoptCompression( parser, EXTENSIONS.EXT_MESHOPT_COMPRESSION ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMeshoptCompression( parser, EXTENSIONS.KHR_MESHOPT_COMPRESSION ); - - } ); - - this.register( function ( parser ) { - - return new GLTFMeshGpuInstancing( parser ); - - } ); - - } - - /** - * Starts loading from the given URL and passes the loaded glTF asset - * to the `onLoad()` callback. - * - * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. - * @param {function(GLTFLoader~LoadObject)} onLoad - Executed when the loading process has been finished. - * @param {onProgressCallback} onProgress - Executed while the loading is in progress. - * @param {onErrorCallback} onError - Executed when errors occur. - */ - load( url, onLoad, onProgress, onError ) { - - const scope = this; - - let resourcePath; - - if ( this.resourcePath !== '' ) { - - resourcePath = this.resourcePath; - - } else if ( this.path !== '' ) { - - // If a base path is set, resources will be relative paths from that plus the relative path of the gltf file - // Example path = 'https://my-cnd-server.com/', url = 'assets/models/model.gltf' - // resourcePath = 'https://my-cnd-server.com/assets/models/' - // referenced resource 'model.bin' will be loaded from 'https://my-cnd-server.com/assets/models/model.bin' - // referenced resource '../textures/texture.png' will be loaded from 'https://my-cnd-server.com/assets/textures/texture.png' - const relativeUrl = LoaderUtils.extractUrlBase( url ); - resourcePath = LoaderUtils.resolveURL( relativeUrl, this.path ); - - } else { - - resourcePath = LoaderUtils.extractUrlBase( url ); - - } - - // Tells the LoadingManager to track an extra item, which resolves after - // the model is fully loaded. This means the count of items loaded will - // be incorrect, but ensures manager.onLoad() does not fire early. - this.manager.itemStart( url ); - - const _onError = function ( e ) { - - if ( onError ) { - - onError( e ); - - } else { - - console.error( e ); - - } - - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); - - }; - - const loader = new FileLoader( this.manager ); - - loader.setPath( this.path ); - loader.setResponseType( 'arraybuffer' ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - - loader.load( url, function ( data ) { - - try { - - scope.parse( data, resourcePath, function ( gltf ) { - - onLoad( gltf ); - - scope.manager.itemEnd( url ); - - }, _onError ); - - } catch ( e ) { - - _onError( e ); - - } - - }, onProgress, _onError ); - - } - - /** - * Sets the given Draco loader to this loader. Required for decoding assets - * compressed with the `KHR_draco_mesh_compression` extension. - * - * @param {DRACOLoader} dracoLoader - The Draco loader to set. - * @return {GLTFLoader} A reference to this loader. - */ - setDRACOLoader( dracoLoader ) { - - this.dracoLoader = dracoLoader; - return this; - - } - - /** - * Sets the given KTX2 loader to this loader. Required for loading KTX2 - * compressed textures. - * - * @param {KTX2Loader} ktx2Loader - The KTX2 loader to set. - * @return {GLTFLoader} A reference to this loader. - */ - setKTX2Loader( ktx2Loader ) { - - this.ktx2Loader = ktx2Loader; - return this; - - } - - /** - * Sets the given meshopt decoder. Required for decoding assets - * compressed with the `EXT_meshopt_compression` extension. - * - * @param {Object} meshoptDecoder - The meshopt decoder to set. - * @return {GLTFLoader} A reference to this loader. - */ - setMeshoptDecoder( meshoptDecoder ) { - - this.meshoptDecoder = meshoptDecoder; - return this; - - } - - /** - * Registers a plugin callback. This API is internally used to implement the various - * glTF extensions but can also used by third-party code to add additional logic - * to the loader. - * - * @param {function(parser:GLTFParser)} callback - The callback function to register. - * @return {GLTFLoader} A reference to this loader. - */ - register( callback ) { - - if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { - - this.pluginCallbacks.push( callback ); - - } - - return this; - - } - - /** - * Unregisters a plugin callback. - * - * @param {Function} callback - The callback function to unregister. - * @return {GLTFLoader} A reference to this loader. - */ - unregister( callback ) { - - if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { - - this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); - - } - - return this; - - } - - /** - * Parses the given glTF data and returns the resulting group. - * - * @param {string|ArrayBuffer} data - The raw glTF data. - * @param {string} path - The URL base path. - * @param {function(GLTFLoader~LoadObject)} onLoad - Executed when the loading process has been finished. - * @param {onErrorCallback} onError - Executed when errors occur. - */ - parse( data, path, onLoad, onError ) { - - let json; - const extensions = {}; - const plugins = {}; - const textDecoder = new TextDecoder(); - - if ( typeof data === 'string' ) { - - json = JSON.parse( data ); - - } else if ( data instanceof ArrayBuffer ) { - - const magic = textDecoder.decode( new Uint8Array( data, 0, 4 ) ); - - if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { - - try { - - extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); - - } catch ( error ) { - - if ( onError ) onError( error ); - return; - - } - - json = JSON.parse( extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content ); - - } else { - - json = JSON.parse( textDecoder.decode( data ) ); - - } - - } else { - - json = data; - - } - - if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) { - - if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) ); - return; - - } - - const parser = new GLTFParser( json, { - - path: path || this.resourcePath || '', - crossOrigin: this.crossOrigin, - requestHeader: this.requestHeader, - manager: this.manager, - ktx2Loader: this.ktx2Loader, - meshoptDecoder: this.meshoptDecoder - - } ); - - parser.fileLoader.setRequestHeader( this.requestHeader ); - - for ( let i = 0; i < this.pluginCallbacks.length; i ++ ) { - - const plugin = this.pluginCallbacks[ i ]( parser ); - - if ( ! plugin.name ) console.error( 'THREE.GLTFLoader: Invalid plugin found: missing name' ); - - plugins[ plugin.name ] = plugin; - - // Workaround to avoid determining as unknown extension - // in addUnknownExtensionsToUserData(). - // Remove this workaround if we move all the existing - // extension handlers to plugin system - extensions[ plugin.name ] = true; - - } - - if ( json.extensionsUsed ) { - - for ( let i = 0; i < json.extensionsUsed.length; ++ i ) { - - const extensionName = json.extensionsUsed[ i ]; - const extensionsRequired = json.extensionsRequired || []; - - switch ( extensionName ) { - - case EXTENSIONS.KHR_MATERIALS_UNLIT: - extensions[ extensionName ] = new GLTFMaterialsUnlitExtension(); - break; - - case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: - extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); - break; - - case EXTENSIONS.KHR_TEXTURE_TRANSFORM: - extensions[ extensionName ] = new GLTFTextureTransformExtension(); - break; - - case EXTENSIONS.KHR_MESH_QUANTIZATION: - extensions[ extensionName ] = new GLTFMeshQuantizationExtension(); - break; - - default: - - if ( extensionsRequired.indexOf( extensionName ) >= 0 && plugins[ extensionName ] === undefined ) { - - console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' ); - - } - - } - - } - - } - - parser.setExtensions( extensions ); - parser.setPlugins( plugins ); - parser.parse( onLoad, onError ); - - } - - /** - * Async version of {@link GLTFLoader#parse}. - * - * @async - * @param {string|ArrayBuffer} data - The raw glTF data. - * @param {string} path - The URL base path. - * @return {Promise<GLTFLoader~LoadObject>} A Promise that resolves with the loaded glTF when the parsing has been finished. - */ - parseAsync( data, path ) { - - const scope = this; - - return new Promise( function ( resolve, reject ) { - - scope.parse( data, path, resolve, reject ); - - } ); - - } - -} - -/* GLTFREGISTRY */ - -function GLTFRegistry() { - - let objects = {}; - - return { - - get: function ( key ) { - - return objects[ key ]; - - }, - - add: function ( key, object ) { - - objects[ key ] = object; - - }, - - remove: function ( key ) { - - delete objects[ key ]; - - }, - - removeAll: function () { - - objects = {}; - - } - - }; - -} - -/*********************************/ -/********** EXTENSIONS ***********/ -/*********************************/ - -function getMaterialExtension( parser, materialIndex, extensionName ) { - - const materialDef = parser.json.materials[ materialIndex ]; - - if ( materialDef.extensions && materialDef.extensions[ extensionName ] ) { - - return materialDef.extensions[ extensionName ]; - - } - - return null; - -} - -const EXTENSIONS = { - KHR_BINARY_GLTF: 'KHR_binary_glTF', - KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', - KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', - KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat', - KHR_MATERIALS_DISPERSION: 'KHR_materials_dispersion', - KHR_MATERIALS_IOR: 'KHR_materials_ior', - KHR_MATERIALS_SHEEN: 'KHR_materials_sheen', - KHR_MATERIALS_SPECULAR: 'KHR_materials_specular', - KHR_MATERIALS_TRANSMISSION: 'KHR_materials_transmission', - KHR_MATERIALS_IRIDESCENCE: 'KHR_materials_iridescence', - KHR_MATERIALS_ANISOTROPY: 'KHR_materials_anisotropy', - KHR_MATERIALS_UNLIT: 'KHR_materials_unlit', - KHR_MATERIALS_VOLUME: 'KHR_materials_volume', - KHR_TEXTURE_BASISU: 'KHR_texture_basisu', - KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform', - KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization', - KHR_MATERIALS_EMISSIVE_STRENGTH: 'KHR_materials_emissive_strength', - EXT_MATERIALS_BUMP: 'EXT_materials_bump', - EXT_TEXTURE_WEBP: 'EXT_texture_webp', - EXT_TEXTURE_AVIF: 'EXT_texture_avif', - EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression', - KHR_MESHOPT_COMPRESSION: 'KHR_meshopt_compression', - EXT_MESH_GPU_INSTANCING: 'EXT_mesh_gpu_instancing' -}; - -/** - * Punctual Lights Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual - * - * @private - */ -class GLTFLightsExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; - - // Object3D instance caches - this.cache = { refs: {}, uses: {} }; - - } - - _markDefs() { - - const parser = this.parser; - const nodeDefs = this.parser.json.nodes || []; - - for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { - - const nodeDef = nodeDefs[ nodeIndex ]; - - if ( nodeDef.extensions - && nodeDef.extensions[ this.name ] - && nodeDef.extensions[ this.name ].light !== undefined ) { - - parser._addNodeRef( this.cache, nodeDef.extensions[ this.name ].light ); - - } - - } - - } - - _loadLight( lightIndex ) { - - const parser = this.parser; - const cacheKey = 'light:' + lightIndex; - let dependency = parser.cache.get( cacheKey ); - - if ( dependency ) return dependency; - - const json = parser.json; - const extensions = ( json.extensions && json.extensions[ this.name ] ) || {}; - const lightDefs = extensions.lights || []; - const lightDef = lightDefs[ lightIndex ]; - let lightNode; - - const color = new Color( 0xffffff ); - - if ( lightDef.color !== undefined ) color.setRGB( lightDef.color[ 0 ], lightDef.color[ 1 ], lightDef.color[ 2 ], LinearSRGBColorSpace ); - - const range = lightDef.range !== undefined ? lightDef.range : 0; - - switch ( lightDef.type ) { - - case 'directional': - lightNode = new DirectionalLight( color ); - lightNode.target.position.set( 0, 0, - 1 ); - lightNode.add( lightNode.target ); - break; - - case 'point': - lightNode = new PointLight( color ); - lightNode.distance = range; - break; - - case 'spot': - lightNode = new SpotLight( color ); - lightNode.distance = range; - // Handle spotlight properties. - lightDef.spot = lightDef.spot || {}; - lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0; - lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0; - lightNode.angle = lightDef.spot.outerConeAngle; - lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle; - lightNode.target.position.set( 0, 0, - 1 ); - lightNode.add( lightNode.target ); - break; - - default: - throw new Error( 'THREE.GLTFLoader: Unexpected light type: ' + lightDef.type ); - - } - - // Some lights (e.g. spot) default to a position other than the origin. Reset the position - // here, because node-level parsing will only override position if explicitly specified. - lightNode.position.set( 0, 0, 0 ); - - assignExtrasToUserData( lightNode, lightDef ); - - if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; - - lightNode.name = parser.createUniqueName( lightDef.name || ( 'light_' + lightIndex ) ); - - dependency = Promise.resolve( lightNode ); - - parser.cache.add( cacheKey, dependency ); - - return dependency; - - } - - getDependency( type, index ) { - - if ( type !== 'light' ) return; - - return this._loadLight( index ); - - } - - createNodeAttachment( nodeIndex ) { - - const self = this; - const parser = this.parser; - const json = parser.json; - const nodeDef = json.nodes[ nodeIndex ]; - const lightDef = ( nodeDef.extensions && nodeDef.extensions[ this.name ] ) || {}; - const lightIndex = lightDef.light; - - if ( lightIndex === undefined ) return null; - - return this._loadLight( lightIndex ).then( function ( light ) { - - return parser._getNodeRef( self.cache, lightIndex, light ); - - } ); - - } - -} - -/** - * Unlit Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit - * - * @private - */ -class GLTFMaterialsUnlitExtension { - - constructor() { - - this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; - - } - - getMaterialType() { - - return MeshBasicMaterial; - - } - - extendParams( materialParams, materialDef, parser ) { - - const pending = []; - - materialParams.color = new Color( 1.0, 1.0, 1.0 ); - materialParams.opacity = 1.0; - - const metallicRoughness = materialDef.pbrMetallicRoughness; - - if ( metallicRoughness ) { - - if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { - - const array = metallicRoughness.baseColorFactor; - - materialParams.color.setRGB( array[ 0 ], array[ 1 ], array[ 2 ], LinearSRGBColorSpace ); - materialParams.opacity = array[ 3 ]; - - } - - if ( metallicRoughness.baseColorTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, SRGBColorSpace ) ); - - } - - } - - return Promise.all( pending ); - - } - -} - -/** - * Materials Emissive Strength Extension - * - * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md - * - * @private - */ -class GLTFMaterialsEmissiveStrengthExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_EMISSIVE_STRENGTH; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - if ( extension === null ) return Promise.resolve(); - - if ( extension.emissiveStrength !== undefined ) { - - materialParams.emissiveIntensity = extension.emissiveStrength; - - } - - return Promise.resolve(); - - } - -} - -/** - * Clearcoat Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat - * - * @private - */ -class GLTFMaterialsClearcoatExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT; - - } - - getMaterialType( materialIndex ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - return extension !== null ? MeshPhysicalMaterial : null; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - if ( extension === null ) return Promise.resolve(); - - const pending = []; - - if ( extension.clearcoatFactor !== undefined ) { - - materialParams.clearcoat = extension.clearcoatFactor; - - } - - if ( extension.clearcoatTexture !== undefined ) { - - pending.push( this.parser.assignTexture( materialParams, 'clearcoatMap', extension.clearcoatTexture ) ); - - } - - if ( extension.clearcoatRoughnessFactor !== undefined ) { - - materialParams.clearcoatRoughness = extension.clearcoatRoughnessFactor; - - } - - if ( extension.clearcoatRoughnessTexture !== undefined ) { - - pending.push( this.parser.assignTexture( materialParams, 'clearcoatRoughnessMap', extension.clearcoatRoughnessTexture ) ); - - } - - if ( extension.clearcoatNormalTexture !== undefined ) { - - pending.push( this.parser.assignTexture( materialParams, 'clearcoatNormalMap', extension.clearcoatNormalTexture ) ); - - if ( extension.clearcoatNormalTexture.scale !== undefined ) { - - const scale = extension.clearcoatNormalTexture.scale; - - materialParams.clearcoatNormalScale = new Vector2( scale, scale ); - - } - - } - - return Promise.all( pending ); - - } - -} - -/** - * Materials dispersion Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_dispersion - * - * @private - */ -class GLTFMaterialsDispersionExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_DISPERSION; - - } - - getMaterialType( materialIndex ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - return extension !== null ? MeshPhysicalMaterial : null; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - if ( extension === null ) return Promise.resolve(); - - materialParams.dispersion = extension.dispersion !== undefined ? extension.dispersion : 0; - - return Promise.resolve(); - - } - -} - -/** - * Iridescence Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence - * - * @private - */ -class GLTFMaterialsIridescenceExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_IRIDESCENCE; - - } - - getMaterialType( materialIndex ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - return extension !== null ? MeshPhysicalMaterial : null; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - if ( extension === null ) return Promise.resolve(); - - const pending = []; - - if ( extension.iridescenceFactor !== undefined ) { - - materialParams.iridescence = extension.iridescenceFactor; - - } - - if ( extension.iridescenceTexture !== undefined ) { - - pending.push( this.parser.assignTexture( materialParams, 'iridescenceMap', extension.iridescenceTexture ) ); - - } - - if ( extension.iridescenceIor !== undefined ) { - - materialParams.iridescenceIOR = extension.iridescenceIor; - - } - - if ( materialParams.iridescenceThicknessRange === undefined ) { - - materialParams.iridescenceThicknessRange = [ 100, 400 ]; - - } - - if ( extension.iridescenceThicknessMinimum !== undefined ) { - - materialParams.iridescenceThicknessRange[ 0 ] = extension.iridescenceThicknessMinimum; - - } - - if ( extension.iridescenceThicknessMaximum !== undefined ) { - - materialParams.iridescenceThicknessRange[ 1 ] = extension.iridescenceThicknessMaximum; - - } - - if ( extension.iridescenceThicknessTexture !== undefined ) { - - pending.push( this.parser.assignTexture( materialParams, 'iridescenceThicknessMap', extension.iridescenceThicknessTexture ) ); - - } - - return Promise.all( pending ); - - } - -} - -/** - * Sheen Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen - * - * @private - */ -class GLTFMaterialsSheenExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_SHEEN; - - } - - getMaterialType( materialIndex ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - return extension !== null ? MeshPhysicalMaterial : null; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - if ( extension === null ) return Promise.resolve(); - - const pending = []; - - materialParams.sheenColor = new Color( 0, 0, 0 ); - materialParams.sheenRoughness = 0; - materialParams.sheen = 1; - - if ( extension.sheenColorFactor !== undefined ) { - - const colorFactor = extension.sheenColorFactor; - materialParams.sheenColor.setRGB( colorFactor[ 0 ], colorFactor[ 1 ], colorFactor[ 2 ], LinearSRGBColorSpace ); - - } - - if ( extension.sheenRoughnessFactor !== undefined ) { - - materialParams.sheenRoughness = extension.sheenRoughnessFactor; - - } - - if ( extension.sheenColorTexture !== undefined ) { - - pending.push( this.parser.assignTexture( materialParams, 'sheenColorMap', extension.sheenColorTexture, SRGBColorSpace ) ); - - } - - if ( extension.sheenRoughnessTexture !== undefined ) { - - pending.push( this.parser.assignTexture( materialParams, 'sheenRoughnessMap', extension.sheenRoughnessTexture ) ); - - } - - return Promise.all( pending ); - - } - -} - -/** - * Transmission Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission - * Draft: https://github.com/KhronosGroup/glTF/pull/1698 - * - * @private - */ -class GLTFMaterialsTransmissionExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION; - - } - - getMaterialType( materialIndex ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - return extension !== null ? MeshPhysicalMaterial : null; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - if ( extension === null ) return Promise.resolve(); - - const pending = []; - - if ( extension.transmissionFactor !== undefined ) { - - materialParams.transmission = extension.transmissionFactor; - - } - - if ( extension.transmissionTexture !== undefined ) { - - pending.push( this.parser.assignTexture( materialParams, 'transmissionMap', extension.transmissionTexture ) ); - - } - - return Promise.all( pending ); - - } - -} - -/** - * Materials Volume Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume - * - * @private - */ -class GLTFMaterialsVolumeExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_VOLUME; - - } - - getMaterialType( materialIndex ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - return extension !== null ? MeshPhysicalMaterial : null; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - if ( extension === null ) return Promise.resolve(); - - const pending = []; - - materialParams.thickness = extension.thicknessFactor !== undefined ? extension.thicknessFactor : 0; - - if ( extension.thicknessTexture !== undefined ) { - - pending.push( this.parser.assignTexture( materialParams, 'thicknessMap', extension.thicknessTexture ) ); - - } - - materialParams.attenuationDistance = extension.attenuationDistance || Infinity; - - const colorArray = extension.attenuationColor || [ 1, 1, 1 ]; - materialParams.attenuationColor = new Color().setRGB( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ], LinearSRGBColorSpace ); - - return Promise.all( pending ); - - } - -} - -/** - * Materials ior Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior - * - * @private - */ -class GLTFMaterialsIorExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_IOR; - - } - - getMaterialType( materialIndex ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - return extension !== null ? MeshPhysicalMaterial : null; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - if ( extension === null ) return Promise.resolve(); - - materialParams.ior = extension.ior !== undefined ? extension.ior : 1.5; - - if ( materialParams.ior === 0 ) materialParams.ior = 1000; // see #26167 - - return Promise.resolve(); - - } - -} - -/** - * Materials specular Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular - * - * @private - */ -class GLTFMaterialsSpecularExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_SPECULAR; - - } - - getMaterialType( materialIndex ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - return extension !== null ? MeshPhysicalMaterial : null; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - if ( extension === null ) return Promise.resolve(); - - const pending = []; - - materialParams.specularIntensity = extension.specularFactor !== undefined ? extension.specularFactor : 1.0; - - if ( extension.specularTexture !== undefined ) { - - pending.push( this.parser.assignTexture( materialParams, 'specularIntensityMap', extension.specularTexture ) ); - - } - - const colorArray = extension.specularColorFactor || [ 1, 1, 1 ]; - materialParams.specularColor = new Color().setRGB( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ], LinearSRGBColorSpace ); - - if ( extension.specularColorTexture !== undefined ) { - - pending.push( this.parser.assignTexture( materialParams, 'specularColorMap', extension.specularColorTexture, SRGBColorSpace ) ); - - } - - return Promise.all( pending ); - - } - -} - - -/** - * Materials bump Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/EXT_materials_bump - * - * @private - */ -class GLTFMaterialsBumpExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.EXT_MATERIALS_BUMP; - - } - - getMaterialType( materialIndex ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - return extension !== null ? MeshPhysicalMaterial : null; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - if ( extension === null ) return Promise.resolve(); - - const pending = []; - - materialParams.bumpScale = extension.bumpFactor !== undefined ? extension.bumpFactor : 1.0; - - if ( extension.bumpTexture !== undefined ) { - - pending.push( this.parser.assignTexture( materialParams, 'bumpMap', extension.bumpTexture ) ); - - } - - return Promise.all( pending ); - - } - -} - -/** - * Materials anisotropy Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_anisotropy - * - * @private - */ -class GLTFMaterialsAnisotropyExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_MATERIALS_ANISOTROPY; - - } - - getMaterialType( materialIndex ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - return extension !== null ? MeshPhysicalMaterial : null; - - } - - extendMaterialParams( materialIndex, materialParams ) { - - const extension = getMaterialExtension( this.parser, materialIndex, this.name ); - - if ( extension === null ) return Promise.resolve(); - - const pending = []; - - if ( extension.anisotropyStrength !== undefined ) { - - materialParams.anisotropy = extension.anisotropyStrength; - - } - - if ( extension.anisotropyRotation !== undefined ) { - - materialParams.anisotropyRotation = extension.anisotropyRotation; - - } - - if ( extension.anisotropyTexture !== undefined ) { - - pending.push( this.parser.assignTexture( materialParams, 'anisotropyMap', extension.anisotropyTexture ) ); - - } - - return Promise.all( pending ); - - } - -} - -/** - * BasisU Texture Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu - * - * @private - */ -class GLTFTextureBasisUExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.KHR_TEXTURE_BASISU; - - } - - loadTexture( textureIndex ) { - - const parser = this.parser; - const json = parser.json; - - const textureDef = json.textures[ textureIndex ]; - - if ( ! textureDef.extensions || ! textureDef.extensions[ this.name ] ) { - - return null; - - } - - const extension = textureDef.extensions[ this.name ]; - const loader = parser.options.ktx2Loader; - - if ( ! loader ) { - - if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) { - - throw new Error( 'THREE.GLTFLoader: setKTX2Loader must be called before loading KTX2 textures' ); - - } else { - - // Assumes that the extension is optional and that a fallback texture is present - return null; - - } - - } - - return parser.loadTextureImage( textureIndex, extension.source, loader ); - - } - -} - -/** - * WebP Texture Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_webp - * - * @private - */ -class GLTFTextureWebPExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.EXT_TEXTURE_WEBP; - - } - - loadTexture( textureIndex ) { - - const name = this.name; - const parser = this.parser; - const json = parser.json; - - const textureDef = json.textures[ textureIndex ]; - - if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) { - - return null; - - } - - const extension = textureDef.extensions[ name ]; - const source = json.images[ extension.source ]; - - let loader = parser.textureLoader; - if ( source.uri ) { - - const handler = parser.options.manager.getHandler( source.uri ); - if ( handler !== null ) loader = handler; - - } - - return parser.loadTextureImage( textureIndex, extension.source, loader ); - - } - -} - -/** - * AVIF Texture Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_avif - * - * @private - */ -class GLTFTextureAVIFExtension { - - constructor( parser ) { - - this.parser = parser; - this.name = EXTENSIONS.EXT_TEXTURE_AVIF; - - } - - loadTexture( textureIndex ) { - - const name = this.name; - const parser = this.parser; - const json = parser.json; - - const textureDef = json.textures[ textureIndex ]; - - if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) { - - return null; - - } - - const extension = textureDef.extensions[ name ]; - const source = json.images[ extension.source ]; - - let loader = parser.textureLoader; - if ( source.uri ) { - - const handler = parser.options.manager.getHandler( source.uri ); - if ( handler !== null ) loader = handler; - - } - - return parser.loadTextureImage( textureIndex, extension.source, loader ); - - } - -} - -/** - * meshopt BufferView Compression Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression - * - * @private - */ -class GLTFMeshoptCompression { - - constructor( parser, name ) { - - this.name = name; - this.parser = parser; - - } - - loadBufferView( index ) { - - const json = this.parser.json; - const bufferView = json.bufferViews[ index ]; - - if ( bufferView.extensions && bufferView.extensions[ this.name ] ) { - - const extensionDef = bufferView.extensions[ this.name ]; - - const buffer = this.parser.getDependency( 'buffer', extensionDef.buffer ); - const decoder = this.parser.options.meshoptDecoder; - - if ( ! decoder || ! decoder.supported ) { - - if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) { - - throw new Error( 'THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed files' ); - - } else { - - // Assumes that the extension is optional and that fallback buffer data is present - return null; - - } - - } - - return buffer.then( function ( res ) { - - const byteOffset = extensionDef.byteOffset || 0; - const byteLength = extensionDef.byteLength || 0; - - const count = extensionDef.count; - const stride = extensionDef.byteStride; - - const source = new Uint8Array( res, byteOffset, byteLength ); - - if ( decoder.decodeGltfBufferAsync ) { - - return decoder.decodeGltfBufferAsync( count, stride, source, extensionDef.mode, extensionDef.filter ).then( function ( res ) { - - return res.buffer; - - } ); - - } else { - - // Support for MeshoptDecoder 0.18 or earlier, without decodeGltfBufferAsync - return decoder.ready.then( function () { - - const result = new ArrayBuffer( count * stride ); - decoder.decodeGltfBuffer( new Uint8Array( result ), count, stride, source, extensionDef.mode, extensionDef.filter ); - return result; - - } ); - - } - - } ); - - } else { - - return null; - - } - - } - -} - -/** - * GPU Instancing Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing - * - * @private - */ -class GLTFMeshGpuInstancing { - - constructor( parser ) { - - this.name = EXTENSIONS.EXT_MESH_GPU_INSTANCING; - this.parser = parser; - - } - - createNodeMesh( nodeIndex ) { - - const json = this.parser.json; - const nodeDef = json.nodes[ nodeIndex ]; - - if ( ! nodeDef.extensions || ! nodeDef.extensions[ this.name ] || - nodeDef.mesh === undefined ) { - - return null; - - } - - const meshDef = json.meshes[ nodeDef.mesh ]; - - // No Points or Lines + Instancing support yet - - for ( const primitive of meshDef.primitives ) { - - if ( primitive.mode !== WEBGL_CONSTANTS.TRIANGLES && - primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_STRIP && - primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_FAN && - primitive.mode !== undefined ) { - - return null; - - } - - } - - const extensionDef = nodeDef.extensions[ this.name ]; - const attributesDef = extensionDef.attributes; - - // @TODO: Can we support InstancedMesh + SkinnedMesh? - - const pending = []; - const attributes = {}; - - for ( const key in attributesDef ) { - - pending.push( this.parser.getDependency( 'accessor', attributesDef[ key ] ).then( accessor => { - - attributes[ key ] = accessor; - return attributes[ key ]; - - } ) ); - - } - - if ( pending.length < 1 ) { - - return null; - - } - - pending.push( this.parser.createNodeMesh( nodeIndex ) ); - - return Promise.all( pending ).then( results => { - - const nodeObject = results.pop(); - const meshes = nodeObject.isGroup ? nodeObject.children : [ nodeObject ]; - const count = results[ 0 ].count; // All attribute counts should be same - const instancedMeshes = []; - - for ( const mesh of meshes ) { - - // Temporal variables - const m = new Matrix4(); - const p = new Vector3(); - const q = new Quaternion(); - const s = new Vector3( 1, 1, 1 ); - - const instancedMesh = new InstancedMesh( mesh.geometry, mesh.material, count ); - - for ( let i = 0; i < count; i ++ ) { - - if ( attributes.TRANSLATION ) { - - p.fromBufferAttribute( attributes.TRANSLATION, i ); - - } - - if ( attributes.ROTATION ) { - - q.fromBufferAttribute( attributes.ROTATION, i ); - - } - - if ( attributes.SCALE ) { - - s.fromBufferAttribute( attributes.SCALE, i ); - - } - - instancedMesh.setMatrixAt( i, m.compose( p, q, s ) ); - - } - - // Add instance attributes to the geometry, excluding TRS. - - let instanceGeometry = null; - - for ( const attributeName in attributes ) { - - if ( attributeName === '_COLOR_0' ) { - - const attr = attributes[ attributeName ]; - instancedMesh.instanceColor = new InstancedBufferAttribute( attr.array, attr.itemSize, attr.normalized ); - - } else if ( attributeName !== 'TRANSLATION' && - attributeName !== 'ROTATION' && - attributeName !== 'SCALE' ) { - - if ( instanceGeometry === null ) { - - // do a shallow clone of the goemetry so per-instance data are not shared - - const source = instancedMesh.geometry; - instanceGeometry = new BufferGeometry(); - instanceGeometry.name = source.name; - - for ( const name in source.attributes ) instanceGeometry.setAttribute( name, source.attributes[ name ] ); - for ( const name in source.morphAttributes ) instanceGeometry.morphAttributes[ name ] = source.morphAttributes[ name ]; - if ( source.index !== null ) instanceGeometry.setIndex( source.index ); - - instanceGeometry.morphTargetsRelative = source.morphTargetsRelative; - - for ( const group of source.groups ) instanceGeometry.addGroup( group.start, group.count, group.materialIndex ); - - if ( source.boundingBox !== null ) instanceGeometry.boundingBox = source.boundingBox.clone(); - if ( source.boundingSphere !== null ) instanceGeometry.boundingSphere = source.boundingSphere.clone(); - - instanceGeometry.drawRange.start = source.drawRange.start; - instanceGeometry.drawRange.count = source.drawRange.count; - - instanceGeometry.userData = Object.assign( {}, source.userData ); - - instancedMesh.geometry = instanceGeometry; - - } - - const attr = attributes[ attributeName ]; - instanceGeometry.setAttribute( attributeName, new InstancedBufferAttribute( attr.array, attr.itemSize, attr.normalized ) ); - - } - - } - - // Just in case - Object3D.prototype.copy.call( instancedMesh, mesh ); - - this.parser.assignFinalMaterial( instancedMesh ); - - instancedMeshes.push( instancedMesh ); - - } - - if ( nodeObject.isGroup ) { - - nodeObject.clear(); - - nodeObject.add( ... instancedMeshes ); - - return nodeObject; - - } - - return instancedMeshes[ 0 ]; - - } ); - - } - -} - -/* BINARY EXTENSION */ -const BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; -const BINARY_EXTENSION_HEADER_LENGTH = 12; -const BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; - -class GLTFBinaryExtension { - - constructor( data ) { - - this.name = EXTENSIONS.KHR_BINARY_GLTF; - this.content = null; - this.body = null; - - const headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); - const textDecoder = new TextDecoder(); - - this.header = { - magic: textDecoder.decode( new Uint8Array( data.slice( 0, 4 ) ) ), - version: headerView.getUint32( 4, true ), - length: headerView.getUint32( 8, true ) - }; - - if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) { - - throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' ); - - } else if ( this.header.version < 2.0 ) { - - throw new Error( 'THREE.GLTFLoader: Legacy binary file detected.' ); - - } - - const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH; - const chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); - let chunkIndex = 0; - - while ( chunkIndex < chunkContentsLength ) { - - const chunkLength = chunkView.getUint32( chunkIndex, true ); - chunkIndex += 4; - - const chunkType = chunkView.getUint32( chunkIndex, true ); - chunkIndex += 4; - - if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) { - - const contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); - this.content = textDecoder.decode( contentArray ); - - } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { - - const byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; - this.body = data.slice( byteOffset, byteOffset + chunkLength ); - - } - - // Clients must ignore chunks with unknown types. - - chunkIndex += chunkLength; - - } - - if ( this.content === null ) { - - throw new Error( 'THREE.GLTFLoader: JSON content not found.' ); - - } - - } - -} - -/** - * DRACO Mesh Compression Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression - * - * @private - */ -class GLTFDracoMeshCompressionExtension { - - constructor( json, dracoLoader ) { - - if ( ! dracoLoader ) { - - throw new Error( 'THREE.GLTFLoader: No DRACOLoader instance provided.' ); - - } - - this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION; - this.json = json; - this.dracoLoader = dracoLoader; - this.dracoLoader.preload(); - - } - - decodePrimitive( primitive, parser ) { - - const json = this.json; - const dracoLoader = this.dracoLoader; - const bufferViewIndex = primitive.extensions[ this.name ].bufferView; - const gltfAttributeMap = primitive.extensions[ this.name ].attributes; - const threeAttributeMap = {}; - const attributeNormalizedMap = {}; - const attributeTypeMap = {}; - - for ( const attributeName in gltfAttributeMap ) { - - const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); - - threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; - - } - - for ( const attributeName in primitive.attributes ) { - - const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); - - if ( gltfAttributeMap[ attributeName ] !== undefined ) { - - const accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; - const componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; - - attributeTypeMap[ threeAttributeName ] = componentType.name; - attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; - - } - - } - - return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) { - - return new Promise( function ( resolve, reject ) { - - dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { - - for ( const attributeName in geometry.attributes ) { - - const attribute = geometry.attributes[ attributeName ]; - const normalized = attributeNormalizedMap[ attributeName ]; - - if ( normalized !== undefined ) attribute.normalized = normalized; - - } - - resolve( geometry ); - - }, threeAttributeMap, attributeTypeMap, LinearSRGBColorSpace, reject ); - - } ); - - } ); - - } - -} - -/** - * Texture Transform Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform - * - * @private - */ -class GLTFTextureTransformExtension { - - constructor() { - - this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; - - } - - extendTexture( texture, transform ) { - - if ( ( transform.texCoord === undefined || transform.texCoord === texture.channel ) - && transform.offset === undefined - && transform.rotation === undefined - && transform.scale === undefined ) { - - // See https://github.com/mrdoob/three.js/issues/21819. - return texture; - - } - - texture = texture.clone(); - - if ( transform.texCoord !== undefined ) { - - texture.channel = transform.texCoord; - - } - - if ( transform.offset !== undefined ) { - - texture.offset.fromArray( transform.offset ); - - } - - if ( transform.rotation !== undefined ) { - - texture.rotation = transform.rotation; - - } - - if ( transform.scale !== undefined ) { - - texture.repeat.fromArray( transform.scale ); - - } - - if ( transform.rotation !== undefined ) { - - // glTF's KHR_texture_transform order differs from three.js: - // glTF defines the UV transform as T * R * S - // three.js defines the UV transform as T * S * R - // - // To fix this, we need to override the matrix with the value computed per glTF spec - // We still set the other fields so that you can inspect/export the resulting object. - - const c = Math.cos( texture.rotation ); - const s = Math.sin( texture.rotation ); - - texture.matrix.set( - texture.repeat.x * c, texture.repeat.y * s, texture.offset.x, - - texture.repeat.x * s, texture.repeat.y * c, texture.offset.y, - 0, 0, 1 - ); - texture.matrixAutoUpdate = false; - - } - - texture.needsUpdate = true; - - return texture; - - } - -} - -/** - * Mesh Quantization Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization - * - * @private - */ -class GLTFMeshQuantizationExtension { - - constructor() { - - this.name = EXTENSIONS.KHR_MESH_QUANTIZATION; - - } - -} - -/*********************************/ -/********** INTERPOLATION ********/ -/*********************************/ - -// Spline Interpolation -// Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation -class GLTFCubicSplineInterpolant extends Interpolant { - - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - - super( parameterPositions, sampleValues, sampleSize, resultBuffer ); - - } - - copySampleValue_( index ) { - - // Copies a sample value to the result buffer. See description of glTF - // CUBICSPLINE values layout in interpolate_() function below. - - const result = this.resultBuffer, - values = this.sampleValues, - valueSize = this.valueSize, - offset = index * valueSize * 3 + valueSize; - - for ( let i = 0; i !== valueSize; i ++ ) { - - result[ i ] = values[ offset + i ]; - - } - - return result; - - } - - interpolate_( i1, t0, t, t1 ) { - - const result = this.resultBuffer; - const values = this.sampleValues; - const stride = this.valueSize; - - const stride2 = stride * 2; - const stride3 = stride * 3; - - const td = t1 - t0; - - const p = ( t - t0 ) / td; - const pp = p * p; - const ppp = pp * p; - - const offset1 = i1 * stride3; - const offset0 = offset1 - stride3; - - const s2 = - 2 * ppp + 3 * pp; - const s3 = ppp - pp; - const s0 = 1 - s2; - const s1 = s3 - pp + p; - - // Layout of keyframe output values for CUBICSPLINE animations: - // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ] - for ( let i = 0; i !== stride; i ++ ) { - - const p0 = values[ offset0 + i + stride ]; // splineVertex_k - const m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k) - const p1 = values[ offset1 + i + stride ]; // splineVertex_k+1 - const m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k) - - result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1; - - } - - return result; - - } - -} - -const _quaternion = new Quaternion(); - -class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant { - - interpolate_( i1, t0, t, t1 ) { - - const result = super.interpolate_( i1, t0, t, t1 ); - - _quaternion.fromArray( result ).normalize().toArray( result ); - - return result; - - } - -} - - -/*********************************/ -/********** INTERNALS ************/ -/*********************************/ - -/* CONSTANTS */ - -const WEBGL_CONSTANTS = { - FLOAT: 5126, - //FLOAT_MAT2: 35674, - FLOAT_MAT3: 35675, - FLOAT_MAT4: 35676, - FLOAT_VEC2: 35664, - FLOAT_VEC3: 35665, - FLOAT_VEC4: 35666, - LINEAR: 9729, - REPEAT: 10497, - SAMPLER_2D: 35678, - POINTS: 0, - LINES: 1, - LINE_LOOP: 2, - LINE_STRIP: 3, - TRIANGLES: 4, - TRIANGLE_STRIP: 5, - TRIANGLE_FAN: 6, - UNSIGNED_BYTE: 5121, - UNSIGNED_SHORT: 5123 -}; - -const WEBGL_COMPONENT_TYPES = { - 5120: Int8Array, - 5121: Uint8Array, - 5122: Int16Array, - 5123: Uint16Array, - 5125: Uint32Array, - 5126: Float32Array -}; - -const WEBGL_FILTERS = { - 9728: NearestFilter, - 9729: LinearFilter, - 9984: NearestMipmapNearestFilter, - 9985: LinearMipmapNearestFilter, - 9986: NearestMipmapLinearFilter, - 9987: LinearMipmapLinearFilter -}; - -const WEBGL_WRAPPINGS = { - 33071: ClampToEdgeWrapping, - 33648: MirroredRepeatWrapping, - 10497: RepeatWrapping -}; - -const WEBGL_TYPE_SIZES = { - 'SCALAR': 1, - 'VEC2': 2, - 'VEC3': 3, - 'VEC4': 4, - 'MAT2': 4, - 'MAT3': 9, - 'MAT4': 16 -}; - -const ATTRIBUTES = { - POSITION: 'position', - NORMAL: 'normal', - TANGENT: 'tangent', - TEXCOORD_0: 'uv', - TEXCOORD_1: 'uv1', - TEXCOORD_2: 'uv2', - TEXCOORD_3: 'uv3', - COLOR_0: 'color', - WEIGHTS_0: 'skinWeight', - JOINTS_0: 'skinIndex', -}; - -const PATH_PROPERTIES = { - scale: 'scale', - translation: 'position', - rotation: 'quaternion', - weights: 'morphTargetInfluences' -}; - -const INTERPOLATION = { - CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each - // keyframe track will be initialized with a default interpolation type, then modified. - LINEAR: InterpolateLinear, - STEP: InterpolateDiscrete -}; - -const ALPHA_MODES = { - OPAQUE: 'OPAQUE', - MASK: 'MASK', - BLEND: 'BLEND' -}; - -/** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material - * - * @private - * @param {Object<string, Material>} cache - * @return {Material} - */ -function createDefaultMaterial( cache ) { - - if ( cache[ 'DefaultMaterial' ] === undefined ) { - - cache[ 'DefaultMaterial' ] = new MeshStandardMaterial( { - color: 0xFFFFFF, - emissive: 0x000000, - metalness: 1, - roughness: 1, - transparent: false, - depthTest: true, - side: FrontSide - } ); - - } - - return cache[ 'DefaultMaterial' ]; - -} - -function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { - - // Add unknown glTF extensions to an object's userData. - - for ( const name in objectDef.extensions ) { - - if ( knownExtensions[ name ] === undefined ) { - - object.userData.gltfExtensions = object.userData.gltfExtensions || {}; - object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ]; - - } - - } - -} - -/** - * - * @private - * @param {Object3D|Material|BufferGeometry|Object|AnimationClip} object - * @param {GLTF.definition} gltfDef - */ -function assignExtrasToUserData( object, gltfDef ) { - - if ( gltfDef.extras !== undefined ) { - - if ( typeof gltfDef.extras === 'object' ) { - - Object.assign( object.userData, gltfDef.extras ); - - } else { - - console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras ); - - } - - } - -} - -/** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets - * - * @private - * @param {BufferGeometry} geometry - * @param {Array<GLTF.Target>} targets - * @param {GLTFParser} parser - * @return {Promise<BufferGeometry>} - */ -function addMorphTargets( geometry, targets, parser ) { - - let hasMorphPosition = false; - let hasMorphNormal = false; - let hasMorphColor = false; - - for ( let i = 0, il = targets.length; i < il; i ++ ) { - - const target = targets[ i ]; - - if ( target.POSITION !== undefined ) hasMorphPosition = true; - if ( target.NORMAL !== undefined ) hasMorphNormal = true; - if ( target.COLOR_0 !== undefined ) hasMorphColor = true; - - if ( hasMorphPosition && hasMorphNormal && hasMorphColor ) break; - - } - - if ( ! hasMorphPosition && ! hasMorphNormal && ! hasMorphColor ) return Promise.resolve( geometry ); - - const pendingPositionAccessors = []; - const pendingNormalAccessors = []; - const pendingColorAccessors = []; - - for ( let i = 0, il = targets.length; i < il; i ++ ) { - - const target = targets[ i ]; - - if ( hasMorphPosition ) { - - const pendingAccessor = target.POSITION !== undefined - ? parser.getDependency( 'accessor', target.POSITION ) - : geometry.attributes.position; - - pendingPositionAccessors.push( pendingAccessor ); - - } - - if ( hasMorphNormal ) { - - const pendingAccessor = target.NORMAL !== undefined - ? parser.getDependency( 'accessor', target.NORMAL ) - : geometry.attributes.normal; - - pendingNormalAccessors.push( pendingAccessor ); - - } - - if ( hasMorphColor ) { - - const pendingAccessor = target.COLOR_0 !== undefined - ? parser.getDependency( 'accessor', target.COLOR_0 ) - : geometry.attributes.color; - - pendingColorAccessors.push( pendingAccessor ); - - } - - } - - return Promise.all( [ - Promise.all( pendingPositionAccessors ), - Promise.all( pendingNormalAccessors ), - Promise.all( pendingColorAccessors ) - ] ).then( function ( accessors ) { - - const morphPositions = accessors[ 0 ]; - const morphNormals = accessors[ 1 ]; - const morphColors = accessors[ 2 ]; - - if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions; - if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals; - if ( hasMorphColor ) geometry.morphAttributes.color = morphColors; - geometry.morphTargetsRelative = true; - - return geometry; - - } ); - -} - -/** - * - * @private - * @param {Mesh} mesh - * @param {GLTF.Mesh} meshDef - */ -function updateMorphTargets( mesh, meshDef ) { - - mesh.updateMorphTargets(); - - if ( meshDef.weights !== undefined ) { - - for ( let i = 0, il = meshDef.weights.length; i < il; i ++ ) { - - mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ]; - - } - - } - - // .extras has user-defined data, so check that .extras.targetNames is an array. - if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) { - - const targetNames = meshDef.extras.targetNames; - - if ( mesh.morphTargetInfluences.length === targetNames.length ) { - - mesh.morphTargetDictionary = {}; - - for ( let i = 0, il = targetNames.length; i < il; i ++ ) { - - mesh.morphTargetDictionary[ targetNames[ i ] ] = i; - - } - - } else { - - console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' ); - - } - - } - -} - -function createPrimitiveKey( primitiveDef ) { - - let geometryKey; - - const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]; - - if ( dracoExtension ) { - - geometryKey = 'draco:' + dracoExtension.bufferView - + ':' + dracoExtension.indices - + ':' + createAttributesKey( dracoExtension.attributes ); - - } else { - - geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode; - - } - - if ( primitiveDef.targets !== undefined ) { - - for ( let i = 0, il = primitiveDef.targets.length; i < il; i ++ ) { - - geometryKey += ':' + createAttributesKey( primitiveDef.targets[ i ] ); - - } - - } - - return geometryKey; - -} - -function createAttributesKey( attributes ) { - - let attributesKey = ''; - - const keys = Object.keys( attributes ).sort(); - - for ( let i = 0, il = keys.length; i < il; i ++ ) { - - attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';'; - - } - - return attributesKey; - -} - -function getNormalizedComponentScale( constructor ) { - - // Reference: - // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data - - switch ( constructor ) { - - case Int8Array: - return 1 / 127; - - case Uint8Array: - return 1 / 255; - - case Int16Array: - return 1 / 32767; - - case Uint16Array: - return 1 / 65535; - - default: - throw new Error( 'THREE.GLTFLoader: Unsupported normalized accessor component type.' ); - - } - -} - -function getImageURIMimeType( uri ) { - - if ( uri.search( /\.jpe?g($|\?)/i ) > 0 || uri.search( /^data\:image\/jpeg/ ) === 0 ) return 'image/jpeg'; - if ( uri.search( /\.webp($|\?)/i ) > 0 || uri.search( /^data\:image\/webp/ ) === 0 ) return 'image/webp'; - if ( uri.search( /\.ktx2($|\?)/i ) > 0 || uri.search( /^data\:image\/ktx2/ ) === 0 ) return 'image/ktx2'; - - return 'image/png'; - -} - -const _identityMatrix = new Matrix4(); - -/* GLTF PARSER */ - -class GLTFParser { - - constructor( json = {}, options = {} ) { - - this.json = json; - this.extensions = {}; - this.plugins = {}; - this.options = options; - - // loader object cache - this.cache = new GLTFRegistry(); - - // associations between Three.js objects and glTF elements - this.associations = new Map(); - - // BufferGeometry caching - this.primitiveCache = {}; - - // Node cache - this.nodeCache = {}; - - // Object3D instance caches - this.meshCache = { refs: {}, uses: {} }; - this.cameraCache = { refs: {}, uses: {} }; - this.lightCache = { refs: {}, uses: {} }; - - this.sourceCache = {}; - this.textureCache = {}; - - // Track node names, to ensure no duplicates - this.nodeNamesUsed = {}; - - // Use an ImageBitmapLoader if imageBitmaps are supported. Moves much of the - // expensive work of uploading a texture to the GPU off the main thread. - - let isSafari = false; - let safariVersion = - 1; - let isFirefox = false; - let firefoxVersion = - 1; - - if ( typeof navigator !== 'undefined' && typeof navigator.userAgent !== 'undefined' ) { - - const userAgent = navigator.userAgent; - - isSafari = /^((?!chrome|android).)*safari/i.test( userAgent ) === true; - const safariMatch = userAgent.match( /Version\/(\d+)/ ); - safariVersion = isSafari && safariMatch ? parseInt( safariMatch[ 1 ], 10 ) : - 1; - - isFirefox = userAgent.indexOf( 'Firefox' ) > - 1; - firefoxVersion = isFirefox ? userAgent.match( /Firefox\/([0-9]+)\./ )[ 1 ] : - 1; - - } - - if ( typeof createImageBitmap === 'undefined' || ( isSafari && safariVersion < 17 ) || ( isFirefox && firefoxVersion < 98 ) ) { - - this.textureLoader = new TextureLoader( this.options.manager ); - - } else { - - this.textureLoader = new ImageBitmapLoader( this.options.manager ); - - } - - this.textureLoader.setCrossOrigin( this.options.crossOrigin ); - this.textureLoader.setRequestHeader( this.options.requestHeader ); - - this.fileLoader = new FileLoader( this.options.manager ); - this.fileLoader.setResponseType( 'arraybuffer' ); - - if ( this.options.crossOrigin === 'use-credentials' ) { - - this.fileLoader.setWithCredentials( true ); - - } - - } - - setExtensions( extensions ) { - - this.extensions = extensions; - - } - - setPlugins( plugins ) { - - this.plugins = plugins; - - } - - parse( onLoad, onError ) { - - const parser = this; - const json = this.json; - const extensions = this.extensions; - - // Clear the loader cache - this.cache.removeAll(); - this.nodeCache = {}; - - // Mark the special nodes/meshes in json for efficient parse - this._invokeAll( function ( ext ) { - - return ext._markDefs && ext._markDefs(); - - } ); - - Promise.all( this._invokeAll( function ( ext ) { - - return ext.beforeRoot && ext.beforeRoot(); - - } ) ).then( function () { - - return Promise.all( [ - - parser.getDependencies( 'scene' ), - parser.getDependencies( 'animation' ), - parser.getDependencies( 'camera' ), - - ] ); - - } ).then( function ( dependencies ) { - - const result = { - scene: dependencies[ 0 ][ json.scene || 0 ], - scenes: dependencies[ 0 ], - animations: dependencies[ 1 ], - cameras: dependencies[ 2 ], - asset: json.asset, - parser: parser, - userData: {} - }; - - addUnknownExtensionsToUserData( extensions, result, json ); - - assignExtrasToUserData( result, json ); - - return Promise.all( parser._invokeAll( function ( ext ) { - - return ext.afterRoot && ext.afterRoot( result ); - - } ) ).then( function () { - - for ( const scene of result.scenes ) { - - scene.updateMatrixWorld(); - - } - - onLoad( result ); - - } ); - - } ).catch( onError ); - - } - - /** - * Marks the special nodes/meshes in json for efficient parse. - * - * @private - */ - _markDefs() { - - const nodeDefs = this.json.nodes || []; - const skinDefs = this.json.skins || []; - const meshDefs = this.json.meshes || []; - - // Nothing in the node definition indicates whether it is a Bone or an - // Object3D. Use the skins' joint references to mark bones. - for ( let skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) { - - const joints = skinDefs[ skinIndex ].joints; - - for ( let i = 0, il = joints.length; i < il; i ++ ) { - - nodeDefs[ joints[ i ] ].isBone = true; - - } - - } - - // Iterate over all nodes, marking references to shared resources, - // as well as skeleton joints. - for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { - - const nodeDef = nodeDefs[ nodeIndex ]; - - if ( nodeDef.mesh !== undefined ) { - - this._addNodeRef( this.meshCache, nodeDef.mesh ); - - // Nothing in the mesh definition indicates whether it is - // a SkinnedMesh or Mesh. Use the node's mesh reference - // to mark SkinnedMesh if node has skin. - if ( nodeDef.skin !== undefined ) { - - meshDefs[ nodeDef.mesh ].isSkinnedMesh = true; - - } - - } - - if ( nodeDef.camera !== undefined ) { - - this._addNodeRef( this.cameraCache, nodeDef.camera ); - - } - - } - - } - - /** - * Counts references to shared node / Object3D resources. These resources - * can be reused, or "instantiated", at multiple nodes in the scene - * hierarchy. Mesh, Camera, and Light instances are instantiated and must - * be marked. Non-scenegraph resources (like Materials, Geometries, and - * Textures) can be reused directly and are not marked here. - * - * Example: CesiumMilkTruck sample model reuses "Wheel" meshes. - * - * @private - * @param {Object} cache - * @param {Object3D} index - */ - _addNodeRef( cache, index ) { - - if ( index === undefined ) return; - - if ( cache.refs[ index ] === undefined ) { - - cache.refs[ index ] = cache.uses[ index ] = 0; - - } - - cache.refs[ index ] ++; - - } - - /** - * Returns a reference to a shared resource, cloning it if necessary. - * - * @private - * @param {Object} cache - * @param {number} index - * @param {Object} object - * @return {Object} - */ - _getNodeRef( cache, index, object ) { - - if ( cache.refs[ index ] <= 1 ) return object; - - const ref = object.clone(); - - // Propagates mappings to the cloned object, prevents mappings on the - // original object from being lost. - const updateMappings = ( original, clone ) => { - - const mappings = this.associations.get( original ); - if ( mappings != null ) { - - this.associations.set( clone, mappings ); - - } - - for ( const [ i, child ] of original.children.entries() ) { - - updateMappings( child, clone.children[ i ] ); - - } - - }; - - updateMappings( object, ref ); - - ref.name += '_instance_' + ( cache.uses[ index ] ++ ); - - return ref; - - } - - _invokeOne( func ) { - - const extensions = Object.values( this.plugins ); - extensions.push( this ); - - for ( let i = 0; i < extensions.length; i ++ ) { - - const result = func( extensions[ i ] ); - - if ( result ) return result; - - } - - return null; - - } - - _invokeAll( func ) { - - const extensions = Object.values( this.plugins ); - extensions.unshift( this ); - - const pending = []; - - for ( let i = 0; i < extensions.length; i ++ ) { - - const result = func( extensions[ i ] ); - - if ( result ) pending.push( result ); - - } - - return pending; - - } - - /** - * Requests the specified dependency asynchronously, with caching. - * - * @private - * @param {string} type - * @param {number} index - * @return {Promise<Object3D|Material|Texture|AnimationClip|ArrayBuffer|Object>} - */ - getDependency( type, index ) { - - const cacheKey = type + ':' + index; - let dependency = this.cache.get( cacheKey ); - - if ( ! dependency ) { - - switch ( type ) { - - case 'scene': - dependency = this.loadScene( index ); - break; - - case 'node': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadNode && ext.loadNode( index ); - - } ); - break; - - case 'mesh': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadMesh && ext.loadMesh( index ); - - } ); - break; - - case 'accessor': - dependency = this.loadAccessor( index ); - break; - - case 'bufferView': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadBufferView && ext.loadBufferView( index ); - - } ); - break; - - case 'buffer': - dependency = this.loadBuffer( index ); - break; - - case 'material': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadMaterial && ext.loadMaterial( index ); - - } ); - break; - - case 'texture': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadTexture && ext.loadTexture( index ); - - } ); - break; - - case 'skin': - dependency = this.loadSkin( index ); - break; - - case 'animation': - dependency = this._invokeOne( function ( ext ) { - - return ext.loadAnimation && ext.loadAnimation( index ); - - } ); - break; - - case 'camera': - dependency = this.loadCamera( index ); - break; - - default: - dependency = this._invokeOne( function ( ext ) { - - return ext != this && ext.getDependency && ext.getDependency( type, index ); - - } ); - - if ( ! dependency ) { - - throw new Error( 'Unknown type: ' + type ); - - } - - break; - - } - - this.cache.add( cacheKey, dependency ); - - } - - return dependency; - - } - - /** - * Requests all dependencies of the specified type asynchronously, with caching. - * - * @private - * @param {string} type - * @return {Promise<Array<Object>>} - */ - getDependencies( type ) { - - let dependencies = this.cache.get( type ); - - if ( ! dependencies ) { - - const parser = this; - const defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || []; - - dependencies = Promise.all( defs.map( function ( def, index ) { - - return parser.getDependency( type, index ); - - } ) ); - - this.cache.add( type, dependencies ); - - } - - return dependencies; - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views - * - * @private - * @param {number} bufferIndex - * @return {Promise<ArrayBuffer>} - */ - loadBuffer( bufferIndex ) { - - const bufferDef = this.json.buffers[ bufferIndex ]; - const loader = this.fileLoader; - - if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) { - - throw new Error( 'THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.' ); - - } - - // If present, GLB container is required to be the first buffer. - if ( bufferDef.uri === undefined && bufferIndex === 0 ) { - - return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body ); - - } - - const options = this.options; - - return new Promise( function ( resolve, reject ) { - - loader.load( LoaderUtils.resolveURL( bufferDef.uri, options.path ), resolve, undefined, function () { - - reject( new Error( 'THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".' ) ); - - } ); - - } ); - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views - * - * @private - * @param {number} bufferViewIndex - * @return {Promise<ArrayBuffer>} - */ - loadBufferView( bufferViewIndex ) { - - const bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; - - return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) { - - const byteLength = bufferViewDef.byteLength || 0; - const byteOffset = bufferViewDef.byteOffset || 0; - return buffer.slice( byteOffset, byteOffset + byteLength ); - - } ); - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors - * - * @private - * @param {number} accessorIndex - * @return {Promise<BufferAttribute|InterleavedBufferAttribute>} - */ - loadAccessor( accessorIndex ) { - - const parser = this; - const json = this.json; - - const accessorDef = this.json.accessors[ accessorIndex ]; - - if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) { - - const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; - const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; - const normalized = accessorDef.normalized === true; - - const array = new TypedArray( accessorDef.count * itemSize ); - return Promise.resolve( new BufferAttribute( array, itemSize, normalized ) ); - - } - - const pendingBufferViews = []; - - if ( accessorDef.bufferView !== undefined ) { - - pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.bufferView ) ); - - } else { - - pendingBufferViews.push( null ); - - } - - if ( accessorDef.sparse !== undefined ) { - - pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.indices.bufferView ) ); - pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.values.bufferView ) ); - - } - - return Promise.all( pendingBufferViews ).then( function ( bufferViews ) { - - const bufferView = bufferViews[ 0 ]; - - const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; - const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; - - // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. - const elementBytes = TypedArray.BYTES_PER_ELEMENT; - const itemBytes = elementBytes * itemSize; - const byteOffset = accessorDef.byteOffset || 0; - const byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined; - const normalized = accessorDef.normalized === true; - let array, bufferAttribute; - - // The buffer is not interleaved if the stride is the item size in bytes. - if ( byteStride && byteStride !== itemBytes ) { - - // Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own InterleavedBuffer - // This makes sure that IBA.count reflects accessor.count properly - const ibSlice = Math.floor( byteOffset / byteStride ); - const ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count; - let ib = parser.cache.get( ibCacheKey ); - - if ( ! ib ) { - - array = new TypedArray( bufferView, ibSlice * byteStride, accessorDef.count * byteStride / elementBytes ); - - // Integer parameters to IB/IBA are in array elements, not bytes. - ib = new InterleavedBuffer( array, byteStride / elementBytes ); - - parser.cache.add( ibCacheKey, ib ); - - } - - bufferAttribute = new InterleavedBufferAttribute( ib, itemSize, ( byteOffset % byteStride ) / elementBytes, normalized ); - - } else { - - if ( bufferView === null ) { - - array = new TypedArray( accessorDef.count * itemSize ); - - } else { - - array = new TypedArray( bufferView, byteOffset, accessorDef.count * itemSize ); - - } - - bufferAttribute = new BufferAttribute( array, itemSize, normalized ); - - } - - // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors - if ( accessorDef.sparse !== undefined ) { - - const itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; - const TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]; - - const byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; - const byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; - - const sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices ); - const sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize ); - - if ( bufferView !== null ) { - - // Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes. - bufferAttribute = new BufferAttribute( bufferAttribute.array.slice(), bufferAttribute.itemSize, bufferAttribute.normalized ); - - } - - // Ignore normalized since we copy from sparse - bufferAttribute.normalized = false; - - for ( let i = 0, il = sparseIndices.length; i < il; i ++ ) { - - const index = sparseIndices[ i ]; - - bufferAttribute.setX( index, sparseValues[ i * itemSize ] ); - if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] ); - if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] ); - if ( itemSize >= 4 ) bufferAttribute.setW( index, sparseValues[ i * itemSize + 3 ] ); - if ( itemSize >= 5 ) throw new Error( 'THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.' ); - - } - - bufferAttribute.normalized = normalized; - - } - - return bufferAttribute; - - } ); - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures - * - * @private - * @param {number} textureIndex - * @return {Promise<?Texture>} - */ - loadTexture( textureIndex ) { - - const json = this.json; - const options = this.options; - const textureDef = json.textures[ textureIndex ]; - const sourceIndex = textureDef.source; - const sourceDef = json.images[ sourceIndex ]; - - let loader = this.textureLoader; - - if ( sourceDef.uri ) { - - const handler = options.manager.getHandler( sourceDef.uri ); - if ( handler !== null ) loader = handler; - - } - - return this.loadTextureImage( textureIndex, sourceIndex, loader ); - - } - - loadTextureImage( textureIndex, sourceIndex, loader ) { - - const parser = this; - const json = this.json; - - const textureDef = json.textures[ textureIndex ]; - const sourceDef = json.images[ sourceIndex ]; - - const cacheKey = ( sourceDef.uri || sourceDef.bufferView ) + ':' + textureDef.sampler; - - if ( this.textureCache[ cacheKey ] ) { - - // See https://github.com/mrdoob/three.js/issues/21559. - return this.textureCache[ cacheKey ]; - - } - - const promise = this.loadImageSource( sourceIndex, loader ).then( function ( texture ) { - - texture.flipY = false; - - texture.name = textureDef.name || sourceDef.name || ''; - - if ( texture.name === '' && typeof sourceDef.uri === 'string' && sourceDef.uri.startsWith( 'data:image/' ) === false ) { - - texture.name = sourceDef.uri; - - } - - const samplers = json.samplers || {}; - const sampler = samplers[ textureDef.sampler ] || {}; - - texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || LinearFilter; - texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || LinearMipmapLinearFilter; - texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || RepeatWrapping; - texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || RepeatWrapping; - texture.generateMipmaps = ! texture.isCompressedTexture && texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter; - - parser.associations.set( texture, { textures: textureIndex } ); - - return texture; - - } ).catch( function () { - - return null; - - } ); - - this.textureCache[ cacheKey ] = promise; - - return promise; - - } - - loadImageSource( sourceIndex, loader ) { - - const parser = this; - const json = this.json; - const options = this.options; - - if ( this.sourceCache[ sourceIndex ] !== undefined ) { - - return this.sourceCache[ sourceIndex ].then( ( texture ) => texture.clone() ); - - } - - const sourceDef = json.images[ sourceIndex ]; - - const URL = self.URL || self.webkitURL; - - let sourceURI = sourceDef.uri || ''; - let isObjectURL = false; - - if ( sourceDef.bufferView !== undefined ) { - - // Load binary image data from bufferView, if provided. - - sourceURI = parser.getDependency( 'bufferView', sourceDef.bufferView ).then( function ( bufferView ) { - - isObjectURL = true; - const blob = new Blob( [ bufferView ], { type: sourceDef.mimeType } ); - sourceURI = URL.createObjectURL( blob ); - return sourceURI; - - } ); - - } else if ( sourceDef.uri === undefined ) { - - throw new Error( 'THREE.GLTFLoader: Image ' + sourceIndex + ' is missing URI and bufferView' ); - - } - - const promise = Promise.resolve( sourceURI ).then( function ( sourceURI ) { - - return new Promise( function ( resolve, reject ) { - - let onLoad = resolve; - - if ( loader.isImageBitmapLoader === true ) { - - onLoad = function ( imageBitmap ) { - - const texture = new Texture( imageBitmap ); - texture.needsUpdate = true; - - resolve( texture ); - - }; - - } - - loader.load( LoaderUtils.resolveURL( sourceURI, options.path ), onLoad, undefined, reject ); - - } ); - - } ).then( function ( texture ) { - - // Clean up resources and configure Texture. - - if ( isObjectURL === true ) { - - URL.revokeObjectURL( sourceURI ); - - } - - assignExtrasToUserData( texture, sourceDef ); - - texture.userData.mimeType = sourceDef.mimeType || getImageURIMimeType( sourceDef.uri ); - - return texture; - - } ).catch( function ( error ) { - - console.error( 'THREE.GLTFLoader: Couldn\'t load texture', sourceURI ); - throw error; - - } ); - - this.sourceCache[ sourceIndex ] = promise; - return promise; - - } - - /** - * Asynchronously assigns a texture to the given material parameters. - * - * @private - * @param {Object} materialParams - * @param {string} mapName - * @param {Object} mapDef - * @param {string} [colorSpace] - * @return {Promise<Texture>} - */ - assignTexture( materialParams, mapName, mapDef, colorSpace ) { - - const parser = this; - - return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) { - - if ( ! texture ) return null; - - if ( mapDef.texCoord !== undefined && mapDef.texCoord > 0 ) { - - texture = texture.clone(); - texture.channel = mapDef.texCoord; - - } - - if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) { - - const transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; - - if ( transform ) { - - const gltfReference = parser.associations.get( texture ); - texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); - parser.associations.set( texture, gltfReference ); - - } - - } - - if ( colorSpace !== undefined ) { - - texture.colorSpace = colorSpace; - - } - - materialParams[ mapName ] = texture; - - return texture; - - } ); - - } - - /** - * Assigns final material to a Mesh, Line, or Points instance. The instance - * already has a material (generated from the glTF material options alone) - * but reuse of the same glTF material may require multiple threejs materials - * to accommodate different primitive types, defines, etc. New materials will - * be created if necessary, and reused from a cache. - * - * @private - * @param {Object3D} mesh Mesh, Line, or Points instance. - */ - assignFinalMaterial( mesh ) { - - const geometry = mesh.geometry; - let material = mesh.material; - - const useDerivativeTangents = geometry.attributes.tangent === undefined; - const useVertexColors = geometry.attributes.color !== undefined; - const useFlatShading = geometry.attributes.normal === undefined; - - if ( mesh.isPoints ) { - - const cacheKey = 'PointsMaterial:' + material.uuid; - - let pointsMaterial = this.cache.get( cacheKey ); - - if ( ! pointsMaterial ) { - - pointsMaterial = new PointsMaterial(); - Material.prototype.copy.call( pointsMaterial, material ); - pointsMaterial.color.copy( material.color ); - pointsMaterial.map = material.map; - pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px - - this.cache.add( cacheKey, pointsMaterial ); - - } - - material = pointsMaterial; - - } else if ( mesh.isLine ) { - - const cacheKey = 'LineBasicMaterial:' + material.uuid; - - let lineMaterial = this.cache.get( cacheKey ); - - if ( ! lineMaterial ) { - - lineMaterial = new LineBasicMaterial(); - Material.prototype.copy.call( lineMaterial, material ); - lineMaterial.color.copy( material.color ); - lineMaterial.map = material.map; - - this.cache.add( cacheKey, lineMaterial ); - - } - - material = lineMaterial; - - } - - // Clone the material if it will be modified - if ( useDerivativeTangents || useVertexColors || useFlatShading ) { - - let cacheKey = 'ClonedMaterial:' + material.uuid + ':'; - - if ( useDerivativeTangents ) cacheKey += 'derivative-tangents:'; - if ( useVertexColors ) cacheKey += 'vertex-colors:'; - if ( useFlatShading ) cacheKey += 'flat-shading:'; - - let cachedMaterial = this.cache.get( cacheKey ); - - if ( ! cachedMaterial ) { - - cachedMaterial = material.clone(); - - if ( useVertexColors ) cachedMaterial.vertexColors = true; - if ( useFlatShading ) cachedMaterial.flatShading = true; - - if ( useDerivativeTangents ) { - - // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 - if ( cachedMaterial.normalScale ) cachedMaterial.normalScale.y *= - 1; - if ( cachedMaterial.clearcoatNormalScale ) cachedMaterial.clearcoatNormalScale.y *= - 1; - - } - - this.cache.add( cacheKey, cachedMaterial ); - - this.associations.set( cachedMaterial, this.associations.get( material ) ); - - } - - material = cachedMaterial; - - } - - mesh.material = material; - - } - - getMaterialType( /* materialIndex */ ) { - - return MeshStandardMaterial; - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials - * - * @private - * @param {number} materialIndex - * @return {Promise<Material>} - */ - loadMaterial( materialIndex ) { - - const parser = this; - const json = this.json; - const extensions = this.extensions; - const materialDef = json.materials[ materialIndex ]; - - let materialType; - const materialParams = {}; - const materialExtensions = materialDef.extensions || {}; - - const pending = []; - - if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) { - - const kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ]; - materialType = kmuExtension.getMaterialType(); - pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); - - } else { - - // Specification: - // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material - - const metallicRoughness = materialDef.pbrMetallicRoughness || {}; - - materialParams.color = new Color( 1.0, 1.0, 1.0 ); - materialParams.opacity = 1.0; - - if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { - - const array = metallicRoughness.baseColorFactor; - - materialParams.color.setRGB( array[ 0 ], array[ 1 ], array[ 2 ], LinearSRGBColorSpace ); - materialParams.opacity = array[ 3 ]; - - } - - if ( metallicRoughness.baseColorTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, SRGBColorSpace ) ); - - } - - materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0; - materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0; - - if ( metallicRoughness.metallicRoughnessTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'metalnessMap', metallicRoughness.metallicRoughnessTexture ) ); - pending.push( parser.assignTexture( materialParams, 'roughnessMap', metallicRoughness.metallicRoughnessTexture ) ); - - } - - materialType = this._invokeOne( function ( ext ) { - - return ext.getMaterialType && ext.getMaterialType( materialIndex ); - - } ); - - pending.push( Promise.all( this._invokeAll( function ( ext ) { - - return ext.extendMaterialParams && ext.extendMaterialParams( materialIndex, materialParams ); - - } ) ) ); - - } - - if ( materialDef.doubleSided === true ) { - - materialParams.side = DoubleSide; - - } - - const alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; - - if ( alphaMode === ALPHA_MODES.BLEND ) { - - materialParams.transparent = true; - - // See: https://github.com/mrdoob/three.js/issues/17706 - materialParams.depthWrite = false; - - } else { - - materialParams.transparent = false; - - if ( alphaMode === ALPHA_MODES.MASK ) { - - materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; - - } - - } - - if ( materialDef.normalTexture !== undefined && materialType !== MeshBasicMaterial ) { - - pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) ); - - materialParams.normalScale = new Vector2( 1, 1 ); - - if ( materialDef.normalTexture.scale !== undefined ) { - - const scale = materialDef.normalTexture.scale; - - materialParams.normalScale.set( scale, scale ); - - } - - } - - if ( materialDef.occlusionTexture !== undefined && materialType !== MeshBasicMaterial ) { - - pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) ); - - if ( materialDef.occlusionTexture.strength !== undefined ) { - - materialParams.aoMapIntensity = materialDef.occlusionTexture.strength; - - } - - } - - if ( materialDef.emissiveFactor !== undefined && materialType !== MeshBasicMaterial ) { - - const emissiveFactor = materialDef.emissiveFactor; - materialParams.emissive = new Color().setRGB( emissiveFactor[ 0 ], emissiveFactor[ 1 ], emissiveFactor[ 2 ], LinearSRGBColorSpace ); - - } - - if ( materialDef.emissiveTexture !== undefined && materialType !== MeshBasicMaterial ) { - - pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture, SRGBColorSpace ) ); - - } - - return Promise.all( pending ).then( function () { - - const material = new materialType( materialParams ); - - if ( materialDef.name ) material.name = materialDef.name; - - assignExtrasToUserData( material, materialDef ); - - parser.associations.set( material, { materials: materialIndex } ); - - if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef ); - - return material; - - } ); - - } - - /** - * When Object3D instances are targeted by animation, they need unique names. - * - * @private - * @param {string} originalName - * @return {string} - */ - createUniqueName( originalName ) { - - const sanitizedName = PropertyBinding.sanitizeNodeName( originalName || '' ); - - if ( sanitizedName in this.nodeNamesUsed ) { - - return sanitizedName + '_' + ( ++ this.nodeNamesUsed[ sanitizedName ] ); - - } else { - - this.nodeNamesUsed[ sanitizedName ] = 0; - - return sanitizedName; - - } - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry - * - * Creates BufferGeometries from primitives. - * - * @private - * @param {Array<GLTF.Primitive>} primitives - * @return {Promise<Array<BufferGeometry>>} - */ - loadGeometries( primitives ) { - - const parser = this; - const extensions = this.extensions; - const cache = this.primitiveCache; - - function createDracoPrimitive( primitive ) { - - return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] - .decodePrimitive( primitive, parser ) - .then( function ( geometry ) { - - return addPrimitiveAttributes( geometry, primitive, parser ); - - } ); - - } - - const pending = []; - - for ( let i = 0, il = primitives.length; i < il; i ++ ) { - - const primitive = primitives[ i ]; - const cacheKey = createPrimitiveKey( primitive ); - - // See if we've already created this geometry - const cached = cache[ cacheKey ]; - - if ( cached ) { - - // Use the cached geometry if it exists - pending.push( cached.promise ); - - } else { - - let geometryPromise; - - if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) { - - // Use DRACO geometry if available - geometryPromise = createDracoPrimitive( primitive ); - - } else { - - // Otherwise create a new geometry - geometryPromise = addPrimitiveAttributes( new BufferGeometry(), primitive, parser ); - - } - - // Convert strip/fan primitives to triangles - - if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { - - geometryPromise = geometryPromise.then( geometry => toTrianglesDrawMode( geometry, TriangleStripDrawMode ) ); - - } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { - - geometryPromise = geometryPromise.then( geometry => toTrianglesDrawMode( geometry, TriangleFanDrawMode ) ); - - } - - // Cache this geometry - cache[ cacheKey ] = { primitive: primitive, promise: geometryPromise }; - - pending.push( geometryPromise ); - - } - - } - - return Promise.all( pending ); - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes - * - * @private - * @param {number} meshIndex - * @return {Promise<Group|Mesh|SkinnedMesh|Line|Points>} - */ - loadMesh( meshIndex ) { - - const parser = this; - const json = this.json; - const extensions = this.extensions; - - const meshDef = json.meshes[ meshIndex ]; - const primitives = meshDef.primitives; - - const pending = []; - - for ( let i = 0, il = primitives.length; i < il; i ++ ) { - - const material = primitives[ i ].material === undefined - ? createDefaultMaterial( this.cache ) - : this.getDependency( 'material', primitives[ i ].material ); - - pending.push( material ); - - } - - pending.push( parser.loadGeometries( primitives ) ); - - return Promise.all( pending ).then( async function ( results ) { - - const materials = results.slice( 0, results.length - 1 ); - const geometries = results[ results.length - 1 ]; - - const meshes = []; - - for ( let i = 0, il = geometries.length; i < il; i ++ ) { - - const geometry = geometries[ i ]; - const primitive = primitives[ i ]; - - // 1. create Mesh - - let mesh; - - const material = materials[ i ]; - - if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || - primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP || - primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN || - primitive.mode === undefined ) { - - const needsSkinning = meshDef.isSkinnedMesh === true; - const hasSkinningAttributes = geometry.hasAttribute( 'skinIndex' ) && geometry.hasAttribute( 'skinWeight' ); - - if ( needsSkinning && hasSkinningAttributes === false ) { - - console.warn( 'THREE.GLTFLoader: Missing skinIndex or skinWeight attributes. Skinning disabled.' ); - - } - - // .isSkinnedMesh isn't in glTF spec. See ._markDefs() - mesh = ( needsSkinning && hasSkinningAttributes ) - ? new SkinnedMesh( geometry, material ) - : new Mesh( geometry, material ); - - if ( mesh.isSkinnedMesh === true ) { - - // normalize skin weights to fix malformed assets (see #15319) - mesh.normalizeSkinWeights(); - - } - - } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { - - mesh = new LineSegments( geometry, material ); - - } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) { - - mesh = new Line( geometry, material ); - - } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) { - - mesh = new LineLoop( geometry, material ); - - } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) { - - mesh = new Points( geometry, material ); - - } else { - - throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode ); - - } - - if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) { - - updateMorphTargets( mesh, meshDef ); - - } - - mesh.name = parser.createUniqueName( meshDef.name || ( 'mesh_' + meshIndex ) ); - - assignExtrasToUserData( mesh, meshDef ); - - if ( primitive.extensions ) addUnknownExtensionsToUserData( extensions, mesh, primitive ); - - parser.assignFinalMaterial( mesh ); - - meshes.push( mesh ); - - } - - for ( let i = 0, il = meshes.length; i < il; i ++ ) { - - parser.associations.set( meshes[ i ], { - meshes: meshIndex, - primitives: i - } ); - - } - - if ( meshes.length === 1 ) { - - if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, meshes[ 0 ], meshDef ); - - return meshes[ 0 ]; - - } - - const group = new Group(); - - if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, group, meshDef ); - - parser.associations.set( group, { meshes: meshIndex } ); - - for ( let i = 0, il = meshes.length; i < il; i ++ ) { - - group.add( meshes[ i ] ); - - } - - return group; - - } ); - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras - * - * @private - * @param {number} cameraIndex - * @return {Promise<Camera>|undefined} - */ - loadCamera( cameraIndex ) { - - let camera; - const cameraDef = this.json.cameras[ cameraIndex ]; - const params = cameraDef[ cameraDef.type ]; - - if ( ! params ) { - - console.warn( 'THREE.GLTFLoader: Missing camera parameters.' ); - return; - - } - - if ( cameraDef.type === 'perspective' ) { - - camera = new PerspectiveCamera( MathUtils.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 ); - - } else if ( cameraDef.type === 'orthographic' ) { - - camera = new OrthographicCamera( - params.xmag, params.xmag, params.ymag, - params.ymag, params.znear, params.zfar ); - - } - - if ( cameraDef.name ) camera.name = this.createUniqueName( cameraDef.name ); - - assignExtrasToUserData( camera, cameraDef ); - - return Promise.resolve( camera ); - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins - * - * @private - * @param {number} skinIndex - * @return {Promise<Skeleton>} - */ - loadSkin( skinIndex ) { - - const skinDef = this.json.skins[ skinIndex ]; - - const pending = []; - - for ( let i = 0, il = skinDef.joints.length; i < il; i ++ ) { - - pending.push( this._loadNodeShallow( skinDef.joints[ i ] ) ); - - } - - if ( skinDef.inverseBindMatrices !== undefined ) { - - pending.push( this.getDependency( 'accessor', skinDef.inverseBindMatrices ) ); - - } else { - - pending.push( null ); - - } - - return Promise.all( pending ).then( function ( results ) { - - const inverseBindMatrices = results.pop(); - const jointNodes = results; - - // Note that bones (joint nodes) may or may not be in the - // scene graph at this time. - - const bones = []; - const boneInverses = []; - - for ( let i = 0, il = jointNodes.length; i < il; i ++ ) { - - const jointNode = jointNodes[ i ]; - - if ( jointNode ) { - - bones.push( jointNode ); - - const mat = new Matrix4(); - - if ( inverseBindMatrices !== null ) { - - mat.fromArray( inverseBindMatrices.array, i * 16 ); - - } - - boneInverses.push( mat ); - - } else { - - console.warn( 'THREE.GLTFLoader: Joint "%s" could not be found.', skinDef.joints[ i ] ); - - } - - } - - return new Skeleton( bones, boneInverses ); - - } ); - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations - * - * @private - * @param {number} animationIndex - * @return {Promise<AnimationClip>} - */ - loadAnimation( animationIndex ) { - - const json = this.json; - const parser = this; - - const animationDef = json.animations[ animationIndex ]; - const animationName = animationDef.name ? animationDef.name : 'animation_' + animationIndex; - - const pendingNodes = []; - const pendingInputAccessors = []; - const pendingOutputAccessors = []; - const pendingSamplers = []; - const pendingTargets = []; - - for ( let i = 0, il = animationDef.channels.length; i < il; i ++ ) { - - const channel = animationDef.channels[ i ]; - const sampler = animationDef.samplers[ channel.sampler ]; - const target = channel.target; - const name = target.node; - const input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input; - const output = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.output ] : sampler.output; - - if ( target.node === undefined ) continue; - - pendingNodes.push( this.getDependency( 'node', name ) ); - pendingInputAccessors.push( this.getDependency( 'accessor', input ) ); - pendingOutputAccessors.push( this.getDependency( 'accessor', output ) ); - pendingSamplers.push( sampler ); - pendingTargets.push( target ); - - } - - return Promise.all( [ - - Promise.all( pendingNodes ), - Promise.all( pendingInputAccessors ), - Promise.all( pendingOutputAccessors ), - Promise.all( pendingSamplers ), - Promise.all( pendingTargets ) - - ] ).then( function ( dependencies ) { - - const nodes = dependencies[ 0 ]; - const inputAccessors = dependencies[ 1 ]; - const outputAccessors = dependencies[ 2 ]; - const samplers = dependencies[ 3 ]; - const targets = dependencies[ 4 ]; - - const tracks = []; - - for ( let i = 0, il = nodes.length; i < il; i ++ ) { - - const node = nodes[ i ]; - const inputAccessor = inputAccessors[ i ]; - const outputAccessor = outputAccessors[ i ]; - const sampler = samplers[ i ]; - const target = targets[ i ]; - - if ( node === undefined ) continue; - - if ( node.updateMatrix ) { - - node.updateMatrix(); - - } - - const createdTracks = parser._createAnimationTracks( node, inputAccessor, outputAccessor, sampler, target ); - - if ( createdTracks ) { - - for ( let k = 0; k < createdTracks.length; k ++ ) { - - tracks.push( createdTracks[ k ] ); - - } - - } - - } - - const animation = new AnimationClip( animationName, undefined, tracks ); - - assignExtrasToUserData( animation, animationDef ); - - return animation; - - } ); - - } - - createNodeMesh( nodeIndex ) { - - const json = this.json; - const parser = this; - const nodeDef = json.nodes[ nodeIndex ]; - - if ( nodeDef.mesh === undefined ) return null; - - return parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) { - - const node = parser._getNodeRef( parser.meshCache, nodeDef.mesh, mesh ); - - // if weights are provided on the node, override weights on the mesh. - if ( nodeDef.weights !== undefined ) { - - node.traverse( function ( o ) { - - if ( ! o.isMesh ) return; - - for ( let i = 0, il = nodeDef.weights.length; i < il; i ++ ) { - - o.morphTargetInfluences[ i ] = nodeDef.weights[ i ]; - - } - - } ); - - } - - return node; - - } ); - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy - * - * @private - * @param {number} nodeIndex - * @return {Promise<Object3D>} - */ - loadNode( nodeIndex ) { - - const json = this.json; - const parser = this; - - const nodeDef = json.nodes[ nodeIndex ]; - - const nodePending = parser._loadNodeShallow( nodeIndex ); - - const childPending = []; - const childrenDef = nodeDef.children || []; - - for ( let i = 0, il = childrenDef.length; i < il; i ++ ) { - - childPending.push( parser.getDependency( 'node', childrenDef[ i ] ) ); - - } - - const skeletonPending = nodeDef.skin === undefined - ? Promise.resolve( null ) - : parser.getDependency( 'skin', nodeDef.skin ); - - return Promise.all( [ - nodePending, - Promise.all( childPending ), - skeletonPending - ] ).then( function ( results ) { - - const node = results[ 0 ]; - const children = results[ 1 ]; - const skeleton = results[ 2 ]; - - if ( skeleton !== null ) { - - // This full traverse should be fine because - // child glTF nodes have not been added to this node yet. - node.traverse( function ( mesh ) { - - if ( ! mesh.isSkinnedMesh ) return; - - mesh.bind( skeleton, _identityMatrix ); - - } ); - - } - - for ( let i = 0, il = children.length; i < il; i ++ ) { - - node.add( children[ i ] ); - - } - - // Reconstruct pivot from container pattern created by GLTFExporter - // The container has position+pivot, rotation, scale; child has -pivot offset and mesh - if ( node.userData.pivot !== undefined && children.length > 0 ) { - - const pivot = node.userData.pivot; - const pivotChild = children[ 0 ]; - - // Set pivot on container and adjust transforms - node.pivot = new Vector3().fromArray( pivot ); - - // Adjust container position: stored as position + pivot, so subtract pivot - node.position.x -= pivot[ 0 ]; - node.position.y -= pivot[ 1 ]; - node.position.z -= pivot[ 2 ]; - - // Remove the child's -pivot offset since pivot now handles it - pivotChild.position.set( 0, 0, 0 ); - - delete node.userData.pivot; - - } - - return node; - - } ); - - } - - // ._loadNodeShallow() parses a single node. - // skin and child nodes are created and added in .loadNode() (no '_' prefix). - _loadNodeShallow( nodeIndex ) { - - const json = this.json; - const extensions = this.extensions; - const parser = this; - - // This method is called from .loadNode() and .loadSkin(). - // Cache a node to avoid duplication. - - if ( this.nodeCache[ nodeIndex ] !== undefined ) { - - return this.nodeCache[ nodeIndex ]; - - } - - const nodeDef = json.nodes[ nodeIndex ]; - - // reserve node's name before its dependencies, so the root has the intended name. - const nodeName = nodeDef.name ? parser.createUniqueName( nodeDef.name ) : ''; - - const pending = []; - - const meshPromise = parser._invokeOne( function ( ext ) { - - return ext.createNodeMesh && ext.createNodeMesh( nodeIndex ); - - } ); - - if ( meshPromise ) { - - pending.push( meshPromise ); - - } - - if ( nodeDef.camera !== undefined ) { - - pending.push( parser.getDependency( 'camera', nodeDef.camera ).then( function ( camera ) { - - return parser._getNodeRef( parser.cameraCache, nodeDef.camera, camera ); - - } ) ); - - } - - parser._invokeAll( function ( ext ) { - - return ext.createNodeAttachment && ext.createNodeAttachment( nodeIndex ); - - } ).forEach( function ( promise ) { - - pending.push( promise ); - - } ); - - this.nodeCache[ nodeIndex ] = Promise.all( pending ).then( function ( objects ) { - - let node; - - // .isBone isn't in glTF spec. See ._markDefs - if ( nodeDef.isBone === true ) { - - node = new Bone(); - - } else if ( objects.length > 1 ) { - - node = new Group(); - - } else if ( objects.length === 1 ) { - - node = objects[ 0 ]; - - } else { - - node = new Object3D(); - - } - - if ( node !== objects[ 0 ] ) { - - for ( let i = 0, il = objects.length; i < il; i ++ ) { - - node.add( objects[ i ] ); - - } - - } - - if ( nodeDef.name ) { - - node.userData.name = nodeDef.name; - node.name = nodeName; - - } - - assignExtrasToUserData( node, nodeDef ); - - if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef ); - - if ( nodeDef.matrix !== undefined ) { - - const matrix = new Matrix4(); - matrix.fromArray( nodeDef.matrix ); - node.applyMatrix4( matrix ); - - } else { - - if ( nodeDef.translation !== undefined ) { - - node.position.fromArray( nodeDef.translation ); - - } - - if ( nodeDef.rotation !== undefined ) { - - node.quaternion.fromArray( nodeDef.rotation ); - - } - - if ( nodeDef.scale !== undefined ) { - - node.scale.fromArray( nodeDef.scale ); - - } - - } - - if ( ! parser.associations.has( node ) ) { - - parser.associations.set( node, {} ); - - } else if ( nodeDef.mesh !== undefined && parser.meshCache.refs[ nodeDef.mesh ] > 1 ) { - - const mapping = parser.associations.get( node ); - parser.associations.set( node, { ...mapping } ); - - } - - parser.associations.get( node ).nodes = nodeIndex; - - return node; - - } ); - - return this.nodeCache[ nodeIndex ]; - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes - * - * @private - * @param {number} sceneIndex - * @return {Promise<Group>} - */ - loadScene( sceneIndex ) { - - const extensions = this.extensions; - const sceneDef = this.json.scenes[ sceneIndex ]; - const parser = this; - - // Loader returns Group, not Scene. - // See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172 - const scene = new Group(); - if ( sceneDef.name ) scene.name = parser.createUniqueName( sceneDef.name ); - - assignExtrasToUserData( scene, sceneDef ); - - if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef ); - - const nodeIds = sceneDef.nodes || []; - - const pending = []; - - for ( let i = 0, il = nodeIds.length; i < il; i ++ ) { - - pending.push( parser.getDependency( 'node', nodeIds[ i ] ) ); - - } - - return Promise.all( pending ).then( function ( nodes ) { - - for ( let i = 0, il = nodes.length; i < il; i ++ ) { - - const node = nodes[ i ]; - - // If the node already has a parent, it means it's being reused across multiple scenes. - // Clone it to avoid the second scene's add() removing it from the first scene. - // See: https://github.com/mrdoob/three.js/issues/27993 - if ( node.parent !== null ) { - - scene.add( clone( node ) ); - - } else { - - scene.add( node ); - - } - - } - - // Removes dangling associations, associations that reference a node that - // didn't make it into the scene. - const reduceAssociations = ( node ) => { - - const reducedAssociations = new Map(); - - for ( const [ key, value ] of parser.associations ) { - - if ( key instanceof Material || key instanceof Texture ) { - - reducedAssociations.set( key, value ); - - } - - } - - node.traverse( ( node ) => { - - const mappings = parser.associations.get( node ); - - if ( mappings != null ) { - - reducedAssociations.set( node, mappings ); - - } - - } ); - - return reducedAssociations; - - }; - - parser.associations = reduceAssociations( scene ); - - return scene; - - } ); - - } - - _createAnimationTracks( node, inputAccessor, outputAccessor, sampler, target ) { - - const tracks = []; - - const targetName = node.name ? node.name : node.uuid; - const targetNames = []; - - function collectMorphTargets( object ) { - - if ( object.morphTargetInfluences ) { - - targetNames.push( object.name ? object.name : object.uuid ); - - } - - } - - - if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { - - collectMorphTargets( node ); - - // for multi-primitive meshes, the node is a Group containing the sub-meshes - - if ( node.isGroup ) { - - node.children.forEach( collectMorphTargets ); - - } - - } else { - - targetNames.push( targetName ); - - } - - let TypedKeyframeTrack; - - switch ( PATH_PROPERTIES[ target.path ] ) { - - case PATH_PROPERTIES.weights: - - TypedKeyframeTrack = NumberKeyframeTrack; - break; - - case PATH_PROPERTIES.rotation: - - TypedKeyframeTrack = QuaternionKeyframeTrack; - break; - - case PATH_PROPERTIES.translation: - case PATH_PROPERTIES.scale: - - TypedKeyframeTrack = VectorKeyframeTrack; - break; - - default: - - switch ( outputAccessor.itemSize ) { - - case 1: - TypedKeyframeTrack = NumberKeyframeTrack; - break; - case 2: - case 3: - default: - TypedKeyframeTrack = VectorKeyframeTrack; - break; - - } - - break; - - } - - const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : InterpolateLinear; - - - const outputArray = this._getArrayFromAccessor( outputAccessor ); - - for ( let j = 0, jl = targetNames.length; j < jl; j ++ ) { - - const track = new TypedKeyframeTrack( - targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], - inputAccessor.array, - outputArray, - interpolation - ); - - // Override interpolation with custom factory method. - if ( sampler.interpolation === 'CUBICSPLINE' ) { - - this._createCubicSplineTrackInterpolant( track ); - - } - - tracks.push( track ); - - } - - return tracks; - - } - - _getArrayFromAccessor( accessor ) { - - let outputArray = accessor.array; - - if ( accessor.normalized ) { - - const scale = getNormalizedComponentScale( outputArray.constructor ); - const scaled = new Float32Array( outputArray.length ); - - for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) { - - scaled[ j ] = outputArray[ j ] * scale; - - } - - outputArray = scaled; - - } - - return outputArray; - - } - - _createCubicSplineTrackInterpolant( track ) { - - track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) { - - // A CUBICSPLINE keyframe in glTF has three output values for each input value, - // representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize() - // must be divided by three to get the interpolant's sampleSize argument. - - const interpolantType = ( this instanceof QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant : GLTFCubicSplineInterpolant; - - return new interpolantType( this.times, this.values, this.getValueSize() / 3, result ); - - }; - - // Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants. - track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true; - - } - -} - -/** - * - * @private - * @param {BufferGeometry} geometry - * @param {GLTF.Primitive} primitiveDef - * @param {GLTFParser} parser - */ -function computeBounds( geometry, primitiveDef, parser ) { - - const attributes = primitiveDef.attributes; - - const box = new Box3(); - - if ( attributes.POSITION !== undefined ) { - - const accessor = parser.json.accessors[ attributes.POSITION ]; - - const min = accessor.min; - const max = accessor.max; - - // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. - - if ( min !== undefined && max !== undefined ) { - - box.set( - new Vector3( min[ 0 ], min[ 1 ], min[ 2 ] ), - new Vector3( max[ 0 ], max[ 1 ], max[ 2 ] ) - ); - - if ( accessor.normalized ) { - - const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); - box.min.multiplyScalar( boxScale ); - box.max.multiplyScalar( boxScale ); - - } - - } else { - - console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); - - return; - - } - - } else { - - return; - - } - - const targets = primitiveDef.targets; - - if ( targets !== undefined ) { - - const maxDisplacement = new Vector3(); - const vector = new Vector3(); - - for ( let i = 0, il = targets.length; i < il; i ++ ) { - - const target = targets[ i ]; - - if ( target.POSITION !== undefined ) { - - const accessor = parser.json.accessors[ target.POSITION ]; - const min = accessor.min; - const max = accessor.max; - - // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. - - if ( min !== undefined && max !== undefined ) { - - // we need to get max of absolute components because target weight is [-1,1] - vector.setX( Math.max( Math.abs( min[ 0 ] ), Math.abs( max[ 0 ] ) ) ); - vector.setY( Math.max( Math.abs( min[ 1 ] ), Math.abs( max[ 1 ] ) ) ); - vector.setZ( Math.max( Math.abs( min[ 2 ] ), Math.abs( max[ 2 ] ) ) ); - - - if ( accessor.normalized ) { - - const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); - vector.multiplyScalar( boxScale ); - - } - - // Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative - // to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets - // are used to implement key-frame animations and as such only two are active at a time - this results in very large - // boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size. - maxDisplacement.max( vector ); - - } else { - - console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); - - } - - } - - } - - // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets. - box.expandByVector( maxDisplacement ); - - } - - geometry.boundingBox = box; - - const sphere = new Sphere(); - - box.getCenter( sphere.center ); - sphere.radius = box.min.distanceTo( box.max ) / 2; - - geometry.boundingSphere = sphere; - -} - -/** - * - * @private - * @param {BufferGeometry} geometry - * @param {GLTF.Primitive} primitiveDef - * @param {GLTFParser} parser - * @return {Promise<BufferGeometry>} - */ -function addPrimitiveAttributes( geometry, primitiveDef, parser ) { - - const attributes = primitiveDef.attributes; - - const pending = []; - - function assignAttributeAccessor( accessorIndex, attributeName ) { - - return parser.getDependency( 'accessor', accessorIndex ) - .then( function ( accessor ) { - - geometry.setAttribute( attributeName, accessor ); - - } ); - - } - - for ( const gltfAttributeName in attributes ) { - - const threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase(); - - // Skip attributes already provided by e.g. Draco extension. - if ( threeAttributeName in geometry.attributes ) continue; - - pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) ); - - } - - if ( primitiveDef.indices !== undefined && ! geometry.index ) { - - const accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) { - - geometry.setIndex( accessor ); - - } ); - - pending.push( accessor ); - - } - - if ( ColorManagement.workingColorSpace !== LinearSRGBColorSpace && 'COLOR_0' in attributes ) { - - console.warn( `THREE.GLTFLoader: Converting vertex colors from "srgb-linear" to "${ColorManagement.workingColorSpace}" not supported.` ); - - } - - assignExtrasToUserData( geometry, primitiveDef ); - - computeBounds( geometry, primitiveDef, parser ); - - return Promise.all( pending ).then( function () { - - return primitiveDef.targets !== undefined - ? addMorphTargets( geometry, primitiveDef.targets, parser ) - : geometry; - - } ); - -} - -/** - * Loader result of `GLTFLoader`. - * - * @typedef {Object} GLTFLoader~LoadObject - * @property {Array<AnimationClip>} animations - An array of animation clips. - * @property {Object} asset - Meta data about the loaded asset. - * @property {Array<Camera>} cameras - An array of cameras. - * @property {GLTFParser} parser - A reference to the internal parser. - * @property {Group} scene - The default scene. - * @property {Array<Group>} scenes - glTF assets might define multiple scenes. - * @property {Object} userData - Additional data. - **/ - -export { GLTFLoader }; diff --git a/public/bluebey-studio/vendor/three/examples/jsm/utils/BufferGeometryUtils.js b/public/bluebey-studio/vendor/three/examples/jsm/utils/BufferGeometryUtils.js deleted file mode 100644 index a167e11..0000000 --- a/public/bluebey-studio/vendor/three/examples/jsm/utils/BufferGeometryUtils.js +++ /dev/null @@ -1,1501 +0,0 @@ -import { - BufferAttribute, - BufferGeometry, - Float32BufferAttribute, - InstancedBufferAttribute, - InterleavedBuffer, - InterleavedBufferAttribute, - TriangleFanDrawMode, - TriangleStripDrawMode, - TrianglesDrawMode, - Vector3, -} from 'three'; - -/** - * @module BufferGeometryUtils - * @three_import import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js'; - */ - -/** - * Computes vertex tangents using the MikkTSpace algorithm. MikkTSpace generates the same tangents consistently, - * and is used in most modelling tools and normal map bakers. Use MikkTSpace for materials with normal maps, - * because inconsistent tangents may lead to subtle visual issues in the normal map, particularly around mirrored - * UV seams. - * - * In comparison to this method, {@link BufferGeometry#computeTangents} (a custom algorithm) generates tangents that - * probably will not match the tangents in other software. The custom algorithm is sufficient for general use with a - * custom material, and may be faster than MikkTSpace. - * - * Returns the original BufferGeometry. Indexed geometries will be de-indexed. Requires position, normal, and uv attributes. - * - * @param {BufferGeometry} geometry - The geometry to compute tangents for. - * @param {Object} MikkTSpace - Instance of `examples/jsm/libs/mikktspace.module.js`, or `mikktspace` npm package. - * Await `MikkTSpace.ready` before use. - * @param {boolean} [negateSign=true] - Whether to negate the sign component (.w) of each tangent. - * Required for normal map conventions in some formats, including glTF. - * @return {BufferGeometry} The updated geometry. - */ -function computeMikkTSpaceTangents( geometry, MikkTSpace, negateSign = true ) { - - if ( ! MikkTSpace || ! MikkTSpace.isReady ) { - - throw new Error( 'THREE.BufferGeometryUtils: Initialized MikkTSpace library required.' ); - - } - - if ( ! geometry.hasAttribute( 'position' ) || ! geometry.hasAttribute( 'normal' ) || ! geometry.hasAttribute( 'uv' ) ) { - - throw new Error( 'THREE.BufferGeometryUtils: Tangents require "position", "normal", and "uv" attributes.' ); - - } - - function getAttributeArray( attribute ) { - - if ( attribute.normalized || attribute.isInterleavedBufferAttribute ) { - - const dstArray = new Float32Array( attribute.count * attribute.itemSize ); - - for ( let i = 0, j = 0; i < attribute.count; i ++ ) { - - dstArray[ j ++ ] = attribute.getX( i ); - dstArray[ j ++ ] = attribute.getY( i ); - - if ( attribute.itemSize > 2 ) { - - dstArray[ j ++ ] = attribute.getZ( i ); - - } - - } - - return dstArray; - - } - - if ( attribute.array instanceof Float32Array ) { - - return attribute.array; - - } - - return new Float32Array( attribute.array ); - - } - - // MikkTSpace algorithm requires non-indexed input. - - const _geometry = geometry.index ? geometry.toNonIndexed() : geometry; - - // Compute vertex tangents. - - const tangents = MikkTSpace.generateTangents( - - getAttributeArray( _geometry.attributes.position ), - getAttributeArray( _geometry.attributes.normal ), - getAttributeArray( _geometry.attributes.uv ) - - ); - - // Texture coordinate convention of glTF differs from the apparent - // default of the MikkTSpace library; .w component must be flipped. - - if ( negateSign ) { - - for ( let i = 3; i < tangents.length; i += 4 ) { - - tangents[ i ] *= - 1; - - } - - } - - // - - _geometry.setAttribute( 'tangent', new BufferAttribute( tangents, 4 ) ); - - if ( geometry !== _geometry ) { - - geometry.copy( _geometry ); - - } - - return geometry; - -} - -/** - * Merges a set of geometries into a single instance. All geometries must have compatible attributes. - * - * @param {Array<BufferGeometry>} geometries - The geometries to merge. - * @param {boolean} [useGroups=false] - Whether to use groups or not. - * @return {?BufferGeometry} The merged geometry. Returns `null` if the merge does not succeed. - */ -function mergeGeometries( geometries, useGroups = false ) { - - const isIndexed = geometries[ 0 ].index !== null; - - const attributesUsed = new Set( Object.keys( geometries[ 0 ].attributes ) ); - const morphAttributesUsed = new Set( Object.keys( geometries[ 0 ].morphAttributes ) ); - - const attributes = {}; - const morphAttributes = {}; - - const morphTargetsRelative = geometries[ 0 ].morphTargetsRelative; - - const mergedGeometry = new BufferGeometry(); - - let offset = 0; - - for ( let i = 0; i < geometries.length; ++ i ) { - - const geometry = geometries[ i ]; - let attributesCount = 0; - - // ensure that all geometries are indexed, or none - - if ( isIndexed !== ( geometry.index !== null ) ) { - - console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure index attribute exists among all geometries, or in none of them.' ); - return null; - - } - - // gather attributes, exit early if they're different - - for ( const name in geometry.attributes ) { - - if ( ! attributesUsed.has( name ) ) { - - console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure "' + name + '" attribute exists among all geometries, or in none of them.' ); - return null; - - } - - if ( attributes[ name ] === undefined ) attributes[ name ] = []; - - attributes[ name ].push( geometry.attributes[ name ] ); - - attributesCount ++; - - } - - // ensure geometries have the same number of attributes - - if ( attributesCount !== attributesUsed.size ) { - - console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. Make sure all geometries have the same number of attributes.' ); - return null; - - } - - // gather morph attributes, exit early if they're different - - if ( morphTargetsRelative !== geometry.morphTargetsRelative ) { - - console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. .morphTargetsRelative must be consistent throughout all geometries.' ); - return null; - - } - - for ( const name in geometry.morphAttributes ) { - - if ( ! morphAttributesUsed.has( name ) ) { - - console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. .morphAttributes must be consistent throughout all geometries.' ); - return null; - - } - - if ( morphAttributes[ name ] === undefined ) morphAttributes[ name ] = []; - - morphAttributes[ name ].push( geometry.morphAttributes[ name ] ); - - } - - if ( useGroups ) { - - let count; - - if ( isIndexed ) { - - count = geometry.index.count; - - } else if ( geometry.attributes.position !== undefined ) { - - count = geometry.attributes.position.count; - - } else { - - console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i + '. The geometry must have either an index or a position attribute' ); - return null; - - } - - mergedGeometry.addGroup( offset, count, i ); - - offset += count; - - } - - } - - // merge indices - - if ( isIndexed ) { - - let indexOffset = 0; - const mergedIndex = []; - - for ( let i = 0; i < geometries.length; ++ i ) { - - const index = geometries[ i ].index; - - for ( let j = 0; j < index.count; ++ j ) { - - mergedIndex.push( index.getX( j ) + indexOffset ); - - } - - indexOffset += geometries[ i ].attributes.position.count; - - } - - mergedGeometry.setIndex( mergedIndex ); - - } - - // merge attributes - - for ( const name in attributes ) { - - const mergedAttribute = mergeAttributes( attributes[ name ] ); - - if ( ! mergedAttribute ) { - - console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the ' + name + ' attribute.' ); - return null; - - } - - mergedGeometry.setAttribute( name, mergedAttribute ); - - } - - // merge morph attributes - - for ( const name in morphAttributes ) { - - const numMorphTargets = morphAttributes[ name ][ 0 ].length; - if ( numMorphTargets === 0 ) continue; - - mergedGeometry.morphAttributes = mergedGeometry.morphAttributes || {}; - mergedGeometry.morphAttributes[ name ] = []; - - for ( let i = 0; i < numMorphTargets; ++ i ) { - - const morphAttributesToMerge = []; - - for ( let j = 0; j < morphAttributes[ name ].length; ++ j ) { - - morphAttributesToMerge.push( morphAttributes[ name ][ j ][ i ] ); - - } - - const mergedMorphAttribute = mergeAttributes( morphAttributesToMerge ); - - if ( ! mergedMorphAttribute ) { - - console.error( 'THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the ' + name + ' morphAttribute.' ); - return null; - - } - - mergedGeometry.morphAttributes[ name ].push( mergedMorphAttribute ); - - } - - } - - return mergedGeometry; - -} - -/** - * Merges a set of attributes into a single instance. All attributes must have compatible properties and types. - * Instances of {@link InterleavedBufferAttribute} are not supported. - * - * @param {Array<BufferAttribute>} attributes - The attributes to merge. - * @return {?BufferAttribute} The merged attribute. Returns `null` if the merge does not succeed. - */ -function mergeAttributes( attributes ) { - - let TypedArray; - let itemSize; - let normalized; - let gpuType = - 1; - let arrayLength = 0; - - for ( let i = 0; i < attributes.length; ++ i ) { - - const attribute = attributes[ i ]; - - if ( TypedArray === undefined ) TypedArray = attribute.array.constructor; - if ( TypedArray !== attribute.array.constructor ) { - - console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.array must be of consistent array types across matching attributes.' ); - return null; - - } - - if ( itemSize === undefined ) itemSize = attribute.itemSize; - if ( itemSize !== attribute.itemSize ) { - - console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.itemSize must be consistent across matching attributes.' ); - return null; - - } - - if ( normalized === undefined ) normalized = attribute.normalized; - if ( normalized !== attribute.normalized ) { - - console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.normalized must be consistent across matching attributes.' ); - return null; - - } - - if ( gpuType === - 1 ) gpuType = attribute.gpuType; - if ( gpuType !== attribute.gpuType ) { - - console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.gpuType must be consistent across matching attributes.' ); - return null; - - } - - arrayLength += attribute.count * itemSize; - - } - - const array = new TypedArray( arrayLength ); - const result = new BufferAttribute( array, itemSize, normalized ); - let offset = 0; - - for ( let i = 0; i < attributes.length; ++ i ) { - - const attribute = attributes[ i ]; - if ( attribute.isInterleavedBufferAttribute ) { - - const tupleOffset = offset / itemSize; - for ( let j = 0, l = attribute.count; j < l; j ++ ) { - - for ( let c = 0; c < itemSize; c ++ ) { - - const value = attribute.getComponent( j, c ); - result.setComponent( j + tupleOffset, c, value ); - - } - - } - - } else { - - array.set( attribute.array, offset ); - - } - - offset += attribute.count * itemSize; - - } - - if ( gpuType !== undefined ) { - - result.gpuType = gpuType; - - } - - return result; - -} - -/** - * Performs a deep clone of the given buffer attribute. - * - * @param {BufferAttribute} attribute - The attribute to clone. - * @return {BufferAttribute} The cloned attribute. - */ -function deepCloneAttribute( attribute ) { - - if ( attribute.isInstancedInterleavedBufferAttribute || attribute.isInterleavedBufferAttribute ) { - - return deinterleaveAttribute( attribute ); - - } - - if ( attribute.isInstancedBufferAttribute ) { - - return new InstancedBufferAttribute().copy( attribute ); - - } - - return new BufferAttribute().copy( attribute ); - -} - -/** - * Interleaves a set of attributes and returns a new array of corresponding attributes that share a - * single {@link InterleavedBuffer} instance. All attributes must have compatible types. - * - * @param {Array<BufferAttribute>} attributes - The attributes to interleave. - * @return {?Array<InterleavedBufferAttribute>} An array of interleaved attributes. If interleave does not succeed, the method returns `null`. - */ -function interleaveAttributes( attributes ) { - - // Interleaves the provided attributes into an InterleavedBuffer and returns - // a set of InterleavedBufferAttributes for each attribute - let TypedArray; - let arrayLength = 0; - let stride = 0; - - // calculate the length and type of the interleavedBuffer - for ( let i = 0, l = attributes.length; i < l; ++ i ) { - - const attribute = attributes[ i ]; - - if ( TypedArray === undefined ) TypedArray = attribute.array.constructor; - if ( TypedArray !== attribute.array.constructor ) { - - console.error( 'AttributeBuffers of different types cannot be interleaved' ); - return null; - - } - - arrayLength += attribute.array.length; - stride += attribute.itemSize; - - } - - // Create the set of buffer attributes - const interleavedBuffer = new InterleavedBuffer( new TypedArray( arrayLength ), stride ); - let offset = 0; - const res = []; - const getters = [ 'getX', 'getY', 'getZ', 'getW' ]; - const setters = [ 'setX', 'setY', 'setZ', 'setW' ]; - - for ( let j = 0, l = attributes.length; j < l; j ++ ) { - - const attribute = attributes[ j ]; - const itemSize = attribute.itemSize; - const count = attribute.count; - const iba = new InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, attribute.normalized ); - res.push( iba ); - - offset += itemSize; - - // Move the data for each attribute into the new interleavedBuffer - // at the appropriate offset - for ( let c = 0; c < count; c ++ ) { - - for ( let k = 0; k < itemSize; k ++ ) { - - iba[ setters[ k ] ]( c, attribute[ getters[ k ] ]( c ) ); - - } - - } - - } - - return res; - -} - -/** - * Returns a new, non-interleaved version of the given attribute. - * - * @param {InterleavedBufferAttribute} attribute - The interleaved attribute. - * @return {BufferAttribute} The non-interleaved attribute. - */ -function deinterleaveAttribute( attribute ) { - - const cons = attribute.data.array.constructor; - const count = attribute.count; - const itemSize = attribute.itemSize; - const normalized = attribute.normalized; - - const array = new cons( count * itemSize ); - let newAttribute; - if ( attribute.isInstancedInterleavedBufferAttribute ) { - - newAttribute = new InstancedBufferAttribute( array, itemSize, normalized, attribute.meshPerAttribute ); - - } else { - - newAttribute = new BufferAttribute( array, itemSize, normalized ); - - } - - for ( let i = 0; i < count; i ++ ) { - - newAttribute.setX( i, attribute.getX( i ) ); - - if ( itemSize >= 2 ) { - - newAttribute.setY( i, attribute.getY( i ) ); - - } - - if ( itemSize >= 3 ) { - - newAttribute.setZ( i, attribute.getZ( i ) ); - - } - - if ( itemSize >= 4 ) { - - newAttribute.setW( i, attribute.getW( i ) ); - - } - - } - - return newAttribute; - -} - -/** - * Deinterleaves all attributes on the given geometry. - * - * @param {BufferGeometry} geometry - The geometry to deinterleave. - */ -function deinterleaveGeometry( geometry ) { - - const attributes = geometry.attributes; - const morphTargets = geometry.morphTargets; - const attrMap = new Map(); - - for ( const key in attributes ) { - - const attr = attributes[ key ]; - if ( attr.isInterleavedBufferAttribute ) { - - if ( ! attrMap.has( attr ) ) { - - attrMap.set( attr, deinterleaveAttribute( attr ) ); - - } - - attributes[ key ] = attrMap.get( attr ); - - } - - } - - for ( const key in morphTargets ) { - - const attr = morphTargets[ key ]; - if ( attr.isInterleavedBufferAttribute ) { - - if ( ! attrMap.has( attr ) ) { - - attrMap.set( attr, deinterleaveAttribute( attr ) ); - - } - - morphTargets[ key ] = attrMap.get( attr ); - - } - - } - -} - -/** - * Returns the amount of bytes used by all attributes to represent the geometry. - * - * @param {BufferGeometry} geometry - The geometry. - * @return {number} The estimate bytes used. - */ -function estimateBytesUsed( geometry ) { - - // Return the estimated memory used by this geometry in bytes - // Calculate using itemSize, count, and BYTES_PER_ELEMENT to account - // for InterleavedBufferAttributes. - let mem = 0; - for ( const name in geometry.attributes ) { - - const attr = geometry.getAttribute( name ); - mem += attr.count * attr.itemSize * attr.array.BYTES_PER_ELEMENT; - - } - - const indices = geometry.getIndex(); - mem += indices ? indices.count * indices.itemSize * indices.array.BYTES_PER_ELEMENT : 0; - return mem; - -} - -/** - * Returns a new geometry with vertices for which all similar vertex attributes (within tolerance) are merged. - * - * @param {BufferGeometry} geometry - The geometry to merge vertices for. - * @param {number} [tolerance=1e-4] - The tolerance value. - * @return {BufferGeometry} - The new geometry with merged vertices. - */ -function mergeVertices( geometry, tolerance = 1e-4 ) { - - tolerance = Math.max( tolerance, Number.EPSILON ); - - // Generate an index buffer if the geometry doesn't have one, or optimize it - // if it's already available. - const hashToIndex = {}; - const indices = geometry.getIndex(); - const positions = geometry.getAttribute( 'position' ); - const vertexCount = indices ? indices.count : positions.count; - - // next value for triangle indices - let nextIndex = 0; - - // attributes and new attribute arrays - const attributeNames = Object.keys( geometry.attributes ); - const tmpAttributes = {}; - const tmpMorphAttributes = {}; - const newIndices = []; - const getters = [ 'getX', 'getY', 'getZ', 'getW' ]; - const setters = [ 'setX', 'setY', 'setZ', 'setW' ]; - - // Initialize the arrays, allocating space conservatively. Extra - // space will be trimmed in the last step. - for ( let i = 0, l = attributeNames.length; i < l; i ++ ) { - - const name = attributeNames[ i ]; - const attr = geometry.attributes[ name ]; - - tmpAttributes[ name ] = new attr.constructor( - new attr.array.constructor( attr.count * attr.itemSize ), - attr.itemSize, - attr.normalized - ); - - const morphAttributes = geometry.morphAttributes[ name ]; - if ( morphAttributes ) { - - if ( ! tmpMorphAttributes[ name ] ) tmpMorphAttributes[ name ] = []; - morphAttributes.forEach( ( morphAttr, i ) => { - - const array = new morphAttr.array.constructor( morphAttr.count * morphAttr.itemSize ); - tmpMorphAttributes[ name ][ i ] = new morphAttr.constructor( array, morphAttr.itemSize, morphAttr.normalized ); - - } ); - - } - - } - - // convert the error tolerance to an amount of decimal places to truncate to - const halfTolerance = tolerance * 0.5; - const exponent = Math.log10( 1 / tolerance ); - const hashMultiplier = Math.pow( 10, exponent ); - const hashAdditive = halfTolerance * hashMultiplier; - for ( let i = 0; i < vertexCount; i ++ ) { - - const index = indices ? indices.getX( i ) : i; - - // Generate a hash for the vertex attributes at the current index 'i' - let hash = ''; - for ( let j = 0, l = attributeNames.length; j < l; j ++ ) { - - const name = attributeNames[ j ]; - const attribute = geometry.getAttribute( name ); - const itemSize = attribute.itemSize; - - for ( let k = 0; k < itemSize; k ++ ) { - - // Math.trunc() preserves the full Number range, ~~ would wrap to int32 - hash += `${ Math.trunc( attribute[ getters[ k ] ]( index ) * hashMultiplier + hashAdditive ) },`; - - } - - } - - // Add another reference to the vertex if it's already - // used by another index - if ( hash in hashToIndex ) { - - newIndices.push( hashToIndex[ hash ] ); - - } else { - - // copy data to the new index in the temporary attributes - for ( let j = 0, l = attributeNames.length; j < l; j ++ ) { - - const name = attributeNames[ j ]; - const attribute = geometry.getAttribute( name ); - const morphAttributes = geometry.morphAttributes[ name ]; - const itemSize = attribute.itemSize; - const newArray = tmpAttributes[ name ]; - const newMorphArrays = tmpMorphAttributes[ name ]; - - for ( let k = 0; k < itemSize; k ++ ) { - - const getterFunc = getters[ k ]; - const setterFunc = setters[ k ]; - newArray[ setterFunc ]( nextIndex, attribute[ getterFunc ]( index ) ); - - if ( morphAttributes ) { - - for ( let m = 0, ml = morphAttributes.length; m < ml; m ++ ) { - - newMorphArrays[ m ][ setterFunc ]( nextIndex, morphAttributes[ m ][ getterFunc ]( index ) ); - - } - - } - - } - - } - - hashToIndex[ hash ] = nextIndex; - newIndices.push( nextIndex ); - nextIndex ++; - - } - - } - - // generate result BufferGeometry - const result = geometry.clone(); - for ( const name in geometry.attributes ) { - - const tmpAttribute = tmpAttributes[ name ]; - - result.setAttribute( name, new tmpAttribute.constructor( - tmpAttribute.array.slice( 0, nextIndex * tmpAttribute.itemSize ), - tmpAttribute.itemSize, - tmpAttribute.normalized, - ) ); - - if ( ! ( name in tmpMorphAttributes ) ) continue; - - for ( let j = 0; j < tmpMorphAttributes[ name ].length; j ++ ) { - - const tmpMorphAttribute = tmpMorphAttributes[ name ][ j ]; - - result.morphAttributes[ name ][ j ] = new tmpMorphAttribute.constructor( - tmpMorphAttribute.array.slice( 0, nextIndex * tmpMorphAttribute.itemSize ), - tmpMorphAttribute.itemSize, - tmpMorphAttribute.normalized, - ); - - } - - } - - // indices - - result.setIndex( newIndices ); - - return result; - -} - -/** - * Converts the given geometry to the `TrianglesDrawMode` draw mode, which - * corresponds to the `gl.TRIANGLES` primitive in WebGL. The conversion only - * rewrites the index, so the geometry is modified in place and returned. - * - * @param {BufferGeometry} geometry - The geometry to convert. - * @param {number} drawMode - The current draw mode. - * @return {BufferGeometry} The converted geometry using `TrianglesDrawMode`. - */ -function toTrianglesDrawMode( geometry, drawMode ) { - - if ( drawMode === TrianglesDrawMode ) { - - console.warn( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Geometry already defined as triangles.' ); - return geometry; - - } - - if ( drawMode === TriangleFanDrawMode || drawMode === TriangleStripDrawMode ) { - - let index = geometry.getIndex(); - - // generate index if not present - - if ( index === null ) { - - const indices = []; - - const position = geometry.getAttribute( 'position' ); - - if ( position !== undefined ) { - - for ( let i = 0; i < position.count; i ++ ) { - - indices.push( i ); - - } - - geometry.setIndex( indices ); - index = geometry.getIndex(); - - } else { - - console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.' ); - return geometry; - - } - - } - - // - - const numberOfTriangles = index.count - 2; - const newIndices = []; - - if ( drawMode === TriangleFanDrawMode ) { - - // gl.TRIANGLE_FAN - - for ( let i = 1; i <= numberOfTriangles; i ++ ) { - - newIndices.push( index.getX( 0 ) ); - newIndices.push( index.getX( i ) ); - newIndices.push( index.getX( i + 1 ) ); - - } - - } else { - - // gl.TRIANGLE_STRIP - - for ( let i = 0; i < numberOfTriangles; i ++ ) { - - if ( i % 2 === 0 ) { - - newIndices.push( index.getX( i ) ); - newIndices.push( index.getX( i + 1 ) ); - newIndices.push( index.getX( i + 2 ) ); - - } else { - - newIndices.push( index.getX( i + 2 ) ); - newIndices.push( index.getX( i + 1 ) ); - newIndices.push( index.getX( i ) ); - - } - - } - - } - - if ( ( newIndices.length / 3 ) !== numberOfTriangles ) { - - console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Unable to generate correct amount of triangles.' ); - - } - - // updated indices - - geometry.setIndex( newIndices ); - geometry.clearGroups(); - - return geometry; - - } else { - - console.error( 'THREE.BufferGeometryUtils.toTrianglesDrawMode(): Unknown draw mode:', drawMode ); - return geometry; - - } - -} - -/** - * Calculates the morphed attributes of a morphed/skinned BufferGeometry. - * - * Helpful for Raytracing or Decals (i.e. a `DecalGeometry` applied to a morphed Object with a `BufferGeometry` - * will use the original `BufferGeometry`, not the morphed/skinned one, generating an incorrect result. - * Using this function to create a shadow `Object3`D the `DecalGeometry` can be correctly generated). - * - * @param {Mesh|Line|Points} object - The 3D object to compute morph attributes for. - * @return {Object} An object with original position/normal attributes and morphed ones. - */ -function computeMorphedAttributes( object ) { - - const _vA = new Vector3(); - const _vB = new Vector3(); - const _vC = new Vector3(); - - const _tempA = new Vector3(); - const _tempB = new Vector3(); - const _tempC = new Vector3(); - - const _morphA = new Vector3(); - const _morphB = new Vector3(); - const _morphC = new Vector3(); - - function _calculateMorphedAttributeData( - object, - attribute, - morphAttribute, - morphTargetsRelative, - a, - b, - c, - modifiedAttributeArray - ) { - - _vA.fromBufferAttribute( attribute, a ); - _vB.fromBufferAttribute( attribute, b ); - _vC.fromBufferAttribute( attribute, c ); - - const morphInfluences = object.morphTargetInfluences; - - if ( morphAttribute && morphInfluences ) { - - _morphA.set( 0, 0, 0 ); - _morphB.set( 0, 0, 0 ); - _morphC.set( 0, 0, 0 ); - - for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) { - - const influence = morphInfluences[ i ]; - const morph = morphAttribute[ i ]; - - if ( influence === 0 ) continue; - - _tempA.fromBufferAttribute( morph, a ); - _tempB.fromBufferAttribute( morph, b ); - _tempC.fromBufferAttribute( morph, c ); - - if ( morphTargetsRelative ) { - - _morphA.addScaledVector( _tempA, influence ); - _morphB.addScaledVector( _tempB, influence ); - _morphC.addScaledVector( _tempC, influence ); - - } else { - - _morphA.addScaledVector( _tempA.sub( _vA ), influence ); - _morphB.addScaledVector( _tempB.sub( _vB ), influence ); - _morphC.addScaledVector( _tempC.sub( _vC ), influence ); - - } - - } - - _vA.add( _morphA ); - _vB.add( _morphB ); - _vC.add( _morphC ); - - } - - if ( object.isSkinnedMesh ) { - - object.applyBoneTransform( a, _vA ); - object.applyBoneTransform( b, _vB ); - object.applyBoneTransform( c, _vC ); - - } - - modifiedAttributeArray[ a * 3 + 0 ] = _vA.x; - modifiedAttributeArray[ a * 3 + 1 ] = _vA.y; - modifiedAttributeArray[ a * 3 + 2 ] = _vA.z; - modifiedAttributeArray[ b * 3 + 0 ] = _vB.x; - modifiedAttributeArray[ b * 3 + 1 ] = _vB.y; - modifiedAttributeArray[ b * 3 + 2 ] = _vB.z; - modifiedAttributeArray[ c * 3 + 0 ] = _vC.x; - modifiedAttributeArray[ c * 3 + 1 ] = _vC.y; - modifiedAttributeArray[ c * 3 + 2 ] = _vC.z; - - } - - const geometry = object.geometry; - const material = object.material; - - let a, b, c; - const index = geometry.index; - const positionAttribute = geometry.attributes.position; - const morphPosition = geometry.morphAttributes.position; - const morphTargetsRelative = geometry.morphTargetsRelative; - const normalAttribute = geometry.attributes.normal; - const morphNormal = geometry.morphAttributes.normal; - - const groups = geometry.groups; - const drawRange = geometry.drawRange; - let i, j, il, jl; - let group; - let start, end; - - const modifiedPosition = new Float32Array( positionAttribute.count * positionAttribute.itemSize ); - const modifiedNormal = new Float32Array( normalAttribute.count * normalAttribute.itemSize ); - - if ( index !== null ) { - - // indexed buffer geometry - - if ( Array.isArray( material ) ) { - - for ( i = 0, il = groups.length; i < il; i ++ ) { - - group = groups[ i ]; - - start = Math.max( group.start, drawRange.start ); - end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ); - - for ( j = start, jl = end; j < jl; j += 3 ) { - - a = index.getX( j ); - b = index.getX( j + 1 ); - c = index.getX( j + 2 ); - - _calculateMorphedAttributeData( - object, - positionAttribute, - morphPosition, - morphTargetsRelative, - a, b, c, - modifiedPosition - ); - - _calculateMorphedAttributeData( - object, - normalAttribute, - morphNormal, - morphTargetsRelative, - a, b, c, - modifiedNormal - ); - - } - - } - - } else { - - start = Math.max( 0, drawRange.start ); - end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); - - for ( i = start, il = end; i < il; i += 3 ) { - - a = index.getX( i ); - b = index.getX( i + 1 ); - c = index.getX( i + 2 ); - - _calculateMorphedAttributeData( - object, - positionAttribute, - morphPosition, - morphTargetsRelative, - a, b, c, - modifiedPosition - ); - - _calculateMorphedAttributeData( - object, - normalAttribute, - morphNormal, - morphTargetsRelative, - a, b, c, - modifiedNormal - ); - - } - - } - - } else { - - // non-indexed buffer geometry - - if ( Array.isArray( material ) ) { - - for ( i = 0, il = groups.length; i < il; i ++ ) { - - group = groups[ i ]; - - start = Math.max( group.start, drawRange.start ); - end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ); - - for ( j = start, jl = end; j < jl; j += 3 ) { - - a = j; - b = j + 1; - c = j + 2; - - _calculateMorphedAttributeData( - object, - positionAttribute, - morphPosition, - morphTargetsRelative, - a, b, c, - modifiedPosition - ); - - _calculateMorphedAttributeData( - object, - normalAttribute, - morphNormal, - morphTargetsRelative, - a, b, c, - modifiedNormal - ); - - } - - } - - } else { - - start = Math.max( 0, drawRange.start ); - end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); - - for ( i = start, il = end; i < il; i += 3 ) { - - a = i; - b = i + 1; - c = i + 2; - - _calculateMorphedAttributeData( - object, - positionAttribute, - morphPosition, - morphTargetsRelative, - a, b, c, - modifiedPosition - ); - - _calculateMorphedAttributeData( - object, - normalAttribute, - morphNormal, - morphTargetsRelative, - a, b, c, - modifiedNormal - ); - - } - - } - - } - - const morphedPositionAttribute = new Float32BufferAttribute( modifiedPosition, 3 ); - const morphedNormalAttribute = new Float32BufferAttribute( modifiedNormal, 3 ); - - return { - - positionAttribute: positionAttribute, - normalAttribute: normalAttribute, - morphedPositionAttribute: morphedPositionAttribute, - morphedNormalAttribute: morphedNormalAttribute - - }; - -} - -/** - * Merges the {@link BufferGeometry#groups} for the given geometry. - * - * @param {BufferGeometry} geometry - The geometry to modify. - * @return {BufferGeometry} - The updated geometry - */ -function mergeGroups( geometry ) { - - if ( geometry.groups.length === 0 ) { - - console.warn( 'THREE.BufferGeometryUtils.mergeGroups(): No groups are defined. Nothing to merge.' ); - return geometry; - - } - - let groups = geometry.groups; - - // sort groups by material index - - groups = groups.sort( ( a, b ) => { - - if ( a.materialIndex !== b.materialIndex ) return a.materialIndex - b.materialIndex; - - return a.start - b.start; - - } ); - - // create index for non-indexed geometries - - if ( geometry.getIndex() === null ) { - - const positionAttribute = geometry.getAttribute( 'position' ); - const indices = []; - - for ( let i = 0; i < positionAttribute.count; i += 3 ) { - - indices.push( i, i + 1, i + 2 ); - - } - - geometry.setIndex( indices ); - - } - - // sort index - - const index = geometry.getIndex(); - - const newIndices = []; - - for ( let i = 0; i < groups.length; i ++ ) { - - const group = groups[ i ]; - - const groupStart = group.start; - const groupLength = groupStart + group.count; - - for ( let j = groupStart; j < groupLength; j ++ ) { - - newIndices.push( index.getX( j ) ); - - } - - } - - geometry.dispose(); // Required to force buffer recreation - geometry.setIndex( newIndices ); - - // update groups indices - - let start = 0; - - for ( let i = 0; i < groups.length; i ++ ) { - - const group = groups[ i ]; - - group.start = start; - start += group.count; - - } - - // merge groups - - let currentGroup = groups[ 0 ]; - - geometry.groups = [ currentGroup ]; - - for ( let i = 1; i < groups.length; i ++ ) { - - const group = groups[ i ]; - - if ( currentGroup.materialIndex === group.materialIndex ) { - - currentGroup.count += group.count; - - } else { - - currentGroup = group; - geometry.groups.push( currentGroup ); - - } - - } - - return geometry; - -} - -/** - * Modifies the supplied geometry if it is non-indexed, otherwise creates a new, - * non-indexed geometry. Returns the geometry with smooth normals everywhere except - * faces that meet at an angle greater than the crease angle. - * - * @param {BufferGeometry} geometry - The geometry to modify. - * @param {number} [creaseAngle=Math.PI/3] - The crease angle in radians. - * @return {BufferGeometry} - The updated geometry - */ -function toCreasedNormals( geometry, creaseAngle = Math.PI / 3 /* 60 degrees */ ) { - - // BufferGeometry.toNonIndexed() warns if the geometry is non-indexed - // and returns the original geometry - const resultGeometry = geometry.index ? geometry.toNonIndexed() : geometry; - const posAttr = resultGeometry.attributes.position; - const vertexCount = posAttr.count; - - let positions; - - if ( posAttr.isBufferAttribute === true && posAttr.itemSize === 3 && posAttr.normalized === false ) { - - positions = posAttr.array; - - } else { - - // flatten the position buffer so the math below operates on plain numbers - positions = new Float64Array( vertexCount * 3 ); - - for ( let i = 0; i < vertexCount; i ++ ) { - - positions[ 3 * i + 0 ] = posAttr.getX( i ); - positions[ 3 * i + 1 ] = posAttr.getY( i ); - positions[ 3 * i + 2 ] = posAttr.getZ( i ); - - } - - } - - const creaseDot = Math.cos( creaseAngle ); - const hashMultiplier = ( 1 + 1e-10 ) * 1e2; - const faceCount = vertexCount / 3; - - // compute the normal of each face - const faceNormals = new Float64Array( faceCount * 3 ); - for ( let f = 0; f < faceCount; f ++ ) { - - const f9 = 9 * f; - const ax = positions[ f9 + 0 ], ay = positions[ f9 + 1 ], az = positions[ f9 + 2 ]; - const bx = positions[ f9 + 3 ], by = positions[ f9 + 4 ], bz = positions[ f9 + 5 ]; - const cx = positions[ f9 + 6 ], cy = positions[ f9 + 7 ], cz = positions[ f9 + 8 ]; - - const v1x = cx - bx, v1y = cy - by, v1z = cz - bz; - const v2x = ax - bx, v2y = ay - by, v2z = az - bz; - - const nx = v1y * v2z - v1z * v2y; - const ny = v1z * v2x - v1x * v2z; - const nz = v1x * v2y - v1y * v2x; - - const invLength = 1 / ( Math.sqrt( nx * nx + ny * ny + nz * nz ) || 1 ); - faceNormals[ 3 * f + 0 ] = nx * invLength; - faceNormals[ 3 * f + 1 ] = ny * invLength; - faceNormals[ 3 * f + 2 ] = nz * invLength; - - } - - // assign an id to each vertex, sharing the id between vertices with the same - // quantized position via an open-addressed hash table (slots hold id + 1, 0 means empty) - const vertexIds = new Int32Array( vertexCount ); - const quantized = new Float64Array( vertexCount * 3 ); - - let tableSize = 1; - while ( tableSize < vertexCount * 2 ) tableSize <<= 1; - const tableMask = tableSize - 1; - const table = new Int32Array( tableSize ); - - let uniqueCount = 0; - for ( let i = 0; i < vertexCount; i ++ ) { - - const i3 = 3 * i; - const qx = Math.trunc( positions[ i3 + 0 ] * hashMultiplier ); - const qy = Math.trunc( positions[ i3 + 1 ] * hashMultiplier ); - const qz = Math.trunc( positions[ i3 + 2 ] * hashMultiplier ); - - let slot = ( Math.imul( qx, 73856093 ) ^ Math.imul( qy, 19349663 ) ^ Math.imul( qz, 83492791 ) ) & tableMask; - - while ( true ) { - - const id = table[ slot ]; - - if ( id === 0 ) { - - const q3 = 3 * uniqueCount; - quantized[ q3 + 0 ] = qx; - quantized[ q3 + 1 ] = qy; - quantized[ q3 + 2 ] = qz; - - table[ slot ] = uniqueCount + 1; - vertexIds[ i ] = uniqueCount ++; - break; - - } - - const q3 = 3 * ( id - 1 ); - - if ( quantized[ q3 + 0 ] === qx && quantized[ q3 + 1 ] === qy && quantized[ q3 + 2 ] === qz ) { - - vertexIds[ i ] = id - 1; - break; - - } - - slot = ( slot + 1 ) & tableMask; - - } - - } - - // bucket the faces surrounding each unique vertex position - const bucketOffsets = new Int32Array( uniqueCount + 1 ); - for ( let i = 0; i < vertexCount; i ++ ) bucketOffsets[ vertexIds[ i ] + 1 ] ++; - for ( let i = 0; i < uniqueCount; i ++ ) bucketOffsets[ i + 1 ] += bucketOffsets[ i ]; - - const bucketFaces = new Int32Array( vertexCount ); - const bucketCursors = bucketOffsets.slice( 0, uniqueCount ); - for ( let f = 0; f < faceCount; f ++ ) { - - const f3 = 3 * f; - bucketFaces[ bucketCursors[ vertexIds[ f3 + 0 ] ] ++ ] = f; - bucketFaces[ bucketCursors[ vertexIds[ f3 + 1 ] ] ++ ] = f; - bucketFaces[ bucketCursors[ vertexIds[ f3 + 2 ] ] ++ ] = f; - - } - - // average the normals of the faces surrounding each vertex if they are within the - // provided crease threshold - const normalArray = new Float32Array( vertexCount * 3 ); - for ( let f = 0; f < faceCount; f ++ ) { - - const f3 = 3 * f; - const nx = faceNormals[ f3 + 0 ]; - const ny = faceNormals[ f3 + 1 ]; - const nz = faceNormals[ f3 + 2 ]; - - for ( let n = 0; n < 3; n ++ ) { - - const i = f3 + n; - const id = vertexIds[ i ]; - - let sumX = 0, sumY = 0, sumZ = 0; - - for ( let k = bucketOffsets[ id ], end = bucketOffsets[ id + 1 ]; k < end; k ++ ) { - - const o3 = 3 * bucketFaces[ k ]; - const ox = faceNormals[ o3 + 0 ]; - const oy = faceNormals[ o3 + 1 ]; - const oz = faceNormals[ o3 + 2 ]; - - if ( nx * ox + ny * oy + nz * oz > creaseDot ) { - - sumX += ox; - sumY += oy; - sumZ += oz; - - } - - } - - const invLength = 1 / ( Math.sqrt( sumX * sumX + sumY * sumY + sumZ * sumZ ) || 1 ); - normalArray[ 3 * i + 0 ] = sumX * invLength; - normalArray[ 3 * i + 1 ] = sumY * invLength; - normalArray[ 3 * i + 2 ] = sumZ * invLength; - - } - - } - - resultGeometry.setAttribute( 'normal', new BufferAttribute( normalArray, 3, false ) ); - return resultGeometry; - -} - -export { - computeMikkTSpaceTangents, - mergeGeometries, - mergeAttributes, - deepCloneAttribute, - deinterleaveAttribute, - deinterleaveGeometry, - interleaveAttributes, - estimateBytesUsed, - mergeVertices, - toTrianglesDrawMode, - computeMorphedAttributes, - mergeGroups, - toCreasedNormals -}; diff --git a/public/bluebey-studio/vendor/three/examples/jsm/utils/SkeletonUtils.js b/public/bluebey-studio/vendor/three/examples/jsm/utils/SkeletonUtils.js deleted file mode 100644 index 836c2e2..0000000 --- a/public/bluebey-studio/vendor/three/examples/jsm/utils/SkeletonUtils.js +++ /dev/null @@ -1,496 +0,0 @@ -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 to the target. - * - * Both `target` and `source` can be a 3D object with a skeleton property (e.g. a skinned mesh) - * or a {@link Skeleton} directly. - * - * @param {Object3D|Skeleton} target - The target object. - * @param {Object3D|Skeleton} source - The source 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 to the target 3D object. - * - * The `source` can be a 3D object with a skeleton property (e.g. a skinned mesh) - * or a {@link Skeleton} directly. - * - * @param {Object3D} target - The target 3D object. Must have a `skeleton` property. - * @param {Object3D|Skeleton} source - The source 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. - * @property {Object<string,Matrix4>} [localOffsets] - Per-bone local offset matrices, keyed by bone name. - * @property {Vector3} [hipPosition] - An additional position offset applied to the hip bone. - **/ - -export { - retarget, - retargetClip, - clone, -}; |
