| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- import { Vector3 } from '../math/Vector3.js';
- import { Object3D } from '../core/Object3D.js';
- const _v1 = /*@__PURE__*/ new Vector3();
- const _v2 = /*@__PURE__*/ new Vector3();
- /**
- * A component for providing a basic Level of Detail (LOD) mechanism.
- *
- * Every LOD level is associated with an object, and rendering can be switched
- * between them at the distances specified. Typically you would create, say,
- * three meshes, one for far away (low detail), one for mid range (medium
- * detail) and one for close up (high detail).
- *
- * ```js
- * const lod = new THREE.LOD();
- * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } );
- *
- * //Create spheres with 3 levels of detail and create new LOD levels for them
- * for( let i = 0; i < 3; i++ ) {
- *
- * const geometry = new THREE.IcosahedronGeometry( 10, 3 - i );
- * const mesh = new THREE.Mesh( geometry, material );
- * lod.addLevel( mesh, i * 75 );
- *
- * }
- *
- * scene.add( lod );
- * ```
- *
- * @augments Object3D
- */
- class LOD extends Object3D {
- /**
- * Constructs a new LOD.
- */
- constructor() {
- super();
- /**
- * This flag can be used for type testing.
- *
- * @type {boolean}
- * @readonly
- * @default true
- */
- this.isLOD = true;
- /**
- * The current LOD index.
- *
- * @private
- * @type {number}
- * @default 0
- */
- this._currentLevel = 0;
- this.type = 'LOD';
- Object.defineProperties( this, {
- /**
- * This array holds the LOD levels.
- *
- * @name LOD#levels
- * @type {Array<{object:Object3D,distance:number,hysteresis:number}>}
- */
- levels: {
- enumerable: true,
- value: []
- }
- } );
- /**
- * Whether the LOD object is updated automatically by the renderer per frame
- * or not. If set to `false`, you have to call {@link LOD#update} in the
- * render loop by yourself.
- *
- * @type {boolean}
- * @default true
- */
- this.autoUpdate = true;
- }
- copy( source ) {
- super.copy( source, false );
- const levels = source.levels;
- for ( let i = 0, l = levels.length; i < l; i ++ ) {
- const level = levels[ i ];
- this.addLevel( level.object.clone(), level.distance, level.hysteresis );
- }
- this.autoUpdate = source.autoUpdate;
- return this;
- }
- /**
- * Adds a mesh that will display at a certain distance and greater. Typically
- * the further away the distance, the lower the detail on the mesh.
- *
- * @param {Object3D} object - The 3D object to display at this level.
- * @param {number} [distance=0] - The distance at which to display this level of detail.
- * @param {number} [hysteresis=0] - Threshold used to avoid flickering at LOD boundaries, as a fraction of distance.
- * @return {LOD} A reference to this instance.
- */
- addLevel( object, distance = 0, hysteresis = 0 ) {
- distance = Math.abs( distance );
- const levels = this.levels;
- let l;
- for ( l = 0; l < levels.length; l ++ ) {
- if ( distance < levels[ l ].distance ) {
- break;
- }
- }
- levels.splice( l, 0, { distance: distance, hysteresis: hysteresis, object: object } );
- this.add( object );
- return this;
- }
- /**
- * Removes an existing level, based on the distance from the camera.
- * Returns `true` when the level has been removed. Otherwise `false`.
- *
- * @param {number} distance - Distance of the level to remove.
- * @return {boolean} Whether the level has been removed or not.
- */
- removeLevel( distance ) {
- const levels = this.levels;
- for ( let i = 0; i < levels.length; i ++ ) {
- if ( levels[ i ].distance === distance ) {
- const removedElements = levels.splice( i, 1 );
- this.remove( removedElements[ 0 ].object );
- return true;
- }
- }
- return false;
- }
- /**
- * Returns the currently active LOD level index.
- *
- * @return {number} The current active LOD level index.
- */
- getCurrentLevel() {
- return this._currentLevel;
- }
- /**
- * Returns a reference to the first 3D object that is greater than
- * the given distance.
- *
- * @param {number} distance - The LOD distance.
- * @return {?Object3D} The found 3D object. `null` if no 3D object has been found.
- */
- getObjectForDistance( distance ) {
- const levels = this.levels;
- if ( levels.length > 0 ) {
- let i, l;
- for ( i = 1, l = levels.length; i < l; i ++ ) {
- let levelDistance = levels[ i ].distance;
- if ( levels[ i ].object.visible ) {
- levelDistance -= levelDistance * levels[ i ].hysteresis;
- }
- if ( distance < levelDistance ) {
- break;
- }
- }
- return levels[ i - 1 ].object;
- }
- return null;
- }
- /**
- * Computes intersection points between a casted ray and this LOD.
- *
- * @param {Raycaster} raycaster - The raycaster.
- * @param {Array<Object>} intersects - The target array that holds the intersection points.
- */
- raycast( raycaster, intersects ) {
- const levels = this.levels;
- if ( levels.length > 0 ) {
- _v1.setFromMatrixPosition( this.matrixWorld );
- const distance = raycaster.ray.origin.distanceTo( _v1 );
- this.getObjectForDistance( distance ).raycast( raycaster, intersects );
- }
- }
- /**
- * Updates the LOD by computing which LOD level should be visible according
- * to the current distance of the given camera.
- *
- * @param {Camera} camera - The camera the scene is rendered with.
- */
- update( camera ) {
- const levels = this.levels;
- if ( levels.length > 1 ) {
- _v1.setFromMatrixPosition( camera.matrixWorld );
- _v2.setFromMatrixPosition( this.matrixWorld );
- const distance = _v1.distanceTo( _v2 ) / camera.zoom;
- levels[ 0 ].object.visible = true;
- let i, l;
- for ( i = 1, l = levels.length; i < l; i ++ ) {
- let levelDistance = levels[ i ].distance;
- if ( levels[ i ].object.visible ) {
- levelDistance -= levelDistance * levels[ i ].hysteresis;
- }
- if ( distance >= levelDistance ) {
- levels[ i - 1 ].object.visible = false;
- levels[ i ].object.visible = true;
- } else {
- break;
- }
- }
- this._currentLevel = i - 1;
- for ( ; i < l; i ++ ) {
- levels[ i ].object.visible = false;
- }
- }
- }
- toJSON( meta ) {
- const data = super.toJSON( meta );
- if ( this.autoUpdate === false ) data.object.autoUpdate = false;
- data.object.levels = [];
- const levels = this.levels;
- for ( let i = 0, l = levels.length; i < l; i ++ ) {
- const level = levels[ i ];
- data.object.levels.push( {
- object: level.object.uuid,
- distance: level.distance,
- hysteresis: level.hysteresis
- } );
- }
- return data;
- }
- }
- export { LOD };
|