Loader.js 23 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162
  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 = path.toShapes();
  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. loader.parse( contents, '', function ( group ) {
  438. group.name = filename;
  439. editor.execute( new AddObjectCommand( editor, group ) );
  440. } );
  441. }, false );
  442. reader.readAsArrayBuffer( file );
  443. break;
  444. }
  445. case 'vox':
  446. {
  447. reader.addEventListener( 'load', async function ( event ) {
  448. const contents = event.target.result;
  449. const { VOXLoader } = await import( 'three/addons/loaders/VOXLoader.js' );
  450. const { scene } = new VOXLoader().parse( contents );
  451. scene.name = filename;
  452. editor.execute( new AddObjectCommand( editor, scene ) );
  453. }, false );
  454. reader.readAsArrayBuffer( file );
  455. break;
  456. }
  457. case 'wrl':
  458. {
  459. reader.addEventListener( 'load', async function ( event ) {
  460. const contents = event.target.result;
  461. const { VRMLLoader } = await import( 'three/addons/loaders/VRMLLoader.js' );
  462. const result = new VRMLLoader().parse( contents );
  463. editor.execute( new AddObjectCommand( editor, result ) );
  464. }, false );
  465. reader.readAsText( file );
  466. break;
  467. }
  468. case 'xyz':
  469. {
  470. reader.addEventListener( 'load', async function ( event ) {
  471. const contents = event.target.result;
  472. const { XYZLoader } = await import( 'three/addons/loaders/XYZLoader.js' );
  473. const geometry = new XYZLoader().parse( contents );
  474. const material = new THREE.PointsMaterial();
  475. material.vertexColors = geometry.hasAttribute( 'color' );
  476. const points = new THREE.Points( geometry, material );
  477. points.name = filename;
  478. editor.execute( new AddObjectCommand( editor, points ) );
  479. }, false );
  480. reader.readAsText( file );
  481. break;
  482. }
  483. case 'zip':
  484. {
  485. reader.addEventListener( 'load', function ( event ) {
  486. handleZIP( event.target.result );
  487. }, false );
  488. reader.readAsArrayBuffer( file );
  489. break;
  490. }
  491. case 'bmp':
  492. case 'gif':
  493. case 'jpg':
  494. case 'jpeg':
  495. case 'png':
  496. case 'tga':
  497. break; // Image files are handled as textures by other loaders
  498. default:
  499. console.error( 'Unsupported file format (' + extension + ').' );
  500. break;
  501. }
  502. };
  503. function handleJSON( data ) {
  504. if ( data.metadata === undefined ) { // 2.0
  505. data.metadata = { type: 'Geometry' };
  506. }
  507. if ( data.metadata.type === undefined ) { // 3.0
  508. data.metadata.type = 'Geometry';
  509. }
  510. if ( data.metadata.formatVersion !== undefined ) {
  511. data.metadata.version = data.metadata.formatVersion;
  512. }
  513. switch ( data.metadata.type.toLowerCase() ) {
  514. case 'buffergeometry':
  515. {
  516. const loader = new THREE.BufferGeometryLoader();
  517. const result = loader.parse( data );
  518. const mesh = new THREE.Mesh( result );
  519. editor.execute( new AddObjectCommand( editor, mesh ) );
  520. break;
  521. }
  522. case 'geometry':
  523. console.error( 'Loader: "Geometry" is no longer supported.' );
  524. break;
  525. case 'object':
  526. {
  527. const loader = new THREE.ObjectLoader();
  528. loader.setResourcePath( scope.texturePath );
  529. loader.parse( data, function ( result ) {
  530. editor.execute( new AddObjectCommand( editor, result ) );
  531. } );
  532. break;
  533. }
  534. case 'app':
  535. editor.fromJSON( data );
  536. break;
  537. }
  538. }
  539. async function handleZIP( contents ) {
  540. const zip = unzipSync( new Uint8Array( contents ) );
  541. // Build a lookup map with NFC-normalized keys to handle
  542. // unicode normalization differences (e.g. NFD vs NFC)
  543. const zipLookup = {};
  544. for ( const path in zip ) {
  545. zipLookup[ path.normalize( 'NFC' ) ] = zip[ path ];
  546. }
  547. const manager = new THREE.LoadingManager();
  548. manager.setURLModifier( function ( url ) {
  549. const normalized = decodeURIComponent( url ).normalize( 'NFC' );
  550. const file = zipLookup[ normalized ];
  551. if ( file ) {
  552. console.log( 'Loading', url );
  553. const blob = new Blob( [ file.buffer ], { type: 'application/octet-stream' } );
  554. return URL.createObjectURL( blob );
  555. }
  556. return url;
  557. } );
  558. // Poly
  559. if ( zip[ 'model.obj' ] && zip[ 'materials.mtl' ] ) {
  560. const { MTLLoader } = await import( 'three/addons/loaders/MTLLoader.js' );
  561. const { OBJLoader } = await import( 'three/addons/loaders/OBJLoader.js' );
  562. const materials = new MTLLoader( manager ).parse( strFromU8( zip[ 'materials.mtl' ] ) );
  563. const object = new OBJLoader().setMaterials( materials ).parse( strFromU8( zip[ 'model.obj' ] ) );
  564. editor.execute( new AddObjectCommand( editor, object ) );
  565. return;
  566. }
  567. //
  568. for ( const path in zip ) {
  569. const file = zip[ path ];
  570. const extension = path.split( '.' ).pop().toLowerCase();
  571. switch ( extension ) {
  572. case 'fbx':
  573. {
  574. const { FBXLoader } = await import( 'three/addons/loaders/FBXLoader.js' );
  575. const loader = new FBXLoader( manager );
  576. const object = loader.parse( file.buffer );
  577. editor.execute( new AddObjectCommand( editor, object ) );
  578. break;
  579. }
  580. case 'glb':
  581. {
  582. try {
  583. const dialog = new GLTFImportDialog( editor.strings );
  584. const options = await dialog.show();
  585. const loader = await createGLTFLoader();
  586. loader.parse( file.buffer, '', function ( result ) {
  587. const scene = result.scene;
  588. scene.animations.push( ...result.animations );
  589. if ( options.asScene ) {
  590. editor.execute( new SetSceneCommand( editor, scene ) );
  591. } else {
  592. editor.execute( new AddObjectCommand( editor, scene ) );
  593. }
  594. loader.dracoLoader.dispose();
  595. loader.ktx2Loader.dispose();
  596. } );
  597. } catch ( e ) {
  598. // Import cancelled
  599. }
  600. break;
  601. }
  602. case 'gltf':
  603. {
  604. try {
  605. const dialog = new GLTFImportDialog( editor.strings );
  606. const options = await dialog.show();
  607. const loader = await createGLTFLoader( manager );
  608. loader.parse( strFromU8( file ), '', function ( result ) {
  609. const scene = result.scene;
  610. scene.animations.push( ...result.animations );
  611. if ( options.asScene ) {
  612. editor.execute( new SetSceneCommand( editor, scene ) );
  613. } else {
  614. editor.execute( new AddObjectCommand( editor, scene ) );
  615. }
  616. loader.dracoLoader.dispose();
  617. loader.ktx2Loader.dispose();
  618. } );
  619. } catch ( e ) {
  620. // Import cancelled
  621. }
  622. break;
  623. }
  624. }
  625. }
  626. }
  627. async function createGLTFLoader( manager ) {
  628. const { GLTFLoader } = await import( 'three/addons/loaders/GLTFLoader.js' );
  629. const { DRACOLoader } = await import( 'three/addons/loaders/DRACOLoader.js' );
  630. const { KTX2Loader } = await import( 'three/addons/loaders/KTX2Loader.js' );
  631. const { MeshoptDecoder } = await import( 'three/addons/libs/meshopt_decoder.module.js' );
  632. const dracoLoader = new DRACOLoader();
  633. dracoLoader.setDecoderPath( '../examples/jsm/libs/draco/gltf/' );
  634. const ktx2Loader = new KTX2Loader( manager );
  635. ktx2Loader.setTranscoderPath( '../examples/jsm/libs/basis/' );
  636. editor.signals.rendererDetectKTX2Support.dispatch( ktx2Loader );
  637. const loader = new GLTFLoader( manager );
  638. loader.setDRACOLoader( dracoLoader );
  639. loader.setKTX2Loader( ktx2Loader );
  640. loader.setMeshoptDecoder( MeshoptDecoder );
  641. return loader;
  642. }
  643. }
  644. export { Loader };
粤ICP备19079148号