i should probably commit now

This commit is contained in:
tresabhi committed 2026-06-08 14:26:58 -05:00
1 parent 9cc94635a8
commit 667d8c8f7c
7 files changed
+325 -354

No files matched your search

+1
View File
@@ -0,0 +1 @@
indentSize: 2
+2 -1
View File
@@ -9,7 +9,8 @@
"webp", "webp",
"hocs", "hocs",
"cooldown", "cooldown",
"Lods" "Lods",
"Skia"
], ],
"userWords": [ "userWords": [
"WOTB", "WOTB",
+103 -107
View File
@@ -12,6 +12,7 @@ using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Engine; using CUE4Parse.UE4.Objects.Engine;
using CUE4Parse.UE4.Objects.UObject; using CUE4Parse.UE4.Objects.UObject;
using Microsoft.JavaScript.NodeApi; using Microsoft.JavaScript.NodeApi;
using SharpGLTF.Materials;
using SkiaSharp; using SkiaSharp;
namespace BlitzKit.Game.JSExport; namespace BlitzKit.Game.JSExport;
@@ -19,134 +20,129 @@ namespace BlitzKit.Game.JSExport;
[JSExport] [JSExport]
public partial class GameInterface public partial class GameInterface
{ {
private readonly BlitzFileProvider provider; private readonly BlitzFileProvider provider;
public HashSet<string> files; public HashSet<string> files;
public GameInterface(string directory, string map, string temp) public GameInterface(string directory, string map, string temp)
{
provider = new(directory, map, temp);
files = [.. provider.Files.Keys];
}
public HashSet<string> Files => files;
public byte[] TankBigIcon(string tag)
{
var pda = provider.Discovered<UPrimaryDataAsset>(tag);
var icon = pda.Get<FSoftObjectPath>("BigIcon");
return provider.Image(icon);
}
[GeneratedRegex(@"T_UI_Flag_(\w+)_S")]
private static partial Regex FlagNameRegex();
public byte[] Flag(string nation)
{
var flagsBase = "Blitz/Content/UI/Textures/Flag/TankCard/";
foreach (var path in provider.Files.Keys)
{ {
provider = new(directory, map, temp); if (!path.StartsWith(flagsBase))
{
continue;
}
files = [.. provider.Files.Keys]; var fileName = Path.GetFileNameWithoutExtension(path);
} var match = FlagNameRegex().Match(fileName);
public HashSet<string> Files => files; if (
match.Success
public byte[] TankBigIcon(string tag) && match.Groups[1].Value.Equals(nation, StringComparison.CurrentCultureIgnoreCase)
{ )
var pda = provider.Discovered<UPrimaryDataAsset>(tag); {
var icon = pda.Get<FSoftObjectPath>("BigIcon"); if (provider.TryLoadPackageObject<UTexture2D>($"{path}.{fileName}", out var uTexture))
return provider.Image(icon);
}
[GeneratedRegex(@"T_UI_Flag_(\w+)_S")]
private static partial Regex FlagNameRegex();
public byte[] Flag(string nation)
{
var flagsBase = "Blitz/Content/UI/Textures/Flag/TankCard/";
foreach (var path in provider.Files.Keys)
{ {
if (!path.StartsWith(flagsBase)) CTexture? cTexture = uTexture.Decode(ETexturePlatform.DesktopMobile);
{
continue;
}
var fileName = Path.GetFileNameWithoutExtension(path); if (cTexture is null)
var match = FlagNameRegex().Match(fileName); {
Console.WriteLine($"No flag found for {nation}");
return [];
}
if ( SKBitmap bitmap = cTexture.ToSkBitmap();
match.Success SKData data = bitmap.Encode(SKEncodedImageFormat.Webp, 80);
&& match.Groups[1].Value.Equals(nation, StringComparison.CurrentCultureIgnoreCase)
)
{
if (
provider.TryLoadPackageObject<UTexture2D>(
$"{path}.{fileName}",
out var uTexture
)
)
{
CTexture? cTexture = uTexture.Decode(ETexturePlatform.DesktopMobile);
if (cTexture is null) return data.ToArray();
{
Console.WriteLine($"No flag found for {nation}");
return [];
}
SKBitmap bitmap = cTexture.ToSkBitmap();
SKData data = bitmap.Encode(SKEncodedImageFormat.Webp, 80);
return data.ToArray();
}
Console.WriteLine($"No flag found for {nation}");
return [];
}
} }
Console.WriteLine($"No flag found for {nation}"); Console.WriteLine($"No flag found for {nation}");
return []; return [];
}
} }
public byte[] ConsumableIcon(string tag) Console.WriteLine($"No flag found for {nation}");
return [];
}
public byte[] ConsumableIcon(string tag)
{
var pda = provider.Discovered<UPrimaryDataAsset>(tag);
var icon = pda.Get<FSoftObjectPath>("Icon");
return provider.Image(icon);
}
public byte[] EquipmentIcon(string tag)
{
var pda = provider.Discovered<UPrimaryDataAsset>(tag);
var icon = pda.Get<FSoftObjectPath>("Icon");
return provider.Image(icon);
}
readonly Dictionary<string, string> tankPartPrefixes = new()
{
{ "chassis", "DT_Chassis" },
{ "hull", "DT_Hulls" },
{ "turret", "DT_Turrets" },
{ "gun", "DT_Guns" },
};
public MappedGltf TankPart(string tag, string part)
{
var pda = provider.Discovered<UPrimaryDataAsset>(tag);
UDataTable? dataTable = null;
foreach (var (prefix, name) in tankPartPrefixes)
{ {
var pda = provider.Discovered<UPrimaryDataAsset>(tag); if (!part.StartsWith(prefix))
var icon = pda.Get<FSoftObjectPath>("Icon"); {
return provider.Image(icon); continue;
}
dataTable = pda.Get<UDataTable>(name);
break;
} }
public byte[] EquipmentIcon(string tag) if (dataTable == null)
{ {
var pda = provider.Discovered<UPrimaryDataAsset>(tag); throw new ArgumentException($"Unknown tank part: {part}");
var icon = pda.Get<FSoftObjectPath>("Icon");
return provider.Image(icon);
} }
readonly Dictionary<string, string> tankPartPrefixes = new() dataTable.TryGetDataTableRow(part, StringComparison.Ordinal, out var row);
if (row == null)
{ {
{ "chassis", "DT_Chassis" }, throw new ArgumentException($"Unknown tank part: {part}");
{ "hull", "DT_Hulls" },
{ "turret", "DT_Turrets" },
{ "gun", "DT_Guns" },
};
public byte[] TankPart(string tag, string part)
{
var pda = provider.Discovered<UPrimaryDataAsset>(tag);
UDataTable? dataTable = null;
foreach (var (prefix, name) in tankPartPrefixes)
{
if (!part.StartsWith(prefix))
{
continue;
}
dataTable = pda.Get<UDataTable>(name);
break;
}
if (dataTable == null)
{
throw new ArgumentException($"Unknown tank part: {part}");
}
dataTable.TryGetDataTableRow(part, StringComparison.Ordinal, out var row);
if (row == null)
{
throw new ArgumentException($"Unknown tank part: {part}");
}
var visualData = row.Get<UObject>("VisualData");
var meshSettings = visualData.Get<FStructFallback>("MeshSettings");
// var collisionMesh = meshSettings.Get<UStaticMesh>("CollisionMesh");
var mesh = meshSettings.Get<UStaticMesh>("GunMesh");
var gltf = new MonoGltf(mesh);
return [.. gltf.Write()];
} }
var visualData = row.Get<UObject>("VisualData");
var meshSettings = visualData.Get<FStructFallback>("MeshSettings");
// var collisionMesh = meshSettings.Get<UStaticMesh>("CollisionMesh");
var mesh = meshSettings.Get<UStaticMesh>("Mesh");
var gltf = new MonoGltf(mesh);
return gltf.Write();
}
} }
+17
View File
@@ -0,0 +1,17 @@
using Microsoft.JavaScript.NodeApi;
using SharpGLTF.Materials;
namespace BlitzKit.Game.JSExport;
[JSExport]
public readonly struct MappedGltf
{
public string Json { get; }
public Dictionary<string, Dictionary<KnownChannel, string>> TextureMap { get; }
public MappedGltf(string json, Dictionary<string, Dictionary<KnownChannel, string>> textureMap)
{
Json = json;
TextureMap = textureMap;
}
}
+199 -243
View File
@@ -1,4 +1,6 @@
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;
@@ -13,270 +15,224 @@ using CUE4Parse.UE4.Objects.Meshes;
using SharpGLTF.Geometry; using SharpGLTF.Geometry;
using SharpGLTF.Geometry.VertexTypes; using SharpGLTF.Geometry.VertexTypes;
using SharpGLTF.Materials; using SharpGLTF.Materials;
using SharpGLTF.Memory;
using SharpGLTF.Scenes; using SharpGLTF.Scenes;
using SharpGLTF.Schema2;
using SharpGLTF.Validation;
namespace BlitzKit.Game.Models; namespace BlitzKit.Game.Models;
using ConfiguredMeshBuilder = MeshBuilder< using ConfiguredMeshBuilder = MeshBuilder<
VertexPositionNormalTangent, VertexPositionNormalTangent,
VertexColorXTextureX, VertexColorXTextureX,
VertexEmpty VertexEmpty
>; >;
public class MonoGltf public class MonoGltf
{ {
readonly SceneBuilder Scene; readonly SceneBuilder scene;
readonly Dictionary<string, Dictionary<KnownChannel, string>> textureMap = [];
public MonoGltf(UStaticMesh staticMesh) readonly Dictionary<string, KnownChannel> knownChannels = new()
{
{ "BaseColor", KnownChannel.BaseColor },
{ "Normal", KnownChannel.Normal },
{ "RMAO", KnownChannel.MetallicRoughness },
// { "CDE", ?? },
};
public MonoGltf(UStaticMesh staticMesh)
{
scene = new(staticMesh.Name);
var options = new ExporterOptions() { TextureFormat = ETextureFormat.Jpeg };
staticMesh.TryConvert(out var convertedMesh);
var lod =
convertedMesh.LODs.Find(lod => !lod.SkipLod) ?? throw new Exception("Failed to find lod0");
MaterialBuilder emptyMaterial = new("empty_material");
Dictionary<string, MaterialBuilder> materialMap = [];
int sectionIndex = 0;
foreach (var section in lod.Sections.Value)
{ {
Scene = new(staticMesh.Name); ConfiguredMeshBuilder meshBuilder = new($"{staticMesh.Name}_section_{sectionIndex}");
ExporterOptions options = new() { TextureFormat = ETextureFormat.Jpeg }; MaterialBuilder materialBuilder;
staticMesh.TryConvert(out var convertedMesh);
var lod =
convertedMesh.LODs.Find(lod => !lod.SkipLod)
?? throw new Exception("Failed to find lod0");
MaterialBuilder emptyMaterial = new("empty_material"); if (section.Material == null || section.MaterialName == null)
Dictionary<string, MaterialBuilder> namedMaterials = []; {
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() };
Console.WriteLine(lod.Sections.Value.Length); materialInterface.GetParams(materialData.Parameters, options.MaterialFormat);
materialBuilder = new(section.MaterialName);
int sectionIndex = 0; var textures = new Dictionary<KnownChannel, string>();
foreach (var section in lod.Sections.Value)
materialMap.Add(section.MaterialName, materialBuilder);
textureMap.Add(section.MaterialName, textures);
foreach (var parameterTexture in materialData.Parameters.Textures)
{ {
Console.WriteLine(section.MaterialName); if (knownChannels.TryGetValue(parameterTexture.Key, out var channel))
{
var texture = parameterTexture.Value;
var name = Path.GetFileNameWithoutExtension(texture.GetPathName());
var path = $"/textures/{name}";
// var path = texture.GetPathName();
ConfiguredMeshBuilder meshBuilder = new($"{staticMesh.Name}_section_{sectionIndex}"); textures.Add(channel, path);
MaterialBuilder materialBuilder; }
else
if (section.Material == null || section.MaterialName == null) {
{ // Console.WriteLine($"Unsupported texture type: {parameterTexture.Key}");
materialBuilder = emptyMaterial; }
}
else if (namedMaterials.TryGetValue(section.MaterialName, out MaterialBuilder? value))
{
materialBuilder = value;
}
else
{
var materialInterface = section.Material.Load<UMaterialInterface>()!;
MaterialData materialData = new() { Textures = [], Parameters = new() };
materialInterface.GetParams(materialData.Parameters, options.MaterialFormat);
materialBuilder = new(section.MaterialName);
foreach (var parameterTexture in materialData.Parameters.Textures)
{
var texture = parameterTexture.Value;
if (
texture is not UTexture2D texture2d
|| texture2d.Decode(options.Platform) is not { } bitmap
)
{
Console.WriteLine($"Failed to decode texture: {texture?.Name}");
continue;
}
switch (parameterTexture.Key)
{
case "BaseColor":
{
var encoded = bitmap.Encode(options.TextureFormat, false, out var _);
var bytes = encoded.ToArray();
materialBuilder.WithBaseColor(bytes);
break;
}
case "Normal":
{
var encoded = bitmap.Encode(options.TextureFormat, false, out var _);
var bytes = encoded.ToArray();
materialBuilder.WithNormal(bytes);
break;
}
case "RMAO":
{
var encoded = bitmap.Encode(options.TextureFormat, false, out var _);
// for (int y = 0; y < bitmap.Height; y++)
// {
// for (int x = 0; x < bitmap.Width; x++)
// {
// var index = y * bitmap.Width + x;
// var color = bitmap.Pixels[index];
// bitmap.Pixels[index] = new(
// color.Blue,
// color.Red,
// color.Green,
// color.Alpha
// );
// }
// }
var bytes = encoded.ToArray();
materialBuilder.WithBaseColor(bytes);
break;
}
case "CDE":
{
// idk wtf this is
break;
}
default:
{
Console.WriteLine($"Unsupported texture type: {parameterTexture.Key}");
break;
}
}
}
}
var primitive = meshBuilder.UsePrimitive(materialBuilder);
Console.WriteLine($"faces: {section.NumFaces}");
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 = lod.Verts[wedgeIndex[0]];
var vertex2 = lod.Verts[wedgeIndex[1]];
var vertex3 = lod.Verts[wedgeIndex[2]];
var (v1, v2, v3) = PrepareTris(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 ArraySegment<byte> Write() var primitive = meshBuilder.UsePrimitive(materialBuilder);
{
return Scene.ToGltf2().WriteGLB();
}
private static ( for (int faceIndex = 0; faceIndex < section.NumFaces; faceIndex++)
VertexPositionNormalTangent, {
VertexPositionNormalTangent, var wedgeIndex = new int[3];
VertexPositionNormalTangent for (var k = 0; k < wedgeIndex.Length; k++)
) PrepareTris(CMeshVertex vert1, CMeshVertex vert2, CMeshVertex vert3)
{
VertexPositionNormalTangent v1 = new(
SwapYZ(vert1.Position * 0.01f),
SwapYZAndNormalize((FVector)vert1.Normal),
SwapYZAndNormalize((Vector4)vert1.Tangent)
);
VertexPositionNormalTangent v2 = new(
SwapYZ(vert2.Position * 0.01f),
SwapYZAndNormalize((FVector)vert2.Normal),
SwapYZAndNormalize((Vector4)vert2.Tangent)
);
VertexPositionNormalTangent v3 = new(
SwapYZ(vert3.Position * 0.01f),
SwapYZAndNormalize((FVector)vert3.Normal),
SwapYZAndNormalize((Vector4)vert3.Tangent)
);
return (v1, v2, v3);
}
public static FVector SwapYZ(FVector vec)
{
var res = new FVector(vec.X, vec.Z, vec.Y);
return res;
}
public static FVector SwapYZAndNormalize(FVector vec)
{
var res = SwapYZ(vec);
res.Normalize();
return res;
}
public static Vector4 SwapYZAndNormalize(Vector4 vec)
{
return Vector4.Normalize(new Vector4(vec.X, vec.Z, vec.Y, vec.W));
}
public static (
VertexColorXTextureX,
VertexColorXTextureX,
VertexColorXTextureX
) PrepareUVsAndTexCoords(
CBaseMeshLod lod,
CMeshVertex vert1,
CMeshVertex vert2,
CMeshVertex vert3,
int[] indices
)
{
return PrepareUVsAndTexCoords(
lod.VertexColors ?? new FColor[lod.NumVerts],
vert1,
vert2,
vert3,
lod.ExtraUV.Value,
indices
);
}
public static (
VertexColorXTextureX,
VertexColorXTextureX,
VertexColorXTextureX
) PrepareUVsAndTexCoords(
FColor[] colors,
CMeshVertex vert1,
CMeshVertex vert2,
CMeshVertex vert3,
FMeshUVFloat[][] uvs,
int[] indices
)
{
var (uvs1, uvs2, uvs3) = PrepareUVs(vert1, vert2, vert3, uvs, indices);
var c1 = new VertexColorXTextureX((Vector4)colors[indices[0]] / 255, uvs1);
var c2 = new VertexColorXTextureX((Vector4)colors[indices[1]] / 255, uvs2);
var c3 = new VertexColorXTextureX((Vector4)colors[indices[2]] / 255, uvs3);
return (c1, c2, c3);
}
private static (List<Vector2>, List<Vector2>, List<Vector2>) PrepareUVs(
CMeshVertex vert1,
CMeshVertex vert2,
CMeshVertex vert3,
FMeshUVFloat[][] uvs,
int[] indices
)
{
var uvs1 = new List<Vector2>() { (Vector2)vert1.UV };
var uvs2 = new List<Vector2>() { (Vector2)vert2.UV };
var uvs3 = new List<Vector2>() { (Vector2)vert3.UV };
foreach (var uv in uvs)
{ {
uvs1.Add((Vector2)uv[indices[0]]); wedgeIndex[k] = (int)lod.Indices.Value[section.FirstIndex + faceIndex * 3 + k];
uvs2.Add((Vector2)uv[indices[1]]);
uvs3.Add((Vector2)uv[indices[2]]);
} }
return (uvs1, uvs2, uvs3); var vertex1 = lod.Verts[wedgeIndex[0]];
var vertex2 = lod.Verts[wedgeIndex[1]];
var vertex3 = lod.Verts[wedgeIndex[2]];
var (v1, v2, v3) = PrepareTris(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 MappedGltf Write()
{
var model = scene.ToGltf2();
var json = model.GetJSON(false);
return new MappedGltf(json, textureMap);
}
private static (
VertexPositionNormalTangent,
VertexPositionNormalTangent,
VertexPositionNormalTangent
) PrepareTris(CMeshVertex vert1, CMeshVertex vert2, CMeshVertex vert3)
{
VertexPositionNormalTangent v1 = new(
SwapYZ(vert1.Position * 0.01f),
SwapYZAndNormalize((FVector)vert1.Normal),
SwapYZAndNormalize((Vector4)vert1.Tangent)
);
VertexPositionNormalTangent v2 = new(
SwapYZ(vert2.Position * 0.01f),
SwapYZAndNormalize((FVector)vert2.Normal),
SwapYZAndNormalize((Vector4)vert2.Tangent)
);
VertexPositionNormalTangent v3 = new(
SwapYZ(vert3.Position * 0.01f),
SwapYZAndNormalize((FVector)vert3.Normal),
SwapYZAndNormalize((Vector4)vert3.Tangent)
);
return (v1, v2, v3);
}
public static FVector SwapYZ(FVector vec)
{
var res = new FVector(vec.X, vec.Z, vec.Y);
return res;
}
public static FVector SwapYZAndNormalize(FVector vec)
{
var res = SwapYZ(vec);
res.Normalize();
return res;
}
public static Vector4 SwapYZAndNormalize(Vector4 vec)
{
return Vector4.Normalize(new Vector4(vec.X, vec.Z, vec.Y, vec.W));
}
public static (
VertexColorXTextureX,
VertexColorXTextureX,
VertexColorXTextureX
) PrepareUVsAndTexCoords(
CBaseMeshLod lod,
CMeshVertex vert1,
CMeshVertex vert2,
CMeshVertex vert3,
int[] indices
)
{
return PrepareUVsAndTexCoords(
lod.VertexColors ?? new FColor[lod.NumVerts],
vert1,
vert2,
vert3,
lod.ExtraUV.Value,
indices
);
}
public static (
VertexColorXTextureX,
VertexColorXTextureX,
VertexColorXTextureX
) PrepareUVsAndTexCoords(
FColor[] colors,
CMeshVertex vert1,
CMeshVertex vert2,
CMeshVertex vert3,
FMeshUVFloat[][] uvs,
int[] indices
)
{
var (uvs1, uvs2, uvs3) = PrepareUVs(vert1, vert2, vert3, uvs, indices);
var c1 = new VertexColorXTextureX((Vector4)colors[indices[0]] / 255, uvs1);
var c2 = new VertexColorXTextureX((Vector4)colors[indices[1]] / 255, uvs2);
var c3 = new VertexColorXTextureX((Vector4)colors[indices[2]] / 255, uvs3);
return (c1, c2, c3);
}
private static (List<Vector2>, List<Vector2>, List<Vector2>) PrepareUVs(
CMeshVertex vert1,
CMeshVertex vert2,
CMeshVertex vert3,
FMeshUVFloat[][] uvs,
int[] indices
)
{
var uvs1 = new List<Vector2>() { (Vector2)vert1.UV };
var uvs2 = new List<Vector2>() { (Vector2)vert2.UV };
var uvs3 = new List<Vector2>() { (Vector2)vert3.UV };
foreach (var uv in uvs)
{
uvs1.Add((Vector2)uv[indices[0]]);
uvs2.Add((Vector2)uv[indices[1]]);
uvs3.Add((Vector2)uv[indices[2]]);
}
return (uvs1, uvs2, uvs3);
}
} }
@@ -35,8 +35,8 @@ export async function GET({
const item = api.metadata.item(params.id); const item = api.metadata.item(params.id);
const assetDiscovery = item.BlitzStaticAssetsDiscovery(); const assetDiscovery = item.BlitzStaticAssetsDiscovery();
const tag = assertDiscoveryTag(assetDiscovery); const tag = assertDiscoveryTag(assetDiscovery);
const part = game!.tankPart(tag, params.part); const asd = game!.tankPart(tag, params.part);
const array = new Uint8Array(part); // const array = new Uint8Array(part);
return new Response(array); return Response.json(asd);
} }
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 886 KiB