2018-03-27 17:39:08 +00:00
|
|
|
function Peep(config){
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
// Properties
|
|
|
|
self.x = config.x;
|
|
|
|
self.y = config.y;
|
2018-04-10 17:00:22 +00:00
|
|
|
self.velocity = {x:0, y:0};
|
2018-03-28 14:57:19 +00:00
|
|
|
self.infected = !!config.infected;
|
2018-03-27 19:20:22 +00:00
|
|
|
self.sim = config.sim;
|
2018-03-27 17:39:08 +00:00
|
|
|
|
|
|
|
// Update:
|
|
|
|
self.numFriends = 0;
|
|
|
|
self.numInfectedFriends = 0;
|
2018-04-02 17:43:20 +00:00
|
|
|
self.isPastThreshold = false;
|
2018-03-27 17:39:08 +00:00
|
|
|
self.faceX = 0;
|
|
|
|
self.faceY = 0;
|
|
|
|
self.faceBlink = 0;
|
|
|
|
self.isMajority = false;
|
|
|
|
var _faceFollow = 0.75+(Math.random()*0.1);
|
|
|
|
self.update = function(){
|
|
|
|
|
|
|
|
// Face position!
|
|
|
|
var faceVector = {
|
2018-03-27 19:20:22 +00:00
|
|
|
x: (self.sim.mouse.x-self.x)/5,
|
|
|
|
y: (self.sim.mouse.y-self.y)/5
|
2018-03-27 17:39:08 +00:00
|
|
|
};
|
|
|
|
faceVector.mag = Math.sqrt(faceVector.x*faceVector.x + faceVector.y*faceVector.y);
|
|
|
|
var max_distance = 5;
|
|
|
|
if(faceVector.mag>max_distance){
|
|
|
|
faceVector.x = faceVector.x * (max_distance/faceVector.mag);
|
|
|
|
faceVector.y = faceVector.y * (max_distance/faceVector.mag);
|
|
|
|
}
|
|
|
|
self.faceX = self.faceX*_faceFollow + faceVector.x*(1-_faceFollow);
|
|
|
|
self.faceY = self.faceY*_faceFollow + faceVector.y*(1-_faceFollow);
|
|
|
|
|
|
|
|
// Blink?
|
|
|
|
if(!self.faceBlink){
|
|
|
|
if(Math.random()<0.002) self.faceBlink=true;
|
|
|
|
}else{
|
2018-04-01 11:50:29 +00:00
|
|
|
if(Math.random()<0.07) self.faceBlink=false;
|
2018-03-27 17:39:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Friends connected... or infected
|
2018-03-27 19:20:22 +00:00
|
|
|
var friends = self.sim.getFriendsOf(self);
|
2018-03-27 17:39:08 +00:00
|
|
|
self.numFriends = friends.length;
|
|
|
|
self.numInfectedFriends = 0;
|
|
|
|
friends.forEach(function(friend){
|
2018-03-27 19:20:22 +00:00
|
|
|
if(friend.infected) self.numInfectedFriends++;
|
2018-03-27 17:39:08 +00:00
|
|
|
});
|
|
|
|
|
2018-04-01 16:34:52 +00:00
|
|
|
// Past threshold?
|
|
|
|
self.isPastThreshold = false;
|
|
|
|
if(self.sim.contagion==0){
|
|
|
|
// simple
|
|
|
|
if(self.numInfectedFriends>0) self.isPastThreshold = true;
|
|
|
|
}else{
|
|
|
|
// complex
|
|
|
|
if(self.numFriends>0){
|
|
|
|
var ratio = self.numInfectedFriends/self.numFriends;
|
|
|
|
if(ratio>=self.sim.contagion-0.0001){ // floating point errors
|
|
|
|
self.isPastThreshold = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-10 17:00:22 +00:00
|
|
|
// SPLASH: FORCE-DIRECTED
|
|
|
|
if(self.sim.options.splash){
|
|
|
|
|
|
|
|
// Attract towards 0 (gravity increases slightly the further you get out)
|
|
|
|
var gravity = getUnitVector({
|
|
|
|
x: 0 - self.x,
|
|
|
|
y: 0 - self.y
|
|
|
|
});
|
|
|
|
var gravityScale = getVectorLength(self)*0.00012;
|
2018-04-15 21:24:22 +00:00
|
|
|
if(self.sim.options.CONCLUSION){
|
|
|
|
gravityScale *= 2;
|
|
|
|
}
|
2018-04-10 17:00:22 +00:00
|
|
|
gravity = multiplyVector(gravity, gravityScale);
|
|
|
|
self.velocity = addVectors(self.velocity, gravity);
|
|
|
|
|
|
|
|
// If within the ring, push OUT.
|
2018-04-15 21:24:22 +00:00
|
|
|
if(!self.sim.options.CONCLUSION){
|
|
|
|
var RADIUS = 325;
|
|
|
|
var distanceFromCenter = getVectorLength(self);
|
|
|
|
if(distanceFromCenter<RADIUS){
|
|
|
|
var forcePushOut = RADIUS-distanceFromCenter;
|
|
|
|
forcePushOut *= 0.05;
|
|
|
|
forcePushOut = Math.min(forcePushOut, 2); //cap
|
|
|
|
var forceDirection = getUnitVector(self);
|
|
|
|
var forceOut = multiplyVector( forceDirection, forcePushOut );
|
|
|
|
self.velocity = addVectors(self.velocity, forceOut);
|
|
|
|
}
|
2018-04-10 17:00:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Hookes to Connected
|
|
|
|
var k = 0.002;
|
2018-04-15 21:24:22 +00:00
|
|
|
var hookesDistance = 140;
|
2018-04-10 17:00:22 +00:00
|
|
|
var hookesTotalForce = {x:0, y:0};
|
|
|
|
friends.forEach(function(friend){
|
|
|
|
|
|
|
|
var fromTo = getVectorFromTo(self, friend);
|
|
|
|
var d = getVectorLength(fromTo) - hookesDistance;
|
|
|
|
var forceMagnitude = k*d;
|
|
|
|
var force = multiplyVector( getUnitVector(fromTo), forceMagnitude );
|
|
|
|
|
|
|
|
hookesTotalForce = addVectors(hookesTotalForce, force);
|
|
|
|
|
|
|
|
});
|
|
|
|
self.velocity = addVectors(self.velocity, hookesTotalForce);
|
|
|
|
|
|
|
|
// Coulomb from Disconnected
|
|
|
|
var c = -300;
|
2018-04-15 21:24:22 +00:00
|
|
|
if(self.sim.options.CONCLUSION){
|
|
|
|
c = -400;
|
|
|
|
}
|
2018-04-10 17:00:22 +00:00
|
|
|
var coulombTotalForce = {x:0, y:0};
|
|
|
|
self.sim.peeps.forEach(function(peep){
|
|
|
|
|
|
|
|
if(peep==self) return; // not self
|
|
|
|
if(friends.indexOf(peep)>=0) return; // not a friend
|
|
|
|
|
|
|
|
var fromTo = getVectorFromTo(self, peep);
|
|
|
|
var d = getVectorLength(fromTo);
|
|
|
|
var forceMagnitude = c/(d*d);
|
|
|
|
var force = multiplyVector( getUnitVector(fromTo), forceMagnitude );
|
|
|
|
|
|
|
|
coulombTotalForce = addVectors(coulombTotalForce, force);
|
|
|
|
|
|
|
|
});
|
|
|
|
self.velocity = addVectors(self.velocity, coulombTotalForce);
|
|
|
|
|
|
|
|
// Travel Clockwise
|
|
|
|
var spin = getUnitVector(self);
|
|
|
|
spin = rotateVector(spin, Math.TAU/4);
|
2018-04-20 20:10:18 +00:00
|
|
|
spin = multiplyVector(spin, 0.02*0.8);
|
2018-04-10 17:00:22 +00:00
|
|
|
self.velocity = addVectors(self.velocity, spin);
|
|
|
|
|
|
|
|
// Air Friction
|
|
|
|
self.velocity = multiplyVector(self.velocity, 0.95);
|
|
|
|
|
|
|
|
// Move
|
|
|
|
self.x += self.velocity.x;
|
|
|
|
self.y += self.velocity.y;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-03-27 17:39:08 +00:00
|
|
|
};
|
|
|
|
|
2018-04-01 11:50:29 +00:00
|
|
|
// Body Sprite
|
2018-04-18 18:02:27 +00:00
|
|
|
var _initSpriteScale = 0.3;
|
2018-04-01 11:50:29 +00:00
|
|
|
self.sprite = new Sprite({
|
|
|
|
src: "sprites/peeps.png",
|
|
|
|
frames:6, sw:200, sh:200,
|
|
|
|
});
|
|
|
|
self.sprite.pivotX = 100;
|
|
|
|
self.sprite.pivotY = 100;
|
2018-04-01 16:34:52 +00:00
|
|
|
self.sprite.scale = _initSpriteScale;
|
2018-04-18 18:02:27 +00:00
|
|
|
|
|
|
|
// Prop Sprite
|
|
|
|
self.propSprite = new Sprite({
|
|
|
|
src: "sprites/peeps.png",
|
|
|
|
frames:6, sw:200, sh:200,
|
|
|
|
});
|
|
|
|
self.propSprite.pivotX = 100;
|
|
|
|
self.propSprite.pivotY = 100;
|
|
|
|
self.propSprite.scale = _initSpriteScale;
|
|
|
|
var _bottleAnim = Math.random()*Math.TAU;
|
|
|
|
var _bottleSpeed = 0.01 + Math.random()*0.01;
|
2018-04-01 11:50:29 +00:00
|
|
|
|
2018-03-27 17:39:08 +00:00
|
|
|
// Draw
|
2018-03-27 19:20:22 +00:00
|
|
|
var radius = 25;
|
2018-04-12 17:12:45 +00:00
|
|
|
var barWidth = radius*2;
|
2018-03-27 17:39:08 +00:00
|
|
|
var barHeight = 10;
|
2018-04-01 11:50:29 +00:00
|
|
|
var bodyRotation = Math.TAU*Math.random();
|
2018-04-01 16:34:52 +00:00
|
|
|
var PEEP_COLORS = [
|
|
|
|
"#B4B4B4", // gray
|
|
|
|
"#F73C50", // red
|
|
|
|
"#FEE576", // yellow
|
|
|
|
"#86F5FB", // blue
|
|
|
|
"#7DE74E", // green
|
|
|
|
"#FBCBDC" // pink
|
|
|
|
];
|
2018-04-15 21:24:22 +00:00
|
|
|
var _glowAnim = 0;
|
|
|
|
self.conclusionFrame = Math.floor(1+Math.random()*5);
|
2018-03-27 17:39:08 +00:00
|
|
|
self.draw = function(ctx){
|
|
|
|
|
|
|
|
ctx.save();
|
|
|
|
ctx.translate(self.x, self.y);
|
|
|
|
|
2018-04-01 16:34:52 +00:00
|
|
|
var s;
|
|
|
|
if(s = self.sim.options.scale) ctx.scale(s,s);
|
|
|
|
|
2018-03-27 17:39:08 +00:00
|
|
|
// Circle
|
2018-04-01 16:34:52 +00:00
|
|
|
var infectedFrame = self.sim.options.infectedFrame || 1;
|
|
|
|
var infectedColor = PEEP_COLORS[infectedFrame];
|
|
|
|
var myFrame = self.infected ? infectedFrame : 0;
|
|
|
|
var myColor = PEEP_COLORS[myFrame];
|
2018-04-15 21:24:22 +00:00
|
|
|
// CONCLUSION SPLASH
|
|
|
|
if(self.sim.options.CONCLUSION){
|
|
|
|
var distance = getVectorLength(self);
|
|
|
|
if(distance < self.sim.options.CONCLUSION_GLOW_RADIUS){
|
|
|
|
//self.isPastThreshold = true;
|
|
|
|
myFrame = self.conclusionFrame;
|
|
|
|
infectedFrame = self.conclusionFrame;
|
|
|
|
}
|
|
|
|
}
|
2018-04-01 11:50:29 +00:00
|
|
|
self.sprite.rotation = bodyRotation;
|
2018-04-15 21:24:22 +00:00
|
|
|
if(self.isPastThreshold){ // highlight! glow!
|
2018-04-01 16:34:52 +00:00
|
|
|
|
2018-04-15 21:24:22 +00:00
|
|
|
_glowAnim += 0.03;
|
|
|
|
var _glowScale = 1 + Math.sin(_glowAnim)*0.04;
|
|
|
|
ctx.globalAlpha = 0.35;
|
|
|
|
self.sprite.scale = _initSpriteScale*1.25*_glowScale;
|
2018-04-01 16:34:52 +00:00
|
|
|
|
|
|
|
self.sprite.gotoFrame(infectedFrame);
|
|
|
|
self.sprite.draw(ctx);
|
|
|
|
|
2018-04-15 21:24:22 +00:00
|
|
|
// undo
|
2018-04-01 16:34:52 +00:00
|
|
|
ctx.globalAlpha = 1;
|
|
|
|
self.sprite.scale = _initSpriteScale;
|
|
|
|
|
|
|
|
}
|
|
|
|
self.sprite.gotoFrame(myFrame);
|
2018-04-01 11:50:29 +00:00
|
|
|
self.sprite.draw(ctx);
|
2018-03-27 17:39:08 +00:00
|
|
|
|
|
|
|
// Face
|
|
|
|
ctx.save();
|
|
|
|
ctx.translate(self.faceX, self.faceY);
|
2018-04-01 11:50:29 +00:00
|
|
|
self.sprite.rotation = 0;
|
|
|
|
self.sprite.gotoFrame(self.faceBlink ? 7 : 6);
|
2018-04-18 18:02:27 +00:00
|
|
|
if(self.sim.options._wisdom && self.infected){
|
|
|
|
self.sprite.gotoFrame(10);
|
|
|
|
}
|
|
|
|
if(self.sim.options._bottle && self.infected){
|
|
|
|
self.sprite.rotation = Math.sin(_bottleAnim*1.5)*0.15;
|
|
|
|
}
|
2018-04-01 11:50:29 +00:00
|
|
|
self.sprite.draw(ctx);
|
2018-03-27 17:39:08 +00:00
|
|
|
ctx.restore();
|
|
|
|
|
2018-04-18 18:02:27 +00:00
|
|
|
// PROPS?
|
|
|
|
if(self.sim.options._bottle && self.infected){
|
|
|
|
self.propSprite.x = 25;
|
|
|
|
self.propSprite.y = 15;
|
|
|
|
_bottleAnim += _bottleSpeed;
|
|
|
|
self.propSprite.scale = 0.25;
|
|
|
|
self.propSprite.rotation = 0.2 + Math.sin(_bottleAnim)*0.2;
|
|
|
|
self.propSprite.gotoFrame(9);
|
|
|
|
self.propSprite.draw(ctx);
|
|
|
|
}
|
|
|
|
if(self.sim.options._dunce && self.infected){
|
|
|
|
self.propSprite.x = -14;
|
|
|
|
self.propSprite.y = -22;
|
|
|
|
self.propSprite.scale = 0.25;
|
|
|
|
self.propSprite.gotoFrame(8);
|
|
|
|
self.propSprite.draw(ctx);
|
|
|
|
}
|
|
|
|
|
2018-03-27 17:39:08 +00:00
|
|
|
//////////////////////////////////////////////////////////
|
|
|
|
// LABEL FOR INFECTED/FRIENDS, BAR, AND CONTAGION LEVEL //
|
|
|
|
//////////////////////////////////////////////////////////
|
|
|
|
|
2018-04-01 16:34:52 +00:00
|
|
|
// DON'T show bar if simple contagion
|
2018-03-28 14:57:19 +00:00
|
|
|
if(self.sim.contagion>0){
|
2018-03-27 17:39:08 +00:00
|
|
|
|
|
|
|
ctx.save();
|
|
|
|
|
2018-04-15 21:24:22 +00:00
|
|
|
// SHAKE
|
|
|
|
if(self._shakeAnim>=0){
|
|
|
|
var shake = Math.sin(self._shakeAnim*10)*3;
|
|
|
|
ctx.translate(shake, 0);
|
|
|
|
self._shakeAnim -= 0.05;
|
|
|
|
}
|
|
|
|
|
2018-04-03 17:32:26 +00:00
|
|
|
var bgColor = "#ddd";
|
|
|
|
var uiColor = "#333";
|
2018-04-01 16:34:52 +00:00
|
|
|
|
|
|
|
// Say: Infected/Friends (% then n/n)
|
2018-04-18 14:51:44 +00:00
|
|
|
ctx.translate(0,-46);
|
2018-04-15 21:24:22 +00:00
|
|
|
ctx.font = '12px PatrickHand';
|
2018-04-01 16:34:52 +00:00
|
|
|
ctx.fillStyle = uiColor;
|
2018-03-27 17:39:08 +00:00
|
|
|
ctx.textBaseline = "middle";
|
|
|
|
ctx.fontWeight = "bold";
|
2018-04-01 16:34:52 +00:00
|
|
|
if(self.numFriends>0){
|
2018-04-12 17:12:45 +00:00
|
|
|
|
2018-04-18 14:51:44 +00:00
|
|
|
// %, centered
|
|
|
|
ctx.textAlign = "center";
|
|
|
|
var labelPercent = Math.round(100*(self.numInfectedFriends/self.numFriends)) + "%";
|
|
|
|
ctx.fillText(labelPercent, 0, 0);
|
|
|
|
|
|
|
|
/*
|
2018-04-16 19:14:08 +00:00
|
|
|
// %
|
2018-04-12 17:12:45 +00:00
|
|
|
ctx.textAlign = "left";
|
2018-04-16 19:14:08 +00:00
|
|
|
var labelPercent = Math.round(100*(self.numInfectedFriends/self.numFriends)) + "%";
|
|
|
|
ctx.fillText(labelPercent, -barWidth/2, 0);
|
2018-04-12 17:12:45 +00:00
|
|
|
|
2018-04-16 19:14:08 +00:00
|
|
|
// #/#
|
2018-04-12 17:12:45 +00:00
|
|
|
ctx.textAlign = "right";
|
2018-04-16 19:14:08 +00:00
|
|
|
var labelNum = self.numInfectedFriends+"/"+self.numFriends;
|
|
|
|
ctx.fillText(labelNum, barWidth/2, 0);
|
2018-04-18 14:51:44 +00:00
|
|
|
*/
|
2018-04-12 17:12:45 +00:00
|
|
|
|
|
|
|
|
2018-04-01 16:34:52 +00:00
|
|
|
}else{
|
2018-04-12 17:12:45 +00:00
|
|
|
ctx.textAlign = "center";
|
2018-04-01 16:34:52 +00:00
|
|
|
ctx.fillText("∅", 0, -1);
|
|
|
|
}
|
2018-03-27 17:39:08 +00:00
|
|
|
|
2018-04-01 16:34:52 +00:00
|
|
|
// the gray bg
|
2018-04-18 14:51:44 +00:00
|
|
|
ctx.translate(0,13);
|
2018-04-01 16:34:52 +00:00
|
|
|
ctx.fillStyle = bgColor;
|
2018-03-27 17:39:08 +00:00
|
|
|
ctx.beginPath();
|
|
|
|
ctx.rect(-barWidth/2, -barHeight/2, barWidth, barHeight);
|
|
|
|
ctx.fill();
|
|
|
|
|
2018-04-01 16:34:52 +00:00
|
|
|
// the color fill
|
2018-03-27 17:39:08 +00:00
|
|
|
if(self.numFriends>0){
|
2018-04-01 16:34:52 +00:00
|
|
|
ctx.fillStyle = infectedColor;
|
2018-03-27 19:20:22 +00:00
|
|
|
ctx.beginPath();
|
|
|
|
ctx.rect(-barWidth/2, -barHeight/2, barWidth*(self.numInfectedFriends/self.numFriends), barHeight);
|
|
|
|
ctx.fill();
|
2018-03-27 17:39:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// a pointer for contagion level
|
2018-04-01 16:34:52 +00:00
|
|
|
ctx.translate(0, -barHeight/2);
|
|
|
|
ctx.save();
|
|
|
|
ctx.translate(barWidth*self.sim.contagion - barWidth/2, 0);
|
|
|
|
ctx.lineCap = "butt";
|
|
|
|
ctx.strokeStyle = uiColor;
|
2018-04-18 14:51:44 +00:00
|
|
|
ctx.lineWidth = 1.5;
|
2018-04-01 16:34:52 +00:00
|
|
|
ctx.beginPath();
|
2018-04-18 14:51:44 +00:00
|
|
|
ctx.moveTo(0,-2);
|
|
|
|
ctx.lineTo(0,barHeight+2);
|
2018-04-01 16:34:52 +00:00
|
|
|
ctx.stroke();
|
|
|
|
ctx.restore();
|
2018-03-27 17:39:08 +00:00
|
|
|
|
|
|
|
ctx.restore();
|
|
|
|
|
2018-03-28 14:57:19 +00:00
|
|
|
}
|
2018-03-27 17:39:08 +00:00
|
|
|
|
|
|
|
ctx.restore();
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
// Hit Test
|
|
|
|
self.hitTest = function(x,y,buffer){
|
2018-04-18 14:51:44 +00:00
|
|
|
// ACTUALLY IGNORE BUFFER'S AMOUNT, IT'S TRUE OR FALSE.
|
|
|
|
// if(buffer===undefined) buffer=0;
|
|
|
|
buffer = !!buffer;
|
2018-03-27 17:39:08 +00:00
|
|
|
var dx = self.x-x;
|
|
|
|
var dy = self.y-y;
|
|
|
|
var dist2 = dx*dx+dy*dy;
|
2018-04-18 14:51:44 +00:00
|
|
|
|
|
|
|
var r = radius;
|
2018-04-01 16:34:52 +00:00
|
|
|
var s;
|
|
|
|
if(s = self.sim.options.scale) r*=s;
|
2018-04-18 14:51:44 +00:00
|
|
|
|
|
|
|
r = buffer ? Math.max(r+10, 40) : r;
|
|
|
|
|
2018-03-27 17:39:08 +00:00
|
|
|
return (dist2<r*r);
|
2018-04-18 14:51:44 +00:00
|
|
|
|
2018-03-27 17:39:08 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Infect
|
|
|
|
self.infect = function(){
|
2018-03-27 19:20:22 +00:00
|
|
|
self.infected = true;
|
|
|
|
};
|
2018-03-27 17:39:08 +00:00
|
|
|
|
2018-04-15 21:24:22 +00:00
|
|
|
// Shake
|
|
|
|
self._shakeAnim = -1;
|
|
|
|
self.shake = function(){
|
|
|
|
self._shakeAnim = 1;
|
|
|
|
};
|
|
|
|
|
2018-03-27 17:39:08 +00:00
|
|
|
}
|