JSBIN https://jsbin.com/deritexoya/edit?html,css,js,console,output
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div class="eye"></div>
<!-- Cozmo-like expressions testing -->
<canvas id="myCanvas"></canvas>
</body>
</html>
CSS
.eye {
display: block;
position: absolute;
height: 10px;
width: 10px;
left: 0px;
top: 0px;
background: red;
display: none;
}
#myCanvas {
border: 1px solid black;
display: block;
position: absolute;
}
JS
// quick sprite-based animation on Espruino
var anims = {
'action1': [
new Uint8Array([
0b00000000,
0b01000010,
0b10100101,
0b00000000,
0b10011001,
0b01000010,
0b00111100,
0b00000000,
]),//.buffer,
new Uint8Array([
1, //0b11111111,
2, //0b11111111,
3, //0b11000011,
4, //0b11000011,
5, //0b11000011,
6, //0b11000011,
7, //0b11111111,
8 //0b11111111,
]),//.buffer,
new Uint8Array([
9, //0b11111111,
10, //0b11000011,
11, //0b11000011,
12, //0b11000011,
13, //0b11000011,
14, //0b11000011,
15, //0b11000011,
16 //0b11111111,
]), //.buffer,
new Uint8Array([
17, //0b11111111,
18, //0b11000011,
19, //0b11000011,
20, //0b11000011,
21, //0b11000011,
22, //0b11000011,
23, //0b11000011,
24 //0b11111111,
]), //.buffer,
new Uint8Array([
0b00000000,
0b01000010,
0b10100101,
0b00000000,
0b10011001,
0b01000010,
0b00111100,
0b00000000,
]),//.buffer,
]
}
// type of animation:
// - what to do on anim end
// - looping indefinately, n times or not at all
// - if looping, from end to beginng or forth & back
var animEnd = function(){ console.log('animation complete'); }
var looping = 0; // -1 == infinite, 0 == not looping, n == loop times
var animLoopType = 0; // 0 == beginning to end & over, 1 == forth & back
var animName = 'action1'; // the anim name ( if we have more than one anim )
var animDir = 0; // 0 == forward, 1 == backward
var animIdx = 0; // the step we're in for a particular anim
var animRate = 1000; // the speed of our anim
var animated = true; // whether or not to continue animating at animRate intervals
var animItr = -1; // the interval used for refreshing & stepping through the anim
var animate = function(callback){
console.log('animate() called ..');
if(animated === true){
console.log('animated is true ..');
// check end of anim depending on animLoopType ( end of anim & not looping ? callback if any & set "animate" to false )
if(animLoopType == 1 && animDir == 1 && looping === 0 && animIdx === 0){
// back & forth non-infinite anim just ended
//refreshDisplay4(); // last refresh
refreshDisplay5();
console.log('back & forth non-infinite anim just ended: animIdx ==' + animIdx);
animated = false;
if(callback) callback();
clearInterval(animItr);
animItr = -1;
} else if (animLoopType === 0 && looping === 0 && anims[animName].length-1 == animIdx){
// beginning to end & over non-infinite anim just ended
//refreshDisplay4(); // last refresh
refreshDisplay5();
console.log('beginning to end & over non-infinite anim just ended: animIdx ==' + animIdx);
animated = false;
if(callback) callback();
clearInterval(animItr);
animItr = -1;
// TODO: end of anim & looping ? if not infinite, decrease loop times & act depending on loop type
} else { // not end of anim ? continue stepping through the anim base on animDir & animIdx
console.log('not end of animation: animIdx ==' + animIdx);
// else, update based on stuff
if (animLoopType === 0 && animIdx <= anims[animName].length ){ animIdx++; }
else if (animLoopType === 1 && animDir === 0 && anims[animName].length-1 == animIdx ){ animIdx--; animDir = 1; }
else if (animLoopType === 1 && animDir === 0 && animIdx < anims[animName].length ){ animIdx++; }
else if (animLoopType === 1 && animDir === 1 && animIdx > 0 ){ animIdx--; }
// refresh the display
//refreshDisplay(); // debug helper
//console.clear();
//refreshDisplay4();
refreshDisplay5();
//animated = false; // debug
// way A: new Uint8Array(g.buffer).set(anims[animName][animIdx]); // for full screen anims
// way B: g.drawImage({width: 8, height: 8, bpp: 1, buffer: anims[animName][animIdx], x, y});
// g.flip();
}
} else {
console.log('animated is false ..');
// stop anim & reset anim itr
//clearInterval(animItr);
//animItr = -1;
}
// fail switch ;p
//animated = false;
//clearInterval(animItr);
}
// ---- DISPLAY HELPERS, aka FAKE SCREEN IN CONSOLE ;P ----
// our display helper
var refreshDisplay = function(){
var logStr = '( %c';
for(var i=0; i< animIdx; i++){ logStr+= '['+ anims[animName][i] + '] '; } // add items before
logStr += ') %c['; logStr += anims[animName][animIdx]; logStr += '] %c(';
for(var i=animIdx+1; i< anims[animName].length; i++){ logStr+= '['+ anims[animName][i] + '] '; } // add items after
logStr += ');'
console.log(logStr, 'color: black;', 'color: blue;', 'color: black;');
}
// modded version of the above to display arrays as block rows
var refreshDisplay2 = function(){
var logStr = '%c';
for(var i=0; i< animIdx; i++){ logStr+= ''+ anims[animName][i] + '\n'; } // add items before
logStr += '%c'; logStr += anims[animName][animIdx]; logStr += '%c\n';
for(var i=animIdx+1; i< anims[animName].length; i++){ logStr+= ''+ anims[animName][i] + '\n'; } // add items after
logStr += ''
console.log(logStr, 'color: black;', 'color: blue;', 'color: black;');
}
// modded version of the above to display stuff as bins ( TODO: pad then parse )
var refreshDisplay3 = function(){
var logStr = '%c';
for(var i=0; i< animIdx; i++){ logStr+= ''+ anims[animName][i].toString(2) + '\n'; } // add items before
logStr += '%c';
for(var y=0; y< anims[animName][animIdx].length; y++){ logStr += anims[animName][animIdx][y].toString(2) + '\n'; }
logStr += '%c\n';
for(var i=animIdx+1; i< anims[animName].length; i++){ logStr+= ''+ anims[animName][i].toString(2) + '\n'; } // add items after
logStr += ''
console.log(logStr, 'color: black;', 'color: blue;', 'color: black;');
}
// "final" helper drawing "pixels" within the console ;P
var styles0 = [
//'background: gray' // debug
'background: rgba(255, 255, 255, .7)'
, 'color: black' // nice for debug/testing & displaying overlaying value
//, 'color: rgba(255, 255, 255, .7)' // nice for overlaying value - when using gray background
//, 'color: rgba(0, 0, 0, .7)' // nice for overlaying value - when using white-ish background
//, 'color: transparent' // when not using overlays
, 'display: block'
, 'line-height: 13px'
, 'text-align: left'
, 'text-indent: 6.5px'
, 'font-family: Monospace'
, 'font-weight: 13px'
].join(';');
var styles1 = [
'background: black'
, 'color: white' // nice for debug/testing & displaying overlaying value
//, 'color: rgba(255, 255, 255, .7)' // nice for overlaying value
//, 'color: transparent' // when not using overlays
, 'display: block'
//, 'text-shadow: 0 1px 0 rgba(0, 0, 0, 0.3)'
//, 'box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4) inset, 0 5px 3px -5px rgba(0, 0, 0, 0.5), 0 -13px 5px -10px rgba(255, 255, 255, 0.4) inset'
, 'line-height: 13px'
, 'text-align: left'
, 'text-indent: 6.5px'
//, 'font-weight: bold'
, 'font-family: Monospace'
, 'font-weight: 13px'
].join(';');
//console.log('%c0b1111%c1111\n0b1111%c1111', styles0, styles1, styles0);
//console.log('%c %c \n %c ', styles0, styles1, styles0);
//console.log('%c %c \n %c ', styles0, styles1, styles0); // correct "pixel" ratio
//console.log('%c00%c11\n11%c00', styles0, styles1, styles0); // correct "pixel" ratio
//console.log('%c0 %c1 \n1 %c0 ', styles0, styles1, styles0); // correct "pixel" ratio
//console.log('%c0%c1\n1%c0', styles0, styles1, styles0); // correct "pixel" ratio with only one "bin digit" ( 0 or 1 ) <--- THE NEAT ONE
//console.log('%c %c \n %c ', styles0, styles1, styles0);
var paddingBits = 8; // 1 byte padding for now - could == Uint8Array.byteLength ?
//var paddingBits = 16;
var styles = []; //['color: black;'];
var refreshDisplay4 = function(){
styles = ['color: black;'];
var logStr = '%c';
for(var i=0; i< animIdx; i++){ logStr+= ''+ anims[animName][i].toString(2) + '\n'; } // add items before
//logStr += '%c';
for(var y=0; y< anims[animName][animIdx].length; y++){
//logStr += anims[animName][animIdx][y].toString(2) + '\n';
// get, pad & parse
var binStr = anims[animName][animIdx][y].toString(2);
if( binStr.length < paddingBits){
var padding = '';
for(var p=0; p < paddingBits - binStr.length; p++){ padding += '0'; }
binStr = padding+binStr; // prefix it with padding
}
//logStr += binStr + ' ( unpadded: ' + anims[animName][animIdx][y].toString(2) +')\n'; // works fine
// loop over padded bin str
var tmpStr = '';
for(var s=0; s < binStr.length; s++){
// if s === 0, add style0, else add style 2
tmpStr += ('%c' + binStr[s]); // add to logStr
if(binStr[s] === "0") styles.push(styles0);
else styles.push(styles1);
}
//logStr += tmpStr + ' ( padded' + binStr + 'unpadded: ' + anims[animName][animIdx][y].toString(2) +')\n'; // works fine
logStr += (tmpStr + '\n');
}
logStr += '%c\n';
styles.push('color: black;');
for(var i=animIdx+1; i< anims[animName].length; i++){ logStr+= ''+ anims[animName][i].toString(2) + '\n'; } // add items after
logStr += ''
//console.log(logStr, 'color: black;', 'color: blue;', 'color: black;');
styles.unshift(logStr); // push logStr at beginning our our style array
console.log.apply(this, styles); // pass the array "as is" to the console & have it "expanded" as actual args
return undefined; // prevent filling screen with passed args
styles = ['color: black;'];
}
// mod of the above displaying only the current anim idx
var refreshDisplay5 = function(){
styles = [];
var logStr = '';
for(var y=0; y< anims[animName][animIdx].length; y++){
//logStr += anims[animName][animIdx][y].toString(2) + '\n';
// get, pad & parse
var binStr = anims[animName][animIdx][y].toString(2);
if( binStr.length < paddingBits){
var padding = '';
for(var p=0; p < paddingBits - binStr.length; p++){ padding += '0'; }
binStr = padding+binStr; // prefix it with padding
}
//logStr += binStr + ' ( unpadded: ' + anims[animName][animIdx][y].toString(2) +')\n'; // works fine
// loop over padded bin str
var tmpStr = '';
for(var s=0; s < binStr.length; s++){
// if s === 0, add style0, else add style 2
tmpStr += ('%c' + binStr[s]); // add to logStr
if(binStr[s] === "0") styles.push(styles0);
else styles.push(styles1);
}
//logStr += tmpStr + ' ( padded' + binStr + 'unpadded: ' + anims[animName][animIdx][y].toString(2) +')\n'; // works fine
logStr += (tmpStr + '\n');
}
logStr += '%c\n';
styles.push('color: black;');
logStr += '\n\n'
//console.log(logStr, 'color: black;', 'color: blue;', 'color: black;');
styles.unshift(logStr); // push logStr at beginning our our style array
console.log.apply(this, styles); // pass the array "as is" to the console & have it "expanded" as actual args
return undefined; // prevent filling screen with passed args
styles = [];
}
// use our anim helper
//console.clear();
//refreshDisplay4();
//animIdx = 1;
//refreshDisplay4();
//animIdx = 2;
//refreshDisplay4();
//animIdx = 3;
//refreshDisplay4();
//animItr = setInterval(animate, animRate);
/*
animItr = setInterval(function(){
animate(animEnd);
}, animRate);
*/
// ---- TWEENING TESTS ----
// http://www.acuriousanimal.com/2010/12/18/javascript-animations-why-maths-are-important.html
var eye = { x1: 0, y1: 0, x2: 0, y2: 0, x3: 0, y3: 0, x4: 0, y4: 0, relOffX: 0, relOffY: 0 } // from which we'll draw a polygon
var nullObj = { x: 0, y: 0}; // from which 1 or 2 eyes 'll get their relative position from
// we start with our nullObj centered
var screenW = 128;
var screenH = 64;
nullObj.x = screenW/2;
nullObj.y = screenH/2;
// we set our eye X & Y offsets relative to the nullObj
eye.relOffX = 10; // offset 10 px left from nullObj center X ( 'd be -10 for left eye & +10 for right one or 0 for 1 eye only )
eye.relOffY = 0; // horizontally centered with nullObj ( that is itself centered )
// set the eye x's & y's relative to our nullObj's x & y
eye.x2 = eye.x3 = nullObj.x - eye.relOffX;
eye.x1 = eye.x4 = nullObj.x - (eye.relOffX+10);
eye.y3 = eye.y4 = nullObj.y - (eye.relOffY-10);
eye.y2 = eye.y1 = nullObj.y - (eye.relOffY+10);
// log those
console.log(eye);
// quick tweening
var easeLinear = function(t, b, c, d){ return c*t/d+b; }
var easeInQuad = function(t, b, c, d){ return c*(t/=d)*t+b; }
// quick debug
var eyeItem = document.querySelector('.eye');
eyeItem.style.left = eye.x1 + 'px';
eyeItem.style.top = eye.y1 + 'px';
var endOffsetX = 64;
var endOffsetY = 62;
var beginningX = eye.x1;
var beginningY = eye.y1;
var diffX = endOffsetX - beginningX;
var diffY = endOffsetY - beginningY;
var time = 0;
var duration = 150;
//var interval = 5;
var interval = 10;
var animItr = -1;
var animate = function(callback){
//if(time < duration && beginningX < endOffsetX && beginningY < endOffsetY){
//if(( time < duration && beginningX < endOffsetX ) || ( time < duration && beginningY < endOffsetY )){
if(( time < duration && beginningX < endOffsetX ) ||
( time < duration && beginningX > endOffsetX ) ||
( time < duration && beginningY < endOffsetY ) ||
( time < duration && beginningY > endOffsetY ) ){
//var px = easeLinear(time, beginningX, diffX, duration);
//var py = easeLinear(time, beginningY, diffY, duration);
var px = easeInQuad(time, beginningX, diffX, duration);
var py = easeInQuad(time, beginningY, diffY, duration);
// draw
eyeItem.style.left = px + 'px';
eyeItem.style.top = py + 'px';
time += interval;
animItr = setTimeout(function(){ animate(callback); }, interval);
} else {
//console.log('fk');
time = 0;
if(callback) callback();
}
}
// try out
//animate();
//animate(function(){ console.log('animation ended !'); });
/*
animate(function(){
console.log('animation 1 ended !');
eye.x1 = endOffsetX;
eye.y1 = endOffsetY;
console.log(eye);
//
beginningX = eye.x1;
beginningY = eye.y1;
endOffsetX = 44+100;
//endOffsetY = 22+100;
diffX = endOffsetX - beginningX;
diffY = endOffsetY - beginningY;
//animate(function(){ console.log('animation 2 ended !'); });
animate(function(){
console.log('animation 2 ended !');
eye.x1 = endOffsetX;
eye.y1 = endOffsetY;
console.log(eye);
//
beginningX = eye.x1;
beginningY = eye.y1;
//endOffsetX = 44+200; // ok
endOffsetX = 44; // ok
//endOffsetY = 22+100;
diffX = endOffsetX - beginningX;
diffY = endOffsetY - beginningY;
animate(function(){ console.log('animation 3 ended !'); });
});
});
*/
// ---- CODE TO BE ADDED TO AN ILLUSTRATOR PLUGIN TO GET EYES NULL OBJS FROM POLYGONS ----
function getRelPos(cx, cy, px, px){
return { relX: cx-px, relY: cy-py};
}
var nullObjCenter = { x: 65.584, y: 37.57 }; // illustrator's nullObject center position
// absolute position of eyes polygons in the illustrator artboard
var neutral_eye1 = {
x1: 30.33, y1: 23.5, x2: 62.67, y2: 23.5,
x4: 30.33, y4: 55.83, x3:62.67, y3: 55.83,
};
var neutral_eye2 = {
x1: 68.5, y1: 19.67, x2: 100.83, y2: 19.67,
x4: 68.5, y4: 55.83, x3:100.83, y3: 55.83,
};
// relative positions that'll be computed from the nullObjCenter x & y coords
var neutral_relEye1 = {
x1: 0, y1: 0, x2: 0, y2: 0,
x4: 0, y4: 0, x3: 0, y3: 0,
};
var neutral_relEye2 = {
x1: 0, y1: 0, x2: 0, y2: 0,
x4: 0, y4: 0, x3: 0, y3: 0,
};
// compute both of the aboves
Object.keys(neutral_eye1).forEach(function(key){
if(key.indexOf('x') !== -1 ) neutral_relEye1[key] = (nullObjCenter.x - neutral_eye1[key]).toFixed(1); // handle x1/2/3/4
else if(key.indexOf('y') !== -1 ) neutral_relEye1[key] = (nullObjCenter.y - neutral_eye1[key]).toFixed(1); // // handle y1/2/3/4
});
Object.keys(neutral_eye2).forEach(function(key){
if(key.indexOf('x') !== -1 ) neutral_relEye2[key] = (nullObjCenter.x - neutral_eye2[key]).toFixed(1); // handle x1/2/3/4
else if(key.indexOf('y') !== -1 ) neutral_relEye2[key] = (nullObjCenter.y - neutral_eye2[key]).toFixed(1); // // handle y1/2/3/4
});
// -------- FACIAL EXPRESSION OBJ WIP --------
// -- FACIAL EXPRESSIONS
var fes = {
"neutral":{"noc_x":65.58,"noc_y":37.75,"eyes":[{"x1":35.25,"y1":-18.08,"x2":2.92,"y2":-18.08,"x3":2.92,"y3":18.08,"x4":35.25,"y4":18.08},{"x1":-2.92,"y1":-18.08,"x2":-35.25,"y2":-18.08,"x3":-35.25,"y3":14.25,"x4":-2.92,"y4":14.25}]},
"blink_high":{"noc_x":64.62,"noc_y":30.75,"eyes":[{"x1":0,"y1":-2.75,"x2":-60.38,"y2":-2.75,"x3":-60.38,"y3":2.75,"x4":0,"y4":2.75},{"x1":60.38,"y1":-2.75,"x2":0,"y2":-2.75,"x3":0,"y3":2.75,"x4":60.38,"y4":2.75}]},
"happy":{"noc_x":65.58,"noc_y":27.85,"eyes":[{"x1":35.25,"y1":-4.35,"x2":2.92,"y2":-4.35,"x3":2.92,"y3":4.35,"x4":35.25,"y4":4.35},{"x1":-2.92,"y1":-4.35,"x2":-35.25,"y2":-4.35,"x3":-35.25,"y3":4.35,"x4":-2.92,"y4":4.35}]},
"glee":{"noc_x":65.58,"noc_y":21.58,"eyes":[{"x1":37.25,"y1":-5.62,"x2":4.92,"y2":-3.62,"x3":4.92,"y3":5.08,"x4":37.25,"y4":3.08},{"x1":-4.92,"y1":-3.62,"x2":-37.25,"y2":-5.62,"x3":-37.25,"y3":3.62,"x4":-4.92,"y4":5.62}]},
"blink_low":{"noc_x":64.62,"noc_y":46.75,"eyes":[{"x1":60.38,"y1":-2.75,"x2":0,"y2":-2.75,"x3":0,"y3":2.75,"x4":60.38,"y4":2.75},{"x1":0,"y1":-2.75,"x2":-60.38,"y2":-2.75,"x3":-60.38,"y3":2.75,"x4":0,"y4":2.75}]},
"sad_lookingDown":{"noc_x":65.58,"noc_y":53.81,"eyes":[{"x1":35.25,"y1":-8.19,"x2":2.92,"y2":-8.19,"x3":2.92,"y3":8.19,"x4":35.25,"y4":2.19},{"x1":-2.92,"y1":-8.19,"x2":-35.25,"y2":-8.19,"x3":-35.25,"y3":2.19,"x4":-2.92,"y4":8.19}]},
"sad_lookingArUser":{"noc_x":65.58,"noc_y":13.31,"eyes":[{"x1":35.25,"y1":-11.69,"x2":2.92,"y2":-11.69,"x3":2.92,"y3":11.69,"x4":35.25,"y4":5.69},{"x1":-2.92,"y1":-11.69,"x2":-35.25,"y2":-11.69,"x3":-35.25,"y3":5.69,"x4":-2.92,"y4":11.69}]},
"worried":{"noc_x":65.58,"noc_y":41.56,"eyes":[{"x1":35.25,"y1":-16.19,"x2":2.92,"y2":-16.19,"x3":2.92,"y3":16.19,"x4":35.25,"y4":12.19},{"x1":-2.92,"y1":-16.19,"x2":-35.25,"y2":-16.19,"x3":-35.25,"y3":12.19,"x4":-2.92,"y4":16.19}]},
"focused":{"noc_x":65.58,"noc_y":37.42,"eyes":[{"x1":35.25,"y1":-6.67,"x2":2.92,"y2":-6.67,"x3":2.92,"y3":6.67,"x4":35.25,"y4":6.67},{"x1":-2.92,"y1":-6.67,"x2":-35.25,"y2":-6.67,"x3":-35.25,"y3":6.67,"x4":-2.92,"y4":6.67}]},
"annoyed":{"noc_x":65.58,"noc_y":31.88,"eyes":[{"x1":35.25,"y1":-0.12,"x2":2.92,"y2":-0.12,"x3":2.92,"y3":4.25,"x4":35.25,"y4":4.25},{"x1":-2.92,"y1":-4.25,"x2":-35.25,"y2":-4.25,"x3":-35.25,"y3":3.12,"x4":-2.92,"y4":3.12}]},
"surprised":{"noc_x":66.08,"noc_y":36.06,"eyes":[{"x1":42.75,"y1":-20.69,"x2":0.42,"y2":-24.69,"x3":0.42,"y3":24.69,"x4":42.75,"y4":18.69},{"x1":-1.42,"y1":-24.69,"x2":-42.75,"y2":-20.69,"x3":-42.75,"y3":18.69,"x4":-1.42,"y4":24.69}]},
"skeptic":{"noc_x":65.58,"noc_y":37.56,"eyes":[{"x1":35.25,"y1":-16.19,"x2":2.92,"y2":-16.19,"x3":2.92,"y3":3.19,"x4":35.25,"y4":7.19},{"x1":-2.92,"y1":-16.19,"x2":-35.25,"y2":-16.19,"x3":-35.25,"y3":16.19,"x4":-2.92,"y4":16.19}]},
"frustrated":{"noc_x":68.58,"noc_y":49.56,"eyes":[{"x1":35.25,"y1":-4.19,"x2":2.92,"y2":-4.19,"x3":2.92,"y3":4.19,"x4":35.25,"y4":4.19},{"x1":-2.92,"y1":-4.19,"x2":-35.25,"y2":-4.19,"x3":-35.25,"y3":4.19,"x4":-2.92,"y4":4.19}]},
"unimpressed":{"noc_x":66.58,"noc_y":48.06,"eyes":[{"x1":35.25,"y1":-5.69,"x2":2.92,"y2":-5.69,"x3":2.92,"y3":5.69,"x4":35.25,"y4":5.69},{"x1":-2.92,"y1":-5.69,"x2":-35.25,"y2":-5.69,"x3":-35.25,"y3":2.69,"x4":-2.92,"y4":2.69}]},
"sleepyEyes":{"noc_x":62.67,"noc_y":46.03,"eyes":[{"x1":35.88,"y1":-7.83,"x2":3.62,"y2":-5.6,"x3":4.12,"y3":1.64,"x4":36.38,"y4":-0.6},{"x1":-0.69,"y1":-7.67,"x2":-34.07,"y2":-11.65,"x3":-36.38,"y3":7.67,"x4":-2.99,"y4":11.65}]},
"suspicious":{"noc_x":65.58,"noc_y":36.25,"eyes":[{"x1":38.25,"y1":-3.5,"x2":2.92,"y2":-4.5,"x3":2.92,"y3":3.5,"x4":38.25,"y4":4.5},{"x1":-2.92,"y1":-4.5,"x2":-38.25,"y2":-3.5,"x3":-38.25,"y3":4.5,"x4":-2.92,"y4":3.5}]},
"squint":{"noc_x":65.58,"noc_y":36.25,"eyes":[{"x1":37.25,"y1":-3.5,"x2":2.92,"y2":-4.5,"x3":2.92,"y3":4.5,"x4":37.25,"y4":4.5},{"x1":-2.92,"y1":-4.5,"x2":-37.25,"y2":-3.5,"x3":-37.25,"y3":4.5,"x4":-2.92,"y4":4.5}]},
"angry":{"noc_x":65.58,"noc_y":45.56,"eyes":[{"x1":35.25,"y1":-8.19,"x2":2.92,"y2":-8.19,"x3":2.92,"y3":4.19,"x4":35.25,"y4":8.19},{"x1":-2.92,"y1":-8.19,"x2":-35.25,"y2":-8.19,"x3":-35.25,"y3":8.19,"x4":-2.92,"y4":4.19}]},
"furious":{"noc_x":65.58,"noc_y":35.06,"eyes":[{"x1":35.25,"y1":-11.69,"x2":2.92,"y2":-11.69,"x3":2.92,"y3":3.69,"x4":35.25,"y4":11.69},{"x1":-2.92,"y1":-11.69,"x2":-35.25,"y2":-11.69,"x3":-35.25,"y3":11.69,"x4":-2.92,"y4":3.69}]},
"scared":{"noc_x":65.58,"noc_y":21.56,"eyes":[{"x1":35.25,"y1":-16.19,"x2":2.92,"y2":-16.19,"x3":2.92,"y3":16.19,"x4":35.25,"y4":12.19},{"x1":-2.92,"y1":-16.19,"x2":-35.25,"y2":-16.19,"x3":-35.25,"y3":12.19,"x4":-2.92,"y4":16.19}]},
"awe":{"noc_x":66.08,"noc_y":31.56,"eyes":[{"x1":41.75,"y1":-21.19,"x2":2.42,"y2":-25.19,"x3":2.42,"y3":25.19,"x4":41.75,"y4":19.19},{"x1":-3.42,"y1":-25.19,"x2":-41.75,"y2":-21.19,"x3":-41.75,"y3":19.19,"x4":-3.42,"y4":25.19}]}
};
// -- ANIM HELPER
var animStep = function(time, tweenFcn, beginningX, beginningY, diffX, diffY, duration, updateCallback, endCallback){
//if(time < duration && beginningX < endOffsetX && beginningY < endOffsetY){
//if(( time < duration && beginningX < endOffsetX ) || ( time < duration && beginningY < endOffsetY )){
if(( time < duration && beginningX < endOffsetX ) ||
( time < duration && beginningX > endOffsetX ) ||
( time < duration && beginningY < endOffsetY ) ||
( time < duration && beginningY > endOffsetY ) ){
//var px = easeLinear(time, beginningX, diffX, duration);
//var py = easeLinear(time, beginningY, diffY, duration);
var px = tweenFcn(time, beginningX, diffX, duration);
var py = tweenFcn(time, beginningY, diffY, duration);
// draw
updateCallback(px, py);
time += interval;
//animItr = setTimeout(function(){ animate(callback); }, interval);
animItr = setTimeout(function(){ animStep(time, tweenFcn, beginningX, beginningY, diffX, diffY, duration, updateCallback, endCallback); }, interval);
} else {
//console.log('fk');
time = 0;
if(endCallback) endCallback();
}
}
// -- FACIAL EXPRESSION HELPER
var fe = {
ce: '', // current expression
noc_x: 0,
noc_y: 0,
eyes: [],
that: this,
set: function(expressionName){
if(fes[expressionName]){
var exprObj = fes[expressionName];
this.ce = expressionName;
this.noc_x = 128- exprObj.noc_x;
this.noc_y = 64- exprObj.noc_y;
this.eyes = [];
var exprEyesArr = fes[expressionName].eyes;
for(var i=0; i< exprEyesArr.length; i++){
this.eyes.push({
x1: exprEyesArr[i].x1, y1: exprEyesArr[i].y1, x2: exprEyesArr[i].x2, y2: exprEyesArr[i].y2,
x4: exprEyesArr[i].x4, y4: exprEyesArr[i].y4, x3: exprEyesArr[i].x3, y3: exprEyesArr[i].y3
});
}
} else { console.log('expression not found'); }
},
update: function(duration, expression, blink, callback){
// draw it directly if duration == 0, else animate the morphing
if(duration === 0){ // draw directly
g.clear();
//console.log(this.eyes);
// draw the eyes in correct position
for(var i=0; i< this.eyes.length; i++){
g.fillPoly([this.noc_x+this.eyes[i].x1, this.noc_y+this.eyes[i].y1, this.noc_x+this.eyes[i].x2, this.noc_y+this.eyes[i].y2,
this.noc_x+this.eyes[i].x3, this.noc_y+this.eyes[i].y3, this.noc_x+this.eyes[i].x4, this.noc_y+this.eyes[i].y4]);
}
g.flip();
} else { // animate the morphing for specified duration
console.log('currently not supporting animation between expressions ..');
// handle only transitions to other nullObject center for now
var endOffsetX = fes[expression].noc_x; console.log('fes[expression].noc_x:' + fes[expression].noc_x);
var endOffsetY = fes[expression].noc_y; console.log('fes[expression].noc_y:' + fes[expression].noc_y);
var beginningX = fes[this.ce].noc_x; console.log('fes[this.ce].noc_x:' + fes[this.ce].noc_x);
var beginningY = fes[this.ce].noc_y; console.log('fes[this.ce].noc_y:' + fes[this.ce].noc_y);
var diffX = endOffsetX - beginningX; console.log('diffY:' + diffX);
var diffY = endOffsetY - beginningY; console.log('diffY:' + diffY);
var time = 0;
var duration = 150;
var interval = 10;
animStep(time, easeInQuad, beginningX, beginningY, diffX, diffY, duration, function(px, py){
this.noc_x = px;
this.noc_y = py;
g.clear();
// draw the eyes in correct position
//for(var i=0; i< this.eyes.length; i++){
/*
for(var i=0; i< that.eyes.length; i++){
g.fillPoly([this.noc_x+this.eyes[i].x1, this.noc_y+this.eyes[i].y1, this.noc_x+this.eyes[i].x2, this.noc_y+this.eyes[i].y2,
this.noc_x+this.eyes[i].x3, this.noc_y+this.eyes[i].y3, this.noc_x+this.eyes[i].x4, this.noc_y+this.eyes[i].y4]);
}
*/
/*
for(var i=0; i< fe.length; i++){
g.fillPoly([fe.noc_x+fe.eyes[i].x1, fe.noc_y+fe.eyes[i].y1, fe.noc_x+fe.eyes[i].x2, fe.noc_y+fe.eyes[i].y2,
fe.noc_x+fe.eyes[i].x3, fe.noc_y+fe.eyes[i].y3, fe.noc_x+fe.eyes[i].x4, fe.noc_y+fe.eyes[i].y4]);
}
*/
fe.update(0);
g.flip(); // for Espruino
}, function(){
console.log('transition to expression: '+expression+' ended !');
this.ce = expression;
if(callback) callback();
});
}
},
// move the random nullObject center to get an expression-agnostic x & y offset movement
randMoveNoc: function(min, max, durationMin, durationMax, repeat, callback){},
blink: function(fromBaseLine){}, // close then open both eyes, fromBaseLine bool to offset the blink
closeEye: function(){}, // close the eye(s) whose idxs are passed as parameters or all if no args
openEye: function(){}, // // close the eye(s) whose idxs are passed as parameters or all if no args
};
var feIdx = 0; // to quickly cycle through all facial expressions ( debug )
var fesKeys = Object.keys(fes);
var nextFe = function(){
if(feIdx < fesKeys.length-1) feIdx++;
else feIdx = 0;
fe.set( fesKeys[feIdx] );
fe.update(0);
};
var prevFe = function(){
if(feIdx-1 >= 0) feIdx--;
else feIdx = fesKeys.length-1;
fe.set( fesKeys[feIdx] );
fe.update(0);
};
// ---- CANVAS PREVIEW OF OLED SCREEN DISPLAY ----
myCanvas.height = 64;
myCanvas.width = 128;
// -- adding Espruino's graphics methods to canvas to ease testing
/*
CanvasRenderingContext2D.prototype.fillPolygon = function (pointsArray) {
if (pointsArray.length <= 0) return;
this.moveTo(pointsArray[0][0], pointsArray[0][1]);
for (var i = 0; i < pointsArray.length; i++) {
this.lineTo(pointsArray[i][0], pointsArray[i][1]);
}
if (strokeColor != null && strokeColor != undefined)
this.strokeStyle = strokeColor;
if (fillColor != null && fillColor != undefined) {
this.fillStyle = fillColor;
this.fill();
}
}
*/
// usage:
//var polygonPoints = [[10,100],[20,75],[50,100],[100,100],[10,100]];
//context.fillPolygon(polygonPoints, '#F00','#000');
// my mod for Espruino
//CanvasRenderingContext2D.prototype.fillPoly = function () {
//var pointsArray = arguments;
CanvasRenderingContext2D.prototype.fillPoly = function (pointsArray) {
if (pointsArray.length <= 0) return;
this.moveTo(pointsArray[0], pointsArray[1]);
this.beginPath();
for (var i = 0; i < pointsArray.length; i+=2) {
this.lineTo(pointsArray[i], pointsArray[i+1]);
}
this.lineTo(pointsArray[0], pointsArray[1]); // closed shape -> re-use 1st coord
this.fill();
}
CanvasRenderingContext2D.prototype.setColor = function(color){
this.fillStyle = color;
this.strokeStyle = color;
}
CanvasRenderingContext2D.prototype.clear = function(){
var currFillStyle = this.fillStyle; // bckp
this.fillStyle = 'black';
this.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.fillStyle = currFillStyle; // restore bckp
//this.clearRect(0, 0, this.canvas.width, this.canvas.height);
//console.log('canvas cleared');
}
CanvasRenderingContext2D.prototype.flip = function(){
// TODO: maybe some sort of 'reveal stuff already drawn' ? for now, just have a log ;)
//console.log('canvas flipped');
}
CanvasRenderingContext2D.prototype.setRotation = function(rotation){
// 0 = 0°
// 1 = 90°
// 2 = 180°
// 3 = 270°
if(rotation === 0){ this.setTransform(1, 0, 0, 1, 0, 0); }
else if(rotation === 2){
this.translate(this.canvas.width, this.canvas.height);
this.rotate(rotation*90 * Math.PI / 180);
//this.translate(this.canvas.width, 0);
//this.scale(-1, 1); // mirroring as well
}
//console.log('canvas rotated');
}
/*
var ctx = myCanvas.getContext('2d');
ctx.fillStyle = '#f00';
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(100,50);
ctx.lineTo(50, 100);
ctx.lineTo(0, 90);
ctx.closePath();
ctx.fill();
*/
var g = myCanvas.getContext('2d');
g.setRotation(2);
g.setColor('white');
//g.fillPoly([10, 10, 20, 10, 20, 20, 10, 20]); // square: ok
//g.flip();
//g.clear();
//g.fillPoly([eye.x1, eye.y1, eye.x2, eye.y2, eye.x3, eye.y3, eye.x4, eye.y4]); // ok: polygon
//g.flip();
// hopefully, no error ? ( code below is from Espruino wip implm )
fe.set('happy'); // ok
fe.update(0);
// cycle between available facial expressions ( don't transition between thse for now )
var tout = -1;
//tout = setInterval(function(){ nextFe(); console.log('current expression:' + feIdx + ' --> ' + fe.ce)}, 1000);
//fe.set('happy'); // ok
//fe.update(3, 'worried', false, function(){ console.log('transitionning to nxt noc done !') });