Skip to content

Instantly share code, notes, and snippets.

@edeetee
Forked from dribnet/.block
Last active June 8, 2017 21:43
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/6ff4a260e77466f45231d15c53c66160 to your computer and use it in GitHub Desktop.
Save edeetee/6ff4a260e77466f45231d15c53c66160 to your computer and use it in GitHub Desktop.
17.1.MDDN242 PS4
license: mit
// 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');
}

PS4 MDDN 242 2017

Particles

I have tried to create a kind of simulation of particles and of the idea of groups working together. Different types attract from a distance, same types repel strongly but only very close together. The combination of these forces in different proportions results in an interesting pattern and cycle of moving particles.

I wanted to show the complex relationships between particles, how systems can be generated out of nothing and for people to see how interesting things can come out of basic instructions. One possible angle to take on this would be to show how these systems have reproducible patterns that are more stable than others, just like real live, due to how the smaller rules work together.

I tried really hard with many iterations to get a simulation that worked nicely with interaction and possessed many interesting and useful qualities. To this end, I had to extend the built in code by changing the rendering order and have added my own gui so that one may play around with the values.

This version I have tried out using rings to show when the speed changes. I think this solves the problem of not being able to see some particles while also makes the velocity changes look nice. I also added code that allows the particles to fly across the end of the screen to the other side, as I think them banging against the wall doesn't look satisfactory.

I find it very interesting how they appear as different ordered groups, some which are volatile and lively, and some which basically never move and are very stable. I think this is a nice representation of how there can be different particle interactions in the real world, some stable and barely moving, and others which are active and full of changes, but both are in a kind of balance.

Its interesting how you also get some cycles of interactions similar to my previous experiments, such as particles jumping out then pulling the other group along with them where they end up coalescing with another large group.

I set the default size as 64 as It generally generates only a few groups but different ones with different levels of excitement. The simulation cannot run 16 as it is too slow, but 32 and above is fine.

I also made my code keep a constant speed by keeping it relational to the speed value.

Was previously randomizing the order in which they render as well as adding a slight darkness to the back ones to look nicer and to give a more 3d effect. I pick colors around a color wheel with 30 degree increments that I've tested after adding random other data. I now leave them in the normal (top-down) order as it introduces bugs in the code, but I still render the darker colors.

I tested the final variables I worked on by adding to the debug panel and adding the following parameters:

Some interactions:

  • Increasing Same Radius will make it act more like a gas, they flow more freely due to the repulsion of each other and don't cause as many distinct balls, more zones.
  • Same Radius will effect how far away particles can fly away. This means more spreading, and more other groups being formed.
  • Decreasing Different Radius will make them free at 0 and at slightly higher a more free area. Decreasing also slows the effective time of the simulation, as it is the main interaction always working. Increasing it allows it to make larger groups of matter while visually being faster as they have more area and therefore time to generate speed.
  • Increasing Different Velocity makes it more relaxed with smaller orbits and speeds up interaction with other groups.
  • The change of colors will also change with the velocity of the particle. You can get quite interesting interactions with the right parameters. Placing this on a high level will create quite psychedelic results.
  • Moving your mouse on the screen will repel all others.
  • enable pipe mode to show a trail of renders. Quite pretty.
  • the pipe can be combined with other effects to make creative visuals.
function Agent1() {
// any variables you add here are for your own internal state
this.strength = 0;
this.chain = null;
// setup is run when the agent is reset
// value is a number between 0 and 100
this.setup = function(value) {
if(value > 50) {
this.strength = 1;
this.chain = random();
}
else {
this.strength = 0;
}
}
// this happens generally on mouse over
this.activate = function() {
this.strength = 1;
this.chain = random();
}
// decide on your next move based on neighbors (but don't do it yet)
this.step = constantFlow
var continueRatio = 2;
var takeoverRatio = 10;
function constantFlow(neighbors){
var largestStrengthChain = null;
var largestStrength = null;
for(var i = 0; i < neighbors.length; i++){
var strength = neighbors[i].agent.strength;
var chain = neighbors[i].agent.chain;
//if part of current chain
if(chain == this.chain){
//if neighbor larger than the ratio
if(this.strength < strength && largestStrength < strength)
largestStrength = strength;
//if not current chain, different ratio
} else if((!this.chain || this.strength < 0.01 || this.strength*takeoverRatio < strength) && largestStrength < strength){
largestStrength = 1;
largestStrengthChain = chain;
}
}
this.next_strength = Math.min((largestStrength || this.strength)*0.9, 1);
this.next_chain = largestStrengthChain || this.chain;
}
function separateFlow(neighbors){
var strength = this.strength;
var chain = this.chain;
var biggest_match = null;
neighbors.forEach(function(neighbor){
if(strength < 0.01 || (neighbor.agent.chain == chain && strength < neighbor.agent.strength)){
strength = neighbor.agent.strength;
chain = neighbor.agent.chain;
}
})
this.next_strength = strength*0.9;
this.next_chain = chain;
}
function slowFlow(neighbors){
this.next_strength = this.strength;
var strength = this.strength;
if(strength < 0.01*max_strength){
var max_neighbor = 0;
var tot_strength = 0;
neighbors.forEach(function(neighbor){
if(max_neighbor < neighbor.agent.strength)
max_neighbor = neighbor.agent.strength;
});
if(max_neighbor)
this.next_strength = max_neighbor*1.01;
} else{
var larger = 0;
neighbors.forEach(function(neighbor){
if(strength <= neighbor.agent.strength)
larger++;
});
if(larger)
this.next_strength = strength*0.9;
}
if(this.next_strength < 0)
this.next_strength = 0
if(max_strength < this.next_strength)
max_strength = this.next_strength;
}
var max_strength = 1;
var strength_steal = 0.8;
var steal_mod = 1;
function flashingStrength(neighbors) {
if(this.strength){
var alive_neighbors = 0;
var strong_neighbors = 0;
neighbors.forEach(function(neighbor){
if(neighbor.agent.strength && neighbor.agent.strength <= 3)
alive_neighbors++;
if(3 < neighbor.agent.strength){
strong_neighbors++;
}
});
var change = alive_neighbors-strong_neighbors;
this.next_strength = Math.floor(this.strength+change, 0);
if(max_strength < this.next_strength)
max_strength = this.next_strength;
}
else{
var strong_neighbors = 0;
neighbors.forEach(function(neighbor){
if(0.5*max_strength <= neighbor.agent.strength){
strong_neighbors += neighbor.agent.strength*strength_steal*steal_mod;
neighbor.agent.strength *= (1-strength_steal);
}
});
this.next_strength = Math.max(this.strength+strong_neighbors, 0);
}
}
// execute the next move you decided on
this.update_state = function() {
this.strength = this.next_strength;
this.chain = this.next_chain;
}
colorMode(HSB);
this.draw = function(size) {
if(!this.strength)
return;
var strengthp = this.strength/max_strength;
noStroke();
if(strengthp < 0.4){
var scale = strengthp/0.4;
fill(this.chain*360, strengthp*255, 255-100*scale);
rect(size*(1-scale)/2, size*(1-scale)/2, size*scale, size*scale);
}
else{
fill(this.chain*360, strengthp*255, 155);
rect(0, 0, size, size);
}
}
}
function Agent2() {
// any variables you add here are for your own internal state
this.power = 0.0;
this.next_power = 0.0;
// setup is run when the agent is reset
// value is a number between 0 and 100
this.setup = function(value, agent_type) {
this.power = value;
this.agent_type = agent_type;
this.next_power = this.power;
}
// this happens generally on mouse over
this.activate = function() {
this.power = 100.0;
}
// decide on your next move based on neighbors (but don't do it yet)
this.step = function(neighbors) {
var surrounding_power = 0;
var death_limit1 = 49.9999;
var death_limit2 = 50.0001;
for(var i=0; i<neighbors.length; i++) {
surrounding_power = surrounding_power + neighbors[i].agent.power;
}
var avg_power = surrounding_power / neighbors.length;
if(this.agent_type == 0) {
if(avg_power < death_limit1) {
this.next_power = 0;
}
else {
this.next_power = 100;
}
}
else {
if(avg_power < death_limit2) {
this.next_power = 0;
}
else {
this.next_power = 100;
}
}
if(this.next_power > 100) {
this.next_power = 100;
}
}
// execute the next move you decided on
this.update_state = function() {
this.power = this.next_power;
}
this.draw = function(size) {
var half_size = size/2;
var low = color(0, 0, 0);
var high = color(255, 255, 255);
var c = lerpColor(low, high, this.power / 100.0);
stroke(0);
fill(c);
ellipse(half_size, half_size, size, size);
}
}
var sameRadius;
var diffRadius;
var sameVel;
var diffVel;
var colorSpeed;
var drag;
var pipes;
function Agent3Preload() {
sameRadius = createSlider(0, 0.1, 0.012, 0.001);
sameRadius.parent('sameRadius');
sameRadius.changed(function(){console.log(sameRadius.value())})
sameVel = createSlider(0, 1, 0.6, 0.01);
sameVel.parent('sameVel');
sameVel.changed(function(){console.log(sameVel.value())})
diffRadius = createSlider(0, 0.5, 0.04, 0.01);
diffRadius.parent('diffRadius');
diffRadius.changed(function(){console.log(diffRadius.value())})
diffVel = createSlider(0, 0.1, 0.03, 0.001);
diffVel.parent('diffVel');
diffVel.changed(function(){console.log(diffVel.value())})
colorSpeed = createSlider(0, 1, 0, 0.01);
colorSpeed.parent('colorSpeed')
colorSpeed.changed(function(){console.log(diffVel.value())})
drag = createSlider(0, 0.3, .04, 0.005);
drag.parent('drag');
drag.changed(function(){console.log(drag.value())})
pipes = createCheckbox('click', true);
pipes.parent('pipes');
neighborVector = createVector(0,0);
posVector = createVector(0,0);
}
//for wrapping around the page
Number.prototype.mod = function(n) {
return ((this%n)+n)%n;
};
var maxVel = 30;
var neighborVector
var maxRadius = 0;
var colors = [0, 210, 270, 160, 30];
var drawIndex = 0;
var posVector;
function Agent3() {
// any variables you add here are for your own internal state
this.v = createVector(0);
// setup is run when the agent is reset
// value is a number between 0 and 100
this.setup = function(value, agent_type) {
this.agent_type = agent_type;
this.totDist = 0;
this.lastFrame = 0;
this.mouseVel = 0;
colorMode(HSB);
noFill();
}
// this happens generally on mouse over
this.activate = function() {
//this.touched = millis();
}
//function that gives back a number that is higher when the neighbor distance is closer
function distanceScale(neighbor, radiusSize, maxVel){
if(maxVel){
return constrain(1/sqrt(neighbor.distance/neighbor.radius/radiusSize)-1, 0, maxVel*neighbor.radius);
}else
return max(0, 1/sqrt(neighbor.distance/neighbor.radius/radiusSize)-1);
}
// decide on your next move based on neighbors (but don't do it yet)
this.step = function(neighbors, radius) {
//keep the maxRadius updated
if(maxRadius < radius)
maxRadius = radius;
//this a new frame. reset total values and count indexes
if(this.lastFrame != frameCount){
this.lastFrame = frameCount;
this.totDist = 0;
drawIndex = 0;
}
//move towards the mouse
posVector.set(this._x, this._y);
posVector.sub(mouseX, mouseY);
var mouseMag = posVector.mag();
if(mouseMag < diffRadius.value()*2000){
//make it be very strong at middle point. this could be improved possibly.
this.mouseVel = (-1/sqrt(mouseMag/20000/radius)-1)/20;
this.v.sub(posVector.normalize().mult(this.mouseVel/speedSelector.value()));
} else
//reset the value
this.mouseVel = 0;
for (var i = neighbors.length - 1; i >= 0; i--) {
var neighbor = neighbors[i];
var is_same = neighbor.agent.agent_type == this.agent_type;
//skip over calculations if outside bounds
if(diffRadius.value()*neighbor.radius < neighbor.distance || (is_same && sameRadius.value()*neighbor.radius < neighbor.distance))
continue;
//neighborVector is the normalised direction of the neighbor
neighborVector.set(neighbor.x, neighbor.y);
neighborVector.normalize();
//calculate the velocity based on the radius and other variables
if(!is_same)
var vel = distanceScale(neighbor, diffRadius.value()*radius, maxVel)*diffVel.value();
else
var vel = -distanceScale(neighbor, sameRadius.value()*radius, maxVel)*sameVel.value();
//update total velocity
this.totDist += vel;
//keep same speed over speed selectors
neighborVector.mult(vel/speedSelector.value()*10);
this.v.add(neighborVector);
}
//drag based off the size.
this.v.mult(1-(radius)/maxRadius*drag.value()/speedSelector.value()*10);
//makes it wrap around the screen
var x = this.v.x;
var y = this.v.y;
var border = radius;
if(canvasWidth-border <= (x + this._x))
x -= canvasWidth-border*3;
else if((x + this._x) <= border)
x += canvasWidth-border*3;
if(canvasHeight-border <= (y + this._y))
y -= canvasHeight-border*3;
else if((y + this._y) <= border)
y += canvasHeight+border*3;
return {x,y};
//return this.v;
}
this.curColor = 360;
this.draw = function(radius) {
this.curColor += this.totDist*colorSpeed.value();
var mag = this.v.mag();
//bigger with more speed
radius *= map(mag, 0, 10, 1, 2);
//drawing decimal
var drawp = (drawIndex%numActiveAgents)/numActiveAgents;
//color is agent type array modulus the personal max distance.
//saturation is the magnitide of the velocity
//brightness is the magnitude of the velocity as well
stroke((colors[this.agent_type]+this.curColor) % 360, Math.min(80+40*mag/maxVel, 100), 60+mag/maxVel*5*20);
//stroke weight is thinner when velocity is high and when draw index high (larger at back)
strokeWeight(radius*(0.4 + 0.6*(maxVel-mag)/maxVel)*(0.2 + 0.8*(1-drawp)));
ellipse(0, 0, radius*2, radius*2);
drawIndex++;
}
}
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.7/p5.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.7/addons/p5.dom.js"></script>
<script language="javascript" type="text/javascript" src=".purview_helper.js"></script>
<script language="javascript" type="text/javascript" src="agent3.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>World</td>
<td id="selector1Container"></td>
</tr>
<tr>
<td>Size</td>
<td id="selector2Container"></td>
</tr>
<tr>
<td>Speed</td>
<td id="selector3Container"></td>
</tr>
<tr>
<td></td>
<td id="playButtonContainer"></td>
</tr>
<tr>
<td></td>
<td id="clearButtonContainer"></td>
</tr>
<tr>
<td></td>
<td id="checkContainer"></td>
</tr>
<tr>
<td>Same Radius</td>
<td id="sameRadius"></td>
</tr>
<tr>
<td>Same Velocity</td>
<td id="sameVel"></td>
</tr>
<tr>
<td>Different Radius</td>
<td id="diffRadius"></td>
</tr>
<tr>
<td>Different Velocity</td>
<td id="diffVel"></td>
</tr>
<tr>
<td>Drag</td>
<td id="drag"></td>
</tr>
<tr>
<td>Color Speed</td>
<td id="colorSpeed"></td>
</tr>
<tr>
<td>Pipes Background</td>
<td id="pipes"></td>
</tr>
</div>
</div>
</table>
</body>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.7/p5.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.7/addons/p5.dom.js"></script>
<script language="javascript" type="text/javascript" src=".purview_helper.js"></script>
<script language="javascript" type="text/javascript" src="clock.js"></script>
<script language="javascript" type="text/javascript" src="sketch.js"></script>
</head>
<body style="background-color:white">
<div class="outer">
<div class="inner">
<div id="canvasContainer"></div>
</div>
</div>
</table>
</body>
var canvasWidth = 960;
var canvasHeight = 500;
var is_playing = false;
var show_oddball = false;
var modeSelector;
var sizeSelector;
var speedSelector;
var max_vals = [360, 100, 100];
var allAgents = [];
var numActiveAgents = 0;
var clearButton, randomButton, playButton, stepButton, stopButton;
var backgroundImage = null;
var agentLookupWidth = 1;
var agentLookupHeight = 1;
var agentLookupTable = [0];
var haveSeenMovement = false;
function preload() {
backgroundImage = loadImage("z_background.jpg");
// this is called for static assets your agents need
Agent3Preload();
}
function setup() {
// create the drawing canvas, save the canvas element
var main_canvas = createCanvas(canvasWidth, canvasHeight);
main_canvas.parent('canvasContainer');
modeSelector = createSelect();
modeSelector.option('grid');
modeSelector.option('hexgrid');
// modeSelector.option('vornoi');
// modeSelector.option('freestyle');
modeSelector.changed(gridTypeChanged);
modeSelector.value('hexgrid');
modeSelector.parent('selector1Container');
sizeSelector = createSelect();
sizeSelector.option('16');
sizeSelector.option('32');
sizeSelector.option('64');
sizeSelector.option('128');
sizeSelector.option('256');
sizeSelector.parent('selector2Container');
sizeSelector.value('64');
sizeSelector.changed(sizeChangedEvent);
speedSelector = createSelect();
speedSelector.option('1');
speedSelector.option('2');
speedSelector.option('5');
speedSelector.option('10');
speedSelector.option('24');
speedSelector.option('60');
speedSelector.parent('selector3Container');
speedSelector.value('60');
speedSelector.changed(speedChangedEvent);
stepButton = createButton('step');
stepButton.mousePressed(stepButtonPressedEvent);
stepButton.parent('playButtonContainer');
playButton = createButton('play');
playButton.mousePressed(playButtonPressedEvent);
playButton.parent('playButtonContainer');
// stopButton = createButton('stop');
// stopButton.mousePressed(stopButtonPressedEvent);
// stopButton.parent('playButtonContainer');
clearButton = createButton('reset');
clearButton.mousePressed(clearButtonPressedEvent);
clearButton.parent('clearButtonContainer');
randomButton = createButton('random');
randomButton.mousePressed(randomButtonPressedEvent);
randomButton.parent('clearButtonContainer');
// guideCheckbox = createCheckbox('', false);
// guideCheckbox.parent('checkContainer');
// guideCheckbox.changed(guideChangedEvent);
// setup lookup table from pixels
backgroundImage.loadPixels();
agentLookupHeight = backgroundImage.height;
agentLookupWidth = backgroundImage.width;
agentLookupTable = new Array(agentLookupHeight);
var p = backgroundImage.pixels;
for(var j=0; j<agentLookupHeight; j++) {
agentLookupTable[j] = new Array(agentLookupWidth);
for(var i=0; i<agentLookupWidth; i++) {
var ix = 4 * (j * agentLookupWidth + i);
if(p[ix] > 128 && p[ix+1] > 128 && p[ix+2] > 128) {
// white
agentLookupTable[j][i] = 0;
}
else if(p[ix] < 128 && p[ix+1] < 128 && p[ix+2] < 128) {
// black
agentLookupTable[j][i] = 1;
}
else if(p[ix] > p[ix+1] && p[ix] > p[ix+2] ) {
// red-ish
agentLookupTable[j][i] = 2;
}
else if(p[ix+1] > p[ix] && p[ix+1] > p[ix+2] ) {
// green-ish
agentLookupTable[j][i] = 3;
}
else {
agentLookupTable[j][i] = 4;
}
}
}
noLoop();
refreshGridData();
modeChangedEvent();
speedChangedEvent();
playButtonPressedEvent();
}
/*
function mouseClicked() {
if (mouseX > width/4) {
refreshGridData();
}
redraw();
}
*/
var numGridRows;
var numGridCols;
var gridValues; // row, col order
var gridOffsetX, gridOffsetY;
var gridSpacingX, gridSpacingY;
// Generate data for putting glyphs in a grid
function clamp(num, min, max) {
return Math.min(Math.max(num, min), max);
}
function getNewAgent() {
a = new Agent3();
return a;
}
function lookupAgentType(x, y, size) {
s2 = size/2;
x += s2;
y += s2;
if(x < 0 || y < 0 || x >= canvasWidth || y >= canvasHeight) {
print("ERROR");
return 0;
}
var ix = int(map(x, 0, canvasWidth, 0, agentLookupWidth));
var iy = int(map(y, 0, canvasHeight, 0, agentLookupHeight));
return agentLookupTable[iy][ix];
}
function refreshGridData(random) {
var mode = modeSelector.value();
var glyphSize = parseInt(sizeSelector.value(), 10);
if (mode == "hexgrid") {
if(glyphSize == 16) {
numGridCols = 58;
numGridRows = 33;
gridOffsetX = 20;
gridSpacingX = 16;
gridOffsetY = 1;
gridSpacingY = 15;
}
if(glyphSize == 32) {
numGridCols = 30;
numGridRows = 17;
gridOffsetX = 10;
gridSpacingX = 31;
gridOffsetY = 2;
gridSpacingY = 29;
}
else if(glyphSize == 64) {
numGridCols = 13;
numGridRows = 7;
gridOffsetX = 35;
gridSpacingX = 66;
gridOffsetY = 35;
gridSpacingY = 59;
}
else if(glyphSize == 128) {
numGridCols = 7;
numGridRows = 4;
gridOffsetX = 10;
gridSpacingX = 132;
gridOffsetY = 0;
gridSpacingY = 118;
}
else if(glyphSize == 256) {
numGridCols = 3;
numGridRows = 2;
gridOffsetX = 96;
gridSpacingX = 262;
gridOffsetY = 0;
gridSpacingY = 234;
}
}
else if(glyphSize == 128) {
numGridCols = 7;
numGridRows = 3;
gridOffsetX = 10;
gridSpacingX = 136;
gridOffsetY = 20;
gridSpacingY = 166;
}
else if(glyphSize == 256) {
numGridCols = 3;
numGridRows = 1;
gridOffsetX = 20;
gridSpacingX = 320;
gridOffsetY = 100;
gridSpacingY = 500;
}
else if(glyphSize == 64) {
numGridCols = 14;
numGridRows = 7;
gridOffsetX = 3;
gridSpacingX = 68;
gridOffsetY = 6;
gridSpacingY = 71;
}
else if(glyphSize == 32) {
numGridCols = 24;
numGridRows = 13;
gridOffsetX = 4;
gridSpacingX = 40;
gridOffsetY = 4;
gridSpacingY = 38;
}
else if(glyphSize == 16) {
numGridCols = 48;
numGridRows = 25;
gridOffsetX = 1;
gridSpacingX = 20;
gridOffsetY = 1;
gridSpacingY = 20;
}
// this updates the grid to account for center spacing
gridOffsetX += glyphSize/2;
gridOffsetY += glyphSize/2;
// determine active agents and reset
numActiveAgents = 0;
var hexOffset = (mode == "hexgrid");
gridValues = new Array(numGridRows);
for (var i=0; i<numGridRows; i++) {
var tweakedNumGridCols = numGridCols;
if (hexOffset && i%2 == 1) {
tweakedNumGridCols = numGridCols - 1;
}
gridValues[i] = new Array(tweakedNumGridCols);
for (var j=0; j<tweakedNumGridCols; j++) {
if(numActiveAgents >= allAgents.length) {
allAgents.push(getNewAgent());
}
gridValues[i][j] = allAgents[numActiveAgents];
numActiveAgents = numActiveAgents + 1;
}
}
// assign positions
for (var i=0; i<numGridRows; i++) {
var tweakedNumGridCols = numGridCols;
var offsetX = 0;
if (hexOffset && i%2 == 1) {
offsetX = gridSpacingX / 2;
tweakedNumGridCols = numGridCols - 1;
}
for (var j=0; j<tweakedNumGridCols; j++) {
gridValues[i][j]._x = gridOffsetX + j * gridSpacingX + offsetX;
gridValues[i][j]._y = gridOffsetY + i * gridSpacingY;
gridValues[i][j]._type = !random ? lookupAgentType(gridValues[i][j]._x, gridValues[i][j]._y, glyphSize) : Math.floor(Math.random()*5);
if(gridValues[i][j]._type > 1) {
gridValues[i][j]._static = false;
gridValues[i][j]._size = glyphSize/4;
}
else {
gridValues[i][j]._static = true;
gridValues[i][j]._size = glyphSize/2;
}
}
}
// setup
for (var i=0; i<numActiveAgents; i++) {
var agent = allAgents[i];
agent.setup(0, agent._type);
}
background(colorBack);
//shuffle the order of the actors
// compute neighbors
computeNeighbors(glyphSize);
}
function computeNeighbors(glyphSize) {
var mode = modeSelector.value();
var hexOffset = (mode == "hexgrid");
for (var i=0; i<numActiveAgents; i++) {
allAgents[i]._neighbors = []
}
var dist_thresh = 2.0;
if(hexOffset) {
dist_thresh = 1.4;
}
for (var i=0; i<numActiveAgents; i++) {
var agent = allAgents[i];
// agent.setup(0, agent._type);
for (var j=i+1; j<numActiveAgents; j++) {
var other = allAgents[j]
var d = dist(agent._x, agent._y, other._x, other._y) / glyphSize
if (d < dist_thresh) {
var o1 = {
'distance': d,
'agent': other,
'radius': other._size,
'x': other._x - agent._x,
'y': other._y - agent._y,
'pos': createVector(other._x - agent._x, other._y - agent._y)
}
agent._neighbors.push(o1)
var o2 = {
'distance': d,
'agent': agent,
'radius': agent._size,
'x': agent._x - other._x,
'y': agent._y - other._y,
'pos': createVector(agent._x - other._x, agent._y - other._y)
}
other._neighbors.push(o2)
}
}
}
}
function speedChangedEvent() {
var speed = parseInt(speedSelector.value(), 10);
frameRate(speed)
}
function sizeChangedEvent() {
var mode = sizeSelector.value();
if(mode == '32')
speedSelector.value('10');
else if(mode == '64')
speedSelector.value('60');
refreshGridData();
redraw();
}
function guideChangedEvent() {
show_oddball = guideCheckbox.checked();
redraw();
}
function modeChangedEvent() {
var mode = modeSelector.value();
if (is_playing) {
playButton.elt.textContent = "pause";
stepButton.attribute('disabled','');
// stopButton.removeAttribute('disabled');
}
else {
playButton.elt.textContent = "play";
stepButton.removeAttribute('disabled');
// stopButton.attribute('disabled','');
}
if (mode === "drive") {
// disable the button
// button.attribute('disabled','');
// enable the size selector
sizeSelector.removeAttribute('disabled');
}
else {
// enable the button
// button.removeAttribute('disabled');
// enable the size selector
// sizeSelector.removeAttribute('disabled');
// refresh data
// refreshGridData();
}
if (mode === "hexgrid") {
// refresh data
// refreshGridData();
}
redraw();
}
function gridTypeChanged() {
modeChangedEvent();
refreshGridData();
redraw();
}
function clearButtonPressedEvent() {
refreshGridData();
for(var i=0; i<numActiveAgents; i++) {
var agent = allAgents[i];
agent.setup(0, agent._type);
}
redraw();
}
function randomButtonPressedEvent() {
refreshGridData(true);
for(var i=0; i<numActiveAgents; i++) {
var agent = allAgents[i];
agent.setup(random(100), agent._type);
}
redraw();
}
function playButtonPressedEvent() {
if(is_playing) {
is_playing = false
noLoop();
}
else {
is_playing = true;
loop();
}
modeChangedEvent()
// refreshGridData();
redraw();
}
function stepButtonPressedEvent() {
is_playing = true;
// refreshGridData();
redraw();
is_playing = false;
}
function stopButtonPressedEvent() {
// refreshGridData();
redraw();
}
var colorBack = "rgb(50, 50, 50)"
function highlightGlyph(glyphSize) {
halfSize = glyphSize / 2.0;
stroke(0, 0, 255, 128);
noFill();
strokeWeight(4);
ellipse(halfSize, halfSize, glyphSize+4);
fill(0);
strokeWeight(1);
}
function drawGrid() {
var glyphSize = parseInt(sizeSelector.value(), 10);
//render transparent background for glow effect
if(pipes.checked())
background(0, 0, 20, 0.04);
else
background(colorBack);
//my way of iteration after screwing around here anyway
allAgents.slice(0, numActiveAgents).forEach(function(agent, i){
resetMatrix();
translate(agent._x, agent._y);
agent.draw(agent._size);
resetMatrix();
if (show_oddball) {
translate(agent._x, agent._y);
highlightGlyph(glyphSize)
}
})
}
function clamp(num, min, max) {
return num <= min ? min : num >= max ? max : num;
}
function stepGrid() {
var glyphSize = parseInt(sizeSelector.value(), 10);
var radius = glyphSize / 2;
var min_x = int(0);
var min_y = int(0);
var max_x = int(canvasWidth - radius) - 1;
var max_y = int(canvasHeight - radius) - 1;
var updatedAgents = new Array(numActiveAgents);
for (var i=0; i<numActiveAgents; i++) {
// make a shallow copy of the agent
agent = allAgents[i];
var clone = Object.assign({}, agent);
agent._new_me = clone;
var movement = clone.step(clone._neighbors, clone._size);
if(typeof movement !== 'undefined') {
haveSeenMovement = true;
var new_x = clone._x + movement.x;
var new_y = clone._y + movement.y;
clone._x = clamp(new_x, min_x, max_x);
clone._y = clamp(new_y, min_y, max_y);
}
updatedAgents[i] = clone;
}
for (var i=0; i<numActiveAgents; i++) {
allAgents[i] = updatedAgents[i];
}
if(haveSeenMovement) {
// copy new version of neighbors
computeNeighbors(glyphSize);
}
else {
for (var i=0; i<numActiveAgents; i++) {
agent = allAgents[i];
var old_neighbors = agent._neighbors;
for(var j=0; j<old_neighbors.length; j++) {
if ('_new_me' in old_neighbors[j].agent) {
agent._neighbors[j].agent = agent._neighbors[j].agent._new_me;
}
}
}
// weakly check optimization assertion (not a memory leak)
for (var i=0; i<numActiveAgents; i++) {
if ('_new_me' in allAgents[i]) {
print("you flubbed the _new_me setup");
}
}
}
}
function activateGrid(x, y) {
var glyphSize = parseInt(sizeSelector.value(), 10);
for (var i=0; i<numActiveAgents; i++) {
agent = allAgents[i];
if( (agent._x <= x) && (agent._x + glyphSize > x) &&
(agent._y <= y) && (agent._y + glyphSize > y) ) {
agent.activate();
}
}
}
function mouseClicked () {
activateGrid(mouseX, mouseY);
drawGrid();
}
function draw () {
var mode = modeSelector.value();
// first do all steps
if (is_playing) {
stepGrid();
}
// then do activate
activateGrid(mouseX, mouseY);
// the do all draws
drawGrid();
resetMatrix();
}
function keyTyped() {
if (key == '!') {
saveBlocksImages();
}
else if (key == '@') {
saveBlocksImages(true);
}
else if (key == ' ') {
playButtonPressedEvent();
}
else if (key == 's') {
var old_value = guideCheckbox.checked();
guideCheckbox.checked(!old_value);
guideChangedEvent();
}
else if (key == '1') {
sizeSelector.value('16');
sizeChangedEvent()
}
else if (key == '2') {
sizeSelector.value('32');
sizeChangedEvent()
}
else if (key == '3') {
sizeSelector.value('64');
sizeChangedEvent()
}
else if (key == '4') {
sizeSelector.value('128');
sizeChangedEvent()
}
else if (key == '5') {
sizeSelector.value('256');
sizeChangedEvent()
}
else if (key == 'd') {
modeSelector.value('drive');
modeChangedEvent()
}
else if (key == 'g') {
modeSelector.value('grid');
gridTypeChanged()
}
else if (key == 'r') {
modeSelector.value('random');
modeChangedEvent()
}
else if (key == 'h') {
modeSelector.value('hexgrid');
gridTypeChanged()
}
}
function keyPressed() {
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment