|
// 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); |
|
} |