Better keyframes.

This commit is contained in:
George Michael Brower 2011-02-08 10:40:38 -05:00
parent 046ef1e1c8
commit f96b1f2e9a
9 changed files with 531 additions and 119 deletions

View File

@ -44,8 +44,10 @@ GUI.Controller.prototype.unlisten = function() {
}
GUI.Controller.prototype.setValue = function(n) {
this.object[this.propertyName] = n;
for (var i in this.changeListeners) {
console.log("Telling you I changed to " + n);
this.changeListeners[i].call(this, n);
}
// Whenever you call setValue, the display will be updated automatically.

View File

@ -13,8 +13,8 @@ GUI.StringController = function() {
input.setAttribute('spellcheck', 'false');
this.domElement.addEventListener('mouseup', function() {
input.focus();
input.select();
//input.focus();
//input.select();
}, false);
// TODO: getting messed up on ctrl a

View File

@ -17,6 +17,7 @@ h1, h2, h3, h4, h5, h6 {
color: #222;
}
hr {
border: 0;
height: 0;

View File

@ -44,8 +44,8 @@ function FizzyText(message) {
var width = 550;
var height = 200;
var textAscent = 82;
var textOffsetLeft = 80;
var textAscent = 140;
var textOffsetLeft = 20;
var noiseScale = 300;
var frameTime = 30;

1
gui.js
View File

@ -365,6 +365,7 @@ var GUI = function() {
for (var i in controllers) {
controllerHeight += controllers[i].domElement.offsetHeight;
}
return;
if (controllerHeight - 1 > openHeight) {
controllerContainer.style.overflowY = "auto";
} else {

View File

@ -33,13 +33,13 @@
</script>
<script type="text/javascript">
//<![CDATA[
var timer;
window.onload = function() {
var fizzyText = new FizzyText("gui-dat");
var gui = new GUI();
var timer = new GUI.Timer(gui);
timer = new GUI.Timer(gui);
// Text field
@ -71,7 +71,9 @@
gui.divider();
// gui.add(timer, "playhead").step(100).listen();
gui.add(timer, "playPause");
// gui.add(timer, "playPause");
gui.add(timer, "snapIncrement");
gui.add(timer, "useSnap");
/*
gui.add(timer, "windowMin").listen();
gui.add(timer, "windowWidth").listen();

249
time/easing.js Normal file
View File

@ -0,0 +1,249 @@
TWEEN.Easing = { Linear: {}, Quadratic: {}, Cubic: {}, Quartic: {}, Quintic: {}, Sinusoidal: {}, Exponential: {}, Circular: {}, Elastic: {}, Back: {}, Bounce: {} };
TWEEN.Easing.Linear.EaseNone = function ( k ) {
return k;
};
//
TWEEN.Easing.Quadratic.EaseIn = function ( k ) {
return k * k;
};
TWEEN.Easing.Quadratic.EaseOut = function ( k ) {
return - k * ( k - 2 );
};
TWEEN.Easing.Quadratic.EaseInOut = function ( k ) {
if ( ( k *= 2 ) < 1 ) return 0.5 * k * k;
return - 0.5 * ( --k * ( k - 2 ) - 1 );
};
//
TWEEN.Easing.Cubic.EaseIn = function ( k ) {
return k * k * k;
};
TWEEN.Easing.Cubic.EaseOut = function ( k ) {
return --k * k * k + 1;
};
TWEEN.Easing.Cubic.EaseInOut = function ( k ) {
if ( ( k *= 2 ) < 1 ) return 0.5 * k * k * k;
return 0.5 * ( ( k -= 2 ) * k * k + 2 );
};
//
TWEEN.Easing.Quartic.EaseIn = function ( k ) {
return k * k * k * k;
};
TWEEN.Easing.Quartic.EaseOut = function ( k ) {
return - ( --k * k * k * k - 1 );
}
TWEEN.Easing.Quartic.EaseInOut = function ( k ) {
if ( ( k *= 2 ) < 1) return 0.5 * k * k * k * k;
return - 0.5 * ( ( k -= 2 ) * k * k * k - 2 );
};
//
TWEEN.Easing.Quintic.EaseIn = function ( k ) {
return k * k * k * k * k;
};
TWEEN.Easing.Quintic.EaseOut = function ( k ) {
return ( k = k - 1 ) * k * k * k * k + 1;
};
TWEEN.Easing.Quintic.EaseInOut = function ( k ) {
if ( ( k *= 2 ) < 1 ) return 0.5 * k * k * k * k * k;
return 0.5 * ( ( k -= 2 ) * k * k * k * k + 2 );
};
//
TWEEN.Easing.Sinusoidal.EaseIn = function ( k ) {
return - Math.cos( k * Math.PI / 2 ) + 1;
};
TWEEN.Easing.Sinusoidal.EaseOut = function ( k ) {
return Math.sin( k * Math.PI / 2 );
};
TWEEN.Easing.Sinusoidal.EaseInOut = function ( k ) {
return - 0.5 * ( Math.cos( Math.PI * k ) - 1 );
};
//
TWEEN.Easing.Exponential.EaseIn = function ( k ) {
return k == 0 ? 0 : Math.pow( 2, 10 * ( k - 1 ) );
};
TWEEN.Easing.Exponential.EaseOut = function ( k ) {
return k == 1 ? 1 : - Math.pow( 2, - 10 * k ) + 1;
};
TWEEN.Easing.Exponential.EaseInOut = function ( k ) {
if ( k == 0 ) return 0;
if ( k == 1 ) return 1;
if ( ( k *= 2 ) < 1 ) return 0.5 * Math.pow( 2, 10 * ( k - 1 ) );
return 0.5 * ( - Math.pow( 2, - 10 * ( k - 1 ) ) + 2 );
};
//
TWEEN.Easing.Circular.EaseIn = function ( k ) {
return - ( Math.sqrt( 1 - k * k ) - 1);
};
TWEEN.Easing.Circular.EaseOut = function ( k ) {
return Math.sqrt( 1 - --k * k );
};
TWEEN.Easing.Circular.EaseInOut = function ( k ) {
if ( ( k /= 0.5 ) < 1) return - 0.5 * ( Math.sqrt( 1 - k * k) - 1);
return 0.5 * ( Math.sqrt( 1 - ( k -= 2) * k) + 1);
};
//
TWEEN.Easing.Elastic.EaseIn = function( k ) {
var s, a = 0.1, p = 0.4;
if ( k == 0 ) return 0; if ( k == 1 ) return 1; if ( !p ) p = 0.3;
if ( !a || a < 1 ) { a = 1; s = p / 4; }
else s = p / ( 2 * Math.PI ) * Math.asin( 1 / a );
return - ( a * Math.pow( 2, 10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) );
};
TWEEN.Easing.Elastic.EaseOut = function( k ) {
var s, a = 0.1, p = 0.4;
if ( k == 0 ) return 0; if ( k == 1 ) return 1; if ( !p ) p = 0.3;
if ( !a || a < 1 ) { a = 1; s = p / 4; }
else s = p / ( 2 * Math.PI ) * Math.asin( 1 / a );
return ( a * Math.pow( 2, - 10 * k) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) + 1 );
};
TWEEN.Easing.Elastic.EaseInOut = function( k ) {
var s, a = 0.1, p = 0.4;
if ( k == 0 ) return 0; if ( k == 1 ) return 1; if ( !p ) p = 0.3;
if ( !a || a < 1 ) { a = 1; s = p / 4; }
else s = p / ( 2 * Math.PI ) * Math.asin( 1 / a );
if ( ( k *= 2 ) < 1 ) return - 0.5 * ( a * Math.pow( 2, 10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) );
return a * Math.pow( 2, -10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) * 0.5 + 1;
};
//
TWEEN.Easing.Back.EaseIn = function( k ) {
var s = 1.70158;
return k * k * ( ( s + 1 ) * k - s );
};
TWEEN.Easing.Back.EaseOut = function( k ) {
var s = 1.70158;
return ( k = k - 1 ) * k * ( ( s + 1 ) * k + s ) + 1;
};
TWEEN.Easing.Back.EaseInOut = function( k ) {
var s = 1.70158 * 1.525;
if ( ( k *= 2 ) < 1 ) return 0.5 * ( k * k * ( ( s + 1 ) * k - s ) );
return 0.5 * ( ( k -= 2 ) * k * ( ( s + 1 ) * k + s ) + 2 );
};
//
TWEEN.Easing.Bounce.EaseIn = function( k ) {
return 1 - TWEEN.Easing.Bounce.EaseOut( 1 - k );
};
TWEEN.Easing.Bounce.EaseOut = function( k ) {
if ( ( k /= 1 ) < ( 1 / 2.75 ) ) {
return 7.5625 * k * k;
} else if ( k < ( 2 / 2.75 ) ) {
return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75;
} else if ( k < ( 2.5 / 2.75 ) ) {
return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375;
} else {
return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375;
}
};
TWEEN.Easing.Bounce.EaseInOut = function( k ) {
if ( k < 0.5 ) return TWEEN.Easing.Bounce.EaseIn( k * 2 ) * 0.5;
return TWEEN.Easing.Bounce.EaseOut( k * 2 - 1 ) * 0.5 + 0.5;
};

View File

@ -1,6 +1,5 @@
// Would really love to make it so that as FEW changes as possible are required to gui.js in order to make this work. Would love to make it so you simply include gui.scrubber.min.js in addition to gui.min.js.
GUI.Controller.prototype.at = function(when, what, tween) {
// TODO: tween
this.scrubber.add(new GUI.ScrubberPoint(this.scrubber, when, what));
this.scrubber.render();
return this;
@ -12,32 +11,54 @@ GUI.Scrubber = function(controller, timer) {
this.points = [];
this.timer = timer;
this.timer.scrubbers.push(this);
this.controller = controller;
this.controller.scrubber = this;
this.playing = false;
var previouslyHandled;
this.getSaveObject = function() {
var pointArray = [];
for (var i in this.points) {
pointArray.push(this.points[i].getSaveObject());
}
var obj = {'points': pointArray};
return obj;
};
this.sort = function() {
this.points.sort(function(a,b) {
return a.time - b.time;
});
}
};
this.add = function(p) {
this.points.push(p);
this.getSaveObject();
this.sort();
}
};
var lastDown = 0;
this.controller.addChangeListener(function(newVal) {
if (!_this.playing) {
var v = newVal;
if (_this.controller.type == "boolean") {
v = !v; // Couldn't tell you why I have to do this.
}
if (_this.timer.activePoint == null) {
_this.timer.activePoint = new GUI.ScrubberPoint(_this, _this.timer.playhead, newVal);
_this.timer.activePoint = new GUI.ScrubberPoint(_this, _this.timer.playhead, v);
_this.add(_this.timer.activePoint);
_this.render();
} else {
_this.timer.activePoint.value = newVal;
_this.timer.activePoint.value = v;
}
}
@ -57,11 +78,11 @@ GUI.Scrubber = function(controller, timer) {
var mx, pmx;
this.__defineGetter__("width", function() {
this.__defineGetter__('width', function() {
return width;
});
this.__defineGetter__("height", function() {
this.__defineGetter__('height', function() {
return height;
});
@ -70,15 +91,43 @@ GUI.Scrubber = function(controller, timer) {
this.render = function() {
// TODO: if visible ...
_this.g.clearRect(0, 0, width, height);
// Draw 0
if (_this.timer.windowMin < 0) {
var x = GUI.map(0, _this.timer.windowMin, _this.timer.windowMin+_this.timer.windowWidth, 0, width);
_this.g.fillStyle = '#000';
_this.g.fillRect(0, 0, x, height-1);
}
// Draw ticks
if (_this.timer.useSnap) {
_this.g.lineWidth = 1;
for (var i = _this.timer.snap(_this.timer.windowMin); i < _this.timer.windowMin+_this.timer.windowWidth; i+= _this.timer.snapIncrement) {
if (i == 0) continue;
var x = Math.round(GUI.map(i, _this.timer.windowMin, _this.timer.windowMin+_this.timer.windowWidth, 0, width))+0.5;
if (i < 0) {
_this.g.strokeStyle = '#111111';
} else {
_this.g.strokeStyle = '#282828';
}
_this.g.beginPath();
_this.g.moveTo(x, 0);
_this.g.lineTo(x, height-1);
_this.g.stroke();
}
}
// Draw points
for (var i in _this.points) {
_this.points[i].render();
}
// Draw playhead
_this.g.strokeStyle = "#ff0024";
_this.g.strokeStyle = '#ff0024';
_this.g.lineWidth = 1;
var t = Math.round(GUI.map(_this.timer.playhead, _this.timer.windowMin, _this.timer.windowMin+_this.timer.windowWidth, 0, width))+0.5;
_this.g.beginPath();
@ -90,8 +139,6 @@ GUI.Scrubber = function(controller, timer) {
this.render();
var onResize = function() {
canvas.width = width = _this.domElement.offsetWidth;
canvas.height = height = _this.domElement.offsetHeight;
@ -120,51 +167,64 @@ GUI.Scrubber = function(controller, timer) {
}
}
var scrub = function(e) {
var t = GUI.map(e.pageX, position.left, position.left+width, _this.timer.windowMin, _this.timer.windowMin+_this.timer.windowWidth);
_this.timer.playhead = t;
_this.timer.playhead = _this.timer.snap(t);
scrubPan();
}
var pan = function(e) {
mx = e.pageX;
var t = GUI.map(mx - pmx, 0, width, 0, _this.timer.windowWidth);
_this.timer.windowMin -= t;
pmx = mx;
}
var dragActive = function(e) {
mx = e.pageX;
var t = GUI.map(mx - pmx, 0, width, 0, _this.timer.windowWidth);
_this.timer.activePoint.time += t;
var t = GUI.map(e.pageX, position.left, position.left+width, _this.timer.windowMin, _this.timer.windowMin+_this.timer.windowWidth);
_this.timer.activePoint.time = _this.timer.snap(t);
_this.timer.playhead = _this.timer.snap(t);
pmx = mx;
_this.sort();
_this.timer.playhead += t;
}
canvas.addEventListener('mousedown', function(e) {
if (false) {
e.preventDefault();
document.addEventListener('mousemove', pan, false);
return false;
}
var thisDown = GUI.millis();
// Double click creates a keyframe
if (thisDown - lastDown < 300) {
_this.timer.activePoint = new GUI.ScrubberPoint(_this, _this.timer.playhead, _this.controller.getValue());
_this.add(_this.timer.activePoint);
_this.render();
} else if (_this.timer.hoverPoint != null ) {
// A regular click COULD select a point ...
} else if (_this.timer.hoverPoint != null) {
_this.timer.activePoint = _this.timer.hoverPoint;
_this.timer.playhead = _this.timer.activePoint.time;
pmx = mx = e.pageX;
document.addEventListener("mousemove", dragActive, false);
_this.timer.playhead = _this.timer.snap(_this.timer.activePoint.time);
pmx = mx = e.pageX;
document.addEventListener('mousemove', dragActive, false);
// Or we could just be trying to place the playhead/scrub.
} else {
_this.timer.activePoint = null;
_this.timer.hoverPoint = null;
scrub(e);
document.body.style.cursor = "text";
document.body.style.cursor = 'text';
_this.timer.pause();
pmx = mx = e.pageX;
document.addEventListener('mousemove', scrub, false);
_this.render();
@ -174,6 +234,19 @@ GUI.Scrubber = function(controller, timer) {
}, false);
canvas.addEventListener('mousewheel', function(e) {
e.preventDefault();
var dx = e.wheelDeltaX*4;
var dy = e.wheelDeltaY*4;
_this.timer.windowWidth -= dy;
_this.timer.windowMin += dy/2 + dx;
return false;
}, false);
canvas.addEventListener('mousemove', function(e) {
_this.timer.hoverPoint = null;
for (var i in _this.points) {
@ -183,107 +256,112 @@ GUI.Scrubber = function(controller, timer) {
}
}
if (_this.timer.hoverPoint == null) {
document.body.style.cursor = "pointer";
document.body.style.cursor = 'pointer';
} else {
document.body.style.cursor = "auto";
document.body.style.cursor = 'auto';
}
_this.render();
});
document.addEventListener('mouseup', function() {
document.body.style.cursor = "auto";
document.removeEventListener("mousemove", dragActive, false);
document.body.style.cursor = 'auto';
document.removeEventListener('mousemove', dragActive, false);
document.removeEventListener('mousemove', scrub, false);
document.removeEventListener('mousemove', pan, false);
}, false);
onResize();
this.timer.addPlayListener(this.render);
var handlePoint = function(point) {
if (point != previouslyHandled) {
previouslyHandled = point;
_this.controller.setValue(point.value);
}
};
var onPlayChange = function(curTime, prevTime) {
if (_this.points.length == 0) return;
_this.playing = true;
// This assumes a SORTED point array
// And a PROGRESSING/INCREASING/GROWING playhead
if (_this.controller.type == 'function') {
if (_this.controller.type == "number" ||
_this.controller.type == "string") {
var closestToLeft = null;
for (var i = 0; i < _this.points.length; i++) {
var cur = _this.points[i];
if (cur.time >= curTime && i > 0) {
closestToLeft = _this.points[i-1];
break;
for (var i = 0; i < _this.points.length; i++) {
var t = _this.points[i].time;
if ((curTime > prevTime && prevTime < t && t < curTime) ||
(curTime < prevTime && prevTime > t && t > curTime)) {
_this.controller.getValue().call(this);
}
}
}
if (closestToLeft != null && closestToLeft.time <= curTime &&
_this.controller.type == "number") {
var n = closestToLeft.next;
if (n != null) {
// Interpolate.
var t = GUI.map(curTime, closestToLeft.time, n.time, 0, 1);
t = closestToLeft.tween(t);
var val = GUI.map(t, 0, 1, closestToLeft.value, n.value);
_this.controller.setValue(val);
}
} else if (closestToLeft != null) {
_this.controller.setValue(closestToLeft.value);
}
} else {
var prev = undefined, next = undefined;
// Find "surrounding" points.
for (var i = 0; i < _this.points.length; i++) {
var t = _this.points[i].time;
if (t > curTime) {
var cur = _this.points[i];
if (prevTime < curTime) {
if (i == 0) {
prev = null;
next = _this.points[i];
break;
if (cur.time < prevTime) {
continue;
}
} else {
prev = _this.points[i-1];
next = _this.points[i];
break;
if (cur.time >= prevTime && cur.time <= curTime) {
pointHandlers[_this.controller.type].call(_this, cur);
}
}
}
if (next == undefined) {
prev = _this.points[_this.points.length-1];
next = null;
}
console.log(next, prev);
if (next != null & prev != null) {
if (_this.controller.type == 'number') {
var t = prev.tween(GUI.map(curTime, prev.time, next.time, 0, 1));
_this.controller.setValue(GUI.map(t, 0, 1, prev.value, next.value));
} else {
handlePoint(prev);
}
} else if (next != null) {
handlePoint(next);
} else if (prev != null) {
handlePoint(prev);
}
}
_this.playing = false;
};
var pointHandlers = {
'function': function(point) {
this.controller.getValue().call(this);
},
'boolean': function(point) {
this.controller.setValue(point.value);
},
'string': function(point) {
this.controller.setValue(point.value);
},
}
this.timer.addPlayListener(onPlayChange);
this.timer.addWindowListener(this.render);
@ -293,8 +371,6 @@ GUI.Scrubber = function(controller, timer) {
GUI.ScrubberPoint = function(scrubber, time, value) {
var _this = this;
var g = scrubber.g;
@ -304,13 +380,37 @@ GUI.ScrubberPoint = function(scrubber, time, value) {
this.hold = false;
var val;
this.__defineSetter__("value", function(v) {
val = v;
scrubber.render();
});
this.value = value;
this.__defineGetter__("value", function() {
return val;
});
var barSize = 4;
var rectSize = 7;
var c1 = "#ffd800";
var c2 = "#ff9000";
var c1 = '#ffd800';
var c2 = '#ff9000';
this.getSaveObject = function() {
var obj = { 'value': _this.value, 'time': time };
if (this.hold) {
obj.hold = true;
}
// TODO: save tweens
return obj;
};
this.tween = function(t) {
return t;
@ -325,7 +425,7 @@ GUI.ScrubberPoint = function(scrubber, time, value) {
return xx >= x-rectSize/2 && xx <= x+rectSize/2;
};
this.__defineGetter__("next", function() {
this.__defineGetter__('next', function() {
if (scrubber.points.length <= 1) {
return null;
}
@ -339,10 +439,10 @@ GUI.ScrubberPoint = function(scrubber, time, value) {
});
this.__defineGetter__("time", function() {
this.__defineGetter__('time', function() {
return time;
});
this.__defineSetter__("time", function(s) {
this.__defineSetter__('time', function(s) {
time = s;
});
@ -359,16 +459,16 @@ GUI.ScrubberPoint = function(scrubber, time, value) {
y = scrubber.height/2;
if (scrubber.timer.activePoint == this) {
g.fillStyle = "#ffd800"; //
g.fillStyle = '#ffd800'; //
} else if (scrubber.timer.hoverPoint == this) {
g.fillStyle = "#999";
g.fillStyle = '#999';
} else {
g.fillStyle = "#ccc";
g.fillStyle = '#ccc';
}
switch (type) {
case "boolean":
case 'boolean':
g.save();
@ -393,7 +493,7 @@ GUI.ScrubberPoint = function(scrubber, time, value) {
break;
case "number":
case 'number':
g.save();
var n = this.next;
@ -402,11 +502,10 @@ GUI.ScrubberPoint = function(scrubber, time, value) {
var nx = GUI.constrain(GUI.map(n.time, timer.windowMin, timer.windowMin+timer.windowWidth, 0, 1));
nx = GUI.constrain(GUI.map(nx, 0, 1, 0, scrubber.width));
nx = GUI.constrain(GUI.map(nx, 0, 1, 0, scrubber.width));
g.lineWidth = rectSize/2
g.strokeStyle="#222";
g.strokeStyle='#222';
g.beginPath();
g.moveTo(nx, y);
g.lineTo(x, y);

View File

@ -15,6 +15,7 @@ GUI.Timer = function(gui) {
var playhead = 0;
var lastPlayhead = 0;
var playListeners = [];
var windowListeners = [];
@ -29,6 +30,46 @@ GUI.Timer = function(gui) {
var playing = false;
var snapIncrement = 250;
var useSnap = false;
this.__defineGetter__("useSnap", function() {
return useSnap;
});
this.__defineSetter__("useSnap", function(v) {
useSnap = v;
for (var i in _this.scrubbers) {
_this.scrubbers[i].render();
};
});
this.__defineGetter__("snapIncrement", function() {
return snapIncrement;
});
this.__defineSetter__("snapIncrement", function(v) {
if (snapIncrement > 0) {
snapIncrement = v;
for (var i in _this.scrubbers) {
_this.scrubbers[i].render();
};
}
});
this.snap = function(t) {
if (!this.useSnap) {
return t;
}
var r = Math.round(t/this.snapIncrement)*this.snapIncrement;
return r;
}
this.scrubbers = [];
window.addEventListener("keyup", function(e) {
switch (e.keyCode) {
case 32:
@ -44,9 +85,26 @@ GUI.Timer = function(gui) {
}
break;
}
console.log(e.keyCode);
}, false);
this.getSaveObject = function() {
var scrubberArr = [];
for (var i in _this.scrubbers) {
scrubberArr.push(_this.scrubbers[i].getSaveObject());
}
var obj = {'windowMin':_this.windowMin,
'windowWidth':_this.windowWidth,
'playhead':_this.playhead,
'snapIncrement': _this.snapIncrement,
'scrubbers': scrubberArr};
return obj;
};
this.__defineGetter__("windowMin", function() {
return windowMin;
});