Loader.js 23 KB

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