USDZExporter.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import { zipSync, strToU8 } from '../libs/fflate.module.min.js';
  2. class USDZExporter {
  3. parse( scene ) {
  4. let output = buildHeader();
  5. const materials = {};
  6. scene.traverse( ( object ) => {
  7. if ( object.isMesh ) {
  8. materials[ object.material.uuid ] = object.material;
  9. output += buildXform( object, buildMesh( object.geometry, object.material ) );
  10. }
  11. } );
  12. output += buildMaterials( materials );
  13. return zipSync( { 'model.usda': strToU8( output ) }, { level: 0 } );
  14. }
  15. }
  16. //
  17. function buildHeader() {
  18. return `#usda 1.0
  19. (
  20. doc = "Three.js"
  21. metersPerUnit = 1
  22. upAxis = "Y"
  23. )
  24. `;
  25. }
  26. // Xform
  27. function buildXform( object, define ) {
  28. const name = 'Object' + object.id;
  29. const transform = buildMatrix( object.matrixWorld );
  30. return `def Xform "${ name }"
  31. {
  32. matrix4d xformOp:transform = ${ transform }
  33. uniform token[] xformOpOrder = ["xformOp:transform"]
  34. ${ define }
  35. }
  36. `;
  37. }
  38. function buildMatrix( matrix ) {
  39. const array = matrix.elements;
  40. return `( ${ buildMatrixRow( array, 0 ) }, ${ buildMatrixRow( array, 4 ) }, ${ buildMatrixRow( array, 8 ) }, ${ buildMatrixRow( array, 12 ) } )`;
  41. }
  42. function buildMatrixRow( array, offset ) {
  43. return `(${ array[ offset + 0 ] }, ${ array[ offset + 1 ] }, ${ array[ offset + 2 ] }, ${ array[ offset + 3 ] })`;
  44. }
  45. // Mesh
  46. function buildMesh( geometry, material ) {
  47. const name = 'Geometry' + geometry.id;
  48. const attributes = geometry.attributes;
  49. const count = attributes.position.count;
  50. return `def Mesh "${ name }"
  51. {
  52. int[] faceVertexCounts = [${ buildMeshVertexCount( geometry ) }]
  53. int[] faceVertexIndices = [${ buildMeshVertexIndices( geometry ) }]
  54. rel material:binding = </_materials/Material_${ material.id }>
  55. normal3f[] normals = [${ buildVector3Array( attributes.normal, count )}] (
  56. interpolation = "faceVarying"
  57. )
  58. point3f[] points = [${ buildVector3Array( attributes.position, count )}]
  59. texCoord2f[] primvars:UVMap = [${ buildVector2Array( attributes.uv, count )}] (
  60. interpolation = "faceVarying"
  61. )
  62. uniform token subdivisionScheme = "none"
  63. }
  64. `;
  65. }
  66. function buildMeshVertexCount( geometry ) {
  67. const count = geometry.index !== null ? geometry.index.array.length : geometry.attributes.position.count;
  68. return Array( count / 3 ).fill( 3 ).join( ', ' );
  69. }
  70. function buildMeshVertexIndices( geometry ) {
  71. if ( geometry.index !== null ) {
  72. return geometry.index.array.join( ', ' );
  73. }
  74. const array = [];
  75. const length = geometry.attributes.position.count;
  76. for ( let i = 0; i < length; i ++ ) {
  77. array.push( i );
  78. }
  79. return array.join( ', ' );
  80. }
  81. function buildVector3Array( attribute, count ) {
  82. if ( attribute === undefined ) {
  83. console.warn( 'USDZExporter: Normals missing.' );
  84. return Array( count ).fill( '(0, 0, 0)' ).join( ', ' );
  85. }
  86. const array = [];
  87. const data = attribute.array;
  88. for ( let i = 0; i < data.length; i += 3 ) {
  89. array.push( `(${ data[ i + 0 ] }, ${ data[ i + 1 ] }, ${ data[ i + 2 ] })` );
  90. }
  91. return array.join( ', ' );
  92. }
  93. function buildVector2Array( attribute, count ) {
  94. if ( attribute === undefined ) {
  95. console.warn( 'USDZExporter: UVs missing.' );
  96. return Array( count ).fill( '(0, 0)' ).join( ', ' );
  97. }
  98. const array = [];
  99. const data = attribute.array;
  100. for ( let i = 0; i < data.length; i += 2 ) {
  101. array.push( `(${ data[ i + 0 ] }, ${ data[ i + 1 ] })` );
  102. }
  103. return array.join( ', ' );
  104. }
  105. // Materials
  106. function buildMaterials( materials ) {
  107. const array = [];
  108. for ( const uuid in materials ) {
  109. const material = materials[ uuid ];
  110. array.push( buildMaterial( material ) );
  111. }
  112. return `def "_materials"
  113. {
  114. ${ array.join( '' ) }
  115. }
  116. `;
  117. }
  118. function buildMaterial( material ) {
  119. return `
  120. def Material "Material_${ material.id }"
  121. {
  122. token outputs:surface.connect = </_materials/Material_${ material.id }/previewShader.outputs:surface>
  123. def Shader "previewShader"
  124. {
  125. uniform token info:id = "UsdPreviewSurface"
  126. color3f inputs:diffuseColor = ${ buildColor( material.color ) }
  127. color3f inputs:emissiveColor = ${ buildColor( material.emissive ) }
  128. float inputs:metallic = ${ material.metalness }
  129. float inputs:roughness = ${ material.roughness }
  130. token outputs:surface
  131. }
  132. }
  133. `;
  134. }
  135. function buildColor( color ) {
  136. return `(${ color.r }, ${ color.g }, ${ color.b })`;
  137. }
  138. export { USDZExporter };