feat: sharp-based texture manipulation

This commit is contained in:
TrèsAbhi committed 2024-03-13 17:00:40 -05:00
1 parent f39a94cbe0
commit 4819100ec6
4 files changed
+40 -96

No files matched your search

-19
View File
@@ -1,21 +1,15 @@
import { NodeIO } from '@gltf-transform/core'; import { NodeIO } from '@gltf-transform/core';
import { readdir } from 'fs/promises'; import { readdir } from 'fs/promises';
import { Vector3Tuple } from 'three';
import { extractModel } from '../../src/core/blitz/extractModel'; import { extractModel } from '../../src/core/blitz/extractModel';
import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL'; import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL';
import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL'; import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL';
import { toUniqueId } from '../../src/core/blitz/toUniqueId'; import { toUniqueId } from '../../src/core/blitz/toUniqueId';
// import { AssetCommit } from '../../src/core/github/assetCommit';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; import { commitAssets } from '../../src/core/blitzkrieg/commitAssets';
import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles'; import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles';
import { DATA, POI } from './constants'; import { DATA, POI } from './constants';
import { VehicleDefinitionList } from './definitions'; import { VehicleDefinitionList } from './definitions';
import { TankParameters } from './tankIcons'; import { TankParameters } from './tankIcons';
interface VehicleCustomization {
armorColor: string;
}
export async function tankModels(production: boolean) { export async function tankModels(production: boolean) {
console.log('Building tank models...'); console.log('Building tank models...');
@@ -27,18 +21,9 @@ export async function tankModels(production: boolean) {
for (const nationIndex in nations) { for (const nationIndex in nations) {
const nation = nations[nationIndex]; const nation = nations[nationIndex];
const changes: FileChange[] = []; const changes: FileChange[] = [];
// const assetCommit = new AssetCommit(`tank models ${nation}`, production);
const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>( const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>(
`${DATA}/${POI.vehicleDefinitions}/${nation}/list.xml.dvpl`, `${DATA}/${POI.vehicleDefinitions}/${nation}/list.xml.dvpl`,
); );
const customization = await readXMLDVPL<{ root: VehicleCustomization }>(
`${DATA}/${POI.vehicleDefinitions}/${nation}/customization.xml.dvpl`,
);
const baseColor = customization.root.armorColor
.split(' ')
.slice(0, 3)
.map(Number)
.map((channel) => channel / 255) as Vector3Tuple;
console.log(`Building models for ${nation}`); console.log(`Building models for ${nation}`);
@@ -56,11 +41,8 @@ export async function tankModels(production: boolean) {
const model = await extractModel( const model = await extractModel(
DATA, DATA,
parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, ''), parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, ''),
baseColor,
); );
// writeFile(`test.${tankKey}.glb`, await nodeIO.writeBinary(model));
changes.push({ changes.push({
path: `3d/tanks/models/${id}.glb`, path: `3d/tanks/models/${id}.glb`,
content: Buffer.from(await nodeIO.writeBinary(model)).toString( content: Buffer.from(await nodeIO.writeBinary(model)).toString(
@@ -71,7 +53,6 @@ export async function tankModels(production: boolean) {
}), }),
); );
// assetCommit.push();
await commitAssets(`tank models ${nation}`, changes, production); await commitAssets(`tank models ${nation}`, changes, production);
} }
} }
+6 -18
View File
@@ -1,13 +1,7 @@
import { Document, Material, Node, Scene } from '@gltf-transform/core'; import { Document, Material, Node, Scene } from '@gltf-transform/core';
import { times } from 'lodash'; import { times } from 'lodash';
import { dirname } from 'path'; import { dirname } from 'path';
import { import { Matrix4, Quaternion, Vector3, Vector4Tuple } from 'three';
Matrix4,
Quaternion,
Vector3,
Vector3Tuple,
Vector4Tuple,
} from 'three';
import { readTexture } from '../../blitzkrieg/readTexture'; import { readTexture } from '../../blitzkrieg/readTexture';
import { TextureMutation } from '../../blitzkrieg/readTexture/constants'; import { TextureMutation } from '../../blitzkrieg/readTexture/constants';
import { Hierarchy, Sc2ReadStream, Textures } from '../../streams/sc2'; import { Hierarchy, Sc2ReadStream, Textures } from '../../streams/sc2';
@@ -26,11 +20,7 @@ const omitMeshNames = {
end: ['_POINT'], end: ['_POINT'],
}; };
export async function extractModel( export async function extractModel(data: string, path: string) {
data: string,
path: string,
baseColor?: Vector3Tuple,
) {
const sc2Path = `${data}/3d/${path}.sc2.dvpl`; const sc2Path = `${data}/3d/${path}.sc2.dvpl`;
const scgPath = `${data}/3d/${path}.scg.dvpl`; const scgPath = `${data}/3d/${path}.scg.dvpl`;
const sc2 = new Sc2ReadStream((await readDVPLFile(sc2Path)).buffer).sc2(); const sc2 = new Sc2ReadStream((await readDVPLFile(sc2Path)).buffer).sc2();
@@ -71,9 +61,7 @@ export async function extractModel(
.setImage( .setImage(
await readTexture( await readTexture(
`${data}/3d/${dirname(path)}/${textures.albedo}`, `${data}/3d/${dirname(path)}/${textures.albedo}`,
baseColor TextureMutation.Albedo,
? { mutation: TextureMutation.BaseColor, baseColor }
: undefined,
), ),
), ),
); );
@@ -86,7 +74,7 @@ export async function extractModel(
.setImage( .setImage(
await readTexture( await readTexture(
`${data}/3d/${dirname(path)}/${textures.baseRMMap}`, `${data}/3d/${dirname(path)}/${textures.baseRMMap}`,
{ mutation: TextureMutation.RoughnessMetallicness }, TextureMutation.RoughnessMetallicness,
), ),
), ),
); );
@@ -104,7 +92,7 @@ export async function extractModel(
`${data}/3d/${dirname(path)}/${ `${data}/3d/${dirname(path)}/${
textures.baseNormalMap ?? textures.normalmap textures.baseNormalMap ?? textures.normalmap
}`, }`,
isBase ? { mutation: TextureMutation.Normal } : undefined, isBase ? TextureMutation.Normal : undefined,
), ),
), ),
); );
@@ -118,7 +106,7 @@ export async function extractModel(
.setImage( .setImage(
await readTexture( await readTexture(
`${data}/3d/${dirname(path)}/${textures.miscMap}`, `${data}/3d/${dirname(path)}/${textures.miscMap}`,
{ mutation: TextureMutation.Miscellaneous }, TextureMutation.Miscellaneous,
), ),
), ),
); );
+1 -1
View File
@@ -1,6 +1,6 @@
export enum TextureMutation { export enum TextureMutation {
Normal, Normal,
RoughnessMetallicness, RoughnessMetallicness,
BaseColor, Albedo,
Miscellaneous, Miscellaneous,
} }
+33 -58
View File
@@ -1,27 +1,11 @@
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import sharp from 'sharp'; import sharp from 'sharp';
import { Vector3Tuple } from 'three';
import { readDVPLFile } from '../../blitz/readDVPLFile'; import { readDVPLFile } from '../../blitz/readDVPLFile';
import { DdsReadStream } from '../../streams/dds'; import { DdsReadStream } from '../../streams/dds';
import { PvrReadStream } from '../../streams/pvr'; import { PvrReadStream } from '../../streams/pvr';
import { TextureMutation } from './constants'; import { TextureMutation } from './constants';
type ReadTextureOptions = export async function readTexture(path: string, mutation?: TextureMutation) {
| {
mutation:
| TextureMutation.Normal
| TextureMutation.RoughnessMetallicness
| TextureMutation.Miscellaneous;
}
| {
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 ddsTexturePath = path.replace('.tex', '.dx11.dds.dvpl');
const isDds = existsSync(ddsTexturePath); const isDds = existsSync(ddsTexturePath);
const resolvedTexturePath = isDds const resolvedTexturePath = isDds
@@ -32,11 +16,11 @@ export async function readTexture(path: string, options?: ReadTextureOptions) {
const raw = isDds const raw = isDds
? await new DdsReadStream(decompressedDvpl.buffer).dds() ? await new DdsReadStream(decompressedDvpl.buffer).dds()
: new PvrReadStream(decompressedDvpl.buffer).pvr(); : new PvrReadStream(decompressedDvpl.buffer).pvr();
const channels = 4 * raw.width * raw.height;
switch (options?.mutation) { switch (mutation) {
case TextureMutation.Normal: { case TextureMutation.Normal: {
for (let index = 0; index < channels; index += 4) { const bytes = 4 * raw.width * raw.height;
for (let index = 0; index < bytes; index += 4) {
/** /**
* Red is always 255 and blue is always 0. Only alpha and green contain any * Red is always 255 and blue is always 0. Only alpha and green contain any
* sort of information. * sort of information.
@@ -52,53 +36,44 @@ export async function readTexture(path: string, options?: ReadTextureOptions) {
raw.data[index + 3] = 255; raw.data[index + 3] = 255;
} }
break; return await sharp(raw.data, { raw }).png().toBuffer();
} }
case TextureMutation.RoughnessMetallicness: { case TextureMutation.RoughnessMetallicness: {
for (let index = 0; index < channels; index += 4) { const newImage = sharp(Buffer.alloc(raw.width * raw.height), {
/** raw: { ...raw, channels: 1 },
* Same channel situation as normal maps. });
*/ const metallicness = sharp(raw.data, { raw })
const metallicness = raw.data[index + 1]; .extractChannel('green')
const roughness = raw.data[index + 3]; .png()
.toBuffer();
const roughness = sharp(raw.data, { raw })
.extractChannel('alpha')
.png()
.toBuffer();
raw.data[index] = 0; return await newImage
raw.data[index + 1] = roughness; .joinChannel(await Promise.all([roughness, metallicness]))
raw.data[index + 2] = metallicness; .png()
raw.data[index + 3] = 255; .toBuffer();
}
break;
} }
case TextureMutation.Miscellaneous: { case TextureMutation.Miscellaneous: {
for (let index = 0; index < channels; index += 4) { /**
/** * Alpha is ambient occlusion and green is emissive. But very few tanks
* Alpha is ambient occlusion and green is emissive. But very few tanks * use emissive so I am ignoring it for now.
* use emissive so I am ignoring it for now. */
*/ return await sharp(raw.data, { raw })
// const emissive = raw.data[index + 1]; .extractChannel('alpha')
const occlusion = raw.data[index + 3]; .png()
.toBuffer();
raw.data[index] = occlusion;
raw.data[index + 1] = 0;
raw.data[index + 2] = 0;
raw.data[index + 3] = 255;
}
break;
} }
case TextureMutation.BaseColor: { case TextureMutation.Albedo: {
for (let index = 0; index < channels; index += 4) { return await sharp(raw.data, { raw }).removeAlpha().png().toBuffer();
// alpha map is specularity but I am ignoring it for now
raw.data[index + 3] = 255;
}
break;
} }
default:
return await sharp(raw.data, { raw }).png().toBuffer();
} }
return await sharp(raw.data, { raw: raw }).png().toBuffer();
} }