Skip to content

Commit 3ee8f52

Browse files
authored
Optimize MeshToObjString by modularizing mesh export logic (#109)
Refactored MeshToObjString into smaller, reusable methods for improved readability and maintainability. Added helper methods to handle vertices, texture coordinates, normals, and faces separately. Fixed scaling issue due to double multiplication. The changes enhance code clarity and enforce single-responsibility principles for mesh data processing.
1 parent f65cd0c commit 3ee8f52

File tree

1 file changed

+114
-46
lines changed

1 file changed

+114
-46
lines changed

RuntimeUnityEditor.Core/Utils/ObjectDumper/MeshExport.cs

Lines changed: 114 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -55,72 +55,140 @@ public static bool ExportObj(Renderer rend, bool bakedMesh, bool bakedWorldPosit
5555
return false;
5656
}
5757

58+
private static Mesh GetMeshFromRenderer(Renderer rend, bool baked)
59+
{
60+
switch (rend)
61+
{
62+
case MeshRenderer meshRenderer:
63+
return meshRenderer.GetComponent<MeshFilter>().mesh;
64+
case SkinnedMeshRenderer skinnedMeshRenderer:
65+
return baked ? BakeMesh(skinnedMeshRenderer) : skinnedMeshRenderer.sharedMesh;
66+
default:
67+
throw new ArgumentException("Unsupported Renderer type: " + rend.GetType().FullName);
68+
}
69+
}
70+
71+
private static Mesh BakeMesh(SkinnedMeshRenderer mesh)
72+
{
73+
var bakedMesh = new Mesh();
74+
mesh.BakeMesh(bakedMesh);
75+
return bakedMesh;
76+
}
77+
78+
private static string NameFormatted(this Renderer go) => go == null ? "" : go.name.Replace("(Instance)", "").Replace(" Instance", "").Trim();
79+
5880
private static string MeshToObjString(Renderer rend, bool bakedMesh, bool bakedWorldPosition)
5981
{
6082
if (rend == null) throw new ArgumentNullException(nameof(rend));
6183

62-
Mesh mesh;
63-
if (rend is MeshRenderer meshRenderer)
64-
mesh = meshRenderer.GetComponent<MeshFilter>().mesh;
65-
else if (rend is SkinnedMeshRenderer skinnedMeshRenderer)
84+
var mesh = GetMeshFromRenderer(rend, bakedMesh);
85+
var sb = new StringBuilder();
86+
var hasData = false;
87+
88+
hasData |= AppendVertices(sb, mesh, rend, bakedMesh && bakedWorldPosition);
89+
hasData |= AppendTextureCoordinates(sb, mesh);
90+
hasData |= AppendNormals(sb, mesh, rend, bakedMesh && bakedWorldPosition);
91+
hasData |= AppendFaces(sb, mesh, rend);
92+
93+
if(!hasData)
94+
throw new InvalidOperationException("No mesh data found or mesh is set as not readable");
95+
return sb.ToString();
96+
}
97+
98+
/// <summary>
99+
/// Appends the vertex data of a mesh to a StringBuilder in a specific format.
100+
/// </summary>
101+
/// <param name="builder">The StringBuilder object to append the vertex data to.</param>
102+
/// <param name="mesh">The Mesh object containing the vertex data to export.</param>
103+
/// <param name="renderer">The Renderer associated with the mesh, used for transformations.</param>
104+
/// <param name="bakedMesh">Indicates whether the mesh is baked.</param>
105+
/// <param name="bakedWorldPosition">Indicates whether to apply world position transformations to the vertices.</param>
106+
/// <returns>Returns true if vertex data was successfully appended, otherwise false.</returns>
107+
private static bool AppendVertices(StringBuilder builder, Mesh mesh, Renderer renderer, bool baked)
108+
{
109+
if (mesh.vertices.Length == 0)
110+
return false;
111+
112+
foreach (var v in mesh.vertices)
66113
{
67-
if (bakedMesh)
68-
{
69-
mesh = new Mesh();
70-
skinnedMeshRenderer.BakeMesh(mesh);
71-
}
72-
else
73-
{
74-
mesh = skinnedMeshRenderer.sharedMesh;
75-
}
114+
var transformedVertex = baked ? renderer.transform.TransformPoint(v) : v;
115+
builder.AppendLine($"v {-transformedVertex.x} {transformedVertex.y} {transformedVertex.z}");
76116
}
77-
else throw new ArgumentException("Unsupported Renderer type: " + rend.GetType().FullName);
78117

79-
var scale = rend.transform.lossyScale;
80-
var inverseScale = Matrix4x4.Scale(scale).inverse;
118+
return true;
119+
}
81120

82-
var sb = new StringBuilder();
83-
var any = false;
84-
for (var x = 0; x < mesh.subMeshCount; x++)
121+
/// <summary>
122+
/// Appends the texture coordinates (UV mapping) of a mesh to the provided StringBuilder.
123+
/// </summary>
124+
/// <param name="builder">The StringBuilder instance to append the texture coordinates to.</param>
125+
/// <param name="mesh">The mesh whose texture coordinates will be appended.</param>
126+
/// <returns>True if the mesh has texture coordinates; otherwise, false.</returns>
127+
private static bool AppendTextureCoordinates(StringBuilder builder, Mesh mesh)
128+
{
129+
if (mesh.uv.Length == 0)
130+
return false;
131+
132+
foreach (var uv in mesh.uv)
85133
{
86-
var subMesh = MeshExtensions.Submesh(mesh, x);
134+
builder.AppendLine($"vt {uv.x} {uv.y}");
135+
}
87136

88-
sb.AppendLine($"g {rend.name.Replace("(Instance)", "").Replace(" Instance", "").Trim()}_{x}");
137+
return true;
138+
}
89139

90-
for (var i = 0; i < subMesh.vertices.Length; i++)
91-
{
92-
var v = subMesh.vertices[i];
93-
if (bakedMesh && bakedWorldPosition)
94-
v = rend.transform.TransformPoint(inverseScale.MultiplyPoint(v));
95-
sb.AppendLine($"v {-v.x} {v.y} {v.z}");
96-
any = true;
97-
}
140+
/// <summary>
141+
/// Appends vertex normal data from a mesh to the provided StringBuilder in the Wavefront .obj format.
142+
/// </summary>
143+
/// <param name="builder">The StringBuilder to which the normal data will be appended.</param>
144+
/// <param name="mesh">The mesh whose normals will be processed and appended.</param>
145+
/// <param name="renderer">The renderer associated with the mesh, used for transforming normals if needed.</param>
146+
/// <param name="bakedMesh">Indicates if the mesh should be treated as baked.</param>
147+
/// <param name="bakedWorldPosition">Indicates if the normals should be transformed to world position when the mesh is baked.</param>
148+
/// <returns>True if normals are successfully appended; false if the mesh contains no normals.</returns>
149+
private static bool AppendNormals(StringBuilder builder, Mesh mesh, Renderer renderer, bool baked)
150+
{
151+
if (mesh.normals.Length == 0)
152+
return false;
98153

99-
for (var i = 0; i < subMesh.uv.Length; i++)
100-
{
101-
Vector3 v = subMesh.uv[i];
102-
sb.AppendLine($"vt {v.x} {v.y}");
103-
any = true;
104-
}
154+
foreach (var normal in mesh.normals)
155+
{
156+
var transformedNormal = baked ? renderer.transform.TransformDirection(normal) : normal;
157+
builder.AppendLine($"vn {-transformedNormal.x} {transformedNormal.y} {transformedNormal.z}");
158+
}
105159

106-
for (var i = 0; i < subMesh.normals.Length; i++)
107-
{
108-
var v = subMesh.normals[i];
109-
sb.AppendLine($"vn {-v.x} {v.y} {v.z}");
110-
any = true;
111-
}
160+
return true;
161+
}
112162

113-
var triangles = subMesh.GetTriangles(x);
163+
/// <summary>
164+
/// Appends face data from a mesh to the specified StringBuilder in the Wavefront OBJ format.
165+
/// </summary>
166+
/// <param name="builder">The StringBuilder to which the face data will be appended.</param>
167+
/// <param name="mesh">The mesh containing face data to be exported.</param>
168+
/// <param name="renderer">The Renderer associated with the mesh, used for naming and formatting purposes.</param>
169+
/// <returns>Returns true if the mesh contains sub-mesh data and face data was successfully appended; otherwise, false.</returns>
170+
private static bool AppendFaces(StringBuilder builder, Mesh mesh, Renderer renderer)
171+
{
172+
if (mesh.subMeshCount == 0) return false;
173+
174+
for (var subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; subMeshIndex++)
175+
{
176+
builder.AppendLine($"g {renderer.NameFormatted()}_{subMeshIndex}");
177+
var triangles = mesh.GetTriangles(subMeshIndex);
178+
114179
for (var i = 0; i < triangles.Length; i += 3)
115180
{
116-
sb.AppendFormat("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", triangles[i] + 1, triangles[i + 2] + 1, triangles[i + 1] + 1);
117-
any = true;
181+
var v1 = triangles[i] + 1;
182+
var v2 = triangles[i + 2] + 1;
183+
var v3 = triangles[i + 1] + 1;
184+
builder.AppendLine($"f {v1}/{v1}/{v1} {v2}/{v2}/{v2} {v3}/{v3}/{v3}");
118185
}
119186
}
120-
if (!any) throw new InvalidOperationException("No mesh data found or mesh is set as not readable");
121-
return sb.ToString();
187+
return true;
122188
}
123189

190+
191+
// Should be possible to be safely removed, I'll leave it here for now as a fallback in case something goes wrong
124192
private static class MeshExtensions
125193
{
126194
public static Mesh Submesh(Mesh mesh, int submeshIndex)

0 commit comments

Comments
 (0)