Add pressure-aware worker sculpting, bounded shared input, project files and history, and compact icon navigation with an XYZ orbit gizmo. Amp-Thread-ID: https://ampcode.com/threads/T-01a0b4b4-202c-73f1-984d-9a55c88f9c83 Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
143 lines
3.7 KiB
JavaScript
143 lines
3.7 KiB
JavaScript
import { unit } from "./math.js";
|
|
|
|
export function sphere(level = 5) {
|
|
if (!Number.isInteger(level) || level < 4 || level > 6)
|
|
throw new Error("Choose a supported resolution.");
|
|
const t = (1 + Math.sqrt(5)) / 2;
|
|
const vertices = [
|
|
[-1, t, 0],
|
|
[1, t, 0],
|
|
[-1, -t, 0],
|
|
[1, -t, 0],
|
|
[0, -1, t],
|
|
[0, 1, t],
|
|
[0, -1, -t],
|
|
[0, 1, -t],
|
|
[t, 0, -1],
|
|
[t, 0, 1],
|
|
[-t, 0, -1],
|
|
[-t, 0, 1],
|
|
].map(unit);
|
|
let faces = [
|
|
[0, 11, 5],
|
|
[0, 5, 1],
|
|
[0, 1, 7],
|
|
[0, 7, 10],
|
|
[0, 10, 11],
|
|
[1, 5, 9],
|
|
[5, 11, 4],
|
|
[11, 10, 2],
|
|
[10, 7, 6],
|
|
[7, 1, 8],
|
|
[3, 9, 4],
|
|
[3, 4, 2],
|
|
[3, 2, 6],
|
|
[3, 6, 8],
|
|
[3, 8, 9],
|
|
[4, 9, 5],
|
|
[2, 4, 11],
|
|
[6, 2, 10],
|
|
[8, 6, 7],
|
|
[9, 8, 1],
|
|
];
|
|
for (let l = 0; l < level; l++) {
|
|
const cache = new Map();
|
|
const midpoint = (a, b) => {
|
|
const key = Math.min(a, b) * 100000 + Math.max(a, b);
|
|
if (!cache.has(key)) {
|
|
cache.set(key, vertices.length);
|
|
vertices.push(unit(vertices[a].map((v, i) => v + vertices[b][i])));
|
|
}
|
|
return cache.get(key);
|
|
};
|
|
faces = faces.flatMap(([a, b, c]) => {
|
|
const ab = midpoint(a, b),
|
|
bc = midpoint(b, c),
|
|
ca = midpoint(c, a);
|
|
return [
|
|
[a, ab, ca],
|
|
[b, bc, ab],
|
|
[c, ca, bc],
|
|
[ab, bc, ca],
|
|
];
|
|
});
|
|
}
|
|
const positions = new Float32Array(vertices.flat());
|
|
const indices = new Uint32Array(faces.flat());
|
|
const incident = Array.from({ length: vertices.length }, () => []);
|
|
const neighbors = Array.from({ length: vertices.length }, () => new Set());
|
|
faces.forEach((face, f) =>
|
|
face.forEach((v) => {
|
|
incident[v].push(f);
|
|
for (const other of face) if (other !== v) neighbors[v].add(other);
|
|
}),
|
|
);
|
|
const mesh = {
|
|
level,
|
|
positions,
|
|
indices,
|
|
normals: new Float32Array(positions.length),
|
|
faceNormals: new Float32Array(indices.length),
|
|
incident: incident.map((a) => Uint32Array.from(a)),
|
|
neighbors: neighbors.map((a) => Uint32Array.from(a)),
|
|
};
|
|
mesh.faceMarks = new Uint32Array(faces.length);
|
|
mesh.vertexMarks = new Uint32Array(vertices.length);
|
|
mesh.generation = 0;
|
|
updateNormals(mesh);
|
|
return mesh;
|
|
}
|
|
|
|
// Include the unchanged incident faces when recomputing a boundary vertex normal.
|
|
export function updateNormals(mesh, moved) {
|
|
const { positions: p, indices: idx, normals: n, faceNormals: fn } = mesh;
|
|
const faces = [],
|
|
vertices = [];
|
|
const gen = ++mesh.generation;
|
|
if (moved) {
|
|
for (const v of moved)
|
|
for (const f of mesh.incident[v]) {
|
|
if (mesh.faceMarks[f] !== gen) {
|
|
mesh.faceMarks[f] = gen;
|
|
faces.push(f);
|
|
}
|
|
}
|
|
} else for (let f = 0; f < idx.length / 3; f++) faces.push(f);
|
|
for (const f of faces) {
|
|
const a = idx[f * 3],
|
|
b = idx[f * 3 + 1],
|
|
c = idx[f * 3 + 2];
|
|
const ax = p[b * 3] - p[a * 3],
|
|
ay = p[b * 3 + 1] - p[a * 3 + 1],
|
|
az = p[b * 3 + 2] - p[a * 3 + 2];
|
|
const bx = p[c * 3] - p[a * 3],
|
|
by = p[c * 3 + 1] - p[a * 3 + 1],
|
|
bz = p[c * 3 + 2] - p[a * 3 + 2];
|
|
fn[f * 3] = ay * bz - az * by;
|
|
fn[f * 3 + 1] = az * bx - ax * bz;
|
|
fn[f * 3 + 2] = ax * by - ay * bx;
|
|
for (let j = 0; j < 3; j++) {
|
|
const v = idx[f * 3 + j];
|
|
if (mesh.vertexMarks[v] !== gen) {
|
|
mesh.vertexMarks[v] = gen;
|
|
vertices.push(v);
|
|
}
|
|
}
|
|
}
|
|
for (const v of vertices) {
|
|
let x = 0,
|
|
y = 0,
|
|
z = 0;
|
|
for (const f of mesh.incident[v]) {
|
|
x += fn[f * 3];
|
|
y += fn[f * 3 + 1];
|
|
z += fn[f * 3 + 2];
|
|
}
|
|
const length = Math.hypot(x, y, z) || 1;
|
|
n[v * 3] = x / length;
|
|
n[v * 3 + 1] = y / length;
|
|
n[v * 3 + 2] = z / length;
|
|
}
|
|
return { faces, vertices };
|
|
}
|