D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
erikhazzard
Full window
Github gist
voronoi+boids-1-grey-transparent
Built with
blockbuilder.org
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <link href="https://fonts.googleapis.com/css?family=Inconsolata|Prociono" rel="stylesheet"> <title>Story Viz</title> <link rel="stylesheet" href="style.css" /> </head> <body> <div id='viz-wrapper'> <canvas id='canvas' width="1400" height="700"></canvas> <svg id='viz'> </svg> </div> <script src='d3-topo-combined.js'></script> <script src='entity-vector.js'></script> <script> canvas = document.getElementById('canvas'); context = canvas.getContext('2d'); //width / height var width = canvas.width; var height = canvas.height; var rows = 60; var columns = 60; var size = 10; var GAME = {}; window.GAME = GAME; GAME.entities = new Entities(); GAME.grid = {}; GAME.resolution = size; var image = new Image(); //============================================================================ // //cell // //============================================================================ var Cell = function(params){ var i = 0; var param = null; this.previous = 0; for(i in params){ if (params.hasOwnProperty(i)) { param = params[i]; this[i] = param; }} }; Cell.prototype.draw = function(){ context.save(); context.fillStyle = this.color; if(this.state < 1){ this.color = 'rgba(0,0,0,0)'; } else { this.color = 'rgba(100,150,200,0.4)'; } if(this.previous === 1 && this.state === 0){ this.color = 'rgba(200,50,50,0.4)'; } else if(this.previous === 0 && this.state === 1){ this.color = 'rgba(50,200,50,0.4)'; } context.fillRect( this.i * size, this.j * size, size, size ); context.restore(); }; Cell.prototype.getNeighbors = function(){ //me var neighbors = []; var i=0; var j=0; var targetI = 0; var targetJ = 0; for(i=-1;i<=1;i++){ for(j=-1;j<=1;j++){ targetI = this.i + i; targetJ = this.j + j; if(targetI >= 0 && targetI <= rows && targetJ >= 0 && targetJ <= columns && GAME.grid[targetI] && GAME.grid[targetI][targetJ] ){ neighbors.push( GAME.grid[targetI][targetJ] ); } } } return neighbors; } Cell.prototype.getState = function(){ //get neighbors var neighbors = this.getNeighbors(); var cell = new Cell({ i: this.i, j: this.j, state: this.state, color: this.color }); var i=0; var state = this.state; var num = 0; for(i=neighbors.length-1;i>=0;i--){ if( neighbors[i] && this !== neighbors[i] && neighbors[i].state > 0){ num += 1; } } if(this.state === 1 && num < 2){ cell.state = 0; }else if(this.state === 1 && num > 3){ cell.state = 0; }else if(this.state === 0 && num === 3){ cell.state = 1; } return cell; }; /** * Setup cells */ var i=j=0; for(i=rows-1;i>=0;i--){ GAME.grid[i] = []; for(j=rows-1;j>=0;j--){ GAME.grid[i][j] = new Cell({ i: i, j: j, color: 'rgba(100,150,200,1)', state: 0 }); } }; var updateGrid = function(){ var nextGrid = {}; var color = null; var cell = null; var neighbors = null; var i=0; var j=0; //Draw grid for(i=rows-1;i>=0;i--){ nextGrid[i] = [] for(j=rows-1;j>=0;j--){ cell = GAME.grid[i][j]; cell.draw(); newCell = cell.getState(); nextGrid[i][j] = newCell; nextGrid[i][j].previous = cell.state; } } GAME.grid = nextGrid; } /** * Entities */ //create some entities var numEntities = 20; for(var i=0;i<numEntities; i++){ GAME.entities.add( new Entity({ position: new Vector( Math.random() * width | 0, Math.random() * height | 0 ), velcoity: new Vector( Math.random() * 20 | 0, Math.random() * 20 | 0 ), maxSpeed: 3, mass: 10 })); } GAME.entitiesStatic = new Entities(); var numStaticEntities = 3; for(var i=0;i<numStaticEntities; i++){ GAME.entitiesStatic.add( new Entity({ position: new Vector(width/2, height/2), mass: 20, maxSpeed: 2 }) ); } GAME.util = {}; GAME.mouse = new Vector(0,0); canvas.addEventListener('mousemove', function(e){ GAME.mouse = new Vector( e.clientX, e.clientY ); }); var TOTAL_ENTITIES = numEntities + numStaticEntities; /** * Combined voronoi + boids * */ var canvas = document.querySelector("canvas"), context = canvas.getContext("2d"), width = canvas.width, height = canvas.height; var n = TOTAL_ENTITIES, particles = new Array(n); var voronoi = d3.voronoi() .extent([[-1, -1], [width + 1, height + 1]]); for (var i = 0; i < n; ++i) particles[i] = {0: Math.random() * width, 1: Math.random() * height, vx: 0, vy: 0}; /** * * Tick function * */ var timer = d3.timer(function(elapsed) { var i=0,j=0, entity = null, attraction=null, steer = steerLine = desired = null; var statics = GAME.entitiesStatic.entities; var entities = GAME.entities.entities; //GAME.grid = []; //Draw entities for(i in entities){ entity = entities[i]; ////seek the mouse //if(Math.random() < 0.7){ //entity.applyForce( //entity.seekForce(GAME.mouse).multiply(.5) //); //} //Separate this entity from nearby entities entity.applyForce( entity.separate(entities).multiply(0.3) ); ////FLOCK entity.flock(entities); //Random walking entity.applyForce( entity.walkForce().multiply(1) ); for(j in statics){ entity.applyForce( entity.seekForce(statics[j].position, false, 85).multiply(-4) ); } //entity.separate(statics) //setup grid column = parseInt(parseInt(entity.position.x,10) / GAME.resolution); row = parseInt(parseInt(entity.position.y,10) / GAME.resolution); if( !GAME.grid[column] ){ GAME.grid[column] = []; } if( !GAME.grid[column][row] ){ GAME.grid[column][row] = []; } //add to grid GAME.grid[column][row].state = 1; entity.update(); } ////STATIC entities for(i in GAME.entitiesStatic.entities){ entity = GAME.entitiesStatic.entities[i]; column = parseInt(parseInt(entity.position.x,10) / GAME.resolution); row = parseInt(parseInt(entity.position.y,10) / GAME.resolution); if(GAME.grid[column] && GAME.grid[column][row]){ GAME.grid[column][row].state = 0; } entity.applyForce( entity.walkForce().multiply(1) ); entity.applyForce( entity.separate(GAME.entitiesStatic.entities).multiply(1.2) ); // follow mouse entity.applyForce( entity.seekForce(GAME.mouse).multiply(1.1) ); entity.update(); } let entitiesToIterate = entities.concat(GAME.entitiesStatic.entities); window.z = entitiesToIterate; for (var i = 0; i < n; ++i) { var p = particles[i]; p[0] += p.vx; if (p[0] < 0) p[0] = p.vx *= -1; else if (p[0] > width) p[0] = width + (p.vx *= -1); p[1] += p.vy; if (p[1] < 0) p[1] = p.vy *= -1; else if (p[1] > height) p[1] = height + (p.vy *= -1); p.vx += 0.1 * (Math.random() - 0.5) - 0.01 * p.vx; p.vy += 0.1 * (Math.random() - 0.5) - 0.01 * p.vy; // use entity positions p[0] = entitiesToIterate[i].position.x; p[1] = entitiesToIterate[i].position.y; } var topology = computeTopology(voronoi(particles)); /** * Draw */ // context.clearRect(0, 0, width, height); context.rect(0, 0, width, height); context.stroke(); context.fillStyle = 'rgba(255, 255, 255, 0.2)'; context.fill(); // context.globalAlpha = 0.1; /** Draw lines */ context.beginPath(); renderMultiLineString(context, topojson.mesh(topology, topology.objects.voronoi, function(a, b) { return a !== b; })); context.strokeStyle = "rgba(0,0,0,0.5)"; context.lineWidth = 0.2; context.stroke(); /** Draw dots */ //particles.forEach(function(p, i) { //context.beginPath(); //context.arc(p[0], p[1], 2.5, 0, 2 * Math.PI); //context.fillStyle = i & 1 ? "rgba(255,0,0,1)" : "rgba(0,0,0,0.6)"; //context.fill(); //}); /** Draw topology */ context.beginPath(); renderMultiPolygon(context, topojson.merge(topology, topology.objects.voronoi.geometries.filter(function(d, i) { return i & 1; }))); context.fillStyle = "rgba(100,150,200,0.1)"; context.fillStyle = "rgba(150,150,150,0.001)"; context.fill(); context.lineWidth = 1.5; context.lineJoin = "round"; context.strokeStyle = "rgba(50,100,150,1)"; context.strokeStyle = "rgba(100,100,100,1)"; context.stroke(); /** context.shadowColor = "#a0a0a0" context.shadowOffsetX = 1; context.shadowOffsetY = 0; context.shadowBlur = 10; */ }); function computeTopology(diagram) { var cells = diagram.cells, arcs = [], arcIndex = -1, arcIndexByEdge = {}; return { objects: { voronoi: { type: "GeometryCollection", geometries: cells.map(function(cell) { var cell, site = cell.site, halfedges = cell.halfedges, cellArcs = [], clipArc; halfedges.forEach(function(halfedge) { var edge = diagram.edges[halfedge]; if (edge.right) { var l = edge.left.index, r = edge.right.index, k = l + "," + r, i = arcIndexByEdge[k]; if (i == null) arcs[i = arcIndexByEdge[k] = ++arcIndex] = edge; cellArcs.push(site === edge.left ? i : ~i); clipArc = null; } else if (clipArc) { // Coalesce border edges. if (edge.left) edge = edge.slice(); // Copy-on-write. clipArc.push(edge[1]); } else { arcs[++arcIndex] = clipArc = edge; cellArcs.push(arcIndex); } }); // Ensure the last point in the polygon is identical to the first point. var firstArcIndex = cellArcs[0], lastArcIndex = cellArcs[cellArcs.length - 1], firstArc = arcs[firstArcIndex < 0 ? ~firstArcIndex : firstArcIndex], lastArc = arcs[lastArcIndex < 0 ? ~lastArcIndex : lastArcIndex]; lastArc[lastArcIndex < 0 ? 0 : lastArc.length - 1] = firstArc[firstArcIndex < 0 ? firstArc.length - 1 : 0].slice(); return { type: "Polygon", data: site.data, arcs: [cellArcs] }; }) } }, arcs: arcs }; } function renderMultiLineString(context, line) { line.coordinates.forEach(function(line) { line.forEach(function(point, i) { if (i) context.lineTo(point[0], point[1]); else context.moveTo(point[0], point[1]); }); }); } function renderMultiPolygon(context, polygon) { polygon.coordinates.forEach(function(polygon) { polygon.forEach(function(ring) { ring.forEach(function(point, i) { if (i) context.lineTo(point[0], point[1]); else context.moveTo(point[0], point[1]); }); }); }); } </script> </body> </html>