Simple collision sim with surprisingly mesmerizing visual results. Be sure to check out the options.
Note: Runs at max speed.
Controls:
- press
sto play/pause - press
dto toggle orange dots - press
lto toggle orange lines
Built with blockbuilder.org
| license: mit |
Simple collision sim with surprisingly mesmerizing visual results. Be sure to check out the options.
Note: Runs at max speed.
Controls:
s to play/paused to toggle orange dotsl to toggle orange linesBuilt with blockbuilder.org
| <!DOCTYPE html> | |
| <html> | |
| <head></head> | |
| <body> | |
| <div id="app"></div> | |
| <script> | |
| (function() { | |
| var options = { | |
| width: 960, | |
| height: 500, | |
| points: 100, | |
| delayedReflection: false, | |
| force: 7, // initial force | |
| startX: .5, // relative horizontal offset | |
| startY: .5, // relative vertical offset | |
| fade: .015, | |
| debugDots: true, | |
| debugLines: true | |
| }; | |
| function Simulation(bounds) { | |
| this.points = []; | |
| this.bounds = bounds; | |
| this.addPoint = function(x, y, vx, vy) { | |
| this.points.push({ | |
| x: x, | |
| y: y, | |
| vx: vx, | |
| vy: vy | |
| }); | |
| }; | |
| this.step = function(speed, delayedChange) { | |
| var i = this.points.length, p, m = -1, bx, by; | |
| speed = speed ? speed : 1; | |
| while(i--) { | |
| bx = null; by = null; | |
| p = this.points[i]; | |
| p.x += p.vx * speed; | |
| p.y += p.vy * speed; | |
| if(p.x < this.bounds[0]) { bx = this.bounds[0]; } | |
| if(p.x > this.bounds[2]) { bx = this.bounds[2]; } | |
| if(p.y < this.bounds[1]) { by = this.bounds[1]; } | |
| if(p.y > this.bounds[3]) { by = this.bounds[3]; } | |
| if(bx !== null) { | |
| p.vx *= m; | |
| if(!delayedChange) { | |
| p.x -= 2 * (p.x - bx); | |
| } | |
| } | |
| if(by !== null) { | |
| p.vy *= m; | |
| if(!delayedChange) { | |
| p.y -= 2 * (p.y - by); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| function initRadialPoints(sim, count, initialForce, width, height, relOffsetX, relOffsetY) { | |
| var i = -1, | |
| f = Math.PI * 2 / count, | |
| sx = width * relOffsetX, | |
| sy = height * relOffsetY; | |
| while(++i < options.points) { | |
| sim.addPoint( | |
| sx, | |
| sy, | |
| Math.sin(f * i) * initialForce, | |
| Math.cos(f * i) * initialForce | |
| ); | |
| } | |
| } | |
| var status = true; | |
| var debugDots = options.debugDots; | |
| var debugLines = options.debugLines; | |
| var app = document.getElementById('app'); | |
| var ctx = { | |
| live: document.createElement('canvas').getContext('2d'), | |
| lines: document.createElement('canvas').getContext('2d'), | |
| debug: document.createElement('canvas').getContext('2d'), | |
| trigger: document.createElement('canvas').getContext('2d') | |
| }; | |
| ctx.live.canvas.width = ctx.lines.canvas.width = ctx.debug.canvas.width = options.width; | |
| ctx.live.canvas.height = ctx.lines.canvas.height = ctx.debug.canvas.height = options.height; | |
| app.appendChild(ctx.live.canvas); | |
| ctx.lines.strokeStyle = 'rgba(0,0,0,.2)'; | |
| ctx.lines.fillStyle = 'rgba(255,255,255,' + options.fade + ')'; | |
| ctx.lines.lineWidth = 1; | |
| ctx.debug.fillStyle = ctx.debug.strokeStyle = '#f50'; | |
| ctx.debug.lineWidth = 2; | |
| var sim = new Simulation([0, 0, options.width - 1, options.height - 1]); | |
| initRadialPoints(sim, options.points, options.force, options.width, options.height, options.startX, options.startY); | |
| function clear(ctx, clearColor) { | |
| if(!clearColor) { | |
| ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); | |
| return; | |
| } | |
| var fs = clearColor !== ctx.fillStyle ? ctx.fillStyle : null; | |
| fs && (ctx.fillStyle = clearColor); | |
| ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); | |
| fs && (ctx.fillStyle = fs); | |
| } | |
| function drawLines(ctx, points, scale) { | |
| ctx.beginPath(); | |
| scale = scale || 1; | |
| ctx.moveTo(points[0].x * scale, points[0].y * scale); | |
| for(var i = 1; i < points.length; i++) { | |
| ctx.lineTo(points[i].x * scale, points[i].y * scale); | |
| } | |
| ctx.closePath(); | |
| ctx.stroke(); | |
| } | |
| function drawDots(ctx, points, radius) { | |
| for(var i = 0; i < points.length; i++) { | |
| ctx.beginPath(); | |
| ctx.arc(points[i].x, points[i].y, radius, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| } | |
| function play() { | |
| if(status) { | |
| sim.step(null, options.delayedReflection); | |
| clear(ctx.live); | |
| clear(ctx.lines, ctx.lines.fillStyle); | |
| drawLines(ctx.lines, sim.points, true); | |
| ctx.live.drawImage(ctx.lines.canvas, 0, 0); | |
| (debugDots || debugLines) && clear(ctx.debug); | |
| debugLines && drawLines(ctx.debug, sim.points); | |
| debugDots && drawDots(ctx.debug, sim.points, 3); | |
| (debugDots || debugLines) && ctx.live.drawImage(ctx.debug.canvas, 0, 0); | |
| } | |
| requestAnimationFrame(play); | |
| } | |
| document.addEventListener('keydown', function(e) { | |
| if(e.ctrlKey || e.metaKey || e.altKey) { | |
| return; | |
| } | |
| switch(e.key) { | |
| case 's': status = !status; break; | |
| case 'd': debugDots = !debugDots; break; | |
| case 'l': debugLines = !debugLines; break; | |
| } | |
| }); | |
| play(); | |
| }()); | |
| </script> | |
| </body> | |
| </html> |