Files
opentf/website/source/assets/javascripts/app/Engine.js
Amadeus Demarzi 61536cb1f8 Attach Chainable timers to Engine render loop
Previous to this commit, the Chainable class which is responsible for
iterating on the sequence of events in the intro Terraform animation,
was using setTimeout and thus could result in very messed up race
conditions when triggering physics changes.

This commit attaches the .wait timers to the Engine.render loop which
should enable everything to stay in sync.
2015-02-06 15:28:26 -08:00

389 lines
7.4 KiB
JavaScript

(function(
Base,
Vector,
Logo,
Grid,
Chainable
){
var sqrt, pow, Engine;
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
}
sqrt = Math.sqrt;
pow = Math.pow;
Engine = Base.extend({
scale: window.devicePixelRatio || 1,
// scale:1,
shapes : [],
particles : [],
particlesA : [],
particlesB : [],
_deferredParticles: [],
ticks: [],
starGeneratorRate: 600,
mouse: {
x: -9999,
y: -9999
},
constructor: function(canvas, background, tagLine){
this.canvas = canvas;
this.background = background;
this.tagLine = tagLine;
if (!this.canvas.getContext) {
return null;
}
this.context = this.canvas.getContext('2d');
this.setupEvents();
this.setupStarfield();
this.setupTessellation();
this.setupMisc();
this.startEngine();
},
startEngine: function(){
var parent = this.canvas.parentNode;
this.background.className += ' show';
this.canvas.style.opacity = 1;
// We have to pass the engine into Chainable to
// enable the timers to properly attach to the
// run/render loop
new Chainable(this)
.wait(1000)
.then(function(){
this.starGeneratorRate = 200;
}, this)
.wait(500)
.then(function(){
parent.className += ' state-one';
})
.wait(150)
.then(function(){
parent.className += ' state-two';
})
.wait(150)
.then(function(){
parent.className += ' state-three';
})
.wait(500)
.then(function(){
parent.className += ' state-four';
})
.wait(100)
.then(function(){
this.showShapes = true;
}, this)
.wait(1000)
.then(function(){
this.logo.startBreathing();
this.showGrid = true;
}, this)
.wait(1000)
.then(function(){
this.typewriter.start();
}, this);
this.render();
},
setupMisc: function(){
this.last = Date.now() / 1000;
this.render = this.render.bind(this);
this.typewriter = new Engine.Typewriter(this.tagLine);
},
setupEvents: function(){
this.resize = this.resize.bind(this);
this.resize();
window.addEventListener('resize', this.resize, false);
this._handleScroll = this._handleScroll.bind(this);
this._handleScroll();
window.addEventListener('scroll', this._handleScroll, false);
this._handleMouseCoords = this._handleMouseCoords.bind(this);
window.addEventListener('mousemove', this._handleMouseCoords, false);
},
setupStarfield: function(){
this.particles = [];
// this.generateParticles(50, true);
this.generateParticles(400);
},
setupTessellation: function(canvas){
var size, offset;
this.shapes = [];
if (window.innerWidth < 570) {
size = 300;
offset = 0;
} else {
size = 360;
offset = 40;
}
this.logo = new Engine.Shape(
-(size / 2),
-(size / 2 + offset),
size,
size,
Logo.points,
Logo.polygons
);
this.grid = new Engine.Shape.Puller(this.width, this.height, Grid);
},
getAverageTickTime: function(){
var sum = 0, s;
for (s = 0; s < this.ticks.length; s++) {
sum += this.ticks[s];
}
window.console.log('Average Tick Time:', sum / this.ticks.length);
},
getLongestTick: function(){
var max = 0, index, s;
for (s = 0; s < this.ticks.length; s++) {
if (this.ticks[s] > max) {
max = this.ticks[s];
index = s;
}
}
window.console.log('Max tick was:', max, 'at index:', index);
},
render: function(){
var scale = this.scale, p, particle, index;
if (this.paused) {
return;
}
if (this.scrollY > this.height) {
window.requestAnimationFrame(this.render);
return;
}
this.context.clearRect(
-(this.width / 2) * scale,
-(this.height / 2) * scale,
this.width * scale,
this.height * scale
);
this.now = Date.now() / 1000;
this.tick = Math.min(this.now - this.last, 0.017);
// We must attach the chainable timer to the engine
// run/render loop or else things can get pretty
// out of wack
if (this.updateChainTimer) {
this.updateChainTimer(this.tick);
}
// Update all particles... may need to be optimized
for (p = 0; p < this.particles.length; p++) {
this.particles[p].update(this);
}
// Batch render particles based on color
// to prevent unneeded context state change
this.context.fillStyle = '#8750c2';
for (p = 0; p < this.particlesA.length; p++) {
particle = this.particlesA[p];
if (particle.radius < 0.25) {
continue;
}
this.context.fillRect(
particle.pos.x * scale >> 0,
particle.pos.y * scale >> 0,
particle.radius * scale,
particle.radius * scale
);
}
this.context.fillStyle = '#b976ff';
for (p = 0; p < this.particlesB.length; p++) {
particle = this.particlesB[p];
if (particle.radius < 0.25) {
continue;
}
this.context.fillRect(
particle.pos.x * scale >> 0,
particle.pos.y * scale >> 0,
particle.radius * scale,
particle.radius * scale
);
}
this.particlesA.length = 0;
this.particlesB.length = 0;
// Remove destroyed particles
for (p = 0; p < this._deferredParticles.length; p++) {
index = this.particles.indexOf(this._deferredParticles.pop());
if (index >= 0) {
this.particles.splice(index, 1);
}
}
if (this.showGrid) {
this.grid
.update(this)
.draw(this.context, scale, this);
}
if (this.showShapes) {
this.logo
.update(this)
.draw(this.context, scale, this);
}
this.typewriter.update(this);
this.last = this.now;
this.generateParticles(this.starGeneratorRate * this.tick >> 0);
window.requestAnimationFrame(this.render);
},
generateParticles: function(num, fixed){
var p;
for (p = 0; p < num; p++) {
if (fixed) {
this.particles.push(new Engine.Particle.Fixed(this.width, this.height));
} else {
this.particles.push(new Engine.Particle(this.width, this.height));
}
}
},
resize: function(){
var scale = this.scale,
size, offset;
if (window.innerWidth < 570) {
this.height = 560;
} else {
this.height = 700;
}
this.width = window.innerWidth;
this.canvas.width = this.width * scale;
this.canvas.height = this.height * scale;
this.context.translate(
this.width / 2 * scale >> 0,
this.height / 2 * scale >> 0
);
this.context.lineJoin = 'bevel';
if (this.grid) {
this.grid.resize(this.width, this.height);
}
if (this.logo) {
if (this.height === 560) {
size = 300;
offset = 0;
} else {
size = 360;
offset = 40;
}
this.logo.resize(size, offset);
}
},
_handleMouseCoords: function(event){
this.mouse.x = event.pageX;
this.mouse.y = event.pageY;
},
_handleScroll: function(){
this.scrollY = window.scrollY;
},
pause: function(){
this.paused = true;
},
resume: function(){
if (!this.paused) {
return;
}
this.paused = false;
this.render();
},
getSnapshot: function(){
window.open(this.canvas.toDataURL('image/png'));
}
});
Engine.map = function(val, istart, istop, ostart, ostop) {
return ostart + (ostop - ostart) * ((val - istart) / (istop - istart));
};
Engine.getRandomFloat = function(min, max) {
return Math.random() * (max - min) + min;
};
Engine.getRandomInt = function(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
};
Engine.clone = function(ref) {
var clone = {}, key;
for (key in ref) {
clone[key] = ref[key];
}
return clone;
};
window.Engine = Engine;
})(
window.Base,
window.Vector,
window.Logo,
window.Grid,
window.Chainable
);