Skip to content

Instantly share code, notes, and snippets.

@mattdesl
Last active May 29, 2024 14:07
Show Gist options
  • Save mattdesl/4d9b85e9cc0816f23e553dd670a767bc to your computer and use it in GitHub Desktop.
Save mattdesl/4d9b85e9cc0816f23e553dd670a767bc to your computer and use it in GitHub Desktop.

procedural mesh generation

Generates an algorithmic 3D OBJ file with ThreeJS and Node.js.

# print to stdout
node generate-mesh.js > test.obj

# write to file
node generate-mesh.js test.obj

Assumes [email protected], [email protected], [email protected] (installed from npm).

The script can be browserified (useful if you wanted to build a real-time visualizer as you tweak the algorithm) or run with node.js.

See this tweet for screenshots.

// Optional: this is for predictable randomness
require('seed-random')('1', { global: true });
// Require ThreeJS and extensions
// for this you can `npm install three`
global.THREE = require('three');
require('three/examples/js/exporters/OBJExporter');
require('three/examples/js/utils/GeometryUtils');
// If run in a browser, this will return an empty object
const fs = require('fs');
const randomFloat = require('random-float');
// The browser uses an empty array here
const argv = process.argv.slice(2);
// We can run all this code in the browser too,
// e.g. we could visualize it as we tweak the algorithm
const isBrowser = typeof document !== 'undefined';
// Generate your ThreeJS geometry
const geometry = generate();
// Export the Geometry to a file if specified, otherwise stdout
exportGeometry(geometry);
function generate () {
const geometry = new THREE.Geometry();
// this is our generative/algorithmic 3D code
const rings = 20;
const ringSpacing = 1 / rings * 2;
let ringRadius = ringSpacing;
for (let ringIndex = 0; ringIndex < rings; ringIndex++) {
const steps = 7 * (ringIndex + 1);
const A = ringIndex / Math.max(1, rings - 1);
const radius = ringRadius;
ringRadius += ringSpacing;
for (let i = 0; i < steps; i++) {
const B = i / Math.max(1, steps - 1);
const angle = (i / steps) * Math.PI * 2;
const x = Math.cos(angle) * radius;
const z = Math.sin(angle) * radius;
const thickness = ringSpacing * 0.15;
const height = 0.5 * A * B * randomFloat(0.25, 3);
const length = A * B * randomFloat(0.25, 0.5);
// in this case we will build a geometry made of many smaller
// parts, using geometry.merge()
const chunk = new THREE.BoxGeometry(length, height, thickness);
chunk.translate(0, height / 2, 0);
chunk.rotateX(Math.PI / 2 + -angle * 0.15);
const object = new THREE.Object3D();
object.position.set(x, 0, z);
object.rotation.y = -angle;
object.updateMatrix();
// merge in the geometry with the desired matrix
geometry.merge(chunk, object.matrix);
// clean it up after
chunk.dispose();
}
}
// re-center the whole geometry along XZ axis
geometry.computeBoundingBox();
var offset = geometry.boundingBox.getCenter().negate();
geometry.translate(offset.x, 0, offset.z);
return geometry;
}
function exportGeometry (geometry) {
if (isBrowser) return;
const file = argv[0];
const object = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial());
const scene = new THREE.Scene();
scene.add(object);
object.updateMatrixWorld(true);
const exporter = new THREE.OBJExporter();
const result = exporter.parse(object);
if (file) {
// write to file
try {
console.error('Writing to file', file);
fs.writeFileSync(file, result);
} catch (err) {
console.error('Error:', err.message);
}
} else {
// write to stdout
console.log(result);
}
scene.remove(object);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment