function Peep(config){ var self = this; // Properties self.x = config.x; self.y = config.y; self.velocity = {x:0, y:0}; self.infected = !!config.infected; self.sim = config.sim; // Update: self.numFriends = 0; self.numInfectedFriends = 0; self.isPastThreshold = false; 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 = { x: (self.sim.mouse.x-self.x)/5, y: (self.sim.mouse.y-self.y)/5 }; 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{ if(Math.random()<0.07) self.faceBlink=false; } // Friends connected... or infected var friends = self.sim.getFriendsOf(self); self.numFriends = friends.length; self.numInfectedFriends = 0; friends.forEach(function(friend){ if(friend.infected) self.numInfectedFriends++; }); // 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; } } } // 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; gravity = multiplyVector(gravity, gravityScale); self.velocity = addVectors(self.velocity, gravity); // If within the ring, push OUT. var RADIUS = 325; var distanceFromCenter = getVectorLength(self); if(distanceFromCenter=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); spin = multiplyVector(spin, 0.02); 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; } }; // Body Sprite self.sprite = new Sprite({ src: "sprites/peeps.png", frames:6, sw:200, sh:200, }); self.sprite.pivotX = 100; self.sprite.pivotY = 100; var _initSpriteScale = 0.3; self.sprite.scale = _initSpriteScale; //self.sprite.gotoFrame(1); // Draw var radius = 25; var barWidth = radius*1.75; var barHeight = 10; var bodyRotation = Math.TAU*Math.random(); var PEEP_COLORS = [ "#B4B4B4", // gray "#F73C50", // red "#FEE576", // yellow "#86F5FB", // blue "#7DE74E", // green "#FBCBDC" // pink ]; self.draw = function(ctx){ ctx.save(); ctx.translate(self.x, self.y); var s; if(s = self.sim.options.scale) ctx.scale(s,s); // Circle var infectedFrame = self.sim.options.infectedFrame || 1; var infectedColor = PEEP_COLORS[infectedFrame]; var myFrame = self.infected ? infectedFrame : 0; var myColor = PEEP_COLORS[myFrame]; self.sprite.rotation = bodyRotation; if(self.isPastThreshold){ // highlight! ctx.globalAlpha = 0.4; self.sprite.scale = _initSpriteScale*1.25; self.sprite.gotoFrame(infectedFrame); self.sprite.draw(ctx); ctx.globalAlpha = 1; self.sprite.scale = _initSpriteScale; } self.sprite.gotoFrame(myFrame); self.sprite.draw(ctx); // Face ctx.save(); ctx.translate(self.faceX, self.faceY); self.sprite.rotation = 0; self.sprite.gotoFrame(self.faceBlink ? 7 : 6); self.sprite.draw(ctx); ctx.restore(); ////////////////////////////////////////////////////////// // LABEL FOR INFECTED/FRIENDS, BAR, AND CONTAGION LEVEL // ////////////////////////////////////////////////////////// // DON'T show bar if simple contagion if(self.sim.contagion>0){ ctx.save(); var bgColor = "#ddd"; var uiColor = "#333"; // Say: Infected/Friends (% then n/n) ctx.translate(0,-43); ctx.font = '10px FuturaHandwritten'; ctx.fillStyle = uiColor; ctx.textBaseline = "middle"; ctx.fontWeight = "bold"; ctx.textAlign = "center"; if(self.numFriends>0){ var labelNum = self.numInfectedFriends+"/"+self.numFriends; var labelPercent = Math.round(100*(self.numInfectedFriends/self.numFriends)) + "%"; var label = labelNum + "=" + labelPercent; ctx.fillText(label, 0, 0); }else{ ctx.fillText("∅", 0, -1); } // the gray bg ctx.translate(0,10); ctx.fillStyle = bgColor; ctx.beginPath(); ctx.rect(-barWidth/2, -barHeight/2, barWidth, barHeight); ctx.fill(); // the color fill if(self.numFriends>0){ ctx.fillStyle = infectedColor; ctx.beginPath(); ctx.rect(-barWidth/2, -barHeight/2, barWidth*(self.numInfectedFriends/self.numFriends), barHeight); ctx.fill(); } // a pointer for contagion level ctx.translate(0, -barHeight/2); ctx.save(); ctx.translate(barWidth*self.sim.contagion - barWidth/2, 0); ctx.lineCap = "butt"; ctx.strokeStyle = uiColor; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(0,barHeight); ctx.stroke(); ctx.restore(); ctx.restore(); } ctx.restore(); }; // Hit Test self.hitTest = function(x,y,buffer){ if(buffer===undefined) buffer=0; var dx = self.x-x; var dy = self.y-y; var dist2 = dx*dx+dy*dy; var r = radius+buffer; var s; if(s = self.sim.options.scale) r*=s; return (dist2