Loader.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923
  1. import * as THREE from 'three';
  2. import { TGALoader } from 'three/addons/loaders/TGALoader.js';
  3. import { AddObjectCommand } from './commands/AddObjectCommand.js';
  4. import { SetSceneCommand } from './commands/SetSceneCommand.js';
  5. import { LoaderUtils } from './LoaderUtils.js';
  6. import { unzipSync, strFromU8 } from 'three/addons/libs/fflate.module.js';
  7. function Loader( editor ) {
  8. const scope = this;
  9. this.texturePath = '';
  10. this.loadItemList = function ( items ) {
  11. LoaderUtils.getFilesFromItemList( items, function ( files, filesMap ) {
  12. scope.loadFiles( files, filesMap );
  13. } );
  14. };
  15. this.loadFiles = function ( files, filesMap ) {
  16. if ( files.length > 0 ) {
  17. filesMap = filesMap || LoaderUtils.createFilesMap( files );
  18. const manager = new THREE.LoadingManager();
  19. manager.setURLModifier( function ( url ) {
  20. url = url.replace( /^(\.?\/)/, '' ); // remove './'
  21. const file = filesMap[ url ];
  22. if ( file ) {
  23. console.log( 'Loading', url );
  24. return URL.createObjectURL( file );
  25. }
  26. return url;
  27. } );
  28. manager.addHandler( /\.tga$/i, new TGALoader() );
  29. for ( let i = 0; i < files.length; i ++ ) {
  30. scope.loadFile( files[ i ], manager );
  31. }
  32. }
  33. };
  34. this.loadFile = function ( file, manager ) {
  35. const extension = file.name.split( '.' ).pop().toLowerCase();
  36. const reader = new FileReader();
  37. reader.addEventListener( 'progress', function ( event ) {
  38. const size = '(' + editor.utils.formatNumber( Math.floor( event.total / 1000 ) ) + ' KB)';
  39. const progress = Math.floor( ( event.loaded / event.total ) * 100 ) + '%';
  40. console.log( 'Loading', file.name, size, progress );
  41. } );
  42. if ( extension in fileHandlers ) {
  43. const handleRequest = fileHandlers[ extension ];
  44. if ( extension === 'js' || extension === 'json' ) {
  45. handleRequest( editor, manager, reader, file, this.texturePath );
  46. } else {
  47. handleRequest( editor, manager, reader, file );
  48. }
  49. } else {
  50. // TODO: UI feedback
  51. console.error( 'Unsupported file format (' + extension + ').' );
  52. }
  53. };
  54. }
  55. //
  56. const fileHandlers = {};
  57. fileHandlers[ '3dm' ] = function ( editor, manager, reader, file ) {
  58. reader.addEventListener( 'load', async function ( event ) {
  59. const contents = event.target.result;
  60. const { Rhino3dmLoader } = await import( 'three/addons/loaders/3DMLoader.js' );
  61. const loader = new Rhino3dmLoader();
  62. loader.setLibraryPath( '../examples/jsm/libs/rhino3dm/' );
  63. loader.parse( contents, function ( object ) {
  64. object.name = file.name;
  65. editor.execute( new AddObjectCommand( editor, object ) );
  66. }, function ( error ) {
  67. console.error( error );
  68. } );
  69. }, false );
  70. reader.readAsArrayBuffer( file );
  71. };
  72. fileHandlers[ '3ds' ] = function ( editor, manager, reader, file ) {
  73. reader.addEventListener( 'load', async function ( event ) {
  74. const { TDSLoader } = await import( 'three/addons/loaders/TDSLoader.js' );
  75. const loader = new TDSLoader();
  76. const object = loader.parse( event.target.result );
  77. editor.execute( new AddObjectCommand( editor, object ) );
  78. }, false );
  79. reader.readAsArrayBuffer( file );
  80. };
  81. fileHandlers[ '3mf' ] = function ( editor, manager, reader, file ) {
  82. reader.addEventListener( 'load', async function ( event ) {
  83. const { ThreeMFLoader } = await import( 'three/addons/loaders/3MFLoader.js' );
  84. const loader = new ThreeMFLoader();
  85. const object = loader.parse( event.target.result );
  86. editor.execute( new AddObjectCommand( editor, object ) );
  87. }, false );
  88. reader.readAsArrayBuffer( file );
  89. };
  90. fileHandlers[ 'amf' ] = function ( editor, manager, reader, file ) {
  91. reader.addEventListener( 'load', async function ( event ) {
  92. const { AMFLoader } = await import( 'three/addons/loaders/AMFLoader.js' );
  93. const loader = new AMFLoader();
  94. const amfobject = loader.parse( event.target.result );
  95. editor.execute( new AddObjectCommand( editor, amfobject ) );
  96. }, false );
  97. reader.readAsArrayBuffer( file );
  98. };
  99. fileHandlers[ 'dae' ] = function ( editor, manager, reader, file ) {
  100. reader.addEventListener( 'load', async function ( event ) {
  101. const contents = event.target.result;
  102. const { ColladaLoader } = await import( 'three/addons/loaders/ColladaLoader.js' );
  103. const loader = new ColladaLoader( manager );
  104. const collada = loader.parse( contents );
  105. collada.scene.name = file.name;
  106. editor.execute( new AddObjectCommand( editor, collada.scene ) );
  107. }, false );
  108. reader.readAsText( file );
  109. };
  110. fileHandlers[ 'drc' ] = function ( editor, manager, reader, file ) {
  111. reader.addEventListener( 'load', async function ( event ) {
  112. const contents = event.target.result;
  113. const { DRACOLoader } = await import( 'three/addons/loaders/DRACOLoader.js' );
  114. const loader = new DRACOLoader();
  115. loader.setDecoderPath( '../examples/jsm/libs/draco/' );
  116. loader.parse( contents, function ( geometry ) {
  117. let object;
  118. if ( geometry.index !== null ) {
  119. const material = new THREE.MeshStandardMaterial();
  120. object = new THREE.Mesh( geometry, material );
  121. object.name = file.name;
  122. } else {
  123. const material = new THREE.PointsMaterial( { size: 0.01 } );
  124. material.vertexColors = geometry.hasAttribute( 'color' );
  125. object = new THREE.Points( geometry, material );
  126. object.name = file.name;
  127. }
  128. loader.dispose();
  129. editor.execute( new AddObjectCommand( editor, object ) );
  130. } );
  131. }, false );
  132. reader.readAsArrayBuffer( file );
  133. };
  134. fileHandlers[ 'fbx' ] = function ( editor, manager, reader, file ) {
  135. reader.addEventListener( 'load', async function ( event ) {
  136. const contents = event.target.result;
  137. const { FBXLoader } = await import( 'three/addons/loaders/FBXLoader.js' );
  138. const loader = new FBXLoader( manager );
  139. const object = loader.parse( contents );
  140. editor.execute( new AddObjectCommand( editor, object ) );
  141. }, false );
  142. reader.readAsArrayBuffer( file );
  143. };
  144. fileHandlers[ 'glb' ] = function ( editor, manager, reader, file ) {
  145. reader.addEventListener( 'load', async function ( event ) {
  146. const contents = event.target.result;
  147. const loader = await createGLTFLoader( editor, manager );
  148. loader.parse( contents, '', function ( result ) {
  149. const scene = result.scene;
  150. scene.name = file.name;
  151. scene.animations.push( ...result.animations );
  152. editor.execute( new AddObjectCommand( editor, scene ) );
  153. loader.dracoLoader.dispose();
  154. loader.ktx2Loader.dispose();
  155. } );
  156. }, false );
  157. reader.readAsArrayBuffer( file );
  158. };
  159. fileHandlers[ 'gltf' ] = function ( editor, manager, reader, file ) {
  160. reader.addEventListener( 'load', async function ( event ) {
  161. const contents = event.target.result;
  162. const loader = await createGLTFLoader( editor, manager );
  163. loader.parse( contents, '', function ( result ) {
  164. const scene = result.scene;
  165. scene.name = file.name;
  166. scene.animations.push( ...result.animations );
  167. editor.execute( new AddObjectCommand( editor, scene ) );
  168. loader.dracoLoader.dispose();
  169. loader.ktx2Loader.dispose();
  170. } );
  171. }, false );
  172. reader.readAsArrayBuffer( file );
  173. };
  174. fileHandlers[ 'js' ] = fileHandlers[ 'json' ] = function ( editor, manager, reader, file, texturePath ) {
  175. reader.addEventListener( 'load', function ( event ) {
  176. const contents = event.target.result;
  177. // 2.0
  178. if ( contents.indexOf( 'postMessage' ) !== - 1 ) {
  179. const blob = new Blob( [ contents ], { type: 'text/javascript' } );
  180. const url = URL.createObjectURL( blob );
  181. const worker = new Worker( url );
  182. worker.onmessage = function ( event ) {
  183. event.data.metadata = { version: 2 };
  184. handleJSON( editor, texturePath, event.data );
  185. };
  186. worker.postMessage( Date.now() );
  187. return;
  188. }
  189. // >= 3.0
  190. let data;
  191. try {
  192. data = JSON.parse( contents );
  193. } catch ( error ) {
  194. alert( error );
  195. return;
  196. }
  197. handleJSON( editor, texturePath, data );
  198. }, false );
  199. reader.readAsText( file );
  200. };
  201. fileHandlers[ 'kmz' ] = function ( editor, manager, reader, file ) {
  202. reader.addEventListener( 'load', async function ( event ) {
  203. const { KMZLoader } = await import( 'three/addons/loaders/KMZLoader.js' );
  204. const loader = new KMZLoader();
  205. const collada = loader.parse( event.target.result );
  206. collada.scene.name = file.name;
  207. editor.execute( new AddObjectCommand( editor, collada.scene ) );
  208. }, false );
  209. reader.readAsArrayBuffer( file );
  210. };
  211. fileHandlers[ 'ldr' ] = fileHandlers[ 'mdp' ] = function ( editor, manager, reader, file ) {
  212. reader.addEventListener( 'load', async function ( event ) {
  213. const { LDrawLoader } = await import( 'three/addons/loaders/LDrawLoader.js' );
  214. const loader = new LDrawLoader();
  215. loader.setPath( '../../examples/models/ldraw/officialLibrary/' );
  216. loader.parse( event.target.result, function ( group ) {
  217. group.name = file.name;
  218. // Convert from LDraw coordinates: rotate 180 degrees around OX
  219. group.rotation.x = Math.PI;
  220. editor.execute( new AddObjectCommand( editor, group ) );
  221. } );
  222. }, false );
  223. reader.readAsText( file );
  224. };
  225. fileHandlers[ 'md2' ] = function ( editor, manager, reader, file ) {
  226. reader.addEventListener( 'load', async function ( event ) {
  227. const contents = event.target.result;
  228. const { MD2Loader } = await import( 'three/addons/loaders/MD2Loader.js' );
  229. const geometry = new MD2Loader().parse( contents );
  230. const material = new THREE.MeshStandardMaterial();
  231. const mesh = new THREE.Mesh( geometry, material );
  232. mesh.mixer = new THREE.AnimationMixer( mesh );
  233. mesh.name = file.name;
  234. mesh.animations.push( ...geometry.animations );
  235. editor.execute( new AddObjectCommand( editor, mesh ) );
  236. }, false );
  237. reader.readAsArrayBuffer( file );
  238. };
  239. fileHandlers[ 'obj' ] = function ( editor, manager, reader, file ) {
  240. reader.addEventListener( 'load', async function ( event ) {
  241. const contents = event.target.result;
  242. const { OBJLoader } = await import( 'three/addons/loaders/OBJLoader.js' );
  243. const object = new OBJLoader().parse( contents );
  244. object.name = file.name;
  245. editor.execute( new AddObjectCommand( editor, object ) );
  246. }, false );
  247. reader.readAsText( file );
  248. };
  249. fileHandlers[ 'pcd' ] = function ( editor, manager, reader, file ) {
  250. reader.addEventListener( 'load', async function ( event ) {
  251. const contents = event.target.result;
  252. const { PCDLoader } = await import( 'three/addons/loaders/PCDLoader.js' );
  253. const points = new PCDLoader().parse( contents );
  254. points.name = file.name;
  255. editor.execute( new AddObjectCommand( editor, points ) );
  256. }, false );
  257. reader.readAsArrayBuffer( file );
  258. };
  259. fileHandlers[ 'ply' ] = function ( editor, manager, reader, file ) {
  260. reader.addEventListener( 'load', async function ( event ) {
  261. const contents = event.target.result;
  262. const { PLYLoader } = await import( 'three/addons/loaders/PLYLoader.js' );
  263. const geometry = new PLYLoader().parse( contents );
  264. let object;
  265. if ( geometry.index !== null ) {
  266. const material = new THREE.MeshStandardMaterial();
  267. object = new THREE.Mesh( geometry, material );
  268. object.name = file.name;
  269. } else {
  270. const material = new THREE.PointsMaterial( { size: 0.01 } );
  271. material.vertexColors = geometry.hasAttribute( 'color' );
  272. object = new THREE.Points( geometry, material );
  273. object.name = file.name;
  274. }
  275. editor.execute( new AddObjectCommand( editor, object ) );
  276. }, false );
  277. reader.readAsArrayBuffer( file );
  278. };
  279. fileHandlers[ 'stl' ] = function ( editor, manager, reader, file ) {
  280. reader.addEventListener( 'load', async function ( event ) {
  281. const contents = event.target.result;
  282. const { STLLoader } = await import( 'three/addons/loaders/STLLoader.js' );
  283. const geometry = new STLLoader().parse( contents );
  284. const material = new THREE.MeshStandardMaterial();
  285. const mesh = new THREE.Mesh( geometry, material );
  286. mesh.name = file.name;
  287. editor.execute( new AddObjectCommand( editor, mesh ) );
  288. }, false );
  289. if ( reader.readAsBinaryString !== undefined ) {
  290. reader.readAsBinaryString( file );
  291. } else {
  292. reader.readAsArrayBuffer( file );
  293. }
  294. };
  295. fileHandlers[ 'svg' ] = function ( editor, manager, reader, file ) {
  296. reader.addEventListener( 'load', async function ( event ) {
  297. const contents = event.target.result;
  298. const { SVGLoader } = await import( 'three/addons/loaders/SVGLoader.js' );
  299. const loader = new SVGLoader();
  300. const paths = loader.parse( contents ).paths;
  301. //
  302. const group = new THREE.Group();
  303. group.name = file.name;
  304. group.scale.multiplyScalar( 0.1 );
  305. group.scale.y *= - 1;
  306. for ( let i = 0; i < paths.length; i ++ ) {
  307. const path = paths[ i ];
  308. const material = new THREE.MeshBasicMaterial( {
  309. color: path.color,
  310. depthWrite: false
  311. } );
  312. const shapes = SVGLoader.createShapes( path );
  313. for ( let j = 0; j < shapes.length; j ++ ) {
  314. const shape = shapes[ j ];
  315. const geometry = new THREE.ShapeGeometry( shape );
  316. const mesh = new THREE.Mesh( geometry, material );
  317. group.add( mesh );
  318. }
  319. }
  320. editor.execute( new AddObjectCommand( editor, group ) );
  321. }, false );
  322. reader.readAsText( file );
  323. };
  324. fileHandlers[ 'usdz' ] = function ( editor, manager, reader, file ) {
  325. reader.addEventListener( 'load', async function ( event ) {
  326. const contents = event.target.result;
  327. const { USDZLoader } = await import( 'three/addons/loaders/USDZLoader.js' );
  328. const group = new USDZLoader().parse( contents );
  329. group.name = file.name;
  330. editor.execute( new AddObjectCommand( editor, group ) );
  331. }, false );
  332. reader.readAsArrayBuffer( file );
  333. };
  334. fileHandlers[ 'vox' ] = function ( editor, manager, reader, file ) {
  335. reader.addEventListener( 'load', async function ( event ) {
  336. const contents = event.target.result;
  337. const { VOXLoader, VOXMesh } = await import( 'three/addons/loaders/VOXLoader.js' );
  338. const chunks = new VOXLoader().parse( contents );
  339. const group = new THREE.Group();
  340. group.name = file.name;
  341. for ( let i = 0; i < chunks.length; i ++ ) {
  342. const chunk = chunks[ i ];
  343. const mesh = new VOXMesh( chunk );
  344. group.add( mesh );
  345. }
  346. editor.execute( new AddObjectCommand( editor, group ) );
  347. }, false );
  348. reader.readAsArrayBuffer( file );
  349. };
  350. fileHandlers[ 'vtk' ] = fileHandlers[ 'vtp' ] = function ( editor, manager, reader, file ) {
  351. reader.addEventListener( 'load', async function ( event ) {
  352. const contents = event.target.result;
  353. const { VTKLoader } = await import( 'three/addons/loaders/VTKLoader.js' );
  354. const geometry = new VTKLoader().parse( contents );
  355. const material = new THREE.MeshStandardMaterial();
  356. const mesh = new THREE.Mesh( geometry, material );
  357. mesh.name = file.name;
  358. editor.execute( new AddObjectCommand( editor, mesh ) );
  359. }, false );
  360. reader.readAsArrayBuffer( file );
  361. };
  362. fileHandlers[ 'wrl' ] = function ( editor, manager, reader, file ) {
  363. reader.addEventListener( 'load', async function ( event ) {
  364. const contents = event.target.result;
  365. const { VRMLLoader } = await import( 'three/addons/loaders/VRMLLoader.js' );
  366. const result = new VRMLLoader().parse( contents );
  367. editor.execute( new SetSceneCommand( editor, result ) );
  368. }, false );
  369. reader.readAsText( file );
  370. };
  371. fileHandlers[ 'xyz' ] = function ( editor, manager, reader, file ) {
  372. reader.addEventListener( 'load', async function ( event ) {
  373. const contents = event.target.result;
  374. const { XYZLoader } = await import( 'three/addons/loaders/XYZLoader.js' );
  375. const geometry = new XYZLoader().parse( contents );
  376. const material = new THREE.PointsMaterial();
  377. material.vertexColors = geometry.hasAttribute( 'color' );
  378. const points = new THREE.Points( geometry, material );
  379. points.name = file.name;
  380. editor.execute( new AddObjectCommand( editor, points ) );
  381. }, false );
  382. reader.readAsText( file );
  383. };
  384. fileHandlers[ 'zip' ] = function ( editor, manager, reader, file ) {
  385. reader.addEventListener( 'load', function ( event ) {
  386. handleZIP( editor, event.target.result );
  387. }, false );
  388. reader.readAsArrayBuffer( file );
  389. };
  390. Loader.getSupportedFileFormats = function () {
  391. return Object.keys( fileHandlers ).sort();
  392. };
  393. //
  394. function handleJSON( editor, texturePath, data ) {
  395. if ( data.metadata === undefined ) { // 2.0
  396. data.metadata = { type: 'Geometry' };
  397. }
  398. if ( data.metadata.type === undefined ) { // 3.0
  399. data.metadata.type = 'Geometry';
  400. }
  401. if ( data.metadata.formatVersion !== undefined ) {
  402. data.metadata.version = data.metadata.formatVersion;
  403. }
  404. switch ( data.metadata.type.toLowerCase() ) {
  405. case 'buffergeometry':
  406. {
  407. const loader = new THREE.BufferGeometryLoader();
  408. const result = loader.parse( data );
  409. const mesh = new THREE.Mesh( result );
  410. editor.execute( new AddObjectCommand( editor, mesh ) );
  411. break;
  412. }
  413. case 'geometry':
  414. console.error( 'Loader: "Geometry" is no longer supported.' );
  415. break;
  416. case 'object':
  417. {
  418. const loader = new THREE.ObjectLoader();
  419. loader.setResourcePath( texturePath );
  420. loader.parse( data, function ( result ) {
  421. if ( result.isScene ) {
  422. editor.execute( new SetSceneCommand( editor, result ) );
  423. } else {
  424. editor.execute( new AddObjectCommand( editor, result ) );
  425. }
  426. } );
  427. break;
  428. }
  429. case 'app':
  430. editor.fromJSON( data );
  431. break;
  432. }
  433. }
  434. async function handleZIP( editor, contents ) {
  435. const zip = unzipSync( new Uint8Array( contents ) );
  436. const manager = new THREE.LoadingManager();
  437. manager.setURLModifier( function ( url ) {
  438. const file = zip[ url ];
  439. if ( file ) {
  440. console.log( 'Loading', url );
  441. const blob = new Blob( [ file.buffer ], { type: 'application/octet-stream' } );
  442. return URL.createObjectURL( blob );
  443. }
  444. return url;
  445. } );
  446. // Poly
  447. if ( zip[ 'model.obj' ] && zip[ 'materials.mtl' ] ) {
  448. const { MTLLoader } = await import( 'three/addons/loaders/MTLLoader.js' );
  449. const { OBJLoader } = await import( 'three/addons/loaders/OBJLoader.js' );
  450. const materials = new MTLLoader( manager ).parse( strFromU8( zip[ 'materials.mtl' ] ) );
  451. const object = new OBJLoader().setMaterials( materials ).parse( strFromU8( zip[ 'model.obj' ] ) );
  452. editor.execute( new AddObjectCommand( editor, object ) );
  453. return;
  454. }
  455. //
  456. for ( const path in zip ) {
  457. const file = zip[ path ];
  458. const extension = path.split( '.' ).pop().toLowerCase();
  459. switch ( extension ) {
  460. case 'fbx':
  461. {
  462. const { FBXLoader } = await import( 'three/addons/loaders/FBXLoader.js' );
  463. const loader = new FBXLoader( manager );
  464. const object = loader.parse( file.buffer );
  465. editor.execute( new AddObjectCommand( editor, object ) );
  466. break;
  467. }
  468. case 'glb':
  469. {
  470. const loader = await createGLTFLoader( editor, manager );
  471. loader.parse( file.buffer, '', function ( result ) {
  472. const scene = result.scene;
  473. scene.animations.push( ...result.animations );
  474. editor.execute( new AddObjectCommand( editor, scene ) );
  475. loader.dracoLoader.dispose();
  476. loader.ktx2Loader.dispose();
  477. } );
  478. break;
  479. }
  480. case 'gltf':
  481. {
  482. const loader = await createGLTFLoader( editor, manager );
  483. loader.parse( strFromU8( file ), '', function ( result ) {
  484. const scene = result.scene;
  485. scene.animations.push( ...result.animations );
  486. editor.execute( new AddObjectCommand( editor, scene ) );
  487. loader.dracoLoader.dispose();
  488. loader.ktx2Loader.dispose();
  489. } );
  490. break;
  491. }
  492. }
  493. }
  494. }
  495. async function createGLTFLoader( editor, manager ) {
  496. const { GLTFLoader } = await import( 'three/addons/loaders/GLTFLoader.js' );
  497. const { DRACOLoader } = await import( 'three/addons/loaders/DRACOLoader.js' );
  498. const { KTX2Loader } = await import( 'three/addons/loaders/KTX2Loader.js' );
  499. const { MeshoptDecoder } = await import( 'three/addons/libs/meshopt_decoder.module.js' );
  500. const dracoLoader = new DRACOLoader();
  501. dracoLoader.setDecoderPath( '../examples/jsm/libs/draco/gltf/' );
  502. const ktx2Loader = new KTX2Loader( manager );
  503. ktx2Loader.setTranscoderPath( '../examples/jsm/libs/basis/' );
  504. editor.signals.rendererDetectKTX2Support.dispatch( ktx2Loader );
  505. const loader = new GLTFLoader( manager );
  506. loader.setDRACOLoader( dracoLoader );
  507. loader.setKTX2Loader( ktx2Loader );
  508. loader.setMeshoptDecoder( MeshoptDecoder );
  509. return loader;
  510. }
  511. export { Loader };