Created
          February 14, 2019 16:03 
        
      - 
      
 - 
        
Save mattdesl/7dd34f2bec4cdfa3d8c42295dcf1297f to your computer and use it in GitHub Desktop.  
Revisions
- 
        
mattdesl created this gist
Feb 14, 2019 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,7 @@ # Generative Geometry in Browser + Node.js Here is a script that can be run with [canvas-sketch](https://github.com/mattdesl/canvas-sketch) to generate OBJ files from a parametric/algorithmic 3D ThreeJS geometry. Hitting "Cmd + S" from the canvas-sketch tool will export a PNG and OBJ file of the scene. If the same script is run from Node, it will simply render the OBJ to stdout, or write to the filename argument if given. This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,185 @@ // 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()); }