Loader.js 23 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159
  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 { GLTFImportDialog } from './GLTFImportDialog.js';
  7. import { unzipSync, strFromU8 } from 'three/addons/libs/fflate.module.js';
  8. function Loader( editor ) {
  9. const scope = this;
  10. this.texturePath = '';
  11. this.loadItemList = function ( items ) {
  12. LoaderUtils.getFilesFromItemList( items, function ( files, filesMap ) {
  13. scope.loadFiles( files, filesMap );
  14. } );
  15. };
  16. this.loadFiles = function ( files, filesMap ) {
  17. if ( files.length > 0 ) {
  18. filesMap = filesMap || LoaderUtils.createFilesMap( files );
  19. const normalizeLookupPath = function ( path ) {
  20. let normalized = String( path || '' ).replace( /\\/g, '/' );
  21. const queryIndex = normalized.indexOf( '?' );
  22. if ( queryIndex !== - 1 ) normalized = normalized.slice( 0, queryIndex );
  23. const hashIndex = normalized.indexOf( '#' );
  24. if ( hashIndex !== - 1 ) normalized = normalized.slice( 0, hashIndex );
  25. while ( normalized.startsWith( './' ) ) normalized = normalized.slice( 2 );
  26. while ( normalized.startsWith( '../' ) ) normalized = normalized.slice( 3 );
  27. while ( normalized.startsWith( '/' ) ) normalized = normalized.slice( 1 );
  28. return normalized;
  29. };
  30. const createFileFinder = function ( map ) {
  31. const suffixMap = {};
  32. const warnedAmbiguous = new Set();
  33. const addCandidate = function ( suffix, candidate ) {
  34. if ( ! suffixMap[ suffix ] ) suffixMap[ suffix ] = [];
  35. suffixMap[ suffix ].push( candidate );
  36. };
  37. for ( const rawKey in map ) {
  38. const key = normalizeLookupPath( rawKey );
  39. const file = map[ rawKey ];
  40. if ( key === '' || ! file ) continue;
  41. const parts = key.split( '/' );
  42. for ( let i = 0; i < parts.length; i ++ ) {
  43. const suffix = parts.slice( i ).join( '/' );
  44. if ( suffix !== '' ) addCandidate( suffix, { key, file } );
  45. }
  46. }
  47. for ( const suffix in suffixMap ) {
  48. suffixMap[ suffix ].sort( function ( a, b ) {
  49. if ( a.key.length !== b.key.length ) return a.key.length - b.key.length;
  50. if ( a.key < b.key ) return - 1;
  51. if ( a.key > b.key ) return 1;
  52. return 0;
  53. } );
  54. }
  55. return function findFile( url ) {
  56. const lookup = normalizeLookupPath( url );
  57. if ( lookup === '' ) return null;
  58. const candidates = suffixMap[ lookup ];
  59. if ( ! candidates || candidates.length === 0 ) return null;
  60. if ( candidates.length === 1 ) return candidates[ 0 ];
  61. for ( let i = 0; i < candidates.length; i ++ ) {
  62. if ( candidates[ i ].key === lookup ) return candidates[ i ];
  63. }
  64. if ( ! warnedAmbiguous.has( lookup ) ) {
  65. console.warn( 'Loader: Ambiguous file reference "' + lookup + '". Using "' + candidates[ 0 ].key + '".' );
  66. warnedAmbiguous.add( lookup );
  67. }
  68. return candidates[ 0 ];
  69. };
  70. };
  71. const findFile = createFileFinder( filesMap );
  72. const manager = new THREE.LoadingManager();
  73. manager.setURLModifier( function ( url ) {
  74. const resolved = findFile( url );
  75. if ( resolved ) {
  76. console.log( 'Loading', url );
  77. return URL.createObjectURL( resolved.file );
  78. }
  79. return url;
  80. } );
  81. manager.addHandler( /\.tga$/i, new TGALoader() );
  82. for ( let i = 0; i < files.length; i ++ ) {
  83. scope.loadFile( files[ i ], manager );
  84. }
  85. }
  86. };
  87. this.loadFile = function ( file, manager ) {
  88. const filename = file.name;
  89. const extension = filename.split( '.' ).pop().toLowerCase();
  90. const reader = new FileReader();
  91. reader.addEventListener( 'progress', function ( event ) {
  92. const size = '(' + editor.utils.formatNumber( Math.floor( event.total / 1000 ) ) + ' KB)';
  93. const progress = Math.floor( ( event.loaded / event.total ) * 100 ) + '%';
  94. console.log( 'Loading', filename, size, progress );
  95. } );
  96. switch ( extension ) {
  97. case '3dm':
  98. {
  99. reader.addEventListener( 'load', async function ( event ) {
  100. const contents = event.target.result;
  101. const { Rhino3dmLoader } = await import( 'three/addons/loaders/3DMLoader.js' );
  102. const loader = new Rhino3dmLoader();
  103. loader.setLibraryPath( '../examples/jsm/libs/rhino3dm/' );
  104. loader.parse( contents, function ( object ) {
  105. object.name = filename;
  106. editor.execute( new AddObjectCommand( editor, object ) );
  107. }, function ( error ) {
  108. console.error( error );
  109. } );
  110. }, false );
  111. reader.readAsArrayBuffer( file );
  112. break;
  113. }
  114. case '3ds':
  115. {
  116. reader.addEventListener( 'load', async function ( event ) {
  117. const { TDSLoader } = await import( 'three/addons/loaders/TDSLoader.js' );
  118. const loader = new TDSLoader();
  119. const object = loader.parse( event.target.result );
  120. editor.execute( new AddObjectCommand( editor, object ) );
  121. }, false );
  122. reader.readAsArrayBuffer( file );
  123. break;
  124. }
  125. case '3mf':
  126. {
  127. reader.addEventListener( 'load', async function ( event ) {
  128. const { ThreeMFLoader } = await import( 'three/addons/loaders/3MFLoader.js' );
  129. const loader = new ThreeMFLoader();
  130. const object = loader.parse( event.target.result );
  131. editor.execute( new AddObjectCommand( editor, object ) );
  132. }, false );
  133. reader.readAsArrayBuffer( file );
  134. break;
  135. }
  136. case 'amf':
  137. {
  138. reader.addEventListener( 'load', async function ( event ) {
  139. const { AMFLoader } = await import( 'three/addons/loaders/AMFLoader.js' );
  140. const loader = new AMFLoader();
  141. const amfobject = loader.parse( event.target.result );
  142. editor.execute( new AddObjectCommand( editor, amfobject ) );
  143. }, false );
  144. reader.readAsArrayBuffer( file );
  145. break;
  146. }
  147. case 'dae':
  148. {
  149. reader.addEventListener( 'load', async function ( event ) {
  150. const contents = event.target.result;
  151. const { ColladaLoader } = await import( 'three/addons/loaders/ColladaLoader.js' );
  152. const loader = new ColladaLoader( manager );
  153. const collada = loader.parse( contents );
  154. collada.scene.name = filename;
  155. editor.execute( new AddObjectCommand( editor, collada.scene ) );
  156. }, false );
  157. reader.readAsText( file );
  158. break;
  159. }
  160. case 'drc':
  161. {
  162. reader.addEventListener( 'load', async function ( event ) {
  163. const contents = event.target.result;
  164. const { DRACOLoader } = await import( 'three/addons/loaders/DRACOLoader.js' );
  165. const loader = new DRACOLoader();
  166. loader.setDecoderPath( '../examples/jsm/libs/draco/' );
  167. loader.parse( contents, function ( geometry ) {
  168. let object;
  169. if ( geometry.index !== null ) {
  170. const material = new THREE.MeshStandardMaterial();
  171. object = new THREE.Mesh( geometry, material );
  172. object.name = filename;
  173. } else {
  174. const material = new THREE.PointsMaterial( { size: 0.01 } );
  175. material.vertexColors = geometry.hasAttribute( 'color' );
  176. object = new THREE.Points( geometry, material );
  177. object.name = filename;
  178. }
  179. loader.dispose();
  180. editor.execute( new AddObjectCommand( editor, object ) );
  181. } );
  182. }, false );
  183. reader.readAsArrayBuffer( file );
  184. break;
  185. }
  186. case 'fbx':
  187. {
  188. reader.addEventListener( 'load', async function ( event ) {
  189. const contents = event.target.result;
  190. const { FBXLoader } = await import( 'three/addons/loaders/FBXLoader.js' );
  191. const loader = new FBXLoader( manager );
  192. const object = loader.parse( contents );
  193. editor.execute( new AddObjectCommand( editor, object ) );
  194. }, false );
  195. reader.readAsArrayBuffer( file );
  196. break;
  197. }
  198. case 'glb':
  199. {
  200. reader.addEventListener( 'load', async function ( event ) {
  201. const contents = event.target.result;
  202. try {
  203. const dialog = new GLTFImportDialog( editor.strings );
  204. const options = await dialog.show();
  205. const loader = await createGLTFLoader();
  206. loader.parse( contents, '', function ( result ) {
  207. const scene = result.scene;
  208. scene.name = filename;
  209. scene.animations.push( ...result.animations );
  210. if ( options.asScene ) {
  211. editor.execute( new SetSceneCommand( editor, scene ) );
  212. } else {
  213. editor.execute( new AddObjectCommand( editor, scene ) );
  214. }
  215. loader.dracoLoader.dispose();
  216. loader.ktx2Loader.dispose();
  217. } );
  218. } catch ( e ) {
  219. // Import cancelled
  220. }
  221. }, false );
  222. reader.readAsArrayBuffer( file );
  223. break;
  224. }
  225. case 'gltf':
  226. {
  227. reader.addEventListener( 'load', async function ( event ) {
  228. const contents = event.target.result;
  229. try {
  230. const dialog = new GLTFImportDialog( editor.strings );
  231. const options = await dialog.show();
  232. const loader = await createGLTFLoader( manager );
  233. loader.parse( contents, '', function ( result ) {
  234. const scene = result.scene;
  235. scene.name = filename;
  236. scene.animations.push( ...result.animations );
  237. if ( options.asScene ) {
  238. editor.execute( new SetSceneCommand( editor, scene ) );
  239. } else {
  240. editor.execute( new AddObjectCommand( editor, scene ) );
  241. }
  242. loader.dracoLoader.dispose();
  243. loader.ktx2Loader.dispose();
  244. } );
  245. } catch ( e ) {
  246. // Import cancelled
  247. }
  248. }, false );
  249. reader.readAsArrayBuffer( file );
  250. break;
  251. }
  252. case 'js':
  253. case 'json':
  254. {
  255. reader.addEventListener( 'load', function ( event ) {
  256. const contents = event.target.result;
  257. // 2.0
  258. if ( contents.indexOf( 'postMessage' ) !== - 1 ) {
  259. const blob = new Blob( [ contents ], { type: 'text/javascript' } );
  260. const url = URL.createObjectURL( blob );
  261. const worker = new Worker( url );
  262. worker.onmessage = function ( event ) {
  263. event.data.metadata = { version: 2 };
  264. handleJSON( event.data );
  265. };
  266. worker.postMessage( Date.now() );
  267. return;
  268. }
  269. // >= 3.0
  270. let data;
  271. try {
  272. data = JSON.parse( contents );
  273. } catch ( error ) {
  274. alert( error );
  275. return;
  276. }
  277. handleJSON( data );
  278. }, false );
  279. reader.readAsText( file );
  280. break;
  281. }
  282. case 'kmz':
  283. {
  284. reader.addEventListener( 'load', async function ( event ) {
  285. const { KMZLoader } = await import( 'three/addons/loaders/KMZLoader.js' );
  286. const loader = new KMZLoader();
  287. const collada = loader.parse( event.target.result );
  288. collada.scene.name = filename;
  289. editor.execute( new AddObjectCommand( editor, collada.scene ) );
  290. }, false );
  291. reader.readAsArrayBuffer( file );
  292. break;
  293. }
  294. case 'ldr':
  295. case 'mpd':
  296. {
  297. reader.addEventListener( 'load', async function ( event ) {
  298. const { LDrawLoader } = await import( 'three/addons/loaders/LDrawLoader.js' );
  299. const loader = new LDrawLoader();
  300. loader.setPath( '../../examples/models/ldraw/officialLibrary/' );
  301. loader.parse( event.target.result, function ( group ) {
  302. group.name = filename;
  303. // Convert from LDraw coordinates: rotate 180 degrees around OX
  304. group.rotation.x = Math.PI;
  305. editor.execute( new AddObjectCommand( editor, group ) );
  306. } );
  307. }, false );
  308. reader.readAsText( file );
  309. break;
  310. }
  311. case 'md2':
  312. {
  313. reader.addEventListener( 'load', async function ( event ) {
  314. const contents = event.target.result;
  315. const { MD2Loader } = await import( 'three/addons/loaders/MD2Loader.js' );
  316. const geometry = new MD2Loader().parse( contents );
  317. const material = new THREE.MeshStandardMaterial();
  318. const mesh = new THREE.Mesh( geometry, material );
  319. mesh.mixer = new THREE.AnimationMixer( mesh );
  320. mesh.name = filename;
  321. mesh.animations.push( ...geometry.animations );
  322. editor.execute( new AddObjectCommand( editor, mesh ) );
  323. }, false );
  324. reader.readAsArrayBuffer( file );
  325. break;
  326. }
  327. case 'obj':
  328. {
  329. reader.addEventListener( 'load', async function ( event ) {
  330. const contents = event.target.result;
  331. const { OBJLoader } = await import( 'three/addons/loaders/OBJLoader.js' );
  332. const object = new OBJLoader().parse( contents );
  333. object.name = filename;
  334. editor.execute( new AddObjectCommand( editor, object ) );
  335. }, false );
  336. reader.readAsText( file );
  337. break;
  338. }
  339. case 'pcd':
  340. {
  341. reader.addEventListener( 'load', async function ( event ) {
  342. const contents = event.target.result;
  343. const { PCDLoader } = await import( 'three/addons/loaders/PCDLoader.js' );
  344. const points = new PCDLoader().parse( contents );
  345. points.name = filename;
  346. editor.execute( new AddObjectCommand( editor, points ) );
  347. }, false );
  348. reader.readAsArrayBuffer( file );
  349. break;
  350. }
  351. case 'ply':
  352. {
  353. reader.addEventListener( 'load', async function ( event ) {
  354. const contents = event.target.result;
  355. const { PLYLoader } = await import( 'three/addons/loaders/PLYLoader.js' );
  356. const geometry = new PLYLoader().parse( contents );
  357. let object;
  358. if ( geometry.index !== null ) {
  359. const material = new THREE.MeshStandardMaterial();
  360. object = new THREE.Mesh( geometry, material );
  361. object.name = filename;
  362. } else {
  363. const material = new THREE.PointsMaterial( { size: 0.01 } );
  364. material.vertexColors = geometry.hasAttribute( 'color' );
  365. object = new THREE.Points( geometry, material );
  366. object.name = filename;
  367. }
  368. editor.execute( new AddObjectCommand( editor, object ) );
  369. }, false );
  370. reader.readAsArrayBuffer( file );
  371. break;
  372. }
  373. case 'stl':
  374. {
  375. reader.addEventListener( 'load', async function ( event ) {
  376. const contents = event.target.result;
  377. const { STLLoader } = await import( 'three/addons/loaders/STLLoader.js' );
  378. const geometry = new STLLoader().parse( contents );
  379. const material = new THREE.MeshStandardMaterial();
  380. const mesh = new THREE.Mesh( geometry, material );
  381. mesh.name = filename;
  382. editor.execute( new AddObjectCommand( editor, mesh ) );
  383. }, false );
  384. if ( reader.readAsBinaryString !== undefined ) {
  385. reader.readAsBinaryString( file );
  386. } else {
  387. reader.readAsArrayBuffer( file );
  388. }
  389. break;
  390. }
  391. case 'svg':
  392. {
  393. reader.addEventListener( 'load', async function ( event ) {
  394. const contents = event.target.result;
  395. const { SVGLoader } = await import( 'three/addons/loaders/SVGLoader.js' );
  396. const loader = new SVGLoader();
  397. const paths = loader.parse( contents ).paths;
  398. //
  399. const group = new THREE.Group();
  400. group.name = filename;
  401. group.scale.multiplyScalar( 0.1 );
  402. group.scale.y *= - 1;
  403. for ( let i = 0; i < paths.length; i ++ ) {
  404. const path = paths[ i ];
  405. const material = new THREE.MeshBasicMaterial( {
  406. color: path.color,
  407. depthWrite: false
  408. } );
  409. const shapes = SVGLoader.createShapes( path );
  410. for ( let j = 0; j < shapes.length; j ++ ) {
  411. const shape = shapes[ j ];
  412. const geometry = new THREE.ShapeGeometry( shape );
  413. const mesh = new THREE.Mesh( geometry, material );
  414. group.add( mesh );
  415. }
  416. }
  417. editor.execute( new AddObjectCommand( editor, group ) );
  418. }, false );
  419. reader.readAsText( file );
  420. break;
  421. }
  422. case 'usd':
  423. case 'usda':
  424. case 'usdc':
  425. case 'usdz':
  426. {
  427. reader.addEventListener( 'load', async function ( event ) {
  428. const contents = event.target.result;
  429. const { USDLoader } = await import( 'three/addons/loaders/USDLoader.js' );
  430. const loader = new USDLoader( manager );
  431. const group = loader.parse( contents );
  432. group.name = filename;
  433. editor.execute( new AddObjectCommand( editor, group ) );
  434. }, false );
  435. reader.readAsArrayBuffer( file );
  436. break;
  437. }
  438. case 'vox':
  439. {
  440. reader.addEventListener( 'load', async function ( event ) {
  441. const contents = event.target.result;
  442. const { VOXLoader } = await import( 'three/addons/loaders/VOXLoader.js' );
  443. const { scene } = new VOXLoader().parse( contents );
  444. scene.name = filename;
  445. editor.execute( new AddObjectCommand( editor, scene ) );
  446. }, false );
  447. reader.readAsArrayBuffer( file );
  448. break;
  449. }
  450. case 'vtk':
  451. case 'vtp':
  452. {
  453. reader.addEventListener( 'load', async function ( event ) {
  454. const contents = event.target.result;
  455. const { VTKLoader } = await import( 'three/addons/loaders/VTKLoader.js' );
  456. const geometry = new VTKLoader().parse( contents );
  457. const material = new THREE.MeshStandardMaterial();
  458. const mesh = new THREE.Mesh( geometry, material );
  459. mesh.name = filename;
  460. editor.execute( new AddObjectCommand( editor, mesh ) );
  461. }, false );
  462. reader.readAsArrayBuffer( file );
  463. break;
  464. }
  465. case 'wrl':
  466. {
  467. reader.addEventListener( 'load', async function ( event ) {
  468. const contents = event.target.result;
  469. const { VRMLLoader } = await import( 'three/addons/loaders/VRMLLoader.js' );
  470. const result = new VRMLLoader().parse( contents );
  471. editor.execute( new AddObjectCommand( editor, result ) );
  472. }, false );
  473. reader.readAsText( file );
  474. break;
  475. }
  476. case 'xyz':
  477. {
  478. reader.addEventListener( 'load', async function ( event ) {
  479. const contents = event.target.result;
  480. const { XYZLoader } = await import( 'three/addons/loaders/XYZLoader.js' );
  481. const geometry = new XYZLoader().parse( contents );
  482. const material = new THREE.PointsMaterial();
  483. material.vertexColors = geometry.hasAttribute( 'color' );
  484. const points = new THREE.Points( geometry, material );
  485. points.name = filename;
  486. editor.execute( new AddObjectCommand( editor, points ) );
  487. }, false );
  488. reader.readAsText( file );
  489. break;
  490. }
  491. case 'zip':
  492. {
  493. reader.addEventListener( 'load', function ( event ) {
  494. handleZIP( event.target.result );
  495. }, false );
  496. reader.readAsArrayBuffer( file );
  497. break;
  498. }
  499. case 'bmp':
  500. case 'gif':
  501. case 'jpg':
  502. case 'jpeg':
  503. case 'png':
  504. case 'tga':
  505. break; // Image files are handled as textures by other loaders
  506. default:
  507. console.error( 'Unsupported file format (' + extension + ').' );
  508. break;
  509. }
  510. };
  511. function handleJSON( data ) {
  512. if ( data.metadata === undefined ) { // 2.0
  513. data.metadata = { type: 'Geometry' };
  514. }
  515. if ( data.metadata.type === undefined ) { // 3.0
  516. data.metadata.type = 'Geometry';
  517. }
  518. if ( data.metadata.formatVersion !== undefined ) {
  519. data.metadata.version = data.metadata.formatVersion;
  520. }
  521. switch ( data.metadata.type.toLowerCase() ) {
  522. case 'buffergeometry':
  523. {
  524. const loader = new THREE.BufferGeometryLoader();
  525. const result = loader.parse( data );
  526. const mesh = new THREE.Mesh( result );
  527. editor.execute( new AddObjectCommand( editor, mesh ) );
  528. break;
  529. }
  530. case 'geometry':
  531. console.error( 'Loader: "Geometry" is no longer supported.' );
  532. break;
  533. case 'object':
  534. {
  535. const loader = new THREE.ObjectLoader();
  536. loader.setResourcePath( scope.texturePath );
  537. loader.parse( data, function ( result ) {
  538. editor.execute( new AddObjectCommand( editor, result ) );
  539. } );
  540. break;
  541. }
  542. case 'app':
  543. editor.fromJSON( data );
  544. break;
  545. }
  546. }
  547. async function handleZIP( contents ) {
  548. const zip = unzipSync( new Uint8Array( contents ) );
  549. const manager = new THREE.LoadingManager();
  550. manager.setURLModifier( function ( url ) {
  551. const file = zip[ url ];
  552. if ( file ) {
  553. console.log( 'Loading', url );
  554. const blob = new Blob( [ file.buffer ], { type: 'application/octet-stream' } );
  555. return URL.createObjectURL( blob );
  556. }
  557. return url;
  558. } );
  559. // Poly
  560. if ( zip[ 'model.obj' ] && zip[ 'materials.mtl' ] ) {
  561. const { MTLLoader } = await import( 'three/addons/loaders/MTLLoader.js' );
  562. const { OBJLoader } = await import( 'three/addons/loaders/OBJLoader.js' );
  563. const materials = new MTLLoader( manager ).parse( strFromU8( zip[ 'materials.mtl' ] ) );
  564. const object = new OBJLoader().setMaterials( materials ).parse( strFromU8( zip[ 'model.obj' ] ) );
  565. editor.execute( new AddObjectCommand( editor, object ) );
  566. return;
  567. }
  568. //
  569. for ( const path in zip ) {
  570. const file = zip[ path ];
  571. const extension = path.split( '.' ).pop().toLowerCase();
  572. switch ( extension ) {
  573. case 'fbx':
  574. {
  575. const { FBXLoader } = await import( 'three/addons/loaders/FBXLoader.js' );
  576. const loader = new FBXLoader( manager );
  577. const object = loader.parse( file.buffer );
  578. editor.execute( new AddObjectCommand( editor, object ) );
  579. break;
  580. }
  581. case 'glb':
  582. {
  583. try {
  584. const dialog = new GLTFImportDialog( editor.strings );
  585. const options = await dialog.show();
  586. const loader = await createGLTFLoader();
  587. loader.parse( file.buffer, '', function ( result ) {
  588. const scene = result.scene;
  589. scene.animations.push( ...result.animations );
  590. if ( options.asScene ) {
  591. editor.execute( new SetSceneCommand( editor, scene ) );
  592. } else {
  593. editor.execute( new AddObjectCommand( editor, scene ) );
  594. }
  595. loader.dracoLoader.dispose();
  596. loader.ktx2Loader.dispose();
  597. } );
  598. } catch ( e ) {
  599. // Import cancelled
  600. }
  601. break;
  602. }
  603. case 'gltf':
  604. {
  605. try {
  606. const dialog = new GLTFImportDialog( editor.strings );
  607. const options = await dialog.show();
  608. const loader = await createGLTFLoader( manager );
  609. loader.parse( strFromU8( file ), '', function ( result ) {
  610. const scene = result.scene;
  611. scene.animations.push( ...result.animations );
  612. if ( options.asScene ) {
  613. editor.execute( new SetSceneCommand( editor, scene ) );
  614. } else {
  615. editor.execute( new AddObjectCommand( editor, scene ) );
  616. }
  617. loader.dracoLoader.dispose();
  618. loader.ktx2Loader.dispose();
  619. } );
  620. } catch ( e ) {
  621. // Import cancelled
  622. }
  623. break;
  624. }
  625. }
  626. }
  627. }
  628. async function createGLTFLoader( manager ) {
  629. const { GLTFLoader } = await import( 'three/addons/loaders/GLTFLoader.js' );
  630. const { DRACOLoader } = await import( 'three/addons/loaders/DRACOLoader.js' );
  631. const { KTX2Loader } = await import( 'three/addons/loaders/KTX2Loader.js' );
  632. const { MeshoptDecoder } = await import( 'three/addons/libs/meshopt_decoder.module.js' );
  633. const dracoLoader = new DRACOLoader();
  634. dracoLoader.setDecoderPath( '../examples/jsm/libs/draco/gltf/' );
  635. const ktx2Loader = new KTX2Loader( manager );
  636. ktx2Loader.setTranscoderPath( '../examples/jsm/libs/basis/' );
  637. editor.signals.rendererDetectKTX2Support.dispatch( ktx2Loader );
  638. const loader = new GLTFLoader( manager );
  639. loader.setDRACOLoader( dracoLoader );
  640. loader.setKTX2Loader( ktx2Loader );
  641. loader.setMeshoptDecoder( MeshoptDecoder );
  642. return loader;
  643. }
  644. }
  645. export { Loader };
粤ICP备19079148号