fix: use base color map

This commit is contained in:
TrèsAbhi committed 2025-09-05 20:45:04 -05:00
1 parent 1598ae973f
commit 95fa5bc1a5
2 files changed
+84 -81

No files matched your search

+23 -19
View File
@@ -1,44 +1,48 @@
import { toUniqueId } from '@blitzkit/core'; import { toUniqueId } from "@blitzkit/core";
import { NodeIO } from '@gltf-transform/core'; import { NodeIO } from "@gltf-transform/core";
import { ALL_EXTENSIONS } from '@gltf-transform/extensions'; import { ALL_EXTENSIONS } from "@gltf-transform/extensions";
import { readdir } from 'fs/promises'; import { readdir } from "fs/promises";
import { extractModel } from '../core/blitz/extractModel'; import { extractModel } from "../core/blitz/extractModel";
import { readXMLDVPL } from '../core/blitz/readXMLDVPL'; import { readXMLDVPL } from "../core/blitz/readXMLDVPL";
import { readYAMLDVPL } from '../core/blitz/readYAMLDVPL'; import { readYAMLDVPL } from "../core/blitz/readYAMLDVPL";
import { commitAssets } from '../core/github/commitAssets'; import { commitAssets } from "../core/github/commitAssets";
import { FileChange } from '../core/github/commitMultipleFiles'; import { FileChange } from "../core/github/commitMultipleFiles";
import { DATA } from './constants'; import { DATA } from "./constants";
import { VehicleDefinitionList } from './definitions'; import { VehicleDefinitionList } from "./definitions";
import { TankParameters } from './tankIcons'; import { TankParameters } from "./tankIcons";
export async function tankModels() { export async function tankModels() {
console.log('Building tank models...'); console.log("Building tank models...");
const nodeIO = new NodeIO().registerExtensions(ALL_EXTENSIONS); const nodeIO = new NodeIO().registerExtensions(ALL_EXTENSIONS);
const nations = await readdir(`${DATA}/XML/item_defs/vehicles`).then( const nations = await readdir(`${DATA}/XML/item_defs/vehicles`).then(
(nations) => nations.filter((nation) => nation !== 'common'), (nations) => nations.filter((nation) => nation !== "common")
); );
for (const nationIndex in nations) { for (const nationIndex in nations) {
const nation = nations[nationIndex]; const nation = nations[nationIndex];
const changes: FileChange[] = []; const changes: FileChange[] = [];
const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>( const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>(
`${DATA}/XML/item_defs/vehicles/${nation}/list.xml`, `${DATA}/XML/item_defs/vehicles/${nation}/list.xml`
); );
const entries = Object.entries(tanks.root);
console.log(`Building models for ${nation}`); console.log(`Building models for ${nation}`);
for (const [tankKey, tank] of Object.entries(tanks.root)) { let index = 0;
if (tankKey.includes('tutorial_bot')) continue; for (const [tankKey, tank] of entries) {
console.log(` ${++index} / ${entries.length} : ${tankKey}`);
if (tankKey.includes("tutorial_bot")) continue;
const id = toUniqueId(nation, tank.id); const id = toUniqueId(nation, tank.id);
const parameters = await readYAMLDVPL<TankParameters>( const parameters = await readYAMLDVPL<TankParameters>(
`${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`, `${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`
); );
const model = await extractModel( const model = await extractModel(
DATA, DATA,
parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, ''), parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, "")
); );
changes.push({ changes.push({
@@ -4,35 +4,34 @@ import {
ScgReadStream, ScgReadStream,
Textures, Textures,
VertexAttribute, VertexAttribute,
} from '@blitzkit/core'; } from "@blitzkit/core";
import { Document, Material, Node, Scene } from '@gltf-transform/core'; import { Document, Material, Node, Scene } from "@gltf-transform/core";
import { dedup, prune } from '@gltf-transform/functions'; import { dedup, prune } from "@gltf-transform/functions";
import { times } from 'lodash-es'; import { times } from "lodash-es";
import { dirname } from 'path'; import { dirname } from "path";
import { Matrix4, Quaternion, Vector3, Vector4Tuple } from 'three'; import { readDVPLFile } from "../readDVPLFile";
import { readDVPLFile } from '../readDVPLFile'; import { readTexture } from "../readTexture";
import { readTexture } from '../readTexture'; import { TextureMutation } from "../readTexture/constants";
import { TextureMutation } from '../readTexture/constants';
import { import {
vertexAttributeGLTFName, vertexAttributeGLTFName,
vertexAttributeGltfVectorSizes, vertexAttributeGltfVectorSizes,
} from './constants'; } from "./constants";
const ERROR_ON_UNKNOWN_COMPONENT = false; const ERROR_ON_UNKNOWN_COMPONENT = false;
const omitMeshNames = { const omitMeshNames = {
start: ['chassis_chassis_', 'chassis_track_crash_', 'HP_'], start: ["chassis_chassis_", "chassis_track_crash_", "HP_"],
end: ['_POINT'], end: ["_POINT"],
}; };
export async function extractModel(data: string, path: string) { export async function extractModel(data: string, path: string) {
const sc2Path = `${data}/3d/${path}.sc2`; const sc2Path = `${data}/3d/${path}.sc2`;
const scgPath = `${data}/3d/${path}.scg`; const scgPath = `${data}/3d/${path}.scg`;
const sc2 = new Sc2ReadStream( const sc2 = new Sc2ReadStream(
(await readDVPLFile(sc2Path)).buffer as ArrayBuffer, (await readDVPLFile(sc2Path)).buffer as ArrayBuffer
).sc2(); ).sc2();
const scg = new ScgReadStream( const scg = new ScgReadStream(
(await readDVPLFile(scgPath)).buffer as ArrayBuffer, (await readDVPLFile(scgPath)).buffer as ArrayBuffer
).scg(); ).scg();
const document = new Document(); const document = new Document();
const scene = document.createScene(); const scene = document.createScene();
@@ -41,8 +40,8 @@ export async function extractModel(data: string, path: string) {
// create materials // create materials
await Promise.all( await Promise.all(
sc2['#dataNodes'].map(async (node) => { sc2["#dataNodes"].map(async (node) => {
const id = new DataView(node['#id']).getBigUint64(0, true); const id = new DataView(node["#id"]).getBigUint64(0, true);
if (node.parentMaterialKey !== undefined) { if (node.parentMaterialKey !== undefined) {
/** /**
@@ -58,7 +57,7 @@ export async function extractModel(data: string, path: string) {
if (node.textures) { if (node.textures) {
textures = node.textures; textures = node.textures;
} else if (typeof node.configCount === 'number') { } else if (typeof node.configCount === "number") {
textures = node.configArchive_0.textures; textures = node.configArchive_0.textures;
} }
@@ -66,26 +65,26 @@ export async function extractModel(data: string, path: string) {
material.setBaseColorTexture( material.setBaseColorTexture(
document document
.createTexture(node.materialName) .createTexture(node.materialName)
.setMimeType('image/jpeg') .setMimeType("image/jpeg")
.setImage( .setImage(
await readTexture( await readTexture(
`${data}/3d/${dirname(path)}/${textures.albedo}`, `${data}/3d/${dirname(path)}/${textures.baseColorMap ?? textures.albedo}`,
TextureMutation.Albedo, TextureMutation.Albedo
), )
), )
); );
if (textures.baseRMMap) { if (textures.baseRMMap) {
material.setMetallicRoughnessTexture( material.setMetallicRoughnessTexture(
document document
.createTexture(node.materialName) .createTexture(node.materialName)
.setMimeType('image/jpeg') .setMimeType("image/jpeg")
.setImage( .setImage(
await readTexture( await readTexture(
`${data}/3d/${dirname(path)}/${textures.baseRMMap}`, `${data}/3d/${dirname(path)}/${textures.baseRMMap}`,
TextureMutation.RoughnessMetallicness, TextureMutation.RoughnessMetallicness
), )
), )
); );
} }
@@ -95,15 +94,15 @@ export async function extractModel(data: string, path: string) {
material.setNormalTexture( material.setNormalTexture(
document document
.createTexture(node.materialName) .createTexture(node.materialName)
.setMimeType('image/jpeg') .setMimeType("image/jpeg")
.setImage( .setImage(
await readTexture( await readTexture(
`${data}/3d/${dirname(path)}/${ `${data}/3d/${dirname(path)}/${
textures.baseNormalMap ?? textures.normalmap textures.baseNormalMap ?? textures.normalmap
}`, }`,
isBase ? TextureMutation.Normal : undefined, isBase ? TextureMutation.Normal : undefined
), )
), )
); );
} }
@@ -111,31 +110,31 @@ export async function extractModel(data: string, path: string) {
material.setOcclusionTexture( material.setOcclusionTexture(
document document
.createTexture(node.materialName) .createTexture(node.materialName)
.setMimeType('image/jpeg') .setMimeType("image/jpeg")
.setImage( .setImage(
await readTexture( await readTexture(
`${data}/3d/${dirname(path)}/${textures.miscMap}`, `${data}/3d/${dirname(path)}/${textures.miscMap}`,
TextureMutation.Miscellaneous, TextureMutation.Miscellaneous
), )
), )
); );
} }
materials.set( materials.set(
new DataView(node['#id']).getBigUint64(0, true), new DataView(node["#id"]).getBigUint64(0, true),
material, material
); );
} }
}), })
); );
// replace children materials with parents // replace children materials with parents
materials.forEach((material, id) => { materials.forEach((material, id) => {
if (typeof material !== 'bigint') return; if (typeof material !== "bigint") return;
let resolvedMaterial: Material | bigint | undefined = material; let resolvedMaterial: Material | bigint | undefined = material;
while (typeof resolvedMaterial === 'bigint') { while (typeof resolvedMaterial === "bigint") {
const linkedParentMaterial = materials.get(resolvedMaterial); const linkedParentMaterial = materials.get(resolvedMaterial);
resolvedMaterial = linkedParentMaterial; resolvedMaterial = linkedParentMaterial;
@@ -155,36 +154,36 @@ export async function extractModel(data: string, path: string) {
const node = document.createNode(hierarchy.name); const node = document.createNode(hierarchy.name);
const components = times( const components = times(
hierarchy.components.count, hierarchy.components.count,
(index) => hierarchy.components[index.toString().padStart(4, '0')], (index) => hierarchy.components[index.toString().padStart(4, "0")]
); );
components.forEach((component) => { components.forEach((component) => {
switch (component['comp.typename']) { switch (component["comp.typename"]) {
case 'LodComponent': case "LodComponent":
// found and used later by transform component // found and used later by transform component
// TODO: lod component always shows up before transform component; cache it before parsing transform // TODO: lod component always shows up before transform component; cache it before parsing transform
break; break;
case 'TransformComponent': { case "TransformComponent": {
node.setTranslation(component['tc.localTranslation']); node.setTranslation(component["tc.localTranslation"]);
node.setRotation(component['tc.localRotation']); node.setRotation(component["tc.localRotation"]);
node.setScale(component['tc.localScale']); node.setScale(component["tc.localScale"]);
break; break;
} }
case 'RenderComponent': { case "RenderComponent": {
const renderObject = component['rc.renderObj']; const renderObject = component["rc.renderObj"];
times(renderObject['ro.batchCount'], (batchIndex): void => { times(renderObject["ro.batchCount"], (batchIndex): void => {
const lodIndex = renderObject[`rb${batchIndex}.lodIndex`]; const lodIndex = renderObject[`rb${batchIndex}.lodIndex`];
if (lodIndex !== 0) return; if (lodIndex !== 0) return;
const batchKey = batchIndex.toString().padStart(4, '0'); const batchKey = batchIndex.toString().padStart(4, "0");
const batch = renderObject['ro.batches'][batchKey]; const batch = renderObject["ro.batches"][batchKey];
const material = materials.get(batch['rb.nmatname']); const material = materials.get(batch["rb.nmatname"]);
const polygonGroup = scg.get(batch['rb.datasource']); const polygonGroup = scg.get(batch["rb.datasource"]);
if (!(material instanceof Material)) { if (!(material instanceof Material)) {
// probably shadow material // probably shadow material
@@ -192,14 +191,14 @@ export async function extractModel(data: string, path: string) {
} }
if (polygonGroup === undefined) { if (polygonGroup === undefined) {
throw new Error( throw new Error(
`Missing polygon group ${batch['rb.datasource']}`, `Missing polygon group ${batch["rb.datasource"]}`
); );
} }
const lodNode = document.createNode(batchKey); const lodNode = document.createNode(batchKey);
const indicesAccessor = document const indicesAccessor = document
.createAccessor() .createAccessor()
.setType('SCALAR') .setType("SCALAR")
.setArray(new Uint16Array(polygonGroup.indices)) .setArray(new Uint16Array(polygonGroup.indices))
.setBuffer(buffer); .setBuffer(buffer);
const primitive = document const primitive = document
@@ -233,7 +232,7 @@ export async function extractModel(data: string, path: string) {
const vertexSize = vertexAttributeGltfVectorSizes[attribute]; const vertexSize = vertexAttributeGltfVectorSizes[attribute];
const attributeAccessor = document const attributeAccessor = document
.createAccessor(name) .createAccessor(name)
.setType(vertexSize === 1 ? 'SCALAR' : `VEC${vertexSize}`) .setType(vertexSize === 1 ? "SCALAR" : `VEC${vertexSize}`)
.setArray(new Float32Array(value.flat())) .setArray(new Float32Array(value.flat()))
.setBuffer(buffer); .setBuffer(buffer);
@@ -241,7 +240,7 @@ export async function extractModel(data: string, path: string) {
}); });
const mesh = document const mesh = document
.createMesh(batch['##name']) .createMesh(batch["##name"])
.addPrimitive(primitive); .addPrimitive(primitive);
lodNode.setMesh(mesh); lodNode.setMesh(mesh);
node.addChild(lodNode); node.addChild(lodNode);
@@ -253,24 +252,24 @@ export async function extractModel(data: string, path: string) {
default: { default: {
if (ERROR_ON_UNKNOWN_COMPONENT) { if (ERROR_ON_UNKNOWN_COMPONENT) {
throw new Error( throw new Error(
`Unhandled component type: ${component['comp.typename']}`, `Unhandled component type: ${component["comp.typename"]}`
); );
} }
} }
} }
}); });
if (hierarchy['#hierarchy']) { if (hierarchy["#hierarchy"]) {
parseHierarchies(hierarchy['#hierarchy'], node); parseHierarchies(hierarchy["#hierarchy"], node);
} }
parent.addChild(node); parent.addChild(node);
}); });
} }
parseHierarchies(sc2['#hierarchy'], scene); parseHierarchies(sc2["#hierarchy"], scene);
scene.addChild(document.createNode('test')); scene.addChild(document.createNode("test"));
await document.transform(prune({ keepAttributes: true }), dedup()); await document.transform(prune({ keepAttributes: true }), dedup());