3d/hack/convert-meshes.ts

261 lines
6 KiB
TypeScript

import chalk from "chalk";
import { globSync } from "glob";
import { relative, resolve, dirname } from "node:path";
const w = 1;
type Vertex = {
position: {
x: number;
y: number;
z: number;
};
color?: {
r: number;
g: number;
b: number;
a: number;
};
uv?: {
u: number;
v: number;
};
normal?: {
nx: number;
ny: number;
nz: number;
};
};
type Face = {
a: number;
b: number;
c: number;
};
type Triangle = {
a: Vertex;
b: Vertex;
c: Vertex;
};
// const vertToArray = ({ x, y, z, w, r, g, b, a, u, v }: Vertex): number[] => {
// if (!r) return [x, y, z, u, v];
// else return [x, y, z, r, g as number, b as number, a as number, u, v];
// };
// const triangleToArray = (t: Triangle): number[] => [
// ...vertToArray(t.a),
// ...vertToArray(t.b),
// ...vertToArray(t.c),
// ];
const meshAbsolutePath = resolve("./src/renderer/mesh.ts");
const getMeshImportPath = (thisFile: string) =>
relative(dirname(thisFile), meshAbsolutePath).replace(/\.ts$/g, "");
export const convertMeshes = async () => {
const meshes = globSync("src/meshes/**/*.ply");
for (const file of meshes) {
const ply = await Bun.file(file).text();
const [header, body] = ply.split("end_header");
const propertyList: [string, "float" | "uchar"][] = [];
const vertexConfig = {
colors: false,
uvs: false,
normals: false,
};
const headerLines = header.split("\n");
for (const property of headerLines.filter((header) =>
header.startsWith("property")
)) {
const [, propType, name] = property.split(" ");
if (propType === "list") {
continue;
}
propertyList.push([name, propType as any]);
switch (name) {
case "red":
case "green":
case "blue":
case "alpha":
vertexConfig.colors = true;
break;
case "nx":
case "ny":
case "nz":
vertexConfig.normals = true;
break;
case "s":
case "t":
vertexConfig.uvs = true;
break;
}
}
const vertexCount = Number(
headerLines
.find((header) => header.startsWith("element vertex"))
?.replace("element vertex ", "")
);
if (!vertexCount) {
throw new Error("couldn't get vertex count...");
}
const vertexes: Vertex[] = [];
const faces: Face[] = [];
for (const line of body.split("\n")) {
const components = line.split(" ");
if (!components || !components[0]) {
continue;
}
// do we only have 4 components?
if (components[0] === "3" || components.length === 4) {
const [, a, b, c] = components;
// We do??!?! 🥺👉👈
faces.push({ a: Number(a), b: Number(b), c: Number(c) });
continue;
}
const vertex: Required<Vertex> = {
position: {
x: -1,
y: -1,
z: -1,
},
color: {
r: 0,
g: 0,
b: 0,
a: 255,
},
normal: {
nx: -1,
ny: -1,
nz: -1,
},
uv: {
u: -1,
v: -1,
},
};
for (const idx in components) {
const component = components[idx];
const [propName, propType] = propertyList[idx];
const p = parser[propType] ?? parseFloat;
switch (propName) {
case "x":
vertex.position.x = p(component);
break;
case "y":
vertex.position.y = p(component);
break;
case "z":
vertex.position.z = p(component);
case "s":
vertex.uv.u = p(component);
break;
case "t":
vertex.uv.v = p(component);
break;
case "red":
vertex.color.r = p(component);
break;
case "green":
vertex.color.g = p(component);
break;
case "blue":
vertex.color.b = p(component);
break;
case "nx":
vertex.normal.nx = p(component);
break;
case "ny":
vertex.normal.ny = p(component);
break;
case "nz":
vertex.normal.nz = p(component);
break;
}
}
vertexes.push(vertex);
}
const positions: number[] = vertexes.flatMap((v) => [
v.position.x,
v.position.y,
v.position.z,
]);
const normals: number[] = vertexConfig.normals
? vertexes.flatMap((v: any) => [v.normal.nx, v.normal.ny, v.normal.nz])
: [];
const colors: number[] = vertexConfig.colors
? vertexes.flatMap((v: any) => [
v.color.r,
v.color.g,
v.color.b,
v.color.a,
])
: [];
const uvs: number[] = vertexConfig.uvs
? vertexes.flatMap((v: any) => [v.uv.u, v.uv.v])
: [];
const facesArray: number[] = faces.flatMap((f) => [f.a, f.b, f.c]);
const facesMaxValue = facesArray.reduce(
(acc, face) => (face > acc ? face : acc),
0
);
const facesBitDepth =
facesMaxValue <= 0xff ? 8 : facesMaxValue <= 0xffff ? 16 : 32;
const outFile = file.replace(".ply", ".ts");
const outString = `import { Mesh } from "${getMeshImportPath(outFile)}";
// prettier-ignore
export default new Mesh({
colors: ${
vertexConfig.colors ? `new Uint8Array(${JSON.stringify(colors)})` : "null"
},
faces: new Uint${facesBitDepth}Array(${JSON.stringify(facesArray)}),
name: ${JSON.stringify(file)},
normals: ${
vertexConfig.normals
? `new Float32Array(${JSON.stringify(normals)})`
: "null"
},
positions: new Float32Array(${JSON.stringify(positions)}),
uvs: ${
vertexConfig.uvs ? `new Float32Array(${JSON.stringify(uvs)})` : "null"
},
vertexCount: ${vertexCount}
});
`;
await Bun.write(outFile, outString);
console.log(chalk.yellow(` -> ${file}...`));
}
};
const parser = {
float: (x: string) => parseFloat(x),
uchar: (x: string) => Number(x),
};