feat: dds decompression

This commit is contained in:
TrèsAbhi committed 2023-12-25 20:50:19 -06:00
1 parent 6361b09129
commit 4ffb7cac7c
6 files changed
+164 -17

No files matched your search

+2 -2
View File
@@ -519,9 +519,9 @@ if (allTargets || targets?.includes('tankModels')) {
const nationVehicleId = tank.id;
const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1;
// if (id !== 15697) continue; // chieftain TODO: investigate vertices stream over read
if (id !== 15697) continue; // chieftain TODO: investigate vertices stream over read
// if (id !== 4417) continue; // amx m4 mle
if (id !== 7297) continue; // 60tp
// if (id !== 7297) continue; // 60tp
console.log(`Building model ${id} @ ${nation}/${tankIndex}`);
const parameters = await readYAMLDVPL<TankParameters>(
+75
View File
@@ -0,0 +1,75 @@
import { times } from 'lodash';
import { Vector4Tuple } from 'three';
import { BinaryStream } from './binary';
const MAX_UINT5 = 2 ** 5 - 1;
const MAX_UINT6 = 2 ** 6 - 1;
type InterpolationKey = 0b00 | 0b01 | 0b10 | 0b11;
export class Bc1Stream extends BinaryStream {
color() {
const r = this.consume(5) / MAX_UINT5;
const g = this.consume(6) / MAX_UINT6;
const b = this.consume(5) / MAX_UINT5;
return [r, g, b, 1];
}
interpolation() {
const color0 = this.color();
const color1 = this.color();
const color2 = color0.map(
(channel0, index) => (channel0 + color1[index]) / 2,
);
const color3 = [...color2.slice(0, -1), 0];
return {
[0b00]: color0,
[0b01]: color1,
[0b10]: color2,
[0b11]: color3,
};
}
blockFloat() {
const interpolation = this.interpolation();
return times(16, () => interpolation[this.consume(2) as InterpolationKey]);
}
block() {
return this.blockFloat().map(
(pixel) =>
pixel.map((channel) => Math.round(channel * 255)) as Vector4Tuple,
);
}
blocks(count: number) {
return times(count, () => this.block());
}
bc1(width: number, height: number) {
if (width % 4 !== 0 || height % 4 !== 0) {
throw new RangeError('Width and height must be divisible by 4');
}
const count = (width / 4) * (height / 4);
const blocks = this.blocks(count);
const image = Buffer.alloc(width * height * 4);
let pixelIndex = 0;
for (let y = 0; y < width; y++) {
const yOffset = Math.floor(y / 4) * (width / 4);
for (let x = 0; x < height; x++) {
const xOffset = Math.floor(x / 4);
const block = blocks[yOffset + xOffset];
const pixel = block[Math.floor(y % 4) * 4 + Math.floor(x % 4)];
pixel.forEach((channel) => (image[pixelIndex++] = channel));
}
}
return image;
}
}
+35
View File
@@ -0,0 +1,35 @@
type Bit = 0 | 1;
export class BinaryStream {
public index = 0;
bits: Bit[] = [];
constructor(buffer: Buffer) {
for (let bufferIndex = 0; bufferIndex < buffer.length; bufferIndex++) {
const byte = buffer[bufferIndex];
for (let byteIndex = 7; byteIndex >= 0; byteIndex--) {
this.bits.push(((byte >> byteIndex) & 1) as Bit);
}
}
}
skip(size: number) {
this.index += size;
}
read(size: number) {
if (this.index + size > this.bits.length) {
throw new RangeError('Cannot read beyond end of stream');
}
return this.bits
.slice(this.index, this.index + size)
.reduce((accumulator, bit) => (accumulator << 1) | bit, 0 as number);
}
consume(size: number) {
const value = this.read(size);
this.skip(size);
return value;
}
}
+47 -10
View File
@@ -1,5 +1,8 @@
import { writeFileSync } from 'fs';
import { times } from 'lodash';
import sharp from 'sharp';
import { readDVPLFile } from '../blitz/readDVPLFile';
import { Bc1Stream } from './bc1';
import { WindowsStream } from './windows';
enum DxgiFormat {
@@ -127,7 +130,7 @@ enum DxgiFormat {
FORCE_UINT = 0xffffffff,
}
enum D3d10ResourceDimension {
enum DdsDimension {
UNKNOWN = 0,
BUFFER = 1,
TEXTURE1D = 2,
@@ -172,13 +175,48 @@ enum DdsCaps2 {
}
export class DdsStream extends WindowsStream {
dds() {
const magicNumber = this.magicNumber();
async dds() {
this.magicNumber();
const header = this.header();
const headerDxt10 =
header.pf.flags === DdpfFlags.FOURCC && header.pf.fourCC === 0x30315844
? this.headerDxt10()
: undefined;
if (
header.pf.flags !== DdpfFlags.FOURCC ||
header.pf.fourCC !== 0x30315844 // "DX10"
) {
throw new RangeError('No other formats other than DX10 are supported');
}
const headerDxt10 = this.headerDxt10();
switch (headerDxt10.dxgiFormat) {
case DxgiFormat.BC1_UNORM_SRGB: {
const image = new Bc1Stream(
this.consume((header.width / 4) * (header.height / 4) * 8),
).bc1(header.width, header.height);
writeFileSync(
'test.png',
await sharp(image, {
raw: {
channels: 4,
width: header.width,
height: header.height,
},
})
.png()
.toBuffer(),
);
break;
}
default:
throw new TypeError(
`Unsupported DXGI format: ${DxgiFormat[headerDxt10.dxgiFormat]} (${
headerDxt10.dxgiFormat
})`,
);
}
}
magicNumber() {
@@ -201,7 +239,6 @@ export class DdsStream extends WindowsStream {
caps3: this.dword(),
caps4: this.dword(),
reserved2: times(1, () => this.dword()),
headerDxt10: undefined,
};
}
@@ -221,7 +258,7 @@ export class DdsStream extends WindowsStream {
headerDxt10() {
return {
dxgiFormat: this.uint() as DxgiFormat,
resourceDimension: this.uint() as D3d10ResourceDimension,
resourceDimension: this.uint() as DdsDimension,
miscFlag: this.uint(),
arraySize: this.uint(),
miscFlags2: this.uint(),
@@ -231,6 +268,6 @@ export class DdsStream extends WindowsStream {
new DdsStream(
await readDVPLFile(
'C:/Program Files (x86)/Steam/steamapps/common/World of Tanks Blitz/Data/3d/Tanks/German/images_pbr/E_100_BC.dx11.dds.dvpl',
'C:/Program Files (x86)/Steam/steamapps/common/World of Tanks Blitz/Data/3d/Tanks/GB/images_pbr/Chieftain_Mk6_BC.dx11.dds.dvpl',
),
).dds();
+3 -3
View File
@@ -1,12 +1,12 @@
export class PrimitiveStream {
public index = 0;
constructor(public buffer: Buffer) {}
constructor(public stream: Buffer) {}
skip(size: number) {
this.index += size;
}
read(size: number) {
return this.buffer.subarray(this.index, this.index + size);
return this.stream.subarray(this.index, this.index + size);
}
consume(size: number) {
const subarray = this.read(size);
@@ -19,7 +19,7 @@ export class PrimitiveStream {
return this.read(Number.POSITIVE_INFINITY);
}
readRemainingLength() {
return this.buffer.length - this.index;
return this.stream.length - this.index;
}
consumeRemaining() {
return this.consume(Number.POSITIVE_INFINITY);
+2 -2
View File
@@ -84,9 +84,9 @@ export class ScgStream extends ScpgStream {
ScgStream.vertexStride(polygonGroupRaw.vertexFormat) *
polygonGroupRaw.vertexCount;
if (verticesStream.buffer.length !== stride) {
if (verticesStream.stream.length !== stride) {
console.warn(
`Vertex stride mismatch; expected ${stride}, got ${verticesStream.buffer.length}`,
`Vertex stride mismatch; expected ${stride}, got ${verticesStream.stream.length}`,
);
return;