|
;(function() { |
|
'use strict'; |
|
|
|
// CONSTANTS lol not really |
|
var SHOW_CONSOLE_FPS = false, |
|
VISUAL_MOUSE_EFFECT = false, |
|
VISUAL_SPEED_MULT = 5, // 'master' speed var |
|
VISUAL_RESPEED_STEP = 0.5, |
|
VISUAL_HUE = Math.random() * (255 + 1), |
|
VISUAL_SATURATION = 48, |
|
VISUAL_LIGHTING = 80, |
|
PARTICLE_AMOUNT = 50, |
|
PARTICLE_MAX_SIZE = 8, // px |
|
PARTICLE_MIN_SIZE = 2, |
|
PARTICLE_JITTER_RATE, |
|
PARTICLE_BLINK_RATE, |
|
KEYS = { |
|
LEFT: 37, |
|
RIGHT: 39, |
|
UP: 38, |
|
DOWN: 40, |
|
SPACE: 32 |
|
}; |
|
|
|
var frames = 0, // fps counter |
|
skip = false, // switch bool for skipping every second frame |
|
userSpeed = 0, |
|
userAmount = 0; |
|
|
|
/** |
|
* A single visualisation instance, handling updating and |
|
* drawing of visual content. |
|
*/ |
|
var Visual = function(canvasContext, width, height) { |
|
var screen = canvasContext; |
|
|
|
var self = this; |
|
self.size = { |
|
x: width, |
|
y: height |
|
}; |
|
console.log('~~~~'); |
|
console.log('Particle Visualisation started.'); |
|
console.log(' Particles: ' + PARTICLE_AMOUNT + '.'); |
|
console.log(' Size: ' + self.size.x + 'px * ' + self.size.y + 'px.'); |
|
console.log('~~~~'); |
|
self.particles = []; |
|
for (var i = 0; i < PARTICLE_AMOUNT; i++) { |
|
self.particles.push(new Particle(self, self.size)); |
|
} |
|
|
|
// per frame jobs |
|
self.tick = function() { |
|
if (!skip) { |
|
self.update(); |
|
self.draw(screen, self.size); |
|
frames++; |
|
} |
|
skip = !skip; |
|
self.animationRequest = window.requestAnimationFrame(self.tick); |
|
}; |
|
|
|
// setup cheeky hidden controls |
|
window.onkeydown = function(e) { |
|
var key = e.keyCode; |
|
|
|
if (key === KEYS.UP) { |
|
self.particles.push(new Particle(self, self.size)); |
|
} else if (key === KEYS.DOWN) { |
|
self.particles.shift(); |
|
} else if (key === KEYS.RIGHT) { |
|
var restart = VISUAL_SPEED_MULT === 0; |
|
_.cheekyRespeed(VISUAL_RESPEED_STEP); |
|
if (restart) { |
|
self.start(); |
|
} |
|
} else if (key === KEYS.LEFT) { |
|
var alreadyStopped = VISUAL_SPEED_MULT === 0; |
|
_.cheekyRespeed(-(VISUAL_RESPEED_STEP)); |
|
if (VISUAL_SPEED_MULT === 0 && !alreadyStopped) { |
|
self.stop(); |
|
} |
|
} else if (key === KEYS.SPACE) { |
|
self.changeHue(Math.random() * (255 + 1)); |
|
} |
|
}; |
|
|
|
// first tick to start things off |
|
self.tick(); |
|
}; |
|
|
|
Visual.prototype = { |
|
|
|
update: function() { |
|
this.particles.forEach(function(body) { |
|
body.update(); |
|
}); |
|
}, |
|
|
|
draw: function(screen, size) { |
|
// screen.clearRect(0, 0, size.x, size.y); |
|
this.particles.forEach(function(particle) { |
|
_.drawRect(screen, particle); |
|
}); |
|
}, |
|
|
|
// pause drawing |
|
stop: function() { |
|
_.logInfo('Paused drawing.'); |
|
window.cancelAnimationFrame(this.animationRequest); |
|
}, |
|
|
|
// unpause drawing |
|
start: function() { |
|
_.logInfo('Unpausing drawing.'); |
|
this.tick(); |
|
}, |
|
|
|
// proportionately move all visual content along resizing canvas |
|
resize: function(width, height) { |
|
var self = this; |
|
self.stop(); |
|
self.particles.forEach(function(particle) { |
|
particle.rePosition(self.size, {x: width, y: height}); |
|
}); |
|
|
|
self.size = { |
|
x: width, |
|
y: height |
|
}; |
|
self.start(); |
|
}, |
|
|
|
// changes relative hue of all content |
|
changeHue: function(hue) { |
|
var change = -(VISUAL_HUE - hue); |
|
this.particles.forEach(function(particle) { |
|
particle.adjustHue(change); |
|
}); |
|
}, |
|
|
|
// changes relative lighting of all content |
|
changeLighting: function(light) { |
|
var change = -(VISUAL_LIGHTING - light); |
|
this.particles.forEach(function(particle) { |
|
particle.adjustLighting(change); |
|
}); |
|
}, |
|
|
|
_isCollision: function(ours, theirs) { |
|
return (theirs < 0 || theirs > ours); |
|
}, |
|
|
|
_isXCollision: function(theirs) { |
|
return this._isCollision(this.size.x, theirs); |
|
}, |
|
|
|
_isYCollision: function(theirs) { |
|
return this._isCollision(this.size.y, theirs); |
|
} |
|
|
|
}; |
|
|
|
/** |
|
* A single particle, holding state and methods to change. |
|
*/ |
|
var Particle = function(visual, screenSize) { |
|
var randomSize = 2 + _.inRange(PARTICLE_MIN_SIZE, PARTICLE_MAX_SIZE), |
|
randomBlinkRate = _.zeroTo(PARTICLE_BLINK_RATE); |
|
|
|
this.visual = visual; |
|
this.size = { |
|
x: randomSize, |
|
y: randomSize |
|
}; |
|
this.center = { |
|
x: _.zeroTo(screenSize.x), |
|
y: _.zeroTo(screenSize.y) |
|
}; |
|
this.color = { |
|
h: _.roofRange(VISUAL_HUE, 5), |
|
s: _.roofRange(VISUAL_SATURATION, 5), |
|
l: _.inRange(VISUAL_LIGHTING - 40, VISUAL_LIGHTING), |
|
a: 1, |
|
getHsla: function() { |
|
return 'hsla('+this.h+','+this.s+'%,'+this.l+'%,'+this.a+')'; |
|
} |
|
}; |
|
this.color._h = this.color.h; |
|
this.color._s = this.color.s; |
|
this.color._l = this.color.l; |
|
this.dimming = true; |
|
this.blinkRate = randomBlinkRate; |
|
|
|
}; |
|
|
|
Particle.prototype = { |
|
|
|
update: function() { |
|
/* Color */ |
|
this.blink(); |
|
|
|
/* Center */ |
|
this.jitterCenter(); |
|
}, |
|
|
|
blink: function() { |
|
if (this.color.a >= 1) { |
|
this.dimming = true; |
|
} else if (this.color.a <= 0) { |
|
this.dimming = false; |
|
} |
|
|
|
this.color.a += this.dimming ? -(this.blinkRate) : this.blinkRate; |
|
// this.color.s = _.roofRange((this.color.a * 20)^10, 10); |
|
}, |
|
|
|
jitterCenter: function() { |
|
this.center.x += (_.randomSign() * _.zeroTo(PARTICLE_JITTER_RATE)); |
|
this.center.y += (_.randomSign() * _.zeroTo(PARTICLE_JITTER_RATE)); |
|
if (this.visual._isXCollision(this.center.x)) { |
|
this.center.x = this.center.x > this.visual.size.x ? this.visual.size.x - 1 : 1; |
|
} |
|
|
|
if (this.visual._isYCollision(this.center.y)) { |
|
this.center.y = this.center.y > this.visual.size.y ? this.visual.size.y - 1 : 1; |
|
} |
|
|
|
this.center.y = Math.round(this.center.y); |
|
this.center.y = Math.round(this.center.y); |
|
}, |
|
|
|
rePosition: function(oldSize, newSize) { |
|
this.center.x = newSize.x / (oldSize.x / this.center.x); |
|
this.center.y = newSize.y / (oldSize.y / this.center.y); |
|
}, |
|
|
|
adjustHue: function(amount) { |
|
this.color.h = this.color._h + amount; |
|
}, |
|
|
|
adjustLighting: function(amount) { |
|
this.color.l = this.color._l + amount; |
|
} |
|
|
|
}; |
|
|
|
/** |
|
* Utility object. Bad programmer. Bad. |
|
*/ |
|
var _ = { |
|
|
|
zeroTo: function(max) { |
|
return Math.random() * max; |
|
}, |
|
|
|
randomSign: function() { |
|
return Math.random() > 0.5 ? -1 : 1; |
|
}, |
|
|
|
roofRange: function(roof, range) { |
|
return (Math.random() * range) + (roof - range); |
|
}, |
|
|
|
inRange: function(min, max) { |
|
return min + _.zeroTo(max - min); |
|
}, |
|
|
|
getCenter: function(screenSize) { |
|
return { |
|
x: screenSize.x / 2, |
|
y: screenSize.y / 2 |
|
}; |
|
}, |
|
|
|
drawRect: function(screen, rect) { |
|
|
|
if (rect.color) { |
|
screen.fillStyle = rect.color.getHsla(); |
|
} |
|
|
|
screen.fillRect(rect.center.x - rect.size.x / 2, |
|
rect.center.y - rect.size.y / 2, |
|
rect.size.x, |
|
rect.size.y); |
|
}, |
|
|
|
logInfo: function(str) { |
|
console.log('[info] ' + str); |
|
}, |
|
|
|
cheekyRespeed: function(amount) { |
|
VISUAL_SPEED_MULT += amount; |
|
VISUAL_SPEED_MULT = VISUAL_SPEED_MULT < 0 ? 0 : VISUAL_SPEED_MULT; |
|
PARTICLE_JITTER_RATE = 0.45 * VISUAL_SPEED_MULT; |
|
PARTICLE_BLINK_RATE = 0.01 * VISUAL_SPEED_MULT; |
|
} |
|
|
|
}; |
|
_.cheekyRespeed(0); |
|
|
|
// Init on browser window |
|
window.onload = function() { |
|
|
|
var canvas = document.getElementById('particles'), |
|
canvasContext = canvas.getContext('2d'), |
|
originalSize = getSize(), |
|
currentSize, |
|
visual, |
|
intervalFps; |
|
|
|
window.onresize = resizeCanvas; |
|
|
|
setElementDimensions(canvas, originalSize.width, originalSize.height); |
|
|
|
function resizeCanvas () { |
|
var size = getSize(); |
|
|
|
if (visual) { |
|
setTimeout(function() { |
|
var newSize = getSize(); |
|
if (newSize.width === size.width && |
|
newSize.height === size.height) { |
|
|
|
setElementDimensions(canvas, size.width, size.height); |
|
visual.resize(size.width, size.height); |
|
currentSize = size; |
|
_.logInfo('Resized: ' + size.width + 'px * ' + size.height + 'px.'); |
|
|
|
} |
|
}, 1500); |
|
} else { |
|
visual = new Visual(canvasContext, size.width, size.height); |
|
currentSize = size; |
|
} |
|
|
|
} |
|
|
|
function getSize() { |
|
return { |
|
width: window.innerWidth, |
|
height: window.innerHeight |
|
}; |
|
} |
|
|
|
function setElementDimensions(el, width, height) { |
|
el.setAttribute('width', width.toString()); |
|
el.setAttribute('height', height.toString()); |
|
} |
|
|
|
window.mouseEffect = function(isOn) { |
|
if (isOn === true) { |
|
document.addEventListener('mousemove', processMouseEffect); |
|
} else if (isOn === false) { |
|
document.removeEventListener('mousemove', processMouseEffect); |
|
} |
|
}; |
|
|
|
var processMouseEffect = function(event) { |
|
var mouseLoop; |
|
if (mouseLoop) { |
|
clearTimeout(mouseLoop); |
|
} |
|
mouseLoop = setTimeout(function() { |
|
visual.stop(); |
|
visual.changeHue(255 * (event.pageX/currentSize.width)); |
|
visual.changeLighting(50 * (event.pageY/currentSize.height) + 25); |
|
visual.start(); |
|
}, 5); |
|
}; |
|
|
|
window.consoleFps = function(isOn) { |
|
if (isOn === true) { |
|
intervalFps = setInterval(function() { |
|
_.logInfo('FPS: ' + frames); |
|
frames = 0; |
|
}, 5000); |
|
} else if(isOn === false) { |
|
clearInterval(intervalFps); |
|
} |
|
}; |
|
|
|
// init canvas |
|
resizeCanvas(); |
|
|
|
// change color based on mouse position |
|
if (VISUAL_MOUSE_EFFECT) { |
|
mouseEffect(true); |
|
} |
|
|
|
if (SHOW_CONSOLE_FPS) { |
|
window.consoleFps(true); |
|
} |
|
|
|
}; |
|
|
|
})(); |