mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-27 04:29:50 +01:00
remove skins from api
This commit is contained in:
1 parent
7f77b4006d
commit
0d6c508382
1 file changed
-237
@@ -1,237 +0,0 @@
|
|||||||
import {
|
|
||||||
parseHierarchies,
|
|
||||||
Sc2ReadStream,
|
|
||||||
type CamouflagesXml,
|
|
||||||
type CamouflagesYaml,
|
|
||||||
type TankParameters,
|
|
||||||
} from "@blitzkit/core";
|
|
||||||
import {
|
|
||||||
Document,
|
|
||||||
Node,
|
|
||||||
NodeIO,
|
|
||||||
PropertyType,
|
|
||||||
Scene,
|
|
||||||
} from "@gltf-transform/core";
|
|
||||||
import { ALL_EXTENSIONS } from "@gltf-transform/extensions";
|
|
||||||
import { dedup, mergeDocuments, unpartition } from "@gltf-transform/functions";
|
|
||||||
import type { APIContext } from "astro";
|
|
||||||
import type { Vector3Tuple, Vector4Tuple } from "three";
|
|
||||||
import { SKIN_NUMBER } from "../../../../../constants/skinNumber";
|
|
||||||
import { api } from "../../../../../core/blitzkit/api";
|
|
||||||
import { mixStaticPaths } from "../../../../../core/blitzkit/mixStaticPaths";
|
|
||||||
import { vfs } from "../../../../../core/blitzkit/vfs";
|
|
||||||
import {
|
|
||||||
extractMaterials,
|
|
||||||
extractModel,
|
|
||||||
} from "../../../../[...chunk_models_tanks]/[id]/model.glb";
|
|
||||||
import { getStaticPaths as _getStaticPaths } from "../_index";
|
|
||||||
|
|
||||||
const SKIN_NUMBER_SLOTS = 5;
|
|
||||||
const DIGIT_COLUMNS = 5;
|
|
||||||
const DIGIT_ROWS = 2;
|
|
||||||
|
|
||||||
export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => {
|
|
||||||
const camouflages = await api.camouflageDefinitions();
|
|
||||||
|
|
||||||
return Object.values(camouflages.camouflages)
|
|
||||||
.filter((camouflage) => camouflage.tank_id)
|
|
||||||
.map((camouflage) => ({
|
|
||||||
params: { id: camouflage.tank_id!, skin: camouflage.id },
|
|
||||||
props: { skin: camouflage.id },
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
const camouflagesXml = await vfs.xml<{ root: CamouflagesXml }>(
|
|
||||||
"Data/XML/item_defs/vehicles/common/camouflages.xml",
|
|
||||||
);
|
|
||||||
const camouflagesYaml = await vfs.yaml<CamouflagesYaml>(
|
|
||||||
"Data/camouflages.yaml",
|
|
||||||
);
|
|
||||||
const customization = await vfs.yaml<
|
|
||||||
Record<string, { Name: string; Path: string }>
|
|
||||||
>("Data/3d/Customization.yaml");
|
|
||||||
|
|
||||||
export async function GET({ props }: APIContext<{ skin: number }>) {
|
|
||||||
const nodeIO = new NodeIO().registerExtensions(ALL_EXTENSIONS);
|
|
||||||
|
|
||||||
const items = new Map(
|
|
||||||
Object.values(customization).map(({ Name, Path }) => [
|
|
||||||
Name,
|
|
||||||
Path.replace("~res:/3d/", "").replace(/\.sc2$/, ""),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
const key = Object.keys(camouflagesXml.root.camouflages).find(
|
|
||||||
(key) => camouflagesXml.root.camouflages[key].id === props.skin,
|
|
||||||
)!;
|
|
||||||
const include = camouflagesXml.root.camouflages[key].vehicleFilter.include;
|
|
||||||
const [nation, tankKey] = (
|
|
||||||
include as { vehicle: { name: string } }
|
|
||||||
).vehicle.name.split(":");
|
|
||||||
const { preset, customEntities } = camouflagesYaml[key];
|
|
||||||
const skinNumber = Number(preset?.match(/(\d+)$/)?.[1] ?? 1);
|
|
||||||
const parameters = await vfs.yaml<TankParameters>(
|
|
||||||
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
|
|
||||||
);
|
|
||||||
const basePath = parameters.resourcesPath.blitzModelPath.replace(
|
|
||||||
/\.sc2$/,
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
const sc2 = new Sc2ReadStream(
|
|
||||||
(await vfs.file(`Data/3d/${basePath}.sc2`)).buffer as ArrayBuffer,
|
|
||||||
).sc2();
|
|
||||||
const slots: Record<
|
|
||||||
string,
|
|
||||||
{
|
|
||||||
target: string;
|
|
||||||
position: Vector3Tuple;
|
|
||||||
rotation: Vector4Tuple;
|
|
||||||
scale: Vector3Tuple;
|
|
||||||
}
|
|
||||||
> = {};
|
|
||||||
const states: Record<string, string | null> = {};
|
|
||||||
|
|
||||||
parseHierarchies(sc2["#hierarchy"], null, (hierarchy, components) => {
|
|
||||||
for (const component of components) {
|
|
||||||
if (component["comp.typename"] === "NewSlotComponent") {
|
|
||||||
for (const slot of Object.values(component.slots)) {
|
|
||||||
slots[slot.name] = {
|
|
||||||
target: hierarchy.name,
|
|
||||||
position: slot.transform.position,
|
|
||||||
rotation: slot.transform.rotation,
|
|
||||||
scale: slot.transform.scale,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component["comp.typename"] === "SlotComponent") {
|
|
||||||
slots[component["sc.slotName"]] = {
|
|
||||||
target: hierarchy.name,
|
|
||||||
position: component["sc.attachmentTranslation"] as Vector3Tuple,
|
|
||||||
rotation: component["sc.attachmentRotation"] as Vector4Tuple,
|
|
||||||
scale: component["sc.attachmentScale"] as Vector3Tuple,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component["comp.typename"] !== "StateSwitcherComponent") continue;
|
|
||||||
|
|
||||||
const active = component[`ssc.state${component["ssc.activeState"]}`];
|
|
||||||
// camos without their own variant keep the default piece, but groups
|
|
||||||
// of elements to hide stay hidden
|
|
||||||
let state: string | null =
|
|
||||||
active && !/skin\d*/.test(active) && !active.includes("hide")
|
|
||||||
? active
|
|
||||||
: null;
|
|
||||||
|
|
||||||
for (let index = 0; index < component["ssc.statesCount"]; index++) {
|
|
||||||
const name = component[`ssc.state${index}`];
|
|
||||||
const match = name.match(/skin(\d*)/);
|
|
||||||
|
|
||||||
if (
|
|
||||||
match &&
|
|
||||||
!name.includes("hide") &&
|
|
||||||
Number(match[1] || 1) === skinNumber
|
|
||||||
) {
|
|
||||||
state = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
states[hierarchy.name] = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
const document = new Document();
|
|
||||||
const scene = document.createScene().setExtras({ states });
|
|
||||||
const targets = new Map<string, Node>();
|
|
||||||
|
|
||||||
document.createBuffer();
|
|
||||||
|
|
||||||
if (preset) await extractMaterials(document, sc2, basePath, preset);
|
|
||||||
|
|
||||||
for (const name in customEntities) {
|
|
||||||
const slot = slots[name];
|
|
||||||
const path = items.get(customEntities[name].item);
|
|
||||||
|
|
||||||
if (
|
|
||||||
!slot ||
|
|
||||||
!path ||
|
|
||||||
!(await vfs.resolve(`Data/3d/${path}.sc2`)) ||
|
|
||||||
!(await vfs.resolve(`Data/3d/${path}.scg`))
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const part = await extractModel(vfs, path, true);
|
|
||||||
const map = mergeDocuments(document, part);
|
|
||||||
const partScene = map.get(part.getRoot().listScenes()[0]) as Scene;
|
|
||||||
const node = document
|
|
||||||
.createNode(name)
|
|
||||||
.setTranslation(slot.position)
|
|
||||||
.setRotation(slot.rotation)
|
|
||||||
.setScale(slot.scale);
|
|
||||||
let target = targets.get(slot.target);
|
|
||||||
|
|
||||||
if (!target) {
|
|
||||||
target = document.createNode(slot.target);
|
|
||||||
targets.set(slot.target, target);
|
|
||||||
scene.addChild(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const child of partScene.listChildren()) {
|
|
||||||
node.addChild(child as Node);
|
|
||||||
}
|
|
||||||
|
|
||||||
partScene.dispose();
|
|
||||||
target.addChild(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
applySkinNumber(document, SKIN_NUMBER);
|
|
||||||
|
|
||||||
await document.transform(
|
|
||||||
dedup({ propertyTypes: [PropertyType.ACCESSOR, PropertyType.TEXTURE] }),
|
|
||||||
unpartition(),
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Response(await nodeIO.writeBinary(document));
|
|
||||||
}
|
|
||||||
|
|
||||||
function applySkinNumber(document: Document, number: string) {
|
|
||||||
const start = Math.ceil((SKIN_NUMBER_SLOTS - number.length) / 2) + 1;
|
|
||||||
|
|
||||||
for (const node of document.getRoot().listNodes()) {
|
|
||||||
const match = node.getName().match(/_number_\d+_(\d+)$/);
|
|
||||||
|
|
||||||
if (!match) continue;
|
|
||||||
|
|
||||||
const digit = number[Number(match[1]) - start];
|
|
||||||
|
|
||||||
if (digit === undefined) {
|
|
||||||
node.dispose();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cell = (Number(digit) + 9) % 10;
|
|
||||||
const column = cell % DIGIT_COLUMNS;
|
|
||||||
const row = Math.floor(cell / DIGIT_COLUMNS);
|
|
||||||
|
|
||||||
node.traverse((child) => {
|
|
||||||
for (const primitive of child.getMesh()?.listPrimitives() ?? []) {
|
|
||||||
const uvs = primitive.getAttribute("TEXCOORD_0")?.clone();
|
|
||||||
|
|
||||||
if (!uvs) continue;
|
|
||||||
|
|
||||||
for (let index = 0; index < uvs.getCount(); index++) {
|
|
||||||
const [u, v] = uvs.getElement(index, [0, 0]) as [number, number];
|
|
||||||
|
|
||||||
uvs.setElement(index, [
|
|
||||||
(column + u) / DIGIT_COLUMNS,
|
|
||||||
(row + v) / DIGIT_ROWS,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
primitive.setAttribute("TEXCOORD_0", uvs);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user