USDZLoader.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. import {
  2. BufferAttribute,
  3. BufferGeometry,
  4. ClampToEdgeWrapping,
  5. FileLoader,
  6. Group,
  7. Loader,
  8. Mesh,
  9. MeshStandardMaterial,
  10. MirroredRepeatWrapping,
  11. RepeatWrapping,
  12. sRGBEncoding,
  13. TextureLoader,
  14. } from 'three';
  15. import * as fflate from '../libs/fflate.module.js';
  16. class USDAParser {
  17. parse( text ) {
  18. const data = {};
  19. const lines = text.split( '\n' );
  20. const length = lines.length;
  21. let current = 0;
  22. let string = null;
  23. let target = data;
  24. const stack = [ data ];
  25. // debugger;
  26. function parseNextLine() {
  27. const line = lines[ current ];
  28. // console.log( line );
  29. if ( line.includes( '=' ) ) {
  30. const assignment = line.split( '=' );
  31. const lhs = assignment[ 0 ].trim();
  32. const rhs = assignment[ 1 ].trim();
  33. if ( rhs.endsWith( '{' ) ) {
  34. const group = {};
  35. stack.push( group );
  36. target[ lhs ] = group;
  37. target = group;
  38. } else {
  39. target[ lhs ] = rhs;
  40. }
  41. } else if ( line.endsWith( '{' ) ) {
  42. const group = target[ string ] || {};
  43. stack.push( group );
  44. target[ string ] = group;
  45. target = group;
  46. } else if ( line.endsWith( '}' ) ) {
  47. stack.pop();
  48. if ( stack.length === 0 ) return;
  49. target = stack[ stack.length - 1 ];
  50. } else if ( line.endsWith( '(' ) ) {
  51. const meta = {};
  52. stack.push( meta );
  53. string = line.split( '(' )[ 0 ].trim() || string;
  54. target[ string ] = meta;
  55. target = meta;
  56. } else if ( line.endsWith( ')' ) ) {
  57. stack.pop();
  58. target = stack[ stack.length - 1 ];
  59. } else {
  60. string = line.trim();
  61. }
  62. current ++;
  63. if ( current < length ) {
  64. parseNextLine();
  65. }
  66. }
  67. parseNextLine();
  68. return data;
  69. }
  70. }
  71. class USDZLoader extends Loader {
  72. constructor( manager ) {
  73. super( manager );
  74. }
  75. load( url, onLoad, onProgress, onError ) {
  76. const scope = this;
  77. const loader = new FileLoader( scope.manager );
  78. loader.setPath( scope.path );
  79. loader.setResponseType( 'arraybuffer' );
  80. loader.setRequestHeader( scope.requestHeader );
  81. loader.setWithCredentials( scope.withCredentials );
  82. loader.load( url, function ( text ) {
  83. try {
  84. onLoad( scope.parse( text ) );
  85. } catch ( e ) {
  86. if ( onError ) {
  87. onError( e );
  88. } else {
  89. console.error( e );
  90. }
  91. scope.manager.itemError( url );
  92. }
  93. }, onProgress, onError );
  94. }
  95. parse( buffer ) {
  96. const parser = new USDAParser();
  97. function parseAssets( zip ) {
  98. const data = {};
  99. const loader = new FileLoader();
  100. loader.setResponseType( 'arraybuffer' );
  101. for ( const filename in zip ) {
  102. if ( filename.endsWith( 'png' ) ) {
  103. const blob = new Blob( [ zip[ filename ] ], { type: { type: 'image/png' } } );
  104. data[ filename ] = URL.createObjectURL( blob );
  105. }
  106. if ( filename.endsWith( 'usd' ) ) {
  107. const text = fflate.strFromU8( zip[ filename ] );
  108. data[ filename ] = parser.parse( text );
  109. }
  110. }
  111. return data;
  112. }
  113. function findUSD( zip ) {
  114. for ( const filename in zip ) {
  115. if ( filename.endsWith( 'usda' ) ) {
  116. return zip[ filename ];
  117. }
  118. }
  119. }
  120. const zip = fflate.unzipSync( new Uint8Array( buffer ) ); // eslint-disable-line no-undef
  121. console.log( zip );
  122. const assets = parseAssets( zip );
  123. // console.log( assets )
  124. const file = findUSD( zip );
  125. if ( file === undefined ) {
  126. console.warn( 'THREE.USDZLoader: No usda file found.' );
  127. return new Group();
  128. }
  129. // Parse file
  130. const text = fflate.strFromU8( file );
  131. const root = parser.parse( text );
  132. // Build scene
  133. function findMeshGeometry( data ) {
  134. if ( 'prepend references' in data ) {
  135. const reference = data[ 'prepend references' ];
  136. const parts = reference.split( '@' );
  137. const path = parts[ 1 ].replace( /^.\//, '' );
  138. const id = parts[ 2 ].replace( /^<\//, '' ).replace( />$/, '' );
  139. return findGeometry( assets[ path ], id );
  140. }
  141. return findGeometry( data );
  142. }
  143. function findGeometry( data, id ) {
  144. if ( id !== undefined ) {
  145. const def = `def "%{id}"`;
  146. if ( def in data ) {
  147. return data[ def ];
  148. }
  149. }
  150. for ( const name in data ) {
  151. const object = data[ name ];
  152. if ( name.startsWith( 'def Mesh' ) ) {
  153. // Move points to Mesh
  154. if ( 'point3f[] points' in data ) {
  155. object[ 'point3f[] points' ] = data[ 'point3f[] points' ];
  156. }
  157. // Move st to Mesh
  158. if ( 'float2[] primvars:st' in data ) {
  159. object[ 'float2[] primvars:st' ] = data[ 'float2[] primvars:st' ];
  160. }
  161. // Move st indices to Mesh
  162. if ( 'int[] primvars:st:indices' in data ) {
  163. object[ 'int[] primvars:st:indices' ] = data[ 'int[] primvars:st:indices' ];
  164. }
  165. return object;
  166. }
  167. if ( typeof object === 'object' ) {
  168. const geometry = findGeometry( object );
  169. if ( geometry ) return geometry;
  170. }
  171. }
  172. }
  173. function buildGeometry( data ) {
  174. let geometry = new BufferGeometry();
  175. if ( 'int[] faceVertexIndices' in data ) {
  176. const indices = JSON.parse( data[ 'int[] faceVertexIndices' ] );
  177. geometry.setIndex( new BufferAttribute( new Uint16Array( indices ), 1 ) );
  178. }
  179. if ( 'point3f[] points' in data ) {
  180. const positions = JSON.parse( data[ 'point3f[] points' ].replace( /[()]*/g, '' ) );
  181. const attribute = new BufferAttribute( new Float32Array( positions ), 3 );
  182. geometry.setAttribute( 'position', attribute );
  183. }
  184. if ( 'normal3f[] normals' in data ) {
  185. const normals = JSON.parse( data[ 'normal3f[] normals' ].replace( /[()]*/g, '' ) );
  186. const attribute = new BufferAttribute( new Float32Array( normals ), 3 );
  187. geometry.setAttribute( 'normal', attribute );
  188. } else {
  189. geometry.computeVertexNormals();
  190. }
  191. if ( 'float2[] primvars:st' in data ) {
  192. data[ 'texCoord2f[] primvars:st' ] = data[ 'float2[] primvars:st' ];
  193. }
  194. if ( 'texCoord2f[] primvars:st' in data ) {
  195. const uvs = JSON.parse( data[ 'texCoord2f[] primvars:st' ].replace( /[()]*/g, '' ) );
  196. const attribute = new BufferAttribute( new Float32Array( uvs ), 2 );
  197. if ( 'int[] primvars:st:indices' in data ) {
  198. geometry = geometry.toNonIndexed();
  199. const indices = JSON.parse( data[ 'int[] primvars:st:indices' ] );
  200. geometry.setAttribute( 'uv', toFlatBufferAttribute( attribute, indices ) );
  201. } else {
  202. geometry.setAttribute( 'uv', attribute );
  203. }
  204. }
  205. return geometry;
  206. }
  207. function toFlatBufferAttribute( attribute, indices ) {
  208. const array = attribute.array;
  209. const itemSize = attribute.itemSize;
  210. const array2 = new array.constructor( indices.length * itemSize );
  211. let index = 0, index2 = 0;
  212. for ( let i = 0, l = indices.length; i < l; i ++ ) {
  213. index = indices[ i ] * itemSize;
  214. for ( let j = 0; j < itemSize; j ++ ) {
  215. array2[ index2 ++ ] = array[ index ++ ];
  216. }
  217. }
  218. return new BufferAttribute( array2, itemSize );
  219. }
  220. function findMeshMaterial( data ) {
  221. if ( 'rel material:binding' in data ) {
  222. const reference = data[ 'rel material:binding' ];
  223. const id = reference.replace( /^<\//, '' ).replace( />$/, '' );
  224. const parts = id.split( '/' );
  225. return findMaterial( root, ` "${ parts[ 1 ] }"` );
  226. }
  227. return findMaterial( data );
  228. }
  229. function findMaterial( data, id = '' ) {
  230. for ( const name in data ) {
  231. const object = data[ name ];
  232. if ( name.startsWith( 'def Material' + id ) ) {
  233. return object;
  234. }
  235. if ( typeof object === 'object' ) {
  236. const material = findMaterial( object, id );
  237. if ( material ) return material;
  238. }
  239. }
  240. }
  241. function buildMaterial( data ) {
  242. const material = new MeshStandardMaterial();
  243. if ( data !== undefined ) {
  244. if ( 'def Shader "PreviewSurface"' in data ) {
  245. const surface = data[ 'def Shader "PreviewSurface"' ];
  246. if ( 'color3f inputs:diffuseColor.connect' in surface ) {
  247. const path = surface[ 'color3f inputs:diffuseColor.connect' ];
  248. const sampler = findTexture( root, /(\w+).output/.exec( path )[ 1 ] );
  249. material.map = buildTexture( sampler );
  250. material.map.encoding = sRGBEncoding;
  251. } else if ( 'color3f inputs:diffuseColor' in surface ) {
  252. const color = surface[ 'color3f inputs:diffuseColor' ].replace( /[()]*/g, '' );
  253. material.color.fromArray( JSON.parse( '[' + color + ']' ) );
  254. }
  255. if ( 'normal3f inputs:normal.connect' in surface ) {
  256. const path = surface[ 'normal3f inputs:normal.connect' ];
  257. const sampler = findTexture( root, /(\w+).output/.exec( path )[ 1 ] );
  258. material.normalMap = buildTexture( sampler );
  259. }
  260. if ( 'float inputs:roughness' in surface ) {
  261. material.roughness = parseFloat( surface[ 'float inputs:roughness' ] );
  262. }
  263. if ( 'float inputs:metallic' in surface ) {
  264. material.metalness = parseFloat( surface[ 'float inputs:metallic' ] );
  265. }
  266. }
  267. if ( 'def Shader "diffuseColor_texture"' in data ) {
  268. const sampler = data[ 'def Shader "diffuseColor_texture"' ];
  269. material.map = buildTexture( sampler );
  270. material.map.encoding = sRGBEncoding;
  271. }
  272. if ( 'def Shader "normal_texture"' in data ) {
  273. const sampler = data[ 'def Shader "normal_texture"' ];
  274. material.normalMap = buildTexture( sampler );
  275. }
  276. }
  277. return material;
  278. }
  279. function findTexture( data, id ) {
  280. for ( const name in data ) {
  281. const object = data[ name ];
  282. if ( name.startsWith( `def Shader "${ id }"` ) ) {
  283. return object;
  284. }
  285. if ( typeof object === 'object' ) {
  286. const texture = findTexture( object, id );
  287. if ( texture ) return texture;
  288. }
  289. }
  290. }
  291. function buildTexture( data ) {
  292. if ( 'asset inputs:file' in data ) {
  293. const path = data[ 'asset inputs:file' ].replace( /@*/g, '' );
  294. const loader = new TextureLoader();
  295. const texture = loader.load( assets[ path ] );
  296. const map = {
  297. '"clamp"': ClampToEdgeWrapping,
  298. '"mirror"': MirroredRepeatWrapping,
  299. '"repeat"': RepeatWrapping
  300. };
  301. if ( 'token inputs:wrapS' in data ) {
  302. texture.wrapS = map[ data[ 'token inputs:wrapS' ] ];
  303. }
  304. if ( 'token inputs:wrapT' in data ) {
  305. texture.wrapT = map[ data[ 'token inputs:wrapT' ] ];
  306. }
  307. return texture;
  308. }
  309. return null;
  310. }
  311. function buildMesh( data ) {
  312. const geometry = buildGeometry( findMeshGeometry( data ) );
  313. const material = buildMaterial( findMeshMaterial( data ) );
  314. const mesh = new Mesh( geometry, material );
  315. if ( 'matrix4d xformOp:transform' in data ) {
  316. const array = JSON.parse( '[' + data[ 'matrix4d xformOp:transform' ].replace( /[()]*/g, '' ) + ']' );
  317. mesh.matrix.fromArray( array );
  318. mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
  319. }
  320. return mesh;
  321. }
  322. // console.log( data );
  323. const group = new Group();
  324. for ( const name in root ) {
  325. if ( name.startsWith( 'def Xform' ) ) {
  326. const mesh = buildMesh( root[ name ] );
  327. if ( /def Xform "(\w+)"/.test( name ) ) {
  328. mesh.name = /def Xform "(\w+)"/.exec( name )[ 1 ];
  329. }
  330. group.add( mesh );
  331. }
  332. }
  333. // console.log( group );
  334. return group;
  335. }
  336. }
  337. export { USDZLoader };