131 lines
4.0 KiB
JavaScript
131 lines
4.0 KiB
JavaScript
// Minimal parametric mesh generators (box/sphere/cylinder), dependency-free.
|
|
// Returns { positions: Float32Array, normals: Float32Array, indices: Uint32Array }.
|
|
|
|
export function box(w, h, d) {
|
|
const x = w / 2, y = h / 2, z = d / 2;
|
|
const positions = [];
|
|
const normals = [];
|
|
const indices = [];
|
|
|
|
const faces = [
|
|
// [normal, 4 corners CCW]
|
|
[[0, 0, 1], [[-x, -y, z], [x, -y, z], [x, y, z], [-x, y, z]]],
|
|
[[0, 0, -1], [[x, -y, -z], [-x, -y, -z], [-x, y, -z], [x, y, -z]]],
|
|
[[0, 1, 0], [[-x, y, z], [x, y, z], [x, y, -z], [-x, y, -z]]],
|
|
[[0, -1, 0], [[-x, -y, -z], [x, -y, -z], [x, -y, z], [-x, -y, z]]],
|
|
[[1, 0, 0], [[x, -y, z], [x, -y, -z], [x, y, -z], [x, y, z]]],
|
|
[[-1, 0, 0], [[-x, -y, -z], [-x, -y, z], [-x, y, z], [-x, y, -z]]],
|
|
];
|
|
|
|
for (const [n, corners] of faces) {
|
|
const base = positions.length / 3;
|
|
for (const c of corners) {
|
|
positions.push(...c);
|
|
normals.push(...n);
|
|
}
|
|
indices.push(base, base + 1, base + 2, base, base + 2, base + 3);
|
|
}
|
|
|
|
return {
|
|
positions: new Float32Array(positions),
|
|
normals: new Float32Array(normals),
|
|
indices: new Uint32Array(indices),
|
|
};
|
|
}
|
|
|
|
export function sphere(radius, widthSegments = 16, heightSegments = 12) {
|
|
const positions = [];
|
|
const normals = [];
|
|
const indices = [];
|
|
|
|
for (let y = 0; y <= heightSegments; y++) {
|
|
const v = y / heightSegments;
|
|
const theta = v * Math.PI;
|
|
for (let x = 0; x <= widthSegments; x++) {
|
|
const u = x / widthSegments;
|
|
const phi = u * Math.PI * 2;
|
|
const nx = -Math.cos(phi) * Math.sin(theta);
|
|
const ny = Math.cos(theta);
|
|
const nz = Math.sin(phi) * Math.sin(theta);
|
|
positions.push(radius * nx, radius * ny, radius * nz);
|
|
normals.push(nx, ny, nz);
|
|
}
|
|
}
|
|
|
|
const rowSize = widthSegments + 1;
|
|
for (let y = 0; y < heightSegments; y++) {
|
|
for (let x = 0; x < widthSegments; x++) {
|
|
const a = y * rowSize + x;
|
|
const b = a + rowSize;
|
|
indices.push(a, b, a + 1, b, b + 1, a + 1);
|
|
}
|
|
}
|
|
|
|
return {
|
|
positions: new Float32Array(positions),
|
|
normals: new Float32Array(normals),
|
|
indices: new Uint32Array(indices),
|
|
};
|
|
}
|
|
|
|
export function cylinder(radiusTop, radiusBottom, height, radialSegments = 12) {
|
|
const positions = [];
|
|
const normals = [];
|
|
const indices = [];
|
|
const halfH = height / 2;
|
|
|
|
// Side wall.
|
|
for (let y = 0; y <= 1; y++) {
|
|
const r = y === 0 ? radiusTop : radiusBottom;
|
|
const py = y === 0 ? halfH : -halfH;
|
|
for (let x = 0; x <= radialSegments; x++) {
|
|
const u = (x / radialSegments) * Math.PI * 2;
|
|
const nx = Math.sin(u);
|
|
const nz = Math.cos(u);
|
|
positions.push(r * nx, py, r * nz);
|
|
const slope = (radiusBottom - radiusTop) / height;
|
|
const len = Math.sqrt(1 + slope * slope);
|
|
normals.push(nx / len, -slope / len, nz / len);
|
|
}
|
|
}
|
|
const rowSize = radialSegments + 1;
|
|
for (let x = 0; x < radialSegments; x++) {
|
|
const a = x, b = a + rowSize;
|
|
indices.push(a, b, a + 1, b, b + 1, a + 1);
|
|
}
|
|
|
|
// Caps.
|
|
function cap(y, r, py, flip) {
|
|
const base = positions.length / 3;
|
|
const ny = flip ? 1 : -1;
|
|
positions.push(0, py, 0);
|
|
normals.push(0, ny, 0);
|
|
for (let x = 0; x <= radialSegments; x++) {
|
|
const u = (x / radialSegments) * Math.PI * 2;
|
|
positions.push(r * Math.sin(u), py, r * Math.cos(u));
|
|
normals.push(0, ny, 0);
|
|
}
|
|
for (let x = 0; x < radialSegments; x++) {
|
|
if (flip) indices.push(base, base + x + 2, base + x + 1);
|
|
else indices.push(base, base + x + 1, base + x + 2);
|
|
}
|
|
}
|
|
if (radiusTop > 0) cap(0, radiusTop, halfH, true);
|
|
if (radiusBottom > 0) cap(1, radiusBottom, -halfH, false);
|
|
|
|
return {
|
|
positions: new Float32Array(positions),
|
|
normals: new Float32Array(normals),
|
|
indices: new Uint32Array(indices),
|
|
};
|
|
}
|
|
|
|
export function buildGeometry(shape) {
|
|
switch (shape.type) {
|
|
case "box": return box(shape.w, shape.h, shape.d);
|
|
case "sphere": return sphere(shape.r);
|
|
case "cylinder": return cylinder(shape.rt, shape.rb, shape.h);
|
|
default: throw new Error(`Unknown shape type: ${shape.type}`);
|
|
}
|
|
}
|