LWOLoader.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966
  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. if ( materialType !== THREE.MeshPhongMaterial ) delete params.refractionRatio; // PBR materials do not support "refractionRatio"
  173. return new materialType( params );
  174. }
  175. parseMaterialLwo2( materialData, name
  176. /*, textures*/
  177. ) {
  178. let params = {
  179. name: name,
  180. side: this.getSide( materialData.attributes ),
  181. flatShading: this.getSmooth( materialData.attributes )
  182. };
  183. const attributes = this.parseAttributes( materialData.attributes, {} );
  184. params = Object.assign( params, attributes );
  185. return new THREE.MeshPhongMaterial( params );
  186. } // Note: converting from left to right handed coords by switching x -> -x in vertices, and
  187. // then switching mat THREE.FrontSide -> THREE.BackSide
  188. // NB: this means that THREE.FrontSide and THREE.BackSide have been switched!
  189. getSide( attributes ) {
  190. if ( ! attributes.side ) return THREE.BackSide;
  191. switch ( attributes.side ) {
  192. case 0:
  193. case 1:
  194. return THREE.BackSide;
  195. case 2:
  196. return THREE.FrontSide;
  197. case 3:
  198. return THREE.DoubleSide;
  199. }
  200. }
  201. getSmooth( attributes ) {
  202. if ( ! attributes.smooth ) return true;
  203. return ! attributes.smooth;
  204. }
  205. parseConnections( connections, nodes ) {
  206. const materialConnections = {
  207. maps: {}
  208. };
  209. const inputName = connections.inputName;
  210. const inputNodeName = connections.inputNodeName;
  211. const nodeName = connections.nodeName;
  212. const scope = this;
  213. inputName.forEach( function ( name, index ) {
  214. if ( name === 'Material' ) {
  215. const matNode = scope.getNodeByRefName( inputNodeName[ index ], nodes );
  216. materialConnections.attributes = matNode.attributes;
  217. materialConnections.envMap = matNode.fileName;
  218. materialConnections.name = inputNodeName[ index ];
  219. }
  220. } );
  221. nodeName.forEach( function ( name, index ) {
  222. if ( name === materialConnections.name ) {
  223. materialConnections.maps[ inputName[ index ] ] = scope.getNodeByRefName( inputNodeName[ index ], nodes );
  224. }
  225. } );
  226. return materialConnections;
  227. }
  228. getNodeByRefName( refName, nodes ) {
  229. for ( const name in nodes ) {
  230. if ( nodes[ name ].refName === refName ) return nodes[ name ];
  231. }
  232. }
  233. parseTextureNodes( textureNodes ) {
  234. const maps = {};
  235. for ( const name in textureNodes ) {
  236. const node = textureNodes[ name ];
  237. const path = node.fileName;
  238. if ( ! path ) return;
  239. const texture = this.loadTexture( path );
  240. if ( node.widthWrappingMode !== undefined ) texture.wrapS = this.getWrappingType( node.widthWrappingMode );
  241. if ( node.heightWrappingMode !== undefined ) texture.wrapT = this.getWrappingType( node.heightWrappingMode );
  242. switch ( name ) {
  243. case 'Color':
  244. maps.map = texture;
  245. break;
  246. case 'Roughness':
  247. maps.roughnessMap = texture;
  248. maps.roughness = 1;
  249. break;
  250. case 'Specular':
  251. maps.specularMap = texture;
  252. maps.specular = 0xffffff;
  253. break;
  254. case 'Luminous':
  255. maps.emissiveMap = texture;
  256. maps.emissive = 0x808080;
  257. break;
  258. case 'Luminous THREE.Color':
  259. maps.emissive = 0x808080;
  260. break;
  261. case 'Metallic':
  262. maps.metalnessMap = texture;
  263. maps.metalness = 1;
  264. break;
  265. case 'Transparency':
  266. case 'Alpha':
  267. maps.alphaMap = texture;
  268. maps.transparent = true;
  269. break;
  270. case 'Normal':
  271. maps.normalMap = texture;
  272. if ( node.amplitude !== undefined ) maps.normalScale = new THREE.Vector2( node.amplitude, node.amplitude );
  273. break;
  274. case 'Bump':
  275. maps.bumpMap = texture;
  276. break;
  277. }
  278. } // LWO BSDF materials can have both spec and rough, but this is not valid in three
  279. if ( maps.roughnessMap && maps.specularMap ) delete maps.specularMap;
  280. return maps;
  281. } // maps can also be defined on individual material attributes, parse those here
  282. // This occurs on Standard (Phong) surfaces
  283. parseAttributeImageMaps( attributes, textures, maps ) {
  284. for ( const name in attributes ) {
  285. const attribute = attributes[ name ];
  286. if ( attribute.maps ) {
  287. const mapData = attribute.maps[ 0 ];
  288. const path = this.getTexturePathByIndex( mapData.imageIndex, textures );
  289. if ( ! path ) return;
  290. const texture = this.loadTexture( path );
  291. if ( mapData.wrap !== undefined ) texture.wrapS = this.getWrappingType( mapData.wrap.w );
  292. if ( mapData.wrap !== undefined ) texture.wrapT = this.getWrappingType( mapData.wrap.h );
  293. switch ( name ) {
  294. case 'Color':
  295. maps.map = texture;
  296. break;
  297. case 'Diffuse':
  298. maps.aoMap = texture;
  299. break;
  300. case 'Roughness':
  301. maps.roughnessMap = texture;
  302. maps.roughness = 1;
  303. break;
  304. case 'Specular':
  305. maps.specularMap = texture;
  306. maps.specular = 0xffffff;
  307. break;
  308. case 'Luminosity':
  309. maps.emissiveMap = texture;
  310. maps.emissive = 0x808080;
  311. break;
  312. case 'Metallic':
  313. maps.metalnessMap = texture;
  314. maps.metalness = 1;
  315. break;
  316. case 'Transparency':
  317. case 'Alpha':
  318. maps.alphaMap = texture;
  319. maps.transparent = true;
  320. break;
  321. case 'Normal':
  322. maps.normalMap = texture;
  323. break;
  324. case 'Bump':
  325. maps.bumpMap = texture;
  326. break;
  327. }
  328. }
  329. }
  330. }
  331. parseAttributes( attributes, maps ) {
  332. const params = {}; // don't use color data if color map is present
  333. if ( attributes.Color && ! maps.map ) {
  334. params.color = new THREE.Color().fromArray( attributes.Color.value );
  335. } else params.color = new THREE.Color();
  336. if ( attributes.Transparency && attributes.Transparency.value !== 0 ) {
  337. params.opacity = 1 - attributes.Transparency.value;
  338. params.transparent = true;
  339. }
  340. if ( attributes[ 'Bump Height' ] ) params.bumpScale = attributes[ 'Bump Height' ].value * 0.1;
  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[ 'Refraction Index' ] ) params.refractionRatio = 0.98 / attributes[ 'Refraction Index' ].value;
  370. if ( attributes.Diffuse ) params.color.multiplyScalar( attributes.Diffuse.value );
  371. if ( attributes.Reflection ) {
  372. params.reflectivity = attributes.Reflection.value;
  373. params.combine = THREE.AddOperation;
  374. }
  375. if ( attributes.Luminosity ) {
  376. params.emissiveIntensity = attributes.Luminosity.value;
  377. if ( ! maps.emissiveMap && ! maps.map ) {
  378. params.emissive = params.color;
  379. } else {
  380. params.emissive = new THREE.Color( 0x808080 );
  381. }
  382. } // parse specular if there is no roughness - we will interpret the material as 'Phong' in this case
  383. if ( ! attributes.Roughness && attributes.Specular && ! maps.specularMap ) {
  384. if ( attributes[ 'Color Highlight' ] ) {
  385. params.specular = new THREE.Color().setScalar( attributes.Specular.value ).lerp( params.color.clone().multiplyScalar( attributes.Specular.value ), attributes[ 'Color Highlight' ].value );
  386. } else {
  387. params.specular = new THREE.Color().setScalar( attributes.Specular.value );
  388. }
  389. }
  390. if ( params.specular && attributes.Glossiness ) params.shininess = 7 + Math.pow( 2, attributes.Glossiness.value * 12 + 2 );
  391. }
  392. parseEnvMap( connections, maps, attributes ) {
  393. if ( connections.envMap ) {
  394. const envMap = this.loadTexture( connections.envMap );
  395. if ( attributes.transparent && attributes.opacity < 0.999 ) {
  396. envMap.mapping = THREE.EquirectangularRefractionMapping; // Reflectivity and refraction mapping don't work well together in Phong materials
  397. if ( attributes.reflectivity !== undefined ) {
  398. delete attributes.reflectivity;
  399. delete attributes.combine;
  400. }
  401. if ( attributes.metalness !== undefined ) {
  402. 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
  403. }
  404. attributes.opacity = 1; // transparency fades out refraction, forcing opacity to 1 ensures a closer visual match to the material in Lightwave.
  405. } else envMap.mapping = THREE.EquirectangularReflectionMapping;
  406. maps.envMap = envMap;
  407. }
  408. } // get texture defined at top level by its index
  409. getTexturePathByIndex( index ) {
  410. let fileName = '';
  411. if ( ! _lwoTree.textures ) return fileName;
  412. _lwoTree.textures.forEach( function ( texture ) {
  413. if ( texture.index === index ) fileName = texture.fileName;
  414. } );
  415. return fileName;
  416. }
  417. loadTexture( path ) {
  418. if ( ! path ) return null;
  419. const texture = this.textureLoader.load( path, undefined, undefined, function () {
  420. console.warn( 'LWOLoader: non-standard resource hierarchy. Use \`resourcePath\` parameter to specify root content directory.' );
  421. } );
  422. return texture;
  423. } // 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge
  424. getWrappingType( num ) {
  425. switch ( num ) {
  426. case 0:
  427. console.warn( 'LWOLoader: "Reset" texture wrapping type is not supported in three.js' );
  428. return THREE.ClampToEdgeWrapping;
  429. case 1:
  430. return THREE.RepeatWrapping;
  431. case 2:
  432. return THREE.MirroredRepeatWrapping;
  433. case 3:
  434. return THREE.ClampToEdgeWrapping;
  435. }
  436. }
  437. getMaterialType( nodeData ) {
  438. if ( nodeData.Clearcoat && nodeData.Clearcoat.value > 0 ) return THREE.MeshPhysicalMaterial;
  439. if ( nodeData.Roughness ) return THREE.MeshStandardMaterial;
  440. return THREE.MeshPhongMaterial;
  441. }
  442. }
  443. class GeometryParser {
  444. parse( geoData, layer ) {
  445. const geometry = new THREE.BufferGeometry();
  446. geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( geoData.points, 3 ) );
  447. const indices = this.splitIndices( geoData.vertexIndices, geoData.polygonDimensions );
  448. geometry.setIndex( indices );
  449. this.parseGroups( geometry, geoData );
  450. geometry.computeVertexNormals();
  451. this.parseUVs( geometry, layer, indices );
  452. this.parseMorphTargets( geometry, layer, indices ); // TODO: z may need to be reversed to account for coordinate system change
  453. geometry.translate( - layer.pivot[ 0 ], - layer.pivot[ 1 ], - layer.pivot[ 2 ] ); // let userData = geometry.userData;
  454. // geometry = geometry.toNonIndexed()
  455. // geometry.userData = userData;
  456. return geometry;
  457. } // split quads into tris
  458. splitIndices( indices, polygonDimensions ) {
  459. const remappedIndices = [];
  460. let i = 0;
  461. polygonDimensions.forEach( function ( dim ) {
  462. if ( dim < 4 ) {
  463. for ( let k = 0; k < dim; k ++ ) remappedIndices.push( indices[ i + k ] );
  464. } else if ( dim === 4 ) {
  465. remappedIndices.push( indices[ i ], indices[ i + 1 ], indices[ i + 2 ], indices[ i ], indices[ i + 2 ], indices[ i + 3 ] );
  466. } else if ( dim > 4 ) {
  467. for ( let k = 1; k < dim - 1; k ++ ) {
  468. remappedIndices.push( indices[ i ], indices[ i + k ], indices[ i + k + 1 ] );
  469. }
  470. console.warn( 'LWOLoader: polygons with greater than 4 sides are not supported' );
  471. }
  472. i += dim;
  473. } );
  474. return remappedIndices;
  475. } // NOTE: currently ignoring poly indices and assuming that they are intelligently ordered
  476. parseGroups( geometry, geoData ) {
  477. const tags = _lwoTree.tags;
  478. const matNames = [];
  479. let elemSize = 3;
  480. if ( geoData.type === 'lines' ) elemSize = 2;
  481. if ( geoData.type === 'points' ) elemSize = 1;
  482. const remappedIndices = this.splitMaterialIndices( geoData.polygonDimensions, geoData.materialIndices );
  483. let indexNum = 0; // create new indices in numerical order
  484. const indexPairs = {}; // original indices mapped to numerical indices
  485. let prevMaterialIndex;
  486. let materialIndex;
  487. let prevStart = 0;
  488. let currentCount = 0;
  489. for ( let i = 0; i < remappedIndices.length; i += 2 ) {
  490. materialIndex = remappedIndices[ i + 1 ];
  491. if ( i === 0 ) matNames[ indexNum ] = tags[ materialIndex ];
  492. if ( prevMaterialIndex === undefined ) prevMaterialIndex = materialIndex;
  493. if ( materialIndex !== prevMaterialIndex ) {
  494. let currentIndex;
  495. if ( indexPairs[ tags[ prevMaterialIndex ] ] ) {
  496. currentIndex = indexPairs[ tags[ prevMaterialIndex ] ];
  497. } else {
  498. currentIndex = indexNum;
  499. indexPairs[ tags[ prevMaterialIndex ] ] = indexNum;
  500. matNames[ indexNum ] = tags[ prevMaterialIndex ];
  501. indexNum ++;
  502. }
  503. geometry.addGroup( prevStart, currentCount, currentIndex );
  504. prevStart += currentCount;
  505. prevMaterialIndex = materialIndex;
  506. currentCount = 0;
  507. }
  508. currentCount += elemSize;
  509. } // the loop above doesn't add the last group, do that here.
  510. if ( geometry.groups.length > 0 ) {
  511. let currentIndex;
  512. if ( indexPairs[ tags[ materialIndex ] ] ) {
  513. currentIndex = indexPairs[ tags[ materialIndex ] ];
  514. } else {
  515. currentIndex = indexNum;
  516. indexPairs[ tags[ materialIndex ] ] = indexNum;
  517. matNames[ indexNum ] = tags[ materialIndex ];
  518. }
  519. geometry.addGroup( prevStart, currentCount, currentIndex );
  520. } // Mat names from TAGS chunk, used to build up an array of materials for this geometry
  521. geometry.userData.matNames = matNames;
  522. }
  523. splitMaterialIndices( polygonDimensions, indices ) {
  524. const remappedIndices = [];
  525. polygonDimensions.forEach( function ( dim, i ) {
  526. if ( dim <= 3 ) {
  527. remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ] );
  528. } else if ( dim === 4 ) {
  529. remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ], indices[ i * 2 ], indices[ i * 2 + 1 ] );
  530. } else {
  531. // ignore > 4 for now
  532. for ( let k = 0; k < dim - 2; k ++ ) {
  533. remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ] );
  534. }
  535. }
  536. } );
  537. return remappedIndices;
  538. } // UV maps:
  539. // 1: are defined via index into an array of points, not into a geometry
  540. // - the geometry is also defined by an index into this array, but the indexes may not match
  541. // 2: there can be any number of UV maps for a single geometry. Here these are combined,
  542. // with preference given to the first map encountered
  543. // 3: UV maps can be partial - that is, defined for only a part of the geometry
  544. // 4: UV maps can be VMAP or VMAD (discontinuous, to allow for seams). In practice, most
  545. // UV maps are defined as partially VMAP and partially VMAD
  546. // VMADs are currently not supported
  547. parseUVs( geometry, layer ) {
  548. // start by creating a UV map set to zero for the whole geometry
  549. const remappedUVs = Array.from( Array( geometry.attributes.position.count * 2 ), function () {
  550. return 0;
  551. } );
  552. for ( const name in layer.uvs ) {
  553. const uvs = layer.uvs[ name ].uvs;
  554. const uvIndices = layer.uvs[ name ].uvIndices;
  555. uvIndices.forEach( function ( i, j ) {
  556. remappedUVs[ i * 2 ] = uvs[ j * 2 ];
  557. remappedUVs[ i * 2 + 1 ] = uvs[ j * 2 + 1 ];
  558. } );
  559. }
  560. geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( remappedUVs, 2 ) );
  561. }
  562. parseMorphTargets( geometry, layer ) {
  563. let num = 0;
  564. for ( const name in layer.morphTargets ) {
  565. const remappedPoints = geometry.attributes.position.array.slice();
  566. if ( ! geometry.morphAttributes.position ) geometry.morphAttributes.position = [];
  567. const morphPoints = layer.morphTargets[ name ].points;
  568. const morphIndices = layer.morphTargets[ name ].indices;
  569. const type = layer.morphTargets[ name ].type;
  570. morphIndices.forEach( function ( i, j ) {
  571. if ( type === 'relative' ) {
  572. remappedPoints[ i * 3 ] += morphPoints[ j * 3 ];
  573. remappedPoints[ i * 3 + 1 ] += morphPoints[ j * 3 + 1 ];
  574. remappedPoints[ i * 3 + 2 ] += morphPoints[ j * 3 + 2 ];
  575. } else {
  576. remappedPoints[ i * 3 ] = morphPoints[ j * 3 ];
  577. remappedPoints[ i * 3 + 1 ] = morphPoints[ j * 3 + 1 ];
  578. remappedPoints[ i * 3 + 2 ] = morphPoints[ j * 3 + 2 ];
  579. }
  580. } );
  581. geometry.morphAttributes.position[ num ] = new THREE.Float32BufferAttribute( remappedPoints, 3 );
  582. geometry.morphAttributes.position[ num ].name = name;
  583. num ++;
  584. }
  585. geometry.morphTargetsRelative = false;
  586. }
  587. } // ************** UTILITY FUNCTIONS **************
  588. function extractParentUrl( url, dir ) {
  589. const index = url.indexOf( dir );
  590. if ( index === - 1 ) return './';
  591. return url.slice( 0, index );
  592. }
  593. THREE.LWOLoader = LWOLoader;
  594. } )();