LWOLoader.js 68 KB

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