Compare commits

...
5 Commits
Author SHA1 Message Date
tresabhi 62b629fe3a go higher 2026-06-08 20:25:15 -05:00
tresabhi f790d07ed6 point to textures 2026-06-08 20:24:51 -05:00
tresabhi c86b269cf2 cleanup 2026-06-08 20:14:55 -05:00
tresabhi a878a7a143 texture mapping actually works omg! 2026-06-08 19:46:18 -05:00
tresabhi 76fc900a15 context shit 2026-06-08 19:17:27 -05:00
7 changed files with 101 additions and 76 deletions

No files matched your search

+5
View File
@@ -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/[email protected]", "", { "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/[email protected]", "", { "dependencies": { "property-graph": "^4.1.0" } }, "sha512-cOPxOhHFFz5hwmix+li1+Nnq5qMV/QD3fTCsVlApxxFACtFdjkt2R/juseD4gvZ7D2c/yl6OilKH0pvI735YyQ=="],
"@blitzkit/website-ue/@types/react": ["@types/[email protected]", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q=="],
"@blitzkit/website-ue/astro": ["[email protected]", "", { "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/[email protected]", "", {}, "sha512-GOle7smBWKfMSP8osUIGOlB5kaHdQLV3foCsf+5Q9Wsuu+C6Fs3Ez/ttXmhjZ1HkSgsogcM1RXSjjOVieHq16Q=="],
"@blitzkit/website-ue/@gltf-transform/core/property-graph": ["[email protected]", "", {}, "sha512-AvPcP7XECNWy4LGmFQ77k7un4lSKM4eS29PTvW4ck95uYeLxXPWJM7hLuBqK91FaHqCcgJvIUCuNJjjxKE7VKQ=="],
"@blitzkit/website-ue/astro/@astrojs/internal-helpers": ["@astrojs/[email protected]", "", {}, "sha512-GOle7smBWKfMSP8osUIGOlB5kaHdQLV3foCsf+5Q9Wsuu+C6Fs3Ez/ttXmhjZ1HkSgsogcM1RXSjjOVieHq16Q=="],
"@blitzkit/website-ue/astro/@astrojs/markdown-remark": ["@astrojs/[email protected]", "", { "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=="],
+35 -27
View File
@@ -109,40 +109,48 @@ public partial class GameInterface
{ "gun", "DT_Guns" },
};
public MappedGltf TankPart(string tag, string part)
public byte[] TankPart(string tag, string part)
{
var pda = provider.Discovered<UPrimaryDataAsset>(tag);
UDataTable? dataTable = null;
foreach (var (prefix, name) in tankPartPrefixes)
try
{
if (!part.StartsWith(prefix))
var pda = provider.Discovered<UPrimaryDataAsset>(tag);
UDataTable? dataTable = null;
foreach (var (prefix, name) in tankPartPrefixes)
{
continue;
if (!part.StartsWith(prefix))
{
continue;
}
dataTable = pda.Get<UDataTable>(name);
break;
}
dataTable = pda.Get<UDataTable>(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<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($"{tag}/{part}");
}
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<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
@@ -1,17 +0,0 @@
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;
}
}
+45 -21
View File
@@ -30,8 +30,7 @@ using ConfiguredMeshBuilder = MeshBuilder<
public class MonoGltf
{
readonly SceneBuilder scene;
readonly Dictionary<string, Dictionary<KnownChannel, string>> textureMap = [];
readonly SceneBuilder scene = new();
readonly Dictionary<string, KnownChannel> knownChannels = new()
{
@@ -40,23 +39,23 @@ public class MonoGltf
{ "RMAO", KnownChannel.MetallicRoughness },
// { "CDE", ?? },
};
readonly string stubTexture = "../../stub/stupid.png";
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 = [];
var emptyMaterial = new MaterialBuilder("empty_material");
var materialMap = new Dictionary<string, MaterialBuilder>();
int sectionIndex = 0;
foreach (var section in lod.Sections.Value)
{
ConfiguredMeshBuilder meshBuilder = new($"{staticMesh.Name}_section_{sectionIndex}");
var meshBuilder = new ConfiguredMeshBuilder();
MaterialBuilder materialBuilder;
if (section.Material == null || section.MaterialName == null)
@@ -73,12 +72,8 @@ public class MonoGltf
var materialData = new MaterialData() { Parameters = new() };
materialInterface.GetParams(materialData.Parameters, options.MaterialFormat);
materialBuilder = new(section.MaterialName);
var textures = new Dictionary<KnownChannel, string>();
materialBuilder = new();
materialMap.Add(section.MaterialName, materialBuilder);
textureMap.Add(section.MaterialName, textures);
foreach (var parameterTexture in materialData.Parameters.Textures)
{
@@ -86,14 +81,14 @@ public class MonoGltf
{
var texture = parameterTexture.Value;
var name = Path.GetFileNameWithoutExtension(texture.GetPathName());
var path = $"/textures/{name}";
// var path = texture.GetPathName();
var path = $"../../../textures/{name}.webp";
textures.Add(channel, path);
materialBuilder.WithChannelImage(channel, stubTexture);
materialBuilder.GetChannel(channel).Texture.PrimaryImage.Name = path;
}
else
{
// Console.WriteLine($"Unsupported texture type: {parameterTexture.Key}");
Console.WriteLine($"Unsupported texture type: {parameterTexture.Key}");
}
}
}
@@ -122,12 +117,38 @@ public class MonoGltf
}
}
public MappedGltf Write()
public byte[] Write(string name)
{
var model = scene.ToGltf2();
var json = model.GetJSON(false);
return new MappedGltf(json, textureMap);
model.Asset.Copyright = null;
model.Asset.Generator = null;
var mappedTextures = new Dictionary<MemoryImage, string>();
foreach (var image in model.LogicalImages)
{
mappedTextures[image.Content] = image.Name!;
image.Name = null;
}
var stream = new MemoryStream();
var context = WriteContext
.CreateFromStream(stream)
.WithSettingsFrom(
new()
{
ImageWriting = ResourceWriteMode.SatelliteFile,
ImageWriteCallback = (context, assetName, image) =>
{
return mappedTextures[image];
},
}
);
context.WriteBinarySchema2(name, model);
return stream.ToArray();
}
private static (
@@ -136,17 +157,17 @@ public class MonoGltf
VertexPositionNormalTangent
) PrepareTris(CMeshVertex vert1, CMeshVertex vert2, CMeshVertex vert3)
{
VertexPositionNormalTangent v1 = new(
var v1 = new VertexPositionNormalTangent(
SwapYZ(vert1.Position * 0.01f),
SwapYZAndNormalize((FVector)vert1.Normal),
SwapYZAndNormalize((Vector4)vert1.Tangent)
);
VertexPositionNormalTangent v2 = new(
var v2 = new VertexPositionNormalTangent(
SwapYZ(vert2.Position * 0.01f),
SwapYZAndNormalize((FVector)vert2.Normal),
SwapYZAndNormalize((Vector4)vert2.Tangent)
);
VertexPositionNormalTangent v3 = new(
var v3 = new VertexPositionNormalTangent(
SwapYZ(vert3.Position * 0.01f),
SwapYZAndNormalize((FVector)vert3.Normal),
SwapYZAndNormalize((Vector4)vert3.Tangent)
@@ -209,9 +230,11 @@ public class MonoGltf
)
{
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);
}
@@ -226,6 +249,7 @@ public class MonoGltf
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]]);
+1
View File
@@ -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",
@@ -1,5 +1,5 @@
import { OrbitControls } from "@react-three/drei";
import { useLoader } from "@react-three/fiber";
import { Canvas, useLoader } from "@react-three/fiber";
import { GLTFLoader } from "three-stdlib";
import styles from "./index.module.css";
@@ -7,21 +7,24 @@ export function TankopediaSandbox() {
return (
<div className={styles.wrapper}>
<div className={styles.sandbox}>
{/*<Canvas
scene={{ fog: new FogExp2("black", 0.05) }}
<Canvas
// scene={{ fog: new FogExp2("black", 0.05) }}
shadows
className={styles.canvas}
frameloop="demand"
>
{!import.meta.env.SSR && <Content />}
</Canvas>*/}
</Canvas>
</div>
</div>
);
}
function Content() {
const gltf = useLoader(GLTFLoader, "/amogos.glb");
const gltf = useLoader(
GLTFLoader,
"/models/tanks/TankEntity.A128_Concept_1b/hull.glb",
);
gltf.scene.traverse((child) => {
child.castShadow = true;
@@ -29,12 +32,12 @@ function Content() {
return (
<>
<group scale={4}>
<group>
<primitive object={gltf.scene} />
</group>
<ambientLight />
<pointLight castShadow position={[3, 10, 5]} intensity={1000} />
<pointLight castShadow position={[3, 10, 5]} intensity={2 ** 14} />
<ambientLight intensity={1} />
<mesh receiveShadow position={[0, -1, 0]} rotation={[-Math.PI / 2, 0, 0]}>
<planeGeometry args={[2 ** 8, 2 ** 8]} />
@@ -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";
@@ -35,8 +36,8 @@ export async function GET({
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 bytes = game!.tankPart(tag, params.part);
const array = new Uint8Array(bytes);
return Response.json(asd);
return new Response(array);
}