Files
blitzkit/src/core/blitzkrieg/readTexture.ts
T

101 lines
3.1 KiB
TypeScript
Raw Normal View History

2023-12-29 19:56:50 -06:00
import { existsSync } from 'fs';
2023-12-29 12:41:21 -06:00
import { clamp } from 'lodash';
2023-12-26 20:02:01 -06:00
import sharp from 'sharp';
2023-12-28 20:58:18 -06:00
import { Vector3Tuple } from 'three';
2023-12-22 15:59:06 -06:00
import { readDVPLFile } from '../blitz/readDVPLFile';
2023-12-26 20:02:01 -06:00
import { DdsStream } from '../streams/dds';
2023-12-29 19:56:50 -06:00
import { PvrStream } from '../streams/pvr';
2023-12-22 15:59:06 -06:00
2023-12-28 20:58:18 -06:00
export enum TextureMutation {
Normal,
RoughnessMetallicness,
BaseColor,
}
type ReadTextureOptions =
| {
mutation: TextureMutation.Normal | TextureMutation.RoughnessMetallicness;
}
| {
mutation: TextureMutation.BaseColor;
/**
* Normalized between 0 and 1
*/
baseColor: Vector3Tuple;
};
export async function readTexture(path: string, options?: ReadTextureOptions) {
2023-12-22 15:59:06 -06:00
const ddsTexturePath = path.replace('.tex', '.dx11.dds.dvpl');
2023-12-29 19:56:50 -06:00
const isDds = existsSync(ddsTexturePath);
const resolvedTexturePath = isDds
? ddsTexturePath
: ddsTexturePath.replace('.dds', '.pvr');
const decompressedDvpl = await readDVPLFile(resolvedTexturePath);
const raw = isDds
? await new DdsStream(decompressedDvpl).dds()
: new PvrStream(decompressedDvpl).pvr();
const channels = 4 * raw.width * raw.height;
2023-12-28 13:11:52 -06:00
2023-12-28 20:58:18 -06:00
switch (options?.mutation) {
case TextureMutation.Normal: {
for (let index = 0; index < channels; index += 4) {
2023-12-29 19:56:50 -06:00
/**
* Red is always 255 and blue is always 0. Only alpha and green contain any
* sort of information.
*/
let x = raw.data[index + 3] * (2 / 255) - 1;
let y = raw.data[index + 1] * (2 / 255) - 1;
2023-12-29 12:41:21 -06:00
const underSqrt = 1 - x ** 2 - y ** 2;
let z = underSqrt > 0 ? Math.sqrt(underSqrt) : -Math.sqrt(-underSqrt);
2023-12-28 20:58:18 -06:00
2023-12-29 19:56:50 -06:00
raw.data[index] = Math.round((x + 1) * (255 / 2));
raw.data[index + 1] = Math.round((y + 1) * (255 / 2));
raw.data[index + 2] = Math.round((z + 1) * (255 / 2));
raw.data[index + 3] = 255;
2023-12-28 20:58:18 -06:00
}
break;
}
case TextureMutation.RoughnessMetallicness: {
for (let index = 0; index < channels; index += 4) {
2023-12-29 19:56:50 -06:00
/**
* Same channel situation as normal maps.
*/
const metallicness = raw.data[index + 1];
const roughness = raw.data[index + 3];
2023-12-28 20:58:18 -06:00
2023-12-29 19:56:50 -06:00
raw.data[index] = 0;
raw.data[index + 1] = roughness;
raw.data[index + 2] = metallicness;
raw.data[index + 3] = 255;
2023-12-28 20:58:18 -06:00
}
break;
}
case TextureMutation.BaseColor: {
for (let index = 0; index < channels; index += 4) {
2023-12-29 12:41:21 -06:00
// treating as pre multiplied but I can't fully confirm
2023-12-29 19:56:50 -06:00
const r = raw.data[index] / 255;
const g = raw.data[index + 1] / 255;
const b = raw.data[index + 2] / 255;
const a = raw.data[index + 3] / 255;
2023-12-29 12:41:21 -06:00
const invA = 1 - a;
2023-12-29 13:01:45 -06:00
const rPrime = r + invA * options.baseColor[0];
const gPrime = g + invA * options.baseColor[1];
const bPrime = b + invA * options.baseColor[2];
2023-12-28 20:58:18 -06:00
2023-12-29 19:56:50 -06:00
raw.data[index] = Math.round(clamp(rPrime * 255, 0, 255));
raw.data[index + 1] = Math.round(clamp(gPrime * 255, 0, 255));
raw.data[index + 2] = Math.round(clamp(bPrime * 255, 0, 255));
raw.data[index + 3] = 255;
2023-12-28 20:58:18 -06:00
}
break;
}
}
2023-12-29 19:56:50 -06:00
return await sharp(raw.data, { raw: raw }).png().toBuffer();
2023-12-22 15:59:06 -06:00
}