feat: dds stream

This commit is contained in:
TrèsAbhi committed 2023-12-26 20:02:01 -06:00
1 parent 2f650af0f7
commit cd546b274f
10 files changed
+178 -154

No files matched your search

+43 -39
View File
@@ -1,8 +1,6 @@
import { Document, Material, Node, Scene } from '@gltf-transform/core';
import { writeFileSync } from 'fs';
import { range, times } from 'lodash';
import { dirname } from 'path';
import sharp from 'sharp';
import { Matrix4, Quaternion, Vector3, Vector4Tuple } from 'three';
import { readTexture } from '../blitzkrieg/readTexture';
import { Hierarchy, Sc2Stream, Textures } from '../streams/sc2';
@@ -35,8 +33,6 @@ export async function extractModel(data: string, path: string) {
const buffer = document.createBuffer();
const materials = new Map<bigint, Material | bigint>();
writeFileSync('test.sc2.json', JSON.stringify(sc2, null, 2));
// create materials
await Promise.all(
sc2['#dataNodes'].map(async (node) => {
@@ -64,7 +60,7 @@ export async function extractModel(data: string, path: string) {
material.setBaseColorTexture(
document
.createTexture(node.materialName)
.setMimeType('image/jpg')
.setMimeType('image/png')
.setImage(
await readTexture(
`${data}/3d/${dirname(path)}/${textures.albedo}`,
@@ -76,24 +72,29 @@ export async function extractModel(data: string, path: string) {
material.setMetallicRoughnessTexture(
document
.createTexture(node.materialName)
.setMimeType('image/jpg')
.setMimeType('image/png')
.setImage(
await sharp(
await readTexture(
`${data}/3d/${dirname(path)}/${textures.baseRMMap}`,
),
)
.raw()
.toBuffer({ resolveWithObject: true })
.then(({ data, info }) => {
// fake it till you make it
for (let index = 0; index < data.length; index++) {
data[index] = 255 - data[index];
}
return sharp(data, { raw: info }).jpeg().toBuffer();
}),
await readTexture(
`${data}/3d/${dirname(path)}/${textures.baseRMMap}`,
),
),
// .setImage(
// await sharp(
// await readTexture(
// `${data}/3d/${dirname(path)}/${textures.baseRMMap}`,
// ),
// )
// .raw()
// .toBuffer({ resolveWithObject: true })
// .then(({ data, info }) => {
// // fake it till you make it
// for (let index = 0; index < data.length; index++) {
// data[index] = 255 - data[index];
// }
// return sharp(data, { raw: info }).jpeg().toBuffer();
// }),
// ),
);
}
@@ -101,24 +102,29 @@ export async function extractModel(data: string, path: string) {
material.setNormalTexture(
document
.createTexture(node.materialName)
.setMimeType('image/jpg')
.setMimeType('image/png')
.setImage(
await sharp(
await readTexture(
`${data}/3d/${dirname(path)}/${textures.baseNormalMap}`,
),
)
.raw()
.toBuffer({ resolveWithObject: true })
.then(({ data, info }) => {
// https://www.youtube.com/watch?v=CGys6CnnYSg
for (let index = 0; index < data.length; index += 3) {
data[index + 2] = 255 - data[index + 2];
}
return sharp(data, { raw: info }).jpeg().toBuffer();
}),
await readTexture(
`${data}/3d/${dirname(path)}/${textures.baseNormalMap}`,
),
),
// .setImage(
// await sharp(
// await readTexture(
// `${data}/3d/${dirname(path)}/${textures.baseNormalMap}`,
// ),
// )
// .raw()
// .toBuffer({ resolveWithObject: true })
// .then(({ data, info }) => {
// // https://www.youtube.com/watch?v=CGys6CnnYSg
// for (let index = 0; index < data.length; index += 3) {
// data[index + 2] = 255 - data[index + 2];
// }
// return sharp(data, { raw: info }).jpeg().toBuffer();
// }),
// ),
);
}
}
@@ -146,8 +152,6 @@ export async function extractModel(data: string, path: string) {
materials.set(id, resolvedMaterial);
});
writeFileSync('test.sc2.json', JSON.stringify(sc2, null, 2));
function parseHierarchies(hierarchies: Hierarchy[], parent: Scene | Node) {
hierarchies.forEach((hierarchy) => {
const node = document.createNode(hierarchy.name);
+20 -12
View File
@@ -1,21 +1,29 @@
import { existsSync } from 'fs';
import sharp from 'sharp';
import { readDVPLFile } from '../blitz/readDVPLFile';
import { ToJpgOptions, toJpg } from './toJpg';
import { DdsStream } from '../streams/dds';
import { ToJpgOptions } from './toJpg';
export async function readTexture(
path: string,
options?: Partial<Omit<ToJpgOptions, 'format'>>,
) {
const ddsTexturePath = path.replace('.tex', '.dx11.dds.dvpl');
const isDds = existsSync(ddsTexturePath);
const resolvedTexturePath = isDds
? ddsTexturePath
: ddsTexturePath.replace('.dds', '.pvr');
const decompressedTexture = await readDVPLFile(resolvedTexturePath);
const jpg = await toJpg(decompressedTexture, {
...options,
format: isDds ? 'dds' : 'pvr',
});
const decompressedDvpl = await readDVPLFile(ddsTexturePath);
const stream = new DdsStream(decompressedDvpl);
const ddsRaw = await stream.dds();
const png = await sharp(ddsRaw.data, { raw: ddsRaw }).png().toBuffer();
return jpg;
return png;
// const isDds = existsSync(ddsTexturePath);
// const resolvedTexturePath = isDds
// ? ddsTexturePath
// : ddsTexturePath.replace('.dds', '.pvr');
// const decompressedTexture = await readDVPLFile(resolvedTexturePath);
// const jpg = await toJpg(decompressedTexture, {
// ...options,
// format: isDds ? 'dds' : 'pvr',
// });
// return jpg;
}
-47
View File
@@ -1,47 +0,0 @@
import { times } from 'lodash';
type Bit = 0 | 1;
export class BitStream {
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 (size > 32) {
throw new RangeError('Cannot read more than 32 bits at a time');
}
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);
}
readBytes(size: number) {
const buffer = Buffer.from(times(size, () => this.consume(8)));
this.skip(-8 * size);
return buffer;
}
consume(size: number) {
const value = this.read(size);
this.skip(size);
return value;
}
}
@@ -1,4 +1,4 @@
export class ByteStream {
export class BufferStream {
public index = 0;
constructor(public stream: Buffer) {}
+110 -26
View File
@@ -1,10 +1,9 @@
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';
type Bits2 = 0b00 | 0b01 | 0b10 | 0b11;
enum DxgiFormat {
UNKNOWN = 0,
R32G32B32A32_TYPELESS = 1,
@@ -180,10 +179,7 @@ export class DdsStream extends WindowsStream {
const header = this.header();
if (
header.pf.flags !== DdpfFlags.FOURCC ||
header.pf.fourCC !== 0x30315844 // "DX10"
) {
if (header.pf.flags !== DdpfFlags.FOURCC || header.pf.fourCC !== 'DX10') {
throw new RangeError('No other formats other than DX10 are supported');
}
@@ -191,25 +187,57 @@ export class DdsStream extends WindowsStream {
switch (headerDxt10.dxgiFormat) {
case DxgiFormat.BC1_UNORM_SRGB: {
this.skip(1);
if (header.width % 4 !== 0 || header.height % 4 !== 0) {
throw new RangeError('Width and height must be divisible by 4');
}
const image = new Bc1Stream(
// this.consume((header.width / 4) * (header.height / 4) * 8),
this.consumeRemaining(),
).bc1(header.width, header.height);
const data = Buffer.alloc(header.width * header.height * 4);
const blocksX = header.width / 4;
const blocksY = header.height / 4;
writeFileSync(
'test.png',
await sharp(image, {
raw: {
channels: 4,
width: header.width,
height: header.height,
},
})
.png()
.toBuffer(),
);
times(blocksX * blocksY, (blockIndex) => {
const blockPosX = (blockIndex % blocksX) * 4;
const blockPosY = Math.floor(blockIndex / blocksX) * 4;
const interpolations = this.interpolations();
const indices = this.indices();
indices.forEach((index, indexIndex) => {
let pixelPosX = 4 - (indexIndex % 4);
let pixelPosY = Math.floor(indexIndex / 4);
let x = blockPosX + pixelPosX;
let y = blockPosY + pixelPosY;
const bufferIndex = 4 * (y * header.width + x);
const color = interpolations[index].map((standard) =>
Math.round(255 * standard),
);
data[bufferIndex] = color[0];
data[bufferIndex + 1] = color[1];
data[bufferIndex + 2] = color[2];
data[bufferIndex + 3] = color[3];
});
});
// writeFileSync(
// 'dist/assets/models/15697/baseColor_1.jpg',
// await sharp(image, {
// raw: {
// width: header.width,
// height: header.height,
// channels: 4,
// },
// })
// .jpeg()
// .toBuffer(),
// );
return {
width: header.width,
height: header.height,
channels: 4 as const,
data,
};
break;
}
@@ -223,8 +251,64 @@ export class DdsStream extends WindowsStream {
}
}
interpolations() {
const buffer0 = this.consume(2);
const buffer1 = this.consume(2);
const int0 = buffer0.readUInt16LE();
const int1 = buffer1.readUInt16LE();
const alpha = int0 < int1;
const color0 = [
((buffer0[1] & 0b11111000) >>> 3) / 0x1f,
(((buffer0[1] & 0b111) << 3) + ((buffer0[0] & 0b11100000) >>> 5)) / 0x3f,
(buffer0[0] & 0b11111) / 0x1f,
1,
];
const color1 = [
((buffer1[1] & 0b11111000) >>> 3) / 0x1f,
(((buffer1[1] & 0b111) << 3) + ((buffer1[0] & 0b11100000) >>> 5)) / 0x3f,
(buffer1[0] & 0b11111) / 0x1f,
1,
];
const color2 = alpha
? color0.map((channel0, index) => (channel0 + color1[index]) / 2)
: color0.map((channel0, index) => (2 * channel0 + color1[index]) / 3);
const color3 = alpha
? [0, 0, 0, 0]
: color0.map((channel0, index) => (channel0 + 2 * color1[index]) / 3);
return {
[0b00]: color0,
[0b01]: color1,
[0b10]: color2,
[0b11]: color3,
};
}
indices() {
const indicesBuffer = this.consume(4);
const indices = times(16, (index) => {
const bufferIndex = Math.floor(index / 4);
const maskShift = 2 * (index % 4);
const valueShift = 2 * (3 - (index % 4));
const buffer = indicesBuffer[bufferIndex];
const mask = 0b11000000 >>> maskShift;
const masked = buffer & mask;
const value = masked >>> valueShift;
return value as Bits2;
});
return indices;
}
magicNumber() {
return this.dword();
const magic = this.dword();
if (magic !== 0x20534444) {
throw new Error(`Invalid DDS magic number: 0x${magic.toString(16)}`);
}
return magic;
}
header() {
@@ -250,7 +334,7 @@ export class DdsStream extends WindowsStream {
return {
size: this.dword(),
flags: this.dword() as DdpfFlags,
fourCC: this.dword(),
fourCC: this.ascii(4),
rgbBitCount: this.dword(),
rBitMask: this.dword(),
gBitMask: this.dword(),
+2 -2
View File
@@ -1,5 +1,5 @@
import { times } from 'lodash';
import { ByteStream } from './byte';
import { BufferStream } from './buffer';
enum KAType {
NONE = 0,
@@ -55,7 +55,7 @@ export enum VertexAttribute {
CUBETEXCOORD3 = 19,
}
export class ScpgStream extends ByteStream {
export class ScpgStream extends BufferStream {
vectorN(size: number) {
return times(size, () => this.float());
}
+2 -2
View File
@@ -1,6 +1,6 @@
import { ByteStream } from './byte';
import { BufferStream } from './buffer';
export class WindowsStream extends ByteStream {
export class WindowsStream extends BufferStream {
dword() {
return this.uint32();
}