Googly pipe
This is a pseudo 3D pipe created using 2D circles placed along a circle with varying size.
<canvas id="surface"></canvas>
<canvas id="surface"></canvas>
body {
body {
background: #222;
}
var PI_BY_180 = Math.PI / 180,
var PI_BY_180 = Math.PI / 180,
G = 20,
springConstant = 0.4,
dampingConstant = 0.05,
friction = 0.90,
epsilon = 0.2,
KEYS = {
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40
};
var c = surface,
ctx = c.getContext('2d'),
cw = window.innerWidth,
ch = window.innerHeight,
last_time = Date.now(),
particles = [],
selectedParticle = 0,
keys = {},
maxStringLength = 300;
function drawRect(fill_color, x, y, width, height, _ctx) {
_ctx = _ctx || ctx;
_ctx.beginPath();
_ctx.fillStyle = fill_color || '#e22';
_ctx.rect(x, y, width, height);
_ctx.fill();
}
function drawCircle(fill_color, x, y, radius) {
ctx.beginPath();
ctx.fillStyle = fill_color;
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
ctx.fill();
}
function drawLine(color, x1, y1, x2, y2, thickness) {
ctx.beginPath();
ctx.strokeStyle = color;
if (thickness !== undefined) {
ctx.lineWidth = thickness;
}
ctx.strokeStyle = color;
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
function Particle (params) {
this.speedX = 0;
this.speedY = 0;
this.thrust = 2050;
_.extend(this, params);
this.ox = this.x;
this.oy = this.y;
this.color = 'rgb(' + random(10, 350) + ', 247, 0)';
}
Particle.prototype.doThrust = function(dx, dy) {
this.thrustData = {
dx: dx === undefined ? 0 : dx,
dy: dy === undefined ? 0 : dy
};
};
Particle.prototype.update = function(dt) {
var accX, accY;
if (selectedParticle === this.id) {
if (keys[KEYS.LEFT]) this.speedX -= this.thrust * dt;
if (keys[KEYS.RIGHT]) this.speedX += this.thrust * dt;
if (keys[KEYS.UP]) this.speedY -= this.thrust * dt;
if (keys[KEYS.DOWN]) this.speedY += this.thrust * dt;
}
if (this.thrustData) {
this.speedX += this.thrustData.dx * this.thrust * 0.4 * (this.mass) * dt;
this.speedY += this.thrustData.dy * this.thrust * 0.4 * (this.mass) * dt;
this.thrustData = null;
}
accX = -springConstant * (this.x - this.ox) - dampingConstant * this.speedX;
accY = -springConstant * (this.y - this.oy) - dampingConstant * this.speedY;
this.speedX += accX;
this.speedY += accY;
if (Math.abs(this.speedX) < epsilon) {
this.speedX = 0;
}
if (Math.abs(this.speedY) < epsilon) {
this.speedY = 0;
}
this.x += this.speedX * dt;
this.y += this.speedY * dt;
};
Particle.prototype.draw = function() {
// var color = 'rgb(' + random(10, 350) + ', 247, 0)';
// var color = selectedParticle === this.id ? 'rgb(118, 247, 0)' : 'rgb(118, 247, 0)';
drawCircle(this.color, 0, 0, 0.7 * this.mass);
ctx.font = '9pt Arial';
ctx.textAlign = 'center';
ctx.fillStyle = '#000';
// ctx.fillText(String.fromCharCode(65 + this.id), -18, 0);
};
function addListeners () {
document.addEventListener('keydown', function (e) {
keys[e.which] = true;
if ([KEYS.UP, KEYS.DOWN, KEYS.LEFT, KEYS.RIGHT].indexOf(e.which) === -1) {
selectedParticle = e.which - 65;
}
});
document.addEventListener('keyup', function (e) {
keys[e.which] = false;
// selectedParticle = -1;
});
}
function init () {
c.width = cw;
c.height = ch;
var angle = 0,
count = 180,
angleDiff = 720 / count,
displacement = 0,
radius = count * 2;
for(var i = count; i--; angle += angleDiff) {
particles.push(new Particle({
x: cw / 2 + Math.cos(angle * PI_BY_180) * radius + random(-displacement, displacement),
y: ch / 2 + Math.sin(angle * PI_BY_180) * radius + random(-displacement, displacement),
mass: count * 2 - i * 2,
id: i
}));
}
var pi = 0,
dx = [1, -1][random(1)],
dy = [1, -1][random(1)];
setInterval(function () {
// var p = particles[random(0, particles.length - 1)];
var p = particles[pi];
if (pi === particles.length) {
var p = particles[pi = 0];
dx = [1, -1][random(1)];
dy = [1, -1][random(1)];
}
p.doThrust(dx, dy);
pi++;
}, 20);
addListeners();
loop();
}
function drawStrings () {
var p2,
dist,
ratio,
stringColor,
stringThickness;
for (var i = particles.length; i--;) {
if (!i) {
p2 = particles[particles.length - 1];
}
else {
p2 = particles[i - 1];
}
dist = distance(particles[i].x, particles[i].y, p2.x, p2.y);
ratio = dist / maxStringLength;
stringColor = 'hsl(0, 0%, ' + ratio * 100 + '%)';
stringThickness = parseInt(5 / ratio, 10);
if (stringThickness > 5) {
stringThickness = 5;
}
drawLine(stringColor, particles[i].x, particles[i].y, p2.x, p2.y, stringThickness);
}
}
function loop () {
var current_time = Date.now(),
dt = (current_time - last_time) / 1000;
last_time = current_time;
ctx.clearRect(0, 0, cw, ch);
particles.forEach(function (obj) {
ctx.save();
// if (!isNaN(obj.alpha)
obj.update(dt);
ctx.globalComposition = "overlay";
!isNaN(obj.x) && !isNaN(obj.y) && ctx.translate(obj.x, obj.y);
!isNaN(obj.scale_x) && !isNaN(obj.scale_y) && ctx.scale(obj.scale_x, obj.scale_y);
!isNaN(obj.scale) && ctx.scale(obj.scale, obj.scale);
!isNaN(obj.rotation) && ctx.rotate(obj.rotation * PI_BY_180);
!isNaN(obj.alpha) && (ctx.globalAlpha = obj.alpha);
obj.draw();
ctx.restore();
});
// drawStrings();
requestAnimationFrame(loop);
}
function random (start, end) {
if (arguments.length < 2) {
end = start;
start = 0;
}
return ~~(Math.random() * (end - start + 1)) + start;
}
function distance (x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}
_ = {};
_.extend = function (obj) {
[].slice.call(arguments, 1).forEach (function (source) {
if (source) {
for (var prop in source) {
obj[prop] = source[prop];
}
}
});
return obj;
};
init();