mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
feat: texture mutation
This commit is contained in:
1 parent
0f9f15b268
commit
7b136e61a1
3 files changed
+115
-40
No files matched your search
@@ -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 });
|
||||
|
||||
@@ -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();
|
||||
// }),
|
||||
// ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user