USDZLoader.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. import {
  2. BufferAttribute,
  3. BufferGeometry,
  4. FileLoader,
  5. Group,
  6. Loader,
  7. Mesh,
  8. MeshStandardMaterial,
  9. TextureLoader
  10. } from 'three';
  11. import * as fflate from '../libs/fflate.module.js';
  12. class USDZLoader extends Loader {
  13. constructor( manager ) {
  14. super( manager );
  15. }
  16. load( url, onLoad, onProgress, onError ) {
  17. const scope = this;
  18. const loader = new FileLoader( scope.manager );
  19. loader.setPath( scope.path );
  20. loader.setResponseType( 'arraybuffer' );
  21. loader.setRequestHeader( scope.requestHeader );
  22. loader.setWithCredentials( scope.withCredentials );
  23. loader.load( url, function ( text ) {
  24. try {
  25. onLoad( scope.parse( text ) );
  26. } catch ( e ) {
  27. if ( onError ) {
  28. onError( e );
  29. } else {
  30. console.error( e );
  31. }
  32. scope.manager.itemError( url );
  33. }
  34. }, onProgress, onError );
  35. }
  36. parse( buffer ) {
  37. function createImages( zip ) {
  38. const data = {};
  39. const loader = new FileLoader();
  40. loader.setResponseType( 'arraybuffer' );
  41. for ( const filename in zip ) {
  42. if ( filename.endsWith( 'png' ) ) {
  43. const blob = new Blob( [ zip[ filename ] ], { type: { type: 'image/png' } } );
  44. data[ filename ] = URL.createObjectURL( blob );
  45. }
  46. }
  47. return data;
  48. }
  49. function findUSD( zip ) {
  50. for ( const filename in zip ) {
  51. if ( filename.endsWith( 'usda' ) ) {
  52. return zip[ filename ];
  53. }
  54. }
  55. }
  56. const zip = fflate.unzipSync( new Uint8Array( buffer ) ); // eslint-disable-line no-undef
  57. // console.log( zip );
  58. const images = createImages( zip );
  59. const file = findUSD( zip );
  60. if ( file === undefined ) {
  61. console.warn( 'THREE.USDZLoader: No usda file found.', zip );
  62. return new Group();
  63. }
  64. // Parse file
  65. const text = fflate.strFromU8( file );
  66. const lines = text.split( '\n' );
  67. const length = lines.length;
  68. const data = {};
  69. let current = 0;
  70. let string = null;
  71. let target = data;
  72. const stack = [ data ];
  73. // debugger;
  74. function parseNextLine() {
  75. const line = lines[ current ];
  76. // console.log( line );
  77. if ( line.includes( '=' ) ) {
  78. const assignment = line.split( '=' );
  79. const lhs = assignment[ 0 ].trim();
  80. const rhs = assignment[ 1 ].trim();
  81. if ( rhs.endsWith( '{' ) ) {
  82. const group = {};
  83. stack.push( group );
  84. target[ lhs ] = group;
  85. target = group;
  86. } else {
  87. target[ lhs ] = rhs;
  88. }
  89. } else if ( line.endsWith( '{' ) ) {
  90. const group = target[ string ] || {};
  91. stack.push( group );
  92. target[ string ] = group;
  93. target = group;
  94. } else if ( line.endsWith( '}' ) ) {
  95. stack.pop();
  96. if ( stack.length === 0 ) return;
  97. target = stack[ stack.length - 1 ];
  98. } else if ( line.endsWith( '(' ) ) {
  99. const meta = {};
  100. stack.push( meta );
  101. string = line.split( '(' )[ 0 ].trim() || string;
  102. target[ string ] = meta;
  103. target = meta;
  104. } else if ( line.endsWith( ')' ) ) {
  105. stack.pop();
  106. target = stack[ stack.length - 1 ];
  107. } else {
  108. string = line.trim();
  109. }
  110. current ++;
  111. if ( current < length ) {
  112. parseNextLine();
  113. }
  114. }
  115. parseNextLine();
  116. // Build scene
  117. function findGeometry( data ) {
  118. for ( const name in data ) {
  119. const object = data[ name ];
  120. if ( name.startsWith( 'def Mesh' ) ) {
  121. // Move st indices to Mesh
  122. if ( data[ 'int[] primvars:st:indices' ] ) {
  123. object[ 'int[] primvars:st:indices' ] = data[ 'int[] primvars:st:indices' ];
  124. }
  125. return object;
  126. }
  127. if ( typeof object === 'object' ) {
  128. const geometry = findGeometry( object );
  129. if ( geometry ) return geometry;
  130. }
  131. }
  132. }
  133. function buildGeometry( data ) {
  134. const geometry = new BufferGeometry();
  135. const positions = JSON.parse( data[ 'point3f[] points' ].replace( /[()]*/g, '' ) );
  136. const attribute = new BufferAttribute( new Float32Array( positions ), 3 );
  137. if ( data[ 'int[] faceVertexIndices' ] ) {
  138. const indices = JSON.parse( data[ 'int[] faceVertexIndices' ] );
  139. geometry.setAttribute( 'position', toFlatBufferAttribute( attribute, indices ) );
  140. } else {
  141. geometry.setAttribute( 'position', attribute );
  142. }
  143. if ( data[ 'texCoord2f[] primvars:st' ] ) {
  144. const uvs = JSON.parse( data[ 'texCoord2f[] primvars:st' ].replace( /[()]*/g, '' ) );
  145. const attribute = new BufferAttribute( new Float32Array( uvs ), 2 );
  146. if ( data[ 'int[] primvars:st:indices' ] ) {
  147. const indices = JSON.parse( data[ 'int[] primvars:st:indices' ] );
  148. geometry.setAttribute( 'uv', toFlatBufferAttribute( attribute, indices ) );
  149. } else {
  150. geometry.setAttribute( 'uv', attribute );
  151. }
  152. }
  153. geometry.computeVertexNormals();
  154. return geometry;
  155. }
  156. function toFlatBufferAttribute( attribute, indices ) {
  157. const array = attribute.array;
  158. const itemSize = attribute.itemSize;
  159. const array2 = new array.constructor( indices.length * itemSize );
  160. let index = 0, index2 = 0;
  161. for ( let i = 0, l = indices.length; i < l; i ++ ) {
  162. index = indices[ i ] * itemSize;
  163. for ( let j = 0; j < itemSize; j ++ ) {
  164. array2[ index2 ++ ] = array[ index ++ ];
  165. }
  166. }
  167. return new BufferAttribute( array2, itemSize );
  168. }
  169. function findMaterial( data ) {
  170. for ( const name in data ) {
  171. const object = data[ name ];
  172. if ( name.startsWith( 'def Material' ) ) {
  173. return object;
  174. }
  175. if ( typeof object === 'object' ) {
  176. const material = findMaterial( object );
  177. if ( material ) return material;
  178. }
  179. }
  180. }
  181. function buildMaterial( data ) {
  182. const material = new MeshStandardMaterial();
  183. // console.log( data );
  184. if ( data[ 'def Shader "diffuseColor_texture"' ] ) {
  185. const texture = data[ 'def Shader "diffuseColor_texture"' ];
  186. const file = texture[ 'asset inputs:file' ].replace( /@*/g, '' );
  187. material.map = new TextureLoader().load( images[ file ] );
  188. }
  189. if ( data[ 'def Shader "normal_texture"' ] ) {
  190. const texture = data[ 'def Shader "normal_texture"' ];
  191. const file = texture[ 'asset inputs:file' ].replace( /@*/g, '' );
  192. material.normalMap = new TextureLoader().load( images[ file ] );
  193. }
  194. return material;
  195. }
  196. function buildMesh( data ) {
  197. const geometry = buildGeometry( findGeometry( data ) );
  198. const material = buildMaterial( findMaterial( data ) );
  199. const mesh = new Mesh( geometry, material );
  200. return mesh;
  201. }
  202. // console.log( data );
  203. const group = new Group();
  204. for ( const name in data ) {
  205. if ( name.startsWith( 'def Xform' ) ) {
  206. const mesh = buildMesh( data[ name ] );
  207. group.add( mesh );
  208. }
  209. }
  210. // console.log( group );
  211. return group;
  212. }
  213. }
  214. export { USDZLoader };