diff --git a/public/test/5137.glb b/public/test/5137.glb index 53b78306d..f2622dfed 100644 Binary files a/public/test/5137.glb and b/public/test/5137.glb differ diff --git a/scripts/buildAssets/tankArmors.ts b/scripts/buildAssets/tankArmors.ts index e80b9a36c..fc36bc487 100644 --- a/scripts/buildAssets/tankArmors.ts +++ b/scripts/buildAssets/tankArmors.ts @@ -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)); } diff --git a/src/core/blitz/extractArmor.ts b/src/core/blitz/extractArmor.ts index 7be3c4c23..5f33da5a4 100644 --- a/src/core/blitz/extractArmor.ts +++ b/src/core/blitz/extractArmor.ts @@ -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> = { - [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> = {}; - const indexedAttributes: VertexAttribute[] = []; + const hardJointIndices = new Set(); + const vertexHardJointIndices = new Map(); + const attributes = new Map(); - 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(); - 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; } diff --git a/src/core/blitz/extractModel.ts b/src/core/blitz/extractModel.ts index 403af4d92..cf1919912 100644 --- a/src/core/blitz/extractModel.ts +++ b/src/core/blitz/extractModel.ts @@ -16,28 +16,28 @@ import { readDVPLFile } from './readDVPLFile'; const MAX_FLOAT32 = 2 ** 127 * (2 - 2 ** -23); -const vertexAttributeGLTFName: Partial> = { - [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> = + { + [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> = {}; - const indexedAttributes: VertexAttribute[] = []; + const attributes = new Map(); 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); }); diff --git a/src/core/streams/scg.ts b/src/core/streams/scg.ts index cbc693dcc..ecbe7b1f6 100644 --- a/src/core/streams/scg.ts +++ b/src/core/streams/scg.ts @@ -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 }; }