LOD.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. import { Vector3 } from '../math/Vector3.js';
  2. import { Object3D } from '../core/Object3D.js';
  3. const _v1 = /*@__PURE__*/ new Vector3();
  4. const _v2 = /*@__PURE__*/ new Vector3();
  5. /**
  6. * A component for providing a basic Level of Detail (LOD) mechanism.
  7. *
  8. * Every LOD level is associated with an object, and rendering can be switched
  9. * between them at the distances specified. Typically you would create, say,
  10. * three meshes, one for far away (low detail), one for mid range (medium
  11. * detail) and one for close up (high detail).
  12. *
  13. * ```js
  14. * const lod = new THREE.LOD();
  15. * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } );
  16. *
  17. * //Create spheres with 3 levels of detail and create new LOD levels for them
  18. * for( let i = 0; i < 3; i++ ) {
  19. *
  20. * const geometry = new THREE.IcosahedronGeometry( 10, 3 - i );
  21. * const mesh = new THREE.Mesh( geometry, material );
  22. * lod.addLevel( mesh, i * 75 );
  23. *
  24. * }
  25. *
  26. * scene.add( lod );
  27. * ```
  28. *
  29. * @augments Object3D
  30. */
  31. class LOD extends Object3D {
  32. /**
  33. * Constructs a new LOD.
  34. */
  35. constructor() {
  36. super();
  37. /**
  38. * This flag can be used for type testing.
  39. *
  40. * @type {boolean}
  41. * @readonly
  42. * @default true
  43. */
  44. this.isLOD = true;
  45. /**
  46. * The current LOD index.
  47. *
  48. * @private
  49. * @type {number}
  50. * @default 0
  51. */
  52. this._currentLevel = 0;
  53. this.type = 'LOD';
  54. Object.defineProperties( this, {
  55. /**
  56. * This array holds the LOD levels.
  57. *
  58. * @name LOD#levels
  59. * @type {Array<{object:Object3D,distance:number,hysteresis:number}>}
  60. */
  61. levels: {
  62. enumerable: true,
  63. value: []
  64. }
  65. } );
  66. /**
  67. * Whether the LOD object is updated automatically by the renderer per frame
  68. * or not. If set to `false`, you have to call {@link LOD#update} in the
  69. * render loop by yourself.
  70. *
  71. * @type {boolean}
  72. * @default true
  73. */
  74. this.autoUpdate = true;
  75. }
  76. copy( source ) {
  77. super.copy( source, false );
  78. const levels = source.levels;
  79. for ( let i = 0, l = levels.length; i < l; i ++ ) {
  80. const level = levels[ i ];
  81. this.addLevel( level.object.clone(), level.distance, level.hysteresis );
  82. }
  83. this.autoUpdate = source.autoUpdate;
  84. return this;
  85. }
  86. /**
  87. * Adds a mesh that will display at a certain distance and greater. Typically
  88. * the further away the distance, the lower the detail on the mesh.
  89. *
  90. * @param {Object3D} object - The 3D object to display at this level.
  91. * @param {number} [distance=0] - The distance at which to display this level of detail.
  92. * @param {number} [hysteresis=0] - Threshold used to avoid flickering at LOD boundaries, as a fraction of distance.
  93. * @return {LOD} A reference to this instance.
  94. */
  95. addLevel( object, distance = 0, hysteresis = 0 ) {
  96. distance = Math.abs( distance );
  97. const levels = this.levels;
  98. let l;
  99. for ( l = 0; l < levels.length; l ++ ) {
  100. if ( distance < levels[ l ].distance ) {
  101. break;
  102. }
  103. }
  104. levels.splice( l, 0, { distance: distance, hysteresis: hysteresis, object: object } );
  105. this.add( object );
  106. return this;
  107. }
  108. /**
  109. * Removes an existing level, based on the distance from the camera.
  110. * Returns `true` when the level has been removed. Otherwise `false`.
  111. *
  112. * @param {number} distance - Distance of the level to remove.
  113. * @return {boolean} Whether the level has been removed or not.
  114. */
  115. removeLevel( distance ) {
  116. const levels = this.levels;
  117. for ( let i = 0; i < levels.length; i ++ ) {
  118. if ( levels[ i ].distance === distance ) {
  119. const removedElements = levels.splice( i, 1 );
  120. this.remove( removedElements[ 0 ].object );
  121. return true;
  122. }
  123. }
  124. return false;
  125. }
  126. /**
  127. * Returns the currently active LOD level index.
  128. *
  129. * @return {number} The current active LOD level index.
  130. */
  131. getCurrentLevel() {
  132. return this._currentLevel;
  133. }
  134. /**
  135. * Returns a reference to the first 3D object that is greater than
  136. * the given distance.
  137. *
  138. * @param {number} distance - The LOD distance.
  139. * @return {?Object3D} The found 3D object. `null` if no 3D object has been found.
  140. */
  141. getObjectForDistance( distance ) {
  142. const levels = this.levels;
  143. if ( levels.length > 0 ) {
  144. let i, l;
  145. for ( i = 1, l = levels.length; i < l; i ++ ) {
  146. let levelDistance = levels[ i ].distance;
  147. if ( levels[ i ].object.visible ) {
  148. levelDistance -= levelDistance * levels[ i ].hysteresis;
  149. }
  150. if ( distance < levelDistance ) {
  151. break;
  152. }
  153. }
  154. return levels[ i - 1 ].object;
  155. }
  156. return null;
  157. }
  158. /**
  159. * Computes intersection points between a casted ray and this LOD.
  160. *
  161. * @param {Raycaster} raycaster - The raycaster.
  162. * @param {Array<Object>} intersects - The target array that holds the intersection points.
  163. */
  164. raycast( raycaster, intersects ) {
  165. const levels = this.levels;
  166. if ( levels.length > 0 ) {
  167. _v1.setFromMatrixPosition( this.matrixWorld );
  168. const distance = raycaster.ray.origin.distanceTo( _v1 );
  169. this.getObjectForDistance( distance ).raycast( raycaster, intersects );
  170. }
  171. }
  172. /**
  173. * Updates the LOD by computing which LOD level should be visible according
  174. * to the current distance of the given camera.
  175. *
  176. * @param {Camera} camera - The camera the scene is rendered with.
  177. */
  178. update( camera ) {
  179. const levels = this.levels;
  180. if ( levels.length > 1 ) {
  181. _v1.setFromMatrixPosition( camera.matrixWorld );
  182. _v2.setFromMatrixPosition( this.matrixWorld );
  183. const distance = _v1.distanceTo( _v2 ) / camera.zoom;
  184. levels[ 0 ].object.visible = true;
  185. let i, l;
  186. for ( i = 1, l = levels.length; i < l; i ++ ) {
  187. let levelDistance = levels[ i ].distance;
  188. if ( levels[ i ].object.visible ) {
  189. levelDistance -= levelDistance * levels[ i ].hysteresis;
  190. }
  191. if ( distance >= levelDistance ) {
  192. levels[ i - 1 ].object.visible = false;
  193. levels[ i ].object.visible = true;
  194. } else {
  195. break;
  196. }
  197. }
  198. this._currentLevel = i - 1;
  199. for ( ; i < l; i ++ ) {
  200. levels[ i ].object.visible = false;
  201. }
  202. }
  203. }
  204. toJSON( meta ) {
  205. const data = super.toJSON( meta );
  206. if ( this.autoUpdate === false ) data.object.autoUpdate = false;
  207. data.object.levels = [];
  208. const levels = this.levels;
  209. for ( let i = 0, l = levels.length; i < l; i ++ ) {
  210. const level = levels[ i ];
  211. data.object.levels.push( {
  212. object: level.object.uuid,
  213. distance: level.distance,
  214. hysteresis: level.hysteresis
  215. } );
  216. }
  217. return data;
  218. }
  219. }
  220. export { LOD };