// Require ThreeJS and utilities global.THREE = require('three'); require('three/examples/js/exporters/OBJExporter'); require('three/examples/js/utils/GeometryUtils'); require('three/examples/js/controls/OrbitControls'); // Grab canvas-sketch utils const canvasSketch = require('canvas-sketch'); const random = require('canvas-sketch-util/random'); // If run in a browser, this will return an empty object const fs = require('fs'); // 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'; // Set a fixed seed so it always renders the same geometry random.setSeed('256'); const settings = { suffix: random.getSeed(), dimensions: [ 1920, 1080 ], scaleToView: true, context: 'webgl', animate: true, attributes: { antialias: true } }; // A browser sketch so we can iterate & visualize it without exporting each time const sketch = ({ context }) => { // Create a renderer const renderer = new THREE.WebGLRenderer({ context }); // WebGL background color renderer.setClearColor('#fff', 1); // Setup a camera const camera = new THREE.PerspectiveCamera(45, 1, 0.01, 100); const orbitAngle = -45 * Math.PI / 180; const orbitDistance = 5; const orbitHeight = 5; const orbitTranslate = new THREE.Vector3(-2, 0, 0); camera.position.set( Math.cos(orbitAngle) * orbitDistance, orbitHeight, Math.sin(orbitAngle) * orbitDistance ).add(orbitTranslate); camera.lookAt(orbitTranslate); // Setup camera controller const controls = new THREE.OrbitControls(camera); // Setup your scene const scene = new THREE.Scene(); const geometry = generate(); const mesh = new THREE.Mesh( geometry, new THREE.MeshNormalMaterial({ flatShading: true }) ); scene.add(mesh); // draw each frame return { // Handle resize events here resize ({ pixelRatio, viewportWidth, viewportHeight }) { renderer.setPixelRatio(pixelRatio); renderer.setSize(viewportWidth, viewportHeight); camera.aspect = viewportWidth / viewportHeight; camera.updateProjectionMatrix(); }, // Update & render your scene here render ({ time, exporting }) { controls.update(); renderer.render(scene, camera); if (exporting) { // Export both PNG and OBJ file return [ context.canvas, { data: exportGeometry(geometry), extension: '.obj' } ]; } }, // Dispose of events & renderer for cleaner hot-reloading unload () { controls.dispose(); renderer.dispose(); } }; }; // The actual 'generative geometry' part 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 * random.range(0.25, 3); const length = A * B * random.range(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(); const out = new THREE.Vector3(); const offset = geometry.boundingBox.getCenter(out); out.negate(); geometry.translate(offset.x, 0, offset.z); return geometry; } // This generates an OBJ file from the geometry // In Node.js it can write it to a file or stdout, // In browser it simply returns the string for canvas-sketch to export function exportGeometry (geometry) { 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 && !isBrowser) { // 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 if (!isBrowser) console.log(result); } scene.remove(object); return result; } if (isBrowser) { canvasSketch(sketch, settings); } else { exportGeometry(generate()); }