Skip to content

Instantly share code, notes, and snippets.

@edeetee
Forked from dribnet/.block
Created July 17, 2017 01:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save edeetee/8863ab6d42c35c1ec0357288aaa2a79a to your computer and use it in GitHub Desktop.
Save edeetee/8863ab6d42c35c1ec0357288aaa2a79a to your computer and use it in GitHub Desktop.
17.2.MDDN342 PS1
license: mit
function resetFocusedRandom() {
return Math.seedrandom(arguments);
}
function focusedRandom(min, max, focus, mean, source) {
// console.log("hello")
if(source){
var randomUniform = d3.randomUniform.source(source)
var randomNormal = d3.randomNormal.source(source)
} else{
var randomUniform = d3.randomUniform
var randomNormal = d3.randomNormal
}
if(max === undefined) {
max = min;
min = 0;
}
if(focus === undefined) {
focus = 1.0;
}
if(mean === undefined) {
mean = (min + max) / 2.0;
}
if(focus == 0) {
return randomUniform(min, max)();
}
else if(focus < 0) {
focus = -1 / focus;
}
sigma = (max - mean) / focus;
val = randomNormal(mean, sigma)();
if (val > min && val < max) {
return val;
}
return randomUniform(min, max)();
}
// note: this file is poorly named - it can generally be ignored.
// helper functions below for supporting blocks/purview
function saveBlocksImages(doZoom) {
if(doZoom == null) {
doZoom = false;
}
// generate 960x500 preview.jpg of entire canvas
// TODO: should this be recycled?
var offscreenCanvas = document.createElement('canvas');
offscreenCanvas.width = 960;
offscreenCanvas.height = 500;
var context = offscreenCanvas.getContext('2d');
// background is flat white
context.fillStyle="#FFFFFF";
context.fillRect(0, 0, 960, 500);
context.drawImage(this.canvas, 0, 0, 960, 500);
// save to browser
var downloadMime = 'image/octet-stream';
var imageData = offscreenCanvas.toDataURL('image/jpeg');
imageData = imageData.replace('image/jpeg', downloadMime);
p5.prototype.downloadFile(imageData, 'preview.jpg', 'jpg');
// generate 230x120 thumbnail.png centered on mouse
offscreenCanvas.width = 230;
offscreenCanvas.height = 120;
// background is flat white
context = offscreenCanvas.getContext('2d');
context.fillStyle="#FFFFFF";
context.fillRect(0, 0, 230, 120);
if(doZoom) {
// pixelDensity does the right thing on retina displays
var pd = this._pixelDensity;
var sx = pd * mouseX - pd * 230/2;
var sy = pd * mouseY - pd * 120/2;
var sw = pd * 230;
var sh = pd * 120;
// bounds checking - just displace if necessary
if (sx < 0) {
sx = 0;
}
if (sx > this.canvas.width - sw) {
sx = this.canvas.width - sw;
}
if (sy < 0) {
sy = 0;
}
if (sy > this.canvas.height - sh) {
sy = this.canvas.height - sh;
}
// save to browser
context.drawImage(this.canvas, sx, sy, sw, sh, 0, 0, 230, 120);
}
else {
// now scaledown
var full_width = this.canvas.width;
var full_height = this.canvas.height;
context.drawImage(this.canvas, 0, 0, full_width, full_height, 0, 0, 230, 120);
}
imageData = offscreenCanvas.toDataURL('image/png');
imageData = imageData.replace('image/png', downloadMime);
p5.prototype.downloadFile(imageData, 'thumbnail.png', 'png');
}

PS1 MDDN 342 2017

This readme describes the final version of parametric faces assignment (part5/assignment1/CCIII).

In this final version, I have decided to make the characters interact with each other in some ways through physically moving around the scene in an organic, childish way. I have created a basic physics system with velocities to do this (the PhysicsObject class), and to create the childlike interactions and animations I have made a system for managing the Slerping and Lerping of values over time (the Slerper and Lerper classes). For this final version I have paid specific attention to the distribution of values as well as using different seed sources. I generate them at the start, then I use a special seed source to generate new versions when the end of a lerp/slerp is triggered. I have added a fourth character, Elmo, which reacts in a similar way to the others. Bert and Oscar move their eyebrows instead of their eyes, as for their characters I think it represents a similar change of emotion. All of the characters have changing mouths and tilts. The mouths are distributed to mostly be quite smiley, but rarely low and extreme values. The eyes change more than the mouth as the distribution of their period is lower.

The velocity of each character is constantly changing direction and speed. The speed is normally 1, but can get as high as 5, and the velocity direction can be changing up to a 1/40th of a revolution each tick, but its mean is keeping straight.

Characters get happier as they get closer together, and if they touch then they do a little chatter animation.

When they randomise, objects are kept the same pos/vel, but change their draw function and all of their easing functions reset as it lets people keep on following the objects they were while showing an interesting change.

<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.11/p5.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.11/addons/p5.dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.0/seedrandom.min.js"></script>
<script src="https://d3js.org/d3-random.v1.js"></script>
<script language="javascript" type="text/javascript" src=".purview_helper.js"></script>
<script language="javascript" type="text/javascript" src=".focused_random.js"></script>
<script language="javascript" type="text/javascript" src="sketch.js"></script>
<style>
body { padding: 0; margin: 0; }
.inner { position: absolute; }
#controls {
font: 300 12px "Helvetica Neue";
padding: 5;
margin: 5;
background: #f0f0f0;
opacity: 0.0;
-webkit-transition: opacity 0.2s ease;
-moz-transition: opacity 0.2s ease;
-o-transition: opacity 0.2s ease;
-ms-transition: opacity 0.2s ease;
}
#controls:hover { opacity: 0.9; }
</style>
</head>
<body style="background-color:white">
<div class="outer">
<div class="inner">
<div id="canvasContainer"></div>
</div>
<div class="inner" id="controls" height="500px">
<table>
<tr>
<td>Randomizer: </td>
<td id="selector1Container"></td>
</tr>
</table>
</div>
</div>
</table>
</body>
{
"commits": [
{
"sha": "a633080689905ded96cc2132604b50badde31307",
"name": "final"
},
{
"sha": "75b05245f3b1e2422171cc5ba94a0bb37392101f",
"name": "distribution"
},
{
"sha": "ffb5d76bfa6be77d288e36faa587b429b1c02c8a",
"name": "randomize"
},
{
"sha": "5c5b1b30a05402bd861aa925241e5efef85d2a15",
"name": "drawing_styles"
},
{
"sha": "156af9570b376ea6b5482376a50af9888434cc08",
"name": "portraits"
}
]
}
var canvasWidth = 960;
var canvasHeight = 500;
var slider1, slider2, slider3, slider4, slider5;
var drawFaceSelector;
var curRandomSeed = 0;
function setup () {
// create the drawing canvas, save the canvas element
var main_canvas = createCanvas(canvasWidth, canvasHeight);
main_canvas.parent('canvasContainer');
randButton = createButton('randomize');
randButton.mousePressed(changeRandomSeed);
randButton.parent('selector1Container');
// rotation in degrees
angleMode(DEGREES);
rectMode(CENTER);
colorMode(HSB);
noStroke();
bert_drawFace = color(45, 90, 95)
bert_nose = color(29, 96, 94)
ernie_drawFace = color(30, 90, 92)
ernie_drawEars = color(30, 90, 75);
mouth = color(351, 100, 55)
oscar_drawFace = color(75, 80, 50)
oscar_brow = color(23, 80, 30)
elmo_face_color = color(0, 100, 80);
changeRandomSeed()
}
function mouseClicked() {
changeRandomSeed();
}
var ernie_drawFace; //orange
var ernie_drawEars;
var ernie_nose = "rgb(218, 10, 31)" //red
var bert_drawFace //yellow
var bert_nose //orange
var oscar_drawFace;
var oscar_brow;
var elmo_face_color;
function drawErnie(x, y, w, h, tilt_value, eye_value, mouth_value) {
// move to position1, rotate, draw "head" ellipse
push();
translate(x, y);
rotate(-tilt_value/2);
scale(0.6);
//ears
var rotation = -30;
fill(ernie_drawEars);
translate(-290/2, -10);
rotate(rotation)
ellipse(0, 0, 60, 70);
rotate(-rotation)
translate(290, 0);
rotate(-rotation)
ellipse(0, 0, 60, 70);
rotate(rotation)
translate(-290/2, 10);
fill(ernie_drawFace);
ellipse(0, 0, 290, 230);
drawMouthEllipse(0, 30, 230, 50, 0.8-0.4*mouth_value/100, mouth, ernie_drawFace);
// set fill to match background color
var rotation = -40;
fill("white");
// draw two eyes
rotate(rotation)
ellipse(-10, -50, 60, 50);
rotate(-rotation*2)
ellipse(10, -50, 60, 50);
rotate(rotation)
// set fill back to black for eyeballs
fill("black");
rotate(rotation)
ellipse(5-eye_value/100*20, -50, 25, 25);
rotate(-rotation*2)
ellipse(-5+eye_value/100*20, -50, 25, 25);
rotate(rotation)
fill(ernie_nose)
ellipse(0, 5, 65, 70);
pop();
}
function drawBert(x, y, w, h, tilt_value, eye_value, mouth_value) {
// move to position2, rotate, draw "head" ellipse
push();
translate(x, y);
rotate(tilt_value/2);
scale(0.6);
fill(bert_drawFace);
ellipse(0, 0, 240, 370);
translate(0, 30)
var bert_mouth_h = 54;
// mouth-hole with background color
drawMouthArc(0, 30, 200, 5+50*mouth_value/100, mouth);
// set fill to match background color
var diff = map(eye_value, 0, 100, 0, 10);
fill("white");
// draw two eyes
ellipse(-50, -80+diff, 70);
ellipse( 50, -80+diff, 70);
// set fill back to foreground for eyeballs
fill("black");
ellipse(-40, -80+diff, 30);
ellipse( 40, -80+diff, 30);
//bert eyebrows
fill("black")
rect(0, -120-30*eye_value/100, 180, 25)
fill(bert_nose);
ellipse(0, -10, 80, 110)
pop();
}
function drawOscar(x, y, w, h, tilt_value, eye_value, mouth_value) {
// move to position1, rotate, draw "head" ellipse
push();
translate(x, y);
rotate(tilt_value*1.5);
scale(0.6);
//squeeze oscar
fill(oscar_drawFace);
ellipse(0, 0, 290, 200);
drawMouthArc(0, 20, 260, 20+30*mouth_value/100, 'black');
// set fill to match background color
fill("white");
// draw two eyes
ellipse(-40, -20, 60, 45);
ellipse( 40, -20, 60, 45);
// set fill back to black for eyeballs
fill("black");
ellipse(-40, -20, 30);
ellipse( 40, -20, 30);
//oscar eyebrows
fill(oscar_brow)
rect(0, map(eye_value, 0, 100, -65, -40), 180, 20)
pop();
}
function drawElmo(x, y, w, h, tilt_value, eye_value, mouth_value) {
// move to position1, rotate, draw "head" ellipse
push();
translate(x, y);
rotate(tilt_value);
scale(0.6);
//squeeze oscar
fill(elmo_face_color);
ellipse(0, 0, 260, 230);
drawMouthArc(0, 10, 220, 20+60*mouth_value/100, 'black');
// set fill to match background color
fill("white");
// draw two eyes
ellipse(-40, -110, 70);
ellipse( 40, -110, 70);
// set fill back to black for eyeballs
fill("black");
ellipse(-37, -95-30*eye_value/100, 25);
ellipse( 37, -95-30*eye_value/100, 25);
fill(bert_nose)
ellipse(0, -60, 70, 80);
pop();
}
function drawMouthArc(x, y, width, height, mouthColor, inverse){
//default to 0
inverse |= false
fill(mouthColor);
if(inverse)
arc(x, y+height, width, height*2, 180, 180, CHORD)
else
arc(x, y, width, height*2, 0, 180, CHORD)
}
function drawMouthEllipse(x, y, width, height, ellipseMod, mouthColor, faceColor){
var ellipseHeight = height*2*ellipseMod;
var ellipseY = y-height+ellipseHeight/2
// mouth-hole with background color
fill(mouth);
ellipse(x, y, width, height*2);
//cut out mouth
fill(ernie_drawFace);
ellipse(x, ellipseY, width*1.1, ellipseHeight*1.1);
}
var drawFuncs = [drawErnie, drawBert, drawOscar, drawElmo];
var lastSwap;
function changeRandomSeed() {
var startPositions = [createVector(0.25*canvasWidth, 0.25*canvasHeight),
createVector(0.25*canvasWidth, 0.75*canvasHeight),
createVector(0.75*canvasWidth, 0.75*canvasHeight),
createVector(0.75*canvasWidth, 0.25*canvasHeight),
createVector(0.5*canvasWidth, 0.5*canvasHeight)]
curRandomSeed = curRandomSeed + 1;
//randomize draws
drawFuncs = shuffle(drawFuncs);
drawFuncs.forEach(function(val, i){
//if already rendered, just randomize drawFuncs
if(objects[i]){
objects[i].tilt = null;
objects[i].smile = null;
objects[i].eyes = null;
objects[i].draw = drawFuncs[i];
}
else
objects[i] = new PhysicsObject(startPositions[i], p5.Vector.fromAngle(focusedRandom(0, TAU)), drawFuncs[i]);
})
lastSwap = millis();
}
//buffer around edges
var buffer = 60;
class PhysicsObject{
constructor(startPos, startVel, drawFunc){
this.position = startPos.copy();
this.velocity = startVel.copy();
this.draw = drawFunc;
}
updatePhysics(){
var time = millis();
//set velocity of object
if(this.push)
this.velocity = this.push.getVal(time).mult(this.speed.getVal(time));
//if outta bounds
if(this.withinBufferDistance())
this.velocity.rotate(180);
this.position.add(this.velocity)
}
withinBufferDistance(){
var minDist = buffer*2.5;
var pos = this.position;
objects.forEach(function(object){
if(pos != object.position)
minDist = min(pos.dist(object.position), minDist);
})
if(minDist < buffer*2.5)
this.smile = new Lerper(millis(), 1500, function(val){
return 50+50*sin(val*360*6);
})
return this.position.x <= buffer || this.position.y <= buffer ||
canvasWidth-buffer <= this.position.x || canvasHeight-buffer <= this.position.y ||
minDist < buffer*2.5;
}
getHappiness(){
var minDist = buffer*6;
var pos = this.position;
objects.forEach(function(object){
if(pos != object.position)
minDist = min(pos.dist(object.position), minDist);
})
var returnVal = constrain(map(minDist, buffer*6, buffer*2.5, 0, 1), 0, 1);
return returnVal*returnVal;
}
}
//physics update tick
(function doPhysicsUpdate(){
setTimeout(doPhysicsUpdate, 20);
if(objects)
objects.forEach(function(object){
object.updatePhysics();
});
})();
//mod helper
Math.mod = function(x, y){
return x - y * floor(x / y)
}
//list of physics objects
var objects = [];
class Lerper{
constructor(startTime, duration, from, to){
this.startTime = startTime;
this.duration = duration;
if(typeof from == "number"){
this.from = from;
this.to = to;
} else if(from instanceof Function)
this.func = from;
}
getVal(time){
//slerp time to 0..1, slerped 0..1 mapped on from..to
if(this.from !== undefined)
return this.doLerp(time)*(this.to-this.from)+this.from;
else if(this.func !== undefined)
return this.func(this.doLerp(time));
else
return this.doLerp(time);
}
doLerp(time){
return (time-this.startTime)/this.duration;
}
isFinished(time){
return (this.startTime+this.duration) < time;
}
}
class Slerper extends Lerper{
getVal(time){
//slerp time to 0..1, slerped 0..1 mapped on from..to
if(this.from !== undefined)
return this.doSlerp(time)*(this.to-this.from)+this.from;
else if(this.func !== undefined)
return this.func(this.doSlerp(time));
else
return this.doSlerp(time);
}
doSlerp(time){
return slerp((time-this.startTime)/this.duration);
}
}
function slerp(val){
val = constrain(val, 0, 1);
return (cos(180+180*val)+1)/2;
}
var alwaysRand = new Math.seedrandom(focusedRandom())
function draw () {
if(lastSwap+5000 < millis())
changeRandomSeed();
resetFocusedRandom(curRandomSeed);
noStroke();
//light blue
background(210, 70, 100);
var w = canvasWidth / 5;
var h = canvasHeight / 3;
objects.forEach(function(object, i){
var time = millis();
var x = object.position.x;
var y = object.position.y;
//set velocity
if(!object.push || object.push.isFinished(time)){
var div = 20
let delta = focusedRandom(-PI/div, PI/div, 10, 0, alwaysRand);
var duration = focusedRandom(2000, 5000, 1, 3000, alwaysRand)
if(object.speed)
var fromSpeed = object.speed.to;
else
var fromSpeed = focusedRandom(4, 0.2, 2, 1, alwaysRand);
let toSpeed = focusedRandom(5, 0.2, 2, 1, alwaysRand);
object.speed = new Slerper(time, duration, fromSpeed, toSpeed);
object.push = new Lerper(time, duration, function(val){
return p5.Vector.fromAngle(object.velocity.heading()+delta);
})
}
//set tilt
if(!object.tilt || object.tilt.isFinished(time)){
if(object.tilt)
var from = object.tilt.to;
else
var from = focusedRandom(-90, 90, 4, from/3, alwaysRand);
var duration = focusedRandom(500, 2000, 1, 1000, alwaysRand)
var to = focusedRandom(-90, 90, 4, from/3, alwaysRand)
object.tilt = new Slerper(time, duration, from, to)
}
//set smile
if(!object.smile || object.smile.isFinished(time)){
if(object.smile && typeof object.smile.to == "number")
var from = object.smile.to;
else
var from = focusedRandom(40, 100, 1, 60, alwaysRand)
var duration = focusedRandom(100, 3000, 2, 600, alwaysRand)
if(focusedRandom(null, null, null, null, alwaysRand) < 0.3)
var to = from
else
var to = focusedRandom(20, 100, 2, 60, alwaysRand)
object.smile = new Slerper(time, duration, from, to)
}
//set eyes
if(!object.eyes || object.eyes.isFinished(time)){
if(object.eyes && typeof object.eyes.to == "number")
var from = object.eyes.to;
else
var from = 50
var duration = focusedRandom(100, 2000, 1, 1000, alwaysRand)
var to = focusedRandom(0, 100, 1, 50, alwaysRand)
object.eyes = new Slerper(time, duration, from, to)
}
//do the draw
object.draw(x, y, w, h, object.tilt.getVal(time), object.eyes.getVal(time), max(object.smile.getVal(time), 100*object.getHappiness()));
})
}
function keyTyped() {
if (key == '!') {
saveBlocksImages();
}
else if (key == '@') {
saveBlocksImages(true);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment