LWOLoader.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  1. ( function () {
  2. /**
  3. * @version 1.1.1
  4. *
  5. * @desc Load files in LWO3 and LWO2 format on Three.js
  6. *
  7. * LWO3 format specification:
  8. * https://static.lightwave3d.com/sdk/2019/html/filefmts/lwo3.html
  9. *
  10. * LWO2 format specification:
  11. * https://static.lightwave3d.com/sdk/2019/html/filefmts/lwo2.html
  12. *
  13. **/
  14. let _lwoTree;
  15. class LWOLoader extends THREE.Loader {
  16. constructor( manager, parameters = {} ) {
  17. super( manager );
  18. this.resourcePath = parameters.resourcePath !== undefined ? parameters.resourcePath : '';
  19. }
  20. load( url, onLoad, onProgress, onError ) {
  21. const scope = this;
  22. const path = scope.path === '' ? extractParentUrl( url, 'Objects' ) : scope.path; // give the mesh a default name based on the filename
  23. const modelName = url.split( path ).pop().split( '.' )[ 0 ];
  24. const loader = new THREE.FileLoader( this.manager );
  25. loader.setPath( scope.path );
  26. loader.setResponseType( 'arraybuffer' );
  27. loader.load( url, function ( buffer ) {
  28. // console.time( 'Total parsing: ' );
  29. try {
  30. onLoad( scope.parse( buffer, path, modelName ) );
  31. } catch ( e ) {
  32. if ( onError ) {
  33. onError( e );
  34. } else {
  35. console.error( e );
  36. }
  37. scope.manager.itemError( url );
  38. } // console.timeEnd( 'Total parsing: ' );
  39. }, onProgress, onError );
  40. }
  41. parse( iffBuffer, path, modelName ) {
  42. _lwoTree = new THREE.IFFParser().parse( iffBuffer ); // console.log( 'lwoTree', lwoTree );
  43. const textureLoader = new THREE.TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
  44. return new LWOTreeParser( textureLoader ).parse( modelName );
  45. }
  46. } // Parse the lwoTree object
  47. class LWOTreeParser {
  48. constructor( textureLoader ) {
  49. this.textureLoader = textureLoader;
  50. }
  51. parse( modelName ) {
  52. this.materials = new MaterialParser( this.textureLoader ).parse();
  53. this.defaultLayerName = modelName;
  54. this.meshes = this.parseLayers();
  55. return {
  56. materials: this.materials,
  57. meshes: this.meshes
  58. };
  59. }
  60. parseLayers() {
  61. // array of all meshes for building hierarchy
  62. const meshes = []; // final array containing meshes with scene graph hierarchy set up
  63. const finalMeshes = [];
  64. const geometryParser = new GeometryParser();
  65. const scope = this;
  66. _lwoTree.layers.forEach( function ( layer ) {
  67. const geometry = geometryParser.parse( layer.geometry, layer );
  68. const mesh = scope.parseMesh( geometry, layer );
  69. meshes[ layer.number ] = mesh;
  70. if ( layer.parent === - 1 ) finalMeshes.push( mesh ); else meshes[ layer.parent ].add( mesh );
  71. } );
  72. this.applyPivots( finalMeshes );
  73. return finalMeshes;
  74. }
  75. parseMesh( geometry, layer ) {
  76. let mesh;
  77. const materials = this.getMaterials( geometry.userData.matNames, layer.geometry.type );
  78. this.duplicateUVs( geometry, materials );
  79. if ( layer.geometry.type === 'points' ) mesh = new THREE.Points( geometry, materials ); else if ( layer.geometry.type === 'lines' ) mesh = new THREE.LineSegments( geometry, materials ); else mesh = new THREE.Mesh( geometry, materials );
  80. if ( layer.name ) mesh.name = layer.name; else mesh.name = this.defaultLayerName + '_layer_' + layer.number;
  81. mesh.userData.pivot = layer.pivot;
  82. return mesh;
  83. } // TODO: may need to be reversed in z to convert LWO to three.js coordinates
  84. applyPivots( meshes ) {
  85. meshes.forEach( function ( mesh ) {
  86. mesh.traverse( function ( child ) {
  87. const pivot = child.userData.pivot;
  88. child.position.x += pivot[ 0 ];
  89. child.position.y += pivot[ 1 ];
  90. child.position.z += pivot[ 2 ];
  91. if ( child.parent ) {
  92. const parentPivot = child.parent.userData.pivot;
  93. child.position.x -= parentPivot[ 0 ];
  94. child.position.y -= parentPivot[ 1 ];
  95. child.position.z -= parentPivot[ 2 ];
  96. }
  97. } );
  98. } );
  99. }
  100. getMaterials( namesArray, type ) {
  101. const materials = [];
  102. const scope = this;
  103. namesArray.forEach( function ( name, i ) {
  104. materials[ i ] = scope.getMaterialByName( name );
  105. } ); // convert materials to line or point mats if required
  106. if ( type === 'points' || type === 'lines' ) {
  107. materials.forEach( function ( mat, i ) {
  108. const spec = {
  109. color: mat.color
  110. };
  111. if ( type === 'points' ) {
  112. spec.size = 0.1;
  113. spec.map = mat.map;
  114. materials[ i ] = new THREE.PointsMaterial( spec );
  115. } else if ( type === 'lines' ) {
  116. materials[ i ] = new THREE.LineBasicMaterial( spec );
  117. }
  118. } );
  119. } // if there is only one material, return that directly instead of array
  120. const filtered = materials.filter( Boolean );
  121. if ( filtered.length === 1 ) return filtered[ 0 ];
  122. return materials;
  123. }
  124. getMaterialByName( name ) {
  125. return this.materials.filter( function ( m ) {
  126. return m.name === name;
  127. } )[ 0 ];
  128. } // If the material has an aoMap, duplicate UVs
  129. duplicateUVs( geometry, materials ) {
  130. let duplicateUVs = false;
  131. if ( ! Array.isArray( materials ) ) {
  132. if ( materials.aoMap ) duplicateUVs = true;
  133. } else {
  134. materials.forEach( function ( material ) {
  135. if ( material.aoMap ) duplicateUVs = true;
  136. } );
  137. }
  138. if ( ! duplicateUVs ) return;
  139. geometry.setAttribute( 'uv2', new THREE.BufferAttribute( geometry.attributes.uv.array, 2 ) );
  140. }
  141. }
  142. class MaterialParser {
  143. constructor( textureLoader ) {
  144. this.textureLoader = textureLoader;
  145. }
  146. parse() {
  147. const materials = [];
  148. this.textures = {};
  149. for ( const name in _lwoTree.materials ) {
  150. if ( _lwoTree.format === 'LWO3' ) {
  151. materials.push( this.parseMaterial( _lwoTree.materials[ name ], name, _lwoTree.textures ) );
  152. } else if ( _lwoTree.format === 'LWO2' ) {
  153. materials.push( this.parseMaterialLwo2( _lwoTree.materials[ name ], name, _lwoTree.textures ) );
  154. }
  155. }
  156. return materials;
  157. }
  158. parseMaterial( materialData, name, textures ) {
  159. let params = {
  160. name: name,
  161. side: this.getSide( materialData.attributes ),
  162. flatShading: this.getSmooth( materialData.attributes )
  163. };
  164. const connections = this.parseConnections( materialData.connections, materialData.nodes );
  165. const maps = this.parseTextureNodes( connections.maps );
  166. this.parseAttributeImageMaps( connections.attributes, textures, maps, materialData.maps );
  167. const attributes = this.parseAttributes( connections.attributes, maps );
  168. this.parseEnvMap( connections, maps, attributes );
  169. params = Object.assign( maps, params );
  170. params = Object.assign( params, attributes );
  171. const materialType = this.getMaterialType( connections.attributes );
  172. return new materialType( params );
  173. }
  174. parseMaterialLwo2( materialData, name
  175. /*, textures*/
  176. ) {
  177. let params = {
  178. name: name,
  179. side: this.getSide( materialData.attributes ),
  180. flatShading: this.getSmooth( materialData.attributes )
  181. };
  182. const attributes = this.parseAttributes( materialData.attributes, {} );
  183. params = Object.assign( params, attributes );
  184. return new THREE.MeshPhongMaterial( params );
  185. } // Note: converting from left to right handed coords by switching x -> -x in vertices, and
  186. // then switching mat THREE.FrontSide -> THREE.BackSide
  187. // NB: this means that THREE.FrontSide and THREE.BackSide have been switched!
  188. getSide( attributes ) {
  189. if ( ! attributes.side ) return THREE.BackSide;
  190. switch ( attributes.side ) {
  191. case 0:
  192. case 1:
  193. return THREE.BackSide;
  194. case 2:
  195. return THREE.FrontSide;
  196. case 3:
  197. return THREE.DoubleSide;
  198. }
  199. }
  200. getSmooth( attributes ) {
  201. if ( ! attributes.smooth ) return true;
  202. return ! attributes.smooth;
  203. }
  204. parseConnections( connections, nodes ) {
  205. const materialConnections = {
  206. maps: {}
  207. };
  208. const inputName = connections.inputName;
  209. const inputNodeName = connections.inputNodeName;
  210. const nodeName = connections.nodeName;
  211. const scope = this;
  212. inputName.forEach( function ( name, index ) {
  213. if ( name === 'Material' ) {
  214. const matNode = scope.getNodeByRefName( inputNodeName[ index ], nodes );
  215. materialConnections.attributes = matNode.attributes;
  216. materialConnections.envMap = matNode.fileName;
  217. materialConnections.name = inputNodeName[ index ];
  218. }
  219. } );
  220. nodeName.forEach( function ( name, index ) {
  221. if ( name === materialConnections.name ) {
  222. materialConnections.maps[ inputName[ index ] ] = scope.getNodeByRefName( inputNodeName[ index ], nodes );
  223. }
  224. } );
  225. return materialConnections;
  226. }
  227. getNodeByRefName( refName, nodes ) {
  228. for ( const name in nodes ) {
  229. if ( nodes[ name ].refName === refName ) return nodes[ name ];
  230. }
  231. }
  232. parseTextureNodes( textureNodes ) {
  233. const maps = {};
  234. for ( const name in textureNodes ) {
  235. const node = textureNodes[ name ];
  236. const path = node.fileName;
  237. if ( ! path ) return;
  238. const texture = this.loadTexture( path );
  239. if ( node.widthWrappingMode !== undefined ) texture.wrapS = this.getWrappingType( node.widthWrappingMode );
  240. if ( node.heightWrappingMode !== undefined ) texture.wrapT = this.getWrappingType( node.heightWrappingMode );
  241. switch ( name ) {
  242. case 'Color':
  243. maps.map = texture;
  244. break;
  245. case 'Roughness':
  246. maps.roughnessMap = texture;
  247. maps.roughness = 1;
  248. break;
  249. case 'Specular':
  250. maps.specularMap = texture;
  251. maps.specular = 0xffffff;
  252. break;
  253. case 'Luminous':
  254. maps.emissiveMap = texture;
  255. maps.emissive = 0x808080;
  256. break;
  257. case 'Luminous THREE.Color':
  258. maps.emissive = 0x808080;
  259. break;
  260. case 'Metallic':
  261. maps.metalnessMap = texture;
  262. maps.metalness = 1;
  263. break;
  264. case 'Transparency':
  265. case 'Alpha':
  266. maps.alphaMap = texture;
  267. maps.transparent = true;
  268. break;
  269. case 'Normal':
  270. maps.normalMap = texture;
  271. if ( node.amplitude !== undefined ) maps.normalScale = new THREE.Vector2( node.amplitude, node.amplitude );
  272. break;
  273. case 'Bump':
  274. maps.bumpMap = texture;
  275. break;
  276. }
  277. } // LWO BSDF materials can have both spec and rough, but this is not valid in three
  278. if ( maps.roughnessMap && maps.specularMap ) delete maps.specularMap;
  279. return maps;
  280. } // maps can also be defined on individual material attributes, parse those here
  281. // This occurs on Standard (Phong) surfaces
  282. parseAttributeImageMaps( attributes, textures, maps ) {
  283. for ( const name in attributes ) {
  284. const attribute = attributes[ name ];
  285. if ( attribute.maps ) {
  286. const mapData = attribute.maps[ 0 ];
  287. const path = this.getTexturePathByIndex( mapData.imageIndex, textures );
  288. if ( ! path ) return;
  289. const texture = this.loadTexture( path );
  290. if ( mapData.wrap !== undefined ) texture.wrapS = this.getWrappingType( mapData.wrap.w );
  291. if ( mapData.wrap !== undefined ) texture.wrapT = this.getWrappingType( mapData.wrap.h );
  292. switch ( name ) {
  293. case 'Color':
  294. maps.map = texture;
  295. break;
  296. case 'Diffuse':
  297. maps.aoMap = texture;
  298. break;
  299. case 'Roughness':
  300. maps.roughnessMap = texture;
  301. maps.roughness = 1;
  302. break;
  303. case 'Specular':
  304. maps.specularMap = texture;
  305. maps.specular = 0xffffff;
  306. break;
  307. case 'Luminosity':
  308. maps.emissiveMap = texture;
  309. maps.emissive = 0x808080;
  310. break;
  311. case 'Metallic':
  312. maps.metalnessMap = texture;
  313. maps.metalness = 1;
  314. break;
  315. case 'Transparency':
  316. case 'Alpha':
  317. maps.alphaMap = texture;
  318. maps.transparent = true;
  319. break;
  320. case 'Normal':
  321. maps.normalMap = texture;
  322. break;
  323. case 'Bump':
  324. maps.bumpMap = texture;
  325. break;
  326. }
  327. }
  328. }
  329. }
  330. parseAttributes( attributes, maps ) {
  331. const params = {}; // don't use color data if color map is present
  332. if ( attributes.Color && ! maps.map ) {
  333. params.color = new THREE.Color().fromArray( attributes.Color.value );
  334. } else params.color = new THREE.Color();
  335. if ( attributes.Transparency && attributes.Transparency.value !== 0 ) {
  336. params.opacity = 1 - attributes.Transparency.value;
  337. params.transparent = true;
  338. }
  339. if ( attributes[ 'Bump Height' ] ) params.bumpScale = attributes[ 'Bump Height' ].value * 0.1;
  340. if ( attributes[ 'Refraction Index' ] ) params.refractionRatio = 0.98 / attributes[ 'Refraction Index' ].value;
  341. this.parsePhysicalAttributes( params, attributes, maps );
  342. this.parseStandardAttributes( params, attributes, maps );
  343. this.parsePhongAttributes( params, attributes, maps );
  344. return params;
  345. }
  346. parsePhysicalAttributes( params, attributes
  347. /*, maps*/
  348. ) {
  349. if ( attributes.Clearcoat && attributes.Clearcoat.value > 0 ) {
  350. params.clearcoat = attributes.Clearcoat.value;
  351. if ( attributes[ 'Clearcoat Gloss' ] ) {
  352. params.clearcoatRoughness = 0.5 * ( 1 - attributes[ 'Clearcoat Gloss' ].value );
  353. }
  354. }
  355. }
  356. parseStandardAttributes( params, attributes, maps ) {
  357. if ( attributes.Luminous ) {
  358. params.emissiveIntensity = attributes.Luminous.value;
  359. if ( attributes[ 'Luminous THREE.Color' ] && ! maps.emissive ) {
  360. params.emissive = new THREE.Color().fromArray( attributes[ 'Luminous THREE.Color' ].value );
  361. } else {
  362. params.emissive = new THREE.Color( 0x808080 );
  363. }
  364. }
  365. if ( attributes.Roughness && ! maps.roughnessMap ) params.roughness = attributes.Roughness.value;
  366. if ( attributes.Metallic && ! maps.metalnessMap ) params.metalness = attributes.Metallic.value;
  367. }
  368. parsePhongAttributes( params, attributes, maps ) {
  369. if ( attributes.Diffuse ) params.color.multiplyScalar( attributes.Diffuse.value );
  370. if ( attributes.Reflection ) {
  371. params.reflectivity = attributes.Reflection.value;
  372. params.combine = THREE.AddOperation;
  373. }
  374. if ( attributes.Luminosity ) {
  375. params.emissiveIntensity = attributes.Luminosity.value;
  376. if ( ! maps.emissiveMap && ! maps.map ) {
  377. params.emissive = params.color;
  378. } else {
  379. params.emissive = new THREE.Color( 0x808080 );
  380. }
  381. } // parse specular if there is no roughness - we will interpret the material as 'Phong' in this case
  382. if ( ! attributes.Roughness && attributes.Specular && ! maps.specularMap ) {
  383. if ( attributes[ 'Color Highlight' ] ) {
  384. params.specular = new THREE.Color().setScalar( attributes.Specular.value ).lerp( params.color.clone().multiplyScalar( attributes.Specular.value ), attributes[ 'Color Highlight' ].value );
  385. } else {
  386. params.specular = new THREE.Color().setScalar( attributes.Specular.value );
  387. }
  388. }
  389. if ( params.specular && attributes.Glossiness ) params.shininess = 7 + Math.pow( 2, attributes.Glossiness.value * 12 + 2 );
  390. }
  391. parseEnvMap( connections, maps, attributes ) {
  392. if ( connections.envMap ) {
  393. const envMap = this.loadTexture( connections.envMap );
  394. if ( attributes.transparent && attributes.opacity < 0.999 ) {
  395. envMap.mapping = THREE.EquirectangularRefractionMapping; // Reflectivity and refraction mapping don't work well together in Phong materials
  396. if ( attributes.reflectivity !== undefined ) {
  397. delete attributes.reflectivity;
  398. delete attributes.combine;
  399. }
  400. if ( attributes.metalness !== undefined ) {
  401. attributes.metalness = 1; // For most transparent materials metalness should be set to 1 if not otherwise defined. If set to 0 no refraction will be visible
  402. }
  403. attributes.opacity = 1; // transparency fades out refraction, forcing opacity to 1 ensures a closer visual match to the material in Lightwave.
  404. } else envMap.mapping = THREE.EquirectangularReflectionMapping;
  405. maps.envMap = envMap;
  406. }
  407. } // get texture defined at top level by its index
  408. getTexturePathByIndex( index ) {
  409. let fileName = '';
  410. if ( ! _lwoTree.textures ) return fileName;
  411. _lwoTree.textures.forEach( function ( texture ) {
  412. if ( texture.index === index ) fileName = texture.fileName;
  413. } );
  414. return fileName;
  415. }
  416. loadTexture( path ) {
  417. if ( ! path ) return null;
  418. const texture = this.textureLoader.load( path, undefined, undefined, function () {
  419. console.warn( 'LWOLoader: non-standard resource hierarchy. Use \`resourcePath\` parameter to specify root content directory.' );
  420. } );
  421. return texture;
  422. } // 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge
  423. getWrappingType( num ) {
  424. switch ( num ) {
  425. case 0:
  426. console.warn( 'LWOLoader: "Reset" texture wrapping type is not supported in three.js' );
  427. return THREE.ClampToEdgeWrapping;
  428. case 1:
  429. return THREE.RepeatWrapping;
  430. case 2:
  431. return THREE.MirroredRepeatWrapping;
  432. case 3:
  433. return THREE.ClampToEdgeWrapping;
  434. }
  435. }
  436. getMaterialType( nodeData ) {
  437. if ( nodeData.Clearcoat && nodeData.Clearcoat.value > 0 ) return THREE.MeshPhysicalMaterial;
  438. if ( nodeData.Roughness ) return THREE.MeshStandardMaterial;
  439. return THREE.MeshPhongMaterial;
  440. }
  441. }
  442. class GeometryParser {
  443. parse( geoData, layer ) {
  444. const geometry = new THREE.BufferGeometry();
  445. geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( geoData.points, 3 ) );
  446. const indices = this.splitIndices( geoData.vertexIndices, geoData.polygonDimensions );
  447. geometry.setIndex( indices );
  448. this.parseGroups( geometry, geoData );
  449. geometry.computeVertexNormals();
  450. this.parseUVs( geometry, layer, indices );
  451. this.parseMorphTargets( geometry, layer, indices ); // TODO: z may need to be reversed to account for coordinate system change
  452. geometry.translate( - layer.pivot[ 0 ], - layer.pivot[ 1 ], - layer.pivot[ 2 ] ); // let userData = geometry.userData;
  453. // geometry = geometry.toNonIndexed()
  454. // geometry.userData = userData;
  455. return geometry;
  456. } // split quads into tris
  457. splitIndices( indices, polygonDimensions ) {
  458. const remappedIndices = [];
  459. let i = 0;
  460. polygonDimensions.forEach( function ( dim ) {
  461. if ( dim < 4 ) {
  462. for ( let k = 0; k < dim; k ++ ) remappedIndices.push( indices[ i + k ] );
  463. } else if ( dim === 4 ) {
  464. remappedIndices.push( indices[ i ], indices[ i + 1 ], indices[ i + 2 ], indices[ i ], indices[ i + 2 ], indices[ i + 3 ] );
  465. } else if ( dim > 4 ) {
  466. for ( let k = 1; k < dim - 1; k ++ ) {
  467. remappedIndices.push( indices[ i ], indices[ i + k ], indices[ i + k + 1 ] );
  468. }
  469. console.warn( 'LWOLoader: polygons with greater than 4 sides are not supported' );
  470. }
  471. i += dim;
  472. } );
  473. return remappedIndices;
  474. } // NOTE: currently ignoring poly indices and assuming that they are intelligently ordered
  475. parseGroups( geometry, geoData ) {
  476. const tags = _lwoTree.tags;
  477. const matNames = [];
  478. let elemSize = 3;
  479. if ( geoData.type === 'lines' ) elemSize = 2;
  480. if ( geoData.type === 'points' ) elemSize = 1;
  481. const remappedIndices = this.splitMaterialIndices( geoData.polygonDimensions, geoData.materialIndices );
  482. let indexNum = 0; // create new indices in numerical order
  483. const indexPairs = {}; // original indices mapped to numerical indices
  484. let prevMaterialIndex;
  485. let materialIndex;
  486. let prevStart = 0;
  487. let currentCount = 0;
  488. for ( let i = 0; i < remappedIndices.length; i += 2 ) {
  489. materialIndex = remappedIndices[ i + 1 ];
  490. if ( i === 0 ) matNames[ indexNum ] = tags[ materialIndex ];
  491. if ( prevMaterialIndex === undefined ) prevMaterialIndex = materialIndex;
  492. if ( materialIndex !== prevMaterialIndex ) {
  493. let currentIndex;
  494. if ( indexPairs[ tags[ prevMaterialIndex ] ] ) {
  495. currentIndex = indexPairs[ tags[ prevMaterialIndex ] ];
  496. } else {
  497. currentIndex = indexNum;
  498. indexPairs[ tags[ prevMaterialIndex ] ] = indexNum;
  499. matNames[ indexNum ] = tags[ prevMaterialIndex ];
  500. indexNum ++;
  501. }
  502. geometry.addGroup( prevStart, currentCount, currentIndex );
  503. prevStart += currentCount;
  504. prevMaterialIndex = materialIndex;
  505. currentCount = 0;
  506. }
  507. currentCount += elemSize;
  508. } // the loop above doesn't add the last group, do that here.
  509. if ( geometry.groups.length > 0 ) {
  510. let currentIndex;
  511. if ( indexPairs[ tags[ materialIndex ] ] ) {
  512. currentIndex = indexPairs[ tags[ materialIndex ] ];
  513. } else {
  514. currentIndex = indexNum;
  515. indexPairs[ tags[ materialIndex ] ] = indexNum;
  516. matNames[ indexNum ] = tags[ materialIndex ];
  517. }
  518. geometry.addGroup( prevStart, currentCount, currentIndex );
  519. } // Mat names from TAGS chunk, used to build up an array of materials for this geometry
  520. geometry.userData.matNames = matNames;
  521. }
  522. splitMaterialIndices( polygonDimensions, indices ) {
  523. const remappedIndices = [];
  524. polygonDimensions.forEach( function ( dim, i ) {
  525. if ( dim <= 3 ) {
  526. remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ] );
  527. } else if ( dim === 4 ) {
  528. remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ], indices[ i * 2 ], indices[ i * 2 + 1 ] );
  529. } else {
  530. // ignore > 4 for now
  531. for ( let k = 0; k < dim - 2; k ++ ) {
  532. remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ] );
  533. }
  534. }
  535. } );
  536. return remappedIndices;
  537. } // UV maps:
  538. // 1: are defined via index into an array of points, not into a geometry
  539. // - the geometry is also defined by an index into this array, but the indexes may not match
  540. // 2: there can be any number of UV maps for a single geometry. Here these are combined,
  541. // with preference given to the first map encountered
  542. // 3: UV maps can be partial - that is, defined for only a part of the geometry
  543. // 4: UV maps can be VMAP or VMAD (discontinuous, to allow for seams). In practice, most
  544. // UV maps are defined as partially VMAP and partially VMAD
  545. // VMADs are currently not supported
  546. parseUVs( geometry, layer ) {
  547. // start by creating a UV map set to zero for the whole geometry
  548. const remappedUVs = Array.from( Array( geometry.attributes.position.count * 2 ), function () {
  549. return 0;
  550. } );
  551. for ( const name in layer.uvs ) {
  552. const uvs = layer.uvs[ name ].uvs;
  553. const uvIndices = layer.uvs[ name ].uvIndices;
  554. uvIndices.forEach( function ( i, j ) {
  555. remappedUVs[ i * 2 ] = uvs[ j * 2 ];
  556. remappedUVs[ i * 2 + 1 ] = uvs[ j * 2 + 1 ];
  557. } );
  558. }
  559. geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( remappedUVs, 2 ) );
  560. }
  561. parseMorphTargets( geometry, layer ) {
  562. let num = 0;
  563. for ( const name in layer.morphTargets ) {
  564. const remappedPoints = geometry.attributes.position.array.slice();
  565. if ( ! geometry.morphAttributes.position ) geometry.morphAttributes.position = [];
  566. const morphPoints = layer.morphTargets[ name ].points;
  567. const morphIndices = layer.morphTargets[ name ].indices;
  568. const type = layer.morphTargets[ name ].type;
  569. morphIndices.forEach( function ( i, j ) {
  570. if ( type === 'relative' ) {
  571. remappedPoints[ i * 3 ] += morphPoints[ j * 3 ];
  572. remappedPoints[ i * 3 + 1 ] += morphPoints[ j * 3 + 1 ];
  573. remappedPoints[ i * 3 + 2 ] += morphPoints[ j * 3 + 2 ];
  574. } else {
  575. remappedPoints[ i * 3 ] = morphPoints[ j * 3 ];
  576. remappedPoints[ i * 3 + 1 ] = morphPoints[ j * 3 + 1 ];
  577. remappedPoints[ i * 3 + 2 ] = morphPoints[ j * 3 + 2 ];
  578. }
  579. } );
  580. geometry.morphAttributes.position[ num ] = new THREE.Float32BufferAttribute( remappedPoints, 3 );
  581. geometry.morphAttributes.position[ num ].name = name;
  582. num ++;
  583. }
  584. geometry.morphTargetsRelative = false;
  585. }
  586. } // ************** UTILITY FUNCTIONS **************
  587. function extractParentUrl( url, dir ) {
  588. const index = url.indexOf( dir );
  589. if ( index === - 1 ) return './';
  590. return url.substr( 0, index );
  591. }
  592. THREE.LWOLoader = LWOLoader;
  593. } )();