Skip to content

Commit 31c8f4a

Browse files
authored
Merge pull request #21096 from mrdoob/usdz
Added USDZExporter
2 parents 0d83541 + b47c97d commit 31c8f4a

File tree

5 files changed

+2612
-0
lines changed

5 files changed

+2612
-0
lines changed

editor/js/Menubar.File.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,22 @@ function MenubarFile( editor ) {
379379
} );
380380
options.add( option );
381381

382+
// Export USDZ
383+
384+
var option = new UIRow();
385+
option.setClass( 'option' );
386+
option.setTextContent( strings.getKey( 'menubar/file/export/usdz' ) );
387+
option.onClick( async function () {
388+
389+
var { USDZExporter } = await import( '../../examples/jsm/exporters/USDZExporter.js' );
390+
391+
var exporter = new USDZExporter();
392+
393+
saveArrayBuffer( exporter.parse( editor.scene, { binary: true } ), 'model.usdz' );
394+
395+
} );
396+
options.add( option );
397+
382398
//
383399

384400
options.add( new UIHorizontalRule() );

editor/js/Strings.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ function Strings( config ) {
2121
'menubar/file/export/ply_binary': 'Export PLY (Binary)',
2222
'menubar/file/export/stl': 'Export STL',
2323
'menubar/file/export/stl_binary': 'Export STL (Binary)',
24+
'menubar/file/export/usdz': 'Export USDZ',
2425
'menubar/file/publish': 'Publish',
2526

2627
'menubar/edit': 'Edit',
@@ -344,6 +345,7 @@ function Strings( config ) {
344345
'menubar/file/export/ply_binary': 'Exporter PLY (Binaire)',
345346
'menubar/file/export/stl': 'Exporter STL',
346347
'menubar/file/export/stl_binary': 'Exporter STL (Binaire)',
348+
'menubar/file/export/usdz': 'Exporter USDZ',
347349
'menubar/file/publish': 'Publier',
348350

349351
'menubar/edit': 'Edition',
@@ -667,6 +669,7 @@ function Strings( config ) {
667669
'menubar/file/export/ply_binary': '导出PLY(二进制)',
668670
'menubar/file/export/stl': '导出STL',
669671
'menubar/file/export/stl_binary': '导出STL(二进制)',
672+
'menubar/file/export/usdz': '导出USDZ',
670673
'menubar/file/publish': '发布',
671674

672675
'menubar/edit': '编辑',

editor/sw.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const assets = [
1414

1515
'../examples/jsm/libs/chevrotain.module.min.js',
1616
'../examples/jsm/libs/fflate.module.min.js',
17+
'../examples/jsm/libs/fflate.module.js', // TODO: Optimize
1718

1819
'../examples/js/libs/draco/draco_decoder.js',
1920
'../examples/js/libs/draco/draco_decoder.wasm',
@@ -58,6 +59,7 @@ const assets = [
5859
'../examples/jsm/exporters/OBJExporter.js',
5960
'../examples/jsm/exporters/PLYExporter.js',
6061
'../examples/jsm/exporters/STLExporter.js',
62+
'../examples/jsm/exporters/USDZExporter.js',
6163

6264
'../examples/jsm/helpers/VertexNormalsHelper.js',
6365

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
import { zipSync, strToU8 } from '../libs/fflate.module.js';
2+
3+
class USDZExporter {
4+
5+
parse( scene ) {
6+
7+
let output = buildHeader();
8+
9+
const materials = {};
10+
11+
scene.traverse( ( object ) => {
12+
13+
if ( object.isMesh ) {
14+
15+
materials[ object.material.uuid ] = object.material;
16+
output += buildXform( object, buildMesh( object.geometry, object.material ) );
17+
18+
}
19+
20+
} );
21+
22+
output += buildMaterials( materials );
23+
24+
return zipSync( { 'model.usda': strToU8( output ) }, { level: 0 } );
25+
26+
}
27+
28+
}
29+
30+
//
31+
32+
function buildHeader() {
33+
34+
return `#usda 1.0
35+
(
36+
doc = "Three.js"
37+
metersPerUnit = 1
38+
upAxis = "Y"
39+
)
40+
41+
`;
42+
43+
}
44+
45+
// Xform
46+
47+
function buildXform( object, define ) {
48+
49+
const name = object.name || 'Xform1';
50+
const transform = buildMatrix( object.matrixWorld );
51+
52+
return `def Xform "${ name }"
53+
{
54+
matrix4d xformOp:transform = ${ transform }
55+
uniform token[] xformOpOrder = ["xformOp:transform"]
56+
57+
${ define }
58+
}
59+
60+
`;
61+
62+
}
63+
64+
function buildMatrix( matrix ) {
65+
66+
const array = matrix.elements;
67+
68+
return `( ${ buildMatrixRow( array, 0 ) }, ${ buildMatrixRow( array, 4 ) }, ${ buildMatrixRow( array, 8 ) }, ${ buildMatrixRow( array, 12 ) } )`;
69+
70+
}
71+
72+
function buildMatrixRow( array, offset ) {
73+
74+
return `(${ array[ offset + 0 ] }, ${ array[ offset + 1 ] }, ${ array[ offset + 2 ] }, ${ array[ offset + 3 ] })`;
75+
76+
}
77+
78+
// Mesh
79+
80+
function buildMesh( geometry, material ) {
81+
82+
const name = geometry.name || 'Mesh1';
83+
const attributes = geometry.attributes;
84+
const count = attributes.position.count;
85+
86+
return `def Mesh "${ name }"
87+
{
88+
int[] faceVertexCounts = [${ buildMeshVertexCount( geometry ) }]
89+
int[] faceVertexIndices = [${ buildMeshVertexIndices( geometry ) }]
90+
rel material:binding = </_materials/Material_${ material.id }>
91+
normal3f[] normals = [${ buildVector3Array( attributes.normal, count )}] (
92+
interpolation = "faceVarying"
93+
)
94+
point3f[] points = [${ buildVector3Array( attributes.position, count )}]
95+
texCoord2f[] primvars:UVMap = [${ buildVector2Array( attributes.uv, count )}] (
96+
interpolation = "faceVarying"
97+
)
98+
uniform token subdivisionScheme = "none"
99+
}
100+
`;
101+
102+
}
103+
104+
function buildMeshVertexCount( geometry ) {
105+
106+
const count = geometry.index !== null ? geometry.index.array.length : geometry.attributes.position.count;
107+
108+
return Array( count / 3 ).fill( 3 ).join( ', ' );
109+
110+
}
111+
112+
function buildMeshVertexIndices( geometry ) {
113+
114+
if ( geometry.index !== null ) {
115+
116+
return geometry.index.array.join( ', ' );
117+
118+
}
119+
120+
const array = [];
121+
const length = geometry.attributes.position.count;
122+
123+
for ( let i = 0; i < length; i ++ ) {
124+
125+
array.push( i );
126+
127+
}
128+
129+
return array.join( ', ' );
130+
131+
}
132+
133+
function buildVector3Array( attribute, count ) {
134+
135+
if ( attribute === undefined ) {
136+
137+
console.warn( 'USDZExporter: Normals missing.' );
138+
return Array( count ).fill( '(0, 0, 0)' ).join( ', ' );
139+
140+
}
141+
142+
const array = [];
143+
const data = attribute.array;
144+
145+
for ( let i = 0; i < data.length; i += 3 ) {
146+
147+
array.push( `(${ data[ i + 0 ] }, ${ data[ i + 1 ] }, ${ data[ i + 2 ] })` );
148+
149+
}
150+
151+
return array.join( ', ' );
152+
153+
}
154+
155+
function buildVector2Array( attribute, count ) {
156+
157+
if ( attribute === undefined ) {
158+
159+
console.warn( 'USDZExporter: UVs missing.' );
160+
return Array( count ).fill( '(0, 0)' ).join( ', ' );
161+
162+
}
163+
164+
const array = [];
165+
const data = attribute.array;
166+
167+
for ( let i = 0; i < data.length; i += 2 ) {
168+
169+
array.push( `(${ data[ i + 0 ] }, ${ data[ i + 1 ] })` );
170+
171+
}
172+
173+
return array.join( ', ' );
174+
175+
}
176+
177+
// Materials
178+
179+
function buildMaterials( materials ) {
180+
181+
const array = [];
182+
183+
for ( const uuid in materials ) {
184+
185+
const material = materials[ uuid ];
186+
187+
array.push( buildMaterial( material ) );
188+
189+
}
190+
191+
return `def "_materials"
192+
{
193+
${ array.join( '' ) }
194+
}
195+
196+
`;
197+
198+
}
199+
200+
function buildMaterial( material ) {
201+
202+
return `
203+
def Material "Material_${ material.id }"
204+
{
205+
token outputs:surface.connect = </_materials/Material_${ material.id }/previewShader.outputs:surface>
206+
207+
def Shader "previewShader"
208+
{
209+
uniform token info:id = "UsdPreviewSurface"
210+
color3f inputs:diffuseColor = ${ buildColor( material.color ) }
211+
float inputs:metallic = ${ material.metalness }
212+
float inputs:roughness = ${ material.roughness }
213+
token outputs:surface
214+
}
215+
}
216+
`;
217+
218+
}
219+
220+
function buildColor( color ) {
221+
222+
return `(${ color.r }, ${ color.g }, ${ color.b })`;
223+
224+
}
225+
226+
export { USDZExporter };

0 commit comments

Comments
 (0)