feat: texture mutation

This commit is contained in:
TrèsAbhi committed 2023-12-28 20:58:18 -06:00
1 parent 0f9f15b268
commit 7b136e61a1
3 files changed
+115 -40

No files matched your search

+14
View File
@@ -3,6 +3,7 @@ import { config } from 'dotenv';
import { mkdir, readdir } from 'fs/promises';
import { argv } from 'process';
import sharp from 'sharp';
import { Vector3Tuple } from 'three';
import { TankType } from '../src/components/Tanks';
import { NATION_IDS } from '../src/constants/nations';
import { extractModel } from '../src/core/blitz/extractModel';
@@ -29,6 +30,10 @@ config();
// WARNING! MOST OF THESE TYPES ARE NOT EXHAUSTIVE!
interface VehicleCustomization {
armorColor: string;
}
interface VehicleDefinitionList {
[key: string]: {
id: number;
@@ -494,6 +499,14 @@ if (allTargets || targets?.includes('tankModels')) {
const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>(
`${DATA}/${DOI.vehicleDefinitions}/${nation}/list.xml.dvpl`,
);
const customization = await readXMLDVPL<{ root: VehicleCustomization }>(
`${DATA}/${DOI.vehicleDefinitions}/${nation}/customization.xml.dvpl`,
);
const baseColor = customization.root.armorColor
.split(' ')
.slice(0, 3)
.map(Number)
.map((channel) => channel / 255) as Vector3Tuple;
for (const tankIndex in tanks.root) {
const tank = tanks.root[tankIndex];
@@ -511,6 +524,7 @@ if (allTargets || targets?.includes('tankModels')) {
const model = await extractModel(
DATA,
parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, ''),
baseColor,
);
await mkdir(`dist/assets/models/${id}`, { recursive: true });
+16 -37
View File
@@ -1,8 +1,14 @@
import { Document, Material, Node, Scene } from '@gltf-transform/core';
import { range, times } from 'lodash';
import { dirname } from 'path';
import { Matrix4, Quaternion, Vector3, Vector4Tuple } from 'three';
import { readTexture } from '../blitzkrieg/readTexture';
import {
Matrix4,
Quaternion,
Vector3,
Vector3Tuple,
Vector4Tuple,
} from 'three';
import { TextureMutation, readTexture } from '../blitzkrieg/readTexture';
import { Hierarchy, Sc2Stream, Textures } from '../streams/sc2';
import { ScgStream, vertexAttributeVectorSizes } from '../streams/scg';
import { VertexAttribute } from '../streams/scpg';
@@ -23,7 +29,11 @@ const vertexAttributeGLTFName: Partial<Record<VertexAttribute, string>> = {
[VertexAttribute.JOINTWEIGHT]: 'WEIGHT_0',
};
export async function extractModel(data: string, path: string) {
export async function extractModel(
data: string,
path: string,
baseColor: Vector3Tuple,
) {
const sc2Path = `${data}/3d/${path}.sc2.dvpl`;
const scgPath = `${data}/3d/${path}.scg.dvpl`;
const sc2 = new Sc2Stream(await readDVPLFile(sc2Path)).sc2();
@@ -64,6 +74,7 @@ export async function extractModel(data: string, path: string) {
.setImage(
await readTexture(
`${data}/3d/${dirname(path)}/${textures.albedo}`,
{ mutation: TextureMutation.BaseColor, baseColor },
),
),
);
@@ -76,25 +87,9 @@ export async function extractModel(data: string, path: string) {
.setImage(
await readTexture(
`${data}/3d/${dirname(path)}/${textures.baseRMMap}`,
{ mutation: TextureMutation.RoughnessMetallicness },
),
),
// .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();
// }),
// ),
);
}
@@ -106,25 +101,9 @@ export async function extractModel(data: string, path: string) {
.setImage(
await readTexture(
`${data}/3d/${dirname(path)}/${textures.baseNormalMap}`,
{ mutation: TextureMutation.Normal },
),
),
// .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();
// }),
// ),
);
}
}
+85 -3
View File
@@ -1,14 +1,96 @@
import sharp from 'sharp';
import { Vector3Tuple } from 'three';
import { readDVPLFile } from '../blitz/readDVPLFile';
import { DdsStream } from '../streams/dds';
export async function readTexture(path: string) {
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) {
const ddsTexturePath = path.replace('.tex', '.dx11.dds.dvpl');
const decompressedDvpl = await readDVPLFile(ddsTexturePath);
const stream = new DdsStream(decompressedDvpl);
const ddsRaw = await stream.dds();
const dds = await stream.dds();
const channels = 4 * dds.width * dds.height;
return await sharp(ddsRaw.data, { raw: ddsRaw }).png().toBuffer();
/**
* Red is always 255 and blue is always 0. Only alpha and green contain any
* sort of information.
*/
switch (options?.mutation) {
case TextureMutation.Normal: {
for (let index = 0; index < channels; index += 4) {
let x = dds.data[index + 3] / 255;
let y = dds.data[index + 1] / 255;
let z = Math.sqrt(1 - x ** 2 - y ** 2);
const hypot = Math.sqrt(x ** 2 + y ** 2 + z ** 2);
if (isFinite(hypot)) {
x /= hypot;
y /= hypot;
z /= hypot;
}
dds.data[index] = Math.round(x * 255);
dds.data[index + 1] = Math.round(y * 255);
dds.data[index + 2] = Math.round(z * 255);
dds.data[index + 3] = 255;
}
break;
}
case TextureMutation.RoughnessMetallicness: {
for (let index = 0; index < channels; index += 4) {
const metallicness = dds.data[index + 1];
const roughness = dds.data[index + 3];
dds.data[index] = 0;
dds.data[index + 1] = roughness;
dds.data[index + 2] = metallicness;
dds.data[index + 3] = 255;
}
break;
}
case TextureMutation.BaseColor: {
for (let index = 0; index < channels; index += 4) {
const r = dds.data[index] / 255;
const g = dds.data[index + 1] / 255;
const b = dds.data[index + 2] / 255;
const a = dds.data[index + 3] / 255;
const inverseA = 1 - a;
const mixedR = a * r + inverseA * options.baseColor[0];
const mixedG = a * g + inverseA * options.baseColor[1];
const mixedB = a * b + inverseA * options.baseColor[2];
dds.data[index] = mixedR * 255;
dds.data[index + 1] = mixedG * 255;
dds.data[index + 2] = mixedB * 255;
dds.data[index + 3] = 255;
}
break;
}
}
return await sharp(dds.data, { raw: dds }).png().toBuffer();
// const isDds = existsSync(ddsTexturePath);
// const resolvedTexturePath = isDds