Skip to content

Commit 0222b1e

Browse files
committed
Add option to include all meshes in mesh viewer export
1 parent 10d4ece commit 0222b1e

File tree

3 files changed

+53
-25
lines changed

3 files changed

+53
-25
lines changed

ContentEditor.App/CustomizedFileLoaders/CommonMeshResource.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public partial class CommonMeshResource(string Name, Workspace workspace) : IRes
1818

1919
public Assimp.Scene Scene
2020
{
21-
get => _scene ??= ConvertMeshToAssimpScene(NativeMesh, Name, false, false, false, false, null);
21+
get => _scene ??= AddMeshToScene(new Assimp.Scene(), NativeMesh, Name, false, false, false, false, null);
2222
set => _scene = value;
2323
}
2424

@@ -93,7 +93,7 @@ public void ExportToFile(
9393

9494
var ext = PathUtils.GetExtensionWithoutPeriod(filepath);
9595
string exportFormat = context.GetFormatIDFromExtension(ext);
96-
var scene = GetSceneForExport(ext, false, includeLodsShadows, includeOcc, skeleton);
96+
var scene = AddMeshToScene(new Assimp.Scene(), ext, false, includeLodsShadows, includeOcc, skeleton);
9797

9898
if (_mesh == null) {
9999
Logger.Error("Missing mesh file, can't export");
@@ -109,10 +109,17 @@ public void ExportToFile(
109109
}
110110
if (additionalMeshes?.Any() == true) {
111111
foreach (var addm in additionalMeshes) {
112-
var extra = addm.GetSceneForExport(ext, false, includeLodsShadows, includeOcc, skeleton);
113-
scene.Meshes.AddRange(extra.Meshes);
112+
addm.AddMeshToScene(scene, ext, false, includeLodsShadows, includeOcc, skeleton);
114113
}
115114
}
115+
void PrintTree(Node node, int depth)
116+
{
117+
Logger.Info(new string(' ', depth * 2) + node.Name);
118+
foreach (var child in node.Children) {
119+
PrintTree(child, depth + 1);
120+
}
121+
}
122+
PrintTree(scene.RootNode, 0);
116123
context.ExportFile(scene, filepath, exportFormat);
117124
}
118125

@@ -146,12 +153,12 @@ internal void PreloadMeshBuffers()
146153

147154
}
148155

149-
private Assimp.Scene GetSceneForExport(string targetFileExtension, bool allowCache, bool includeLodsShadows, bool includeOcc, FbxSkelFile? skeleton = null)
156+
private Assimp.Scene AddMeshToScene(Assimp.Scene scene, string targetFileExtension, bool allowCache, bool includeLodsShadows, bool includeOcc, FbxSkelFile? skeleton = null)
150157
{
151158
var isGltf = targetFileExtension == "glb" || targetFileExtension == "gltf";
152159
if (allowCache && _scene != null && !isGltf) return _scene;
153160

154-
Assimp.Scene scene = ConvertMeshToAssimpScene(NativeMesh, Name, isGltf, includeLodsShadows, includeLodsShadows, includeOcc, skeleton);
161+
AddMeshToScene(scene, NativeMesh, Name, isGltf, includeLodsShadows, includeLodsShadows, includeOcc, skeleton);
155162
if (allowCache) _scene = scene;
156163
return scene;
157164
}

ContentEditor.App/CustomizedFileLoaders/MeshConversion/AssimpMeshExport.cs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -130,17 +130,15 @@ private static void AddMotToScene(Assimp.Scene scene, MotFile mot, string export
130130
scene.Animations.Add(anim);
131131
}
132132

133-
private static Assimp.Scene ConvertMeshToAssimpScene(MeshFile file, string rootName, bool isGltf, bool includeAllLods, bool includeShadows, bool includeOcclusion, FbxSkelFile? skeleton)
133+
private static Assimp.Scene AddMeshToScene(Assimp.Scene scene, MeshFile file, string rootName, bool isGltf, bool includeAllLods, bool includeShadows, bool includeOcclusion, FbxSkelFile? skeleton)
134134
{
135135
// NOTE: every matrix needs to be transposed, assimp expects them transposed compared to default System.Numeric.Matrix4x4 for some shit ass reason
136136
// NOTE2: assimp currently forces vert deduplication for gltf export so we may lose some vertices (https://github.com/assimp/assimp/issues/6349)
137137
// NOTE3: weights > 4 will get get lost for gltf because we can't tell it to write more weights (AI_CONFIG_EXPORT_GLTF_UNLIMITED_SKINNING_BONES_PER_VERTEX)
138-
// we'd either need access to assim's Exporter class directly, or have the ExportFile method modified on the assimp side
139-
// TODO: export extra dummy nodes to ensure materials with no meshes (possibly used by LODs only) don't get dropped? (see dd2 ch20_000.mesh.240423143)
140-
// also: lods read and export?
138+
// we'd either need access to assimp's Exporter class directly, or have the ExportFile method modified on the assimp side
141139

142-
var scene = new Assimp.Scene();
143-
scene.RootNode = new Node(rootName);
140+
scene.RootNode ??= new Node(rootName);
141+
var matIndexOffset = scene.Materials.Count;
144142
foreach (var name in file.MaterialNames) {
145143
var aiMat = new Material();
146144
aiMat.Name = name;
@@ -152,6 +150,10 @@ private static Assimp.Scene ConvertMeshToAssimpScene(MeshFile file, string rootN
152150

153151
var includeShapeKeys = false;
154152
var extraBones = new List<Node>();
153+
var existingNodes = FlatNodes(scene.RootNode);
154+
var nodeDict = new Dictionary<string, Node>();
155+
foreach (var ext in existingNodes) nodeDict.TryAdd(ext.Name, ext);
156+
155157
if (bones?.Count > 0 && file.MeshBuffer!.Weights.Length > 0) {
156158
// insert root bones first to ensure all parents exist
157159
Node boneRoot = scene.RootNode;
@@ -164,13 +166,19 @@ private static Assimp.Scene ConvertMeshToAssimpScene(MeshFile file, string rootN
164166
}
165167
foreach (var srcBone in bones) {
166168
if (srcBone.parentIndex == -1) {
167-
var boneNode = new Node(srcBone.name, boneRoot);
169+
if (nodeDict.TryGetValue(srcBone.name, out var boneNode)) {
170+
boneDict[srcBone.index] = boneNode;
171+
continue;
172+
}
173+
174+
boneNode = new Node(srcBone.name, boneRoot);
168175
boneDict[srcBone.index] = boneNode;
169176
boneNode.Transform = Matrix4x4.Transpose(srcBone.localTransform.ToSystem());
170177
boneRoot.Children.Add(boneNode);
171178
if (srcBone.useSecondaryWeight) {
172179
boneNode.Children.Add(new Node(SecondaryWeightDummyBonePrefix + srcBone.name, boneNode));
173180
}
181+
nodeDict[srcBone.name] = boneNode;
174182
}
175183
}
176184

@@ -181,18 +189,25 @@ private static Assimp.Scene ConvertMeshToAssimpScene(MeshFile file, string rootN
181189
pendingBones.Enqueue(srcBone);
182190
continue;
183191
}
184-
var boneNode = new Node(srcBone.name, parentBone);
192+
193+
if (nodeDict.TryGetValue(srcBone.name, out var boneNode)) {
194+
boneDict[srcBone.index] = boneNode;
195+
continue;
196+
}
197+
198+
boneNode = new Node(srcBone.name, parentBone);
185199
boneDict[srcBone.index] = boneNode;
186200
boneNode.Transform = Matrix4x4.Transpose(srcBone.localTransform.ToSystem());
187201
if (srcBone.useSecondaryWeight) {
188202
boneNode.Children.Add(new Node(SecondaryWeightDummyBonePrefix + srcBone.name, boneNode));
189203
}
190204
parentBone.Children.Add(boneNode);
205+
nodeDict[srcBone.name] = boneNode;
191206
}
192207

193208
if (skeleton != null) {
194209
foreach (var refBone in skeleton.Bones) {
195-
var (exportId, exportBone) = boneDict.FirstOrDefault(bb => bb.Value.Name == refBone.name);
210+
var exportBone = nodeDict.GetValueOrDefault(refBone.name);
196211
if (exportBone == null) {
197212
var parentRef = refBone.parentIndex == -1 ? null : skeleton.Bones[refBone.parentIndex];
198213
Node? parent = null;
@@ -242,37 +257,37 @@ static void RecursiveDuplicateShapeBones(Node parent, HashSet<string> boneNames,
242257
for (int i = 0; i < file.MeshData.LODs.Count; i++) {
243258
var lod = file.MeshData.LODs[i];
244259
if (i == 0) {
245-
ExportLod(file, isGltf, scene, bones, includeShapeKeys, lod, includeAllLods ? "lod0_" : "", extraBones);
260+
ExportLod(file, isGltf, scene, bones, includeShapeKeys, lod, includeAllLods ? "lod0_" : "", extraBones, matIndexOffset);
246261
if (!includeAllLods) break;
247262
} else {
248-
ExportLod(file, isGltf, scene, bones, includeShapeKeys, lod, $"lod{i}_", extraBones);
263+
ExportLod(file, isGltf, scene, bones, includeShapeKeys, lod, $"lod{i}_", extraBones, matIndexOffset);
249264
}
250265
}
251266
}
252267
if (includeShadows && file.ShadowMesh != null) {
253268
for (int i = 0; i < file.ShadowMesh.LODs.Count; i++) {
254269
var lod = file.ShadowMesh.LODs[i];
255-
ExportLod(file, isGltf, scene, bones, includeShapeKeys, lod, $"shadow_lod{i}_", extraBones);
270+
ExportLod(file, isGltf, scene, bones, includeShapeKeys, lod, $"shadow_lod{i}_", extraBones, matIndexOffset);
256271
}
257272
}
258273
if (includeOcclusion && file.OccluderMesh != null) {
259274
if (scene.MaterialCount == 0) {
260275
scene.Materials.Add(new Material() { Name = "default" });
261276
}
262-
ExportLod(file, isGltf, scene, bones, includeShapeKeys, file.OccluderMesh, $"occ_", extraBones);
277+
ExportLod(file, isGltf, scene, bones, includeShapeKeys, file.OccluderMesh, $"occ_", extraBones, matIndexOffset);
263278
}
264279

265280
return scene;
266281
}
267282

268-
private static void ExportLod(MeshFile file, bool isGltf, Assimp.Scene scene, List<MeshBone>? bones, bool includeShapeKeys, MeshLOD lod, string namePrefix, List<Node> extraBones)
283+
private static void ExportLod(MeshFile file, bool isGltf, Assimp.Scene scene, List<MeshBone>? bones, bool includeShapeKeys, MeshLOD lod, string namePrefix, List<Node> extraBones, int matIndexOffset)
269284
{
270285
var bounds = file.MeshData?.boundingBox ?? new AABB();
271286
foreach (var mesh in lod.MeshGroups) {
272287
int subId = 0;
273288
foreach (var sub in mesh.Submeshes) {
274289
var aiMesh = new Mesh(PrimitiveType.Triangle);
275-
aiMesh.MaterialIndex = sub.materialIndex;
290+
aiMesh.MaterialIndex = sub.materialIndex + matIndexOffset;
276291

277292

278293
aiMesh.Vertices.AddRange(sub.Positions);

ContentEditor.App/Imgui/App/MeshViewer.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ private void ShowMeshCollections()
358358
ImGui.EndMenu();
359359
}
360360
if (ctx.IsAnimatable && ImGui.BeginMenu($"{AppIcons.SI_TagCharacter} Skeleton")) {
361+
if (ctx.Animator == null) ctx.SetupAnimator();
361362
ctx.ShowSkeletonPicker();
362363
ImGui.EndMenu();
363364
}
@@ -571,8 +572,7 @@ private void ShowImportExportMenu()
571572
}
572573
}
573574

574-
// exportFullCollection ? meshContexts.Skip(1).Select(c => c.MeshFile) :
575-
assmesh.ExportToFile(exportPath, exportLods, exportOcclusion, exportFbxskel ? PrimaryAnimator?.skeleton : null, mots, null);
575+
assmesh.ExportToFile(exportPath, exportLods, exportOcclusion, exportFbxskel ? PrimaryAnimator?.skeleton : null, mots, exportFullCollection ? meshContexts.Skip(1).Select(c => c.MeshFile) : null);
576576
} catch (Exception e) {
577577
Logger.Error(e, "Mesh export failed");
578578
} finally {
@@ -617,9 +617,15 @@ private void ShowImportExportMenu()
617617
if (PrimaryAnimator?.File != null) {
618618
ImGui.Checkbox("Include animations", ref exportAnimations);
619619
if (exportAnimations) ImGui.Checkbox("Selected animation only", ref exportCurrentAnimationOnly);
620-
if (PrimaryAnimator?.skeleton != null) ImGui.Checkbox("Export with merged skeleton", ref exportFbxskel);
621620
}
622-
// if (meshContexts.Count > 1) ImGui.Checkbox("Export all meshes", ref exportFullCollection);
621+
if (PrimaryAnimator?.skeleton != null) {
622+
ImGui.Checkbox("Export with merged skeleton", ref exportFbxskel);
623+
ImguiHelpers.Tooltip("Whether to merge the mesh skeleton with the currently active skeleton file");
624+
}
625+
if (meshContexts.Count > 1) {
626+
ImGui.Checkbox("Export all meshes", ref exportFullCollection);
627+
ImguiHelpers.Tooltip("Whether to include all currently open meshes in the exported file");
628+
}
623629
if (mesh.NativeMesh.MeshData?.LODs.Count > 1 || mesh.NativeMesh.ShadowMesh?.LODs.Count > 0) ImGui.Checkbox("Include LODs and Shadow Mesh", ref exportLods);
624630
if (mesh.NativeMesh.OccluderMesh?.MeshGroups.Count > 0) ImGui.Checkbox("Include Occlusion Mesh", ref exportOcclusion);
625631
if (showImportSettings) {

0 commit comments

Comments
 (0)