261 lines
6 KiB
TypeScript
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),
|
|
};
|