utils.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. import { AlwaysDepth, EqualDepth, GreaterDepth, GreaterEqualDepth, LessDepth, LessEqualDepth, NeverDepth, NotEqualDepth } from './constants.js';
  2. /**
  3. * Finds the minimum value in an array.
  4. *
  5. * @private
  6. * @param {Array<number>} array - The array to search for the minimum value.
  7. * @return {number} The minimum value in the array, or Infinity if the array is empty.
  8. */
  9. function arrayMin( array ) {
  10. if ( array.length === 0 ) return Infinity;
  11. let min = array[ 0 ];
  12. for ( let i = 1, l = array.length; i < l; ++ i ) {
  13. if ( array[ i ] < min ) min = array[ i ];
  14. }
  15. return min;
  16. }
  17. /**
  18. * Finds the maximum value in an array.
  19. *
  20. * @private
  21. * @param {Array<number>} array - The array to search for the maximum value.
  22. * @return {number} The maximum value in the array, or -Infinity if the array is empty.
  23. */
  24. function arrayMax( array ) {
  25. if ( array.length === 0 ) return - Infinity;
  26. let max = array[ 0 ];
  27. for ( let i = 1, l = array.length; i < l; ++ i ) {
  28. if ( array[ i ] > max ) max = array[ i ];
  29. }
  30. return max;
  31. }
  32. /**
  33. * Checks if an array contains values that require Uint32 representation.
  34. *
  35. * This function determines whether the array contains any values >= 65535,
  36. * which would require a Uint32Array rather than a Uint16Array for proper storage.
  37. * The function iterates from the end of the array, assuming larger values are
  38. * typically located at the end.
  39. *
  40. * @private
  41. * @param {Array<number>} array - The array to check.
  42. * @return {boolean} True if the array contains values >= 65535, false otherwise.
  43. */
  44. function arrayNeedsUint32( array ) {
  45. // assumes larger values usually on last
  46. for ( let i = array.length - 1; i >= 0; -- i ) {
  47. if ( array[ i ] >= 65535 ) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565
  48. }
  49. return false;
  50. }
  51. /**
  52. * Map of typed array constructor names to their constructors.
  53. * This mapping enables dynamic creation of typed arrays based on string type names.
  54. *
  55. * @private
  56. * @constant
  57. * @type {Object<string, TypedArrayConstructor>}
  58. */
  59. const TYPED_ARRAYS = {
  60. Int8Array: Int8Array,
  61. Uint8Array: Uint8Array,
  62. Uint8ClampedArray: Uint8ClampedArray,
  63. Int16Array: Int16Array,
  64. Uint16Array: Uint16Array,
  65. Int32Array: Int32Array,
  66. Uint32Array: Uint32Array,
  67. Float32Array: Float32Array,
  68. Float64Array: Float64Array
  69. };
  70. /**
  71. * Creates a typed array of the specified type from the given buffer.
  72. *
  73. * @private
  74. * @param {string} type - The name of the typed array type (e.g., 'Float32Array', 'Uint16Array').
  75. * @param {ArrayBuffer} buffer - The buffer to create the typed array from.
  76. * @return {TypedArray} A new typed array of the specified type.
  77. */
  78. function getTypedArray( type, buffer ) {
  79. return new TYPED_ARRAYS[ type ]( buffer );
  80. }
  81. /**
  82. * Returns `true` if the given object is a typed array.
  83. *
  84. * @param {any} array - The object to check.
  85. * @return {boolean} Whether the given object is a typed array.
  86. */
  87. function isTypedArray( array ) {
  88. return ArrayBuffer.isView( array ) && ! ( array instanceof DataView );
  89. }
  90. /**
  91. * Creates an XHTML element with the specified tag name.
  92. *
  93. * This function uses the XHTML namespace to create DOM elements,
  94. * ensuring proper element creation in XML-based contexts.
  95. *
  96. * @private
  97. * @param {string} name - The tag name of the element to create (e.g., 'canvas', 'div').
  98. * @return {HTMLElement} The created XHTML element.
  99. */
  100. function createElementNS( name ) {
  101. return document.createElementNS( 'http://www.w3.org/1999/xhtml', name );
  102. }
  103. /**
  104. * Creates a canvas element configured for block display.
  105. *
  106. * This is a convenience function that creates a canvas element with
  107. * display style set to 'block', which is commonly used in three.js
  108. * rendering contexts to avoid inline element spacing issues.
  109. *
  110. * @return {HTMLCanvasElement} A canvas element with display set to 'block'.
  111. */
  112. function createCanvasElement() {
  113. const canvas = createElementNS( 'canvas' );
  114. canvas.style.display = 'block';
  115. return canvas;
  116. }
  117. /**
  118. * Internal cache for tracking warning messages to prevent duplicate warnings.
  119. *
  120. * @private
  121. * @type {Object<string, boolean>}
  122. */
  123. const _cache = {};
  124. /**
  125. * Custom console function handler for intercepting log, warn, and error calls.
  126. *
  127. * @private
  128. * @type {Function|null}
  129. */
  130. let _setConsoleFunction = null;
  131. /**
  132. * Sets a custom function to handle console output.
  133. *
  134. * This allows external code to intercept and handle console.log, console.warn,
  135. * and console.error calls made by three.js, which is useful for custom logging,
  136. * testing, or debugging workflows.
  137. *
  138. * @param {Function} fn - The function to handle console output. Should accept
  139. * (type, message, ...params) where type is 'log', 'warn', or 'error'.
  140. */
  141. function setConsoleFunction( fn ) {
  142. _setConsoleFunction = fn;
  143. }
  144. /**
  145. * Gets the currently set custom console function.
  146. *
  147. * @return {Function|null} The custom console function, or null if not set.
  148. */
  149. function getConsoleFunction() {
  150. return _setConsoleFunction;
  151. }
  152. /**
  153. * Logs an informational message with the 'THREE.' prefix.
  154. *
  155. * If a custom console function is set via setConsoleFunction(), it will be used
  156. * instead of the native console.log. The first parameter is treated as the
  157. * method name and is automatically prefixed with 'THREE.'.
  158. *
  159. * @param {...any} params - The message components. The first param is used as
  160. * the method name and prefixed with 'THREE.'.
  161. */
  162. function log( ...params ) {
  163. const message = 'THREE.' + params.shift();
  164. if ( _setConsoleFunction ) {
  165. _setConsoleFunction( 'log', message, ...params );
  166. } else {
  167. console.log( message, ...params );
  168. }
  169. }
  170. /**
  171. * Enhances log/warn/error messages related to TSL.
  172. *
  173. * @param {Array<any>} params - The original message parameters.
  174. * @returns {Array<any>} The filtered and enhanced message parameters.
  175. */
  176. function enhanceLogMessage( params ) {
  177. const message = params[ 0 ];
  178. if ( typeof message === 'string' && message.startsWith( 'TSL:' ) ) {
  179. const stackTrace = params[ 1 ];
  180. if ( stackTrace && stackTrace.isStackTrace ) {
  181. params[ 0 ] += ' ' + stackTrace.getLocation();
  182. } else {
  183. params[ 1 ] = 'Stack trace not available. Enable "THREE.Node.captureStackTrace" to capture stack traces.';
  184. }
  185. }
  186. return params;
  187. }
  188. /**
  189. * Logs a warning message with the 'THREE.' prefix.
  190. *
  191. * If a custom console function is set via setConsoleFunction(), it will be used
  192. * instead of the native console.warn. The first parameter is treated as the
  193. * method name and is automatically prefixed with 'THREE.'.
  194. *
  195. * @param {...any} params - The message components. The first param is used as
  196. * the method name and prefixed with 'THREE.'.
  197. */
  198. function warn( ...params ) {
  199. params = enhanceLogMessage( params );
  200. const message = 'THREE.' + params.shift();
  201. if ( _setConsoleFunction ) {
  202. _setConsoleFunction( 'warn', message, ...params );
  203. } else {
  204. const stackTrace = params[ 0 ];
  205. if ( stackTrace && stackTrace.isStackTrace ) {
  206. console.warn( stackTrace.getError( message ) );
  207. } else {
  208. console.warn( message, ...params );
  209. }
  210. }
  211. }
  212. /**
  213. * Logs an error message with the 'THREE.' prefix.
  214. *
  215. * If a custom console function is set via setConsoleFunction(), it will be used
  216. * instead of the native console.error. The first parameter is treated as the
  217. * method name and is automatically prefixed with 'THREE.'.
  218. *
  219. * @param {...any} params - The message components. The first param is used as
  220. * the method name and prefixed with 'THREE.'.
  221. */
  222. function error( ...params ) {
  223. params = enhanceLogMessage( params );
  224. const message = 'THREE.' + params.shift();
  225. if ( _setConsoleFunction ) {
  226. _setConsoleFunction( 'error', message, ...params );
  227. } else {
  228. const stackTrace = params[ 0 ];
  229. if ( stackTrace && stackTrace.isStackTrace ) {
  230. console.error( stackTrace.getError( message ) );
  231. } else {
  232. console.error( message, ...params );
  233. }
  234. }
  235. }
  236. /**
  237. * Logs a warning message only once, preventing duplicate warnings.
  238. *
  239. * This function maintains an internal cache of warning messages and will only
  240. * output each unique warning message once. Useful for warnings that may be
  241. * triggered repeatedly but should only be shown to the user once.
  242. *
  243. * @param {...any} params - The warning message components.
  244. */
  245. function warnOnce( ...params ) {
  246. const message = params.join( ' ' );
  247. if ( message in _cache ) return;
  248. _cache[ message ] = true;
  249. warn( ...params );
  250. }
  251. /**
  252. * Yields execution to the main thread to allow rendering and other tasks.
  253. * Uses scheduler.yield() when available (Chrome 115+), falls back to requestAnimationFrame.
  254. *
  255. * @return {Promise<void>}
  256. */
  257. function yieldToMain() {
  258. if ( typeof self !== 'undefined' && typeof self.scheduler !== 'undefined' && typeof self.scheduler.yield !== 'undefined' ) {
  259. return self.scheduler.yield();
  260. }
  261. return new Promise( resolve => {
  262. requestAnimationFrame( resolve );
  263. } );
  264. }
  265. /**
  266. * Asynchronously probes for WebGL sync object completion.
  267. *
  268. * This function creates a promise that resolves when the WebGL sync object
  269. * signals completion or rejects if the sync operation fails. It uses polling
  270. * at the specified interval to check the sync status without blocking the
  271. * main thread. This is useful for GPU-CPU synchronization in WebGL contexts.
  272. *
  273. * @private
  274. * @param {WebGL2RenderingContext} gl - The WebGL rendering context.
  275. * @param {WebGLSync} sync - The WebGL sync object to wait for.
  276. * @param {number} interval - The polling interval in milliseconds.
  277. * @return {Promise<void>} A promise that resolves when the sync completes or rejects if it fails.
  278. */
  279. function probeAsync( gl, sync, interval ) {
  280. return new Promise( function ( resolve, reject ) {
  281. function probe() {
  282. switch ( gl.clientWaitSync( sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0 ) ) {
  283. case gl.WAIT_FAILED:
  284. reject();
  285. break;
  286. case gl.TIMEOUT_EXPIRED:
  287. setTimeout( probe, interval );
  288. break;
  289. default:
  290. resolve();
  291. }
  292. }
  293. setTimeout( probe, interval );
  294. } );
  295. }
  296. /**
  297. * Converts a projection matrix from normalized device coordinates (NDC)
  298. * range [-1, 1] to [0, 1].
  299. *
  300. * This conversion is commonly needed when working with depth textures or
  301. * render targets that expect depth values in the [0, 1] range rather than
  302. * the standard OpenGL NDC range of [-1, 1]. The function modifies the
  303. * projection matrix in place.
  304. *
  305. * @private
  306. * @param {Matrix4} projectionMatrix - The projection matrix to convert (modified in place).
  307. */
  308. function toNormalizedProjectionMatrix( projectionMatrix ) {
  309. const m = projectionMatrix.elements;
  310. // Convert [-1, 1] to [0, 1] projection matrix
  311. m[ 2 ] = 0.5 * m[ 2 ] + 0.5 * m[ 3 ];
  312. m[ 6 ] = 0.5 * m[ 6 ] + 0.5 * m[ 7 ];
  313. m[ 10 ] = 0.5 * m[ 10 ] + 0.5 * m[ 11 ];
  314. m[ 14 ] = 0.5 * m[ 14 ] + 0.5 * m[ 15 ];
  315. }
  316. /**
  317. * Reverses the depth range of a projection matrix.
  318. *
  319. * This function inverts the depth mapping of a projection matrix, which is
  320. * useful for reversed-Z depth buffer techniques that can improve depth
  321. * precision. The function handles both perspective and orthographic projection
  322. * matrices differently and modifies the matrix in place.
  323. *
  324. * For perspective matrices (where m[11] === -1), the depth mapping is
  325. * reversed with an offset. For orthographic matrices, a simpler reversal
  326. * is applied.
  327. *
  328. * @private
  329. * @param {Matrix4} projectionMatrix - The projection matrix to reverse (modified in place).
  330. */
  331. function toReversedProjectionMatrix( projectionMatrix ) {
  332. const m = projectionMatrix.elements;
  333. const isPerspectiveMatrix = m[ 11 ] === - 1;
  334. // Reverse [0, 1] projection matrix
  335. if ( isPerspectiveMatrix ) {
  336. m[ 10 ] = - m[ 10 ] - 1;
  337. m[ 14 ] = - m[ 14 ];
  338. } else {
  339. m[ 10 ] = - m[ 10 ];
  340. m[ 14 ] = - m[ 14 ] + 1;
  341. }
  342. }
  343. /**
  344. * Used to select the correct depth functions
  345. * when reversed depth buffer is used.
  346. *
  347. * @private
  348. * @type {Object}
  349. */
  350. const ReversedDepthFuncs = {
  351. [ NeverDepth ]: AlwaysDepth,
  352. [ LessDepth ]: GreaterDepth,
  353. [ EqualDepth ]: NotEqualDepth,
  354. [ LessEqualDepth ]: GreaterEqualDepth,
  355. [ AlwaysDepth ]: NeverDepth,
  356. [ GreaterDepth ]: LessDepth,
  357. [ NotEqualDepth ]: EqualDepth,
  358. [ GreaterEqualDepth ]: LessEqualDepth,
  359. };
  360. export { arrayMin, arrayMax, arrayNeedsUint32, getTypedArray, createElementNS, createCanvasElement, setConsoleFunction, getConsoleFunction, log, warn, error, warnOnce, probeAsync, yieldToMain, toNormalizedProjectionMatrix, toReversedProjectionMatrix, isTypedArray, ReversedDepthFuncs };
粤ICP备19079148号