GaussianPhysics / assets.js
openfree's picture
Gaussian kernels as MPM continuum particles
76f24fe verified
Raw
History Blame Contribute Delete
3.96 kB
// Test subjects, generated rather than downloaded.
//
// A splat reconstruction of a real object would do, but every published one we
// could use carries research-only or unstated terms, so the shapes here are made
// from scratch: each returns gaussian centres plus per-kernel sigma, which is all
// the simulation and the renderer need.
function rngFrom(seed) {
let s = seed >>> 0 || 1;
return () => {
s ^= s << 13; s >>>= 0;
s ^= s >> 17;
s ^= s << 5; s >>>= 0;
return s / 4294967296;
};
}
/** Fill a shape by rejection sampling, so density is uniform in volume. */
function fill(inside, count, bounds, rng) {
const pos = [], [lo, hi] = bounds;
let guard = 0;
while (pos.length < count * 3 && guard < count * 400) {
guard++;
const x = lo[0] + rng() * (hi[0] - lo[0]);
const y = lo[1] + rng() * (hi[1] - lo[1]);
const z = lo[2] + rng() * (hi[2] - lo[2]);
if (inside(x, y, z)) pos.push(x, y, z);
}
return new Float32Array(pos);
}
const SHAPES = {
cube: {
label: 'Cube',
make: (rng, n) => fill((x, y, z) =>
Math.abs(x - 0.5) < 0.13 && Math.abs(z - 0.5) < 0.13 && y > 0.52 && y < 0.78,
n, [[0.3, 0.45, 0.3], [0.7, 0.85, 0.7]], rng),
},
ball: {
label: 'Ball',
make: (rng, n) => fill((x, y, z) =>
(x - 0.5) ** 2 + (y - 0.66) ** 2 + (z - 0.5) ** 2 < 0.145 ** 2,
n, [[0.34, 0.5, 0.34], [0.66, 0.82, 0.66]], rng),
},
column: {
label: 'Column',
make: (rng, n) => fill((x, y, z) =>
(x - 0.5) ** 2 + (z - 0.5) ** 2 < 0.09 ** 2 && y > 0.2 && y < 0.86,
n, [[0.4, 0.2, 0.4], [0.6, 0.86, 0.6]], rng),
},
arch: {
label: 'Arch',
make: (rng, n) => fill((x, y, z) => {
if (Math.abs(z - 0.5) > 0.09) return false;
const r = Math.hypot(x - 0.5, y - 0.42);
if (y > 0.42) return r > 0.14 && r < 0.24;
return Math.abs(Math.abs(x - 0.5) - 0.19) < 0.05 && y > 0.2;
}, n, [[0.24, 0.2, 0.4], [0.76, 0.7, 0.6]], rng),
},
tower: {
label: 'Tower',
make: (rng, n) => fill((x, y, z) => {
const t = (y - 0.2) / 0.62;
if (t < 0 || t > 1) return false;
const half = 0.16 * (1 - t * 0.55);
return Math.abs(x - 0.5) < half && Math.abs(z - 0.5) < half;
}, n, [[0.3, 0.2, 0.3], [0.7, 0.85, 0.7]], rng),
},
};
export const SHAPE_LIST = Object.entries(SHAPES).map(([id, s]) => ({ id, label: s.label }));
/**
* Build a gaussian cloud for a shape. Sigma is tied to the spacing implied by the
* particle count so the kernels just overlap — the same reason a splat
* reconstruction looks solid rather than like beads.
*/
export function makeAsset(shape = 'cube', count = 4200, seed = 7) {
const rng = rngFrom(seed);
const spec = SHAPES[shape] || SHAPES.cube;
const pos = spec.make(rng, count);
const n = pos.length / 3;
const sigma = new Float32Array(n * 3);
const base = 0.5 * Math.cbrt(0.02 / Math.max(1, n)); // ~volume per particle
for (let i = 0; i < n; i++) {
const s = base * (2.6 + rng() * 0.8);
sigma[i * 3] = s;
sigma[i * 3 + 1] = s;
sigma[i * 3 + 2] = s;
}
// Colour is banded by height so deformation is legible: a uniform blob tells
// you nothing about which part of it stretched.
const color = new Float32Array(n * 3);
let minY = Infinity, maxY = -Infinity;
for (let i = 0; i < n; i++) { minY = Math.min(minY, pos[i * 3 + 1]); maxY = Math.max(maxY, pos[i * 3 + 1]); }
for (let i = 0; i < n; i++) {
const t = (pos[i * 3 + 1] - minY) / Math.max(1e-6, maxY - minY);
color[i * 3] = 0.55 + 0.4 * t;
color[i * 3 + 1] = 0.62 + 0.22 * Math.sin(t * 6.28);
color[i * 3 + 2] = 0.95 - 0.45 * t;
}
return { positions: pos, sigma, color, count: n };
}