mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: sharp-based texture manipulation
This commit is contained in:
1 parent
f39a94cbe0
commit
4819100ec6
4 files changed
+40
-96
No files matched your search
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,6 +1,6 @@
|
|||||||
export enum TextureMutation {
|
export enum TextureMutation {
|
||||||
Normal,
|
Normal,
|
||||||
RoughnessMetallicness,
|
RoughnessMetallicness,
|
||||||
BaseColor,
|
Albedo,
|
||||||
Miscellaneous,
|
Miscellaneous,
|
||||||
}
|
}
|
||||||
@@ -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();
|
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user