1+ #if ENABLE_SIMPLYGON
2+ using Simplygon ;
3+ using Simplygon . Unity . EditorPlugin ;
4+ using System ;
5+ using System . Collections . Generic ;
6+ using System . IO ;
7+ using UnityEditor ;
8+ using UnityEngine ;
9+ using Unity . Formats . USD ;
10+ using USD . NET ;
11+ using USD . NET . Unity ;
12+
13+ namespace Unity . AutoLOD
14+ {
15+ public struct SimplygonMeshSimplifier : IMeshSimplifier
16+ {
17+ static pxr . TfToken s_MaterialBindToken = new pxr . TfToken ( "materialBind" ) ;
18+ static pxr . TfToken s_SubMeshesToken = new pxr . TfToken ( "subMeshes" ) ;
19+
20+ static object s_ExecutionLock = new object ( ) ;
21+ static ISimplygon s_Simplygon ;
22+ static int s_ReferenceCount ;
23+
24+ public void Simplify ( WorkingMesh inputMesh , WorkingMesh outputMesh , float quality )
25+ {
26+ // We can only have one instance of Simplygon, but we can use it from multiple threads once initialized
27+ lock ( s_ExecutionLock )
28+ {
29+ if ( s_Simplygon == null )
30+ {
31+ s_Simplygon = Loader . InitSimplygon ( out var simplygonErrorCode , out var simplygonErrorMessage ) ;
32+ if ( s_Simplygon == null && simplygonErrorCode != EErrorCodes . NoError )
33+ Debug . Log ( $ "Initializing failed! { simplygonErrorCode } : { simplygonErrorMessage } ") ;
34+ }
35+
36+ s_ReferenceCount ++ ;
37+ }
38+
39+ var simplygon = s_Simplygon ;
40+ if ( simplygon != null )
41+ {
42+ string exportTempDirectory = SimplygonUtils . GetNewTempDirectory ( ) ;
43+
44+ using ( spScene sgScene = ExportSimplygonScene ( simplygon , exportTempDirectory , inputMesh ) )
45+ {
46+ using ( spReductionPipeline reductionPipeline = simplygon . CreateReductionPipeline ( ) )
47+ using ( spReductionSettings reductionSettings = reductionPipeline . GetReductionSettings ( ) )
48+ {
49+ reductionSettings . SetReductionTargets ( EStopCondition . All , true , false , false , false ) ;
50+ reductionSettings . SetReductionTargetTriangleRatio ( quality ) ;
51+
52+ reductionPipeline . RunScene ( sgScene , EPipelineRunMode . RunInThisProcess ) ;
53+
54+ MonoBehaviourHelper . ExecuteOnMainThread ( ( ) =>
55+ {
56+ string baseFolder = "Assets/SimplygonTemp" ;
57+ if ( ! AssetDatabase . IsValidFolder ( baseFolder ) )
58+ AssetDatabase . CreateFolder ( "Assets" , "SimplygonTemp" ) ;
59+
60+ string meshName = inputMesh . name ;
61+ string assetFolderGuid = AssetDatabase . CreateFolder ( baseFolder , meshName ) ;
62+ string assetFolderPath = AssetDatabase . GUIDToAssetPath ( assetFolderGuid ) ;
63+
64+ int startingLodIndex = 0 ;
65+ List < GameObject > importedGameObjects = new List < GameObject > ( ) ;
66+ SimplygonImporter . Import ( simplygon , reductionPipeline , ref startingLodIndex ,
67+ assetFolderPath , meshName , importedGameObjects ) ;
68+
69+ Debug . Assert ( importedGameObjects . Count == 1 , "AutoLOD: There should only be one imported mesh." ) ;
70+ if ( importedGameObjects . Count == 1 )
71+ {
72+ GameObject go = importedGameObjects [ 0 ] ;
73+ MeshFilter mf = go . GetComponentInChildren < MeshFilter > ( ) ;
74+ mf . sharedMesh . ApplyToWorkingMesh ( ref outputMesh ) ;
75+ }
76+
77+ foreach ( var go in importedGameObjects )
78+ {
79+ GameObject . DestroyImmediate ( go ) ;
80+ }
81+
82+ AssetDatabase . DeleteAsset ( baseFolder ) ;
83+ } ) ;
84+ }
85+ }
86+ }
87+
88+ lock ( s_ExecutionLock )
89+ {
90+ s_ReferenceCount -- ;
91+
92+ // Clean up on our way out if we are the last thread using the Simplygon singleton
93+ if ( s_Simplygon != null && s_ReferenceCount == 0 )
94+ {
95+ s_Simplygon . Dispose ( ) ;
96+ s_Simplygon = null ;
97+ }
98+ }
99+ }
100+
101+ static spScene ExportSimplygonScene ( ISimplygon simplygon , string tempDirectory , WorkingMesh mesh )
102+ {
103+ if ( string . IsNullOrEmpty ( tempDirectory ) )
104+ return ( spScene ) null ;
105+
106+ string filePath = Path . Combine ( tempDirectory , "export.usd" ) ;
107+ InitUsd . Initialize ( ) ;
108+ Scene scene = Scene . Create ( filePath ) ;
109+
110+ var context = new ExportContext ( ) ;
111+ context . scene = scene ;
112+ context . basisTransform = BasisTransformation . SlowAndSafe ;
113+ // context.exportRoot = root.transform.parent;
114+
115+ ExportMesh ( context , mesh ) ;
116+
117+ scene . Save ( ) ;
118+ scene . Close ( ) ;
119+
120+ using ( spSceneImporter sceneImporter = simplygon . CreateSceneImporter ( ) )
121+ {
122+ sceneImporter . SetImportFilePath ( filePath ) ;
123+ sceneImporter . RunImport ( ) ;
124+ // SimplygonExporter.ExportSelectionSetsInSelection(simplygon, sceneImporter.GetScene(), selectedGameObjects, rootName);
125+ return sceneImporter . GetScene ( ) ;
126+ }
127+ }
128+
129+ static void ExportMesh ( ExportContext exportContext , WorkingMesh mesh )
130+ {
131+ // path = /build_bighouse_02/build_bighouse_01_dragonhead_01_LOD0
132+ var path = new pxr . SdfPath ( $ "/{ mesh . name } ") ;
133+
134+ var scene = exportContext . scene ;
135+ bool slowAndSafeConversion = exportContext . basisTransform == BasisTransformation . SlowAndSafe ;
136+ var sample = new MeshSample ( ) ;
137+
138+ if ( mesh . bounds . center == Vector3 . zero && mesh . bounds . extents == Vector3 . zero )
139+ {
140+ mesh . RecalculateBounds ( ) ;
141+ }
142+
143+ sample . extent = mesh . bounds ;
144+
145+ if ( slowAndSafeConversion )
146+ {
147+ // Unity uses a forward vector that matches DirectX, but USD matches OpenGL, so a change of
148+ // basis is required. There are shortcuts, but this is fully general.
149+ sample . ConvertTransform ( ) ;
150+ sample . extent . center = UnityTypeConverter . ChangeBasis ( sample . extent . center ) ;
151+ }
152+
153+ // TODO: Technically a mesh could be the root transform, which is not handled correctly here.
154+ // It should have the same logic for root prims as in ExportXform.
155+ // sample.transform = XformExporter.GetLocalTransformMatrix(
156+ // null,
157+ // scene.UpAxis == Scene.UpAxes.Z,
158+ // new pxr.SdfPath(path).IsRootPrimPath(),
159+ // exportContext.basisTransform);
160+
161+ sample . normals = mesh . normals ;
162+ sample . points = mesh . vertices ;
163+ sample . tangents = mesh . tangents ;
164+
165+ sample . colors = mesh . colors ;
166+ if ( sample . colors != null && sample . colors . Length == 0 )
167+ {
168+ sample . colors = null ;
169+ }
170+
171+ // Gah. There is no way to inspect a meshes UVs.
172+ sample . st = mesh . uv ;
173+
174+ // Set face vertex counts and indices.
175+ var tris = mesh . triangles ;
176+
177+ if ( slowAndSafeConversion )
178+ {
179+ // Unity uses a forward vector that matches DirectX, but USD matches OpenGL, so a change
180+ // of basis is required. There are shortcuts, but this is fully general.
181+
182+ for ( int i = 0 ; i < sample . points . Length ; i ++ )
183+ {
184+ sample . points [ i ] = UnityTypeConverter . ChangeBasis ( sample . points [ i ] ) ;
185+ if ( sample . normals != null && sample . normals . Length == sample . points . Length )
186+ {
187+ sample . normals [ i ] = UnityTypeConverter . ChangeBasis ( sample . normals [ i ] ) ;
188+ }
189+
190+ if ( sample . tangents != null && sample . tangents . Length == sample . points . Length )
191+ {
192+ var w = sample . tangents [ i ] . w ;
193+ var t = UnityTypeConverter . ChangeBasis ( sample . tangents [ i ] ) ;
194+ sample . tangents [ i ] = new Vector4 ( t . x , t . y , t . z , w ) ;
195+ }
196+ }
197+
198+ for ( int i = 0 ; i < tris . Length ; i += 3 )
199+ {
200+ var t = tris [ i ] ;
201+ tris [ i ] = tris [ i + 1 ] ;
202+ tris [ i + 1 ] = t ;
203+ }
204+
205+ sample . SetTriangles ( tris ) ;
206+
207+ scene . Write ( path , sample ) ;
208+
209+ // TODO: this is a bit of a half-measure, we need real support for primvar interpolation.
210+ // Set interpolation based on color count.
211+ if ( sample . colors != null && sample . colors . Length == 1 )
212+ {
213+ pxr . UsdPrim usdPrim = scene . GetPrimAtPath ( path ) ;
214+ var colorPrimvar =
215+ new pxr . UsdGeomPrimvar ( usdPrim . GetAttribute ( pxr . UsdGeomTokens . primvarsDisplayColor ) ) ;
216+ colorPrimvar . SetInterpolation ( pxr . UsdGeomTokens . constant ) ;
217+ var opacityPrimvar =
218+ new pxr . UsdGeomPrimvar ( usdPrim . GetAttribute ( pxr . UsdGeomTokens . primvarsDisplayOpacity ) ) ;
219+ opacityPrimvar . SetInterpolation ( pxr . UsdGeomTokens . constant ) ;
220+ }
221+
222+ // In USD subMeshes are represented as UsdGeomSubsets.
223+ // When there are multiple subMeshes, convert them into UsdGeomSubsets.
224+ if ( mesh . subMeshCount > 1 )
225+ {
226+ // Build a table of face indices, used to convert the subMesh triangles to face indices.
227+ var faceTable = new Dictionary < Vector3 , int > ( ) ;
228+ for ( int i = 0 ; i < tris . Length ; i += 3 )
229+ {
230+ if ( ! slowAndSafeConversion )
231+ {
232+ faceTable . Add ( new Vector3 ( tris [ i ] , tris [ i + 1 ] , tris [ i + 2 ] ) , i / 3 ) ;
233+ }
234+ else
235+ {
236+ // Under slow and safe export, index 0 and 1 are swapped.
237+ // This swap will not be present in the subMesh indices, so must be undone here.
238+ faceTable . Add ( new Vector3 ( tris [ i + 1 ] , tris [ i ] , tris [ i + 2 ] ) , i / 3 ) ;
239+ }
240+ }
241+
242+ var usdPrim = scene . GetPrimAtPath ( path ) ;
243+ var usdGeomMesh = new pxr . UsdGeomMesh ( usdPrim ) ;
244+
245+ // Process each subMesh and create a UsdGeomSubset of faces this subMesh targets.
246+ for ( int si = 0 ; si < mesh . subMeshCount ; si ++ )
247+ {
248+ int [ ] indices = mesh . GetTriangles ( si ) ;
249+ int [ ] faceIndices = new int [ indices . Length / 3 ] ;
250+
251+ for ( int i = 0 ; i < indices . Length ; i += 3 )
252+ {
253+ faceIndices [ i / 3 ] = faceTable [ new Vector3 ( indices [ i ] , indices [ i + 1 ] , indices [ i + 2 ] ) ] ;
254+ }
255+
256+ var vtIndices = UnityTypeConverter . ToVtArray ( faceIndices ) ;
257+ var subset = pxr . UsdGeomSubset . CreateUniqueGeomSubset (
258+ usdGeomMesh , // The object of which this subset belongs.
259+ s_SubMeshesToken , // An arbitrary name for the subset.
260+ pxr . UsdGeomTokens . face , // Indicator that these represent face indices
261+ vtIndices , // The actual face indices.
262+ s_MaterialBindToken // familyName = "materialBind"
263+ ) ;
264+ }
265+ }
266+ }
267+ }
268+ }
269+ }
270+ #endif
0 commit comments