Tanoda
FBXUnityMeshGetter.cs
Go to the documentation of this file.
1// ===============================================================================================
2// The MIT License (MIT) for UnityFBXExporter
3//
4// UnityFBXExporter was created for Building Crafter (http://u3d.as/ovC) a tool to rapidly
5// create high quality buildings right in Unity with no need to use 3D modeling programs.
6//
7// Copyright (c) 2016 | 8Bit Goose Games, Inc.
8//
9// Permission is hereby granted, free of charge, to any person obtaining a copy
10// of this software and associated documentation files (the "Software"), to deal
11// in the Software without restriction, including without limitation the rights
12// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
13// of the Software, and to permit persons to whom the Software is furnished to do so,
14// subject to the following conditions:
15//
16// The above copyright notice and this permission notice shall be included in all
17// copies or substantial portions of the Software.
18//
19// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
20// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
21// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
24// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25//
26// ===============================================================================================
27
28using UnityEngine;
29using System.Collections;
30using System.Text;
31using System.Collections.Generic;
33
34namespace UnityFBXExporter
35{
36 public class FBXUnityMeshGetter
37 {
38
49 public static long GetMeshToString(GameObject gameObj,
50 Material[] materials,
51 ref StringBuilder objects,
52 ref StringBuilder connections,
53 GameObject parentObject = null,
54 long parentModelId = 0)
55 {
56 StringBuilder tempObjectSb = new StringBuilder();
57 StringBuilder tempConnectionsSb = new StringBuilder();
58
59 long geometryId = FBXExporter.GetRandomFBXId();
60 long modelId = FBXExporter.GetRandomFBXId();
61
62 // Sees if there is a mesh to export and add to the system
63 MeshFilter filter = gameObj.GetComponent<MeshFilter>();
64 SkinnedMeshRenderer skinnedMesh = gameObj.GetComponent<SkinnedMeshRenderer>();
65
66 // The mesh to export is this level's mesh that is going to be exported
67 Mesh meshToExport = new Mesh();
68 if(filter != null)
69 meshToExport = filter.sharedMesh;
70 else if(skinnedMesh != null) // If this object has a skinned mesh on it, bake that mesh into whatever pose it is at and add it as a new mesh to export
71 {
72 meshToExport = new Mesh();
73 skinnedMesh.BakeMesh(meshToExport);
74 }
75
76 if(meshToExport == null)
77 Debug.LogError("Couldn't find a mesh to export");
78
79 string meshName = gameObj.name;
80
81 // A NULL parent means that the gameObject is at the top
82 string isMesh = "Null";
83
84 if(meshToExport != null)
85 {
86 meshName = meshToExport.name;
87 isMesh = "Mesh";
88 }
89
90 if(filter != null)
91 {
92 if(filter.sharedMesh == null)
93 {
94 // The MeshFilter has no mesh assigned, so treat it like an FBX Null node.
95 filter = null;
96 }
97 else
98 {
99 meshName = filter.sharedMesh.name;
100 isMesh = "Mesh";
101 }
102 }
103
104 // If we've got a skinned mesh without a name, give it a random name
105 if(meshName == "" && skinnedMesh != null)
106 meshName = "Skinned Mesh " + Random.Range(0, 1000000);
107
108 if(parentModelId == 0)
109 tempConnectionsSb.AppendLine("\t;Model::" + meshName + ", Model::RootNode");
110 else
111 tempConnectionsSb.AppendLine("\t;Model::" + meshName + ", Model::USING PARENT");
112 tempConnectionsSb.AppendLine("\tC: \"OO\"," + modelId + "," + parentModelId);
113 tempConnectionsSb.AppendLine();
114 tempObjectSb.AppendLine("\tModel: " + modelId + ", \"Model::" + gameObj.name + "\", \"" + isMesh + "\" {");
115 tempObjectSb.AppendLine("\t\tVersion: 232");
116 tempObjectSb.AppendLine("\t\tProperties70: {");
117 tempObjectSb.AppendLine("\t\t\tP: \"RotationOrder\", \"enum\", \"\", \"\",4");
118 tempObjectSb.AppendLine("\t\t\tP: \"RotationActive\", \"bool\", \"\", \"\",1");
119 tempObjectSb.AppendLine("\t\t\tP: \"InheritType\", \"enum\", \"\", \"\",1");
120 tempObjectSb.AppendLine("\t\t\tP: \"ScalingMax\", \"Vector3D\", \"Vector\", \"\",0,0,0");
121 tempObjectSb.AppendLine("\t\t\tP: \"DefaultAttributeIndex\", \"int\", \"Integer\", \"\",0");
122 // ===== Local Translation Offset =========
123 Vector3 position = gameObj.transform.localPosition;
124
125 tempObjectSb.Append("\t\t\tP: \"Lcl Translation\", \"Lcl Translation\", \"\", \"A+\",");
126
127 // Append the X Y Z coords to the system
128 tempObjectSb.AppendFormat("{0},{1},{2}", FE.FBXFormat(position.x * -1), FE.FBXFormat(position.y), FE.FBXFormat(position.z));
129 tempObjectSb.AppendLine();
130
131 // Rotates the object correctly from Unity space
132 Vector3 localRotation = gameObj.transform.localEulerAngles;
133 tempObjectSb.AppendFormat("\t\t\tP: \"Lcl Rotation\", \"Lcl Rotation\", \"\", \"A+\",{0},{1},{2}", FE.FBXFormat(localRotation.x), FE.FBXFormat(localRotation.y * -1), FE.FBXFormat(-1 * localRotation.z));
134 tempObjectSb.AppendLine();
135
136 // Adds the local scale of this object
137 Vector3 localScale = gameObj.transform.localScale;
138 tempObjectSb.AppendFormat("\t\t\tP: \"Lcl Scaling\", \"Lcl Scaling\", \"\", \"A\",{0},{1},{2}", FE.FBXFormat(localScale.x), FE.FBXFormat(localScale.y), FE.FBXFormat(localScale.z));
139 tempObjectSb.AppendLine();
140
141 tempObjectSb.AppendLine("\t\t\tP: \"currentUVSet\", \"KString\", \"\", \"U\", \"map1\"");
142 tempObjectSb.AppendLine("\t\t}");
143 tempObjectSb.AppendLine("\t\tShading: T");
144 tempObjectSb.AppendLine("\t\tCulling: \"CullingOff\"");
145 tempObjectSb.AppendLine("\t}");
146
147 // Adds in geometry if it exists, if it it does not exist, this is a empty gameObject file and skips over this
148 if(meshToExport != null)
149 {
150 Mesh mesh = meshToExport;
151
152 // =================================
153 // General Geometry Info
154 // =================================
155 // Generate the geometry information for the mesh created
156
157 tempObjectSb.AppendLine("\tGeometry: " + geometryId + ", \"Geometry::\", \"Mesh\" {");
158
159 // ===== WRITE THE VERTICIES =====
160 Vector3[] verticies = mesh.vertices;
161 int vertCount = mesh.vertexCount * 3; // <= because the list of points is just a list of comma seperated values, we need to multiply by three
162
163 tempObjectSb.AppendLine("\t\tVertices: *" + vertCount + " {");
164 tempObjectSb.Append("\t\t\ta: ");
165 for(int i = 0; i < verticies.Length; i++)
166 {
167 if(i > 0)
168 tempObjectSb.Append(",");
169
170 // Points in the verticies. We also reverse the x value because Unity has a reverse X coordinate
171 tempObjectSb.AppendFormat("{0},{1},{2}", FE.FBXFormat(verticies[i].x * -1), FE.FBXFormat(verticies[i].y), FE.FBXFormat(verticies[i].z));
172 }
173
174 tempObjectSb.AppendLine();
175 tempObjectSb.AppendLine("\t\t} ");
176
177 // ======= WRITE THE TRIANGLES ========
178 int triangleCount = mesh.triangles.Length;
179 int[] triangles = mesh.triangles;
180
181 tempObjectSb.AppendLine("\t\tPolygonVertexIndex: *" + triangleCount + " {");
182
183 // Write triangle indexes
184 tempObjectSb.Append("\t\t\ta: ");
185 for(int i = 0; i < triangleCount; i += 3)
186 {
187 if(i > 0)
188 tempObjectSb.Append(",");
189
190 // To get the correct normals, must rewind the triangles since we flipped the x direction
191 tempObjectSb.AppendFormat("{0},{1},{2}",
192 triangles[i],
193 triangles[i + 2],
194 (triangles[i + 1] * -1) - 1); // <= Tells the poly is ended
195
196 }
197
198 tempObjectSb.AppendLine();
199
200 tempObjectSb.AppendLine("\t\t} ");
201 tempObjectSb.AppendLine("\t\tGeometryVersion: 124");
202 tempObjectSb.AppendLine("\t\tLayerElementNormal: 0 {");
203 tempObjectSb.AppendLine("\t\t\tVersion: 101");
204 tempObjectSb.AppendLine("\t\t\tName: \"\"");
205 tempObjectSb.AppendLine("\t\t\tMappingInformationType: \"ByPolygonVertex\"");
206 tempObjectSb.AppendLine("\t\t\tReferenceInformationType: \"Direct\"");
207
208 // ===== WRITE THE NORMALS ==========
209 Vector3[] normals = mesh.normals;
210
211 tempObjectSb.AppendLine("\t\t\tNormals: *" + (triangleCount * 3) + " {");
212 tempObjectSb.Append("\t\t\t\ta: ");
213
214 for(int i = 0; i < triangleCount; i += 3)
215 {
216 if(i > 0)
217 tempObjectSb.Append(",");
218
219 // To get the correct normals, must rewind the normal triangles like the triangles above since x was flipped
220 Vector3 newNormal = normals[triangles[i]];
221
222 tempObjectSb.AppendFormat("{0},{1},{2},",
223 FE.FBXFormat(newNormal.x * -1), // Switch normal as is tradition
224 FE.FBXFormat(newNormal.y),
225 FE.FBXFormat(newNormal.z));
226
227 newNormal = normals[triangles[i + 2]];
228
229 tempObjectSb.AppendFormat("{0},{1},{2},",
230 FE.FBXFormat(newNormal.x * -1), // Switch normal as is tradition
231 FE.FBXFormat(newNormal.y),
232 FE.FBXFormat(newNormal.z));
233
234 newNormal = normals[triangles[i + 1]];
235
236 tempObjectSb.AppendFormat("{0},{1},{2}",
237 FE.FBXFormat(newNormal.x * -1), // Switch normal as is tradition
238 FE.FBXFormat(newNormal.y),
239 FE.FBXFormat(newNormal.z));
240 }
241
242 tempObjectSb.AppendLine();
243 tempObjectSb.AppendLine("\t\t\t}");
244 tempObjectSb.AppendLine("\t\t}");
245
246 // ===== WRITE THE COLORS =====
247 bool containsColors = mesh.colors.Length == verticies.Length;
248
249 if(containsColors)
250 {
251 Color[] colors = mesh.colors;
252
253 Dictionary<Color, int> colorTable = new Dictionary<Color, int>(); // reducing amount of data by only keeping unique colors.
254 int idx = 0;
255
256 // build index table of all the different colors present in the mesh
257 for (int i = 0; i < colors.Length; i++)
258 {
259 if (!colorTable.ContainsKey(colors[i]))
260 {
261 colorTable[colors[i]] = idx;
262 idx++;
263 }
264 }
265
266 tempObjectSb.AppendLine("\t\tLayerElementColor: 0 {");
267 tempObjectSb.AppendLine("\t\t\tVersion: 101");
268 tempObjectSb.AppendLine("\t\t\tName: \"Col\"");
269 tempObjectSb.AppendLine("\t\t\tMappingInformationType: \"ByPolygonVertex\"");
270 tempObjectSb.AppendLine("\t\t\tReferenceInformationType: \"IndexToDirect\"");
271 tempObjectSb.AppendLine("\t\t\tColors: *" + colorTable.Count * 4 + " {");
272 tempObjectSb.Append("\t\t\t\ta: ");
273
274 bool first = true;
275 foreach (KeyValuePair<Color, int> color in colorTable)
276 {
277 if (!first)
278 tempObjectSb.Append(",");
279
280 tempObjectSb.AppendFormat("{0},{1},{2},{3}", FE.FBXFormat(color.Key.r), FE.FBXFormat(color.Key.g), FE.FBXFormat(color.Key.b), FE.FBXFormat(color.Key.a));
281 first = false;
282 }
283 tempObjectSb.AppendLine();
284
285 tempObjectSb.AppendLine("\t\t\t\t}");
286
287 // Color index
288 tempObjectSb.AppendLine("\t\t\tColorIndex: *" + triangles.Length + " {");
289 tempObjectSb.Append("\t\t\t\ta: ");
290
291 for (int i = 0; i < triangles.Length; i += 3)
292 {
293 if (i > 0)
294 tempObjectSb.Append(",");
295
296 // Triangles need to be fliped for the x flip
297 int index1 = triangles[i];
298 int index2 = triangles[i + 2];
299 int index3 = triangles[i + 1];
300
301 // Find the color index related to that vertice index
302 index1 = colorTable[colors[index1]];
303 index2 = colorTable[colors[index2]];
304 index3 = colorTable[colors[index3]];
305
306 tempObjectSb.AppendFormat("{0},{1},{2}", index1, index2, index3);
307 }
308
309 tempObjectSb.AppendLine();
310
311 tempObjectSb.AppendLine("\t\t\t}");
312 tempObjectSb.AppendLine("\t\t}");
313 }
314 else
315 Debug.LogWarning("Mesh contains " + mesh.vertices.Length + " vertices for " + mesh.colors.Length + " colors. Skip color export");
316
317
318
319 // ================ UV CREATION =========================
320
321 // -- UV 1 Creation
322 //int uvLength = mesh.uv.Length;
323 //Vector2[] uvs = mesh.uv;
324 //
325 //tempObjectSb.AppendLine("\t\tLayerElementUV: 0 {"); // the Zero here is for the first UV map
326 //tempObjectSb.AppendLine("\t\t\tVersion: 101");
327 //tempObjectSb.AppendLine("\t\t\tName: \"map1\"");
328 //tempObjectSb.AppendLine("\t\t\tMappingInformationType: \"ByPolygonVertex\"");
329 //tempObjectSb.AppendLine("\t\t\tReferenceInformationType: \"IndexToDirect\"");
330 //tempObjectSb.AppendLine("\t\t\tUV: *" + uvLength * 2 + " {");
331 //tempObjectSb.Append("\t\t\t\ta: ");
332 //
333 //for(int i = 0; i < uvLength; i++)
334 //{
335 // if(i > 0)
336 // tempObjectSb.Append(",");
337 //
338 // tempObjectSb.AppendFormat("{0},{1}", FE.FBXFormat(uvs[i].x), FE.FBXFormat(uvs[i].y));
339 //}
340 //tempObjectSb.AppendLine();
341 //
342 //tempObjectSb.AppendLine("\t\t\t\t}");
343 //
345 //tempObjectSb.AppendLine("\t\t\tUVIndex: *" + triangleCount +" {");
346 //tempObjectSb.Append("\t\t\t\ta: ");
347 //
348 //for(int i = 0; i < triangleCount; i += 3)
349 //{
350 // if(i > 0)
351 // tempObjectSb.Append(",");
352 //
353 // // Triangles need to be fliped for the x flip
354 // int index1 = triangles[i];
355 // int index2 = triangles[i+2];
356 // int index3 = triangles[i+1];
357 //
358 // tempObjectSb.AppendFormat("{0},{1},{2}", index1, index2, index3);
359 //}
360 //
361 //tempObjectSb.AppendLine();
362 //
363 //tempObjectSb.AppendLine("\t\t\t}");
364 //tempObjectSb.AppendLine("\t\t}");
365
366 // -- UV 2 Creation
367 // TODO: Add UV2 Creation here
368
369 // -- Smoothing
370 // TODO: Smoothing doesn't seem to do anything when importing. This maybe should be added. -KBH
371
372 // ============ MATERIALS =============
373
374 tempObjectSb.AppendLine("\t\tLayerElementMaterial: 0 {");
375 tempObjectSb.AppendLine("\t\t\tVersion: 101");
376 tempObjectSb.AppendLine("\t\t\tName: \"\"");
377 tempObjectSb.AppendLine("\t\t\tMappingInformationType: \"ByPolygon\"");
378 tempObjectSb.AppendLine("\t\t\tReferenceInformationType: \"IndexToDirect\"");
379
380 int totalFaceCount = 0;
381
382 // So by polygon means that we need 1/3rd of how many indicies we wrote.
383 int numberOfSubmeshes = mesh.subMeshCount;
384
385 StringBuilder submeshesSb = new StringBuilder();
386
387 // For just one submesh, we set them all to zero
388 if(numberOfSubmeshes == 1)
389 {
390 int numFaces = triangles.Length / 3;
391
392 for(int i = 0; i < numFaces; i++)
393 {
394 submeshesSb.Append("0,");
395 totalFaceCount++;
396 }
397 }
398 else
399 {
400 List<int[]> allSubmeshes = new List<int[]>();
401
402 // Load all submeshes into a space
403 for(int i = 0; i < numberOfSubmeshes; i++)
404 allSubmeshes.Add(mesh.GetIndices(i));
405
406 // TODO: Optimize this search pattern
407 for(int i = 0; i < triangles.Length; i += 3)
408 {
409 for(int subMeshIndex = 0; subMeshIndex < allSubmeshes.Count; subMeshIndex++)
410 {
411 bool breaker = false;
412
413 for(int n = 0; n < allSubmeshes[subMeshIndex].Length; n += 3)
414 {
415 if(triangles[i] == allSubmeshes[subMeshIndex][n]
416 && triangles[i + 1] == allSubmeshes[subMeshIndex][n + 1]
417 && triangles[i + 2] == allSubmeshes[subMeshIndex][n + 2])
418 {
419 submeshesSb.Append(subMeshIndex.ToString());
420 submeshesSb.Append(",");
421 totalFaceCount++;
422 break;
423 }
424
425 if(breaker)
426 break;
427 }
428 }
429 }
430 }
431
432 tempObjectSb.AppendLine("\t\t\tMaterials: *" + totalFaceCount + " {");
433 tempObjectSb.Append("\t\t\t\ta: ");
434 tempObjectSb.AppendLine(submeshesSb.ToString());
435 tempObjectSb.AppendLine("\t\t\t} ");
436 tempObjectSb.AppendLine("\t\t}");
437
438 // ============= INFORMS WHAT TYPE OF LATER ELEMENTS ARE IN THIS GEOMETRY =================
439 tempObjectSb.AppendLine("\t\tLayer: 0 {");
440 tempObjectSb.AppendLine("\t\t\tVersion: 100");
441 tempObjectSb.AppendLine("\t\t\tLayerElement: {");
442 tempObjectSb.AppendLine("\t\t\t\tType: \"LayerElementNormal\"");
443 tempObjectSb.AppendLine("\t\t\t\tTypedIndex: 0");
444 tempObjectSb.AppendLine("\t\t\t}");
445 tempObjectSb.AppendLine("\t\t\tLayerElement: {");
446 tempObjectSb.AppendLine("\t\t\t\tType: \"LayerElementMaterial\"");
447 tempObjectSb.AppendLine("\t\t\t\tTypedIndex: 0");
448 tempObjectSb.AppendLine("\t\t\t}");
449 tempObjectSb.AppendLine("\t\t\tLayerElement: {");
450 tempObjectSb.AppendLine("\t\t\t\tType: \"LayerElementTexture\"");
451 tempObjectSb.AppendLine("\t\t\t\tTypedIndex: 0");
452 tempObjectSb.AppendLine("\t\t\t}");
453 if(containsColors)
454 {
455 tempObjectSb.AppendLine("\t\t\tLayerElement: {");
456 tempObjectSb.AppendLine("\t\t\t\tType: \"LayerElementColor\"");
457 tempObjectSb.AppendLine("\t\t\t\tTypedIndex: 0");
458 tempObjectSb.AppendLine("\t\t\t}");
459 }
460 tempObjectSb.AppendLine("\t\t\tLayerElement: {");
461 tempObjectSb.AppendLine("\t\t\t\tType: \"LayerElementUV\"");
462 tempObjectSb.AppendLine("\t\t\t\tTypedIndex: 0");
463 tempObjectSb.AppendLine("\t\t\t}");
464 // TODO: Here we would add UV layer 1 for ambient occlusion UV file
465 // tempObjectSb.AppendLine("\t\t\tLayerElement: {");
466 // tempObjectSb.AppendLine("\t\t\t\tType: \"LayerElementUV\"");
467 // tempObjectSb.AppendLine("\t\t\t\tTypedIndex: 1");
468 // tempObjectSb.AppendLine("\t\t\t}");
469 tempObjectSb.AppendLine("\t\t}");
470 tempObjectSb.AppendLine("\t}");
471
472 // Add the connection for the model to the geometry so it is attached the right mesh
473 tempConnectionsSb.AppendLine("\t;Geometry::, Model::" + mesh.name);
474 tempConnectionsSb.AppendLine("\tC: \"OO\"," + geometryId + "," + modelId);
475 tempConnectionsSb.AppendLine();
476
477 // Add the connection of all the materials in order of submesh
478 MeshRenderer meshRenderer = gameObj.GetComponent<MeshRenderer>();
479 if(meshRenderer != null)
480 {
481 Material[] allMaterialsInThisMesh = meshRenderer.sharedMaterials;
482
483 for(int i = 0; i < allMaterialsInThisMesh.Length; i++)
484 {
485 Material mat = allMaterialsInThisMesh[i];
486 int referenceId = Mathf.Abs(mat.GetInstanceID());
487
488 if(mat == null)
489 {
490 Debug.LogError("ERROR: the game object " + gameObj.name + " has an empty material on it. This will export problematic files. Please fix and reexport");
491 continue;
492 }
493
494 tempConnectionsSb.AppendLine("\t;Material::" + mat.name + ", Model::" + mesh.name);
495 tempConnectionsSb.AppendLine("\tC: \"OO\"," + referenceId + "," + modelId);
496 tempConnectionsSb.AppendLine();
497 }
498 }
499
500 }
501
502 // Recursively add all the other objects to the string that has been built.
503 for(int i = 0; i < gameObj.transform.childCount; i++)
504 {
505 GameObject childObject = gameObj.transform.GetChild(i).gameObject;
506 if (childObject.activeSelf)
507 {
508 FBXUnityMeshGetter.GetMeshToString(childObject, materials, ref tempObjectSb, ref tempConnectionsSb, gameObj,
509 modelId);
510 }
511 }
512
513 objects.Append(tempObjectSb.ToString());
514 connections.Append(tempConnectionsSb.ToString());
515
516 return modelId;
517 }
518
519 //private Mesh CreateMeshInstance(UnityEngine.Object obj,Mesh mesh)
520 //{
521 // obj = new UnityEngine.Object();
522 // Mesh instanceMesh = UnityEngine.Instantiate(Mesh);
523 //}
524 }
525}
UnityFBXExporter.FBXExporter FE
UnityEngine.Random Random
UnityEngine.Debug Debug
Definition: TanodaServer.cs:19
UnityEngine.Color Color
Definition: TestScript.cs:32
static long GetMeshToString(GameObject gameObj, Material[] materials, ref StringBuilder objects, ref StringBuilder connections, GameObject parentObject=null, long parentModelId=0)
Gets all the meshes and outputs to a string (even grabbing the child of each gameObject)