From 3dd0c4916dc6f2827118128662aa455e00f36b71 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Fri, 12 Jun 2026 13:43:58 -0500 Subject: [PATCH] recursive mesh exporitng --- packages/game/JSExport/GameInterface.cs | 64 ++++++--- packages/game/Models/MonoGltf.cs | 178 +++++++++++++----------- 2 files changed, 140 insertions(+), 102 deletions(-) diff --git a/packages/game/JSExport/GameInterface.cs b/packages/game/JSExport/GameInterface.cs index cc806c41c..c1326ba97 100644 --- a/packages/game/JSExport/GameInterface.cs +++ b/packages/game/JSExport/GameInterface.cs @@ -7,12 +7,16 @@ using CUE4Parse_Conversion.Meshes; using CUE4Parse_Conversion.Textures; using CUE4Parse_Conversion.UEFormat.Enums; using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Exports.Component; using CUE4Parse.UE4.Assets.Exports.Engine; using CUE4Parse.UE4.Assets.Exports.Material; +using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; using CUE4Parse.UE4.Assets.Exports.StaticMesh; using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Assets.Objects; +using CUE4Parse.UE4.Assets.Objects.Properties; using CUE4Parse.UE4.Objects.Engine; +using CUE4Parse.UE4.Objects.GameplayTags; using CUE4Parse.UE4.Objects.UObject; using Microsoft.JavaScript.NodeApi; using SharpGLTF.Materials; @@ -245,25 +249,51 @@ public partial class GameInterface var visualData = row.Get("VisualData"); var meshSettings = visualData.Get("MeshSettings"); - var meshes = new List(); - - foreach (var property in meshSettings.Properties) - { - var name = property.Name.Text; - - if (name == "CollisionMesh") - { - continue; - } - - if (meshSettings.TryGet(name, out var mesh)) - { - meshes.Add(mesh); - } - } - + var meshes = CollectMeshes(meshSettings); var gltf = new MonoGltf(meshes); return gltf.Write($"{tag}/{part}"); } + + static List CollectMeshes(FStructFallback settings) + { + var meshes = new List(); + + foreach (var property in settings.Properties) + { + var key = property.Name.Text; + + if (key == "CollisionMesh") + { + continue; + } + + if (settings.TryGet(key, out var staticMesh)) + { + meshes.Add(staticMesh); + continue; + } + + if (settings.TryGet(key, out var skeletalMesh)) + { + meshes.Add(skeletalMesh); + continue; + } + + if (settings.TryGet(key, out var array)) + { + foreach (var item in array) + { + meshes.AddRange(CollectMeshes(item)); + } + continue; + } + + Console.WriteLine( + $"{key} -> {property.Tag} ({property.TagData}) [{property.PropertyTagFlags}]" + ); + } + + return meshes; + } } diff --git a/packages/game/Models/MonoGltf.cs b/packages/game/Models/MonoGltf.cs index 1cde75a9d..894e011db 100644 --- a/packages/game/Models/MonoGltf.cs +++ b/packages/game/Models/MonoGltf.cs @@ -1,15 +1,14 @@ using System.Numerics; -using System.Text; -using BlitzKit.Game.JSExport; using CUE4Parse_Conversion; using CUE4Parse_Conversion.Materials; using CUE4Parse_Conversion.Meshes; using CUE4Parse_Conversion.Meshes.glTF; using CUE4Parse_Conversion.Meshes.PSK; using CUE4Parse_Conversion.Textures; +using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Material; +using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; using CUE4Parse.UE4.Assets.Exports.StaticMesh; -using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Objects.Core.Math; using CUE4Parse.UE4.Objects.Meshes; using SharpGLTF.Geometry; @@ -18,7 +17,6 @@ using SharpGLTF.Materials; using SharpGLTF.Memory; using SharpGLTF.Scenes; using SharpGLTF.Schema2; -using SharpGLTF.Validation; namespace BlitzKit.Game.Models; @@ -32,6 +30,9 @@ public class MonoGltf { readonly SceneBuilder scene = new(); + readonly MaterialBuilder emptyMaterial = new("empty_material"); + readonly ExporterOptions options = new() { TextureFormat = ETextureFormat.Jpeg }; + public static readonly Dictionary knownChannels = new() { { "BaseColor", KnownChannel.BaseColor }, @@ -41,94 +42,106 @@ public class MonoGltf }; readonly byte[] stubBytes = File.ReadAllBytes("../game/stub/small.png"); - public MonoGltf(List meshes) + public MonoGltf(List meshes) { - var options = new ExporterOptions() { TextureFormat = ETextureFormat.Jpeg }; + var materialMap = new Dictionary(); foreach (var mesh in meshes) { - mesh.TryConvert(out var converted); - var lod0 = - converted.LODs.Find(lod => !lod.SkipLod) ?? throw new Exception("Failed to find lod0"); - - var emptyMaterial = new MaterialBuilder("empty_material"); - var materialMap = new Dictionary(); - - int sectionIndex = 0; - foreach (var section in lod0.Sections.Value) + if (mesh is UStaticMesh staticMesh) { - var meshBuilder = new ConfiguredMeshBuilder(); - MaterialBuilder materialBuilder; + staticMesh.TryConvert(out var convertedStaticMesh); - if (section.Material == null || section.MaterialName == null) + var lod0 = convertedStaticMesh.LODs.Find(lod => !lod.SkipLod)!; + + foreach (var section in lod0.Sections!.Value) { - materialBuilder = emptyMaterial; + ParseSection(section, materialMap, lod0, lod0.Verts!); } - else if (materialMap.TryGetValue(section.MaterialName, out MaterialBuilder? value)) + } + else if (mesh is USkeletalMesh skeletalMesh) + { + skeletalMesh.TryConvert(out var convertedSkeletalMesh); + + var lod0 = convertedSkeletalMesh.LODs.Find(lod => !lod.SkipLod)!; + + foreach (var section in lod0.Sections!.Value) { - materialBuilder = value; + ParseSection(section, materialMap, lod0, lod0.Verts!); } - else - { - var materialInterface = section.Material.Load()!; - var materialData = new MaterialData() { Parameters = new() }; - - materialInterface.GetParams(materialData.Parameters, options.MaterialFormat); - materialBuilder = new(); - materialMap.Add(section.MaterialName, materialBuilder); - - foreach (var parameterTexture in materialData.Parameters.Textures) - { - if (knownChannels.TryGetValue(parameterTexture.Key, out var channel)) - { - var texture = parameterTexture.Value; - var name = Path.GetFileNameWithoutExtension(texture.GetPathName()); - var path = $"../../../textures/{name}.webp"; - - var lastIndex = stubBytes.Length - 1; - stubBytes[lastIndex] = (byte)(++stubBytes[lastIndex] % 255); - var uniqueBytes = (byte[])stubBytes.Clone(); - var stubImage = new MemoryImage(uniqueBytes); - - // Console.WriteLine( - // $"created stub image {stubImage} @ {stubImage.SourcePath} w/ byte {uniqueBytes[lastIndex]}" - // ); - - materialBuilder.WithChannelImage(channel, stubImage); - materialBuilder.GetChannel(channel).Texture.PrimaryImage.Name = path; - } - else - { - // Console.WriteLine($"Unsupported texture type: {parameterTexture.Key}"); - } - } - } - - var primitive = meshBuilder.UsePrimitive(materialBuilder); - - for (int faceIndex = 0; faceIndex < section.NumFaces; faceIndex++) - { - var wedgeIndex = new int[3]; - for (var k = 0; k < wedgeIndex.Length; k++) - { - wedgeIndex[k] = (int)lod0.Indices.Value[section.FirstIndex + faceIndex * 3 + k]; - } - - var vertex1 = lod0.Verts[wedgeIndex[0]]; - var vertex2 = lod0.Verts[wedgeIndex[1]]; - var vertex3 = lod0.Verts[wedgeIndex[2]]; - - var (v1, v2, v3) = PrepareTris(vertex1, vertex2, vertex3); - var (c1, c2, c3) = PrepareUVsAndTexCoords(lod0, vertex1, vertex2, vertex3, wedgeIndex); - - primitive.AddTriangle((v1, c1), (v2, c2), (v3, c3)); - } - - scene.AddRigidMesh(meshBuilder, Matrix4x4.Identity); } } } + void ParseSection( + CMeshSection section, + Dictionary materialMap, + CBaseMeshLod lod, + CMeshVertex[] vertices + ) + { + var meshBuilder = new ConfiguredMeshBuilder(); + MaterialBuilder materialBuilder; + + if (section.Material == null || section.MaterialName == null) + { + materialBuilder = emptyMaterial; + } + else if (materialMap.TryGetValue(section.MaterialName, out MaterialBuilder? value)) + { + materialBuilder = value; + } + else + { + var materialInterface = section.Material.Load()!; + var materialData = new MaterialData() { Parameters = new() }; + + materialInterface.GetParams(materialData.Parameters, options.MaterialFormat); + materialBuilder = new(); + materialMap.Add(section.MaterialName, materialBuilder); + + foreach (var parameterTexture in materialData.Parameters.Textures) + { + if (knownChannels.TryGetValue(parameterTexture.Key, out var channel)) + { + var texture = parameterTexture.Value; + var name = Path.GetFileNameWithoutExtension(texture.GetPathName()); + var path = $"../../../textures/{name}.webp"; + + var lastIndex = stubBytes.Length - 1; + stubBytes[lastIndex] = (byte)(++stubBytes[lastIndex] % 255); + var uniqueBytes = (byte[])stubBytes.Clone(); + var stubImage = new MemoryImage(uniqueBytes); + + materialBuilder.WithChannelImage(channel, stubImage); + materialBuilder.GetChannel(channel).Texture.PrimaryImage.Name = path; + } + } + } + + var primitive = meshBuilder.UsePrimitive(materialBuilder); + + for (int faceIndex = 0; faceIndex < section.NumFaces; faceIndex++) + { + var wedgeIndex = new int[3]; + for (var k = 0; k < wedgeIndex.Length; k++) + { + wedgeIndex[k] = (int)lod.Indices!.Value[section.FirstIndex + faceIndex * 3 + k]; + } + + var vertex1 = vertices[wedgeIndex[0]]; + var vertex2 = vertices[wedgeIndex[1]]; + var vertex3 = vertices[wedgeIndex[2]]; + + var (v1, v2, v3) = PrepareTriangles(vertex1, vertex2, vertex3); + var (c1, c2, c3) = PrepareUVsAndTexCoords(lod, vertex1, vertex2, vertex3, wedgeIndex); + + primitive.AddTriangle((v1, c1), (v2, c2), (v3, c3)); + } + + scene.AddRigidMesh(meshBuilder, Matrix4x4.Identity); + } + public byte[] Write(string name) { var model = scene.ToGltf2(); @@ -138,11 +151,8 @@ public class MonoGltf var mappedTextures = new Dictionary(); - // Console.WriteLine($"found {model.LogicalImages.Count} logical images"); - foreach (var image in model.LogicalImages) { - // Console.WriteLine($"mapping {image.Content} ({image.Name})"); mappedTextures[image.Content] = image.Name!; image.Name = null; } @@ -156,8 +166,6 @@ public class MonoGltf ImageWriting = ResourceWriteMode.SatelliteFile, ImageWriteCallback = (context, assetName, image) => { - // Console.WriteLine($"got {image} -> {mappedTextures[image]}"); - return mappedTextures[image]; }, } @@ -172,7 +180,7 @@ public class MonoGltf VertexPositionNormalTangent, VertexPositionNormalTangent, VertexPositionNormalTangent - ) PrepareTris(CMeshVertex vert1, CMeshVertex vert2, CMeshVertex vert3) + ) PrepareTriangles(CMeshVertex vert1, CMeshVertex vert2, CMeshVertex vert3) { var v1 = new VertexPositionNormalTangent( SwapYZ(vert1.Position * 0.01f), @@ -228,7 +236,7 @@ public class MonoGltf vert1, vert2, vert3, - lod.ExtraUV.Value, + lod.ExtraUV!.Value, indices ); }