diff --git a/bun.lock b/bun.lock index 968745477..78a400c50 100644 --- a/bun.lock +++ b/bun.lock @@ -200,6 +200,7 @@ "@fontsource-variable/jetbrains-mono": "^5.2.8", "@fontsource-variable/roboto-flex": "^5.2.8", "@fontsource-variable/roboto-mono": "^5.2.9", + "@gltf-transform/core": "^4.4.0", "@radix-ui/colors": "^3.0.0", "@radix-ui/react-icons": "^1.3.2", "@react-three/drei": "^10.7.7", @@ -3106,6 +3107,8 @@ "@blitzkit/website-ue/@astrojs/node": ["@astrojs/node@9.5.5", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.6", "send": "^1.2.1", "server-destroy": "^1.0.1" }, "peerDependencies": { "astro": "^5.17.3" } }, "sha512-rtU2BGU5u3SfGURpANfMxVzCIoR86MkaN05ncza9rbtuMKJ/XnRJt/BbyVknDbOJ71hoci0SIsJwKcJR8vvi/A=="], + "@blitzkit/website-ue/@gltf-transform/core": ["@gltf-transform/core@4.4.0", "", { "dependencies": { "property-graph": "^4.1.0" } }, "sha512-cOPxOhHFFz5hwmix+li1+Nnq5qMV/QD3fTCsVlApxxFACtFdjkt2R/juseD4gvZ7D2c/yl6OilKH0pvI735YyQ=="], + "@blitzkit/website-ue/@types/react": ["@types/react@19.2.15", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q=="], "@blitzkit/website-ue/astro": ["astro@5.18.2", "", { "dependencies": { "@astrojs/compiler": "^2.13.0", "@astrojs/internal-helpers": "0.7.6", "@astrojs/markdown-remark": "6.3.11", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^4.0.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "acorn": "^8.15.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.3.1", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.1.1", "cssesc": "^3.0.0", "debug": "^4.4.3", "deterministic-object-hash": "^2.0.2", "devalue": "^5.6.2", "diff": "^8.0.3", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.27.3", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.4.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.1", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.1", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.3", "shiki": "^3.21.0", "smol-toml": "^1.6.0", "svgo": "^4.0.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.7.3", "unist-util-visit": "^5.0.0", "unstorage": "^1.17.4", "vfile": "^6.0.3", "vite": "^6.4.1", "vitefu": "^1.1.1", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.3", "zod": "^3.25.76", "zod-to-json-schema": "^3.25.1", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "astro.js" } }, "sha512-TnFwLnAXty5MXKPDGuKXqK4AMBXG+FH6RUdK7Oyc3gyfNoFIthT+4eRbzOK43bdRlLaZuxgciDSjgtggZ3OtGQ=="], @@ -3316,6 +3319,8 @@ "@blitzkit/website-ue/@astrojs/node/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.6", "", {}, "sha512-GOle7smBWKfMSP8osUIGOlB5kaHdQLV3foCsf+5Q9Wsuu+C6Fs3Ez/ttXmhjZ1HkSgsogcM1RXSjjOVieHq16Q=="], + "@blitzkit/website-ue/@gltf-transform/core/property-graph": ["property-graph@4.1.0", "", {}, "sha512-AvPcP7XECNWy4LGmFQ77k7un4lSKM4eS29PTvW4ck95uYeLxXPWJM7hLuBqK91FaHqCcgJvIUCuNJjjxKE7VKQ=="], + "@blitzkit/website-ue/astro/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.6", "", {}, "sha512-GOle7smBWKfMSP8osUIGOlB5kaHdQLV3foCsf+5Q9Wsuu+C6Fs3Ez/ttXmhjZ1HkSgsogcM1RXSjjOVieHq16Q=="], "@blitzkit/website-ue/astro/@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.11", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.6", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.21.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ=="], diff --git a/packages/game/JSExport/GameInterface.cs b/packages/game/JSExport/GameInterface.cs index 6186e6b8a..891dc8318 100644 --- a/packages/game/JSExport/GameInterface.cs +++ b/packages/game/JSExport/GameInterface.cs @@ -109,40 +109,48 @@ public partial class GameInterface { "gun", "DT_Guns" }, }; - public MappedGltf TankPart(string tag, string part) + public string TankPart(string tag, string part) { - var pda = provider.Discovered(tag); - UDataTable? dataTable = null; - - foreach (var (prefix, name) in tankPartPrefixes) + try { - if (!part.StartsWith(prefix)) + var pda = provider.Discovered(tag); + UDataTable? dataTable = null; + + foreach (var (prefix, name) in tankPartPrefixes) { - continue; + if (!part.StartsWith(prefix)) + { + continue; + } + + dataTable = pda.Get(name); + break; } - dataTable = pda.Get(name); - break; - } + if (dataTable == null) + { + throw new ArgumentException($"Unknown tank part: {part}"); + } - if (dataTable == null) + dataTable.TryGetDataTableRow(part, StringComparison.Ordinal, out var row); + + if (row == null) + { + throw new ArgumentException($"Unknown tank part: {part}"); + } + + var visualData = row.Get("VisualData"); + var meshSettings = visualData.Get("MeshSettings"); + // var collisionMesh = meshSettings.Get("CollisionMesh"); + var mesh = meshSettings.Get("Mesh"); + + var gltf = new MonoGltf(mesh); + + return gltf.Write(); + } + catch (Exception e) { - throw new ArgumentException($"Unknown tank part: {part}"); + throw new Exception($"Failed to export tank part: {part}", e); } - - dataTable.TryGetDataTableRow(part, StringComparison.Ordinal, out var row); - - if (row == null) - { - throw new ArgumentException($"Unknown tank part: {part}"); - } - - var visualData = row.Get("VisualData"); - var meshSettings = visualData.Get("MeshSettings"); - // var collisionMesh = meshSettings.Get("CollisionMesh"); - var mesh = meshSettings.Get("Mesh"); - var gltf = new MonoGltf(mesh); - - return gltf.Write(); } } diff --git a/packages/game/JSExport/MappedGltf.cs b/packages/game/JSExport/MappedGltf.cs index fa8d81178..ce3dac0eb 100644 --- a/packages/game/JSExport/MappedGltf.cs +++ b/packages/game/JSExport/MappedGltf.cs @@ -1,4 +1,5 @@ -using Microsoft.JavaScript.NodeApi; +using System.Text.Json; +using Microsoft.JavaScript.NodeApi; using SharpGLTF.Materials; namespace BlitzKit.Game.JSExport; @@ -14,4 +15,9 @@ public readonly struct MappedGltf Json = json; TextureMap = textureMap; } + + public string ToJson() + { + return JsonSerializer.Serialize(this); + } } diff --git a/packages/game/Models/MonoGltf.cs b/packages/game/Models/MonoGltf.cs index 5b23032f1..217790dd4 100644 --- a/packages/game/Models/MonoGltf.cs +++ b/packages/game/Models/MonoGltf.cs @@ -84,12 +84,12 @@ public class MonoGltf { 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(); + materialBuilder.WithChannelImage(channel, "../../stub/stupid.png"); - textures.Add(channel, path); + // var texture = parameterTexture.Value; + // var name = Path.GetFileNameWithoutExtension(texture.GetPathName()); + + // textures.Add(channel, name); } else { @@ -122,12 +122,48 @@ public class MonoGltf } } - public MappedGltf Write() + public string Write() { - var model = scene.ToGltf2(); - var json = model.GetJSON(false); + try + { + var model = scene.ToGltf2(); - return new MappedGltf(json, textureMap); + var stream = new MemoryStream(); + var context = WriteContext + .CreateFromStream(stream) + .WithSettingsFrom(new WriteSettings() { ImageWriting = ResourceWriteMode.SatelliteFile }); + + context.ImageWriteCallback = (WriteContext context, string assetName, MemoryImage image) => + { + // image.Content is the raw data + // var bytes = image.Content.ToArray(); + + // store.Files[suggestedName] = bytes; + + // // return URI used in JSON + // + return assetName; + }; + + foreach (var img in model.LogicalImages) + { + Console.WriteLine(img.Content.SourcePath); + Console.WriteLine(img.Content.FileExtension); + } + + model.SaveGLTF + + context.WriteTextSchema2("model.gltf", model); + + stream.Position = 0; + using var reader = new StreamReader(stream); + + return reader.ReadToEnd(); + } + catch (Exception ex) + { + throw new Exception("Failed to write GLTF", ex); + } } private static ( diff --git a/packages/website-ue/package.json b/packages/website-ue/package.json index 24af5c38f..647b1f017 100644 --- a/packages/website-ue/package.json +++ b/packages/website-ue/package.json @@ -21,6 +21,7 @@ "@fontsource-variable/jetbrains-mono": "^5.2.8", "@fontsource-variable/roboto-flex": "^5.2.8", "@fontsource-variable/roboto-mono": "^5.2.9", + "@gltf-transform/core": "^4.4.0", "@radix-ui/colors": "^3.0.0", "@radix-ui/react-icons": "^1.3.2", "@react-three/drei": "^10.7.7", diff --git a/packages/website-ue/src/pages/[models]/tanks/[id]/[part].glb.ts b/packages/website-ue/src/pages/[models]/tanks/[id]/[part].glb.ts index d1df7b7e1..2972383b7 100644 --- a/packages/website-ue/src/pages/[models]/tanks/[id]/[part].glb.ts +++ b/packages/website-ue/src/pages/[models]/tanks/[id]/[part].glb.ts @@ -1,3 +1,4 @@ +import { Document, NodeIO } from "@gltf-transform/core"; import type { APIContext, GetStaticPathsItem } from "astro"; import { api } from "../../../../api/dynamic"; import { mixStaticPaths } from "../../../../astro/mixStaticPaths"; @@ -29,14 +30,20 @@ export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { return paths; }); +type KnownChannel = "BaseColor" | "Normal" | "MetallicRoughness"; + +interface MappedGltf { + Json: string; + TextureMap: Record>; +} + export async function GET({ params, }: APIContext) { const item = api.metadata.item(params.id); const assetDiscovery = item.BlitzStaticAssetsDiscovery(); const tag = assertDiscoveryTag(assetDiscovery); - const asd = game!.tankPart(tag, params.part); - // const array = new Uint8Array(part); + const part = game!.tankPart(tag, params.part); - return Response.json(asd); + return Response.json(part); }