mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-25 03:29:51 +01:00
recursive mesh exporitng
This commit is contained in:
1 parent
0531fbf3ef
commit
3dd0c4916d
2 files changed
+140
-102
No files matched your search
@@ -7,12 +7,16 @@ using CUE4Parse_Conversion.Meshes;
|
|||||||
using CUE4Parse_Conversion.Textures;
|
using CUE4Parse_Conversion.Textures;
|
||||||
using CUE4Parse_Conversion.UEFormat.Enums;
|
using CUE4Parse_Conversion.UEFormat.Enums;
|
||||||
using CUE4Parse.UE4.Assets.Exports;
|
using CUE4Parse.UE4.Assets.Exports;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Component;
|
||||||
using CUE4Parse.UE4.Assets.Exports.Engine;
|
using CUE4Parse.UE4.Assets.Exports.Engine;
|
||||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||||
using CUE4Parse.UE4.Assets.Objects;
|
using CUE4Parse.UE4.Assets.Objects;
|
||||||
|
using CUE4Parse.UE4.Assets.Objects.Properties;
|
||||||
using CUE4Parse.UE4.Objects.Engine;
|
using CUE4Parse.UE4.Objects.Engine;
|
||||||
|
using CUE4Parse.UE4.Objects.GameplayTags;
|
||||||
using CUE4Parse.UE4.Objects.UObject;
|
using CUE4Parse.UE4.Objects.UObject;
|
||||||
using Microsoft.JavaScript.NodeApi;
|
using Microsoft.JavaScript.NodeApi;
|
||||||
using SharpGLTF.Materials;
|
using SharpGLTF.Materials;
|
||||||
@@ -245,25 +249,51 @@ public partial class GameInterface
|
|||||||
|
|
||||||
var visualData = row.Get<UObject>("VisualData");
|
var visualData = row.Get<UObject>("VisualData");
|
||||||
var meshSettings = visualData.Get<FStructFallback>("MeshSettings");
|
var meshSettings = visualData.Get<FStructFallback>("MeshSettings");
|
||||||
var meshes = new List<UStaticMesh>();
|
var meshes = CollectMeshes(meshSettings);
|
||||||
|
|
||||||
foreach (var property in meshSettings.Properties)
|
|
||||||
{
|
|
||||||
var name = property.Name.Text;
|
|
||||||
|
|
||||||
if (name == "CollisionMesh")
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (meshSettings.TryGet<UStaticMesh>(name, out var mesh))
|
|
||||||
{
|
|
||||||
meshes.Add(mesh);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var gltf = new MonoGltf(meshes);
|
var gltf = new MonoGltf(meshes);
|
||||||
|
|
||||||
return gltf.Write($"{tag}/{part}");
|
return gltf.Write($"{tag}/{part}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<UObject> CollectMeshes(FStructFallback settings)
|
||||||
|
{
|
||||||
|
var meshes = new List<UObject>();
|
||||||
|
|
||||||
|
foreach (var property in settings.Properties)
|
||||||
|
{
|
||||||
|
var key = property.Name.Text;
|
||||||
|
|
||||||
|
if (key == "CollisionMesh")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.TryGet<UStaticMesh>(key, out var staticMesh))
|
||||||
|
{
|
||||||
|
meshes.Add(staticMesh);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.TryGet<USkeletalMesh>(key, out var skeletalMesh))
|
||||||
|
{
|
||||||
|
meshes.Add(skeletalMesh);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.TryGet<FStructFallback[]>(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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Text;
|
|
||||||
using BlitzKit.Game.JSExport;
|
|
||||||
using CUE4Parse_Conversion;
|
using CUE4Parse_Conversion;
|
||||||
using CUE4Parse_Conversion.Materials;
|
using CUE4Parse_Conversion.Materials;
|
||||||
using CUE4Parse_Conversion.Meshes;
|
using CUE4Parse_Conversion.Meshes;
|
||||||
using CUE4Parse_Conversion.Meshes.glTF;
|
using CUE4Parse_Conversion.Meshes.glTF;
|
||||||
using CUE4Parse_Conversion.Meshes.PSK;
|
using CUE4Parse_Conversion.Meshes.PSK;
|
||||||
using CUE4Parse_Conversion.Textures;
|
using CUE4Parse_Conversion.Textures;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports;
|
||||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
|
||||||
using CUE4Parse.UE4.Objects.Core.Math;
|
using CUE4Parse.UE4.Objects.Core.Math;
|
||||||
using CUE4Parse.UE4.Objects.Meshes;
|
using CUE4Parse.UE4.Objects.Meshes;
|
||||||
using SharpGLTF.Geometry;
|
using SharpGLTF.Geometry;
|
||||||
@@ -18,7 +17,6 @@ using SharpGLTF.Materials;
|
|||||||
using SharpGLTF.Memory;
|
using SharpGLTF.Memory;
|
||||||
using SharpGLTF.Scenes;
|
using SharpGLTF.Scenes;
|
||||||
using SharpGLTF.Schema2;
|
using SharpGLTF.Schema2;
|
||||||
using SharpGLTF.Validation;
|
|
||||||
|
|
||||||
namespace BlitzKit.Game.Models;
|
namespace BlitzKit.Game.Models;
|
||||||
|
|
||||||
@@ -32,6 +30,9 @@ public class MonoGltf
|
|||||||
{
|
{
|
||||||
readonly SceneBuilder scene = new();
|
readonly SceneBuilder scene = new();
|
||||||
|
|
||||||
|
readonly MaterialBuilder emptyMaterial = new("empty_material");
|
||||||
|
readonly ExporterOptions options = new() { TextureFormat = ETextureFormat.Jpeg };
|
||||||
|
|
||||||
public static readonly Dictionary<string, KnownChannel> knownChannels = new()
|
public static readonly Dictionary<string, KnownChannel> knownChannels = new()
|
||||||
{
|
{
|
||||||
{ "BaseColor", KnownChannel.BaseColor },
|
{ "BaseColor", KnownChannel.BaseColor },
|
||||||
@@ -41,94 +42,106 @@ public class MonoGltf
|
|||||||
};
|
};
|
||||||
readonly byte[] stubBytes = File.ReadAllBytes("../game/stub/small.png");
|
readonly byte[] stubBytes = File.ReadAllBytes("../game/stub/small.png");
|
||||||
|
|
||||||
public MonoGltf(List<UStaticMesh> meshes)
|
public MonoGltf(List<UObject> meshes)
|
||||||
{
|
{
|
||||||
var options = new ExporterOptions() { TextureFormat = ETextureFormat.Jpeg };
|
var materialMap = new Dictionary<string, MaterialBuilder>();
|
||||||
|
|
||||||
foreach (var mesh in meshes)
|
foreach (var mesh in meshes)
|
||||||
{
|
{
|
||||||
mesh.TryConvert(out var converted);
|
if (mesh is UStaticMesh staticMesh)
|
||||||
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<string, MaterialBuilder>();
|
|
||||||
|
|
||||||
int sectionIndex = 0;
|
|
||||||
foreach (var section in lod0.Sections.Value)
|
|
||||||
{
|
{
|
||||||
var meshBuilder = new ConfiguredMeshBuilder();
|
staticMesh.TryConvert(out var convertedStaticMesh);
|
||||||
MaterialBuilder materialBuilder;
|
|
||||||
|
|
||||||
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<UMaterialInterface>()!;
|
|
||||||
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<string, MaterialBuilder> 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<UMaterialInterface>()!;
|
||||||
|
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)
|
public byte[] Write(string name)
|
||||||
{
|
{
|
||||||
var model = scene.ToGltf2();
|
var model = scene.ToGltf2();
|
||||||
@@ -138,11 +151,8 @@ public class MonoGltf
|
|||||||
|
|
||||||
var mappedTextures = new Dictionary<MemoryImage, string>();
|
var mappedTextures = new Dictionary<MemoryImage, string>();
|
||||||
|
|
||||||
// Console.WriteLine($"found {model.LogicalImages.Count} logical images");
|
|
||||||
|
|
||||||
foreach (var image in model.LogicalImages)
|
foreach (var image in model.LogicalImages)
|
||||||
{
|
{
|
||||||
// Console.WriteLine($"mapping {image.Content} ({image.Name})");
|
|
||||||
mappedTextures[image.Content] = image.Name!;
|
mappedTextures[image.Content] = image.Name!;
|
||||||
image.Name = null;
|
image.Name = null;
|
||||||
}
|
}
|
||||||
@@ -156,8 +166,6 @@ public class MonoGltf
|
|||||||
ImageWriting = ResourceWriteMode.SatelliteFile,
|
ImageWriting = ResourceWriteMode.SatelliteFile,
|
||||||
ImageWriteCallback = (context, assetName, image) =>
|
ImageWriteCallback = (context, assetName, image) =>
|
||||||
{
|
{
|
||||||
// Console.WriteLine($"got {image} -> {mappedTextures[image]}");
|
|
||||||
|
|
||||||
return mappedTextures[image];
|
return mappedTextures[image];
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -172,7 +180,7 @@ public class MonoGltf
|
|||||||
VertexPositionNormalTangent,
|
VertexPositionNormalTangent,
|
||||||
VertexPositionNormalTangent,
|
VertexPositionNormalTangent,
|
||||||
VertexPositionNormalTangent
|
VertexPositionNormalTangent
|
||||||
) PrepareTris(CMeshVertex vert1, CMeshVertex vert2, CMeshVertex vert3)
|
) PrepareTriangles(CMeshVertex vert1, CMeshVertex vert2, CMeshVertex vert3)
|
||||||
{
|
{
|
||||||
var v1 = new VertexPositionNormalTangent(
|
var v1 = new VertexPositionNormalTangent(
|
||||||
SwapYZ(vert1.Position * 0.01f),
|
SwapYZ(vert1.Position * 0.01f),
|
||||||
@@ -228,7 +236,7 @@ public class MonoGltf
|
|||||||
vert1,
|
vert1,
|
||||||
vert2,
|
vert2,
|
||||||
vert3,
|
vert3,
|
||||||
lod.ExtraUV.Value,
|
lod.ExtraUV!.Value,
|
||||||
indices
|
indices
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user