From a348e005f76193446157c40a0dc9c674b952a850 Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Tue, 9 Jun 2026 12:43:14 -0500 Subject: [PATCH] texture discovery --- .github/workflows/website.yaml | 2 +- packages/game/JSExport/GameInterface.cs | 76 +++++++++++++++++++ packages/game/Models/MonoGltf.cs | 16 ++-- packages/website-ue/src/game/game.ts | 13 ++++ .../src/pages/[textures]/[name].webp.ts | 12 +++ .../website-ue/src/pages/[textures]/_index.ts | 7 ++ 6 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 packages/website-ue/src/pages/[textures]/[name].webp.ts create mode 100644 packages/website-ue/src/pages/[textures]/_index.ts diff --git a/.github/workflows/website.yaml b/.github/workflows/website.yaml index e5e326257..8ab188ed8 100644 --- a/.github/workflows/website.yaml +++ b/.github/workflows/website.yaml @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - chunk: [pages, api, media, models] + chunk: [pages, api, media, models, textures] steps: - name: Free disk space diff --git a/packages/game/JSExport/GameInterface.cs b/packages/game/JSExport/GameInterface.cs index 5dd82797f..4c7698064 100644 --- a/packages/game/JSExport/GameInterface.cs +++ b/packages/game/JSExport/GameInterface.cs @@ -1,11 +1,13 @@ using System.Text.RegularExpressions; using BlitzKit.Game.Models; using CUE4Parse_Conversion; +using CUE4Parse_Conversion.Materials; using CUE4Parse_Conversion.Meshes; using CUE4Parse_Conversion.Textures; using CUE4Parse_Conversion.UEFormat.Enums; using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Engine; +using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.StaticMesh; using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Assets.Objects; @@ -31,6 +33,80 @@ public partial class GameInterface files = [.. provider.Files.Keys]; } + readonly Dictionary texturePaths = new(); + + public void DiscoverTextures(string[] tankTags) + { + Console.WriteLine("Discovering textures..."); + + var options = new ExporterOptions() { TextureFormat = ETextureFormat.Jpeg }; + + foreach (var tag in tankTags) + { + var pda = provider.Discovered(tag); + + foreach (var group in tankPartPrefixes.Values) + { + var dataTable = pda.Get(group); + + foreach (var row in dataTable.RowMap.Values) + { + var visualData = row.Get("VisualData"); + var meshSettings = visualData.Get("MeshSettings"); + + foreach (var property in meshSettings.Properties) + { + if (meshSettings.TryGet(property.Name.Text, out var mesh)) + { + mesh.TryConvert(out var convertedMesh); + + var lod0 = convertedMesh.LODs.Find(lod => !lod.SkipLod); + + if (lod0 == null || lod0.Sections == null) + { + continue; + } + + foreach (var section in lod0.Sections.Value) + { + if (section.Material == null) + { + continue; + } + + var materialInterface = section.Material.Load(); + + if (materialInterface == null) + { + continue; + } + + var materialData = new MaterialData() { Parameters = new() }; + + materialInterface.GetParams(materialData.Parameters, options.MaterialFormat); + + foreach (var parameterTexture in materialData.Parameters.Textures) + { + if (MonoGltf.knownChannels.TryGetValue(parameterTexture.Key, out var channel)) + { + var path = parameterTexture.Value.GetPathName(); + var name = Path.GetFileNameWithoutExtension(path); + + texturePaths[name] = path; + } + } + } + } + } + } + } + } + + Console.WriteLine($"Discovered {texturePaths.Count} textures"); + } + + public List Textures => [.. texturePaths.Keys]; + public HashSet Files => files; public byte[] TankBigIcon(string tag) diff --git a/packages/game/Models/MonoGltf.cs b/packages/game/Models/MonoGltf.cs index 855c571cc..928dca845 100644 --- a/packages/game/Models/MonoGltf.cs +++ b/packages/game/Models/MonoGltf.cs @@ -32,7 +32,7 @@ public class MonoGltf { readonly SceneBuilder scene = new(); - readonly Dictionary knownChannels = new() + public static readonly Dictionary knownChannels = new() { { "BaseColor", KnownChannel.BaseColor }, { "Normal", KnownChannel.Normal }, @@ -46,14 +46,14 @@ public class MonoGltf var options = new ExporterOptions() { TextureFormat = ETextureFormat.Jpeg }; staticMesh.TryConvert(out var convertedMesh); - var lod = + var lod0 = convertedMesh.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 lod.Sections.Value) + foreach (var section in lod0.Sections.Value) { var meshBuilder = new ConfiguredMeshBuilder(); MaterialBuilder materialBuilder; @@ -100,15 +100,15 @@ public class MonoGltf var wedgeIndex = new int[3]; for (var k = 0; k < wedgeIndex.Length; k++) { - wedgeIndex[k] = (int)lod.Indices.Value[section.FirstIndex + faceIndex * 3 + k]; + wedgeIndex[k] = (int)lod0.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 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(lod, vertex1, vertex2, vertex3, wedgeIndex); + var (c1, c2, c3) = PrepareUVsAndTexCoords(lod0, vertex1, vertex2, vertex3, wedgeIndex); primitive.AddTriangle((v1, c1), (v2, c2), (v3, c3)); } diff --git a/packages/website-ue/src/game/game.ts b/packages/website-ue/src/game/game.ts index 563c4a7e9..163f8c0ab 100644 --- a/packages/website-ue/src/game/game.ts +++ b/packages/website-ue/src/game/game.ts @@ -1,6 +1,8 @@ import { assertSecret } from "@blitzkit/core"; import type { GameInterface } from "@blitzkit/game"; import { clientUnmounted } from "./unmounted"; +import { api } from "../api/dynamic"; +import { assertDiscoveryTag } from "./assertDiscoveryTag"; let _game: GameInterface | null = null; @@ -12,6 +14,17 @@ if (!clientUnmounted && import.meta.env.SSR) { assertSecret(import.meta.env.WOTB_USMAP), "../../temp", ); + + const tankTags: string[] = []; + + for (const item of api.metadata.group("TankEntity")) { + const assetDiscovery = item.BlitzStaticAssetsDiscovery(); + const tag = assertDiscoveryTag(assetDiscovery); + + tankTags.push(tag); + } + + _game.discoverTextures(tankTags); } export const game = _game; diff --git a/packages/website-ue/src/pages/[textures]/[name].webp.ts b/packages/website-ue/src/pages/[textures]/[name].webp.ts new file mode 100644 index 000000000..4f7b55d31 --- /dev/null +++ b/packages/website-ue/src/pages/[textures]/[name].webp.ts @@ -0,0 +1,12 @@ +import type { APIContext } from "astro"; +import { mixStaticPaths } from "../../astro/mixStaticPaths"; +import { game } from "../../game/game"; +import { getStaticPaths as _getStaticPaths } from "./_index"; + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, () => { + return game!.textures.map((name) => ({ params: { name } })); +}); + +export function GET({ params }: APIContext) { + return Response.json(params); +} diff --git a/packages/website-ue/src/pages/[textures]/_index.ts b/packages/website-ue/src/pages/[textures]/_index.ts new file mode 100644 index 000000000..cd5d939f5 --- /dev/null +++ b/packages/website-ue/src/pages/[textures]/_index.ts @@ -0,0 +1,7 @@ +import type { GetStaticPaths } from "astro"; +import { isChunkEnabled } from "../../astro/isChunkEnabled"; + +export const getStaticPaths = (() => { + if (!isChunkEnabled("textures")) return []; + return [{ params: { textures: "textures" } }]; +}) satisfies GetStaticPaths;