feat: armor chunking

This commit is contained in:
TrèsAbhi committed 2024-01-09 13:07:27 -06:00
1 parent 1f378137f1
commit f9ccb06d1f
5 files changed
+122 -186

No files matched your search

Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
import { NodeIO } from '@gltf-transform/core';
import { readdir } from 'fs/promises';
import { readdir, writeFile } from 'fs/promises';
import { extractArmor } from '../../src/core/blitz/extractArmor';
import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL';
import { toUniqueId } from '../../src/core/blitz/toUniqueId';
@@ -28,15 +28,15 @@ export async function buildTankArmors() {
const tank = tanks.root[tankKey];
const id = toUniqueId(nation, tank.id);
// if (id !== 7297) continue; // 60tp
if (id !== 5137) continue; // tiger ii
if (id !== 7297) continue; // 60tp
// if (id !== 5137) continue; // tiger ii
// if (id !== 24609) continue; // concept 1b
console.log(`Building armor ${id} @ ${nation}/${tankKey}`);
const armor = await extractArmor(DATA, `${nation}-${tankKey}`);
const model = await extractArmor(DATA, `${nation}-${tankKey}`);
await nodeIO.write('temp/armor/index.gltf', armor);
writeFile(`temp/armor/${id}.glb`, await nodeIO.writeBinary(model));
// writeFile('test.sc2.json', JSON.stringify(sc2, null, 2));
}
+73 -118
View File
@@ -1,47 +1,20 @@
import { Document, Node, Scene } from '@gltf-transform/core';
import { range, times } from 'lodash';
import {
Matrix4,
Quaternion,
Vector3,
Vector3Tuple,
Vector4Tuple,
} from 'three';
import { Accessor, Document, Node, Scene } from '@gltf-transform/core';
import { times } from 'lodash';
import { Matrix4, Quaternion, Vector3, Vector4Tuple } from 'three';
import { Hierarchy, Sc2Stream } from '../streams/sc2';
import { ScgStream, vertexAttributeVectorSizes } from '../streams/scg';
import { ScgStream } from '../streams/scg';
import { VertexAttribute } from '../streams/scpg';
import {
vertexAttributeGLTFName,
vertexAttributeGltfVectorSizes,
} from './extractModel';
import { readDVPLFile } from './readDVPLFile';
Buffer.prototype.toJSON = function () {
return [...(this as Buffer)].map((int) => int.toString(16)).join('');
};
const MAX_FLOAT32 = 2 ** 127 * (2 - 2 ** -23);
const vertexAttributeGLTFName: Partial<Record<VertexAttribute, string>> = {
[VertexAttribute.VERTEX]: 'POSITION',
[VertexAttribute.NORMAL]: 'NORMAL',
[VertexAttribute.COLOR]: 'COLOR_0',
[VertexAttribute.TEXCOORD0]: 'TEXCOORD_0',
[VertexAttribute.TEXCOORD1]: 'TEXCOORD_0',
[VertexAttribute.TEXCOORD2]: 'TEXCOORD_0',
[VertexAttribute.TEXCOORD3]: 'TEXCOORD_0',
[VertexAttribute.TANGENT]: 'TANGENT',
[VertexAttribute.JOINTINDEX]: 'JOINT_0',
[VertexAttribute.JOINTWEIGHT]: 'WEIGHT_0',
};
const vertexAttributeGltfVectorSizes = {
...vertexAttributeVectorSizes,
[VertexAttribute.TANGENT]: 4,
} as const;
export async function extractArmor(
data: string,
fileName: string,
baseColor?: Vector3Tuple,
) {
export async function extractArmor(data: string, fileName: string) {
const sc2Path = `${data}/3d/Tanks/CollisionMeshes/${fileName}.sc2.dvpl`;
const scgPath = `${data}/3d/Tanks/CollisionMeshes/${fileName}.scg.dvpl`;
const sc2 = new Sc2Stream(await readDVPLFile(sc2Path)).sc2();
@@ -88,58 +61,7 @@ export async function extractArmor(
}
case 'RenderComponent': {
let minLODDistanceBatchId: undefined | number;
if (component['rc.renderObj']['rb0.lodIndex'] === -1) {
minLODDistanceBatchId = 0;
} else {
const lodList = components.find(
(component) => component['comp.typename'] === 'LodComponent',
);
if (
lodList === undefined ||
lodList['comp.typename'] !== 'LodComponent' // type annotation hack
) {
throw new SyntaxError('Missing LodComponent');
}
let minLODDistance = Infinity;
let isFirstMaxFloat = true;
let lodIndexStreak = 0;
let lodIndexStreakIndex: undefined | number = undefined;
range(component['rc.renderObj']['ro.batchCount']).forEach(
(id) => {
const lodIndex =
component['rc.renderObj'][`rb${id}.lodIndex`];
const lodDistance =
lodList['lc.loddist'][`distance${lodIndex}`];
if (lodIndexStreakIndex === lodIndex) {
lodIndexStreak++;
} else {
lodIndexStreak = 0;
lodIndexStreakIndex = lodIndex;
}
if (
lodDistance < minLODDistance ||
(lodDistance === minLODDistance && lodIndexStreak > 0) ||
(lodDistance === MAX_FLOAT32 && isFirstMaxFloat)
) {
if (lodDistance === MAX_FLOAT32) isFirstMaxFloat = false;
minLODDistance = lodDistance;
minLODDistanceBatchId = id;
}
},
);
}
const batch =
component['rc.renderObj']['ro.batches'][
minLODDistanceBatchId!.toString().padStart(4, '0')
];
const batch = component['rc.renderObj']['ro.batches']['0000'];
const polygonGroup = scg.get(batch['rb.datasource']);
if (!polygonGroup) {
@@ -150,52 +72,85 @@ export async function extractArmor(
break;
}
const indicesAccessor = document
.createAccessor()
.setType('SCALAR')
.setArray(new Uint16Array(polygonGroup.indices))
.setBuffer(buffer);
const primitive = document
.createPrimitive()
.setIndices(indicesAccessor);
const attributes: Partial<Record<VertexAttribute, number[][]>> = {};
const indexedAttributes: VertexAttribute[] = [];
const hardJointIndices = new Set<number>();
const vertexHardJointIndices = new Map<number, number>();
const attributes = new Map<VertexAttribute, number[][]>();
polygonGroup.vertices.forEach((vertex) => {
polygonGroup.vertices.forEach((vertex, index) => {
vertex.forEach(({ attribute, value }) => {
if (attributes[attribute] === undefined) {
attributes[attribute] = [];
indexedAttributes.push(attribute);
if (!attributes.has(attribute)) {
attributes.set(attribute, []);
}
attributes[attribute]!.push(value);
attributes.get(attribute)!.push(value);
if (attribute === VertexAttribute.HARD_JOINTINDEX) {
hardJointIndices.add(value[0]);
vertexHardJointIndices.set(index, value[0]);
}
});
});
if (indexedAttributes.includes(VertexAttribute.TANGENT)) {
attributes[VertexAttribute.TANGENT] = attributes[
VertexAttribute.TANGENT
]!.map((tangent) => [...tangent, 1]);
if (attributes.has(VertexAttribute.TANGENT)) {
attributes.set(
VertexAttribute.TANGENT,
attributes
.get(VertexAttribute.TANGENT)!
.map((tangent) => [...tangent, 1]),
);
}
indexedAttributes.forEach((attribute) => {
const name = vertexAttributeGLTFName[attribute];
if (!name || primitive.getAttribute(name)) return;
const vertexSize = vertexAttributeGltfVectorSizes[attribute];
const accessors = new Map<string, Accessor>();
const attributeAccessor = document
attributes.forEach((value, attribute) => {
const name = vertexAttributeGLTFName[attribute];
if (!name || accessors.has(name)) return;
const vertexSize = vertexAttributeGltfVectorSizes[attribute];
const accessor = document
.createAccessor(name)
.setType(vertexSize === 1 ? 'SCALAR' : `VEC${vertexSize}`)
.setArray(new Float32Array(attributes[attribute]!.flat()))
.setArray(new Float32Array(value.flat()))
.setBuffer(buffer);
primitive.setAttribute(name, attributeAccessor);
accessors.set(name, accessor);
});
const mesh = document
.createMesh(batch['##name'])
.addPrimitive(primitive);
hardJointIndices.forEach((hardJointIndex) => {
const hardJointNode = document.createNode(
`armor_${hardJointIndex}`,
);
const mesh = document.createMesh(batch['##name']);
const indicesAccessor = document
.createAccessor()
.setType('SCALAR')
.setArray(
new Uint16Array(
polygonGroup.indices.filter((index) => {
if (!vertexHardJointIndices.has(index)) {
throw new Error(
`Missing vertex hard joint index for index ${index}`,
);
}
node.setMesh(mesh);
return (
vertexHardJointIndices.get(index)! === hardJointIndex
);
}),
),
)
.setBuffer(buffer);
const primitive = document
.createPrimitive()
.setIndices(indicesAccessor);
accessors.forEach((accessor, name) => {
primitive.setAttribute(name, accessor);
});
mesh.addPrimitive(primitive);
hardJointNode.setMesh(mesh);
node.addChild(hardJointNode);
});
break;
}
+28 -27
View File
@@ -16,28 +16,28 @@ import { readDVPLFile } from './readDVPLFile';
const MAX_FLOAT32 = 2 ** 127 * (2 - 2 ** -23);
const vertexAttributeGLTFName: Partial<Record<VertexAttribute, string>> = {
[VertexAttribute.VERTEX]: 'POSITION',
[VertexAttribute.NORMAL]: 'NORMAL',
[VertexAttribute.COLOR]: 'COLOR_0',
[VertexAttribute.TEXCOORD0]: 'TEXCOORD_0',
[VertexAttribute.TEXCOORD1]: 'TEXCOORD_0',
[VertexAttribute.TEXCOORD2]: 'TEXCOORD_0',
[VertexAttribute.TEXCOORD3]: 'TEXCOORD_0',
[VertexAttribute.TANGENT]: 'TANGENT',
[VertexAttribute.JOINTINDEX]: 'JOINT_0',
[VertexAttribute.JOINTWEIGHT]: 'WEIGHT_0',
};
export const vertexAttributeGLTFName: Partial<Record<VertexAttribute, string>> =
{
[VertexAttribute.VERTEX]: 'POSITION',
[VertexAttribute.NORMAL]: 'NORMAL',
[VertexAttribute.TEXCOORD0]: 'TEXCOORD_0',
[VertexAttribute.TEXCOORD1]: 'TEXCOORD_0',
[VertexAttribute.TEXCOORD2]: 'TEXCOORD_0',
[VertexAttribute.TEXCOORD3]: 'TEXCOORD_0',
[VertexAttribute.TANGENT]: 'TANGENT',
[VertexAttribute.JOINTINDEX]: 'JOINT_0',
[VertexAttribute.JOINTWEIGHT]: 'WEIGHT_0',
};
const vertexAttributeGltfVectorSizes = {
export const vertexAttributeGltfVectorSizes = {
...vertexAttributeVectorSizes,
[VertexAttribute.TANGENT]: 4,
} as const;
const omitMeshNames = {
start: [], //['chassis_chassis_', 'chassis_track_crash_', 'HP_'],
end: [], //['_POINT'],
start: ['chassis_chassis_', 'chassis_track_crash_', 'HP_'],
end: ['_POINT'],
};
export async function extractModel(
@@ -283,27 +283,28 @@ export async function extractModel(
.createPrimitive()
.setIndices(indicesAccessor)
.setMaterial(material);
const attributes: Partial<Record<VertexAttribute, number[][]>> = {};
const indexedAttributes: VertexAttribute[] = [];
const attributes = new Map<VertexAttribute, number[][]>();
polygonGroup.vertices.forEach((vertex) => {
vertex.forEach(({ attribute, value }) => {
if (attributes[attribute] === undefined) {
attributes[attribute] = [];
indexedAttributes.push(attribute);
if (!attributes.has(attribute)) {
attributes.set(attribute, []);
}
attributes[attribute]!.push(value);
attributes.get(attribute)!.push(value);
});
});
if (indexedAttributes.includes(VertexAttribute.TANGENT)) {
attributes[VertexAttribute.TANGENT] = attributes[
VertexAttribute.TANGENT
]!.map((tangent) => [...tangent, 1]);
if (attributes.has(VertexAttribute.TANGENT)) {
attributes.set(
VertexAttribute.TANGENT,
attributes
.get(VertexAttribute.TANGENT)!
.map((tangent) => [...tangent, 1]),
);
}
indexedAttributes.forEach((attribute) => {
attributes.forEach((value, attribute) => {
const name = vertexAttributeGLTFName[attribute];
if (!name || primitive.getAttribute(name)) return;
const vertexSize = vertexAttributeGltfVectorSizes[attribute];
@@ -311,7 +312,7 @@ export async function extractModel(
const attributeAccessor = document
.createAccessor(name)
.setType(vertexSize === 1 ? 'SCALAR' : `VEC${vertexSize}`)
.setArray(new Float32Array(attributes[attribute]!.flat()))
.setArray(new Float32Array(value.flat()))
.setBuffer(buffer);
primitive.setAttribute(name, attributeAccessor);
});
+16 -36
View File
@@ -23,6 +23,10 @@ interface BlitzkriegPolygonGroup {
indices: number[];
}
const vertexAttributesArray = Object.values(VertexAttribute).filter(
(value) => typeof value === 'number',
);
export const vertexAttributeVectorSizes = {
[VertexAttribute.VERTEX]: 3,
[VertexAttribute.NORMAL]: 3,
@@ -66,24 +70,6 @@ export class ScgStream extends ScpgStream {
polygonGroupsRaw.push(polygonGroupRaw);
}
// writeFile(
// 'test.scg.txt',
// polygonGroupsRaw
// .map((polygonGroupRaw) => {
// const floats: number[] = [];
// const stream = new BufferStream(polygonGroupRaw.vertices);
// while (stream.readRemainingLength()) {
// floats.push(stream.float());
// }
// return `format: ${polygonGroupRaw.vertexFormat.toString(
// 2,
// )}\n\n${floats.join('\n')}`;
// })
// .join('\n\n#####\n\n'),
// );
polygonGroupsRaw.forEach((polygonGroupRaw) => {
const indicesStream = new ScpgStream(polygonGroupRaw.indices);
const indices: number[] = [];
@@ -112,14 +98,10 @@ export class ScgStream extends ScpgStream {
}
for (let index = 0; index < polygonGroupRaw.vertexCount; index++) {
vertices[index] = format.map((attribute) => {
return {
attribute,
value: verticesStream.vectorN(
vertexAttributeVectorSizes[attribute],
),
};
});
vertices[index] = format.map((attribute) => ({
attribute,
value: verticesStream.vectorN(vertexAttributeVectorSizes[attribute]),
}));
}
if (verticesStream.readRemainingLength() !== 0) {
@@ -139,17 +121,15 @@ export class ScgStream extends ScpgStream {
const format: VertexAttribute[] = [];
let stride = 0;
Object.values(VertexAttribute)
.filter((value) => typeof value === 'number')
.forEach((attributeUntyped) => {
const attribute = attributeUntyped as VertexAttribute;
const mask = 1 << attribute;
vertexAttributesArray.forEach((attributeUntyped) => {
const attribute = attributeUntyped as VertexAttribute;
const mask = 1 << attribute;
if (formatInt & mask) {
format.push(attribute);
stride += vertexAttributeVectorSizes[attribute] * 4;
}
});
if (formatInt & mask) {
format.push(attribute);
stride += vertexAttributeVectorSizes[attribute] * 4;
}
});
return { format, stride };
}