LWOLoader.js 68 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064
  1. /**
  2. * @version 1.1.1
  3. *
  4. * @author Lewy Blue https://github.com/looeee
  5. * @author Guilherme Avila https://github/sciecode
  6. *
  7. * @desc Load files in LWO3 and LWO2 format on Three.js
  8. *
  9. * LWO3 format specification:
  10. * http://static.lightwave3d.com/sdk/2018/html/filefmts/lwo3.html
  11. *
  12. * LWO2 format specification:
  13. * http://static.lightwave3d.com/sdk/2018/html/filefmts/lwo2.html
  14. *
  15. * Development and test repository:
  16. * https://github.com/threejs/lwoloader
  17. *
  18. **/
  19. import {
  20. AddOperation,
  21. BackSide,
  22. BufferAttribute,
  23. BufferGeometry,
  24. ClampToEdgeWrapping,
  25. Color,
  26. DoubleSide,
  27. EquirectangularReflectionMapping,
  28. EquirectangularRefractionMapping,
  29. FileLoader,
  30. Float32BufferAttribute,
  31. FrontSide,
  32. LineBasicMaterial,
  33. LineSegments,
  34. Loader,
  35. LoaderUtils,
  36. Mesh,
  37. MeshPhongMaterial,
  38. MeshPhysicalMaterial,
  39. MeshStandardMaterial,
  40. MirroredRepeatWrapping,
  41. Points,
  42. PointsMaterial,
  43. RepeatWrapping,
  44. TextureLoader,
  45. Vector2
  46. } from "../../../build/three.module.js";
  47. function LWO2Parser( IFFParser ) {
  48. this.IFF = IFFParser;
  49. }
  50. LWO2Parser.prototype = {
  51. constructor: LWO2Parser,
  52. parseBlock: function () {
  53. this.IFF.debugger.offset = this.IFF.reader.offset;
  54. this.IFF.debugger.closeForms();
  55. var blockID = this.IFF.reader.getIDTag();
  56. var length = this.IFF.reader.getUint32(); // size of data in bytes
  57. if ( length > this.IFF.reader.dv.byteLength - this.IFF.reader.offset ) {
  58. this.IFF.reader.offset -= 4;
  59. length = this.IFF.reader.getUint16();
  60. }
  61. this.IFF.debugger.dataOffset = this.IFF.reader.offset;
  62. this.IFF.debugger.length = length;
  63. // Data types may be found in either LWO2 OR LWO3 spec
  64. switch ( blockID ) {
  65. case 'FORM': // form blocks may consist of sub -chunks or sub-forms
  66. this.IFF.parseForm( length );
  67. break;
  68. // SKIPPED CHUNKS
  69. // if break; is called directly, the position in the lwoTree is not created
  70. // any sub chunks and forms are added to the parent form instead
  71. // MISC skipped
  72. case 'ICON': // Thumbnail Icon Image
  73. case 'VMPA': // Vertex Map Parameter
  74. case 'BBOX': // bounding box
  75. // case 'VMMD':
  76. // case 'VTYP':
  77. // normal maps can be specified, normally on models imported from other applications. Currently ignored
  78. case 'NORM':
  79. // ENVL FORM skipped
  80. case 'PRE ':
  81. case 'POST':
  82. case 'KEY ':
  83. case 'SPAN':
  84. // CLIP FORM skipped
  85. case 'TIME':
  86. case 'CLRS':
  87. case 'CLRA':
  88. case 'FILT':
  89. case 'DITH':
  90. case 'CONT':
  91. case 'BRIT':
  92. case 'SATR':
  93. case 'HUE ':
  94. case 'GAMM':
  95. case 'NEGA':
  96. case 'IFLT':
  97. case 'PFLT':
  98. // Image Map Layer skipped
  99. case 'PROJ':
  100. case 'AXIS':
  101. case 'AAST':
  102. case 'PIXB':
  103. case 'AUVO':
  104. case 'STCK':
  105. // Procedural Textures skipped
  106. case 'PROC':
  107. case 'VALU':
  108. case 'FUNC':
  109. // Gradient Textures skipped
  110. case 'PNAM':
  111. case 'INAM':
  112. case 'GRST':
  113. case 'GREN':
  114. case 'GRPT':
  115. case 'FKEY':
  116. case 'IKEY':
  117. // Texture Mapping Form skipped
  118. case 'CSYS':
  119. // Surface CHUNKs skipped
  120. case 'OPAQ': // top level 'opacity' checkbox
  121. case 'CMAP': // clip map
  122. // Surface node CHUNKS skipped
  123. // These mainly specify the node editor setup in LW
  124. case 'NLOC':
  125. case 'NZOM':
  126. case 'NVER':
  127. case 'NSRV':
  128. case 'NVSK': // unknown
  129. case 'NCRD':
  130. case 'WRPW': // image wrap w ( for cylindrical and spherical projections)
  131. case 'WRPH': // image wrap h
  132. case 'NMOD':
  133. case 'NPRW':
  134. case 'NPLA':
  135. case 'NODS':
  136. case 'VERS':
  137. case 'ENUM':
  138. case 'TAG ':
  139. case 'OPAC':
  140. // Car Material CHUNKS
  141. case 'CGMD':
  142. case 'CGTY':
  143. case 'CGST':
  144. case 'CGEN':
  145. case 'CGTS':
  146. case 'CGTE':
  147. case 'OSMP':
  148. case 'OMDE':
  149. case 'OUTR':
  150. case 'FLAG':
  151. case 'TRNL':
  152. case 'GLOW':
  153. case 'GVAL': // glow intensity
  154. case 'SHRP':
  155. case 'RFOP':
  156. case 'RSAN':
  157. case 'TROP':
  158. case 'RBLR':
  159. case 'TBLR':
  160. case 'CLRH':
  161. case 'CLRF':
  162. case 'ADTR':
  163. case 'LINE':
  164. case 'ALPH':
  165. case 'VCOL':
  166. case 'ENAB':
  167. this.IFF.debugger.skipped = true;
  168. this.IFF.reader.skip( length );
  169. break;
  170. case 'SURF':
  171. this.IFF.parseSurfaceLwo2( length );
  172. break;
  173. case 'CLIP':
  174. this.IFF.parseClipLwo2( length );
  175. break;
  176. // Texture node chunks (not in spec)
  177. case 'IPIX': // usePixelBlending
  178. case 'IMIP': // useMipMaps
  179. case 'IMOD': // imageBlendingMode
  180. case 'AMOD': // unknown
  181. case 'IINV': // imageInvertAlpha
  182. case 'INCR': // imageInvertColor
  183. case 'IAXS': // imageAxis ( for non-UV maps)
  184. case 'IFOT': // imageFallofType
  185. case 'ITIM': // timing for animated textures
  186. case 'IWRL':
  187. case 'IUTI':
  188. case 'IINX':
  189. case 'IINY':
  190. case 'IINZ':
  191. case 'IREF': // possibly a VX for reused texture nodes
  192. if ( length === 4 ) this.IFF.currentNode[ blockID ] = this.IFF.reader.getInt32();
  193. else this.IFF.reader.skip( length );
  194. break;
  195. case 'OTAG':
  196. this.IFF.parseObjectTag();
  197. break;
  198. case 'LAYR':
  199. this.IFF.parseLayer( length );
  200. break;
  201. case 'PNTS':
  202. this.IFF.parsePoints( length );
  203. break;
  204. case 'VMAP':
  205. this.IFF.parseVertexMapping( length );
  206. break;
  207. case 'AUVU':
  208. case 'AUVN':
  209. this.IFF.reader.skip( length - 1 );
  210. this.IFF.reader.getVariableLengthIndex(); // VX
  211. break;
  212. case 'POLS':
  213. this.IFF.parsePolygonList( length );
  214. break;
  215. case 'TAGS':
  216. this.IFF.parseTagStrings( length );
  217. break;
  218. case 'PTAG':
  219. this.IFF.parsePolygonTagMapping( length );
  220. break;
  221. case 'VMAD':
  222. this.IFF.parseVertexMapping( length, true );
  223. break;
  224. // Misc CHUNKS
  225. case 'DESC': // Description Line
  226. this.IFF.currentForm.description = this.IFF.reader.getString();
  227. break;
  228. case 'TEXT':
  229. case 'CMNT':
  230. case 'NCOM':
  231. this.IFF.currentForm.comment = this.IFF.reader.getString();
  232. break;
  233. // Envelope Form
  234. case 'NAME':
  235. this.IFF.currentForm.channelName = this.IFF.reader.getString();
  236. break;
  237. // Image Map Layer
  238. case 'WRAP':
  239. this.IFF.currentForm.wrap = { w: this.IFF.reader.getUint16(), h: this.IFF.reader.getUint16() };
  240. break;
  241. case 'IMAG':
  242. var index = this.IFF.reader.getVariableLengthIndex();
  243. this.IFF.currentForm.imageIndex = index;
  244. break;
  245. // Texture Mapping Form
  246. case 'OREF':
  247. this.IFF.currentForm.referenceObject = this.IFF.reader.getString();
  248. break;
  249. case 'ROID':
  250. this.IFF.currentForm.referenceObjectID = this.IFF.reader.getUint32();
  251. break;
  252. // Surface Blocks
  253. case 'SSHN':
  254. this.IFF.currentSurface.surfaceShaderName = this.IFF.reader.getString();
  255. break;
  256. case 'AOVN':
  257. this.IFF.currentSurface.surfaceCustomAOVName = this.IFF.reader.getString();
  258. break;
  259. // Nodal Blocks
  260. case 'NSTA':
  261. this.IFF.currentForm.disabled = this.IFF.reader.getUint16();
  262. break;
  263. case 'NRNM':
  264. this.IFF.currentForm.realName = this.IFF.reader.getString();
  265. break;
  266. case 'NNME':
  267. this.IFF.currentForm.refName = this.IFF.reader.getString();
  268. this.IFF.currentSurface.nodes[ this.IFF.currentForm.refName ] = this.IFF.currentForm;
  269. break;
  270. // Nodal Blocks : connections
  271. case 'INME':
  272. if ( ! this.IFF.currentForm.nodeName ) this.IFF.currentForm.nodeName = [];
  273. this.IFF.currentForm.nodeName.push( this.IFF.reader.getString() );
  274. break;
  275. case 'IINN':
  276. if ( ! this.IFF.currentForm.inputNodeName ) this.IFF.currentForm.inputNodeName = [];
  277. this.IFF.currentForm.inputNodeName.push( this.IFF.reader.getString() );
  278. break;
  279. case 'IINM':
  280. if ( ! this.IFF.currentForm.inputName ) this.IFF.currentForm.inputName = [];
  281. this.IFF.currentForm.inputName.push( this.IFF.reader.getString() );
  282. break;
  283. case 'IONM':
  284. if ( ! this.IFF.currentForm.inputOutputName ) this.IFF.currentForm.inputOutputName = [];
  285. this.IFF.currentForm.inputOutputName.push( this.IFF.reader.getString() );
  286. break;
  287. case 'FNAM':
  288. this.IFF.currentForm.fileName = this.IFF.reader.getString();
  289. break;
  290. case 'CHAN': // NOTE: ENVL Forms may also have CHAN chunk, however ENVL is currently ignored
  291. if ( length === 4 ) this.IFF.currentForm.textureChannel = this.IFF.reader.getIDTag();
  292. else this.IFF.reader.skip( length );
  293. break;
  294. // LWO2 Spec chunks: these are needed since the SURF FORMs are often in LWO2 format
  295. case 'SMAN':
  296. var maxSmoothingAngle = this.IFF.reader.getFloat32();
  297. this.IFF.currentSurface.attributes.smooth = ( maxSmoothingAngle < 0 ) ? false : true;
  298. break;
  299. // LWO2: Basic Surface Parameters
  300. case 'COLR':
  301. this.IFF.currentSurface.attributes.Color = { value: this.IFF.reader.getFloat32Array( 3 ) };
  302. this.IFF.reader.skip( 2 ); // VX: envelope
  303. break;
  304. case 'LUMI':
  305. this.IFF.currentSurface.attributes.Luminosity = { value: this.IFF.reader.getFloat32() };
  306. this.IFF.reader.skip( 2 );
  307. break;
  308. case 'SPEC':
  309. this.IFF.currentSurface.attributes.Specular = { value: this.IFF.reader.getFloat32() };
  310. this.IFF.reader.skip( 2 );
  311. break;
  312. case 'DIFF':
  313. this.IFF.currentSurface.attributes.Diffuse = { value: this.IFF.reader.getFloat32() };
  314. this.IFF.reader.skip( 2 );
  315. break;
  316. case 'REFL':
  317. this.IFF.currentSurface.attributes.Reflection = { value: this.IFF.reader.getFloat32() };
  318. this.IFF.reader.skip( 2 );
  319. break;
  320. case 'GLOS':
  321. this.IFF.currentSurface.attributes.Glossiness = { value: this.IFF.reader.getFloat32() };
  322. this.IFF.reader.skip( 2 );
  323. break;
  324. case 'TRAN':
  325. this.IFF.currentSurface.attributes.opacity = this.IFF.reader.getFloat32();
  326. this.IFF.reader.skip( 2 );
  327. break;
  328. case 'BUMP':
  329. this.IFF.currentSurface.attributes.bumpStrength = this.IFF.reader.getFloat32();
  330. this.IFF.reader.skip( 2 );
  331. break;
  332. case 'SIDE':
  333. this.IFF.currentSurface.attributes.side = this.IFF.reader.getUint16();
  334. break;
  335. case 'RIMG':
  336. this.IFF.currentSurface.attributes.reflectionMap = this.IFF.reader.getVariableLengthIndex();
  337. break;
  338. case 'RIND':
  339. this.IFF.currentSurface.attributes.refractiveIndex = this.IFF.reader.getFloat32();
  340. this.IFF.reader.skip( 2 );
  341. break;
  342. case 'TIMG':
  343. this.IFF.currentSurface.attributes.refractionMap = this.IFF.reader.getVariableLengthIndex();
  344. break;
  345. case 'IMAP':
  346. this.IFF.reader.skip( 2 );
  347. break;
  348. case 'TMAP':
  349. this.IFF.debugger.skipped = true;
  350. this.IFF.reader.skip( length ); // needs implementing
  351. break;
  352. case 'IUVI': // uv channel name
  353. this.IFF.currentNode.UVChannel = this.IFF.reader.getString( length );
  354. break;
  355. case 'IUTL': // widthWrappingMode: 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge
  356. this.IFF.currentNode.widthWrappingMode = this.IFF.reader.getUint32();
  357. break;
  358. case 'IVTL': // heightWrappingMode
  359. this.IFF.currentNode.heightWrappingMode = this.IFF.reader.getUint32();
  360. break;
  361. // LWO2 USE
  362. case 'BLOK':
  363. // skip
  364. break;
  365. default:
  366. this.IFF.parseUnknownCHUNK( blockID, length );
  367. }
  368. if ( blockID != 'FORM' ) {
  369. this.IFF.debugger.node = 1;
  370. this.IFF.debugger.nodeID = blockID;
  371. this.IFF.debugger.log();
  372. }
  373. if ( this.IFF.reader.offset >= this.IFF.currentFormEnd ) {
  374. this.IFF.currentForm = this.IFF.parentForm;
  375. }
  376. }
  377. };
  378. function LWO3Parser( IFFParser ) {
  379. this.IFF = IFFParser;
  380. }
  381. LWO3Parser.prototype = {
  382. constructor: LWO3Parser,
  383. parseBlock: function () {
  384. this.IFF.debugger.offset = this.IFF.reader.offset;
  385. this.IFF.debugger.closeForms();
  386. var blockID = this.IFF.reader.getIDTag();
  387. var length = this.IFF.reader.getUint32(); // size of data in bytes
  388. this.IFF.debugger.dataOffset = this.IFF.reader.offset;
  389. this.IFF.debugger.length = length;
  390. // Data types may be found in either LWO2 OR LWO3 spec
  391. switch ( blockID ) {
  392. case 'FORM': // form blocks may consist of sub -chunks or sub-forms
  393. this.IFF.parseForm( length );
  394. break;
  395. // SKIPPED CHUNKS
  396. // MISC skipped
  397. case 'ICON': // Thumbnail Icon Image
  398. case 'VMPA': // Vertex Map Parameter
  399. case 'BBOX': // bounding box
  400. // case 'VMMD':
  401. // case 'VTYP':
  402. // normal maps can be specified, normally on models imported from other applications. Currently ignored
  403. case 'NORM':
  404. // ENVL FORM skipped
  405. case 'PRE ':
  406. case 'POST':
  407. case 'KEY ':
  408. case 'SPAN':
  409. // CLIP FORM skipped
  410. case 'TIME':
  411. case 'CLRS':
  412. case 'CLRA':
  413. case 'FILT':
  414. case 'DITH':
  415. case 'CONT':
  416. case 'BRIT':
  417. case 'SATR':
  418. case 'HUE ':
  419. case 'GAMM':
  420. case 'NEGA':
  421. case 'IFLT':
  422. case 'PFLT':
  423. // Image Map Layer skipped
  424. case 'PROJ':
  425. case 'AXIS':
  426. case 'AAST':
  427. case 'PIXB':
  428. case 'STCK':
  429. // Procedural Textures skipped
  430. case 'VALU':
  431. // Gradient Textures skipped
  432. case 'PNAM':
  433. case 'INAM':
  434. case 'GRST':
  435. case 'GREN':
  436. case 'GRPT':
  437. case 'FKEY':
  438. case 'IKEY':
  439. // Texture Mapping Form skipped
  440. case 'CSYS':
  441. // Surface CHUNKs skipped
  442. case 'OPAQ': // top level 'opacity' checkbox
  443. case 'CMAP': // clip map
  444. // Surface node CHUNKS skipped
  445. // These mainly specify the node editor setup in LW
  446. case 'NLOC':
  447. case 'NZOM':
  448. case 'NVER':
  449. case 'NSRV':
  450. case 'NCRD':
  451. case 'NMOD':
  452. case 'NSEL':
  453. case 'NPRW':
  454. case 'NPLA':
  455. case 'VERS':
  456. case 'ENUM':
  457. case 'TAG ':
  458. // Car Material CHUNKS
  459. case 'CGMD':
  460. case 'CGTY':
  461. case 'CGST':
  462. case 'CGEN':
  463. case 'CGTS':
  464. case 'CGTE':
  465. case 'OSMP':
  466. case 'OMDE':
  467. case 'OUTR':
  468. case 'FLAG':
  469. case 'TRNL':
  470. case 'SHRP':
  471. case 'RFOP':
  472. case 'RSAN':
  473. case 'TROP':
  474. case 'RBLR':
  475. case 'TBLR':
  476. case 'CLRH':
  477. case 'CLRF':
  478. case 'ADTR':
  479. case 'GLOW':
  480. case 'LINE':
  481. case 'ALPH':
  482. case 'VCOL':
  483. case 'ENAB':
  484. this.IFF.debugger.skipped = true;
  485. this.IFF.reader.skip( length );
  486. break;
  487. // Texture node chunks (not in spec)
  488. case 'IPIX': // usePixelBlending
  489. case 'IMIP': // useMipMaps
  490. case 'IMOD': // imageBlendingMode
  491. case 'AMOD': // unknown
  492. case 'IINV': // imageInvertAlpha
  493. case 'INCR': // imageInvertColor
  494. case 'IAXS': // imageAxis ( for non-UV maps)
  495. case 'IFOT': // imageFallofType
  496. case 'ITIM': // timing for animated textures
  497. case 'IWRL':
  498. case 'IUTI':
  499. case 'IINX':
  500. case 'IINY':
  501. case 'IINZ':
  502. case 'IREF': // possibly a VX for reused texture nodes
  503. if ( length === 4 ) this.IFF.currentNode[ blockID ] = this.IFF.reader.getInt32();
  504. else this.IFF.reader.skip( length );
  505. break;
  506. case 'OTAG':
  507. this.IFF.parseObjectTag();
  508. break;
  509. case 'LAYR':
  510. this.IFF.parseLayer( length );
  511. break;
  512. case 'PNTS':
  513. this.IFF.parsePoints( length );
  514. break;
  515. case 'VMAP':
  516. this.IFF.parseVertexMapping( length );
  517. break;
  518. case 'POLS':
  519. this.IFF.parsePolygonList( length );
  520. break;
  521. case 'TAGS':
  522. this.IFF.parseTagStrings( length );
  523. break;
  524. case 'PTAG':
  525. this.IFF.parsePolygonTagMapping( length );
  526. break;
  527. case 'VMAD':
  528. this.IFF.parseVertexMapping( length, true );
  529. break;
  530. // Misc CHUNKS
  531. case 'DESC': // Description Line
  532. this.IFF.currentForm.description = this.IFF.reader.getString();
  533. break;
  534. case 'TEXT':
  535. case 'CMNT':
  536. case 'NCOM':
  537. this.IFF.currentForm.comment = this.IFF.reader.getString();
  538. break;
  539. // Envelope Form
  540. case 'NAME':
  541. this.IFF.currentForm.channelName = this.IFF.reader.getString();
  542. break;
  543. // Image Map Layer
  544. case 'WRAP':
  545. this.IFF.currentForm.wrap = { w: this.IFF.reader.getUint16(), h: this.IFF.reader.getUint16() };
  546. break;
  547. case 'IMAG':
  548. var index = this.IFF.reader.getVariableLengthIndex();
  549. this.IFF.currentForm.imageIndex = index;
  550. break;
  551. // Texture Mapping Form
  552. case 'OREF':
  553. this.IFF.currentForm.referenceObject = this.IFF.reader.getString();
  554. break;
  555. case 'ROID':
  556. this.IFF.currentForm.referenceObjectID = this.IFF.reader.getUint32();
  557. break;
  558. // Surface Blocks
  559. case 'SSHN':
  560. this.IFF.currentSurface.surfaceShaderName = this.IFF.reader.getString();
  561. break;
  562. case 'AOVN':
  563. this.IFF.currentSurface.surfaceCustomAOVName = this.IFF.reader.getString();
  564. break;
  565. // Nodal Blocks
  566. case 'NSTA':
  567. this.IFF.currentForm.disabled = this.IFF.reader.getUint16();
  568. break;
  569. case 'NRNM':
  570. this.IFF.currentForm.realName = this.IFF.reader.getString();
  571. break;
  572. case 'NNME':
  573. this.IFF.currentForm.refName = this.IFF.reader.getString();
  574. this.IFF.currentSurface.nodes[ this.IFF.currentForm.refName ] = this.IFF.currentForm;
  575. break;
  576. // Nodal Blocks : connections
  577. case 'INME':
  578. if ( ! this.IFF.currentForm.nodeName ) this.IFF.currentForm.nodeName = [];
  579. this.IFF.currentForm.nodeName.push( this.IFF.reader.getString() );
  580. break;
  581. case 'IINN':
  582. if ( ! this.IFF.currentForm.inputNodeName ) this.IFF.currentForm.inputNodeName = [];
  583. this.IFF.currentForm.inputNodeName.push( this.IFF.reader.getString() );
  584. break;
  585. case 'IINM':
  586. if ( ! this.IFF.currentForm.inputName ) this.IFF.currentForm.inputName = [];
  587. this.IFF.currentForm.inputName.push( this.IFF.reader.getString() );
  588. break;
  589. case 'IONM':
  590. if ( ! this.IFF.currentForm.inputOutputName ) this.IFF.currentForm.inputOutputName = [];
  591. this.IFF.currentForm.inputOutputName.push( this.IFF.reader.getString() );
  592. break;
  593. case 'FNAM':
  594. this.IFF.currentForm.fileName = this.IFF.reader.getString();
  595. break;
  596. case 'CHAN': // NOTE: ENVL Forms may also have CHAN chunk, however ENVL is currently ignored
  597. if ( length === 4 ) this.IFF.currentForm.textureChannel = this.IFF.reader.getIDTag();
  598. else this.IFF.reader.skip( length );
  599. break;
  600. // LWO2 Spec chunks: these are needed since the SURF FORMs are often in LWO2 format
  601. case 'SMAN':
  602. var maxSmoothingAngle = this.IFF.reader.getFloat32();
  603. this.IFF.currentSurface.attributes.smooth = ( maxSmoothingAngle < 0 ) ? false : true;
  604. break;
  605. // LWO2: Basic Surface Parameters
  606. case 'COLR':
  607. this.IFF.currentSurface.attributes.Color = { value: this.IFF.reader.getFloat32Array( 3 ) };
  608. this.IFF.reader.skip( 2 ); // VX: envelope
  609. break;
  610. case 'LUMI':
  611. this.IFF.currentSurface.attributes.Luminosity = { value: this.IFF.reader.getFloat32() };
  612. this.IFF.reader.skip( 2 );
  613. break;
  614. case 'SPEC':
  615. this.IFF.currentSurface.attributes.Specular = { value: this.IFF.reader.getFloat32() };
  616. this.IFF.reader.skip( 2 );
  617. break;
  618. case 'DIFF':
  619. this.IFF.currentSurface.attributes.Diffuse = { value: this.IFF.reader.getFloat32() };
  620. this.IFF.reader.skip( 2 );
  621. break;
  622. case 'REFL':
  623. this.IFF.currentSurface.attributes.Reflection = { value: this.IFF.reader.getFloat32() };
  624. this.IFF.reader.skip( 2 );
  625. break;
  626. case 'GLOS':
  627. this.IFF.currentSurface.attributes.Glossiness = { value: this.IFF.reader.getFloat32() };
  628. this.IFF.reader.skip( 2 );
  629. break;
  630. case 'TRAN':
  631. this.IFF.currentSurface.attributes.opacity = this.IFF.reader.getFloat32();
  632. this.IFF.reader.skip( 2 );
  633. break;
  634. case 'BUMP':
  635. this.IFF.currentSurface.attributes.bumpStrength = this.IFF.reader.getFloat32();
  636. this.IFF.reader.skip( 2 );
  637. break;
  638. case 'SIDE':
  639. this.IFF.currentSurface.attributes.side = this.IFF.reader.getUint16();
  640. break;
  641. case 'RIMG':
  642. this.IFF.currentSurface.attributes.reflectionMap = this.IFF.reader.getVariableLengthIndex();
  643. break;
  644. case 'RIND':
  645. this.IFF.currentSurface.attributes.refractiveIndex = this.IFF.reader.getFloat32();
  646. this.IFF.reader.skip( 2 );
  647. break;
  648. case 'TIMG':
  649. this.IFF.currentSurface.attributes.refractionMap = this.IFF.reader.getVariableLengthIndex();
  650. break;
  651. case 'IMAP':
  652. this.IFF.currentSurface.attributes.imageMapIndex = this.IFF.reader.getUint32();
  653. break;
  654. case 'IUVI': // uv channel name
  655. this.IFF.currentNode.UVChannel = this.IFF.reader.getString( length );
  656. break;
  657. case 'IUTL': // widthWrappingMode: 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge
  658. this.IFF.currentNode.widthWrappingMode = this.IFF.reader.getUint32();
  659. break;
  660. case 'IVTL': // heightWrappingMode
  661. this.IFF.currentNode.heightWrappingMode = this.IFF.reader.getUint32();
  662. break;
  663. default:
  664. this.IFF.parseUnknownCHUNK( blockID, length );
  665. }
  666. if ( blockID != 'FORM' ) {
  667. this.IFF.debugger.node = 1;
  668. this.IFF.debugger.nodeID = blockID;
  669. this.IFF.debugger.log();
  670. }
  671. if ( this.IFF.reader.offset >= this.IFF.currentFormEnd ) {
  672. this.IFF.currentForm = this.IFF.parentForm;
  673. }
  674. }
  675. };
  676. /**
  677. * === IFFParser ===
  678. * - Parses data from the IFF buffer.
  679. * - LWO3 files are in IFF format and can contain the following data types, referred to by shorthand codes
  680. *
  681. * ATOMIC DATA TYPES
  682. * ID Tag - 4x 7 bit uppercase ASCII chars: ID4
  683. * signed integer, 1, 2, or 4 byte length: I1, I2, I4
  684. * unsigned integer, 1, 2, or 4 byte length: U1, U2, U4
  685. * float, 4 byte length: F4
  686. * string, series of ASCII chars followed by null byte (If the length of the string including the null terminating byte is odd, an extra null is added so that the data that follows will begin on an even byte boundary): S0
  687. *
  688. * COMPOUND DATA TYPES
  689. * Variable-length Index (index into an array or collection): U2 or U4 : VX
  690. * Color (RGB): F4 + F4 + F4: COL12
  691. * Coordinate (x, y, z): F4 + F4 + F4: VEC12
  692. * Percentage F4 data type from 0->1 with 1 = 100%: FP4
  693. * Angle in radian F4: ANG4
  694. * Filename (string) S0: FNAM0
  695. * XValue F4 + index (VX) + optional envelope( ENVL ): XVAL
  696. * XValue vector VEC12 + index (VX) + optional envelope( ENVL ): XVAL3
  697. *
  698. * The IFF file is arranged in chunks:
  699. * CHUNK = ID4 + length (U4) + length X bytes of data + optional 0 pad byte
  700. * optional 0 pad byte is there to ensure chunk ends on even boundary, not counted in size
  701. *
  702. * COMPOUND DATA TYPES
  703. * - Chunks are combined in Forms (collections of chunks)
  704. * - FORM = string 'FORM' (ID4) + length (U4) + type (ID4) + optional ( CHUNK | FORM )
  705. * - CHUNKS and FORMS are collectively referred to as blocks
  706. * - The entire file is contained in one top level FORM
  707. *
  708. **/
  709. function IFFParser( ) {
  710. this.debugger = new Debugger();
  711. // this.debugger.enable(); // un-comment to log IFF hierarchy.
  712. }
  713. IFFParser.prototype = {
  714. constructor: IFFParser,
  715. parse: function ( buffer ) {
  716. this.reader = new DataViewReader( buffer );
  717. this.tree = {
  718. materials: {},
  719. layers: [],
  720. tags: [],
  721. textures: [],
  722. };
  723. // start out at the top level to add any data before first layer is encountered
  724. this.currentLayer = this.tree;
  725. this.currentForm = this.tree;
  726. this.parseTopForm();
  727. if ( this.tree.format === undefined ) return;
  728. if ( this.tree.format === 'LWO2' ) {
  729. this.parser = new LWO2Parser( this );
  730. while ( ! this.reader.endOfFile() ) this.parser.parseBlock();
  731. } else if ( this.tree.format === 'LWO3' ) {
  732. this.parser = new LWO3Parser( this );
  733. while ( ! this.reader.endOfFile() ) this.parser.parseBlock();
  734. }
  735. this.debugger.offset = this.reader.offset;
  736. this.debugger.closeForms();
  737. return this.tree;
  738. },
  739. parseTopForm() {
  740. this.debugger.offset = this.reader.offset;
  741. var topForm = this.reader.getIDTag();
  742. if ( topForm !== 'FORM' ) {
  743. console.warn( "LWOLoader: Top-level FORM missing." );
  744. return;
  745. }
  746. var length = this.reader.getUint32();
  747. this.debugger.dataOffset = this.reader.offset;
  748. this.debugger.length = length;
  749. var type = this.reader.getIDTag();
  750. if ( type === 'LWO2' ) {
  751. this.tree.format = type;
  752. } else if ( type === 'LWO3' ) {
  753. this.tree.format = type;
  754. }
  755. this.debugger.node = 0;
  756. this.debugger.nodeID = type;
  757. this.debugger.log();
  758. return;
  759. },
  760. ///
  761. // FORM PARSING METHODS
  762. ///
  763. // Forms are organisational and can contain any number of sub chunks and sub forms
  764. // FORM ::= 'FORM'[ID4], length[U4], type[ID4], ( chunk[CHUNK] | form[FORM] ) * }
  765. parseForm( length ) {
  766. var type = this.reader.getIDTag();
  767. switch ( type ) {
  768. // SKIPPED FORMS
  769. // if skipForm( length ) is called, the entire form and any sub forms and chunks are skipped
  770. case 'ISEQ': // Image sequence
  771. case 'ANIM': // plug in animation
  772. case 'STCC': // Color-cycling Still
  773. case 'VPVL':
  774. case 'VPRM':
  775. case 'NROT':
  776. case 'WRPW': // image wrap w ( for cylindrical and spherical projections)
  777. case 'WRPH': // image wrap h
  778. case 'FUNC':
  779. case 'FALL':
  780. case 'OPAC':
  781. case 'GRAD': // gradient texture
  782. case 'ENVS':
  783. case 'VMOP':
  784. case 'VMBG':
  785. // Car Material FORMS
  786. case 'OMAX':
  787. case 'STEX':
  788. case 'CKBG':
  789. case 'CKEY':
  790. case 'VMLA':
  791. case 'VMLB':
  792. this.debugger.skipped = true;
  793. this.skipForm( length ); // not currently supported
  794. break;
  795. // if break; is called directly, the position in the lwoTree is not created
  796. // any sub chunks and forms are added to the parent form instead
  797. case 'META':
  798. case 'NNDS':
  799. case 'NODS':
  800. case 'NDTA':
  801. case 'ADAT':
  802. case 'AOVS':
  803. case 'BLOK':
  804. // used by texture nodes
  805. case 'IBGC': // imageBackgroundColor
  806. case 'IOPC': // imageOpacity
  807. case 'IIMG': // hold reference to image path
  808. case 'TXTR':
  809. // this.setupForm( type, length );
  810. this.debugger.length = 4;
  811. this.debugger.skipped = true;
  812. break;
  813. case 'IFAL': // imageFallof
  814. case 'ISCL': // imageScale
  815. case 'IPOS': // imagePosition
  816. case 'IROT': // imageRotation
  817. case 'IBMP':
  818. case 'IUTD':
  819. case 'IVTD':
  820. this.parseTextureNodeAttribute( type );
  821. break;
  822. case 'ENVL':
  823. this.parseEnvelope( length );
  824. break;
  825. // CLIP FORM AND SUB FORMS
  826. case 'CLIP':
  827. if ( this.tree.format === 'LWO2' ) {
  828. this.parseForm( length );
  829. } else {
  830. this.parseClip( length );
  831. }
  832. break;
  833. case 'STIL':
  834. this.parseImage();
  835. break;
  836. case 'XREF': // clone of another STIL
  837. this.reader.skip( 8 ); // unknown
  838. this.currentForm.referenceTexture = {
  839. index: this.reader.getUint32(),
  840. refName: this.reader.getString() // internal unique ref
  841. };
  842. break;
  843. // Not in spec, used by texture nodes
  844. case 'IMST':
  845. this.parseImageStateForm( length );
  846. break;
  847. // SURF FORM AND SUB FORMS
  848. case 'SURF':
  849. this.parseSurfaceForm( length );
  850. break;
  851. case 'VALU': // Not in spec
  852. this.parseValueForm( length );
  853. break;
  854. case 'NTAG':
  855. this.parseSubNode( length );
  856. break;
  857. case 'ATTR': // BSDF Node Attributes
  858. case 'SATR': // Standard Node Attributes
  859. this.setupForm( 'attributes', length );
  860. break;
  861. case 'NCON':
  862. this.parseConnections( length );
  863. break;
  864. case 'SSHA':
  865. this.parentForm = this.currentForm;
  866. this.currentForm = this.currentSurface;
  867. this.setupForm( 'surfaceShader', length );
  868. break;
  869. case 'SSHD':
  870. this.setupForm( 'surfaceShaderData', length );
  871. break;
  872. case 'ENTR': // Not in spec
  873. this.parseEntryForm( length );
  874. break;
  875. // Image Map Layer
  876. case 'IMAP':
  877. this.parseImageMap( length );
  878. break;
  879. case 'TAMP':
  880. this.parseXVAL( 'amplitude', length );
  881. break;
  882. //Texture Mapping Form
  883. case 'TMAP':
  884. this.setupForm( 'textureMap', length );
  885. break;
  886. case 'CNTR':
  887. this.parseXVAL3( 'center', length );
  888. break;
  889. case 'SIZE':
  890. this.parseXVAL3( 'scale', length );
  891. break;
  892. case 'ROTA':
  893. this.parseXVAL3( 'rotation', length );
  894. break;
  895. default:
  896. this.parseUnknownForm( type, length );
  897. }
  898. this.debugger.node = 0;
  899. this.debugger.nodeID = type;
  900. this.debugger.log();
  901. },
  902. setupForm( type, length ) {
  903. if ( ! this.currentForm ) this.currentForm = this.currentNode;
  904. this.currentFormEnd = this.reader.offset + length;
  905. this.parentForm = this.currentForm;
  906. if ( ! this.currentForm[ type ] ) {
  907. this.currentForm[ type ] = {};
  908. this.currentForm = this.currentForm[ type ];
  909. } else {
  910. // should never see this unless there's a bug in the reader
  911. console.warn( 'LWOLoader: form already exists on parent: ', type, this.currentForm );
  912. this.currentForm = this.currentForm[ type ];
  913. }
  914. },
  915. skipForm( length ) {
  916. this.reader.skip( length - 4 );
  917. },
  918. parseUnknownForm( type, length ) {
  919. console.warn( 'LWOLoader: unknown FORM encountered: ' + type, length );
  920. printBuffer( this.reader.dv.buffer, this.reader.offset, length - 4 );
  921. this.reader.skip( length - 4 );
  922. },
  923. parseSurfaceForm( length ) {
  924. this.reader.skip( 8 ); // unknown Uint32 x2
  925. var name = this.reader.getString();
  926. var surface = {
  927. attributes: {}, // LWO2 style non-node attributes will go here
  928. connections: {},
  929. name: name,
  930. inputName: name,
  931. nodes: {},
  932. source: this.reader.getString(),
  933. };
  934. this.tree.materials[ name ] = surface;
  935. this.currentSurface = surface;
  936. this.parentForm = this.tree.materials;
  937. this.currentForm = surface;
  938. this.currentFormEnd = this.reader.offset + length;
  939. },
  940. parseSurfaceLwo2( length ) {
  941. var name = this.reader.getString();
  942. var surface = {
  943. attributes: {}, // LWO2 style non-node attributes will go here
  944. connections: {},
  945. name: name,
  946. nodes: {},
  947. source: this.reader.getString(),
  948. };
  949. this.tree.materials[ name ] = surface;
  950. this.currentSurface = surface;
  951. this.parentForm = this.tree.materials;
  952. this.currentForm = surface;
  953. this.currentFormEnd = this.reader.offset + length;
  954. },
  955. parseSubNode( length ) {
  956. // parse the NRNM CHUNK of the subnode FORM to get
  957. // a meaningful name for the subNode
  958. // some subnodes can be renamed, but Input and Surface cannot
  959. this.reader.skip( 8 ); // NRNM + length
  960. var name = this.reader.getString();
  961. var node = {
  962. name: name
  963. };
  964. this.currentForm = node;
  965. this.currentNode = node;
  966. this.currentFormEnd = this.reader.offset + length;
  967. },
  968. // collect attributes from all nodes at the top level of a surface
  969. parseConnections( length ) {
  970. this.currentFormEnd = this.reader.offset + length;
  971. this.parentForm = this.currentForm;
  972. this.currentForm = this.currentSurface.connections;
  973. },
  974. // surface node attribute data, e.g. specular, roughness etc
  975. parseEntryForm( length ) {
  976. this.reader.skip( 8 ); // NAME + length
  977. var name = this.reader.getString();
  978. this.currentForm = this.currentNode.attributes;
  979. this.setupForm( name, length );
  980. },
  981. // parse values from material - doesn't match up to other LWO3 data types
  982. // sub form of entry form
  983. parseValueForm() {
  984. this.reader.skip( 8 ); // unknown + length
  985. var valueType = this.reader.getString();
  986. if ( valueType === 'double' ) {
  987. this.currentForm.value = this.reader.getUint64();
  988. } else if ( valueType === 'int' ) {
  989. this.currentForm.value = this.reader.getUint32();
  990. } else if ( valueType === 'vparam' ) {
  991. this.reader.skip( 24 );
  992. this.currentForm.value = this.reader.getFloat64();
  993. } else if ( valueType === 'vparam3' ) {
  994. this.reader.skip( 24 );
  995. this.currentForm.value = this.reader.getFloat64Array( 3 );
  996. }
  997. },
  998. // holds various data about texture node image state
  999. // Data other thanmipMapLevel unknown
  1000. parseImageStateForm() {
  1001. this.reader.skip( 8 ); // unknown
  1002. this.currentForm.mipMapLevel = this.reader.getFloat32();
  1003. },
  1004. // LWO2 style image data node OR LWO3 textures defined at top level in editor (not as SURF node)
  1005. parseImageMap( length ) {
  1006. this.currentFormEnd = this.reader.offset + length;
  1007. this.parentForm = this.currentForm;
  1008. if ( ! this.currentForm.maps ) this.currentForm.maps = [];
  1009. var map = {};
  1010. this.currentForm.maps.push( map );
  1011. this.currentForm = map;
  1012. this.reader.skip( 10 ); // unknown, could be an issue if it contains a VX
  1013. },
  1014. parseTextureNodeAttribute( type ) {
  1015. this.reader.skip( 28 ); // FORM + length + VPRM + unknown + Uint32 x2 + float32
  1016. this.reader.skip( 20 ); // FORM + length + VPVL + float32 + Uint32
  1017. switch ( type ) {
  1018. case 'ISCL':
  1019. this.currentNode.scale = this.reader.getFloat32Array( 3 );
  1020. break;
  1021. case 'IPOS':
  1022. this.currentNode.position = this.reader.getFloat32Array( 3 );
  1023. break;
  1024. case 'IROT':
  1025. this.currentNode.rotation = this.reader.getFloat32Array( 3 );
  1026. break;
  1027. case 'IFAL':
  1028. this.currentNode.falloff = this.reader.getFloat32Array( 3 );
  1029. break;
  1030. case 'IBMP':
  1031. this.currentNode.amplitude = this.reader.getFloat32();
  1032. break;
  1033. case 'IUTD':
  1034. this.currentNode.uTiles = this.reader.getFloat32();
  1035. break;
  1036. case 'IVTD':
  1037. this.currentNode.vTiles = this.reader.getFloat32();
  1038. break;
  1039. }
  1040. this.reader.skip( 2 ); // unknown
  1041. },
  1042. // ENVL forms are currently ignored
  1043. parseEnvelope( length ) {
  1044. this.reader.skip( length - 4 ); // skipping entirely for now
  1045. },
  1046. ///
  1047. // CHUNK PARSING METHODS
  1048. ///
  1049. // clips can either be defined inside a surface node, or at the top
  1050. // level and they have a different format in each case
  1051. parseClip( length ) {
  1052. var tag = this.reader.getIDTag();
  1053. // inside surface node
  1054. if ( tag === 'FORM' ) {
  1055. this.reader.skip( 16 );
  1056. this.currentNode.fileName = this.reader.getString();
  1057. return;
  1058. }
  1059. // otherwise top level
  1060. this.reader.setOffset( this.reader.offset - 4 );
  1061. this.currentFormEnd = this.reader.offset + length;
  1062. this.parentForm = this.currentForm;
  1063. this.reader.skip( 8 ); // unknown
  1064. var texture = {
  1065. index: this.reader.getUint32()
  1066. };
  1067. this.tree.textures.push( texture );
  1068. this.currentForm = texture;
  1069. },
  1070. parseClipLwo2( length ) {
  1071. var texture = {
  1072. index: this.reader.getUint32(),
  1073. fileName: ""
  1074. };
  1075. // seach STIL block
  1076. while ( true ) {
  1077. var tag = this.reader.getIDTag();
  1078. var n_length = this.reader.getUint16();
  1079. if ( tag === 'STIL' ) {
  1080. texture.fileName = this.reader.getString();
  1081. break;
  1082. }
  1083. if ( n_length >= length ) {
  1084. break;
  1085. }
  1086. }
  1087. this.tree.textures.push( texture );
  1088. this.currentForm = texture;
  1089. },
  1090. parseImage() {
  1091. this.reader.skip( 8 ); // unknown
  1092. this.currentForm.fileName = this.reader.getString();
  1093. },
  1094. parseXVAL( type, length ) {
  1095. var endOffset = this.reader.offset + length - 4;
  1096. this.reader.skip( 8 );
  1097. this.currentForm[ type ] = this.reader.getFloat32();
  1098. this.reader.setOffset( endOffset ); // set end offset directly to skip optional envelope
  1099. },
  1100. parseXVAL3( type, length ) {
  1101. var endOffset = this.reader.offset + length - 4;
  1102. this.reader.skip( 8 );
  1103. this.currentForm[ type ] = {
  1104. x: this.reader.getFloat32(),
  1105. y: this.reader.getFloat32(),
  1106. z: this.reader.getFloat32(),
  1107. };
  1108. this.reader.setOffset( endOffset );
  1109. },
  1110. // Tags associated with an object
  1111. // OTAG { type[ID4], tag-string[S0] }
  1112. parseObjectTag() {
  1113. if ( ! this.tree.objectTags ) this.tree.objectTags = {};
  1114. this.tree.objectTags[ this.reader.getIDTag() ] = {
  1115. tagString: this.reader.getString()
  1116. };
  1117. },
  1118. // Signals the start of a new layer. All the data chunks which follow will be included in this layer until another layer chunk is encountered.
  1119. // LAYR: number[U2], flags[U2], pivot[VEC12], name[S0], parent[U2]
  1120. parseLayer( length ) {
  1121. var layer = {
  1122. number: this.reader.getUint16(),
  1123. flags: this.reader.getUint16(), // If the least significant bit of flags is set, the layer is hidden.
  1124. pivot: this.reader.getFloat32Array( 3 ), // Note: this seems to be superflous, as the geometry is translated when pivot is present
  1125. name: this.reader.getString(),
  1126. };
  1127. this.tree.layers.push( layer );
  1128. this.currentLayer = layer;
  1129. var parsedLength = 16 + stringOffset( this.currentLayer.name ); // index ( 2 ) + flags( 2 ) + pivot( 12 ) + stringlength
  1130. // if we have not reached then end of the layer block, there must be a parent defined
  1131. this.currentLayer.parent = ( parsedLength < length ) ? this.reader.getUint16() : - 1; // omitted or -1 for no parent
  1132. },
  1133. // VEC12 * ( F4 + F4 + F4 ) array of x,y,z vectors
  1134. // Converting from left to right handed coordinate system:
  1135. // x -> -x and switch material FrontSide -> BackSide
  1136. parsePoints( length ) {
  1137. this.currentPoints = [];
  1138. for ( var i = 0; i < length / 4; i += 3 ) {
  1139. // z -> -z to match three.js right handed coords
  1140. this.currentPoints.push( this.reader.getFloat32(), this.reader.getFloat32(), - this.reader.getFloat32() );
  1141. }
  1142. },
  1143. // parse VMAP or VMAD
  1144. // Associates a set of floating-point vectors with a set of points.
  1145. // VMAP: { type[ID4], dimension[U2], name[S0], ( vert[VX], value[F4] # dimension ) * }
  1146. // VMAD Associates a set of floating-point vectors with the vertices of specific polygons.
  1147. // Similar to VMAP UVs, but associates with polygon vertices rather than points
  1148. // to solve to problem of UV seams: VMAD chunks are paired with VMAPs of the same name,
  1149. // if they exist. The vector values in the VMAD will then replace those in the
  1150. // corresponding VMAP, but only for calculations involving the specified polygons.
  1151. // VMAD { type[ID4], dimension[U2], name[S0], ( vert[VX], poly[VX], value[F4] # dimension ) * }
  1152. parseVertexMapping( length, discontinuous ) {
  1153. var finalOffset = this.reader.offset + length;
  1154. var channelName = this.reader.getString();
  1155. if ( this.reader.offset === finalOffset ) {
  1156. // then we are in a texture node and the VMAP chunk is just a reference to a UV channel name
  1157. this.currentForm.UVChannel = channelName;
  1158. return;
  1159. }
  1160. // otherwise reset to initial length and parse normal VMAP CHUNK
  1161. this.reader.setOffset( this.reader.offset - stringOffset( channelName ) );
  1162. var type = this.reader.getIDTag();
  1163. this.reader.getUint16(); // dimension
  1164. var name = this.reader.getString();
  1165. var remainingLength = length - 6 - stringOffset( name );
  1166. switch ( type ) {
  1167. case 'TXUV':
  1168. this.parseUVMapping( name, finalOffset, discontinuous );
  1169. break;
  1170. case 'MORF':
  1171. case 'SPOT':
  1172. this.parseMorphTargets( name, finalOffset, type ); // can't be discontinuous
  1173. break;
  1174. // unsupported VMAPs
  1175. case 'APSL':
  1176. case 'NORM':
  1177. case 'WGHT':
  1178. case 'MNVW':
  1179. case 'PICK':
  1180. case 'RGB ':
  1181. case 'RGBA':
  1182. this.reader.skip( remainingLength );
  1183. break;
  1184. default:
  1185. console.warn( 'LWOLoader: unknown vertex map type: ' + type );
  1186. this.reader.skip( remainingLength );
  1187. }
  1188. },
  1189. parseUVMapping( name, finalOffset, discontinuous ) {
  1190. var uvIndices = [];
  1191. var polyIndices = [];
  1192. var uvs = [];
  1193. while ( this.reader.offset < finalOffset ) {
  1194. uvIndices.push( this.reader.getVariableLengthIndex() );
  1195. if ( discontinuous ) polyIndices.push( this.reader.getVariableLengthIndex() );
  1196. uvs.push( this.reader.getFloat32(), this.reader.getFloat32() );
  1197. }
  1198. if ( discontinuous ) {
  1199. if ( ! this.currentLayer.discontinuousUVs ) this.currentLayer.discontinuousUVs = {};
  1200. this.currentLayer.discontinuousUVs[ name ] = {
  1201. uvIndices: uvIndices,
  1202. polyIndices: polyIndices,
  1203. uvs: uvs,
  1204. };
  1205. } else {
  1206. if ( ! this.currentLayer.uvs ) this.currentLayer.uvs = {};
  1207. this.currentLayer.uvs[ name ] = {
  1208. uvIndices: uvIndices,
  1209. uvs: uvs,
  1210. };
  1211. }
  1212. },
  1213. parseMorphTargets( name, finalOffset, type ) {
  1214. var indices = [];
  1215. var points = [];
  1216. type = ( type === 'MORF' ) ? 'relative' : 'absolute';
  1217. while ( this.reader.offset < finalOffset ) {
  1218. indices.push( this.reader.getVariableLengthIndex() );
  1219. // z -> -z to match three.js right handed coords
  1220. points.push( this.reader.getFloat32(), this.reader.getFloat32(), - this.reader.getFloat32() );
  1221. }
  1222. if ( ! this.currentLayer.morphTargets ) this.currentLayer.morphTargets = {};
  1223. this.currentLayer.morphTargets[ name ] = {
  1224. indices: indices,
  1225. points: points,
  1226. type: type,
  1227. };
  1228. },
  1229. // A list of polygons for the current layer.
  1230. // POLS { type[ID4], ( numvert+flags[U2], vert[VX] # numvert ) * }
  1231. parsePolygonList( length ) {
  1232. var finalOffset = this.reader.offset + length;
  1233. var type = this.reader.getIDTag();
  1234. var indices = [];
  1235. // hold a list of polygon sizes, to be split up later
  1236. var polygonDimensions = [];
  1237. while ( this.reader.offset < finalOffset ) {
  1238. var numverts = this.reader.getUint16();
  1239. //var flags = numverts & 64512; // 6 high order bits are flags - ignoring for now
  1240. numverts = numverts & 1023; // remaining ten low order bits are vertex num
  1241. polygonDimensions.push( numverts );
  1242. for ( var j = 0; j < numverts; j ++ ) indices.push( this.reader.getVariableLengthIndex() );
  1243. }
  1244. var geometryData = {
  1245. type: type,
  1246. vertexIndices: indices,
  1247. polygonDimensions: polygonDimensions,
  1248. points: this.currentPoints
  1249. };
  1250. // Note: assuming that all polys will be lines or points if the first is
  1251. if ( polygonDimensions[ 0 ] === 1 ) geometryData.type = 'points';
  1252. else if ( polygonDimensions[ 0 ] === 2 ) geometryData.type = 'lines';
  1253. this.currentLayer.geometry = geometryData;
  1254. },
  1255. // Lists the tag strings that can be associated with polygons by the PTAG chunk.
  1256. // TAGS { tag-string[S0] * }
  1257. parseTagStrings( length ) {
  1258. this.tree.tags = this.reader.getStringArray( length );
  1259. },
  1260. // Associates tags of a given type with polygons in the most recent POLS chunk.
  1261. // PTAG { type[ID4], ( poly[VX], tag[U2] ) * }
  1262. parsePolygonTagMapping( length ) {
  1263. var finalOffset = this.reader.offset + length;
  1264. var type = this.reader.getIDTag();
  1265. if ( type === 'SURF' ) this.parseMaterialIndices( finalOffset );
  1266. else { //PART, SMGP, COLR not supported
  1267. this.reader.skip( length - 4 );
  1268. }
  1269. },
  1270. parseMaterialIndices( finalOffset ) {
  1271. // array holds polygon index followed by material index
  1272. this.currentLayer.geometry.materialIndices = [];
  1273. while ( this.reader.offset < finalOffset ) {
  1274. var polygonIndex = this.reader.getVariableLengthIndex();
  1275. var materialIndex = this.reader.getUint16();
  1276. this.currentLayer.geometry.materialIndices.push( polygonIndex, materialIndex );
  1277. }
  1278. },
  1279. parseUnknownCHUNK( blockID, length ) {
  1280. console.warn( 'LWOLoader: unknown chunk type: ' + blockID + ' length: ' + length );
  1281. // print the chunk plus some bytes padding either side
  1282. // printBuffer( this.reader.dv.buffer, this.reader.offset - 20, length + 40 );
  1283. var data = this.reader.getString( length );
  1284. this.currentForm[ blockID ] = data;
  1285. }
  1286. };
  1287. function DataViewReader( buffer ) {
  1288. this.dv = new DataView( buffer );
  1289. this.offset = 0;
  1290. }
  1291. DataViewReader.prototype = {
  1292. constructor: DataViewReader,
  1293. size: function () {
  1294. return this.dv.buffer.byteLength;
  1295. },
  1296. setOffset( offset ) {
  1297. if ( offset > 0 && offset < this.dv.buffer.byteLength ) {
  1298. this.offset = offset;
  1299. } else {
  1300. console.error( 'LWOLoader: invalid buffer offset' );
  1301. }
  1302. },
  1303. endOfFile: function () {
  1304. if ( this.offset >= this.size() ) return true;
  1305. return false;
  1306. },
  1307. skip: function ( length ) {
  1308. this.offset += length;
  1309. },
  1310. getUint8: function () {
  1311. var value = this.dv.getUint8( this.offset );
  1312. this.offset += 1;
  1313. return value;
  1314. },
  1315. getUint16: function () {
  1316. var value = this.dv.getUint16( this.offset );
  1317. this.offset += 2;
  1318. return value;
  1319. },
  1320. getInt32: function () {
  1321. var value = this.dv.getInt32( this.offset, false );
  1322. this.offset += 4;
  1323. return value;
  1324. },
  1325. getUint32: function () {
  1326. var value = this.dv.getUint32( this.offset, false );
  1327. this.offset += 4;
  1328. return value;
  1329. },
  1330. getUint64: function () {
  1331. var low, high;
  1332. high = this.getUint32();
  1333. low = this.getUint32();
  1334. return high * 0x100000000 + low;
  1335. },
  1336. getFloat32: function () {
  1337. var value = this.dv.getFloat32( this.offset, false );
  1338. this.offset += 4;
  1339. return value;
  1340. },
  1341. getFloat32Array: function ( size ) {
  1342. var a = [];
  1343. for ( var i = 0; i < size; i ++ ) {
  1344. a.push( this.getFloat32() );
  1345. }
  1346. return a;
  1347. },
  1348. getFloat64: function () {
  1349. var value = this.dv.getFloat64( this.offset, this.littleEndian );
  1350. this.offset += 8;
  1351. return value;
  1352. },
  1353. getFloat64Array: function ( size ) {
  1354. var a = [];
  1355. for ( var i = 0; i < size; i ++ ) {
  1356. a.push( this.getFloat64() );
  1357. }
  1358. return a;
  1359. },
  1360. // get variable-length index data type
  1361. // VX ::= index[U2] | (index + 0xFF000000)[U4]
  1362. // If the index value is less than 65,280 (0xFF00),then VX === U2
  1363. // otherwise VX === U4 with bits 24-31 set
  1364. // When reading an index, if the first byte encountered is 255 (0xFF), then
  1365. // the four-byte form is being used and the first byte should be discarded or masked out.
  1366. getVariableLengthIndex() {
  1367. var firstByte = this.getUint8();
  1368. if ( firstByte === 255 ) {
  1369. return this.getUint8() * 65536 + this.getUint8() * 256 + this.getUint8();
  1370. }
  1371. return firstByte * 256 + this.getUint8();
  1372. },
  1373. // An ID tag is a sequence of 4 bytes containing 7-bit ASCII values
  1374. getIDTag() {
  1375. return this.getString( 4 );
  1376. },
  1377. getString: function ( size ) {
  1378. if ( size === 0 ) return;
  1379. // note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead
  1380. var a = [];
  1381. if ( size ) {
  1382. for ( var i = 0; i < size; i ++ ) {
  1383. a[ i ] = this.getUint8();
  1384. }
  1385. } else {
  1386. var currentChar;
  1387. var len = 0;
  1388. while ( currentChar !== 0 ) {
  1389. currentChar = this.getUint8();
  1390. if ( currentChar !== 0 ) a.push( currentChar );
  1391. len ++;
  1392. }
  1393. if ( ! isEven( len + 1 ) ) this.getUint8(); // if string with terminating nullbyte is uneven, extra nullbyte is added
  1394. }
  1395. return LoaderUtils.decodeText( new Uint8Array( a ) );
  1396. },
  1397. getStringArray: function ( size ) {
  1398. var a = this.getString( size );
  1399. a = a.split( '\0' );
  1400. return a.filter( Boolean ); // return array with any empty strings removed
  1401. }
  1402. };
  1403. // ************** DEBUGGER **************
  1404. function Debugger( ) {
  1405. this.active = false;
  1406. this.depth = 0;
  1407. this.formList = [];
  1408. }
  1409. Debugger.prototype = {
  1410. constructor: Debugger,
  1411. enable: function () {
  1412. this.active = true;
  1413. },
  1414. log: function () {
  1415. if ( ! this.active ) return;
  1416. var nodeType;
  1417. switch ( this.node ) {
  1418. case 0:
  1419. nodeType = "FORM";
  1420. break;
  1421. case 1:
  1422. nodeType = "CHK";
  1423. break;
  1424. case 2:
  1425. nodeType = "S-CHK";
  1426. break;
  1427. }
  1428. console.log(
  1429. "| ".repeat( this.depth ) +
  1430. nodeType,
  1431. this.nodeID,
  1432. `( ${this.offset} ) -> ( ${this.dataOffset + this.length} )`,
  1433. ( ( this.node == 0 ) ? " {" : "" ),
  1434. ( ( this.skipped ) ? "SKIPPED" : "" ),
  1435. ( ( this.node == 0 && this.skipped ) ? "}" : "" )
  1436. );
  1437. if ( this.node == 0 && ! this.skipped ) {
  1438. this.depth += 1;
  1439. this.formList.push( this.dataOffset + this.length );
  1440. }
  1441. this.skipped = false;
  1442. },
  1443. closeForms: function () {
  1444. if ( ! this.active ) return;
  1445. for ( var i = this.formList.length - 1; i >= 0; i -- ) {
  1446. if ( this.offset >= this.formList[ i ] ) {
  1447. this.depth -= 1;
  1448. console.log( "| ".repeat( this.depth ) + "}" );
  1449. this.formList.splice( - 1, 1 );
  1450. }
  1451. }
  1452. }
  1453. };
  1454. // ************** UTILITY FUNCTIONS **************
  1455. function isEven( num ) {
  1456. return num % 2;
  1457. }
  1458. // calculate the length of the string in the buffer
  1459. // this will be string.length + nullbyte + optional padbyte to make the length even
  1460. function stringOffset( string ) {
  1461. return string.length + 1 + ( isEven( string.length + 1 ) ? 1 : 0 );
  1462. }
  1463. // for testing purposes, dump buffer to console
  1464. // printBuffer( this.reader.dv.buffer, this.reader.offset, length );
  1465. function printBuffer( buffer, from, to ) {
  1466. console.log( LoaderUtils.decodeText( new Uint8Array( buffer, from, to ) ) );
  1467. }
  1468. var lwoTree;
  1469. var LWOLoader = function ( manager, parameters ) {
  1470. Loader.call( this, manager );
  1471. parameters = parameters || {};
  1472. this.resourcePath = ( parameters.resourcePath !== undefined ) ? parameters.resourcePath : '';
  1473. };
  1474. LWOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
  1475. constructor: LWOLoader,
  1476. load: function ( url, onLoad, onProgress, onError ) {
  1477. var self = this;
  1478. var path = ( self.path === '' ) ? extractParentUrl( url, 'Objects' ) : self.path;
  1479. // give the mesh a default name based on the filename
  1480. var modelName = url.split( path ).pop().split( '.' )[ 0 ];
  1481. var loader = new FileLoader( this.manager );
  1482. loader.setPath( self.path );
  1483. loader.setResponseType( 'arraybuffer' );
  1484. loader.load( url, function ( buffer ) {
  1485. // console.time( 'Total parsing: ' );
  1486. onLoad( self.parse( buffer, path, modelName ) );
  1487. // console.timeEnd( 'Total parsing: ' );
  1488. }, onProgress, onError );
  1489. },
  1490. parse: function ( iffBuffer, path, modelName ) {
  1491. lwoTree = new IFFParser().parse( iffBuffer );
  1492. // console.log( 'lwoTree', lwoTree );
  1493. var textureLoader = new TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
  1494. return new LWOTreeParser( textureLoader ).parse( modelName );
  1495. }
  1496. } );
  1497. // Parse the lwoTree object
  1498. function LWOTreeParser( textureLoader ) {
  1499. this.textureLoader = textureLoader;
  1500. }
  1501. LWOTreeParser.prototype = {
  1502. constructor: LWOTreeParser,
  1503. parse: function ( modelName ) {
  1504. this.materials = new MaterialParser( this.textureLoader ).parse();
  1505. this.defaultLayerName = modelName;
  1506. this.meshes = this.parseLayers();
  1507. return {
  1508. materials: this.materials,
  1509. meshes: this.meshes,
  1510. };
  1511. },
  1512. parseLayers() {
  1513. // array of all meshes for building hierarchy
  1514. var meshes = [];
  1515. // final array containing meshes with scene graph hierarchy set up
  1516. var finalMeshes = [];
  1517. var geometryParser = new GeometryParser();
  1518. var self = this;
  1519. lwoTree.layers.forEach( function ( layer ) {
  1520. var geometry = geometryParser.parse( layer.geometry, layer );
  1521. var mesh = self.parseMesh( geometry, layer );
  1522. meshes[ layer.number ] = mesh;
  1523. if ( layer.parent === - 1 ) finalMeshes.push( mesh );
  1524. else meshes[ layer.parent ].add( mesh );
  1525. } );
  1526. this.applyPivots( finalMeshes );
  1527. return finalMeshes;
  1528. },
  1529. parseMesh( geometry, layer ) {
  1530. var mesh;
  1531. var materials = this.getMaterials( geometry.userData.matNames, layer.geometry.type );
  1532. this.duplicateUVs( geometry, materials );
  1533. if ( layer.geometry.type === 'points' ) mesh = new Points( geometry, materials );
  1534. else if ( layer.geometry.type === 'lines' ) mesh = new LineSegments( geometry, materials );
  1535. else mesh = new Mesh( geometry, materials );
  1536. if ( layer.name ) mesh.name = layer.name;
  1537. else mesh.name = this.defaultLayerName + '_layer_' + layer.number;
  1538. mesh.userData.pivot = layer.pivot;
  1539. return mesh;
  1540. },
  1541. // TODO: may need to be reversed in z to convert LWO to three.js coordinates
  1542. applyPivots( meshes ) {
  1543. meshes.forEach( function ( mesh ) {
  1544. mesh.traverse( function ( child ) {
  1545. var pivot = child.userData.pivot;
  1546. child.position.x += pivot[ 0 ];
  1547. child.position.y += pivot[ 1 ];
  1548. child.position.z += pivot[ 2 ];
  1549. if ( child.parent ) {
  1550. var parentPivot = child.parent.userData.pivot;
  1551. child.position.x -= parentPivot[ 0 ];
  1552. child.position.y -= parentPivot[ 1 ];
  1553. child.position.z -= parentPivot[ 2 ];
  1554. }
  1555. } );
  1556. } );
  1557. },
  1558. getMaterials( namesArray, type ) {
  1559. var materials = [];
  1560. var self = this;
  1561. namesArray.forEach( function ( name, i ) {
  1562. materials[ i ] = self.getMaterialByName( name );
  1563. } );
  1564. // convert materials to line or point mats if required
  1565. if ( type === 'points' || type === 'lines' ) {
  1566. materials.forEach( function ( mat, i ) {
  1567. var spec = {
  1568. color: mat.color,
  1569. };
  1570. if ( type === 'points' ) {
  1571. spec.size = 0.1;
  1572. spec.map = mat.map;
  1573. spec.morphTargets = mat.morphTargets;
  1574. materials[ i ] = new PointsMaterial( spec );
  1575. } else if ( type === 'lines' ) {
  1576. materials[ i ] = new LineBasicMaterial( spec );
  1577. }
  1578. } );
  1579. }
  1580. // if there is only one material, return that directly instead of array
  1581. var filtered = materials.filter( Boolean );
  1582. if ( filtered.length === 1 ) return filtered[ 0 ];
  1583. return materials;
  1584. },
  1585. getMaterialByName( name ) {
  1586. return this.materials.filter( function ( m ) {
  1587. return m.name === name;
  1588. } )[ 0 ];
  1589. },
  1590. // If the material has an aoMap, duplicate UVs
  1591. duplicateUVs( geometry, materials ) {
  1592. var duplicateUVs = false;
  1593. if ( ! Array.isArray( materials ) ) {
  1594. if ( materials.aoMap ) duplicateUVs = true;
  1595. } else {
  1596. materials.forEach( function ( material ) {
  1597. if ( material.aoMap ) duplicateUVs = true;
  1598. } );
  1599. }
  1600. if ( ! duplicateUVs ) return;
  1601. geometry.setAttribute( 'uv2', new BufferAttribute( geometry.attributes.uv.array, 2 ) );
  1602. },
  1603. };
  1604. function MaterialParser( textureLoader ) {
  1605. this.textureLoader = textureLoader;
  1606. }
  1607. MaterialParser.prototype = {
  1608. constructor: MaterialParser,
  1609. parse: function () {
  1610. var materials = [];
  1611. this.textures = {};
  1612. for ( var name in lwoTree.materials ) {
  1613. if ( lwoTree.format === 'LWO3' ) {
  1614. materials.push( this.parseMaterial( lwoTree.materials[ name ], name, lwoTree.textures ) );
  1615. } else if ( lwoTree.format === 'LWO2' ) {
  1616. materials.push( this.parseMaterialLwo2( lwoTree.materials[ name ], name, lwoTree.textures ) );
  1617. }
  1618. }
  1619. return materials;
  1620. },
  1621. parseMaterial( materialData, name, textures ) {
  1622. var params = {
  1623. name: name,
  1624. side: this.getSide( materialData.attributes ),
  1625. flatShading: this.getSmooth( materialData.attributes ),
  1626. };
  1627. var connections = this.parseConnections( materialData.connections, materialData.nodes );
  1628. var maps = this.parseTextureNodes( connections.maps );
  1629. this.parseAttributeImageMaps( connections.attributes, textures, maps, materialData.maps );
  1630. var attributes = this.parseAttributes( connections.attributes, maps );
  1631. this.parseEnvMap( connections, maps, attributes );
  1632. params = Object.assign( maps, params );
  1633. params = Object.assign( params, attributes );
  1634. var materialType = this.getMaterialType( connections.attributes );
  1635. return new materialType( params );
  1636. },
  1637. parseMaterialLwo2( materialData, name/*, textures*/ ) {
  1638. var params = {
  1639. name: name,
  1640. side: this.getSide( materialData.attributes ),
  1641. flatShading: this.getSmooth( materialData.attributes ),
  1642. };
  1643. var attributes = this.parseAttributes( materialData.attributes, {} );
  1644. params = Object.assign( params, attributes );
  1645. return new MeshPhongMaterial( params );
  1646. },
  1647. // Note: converting from left to right handed coords by switching x -> -x in vertices, and
  1648. // then switching mat FrontSide -> BackSide
  1649. // NB: this means that FrontSide and BackSide have been switched!
  1650. getSide( attributes ) {
  1651. if ( ! attributes.side ) return BackSide;
  1652. switch ( attributes.side ) {
  1653. case 0:
  1654. case 1:
  1655. return BackSide;
  1656. case 2: return FrontSide;
  1657. case 3: return DoubleSide;
  1658. }
  1659. },
  1660. getSmooth( attributes ) {
  1661. if ( ! attributes.smooth ) return true;
  1662. return ! attributes.smooth;
  1663. },
  1664. parseConnections( connections, nodes ) {
  1665. var materialConnections = {
  1666. maps: {}
  1667. };
  1668. var inputName = connections.inputName;
  1669. var inputNodeName = connections.inputNodeName;
  1670. var nodeName = connections.nodeName;
  1671. var self = this;
  1672. inputName.forEach( function ( name, index ) {
  1673. if ( name === 'Material' ) {
  1674. var matNode = self.getNodeByRefName( inputNodeName[ index ], nodes );
  1675. materialConnections.attributes = matNode.attributes;
  1676. materialConnections.envMap = matNode.fileName;
  1677. materialConnections.name = inputNodeName[ index ];
  1678. }
  1679. } );
  1680. nodeName.forEach( function ( name, index ) {
  1681. if ( name === materialConnections.name ) {
  1682. materialConnections.maps[ inputName[ index ] ] = self.getNodeByRefName( inputNodeName[ index ], nodes );
  1683. }
  1684. } );
  1685. return materialConnections;
  1686. },
  1687. getNodeByRefName( refName, nodes ) {
  1688. for ( var name in nodes ) {
  1689. if ( nodes[ name ].refName === refName ) return nodes[ name ];
  1690. }
  1691. },
  1692. parseTextureNodes( textureNodes ) {
  1693. var maps = {};
  1694. for ( var name in textureNodes ) {
  1695. var node = textureNodes[ name ];
  1696. var path = node.fileName;
  1697. if ( ! path ) return;
  1698. var texture = this.loadTexture( path );
  1699. if ( node.widthWrappingMode !== undefined ) texture.wrapS = this.getWrappingType( node.widthWrappingMode );
  1700. if ( node.heightWrappingMode !== undefined ) texture.wrapT = this.getWrappingType( node.heightWrappingMode );
  1701. switch ( name ) {
  1702. case 'Color':
  1703. maps.map = texture;
  1704. break;
  1705. case 'Roughness':
  1706. maps.roughnessMap = texture;
  1707. maps.roughness = 0.5;
  1708. break;
  1709. case 'Specular':
  1710. maps.specularMap = texture;
  1711. maps.specular = 0xffffff;
  1712. break;
  1713. case 'Luminous':
  1714. maps.emissiveMap = texture;
  1715. maps.emissive = 0x808080;
  1716. break;
  1717. case 'Luminous Color':
  1718. maps.emissive = 0x808080;
  1719. break;
  1720. case 'Metallic':
  1721. maps.metalnessMap = texture;
  1722. maps.metalness = 0.5;
  1723. break;
  1724. case 'Transparency':
  1725. case 'Alpha':
  1726. maps.alphaMap = texture;
  1727. maps.transparent = true;
  1728. break;
  1729. case 'Normal':
  1730. maps.normalMap = texture;
  1731. if ( node.amplitude !== undefined ) maps.normalScale = new Vector2( node.amplitude, node.amplitude );
  1732. break;
  1733. case 'Bump':
  1734. maps.bumpMap = texture;
  1735. break;
  1736. }
  1737. }
  1738. // LWO BSDF materials can have both spec and rough, but this is not valid in three
  1739. if ( maps.roughnessMap && maps.specularMap ) delete maps.specularMap;
  1740. return maps;
  1741. },
  1742. // maps can also be defined on individual material attributes, parse those here
  1743. // This occurs on Standard (Phong) surfaces
  1744. parseAttributeImageMaps( attributes, textures, maps ) {
  1745. for ( var name in attributes ) {
  1746. var attribute = attributes[ name ];
  1747. if ( attribute.maps ) {
  1748. var mapData = attribute.maps[ 0 ];
  1749. var path = this.getTexturePathByIndex( mapData.imageIndex, textures );
  1750. if ( ! path ) return;
  1751. var texture = this.loadTexture( path );
  1752. if ( mapData.wrap !== undefined ) texture.wrapS = this.getWrappingType( mapData.wrap.w );
  1753. if ( mapData.wrap !== undefined ) texture.wrapT = this.getWrappingType( mapData.wrap.h );
  1754. switch ( name ) {
  1755. case 'Color':
  1756. maps.map = texture;
  1757. break;
  1758. case 'Diffuse':
  1759. maps.aoMap = texture;
  1760. break;
  1761. case 'Roughness':
  1762. maps.roughnessMap = texture;
  1763. maps.roughness = 1;
  1764. break;
  1765. case 'Specular':
  1766. maps.specularMap = texture;
  1767. maps.specular = 0xffffff;
  1768. break;
  1769. case 'Luminosity':
  1770. maps.emissiveMap = texture;
  1771. maps.emissive = 0x808080;
  1772. break;
  1773. case 'Metallic':
  1774. maps.metalnessMap = texture;
  1775. maps.metalness = 1;
  1776. break;
  1777. case 'Transparency':
  1778. case 'Alpha':
  1779. maps.alphaMap = texture;
  1780. maps.transparent = true;
  1781. break;
  1782. case 'Normal':
  1783. maps.normalMap = texture;
  1784. break;
  1785. case 'Bump':
  1786. maps.bumpMap = texture;
  1787. break;
  1788. }
  1789. }
  1790. }
  1791. },
  1792. parseAttributes( attributes, maps ) {
  1793. var params = {};
  1794. // don't use color data if color map is present
  1795. if ( attributes.Color && ! maps.map ) {
  1796. params.color = new Color().fromArray( attributes.Color.value );
  1797. } else params.color = new Color();
  1798. if ( attributes.Transparency && attributes.Transparency.value !== 0 ) {
  1799. params.opacity = 1 - attributes.Transparency.value;
  1800. params.transparent = true;
  1801. }
  1802. if ( attributes[ 'Bump Height' ] ) params.bumpScale = attributes[ 'Bump Height' ].value * 0.1;
  1803. if ( attributes[ 'Refraction Index' ] ) params.refractionRatio = 1 / attributes[ 'Refraction Index' ].value;
  1804. this.parsePhysicalAttributes( params, attributes, maps );
  1805. this.parseStandardAttributes( params, attributes, maps );
  1806. this.parsePhongAttributes( params, attributes, maps );
  1807. return params;
  1808. },
  1809. parsePhysicalAttributes( params, attributes/*, maps*/ ) {
  1810. if ( attributes.Clearcoat && attributes.Clearcoat.value > 0 ) {
  1811. params.clearcoat = attributes.Clearcoat.value;
  1812. if ( attributes[ 'Clearcoat Gloss' ] ) {
  1813. params.clearcoatRoughness = 0.5 * ( 1 - attributes[ 'Clearcoat Gloss' ].value );
  1814. }
  1815. }
  1816. },
  1817. parseStandardAttributes( params, attributes, maps ) {
  1818. if ( attributes.Luminous ) {
  1819. params.emissiveIntensity = attributes.Luminous.value;
  1820. if ( attributes[ 'Luminous Color' ] && ! maps.emissive ) {
  1821. params.emissive = new Color().fromArray( attributes[ 'Luminous Color' ].value );
  1822. } else {
  1823. params.emissive = new Color( 0x808080 );
  1824. }
  1825. }
  1826. if ( attributes.Roughness && ! maps.roughnessMap ) params.roughness = attributes.Roughness.value;
  1827. if ( attributes.Metallic && ! maps.metalnessMap ) params.metalness = attributes.Metallic.value;
  1828. },
  1829. parsePhongAttributes( params, attributes, maps ) {
  1830. if ( attributes.Diffuse ) params.color.multiplyScalar( attributes.Diffuse.value );
  1831. if ( attributes.Reflection ) {
  1832. params.reflectivity = attributes.Reflection.value;
  1833. params.combine = AddOperation;
  1834. }
  1835. if ( attributes.Luminosity ) {
  1836. params.emissiveIntensity = attributes.Luminosity.value;
  1837. if ( ! maps.emissiveMap && ! maps.map ) {
  1838. params.emissive = params.color;
  1839. } else {
  1840. params.emissive = new Color( 0x808080 );
  1841. }
  1842. }
  1843. // parse specular if there is no roughness - we will interpret the material as 'Phong' in this case
  1844. if ( ! attributes.Roughness && attributes.Specular && ! maps.specularMap ) {
  1845. if ( attributes[ 'Color Highlight' ] ) {
  1846. params.specular = new Color().setScalar( attributes.Specular.value ).lerp( params.color.clone().multiplyScalar( attributes.Specular.value ), attributes[ 'Color Highlight' ].value );
  1847. } else {
  1848. params.specular = new Color().setScalar( attributes.Specular.value );
  1849. }
  1850. }
  1851. if ( params.specular && attributes.Glossiness ) params.shininess = 7 + Math.pow( 2, attributes.Glossiness.value * 12 + 2 );
  1852. },
  1853. parseEnvMap( connections, maps, attributes ) {
  1854. if ( connections.envMap ) {
  1855. var envMap = this.loadTexture( connections.envMap );
  1856. if ( attributes.transparent && attributes.opacity < 0.999 ) {
  1857. envMap.mapping = EquirectangularRefractionMapping;
  1858. // Reflectivity and refraction mapping don't work well together in Phong materials
  1859. if ( attributes.reflectivity !== undefined ) {
  1860. delete attributes.reflectivity;
  1861. delete attributes.combine;
  1862. }
  1863. if ( attributes.metalness !== undefined ) {
  1864. delete attributes.metalness;
  1865. }
  1866. } else envMap.mapping = EquirectangularReflectionMapping;
  1867. maps.envMap = envMap;
  1868. }
  1869. },
  1870. // get texture defined at top level by its index
  1871. getTexturePathByIndex( index ) {
  1872. var fileName = '';
  1873. if ( ! lwoTree.textures ) return fileName;
  1874. lwoTree.textures.forEach( function ( texture ) {
  1875. if ( texture.index === index ) fileName = texture.fileName;
  1876. } );
  1877. return fileName;
  1878. },
  1879. loadTexture( path ) {
  1880. if ( ! path ) return null;
  1881. var texture;
  1882. texture = this.textureLoader.load(
  1883. path,
  1884. undefined,
  1885. undefined,
  1886. function () {
  1887. console.warn( 'LWOLoader: non-standard resource hierarchy. Use \`resourcePath\` parameter to specify root content directory.' );
  1888. }
  1889. );
  1890. return texture;
  1891. },
  1892. // 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge
  1893. getWrappingType( num ) {
  1894. switch ( num ) {
  1895. case 0:
  1896. console.warn( 'LWOLoader: "Reset" texture wrapping type is not supported in three.js' );
  1897. return ClampToEdgeWrapping;
  1898. case 1: return RepeatWrapping;
  1899. case 2: return MirroredRepeatWrapping;
  1900. case 3: return ClampToEdgeWrapping;
  1901. }
  1902. },
  1903. getMaterialType( nodeData ) {
  1904. if ( nodeData.Clearcoat && nodeData.Clearcoat.value > 0 ) return MeshPhysicalMaterial;
  1905. if ( nodeData.Roughness ) return MeshStandardMaterial;
  1906. return MeshPhongMaterial;
  1907. }
  1908. };
  1909. function GeometryParser() {}
  1910. GeometryParser.prototype = {
  1911. constructor: GeometryParser,
  1912. parse( geoData, layer ) {
  1913. var geometry = new BufferGeometry();
  1914. geometry.setAttribute( 'position', new Float32BufferAttribute( geoData.points, 3 ) );
  1915. var indices = this.splitIndices( geoData.vertexIndices, geoData.polygonDimensions );
  1916. geometry.setIndex( indices );
  1917. this.parseGroups( geometry, geoData );
  1918. geometry.computeVertexNormals();
  1919. this.parseUVs( geometry, layer, indices );
  1920. this.parseMorphTargets( geometry, layer, indices );
  1921. // TODO: z may need to be reversed to account for coordinate system change
  1922. geometry.translate( - layer.pivot[ 0 ], - layer.pivot[ 1 ], - layer.pivot[ 2 ] );
  1923. // var userData = geometry.userData;
  1924. // geometry = geometry.toNonIndexed()
  1925. // geometry.userData = userData;
  1926. return geometry;
  1927. },
  1928. // split quads into tris
  1929. splitIndices( indices, polygonDimensions ) {
  1930. var remappedIndices = [];
  1931. var i = 0;
  1932. polygonDimensions.forEach( function ( dim ) {
  1933. if ( dim < 4 ) {
  1934. for ( var k = 0; k < dim; k ++ ) remappedIndices.push( indices[ i + k ] );
  1935. } else if ( dim === 4 ) {
  1936. remappedIndices.push(
  1937. indices[ i ],
  1938. indices[ i + 1 ],
  1939. indices[ i + 2 ],
  1940. indices[ i ],
  1941. indices[ i + 2 ],
  1942. indices[ i + 3 ]
  1943. );
  1944. } else if ( dim > 4 ) {
  1945. for ( var k = 1; k < dim - 1; k ++ ) {
  1946. remappedIndices.push( indices[ i ], indices[ i + k ], indices[ i + k + 1 ] );
  1947. }
  1948. console.warn( 'LWOLoader: polygons with greater than 4 sides are not supported' );
  1949. }
  1950. i += dim;
  1951. } );
  1952. return remappedIndices;
  1953. },
  1954. // NOTE: currently ignoring poly indices and assuming that they are intelligently ordered
  1955. parseGroups( geometry, geoData ) {
  1956. var tags = lwoTree.tags;
  1957. var matNames = [];
  1958. var elemSize = 3;
  1959. if ( geoData.type === 'lines' ) elemSize = 2;
  1960. if ( geoData.type === 'points' ) elemSize = 1;
  1961. var remappedIndices = this.splitMaterialIndices( geoData.polygonDimensions, geoData.materialIndices );
  1962. var indexNum = 0; // create new indices in numerical order
  1963. var indexPairs = {}; // original indices mapped to numerical indices
  1964. var prevMaterialIndex;
  1965. var prevStart = 0;
  1966. var currentCount = 0;
  1967. for ( var i = 0; i < remappedIndices.length; i += 2 ) {
  1968. var materialIndex = remappedIndices[ i + 1 ];
  1969. if ( i === 0 ) matNames[ indexNum ] = tags[ materialIndex ];
  1970. if ( prevMaterialIndex === undefined ) prevMaterialIndex = materialIndex;
  1971. if ( materialIndex !== prevMaterialIndex ) {
  1972. var currentIndex;
  1973. if ( indexPairs[ tags[ prevMaterialIndex ] ] ) {
  1974. currentIndex = indexPairs[ tags[ prevMaterialIndex ] ];
  1975. } else {
  1976. currentIndex = indexNum;
  1977. indexPairs[ tags[ prevMaterialIndex ] ] = indexNum;
  1978. matNames[ indexNum ] = tags[ prevMaterialIndex ];
  1979. indexNum ++;
  1980. }
  1981. geometry.addGroup( prevStart, currentCount, currentIndex );
  1982. prevStart += currentCount;
  1983. prevMaterialIndex = materialIndex;
  1984. currentCount = 0;
  1985. }
  1986. currentCount += elemSize;
  1987. }
  1988. // the loop above doesn't add the last group, do that here.
  1989. if ( geometry.groups.length > 0 ) {
  1990. var currentIndex;
  1991. if ( indexPairs[ tags[ materialIndex ] ] ) {
  1992. currentIndex = indexPairs[ tags[ materialIndex ] ];
  1993. } else {
  1994. currentIndex = indexNum;
  1995. indexPairs[ tags[ materialIndex ] ] = indexNum;
  1996. matNames[ indexNum ] = tags[ materialIndex ];
  1997. }
  1998. geometry.addGroup( prevStart, currentCount, currentIndex );
  1999. }
  2000. // Mat names from TAGS chunk, used to build up an array of materials for this geometry
  2001. geometry.userData.matNames = matNames;
  2002. },
  2003. splitMaterialIndices( polygonDimensions, indices ) {
  2004. var remappedIndices = [];
  2005. polygonDimensions.forEach( function ( dim, i ) {
  2006. if ( dim <= 3 ) {
  2007. remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ] );
  2008. } else if ( dim === 4 ) {
  2009. remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ], indices[ i * 2 ], indices[ i * 2 + 1 ] );
  2010. } else {
  2011. // ignore > 4 for now
  2012. for ( var k = 0; k < dim - 2; k ++ ) {
  2013. remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ] );
  2014. }
  2015. }
  2016. } );
  2017. return remappedIndices;
  2018. },
  2019. // UV maps:
  2020. // 1: are defined via index into an array of points, not into a geometry
  2021. // - the geometry is also defined by an index into this array, but the indexes may not match
  2022. // 2: there can be any number of UV maps for a single geometry. Here these are combined,
  2023. // with preference given to the first map encountered
  2024. // 3: UV maps can be partial - that is, defined for only a part of the geometry
  2025. // 4: UV maps can be VMAP or VMAD (discontinuous, to allow for seams). In practice, most
  2026. // UV maps are defined as partially VMAP and partially VMAD
  2027. // VMADs are currently not supported
  2028. parseUVs( geometry, layer ) {
  2029. // start by creating a UV map set to zero for the whole geometry
  2030. var remappedUVs = Array.from( Array( geometry.attributes.position.count * 2 ), function () {
  2031. return 0;
  2032. } );
  2033. for ( var name in layer.uvs ) {
  2034. var uvs = layer.uvs[ name ].uvs;
  2035. var uvIndices = layer.uvs[ name ].uvIndices;
  2036. uvIndices.forEach( function ( i, j ) {
  2037. remappedUVs[ i * 2 ] = uvs[ j * 2 ];
  2038. remappedUVs[ i * 2 + 1 ] = uvs[ j * 2 + 1 ];
  2039. } );
  2040. }
  2041. geometry.setAttribute( 'uv', new Float32BufferAttribute( remappedUVs, 2 ) );
  2042. },
  2043. parseMorphTargets( geometry, layer ) {
  2044. var num = 0;
  2045. for ( var name in layer.morphTargets ) {
  2046. var remappedPoints = geometry.attributes.position.array.slice();
  2047. if ( ! geometry.morphAttributes.position ) geometry.morphAttributes.position = [];
  2048. var morphPoints = layer.morphTargets[ name ].points;
  2049. var morphIndices = layer.morphTargets[ name ].indices;
  2050. var type = layer.morphTargets[ name ].type;
  2051. morphIndices.forEach( function ( i, j ) {
  2052. if ( type === 'relative' ) {
  2053. remappedPoints[ i * 3 ] += morphPoints[ j * 3 ];
  2054. remappedPoints[ i * 3 + 1 ] += morphPoints[ j * 3 + 1 ];
  2055. remappedPoints[ i * 3 + 2 ] += morphPoints[ j * 3 + 2 ];
  2056. } else {
  2057. remappedPoints[ i * 3 ] = morphPoints[ j * 3 ];
  2058. remappedPoints[ i * 3 + 1 ] = morphPoints[ j * 3 + 1 ];
  2059. remappedPoints[ i * 3 + 2 ] = morphPoints[ j * 3 + 2 ];
  2060. }
  2061. } );
  2062. geometry.morphAttributes.position[ num ] = new Float32BufferAttribute( remappedPoints, 3 );
  2063. geometry.morphAttributes.position[ num ].name = name;
  2064. num ++;
  2065. }
  2066. geometry.morphTargetsRelative = false;
  2067. },
  2068. };
  2069. // ************** UTILITY FUNCTIONS **************
  2070. function extractParentUrl( url, dir ) {
  2071. var index = url.indexOf( dir );
  2072. if ( index === - 1 ) return './';
  2073. return url.substr( 0, index );
  2074. }
  2075. export { LWOLoader };