|
<!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> |