cacatoo
Version:
Building, exploring, and sharing spatially structured models
301 lines (254 loc) • 12.2 kB
HTML
<html>
<script src="../../dist/cacatoo.js"></script> <!-- Include cacatoo library (compiled with rollup) -->
<script src="../../lib/all.js"></script> <!-- Load other packages -->
<link rel="shortcut icon" type="image/jpg" href="../../patterns/cacatoo.png"/>
<link rel="stylesheet" href="../../style/cacatoo.css"> <!-- Set style sheet -->
<script>
/*-----------------------Start user-defined code ---------------------*/
let sim;
let num_ants = 100
var showvector = false
var showpher = true
var max_foraging_time = 800
var depletable = true
var food_depletion_rate = 2
/**
* function cacatoo() contains all the user-defined parts of a cacatoo-model.
Configuration, update rules, what is displayed or plotted, etc. It's all here.
*/
function cacatoo() {
let simconfig = {
title: "Foraging ants", // The name of your cacatoo-simulation
description: "Including mosh pits", // And a description if you wish
maxtime: 1000000, // How many time steps the model continues to run
// (note, the onscreen FPS may drop below 60 fps when using fast mode, although many more timesteps may be handled per second)
ncol: 200, // Number of columns (width of your grid)
nrow: 100, // Number of rows (height of your grid)
scale: 5, // Scale of the grid (nxn pixels per grid point)
sleep: 0,
seed: 3,
wrap: [false ,false],
wrapreflect: 1.0,
bgcolour:'#AAAAAA'
}
// FLOCKCONFIG EXAMPLE
let flockconfig = {
// Flock parameters
num_boids: 0, // Starting number of boids (flocking individuals)
shape: 'ant', // Shape of the boids drawn (options: bird, arrow, line, rect, dot, ant)
click: 'pull',
max_speed: 1, // Maximum velocity of boids
max_force: 1, // Maximum steering force applied to boids (separation/cohesion/alignment rules)
friction: 0.0,
// Mouse parameters
mouse_radius: 30, // Radius of boids captured by the mouse overlay
draw_mouse_radius: true, // Show a circle where the mouse is
size: 3, // Size of the boids (scales drawing and colision detection)
//qt_capacity: 5, // How many boids can be in one subspace of the quadtree before it is divided further
//qt_colour: 'black',
}
sim = new Simulation(simconfig) // Initialise the Cacatoo simulation
sim.makeGridmodel("grid") // Make a grid for the pheromones
sim.grid.colourGradient('food', 5, [170, 170, 170], [255, 255, 255])
sim.createDisplay_continuous({model:"grid", property:"food", label:"",
legend:false,maxval:5,num_colours:5})
sim.addFood = function(){
let x = sim.rng.genrand_int(25,sim.ncol-12)
let y = sim.rng.genrand_int(12,sim.nrow-12)
sim.initialSpot(sim.grid,"food",5,sim.rng.genrand_int(5,32),x,y)
}
sim.initialSpot(sim.grid,"food",5,30,sim.ncol-sim.ncol/4,sim.nrow/2)
sim.makeFlockmodel("flock", flockconfig) // Add a flockmodel, which contains invidiuals (boids) in continuous space
sim.createFlockDisplay("flock",{label:"Ants",addToDisplay:sim.canvases[0]}) // Alias for old 'createDisplay' function, makes distinction with new flocks clearer
// sim.flock.placeObstacle({x:99,y:0,w:2,h:25,fill:'#ffffff33',force:1})
// sim.flock.placeObstacle({x:99,y:30,w:2,h:200,fill:'#ffffff33',force:1})
sim.nest = {x:20,y:50,food:0}
sim.spawnAnt = function(){
let xpos = Math.max(3+sim.nest.x+sim.rng.randomGaus(0,3),0)
let ypos = Math.max(3+sim.nest.y+sim.rng.randomGaus(0,3),0)
let dir = sim.rng.random()*Math.PI
sim.flock.boids.push(boid = {position: {x:xpos,y:ypos},
velocity: {x:Math.sin(dir),y:Math.cos(dir)},
acceleration: {x:0,y:0},
fill: "black",
food: false,
foraging_time:sim.rng.genrand_int(1,50),
size:2.5 })
}
sim.setup = function(){
for(let x=0;x<sim.ncol;x++) for(let y=0;y<sim.nrow;y++){
sim.grid.grid[x][y].homing_velocity = {x:0.0,y:0.0}
sim.grid.grid[x][y].food_velocity = {x:0.0,y:0.0}
}
sim.flock.boids = []
sim.spawnAnt()
// sim.flock.boids[0].food ="green"
}
sim.setup()
/*
2. DEFINING THE RULES. Below, the user defines the nextState function. This function will be applied for each grid point when we will update the grid later.
*/
sim.grid.nextState = function(x, y) {
let amount_homing = sim.flock.lengthVector(this.grid[x][y].homing_velocity)
let amount_food = sim.flock.lengthVector(this.grid[x][y].food_velocity)
this.grid[x][y].homing_velocity = sim.flock.scaleVector(this.grid[x][y].homing_velocity, 0.99)
this.grid[x][y].food_velocity = sim.flock.scaleVector(this.grid[x][y].food_velocity, 0.99)
if(amount_homing>amount_food) this.grid[x][y].homing_pheromone = amount_homing*2
else if(amount_food > amount_homing) this.grid[x][y].homing_pheromone = -amount_food*2
else this.grid[x][y].homing_pheromone = -1
for(let i=1; i<=4; i++) { // diffusion
let neigh = this.getNeighbour(this,x,y,i)
if(neigh==undefined) continue
let diffusion_const = 0.005
neigh.homing_velocity.x += diffusion_const*this.grid[x][y].homing_velocity.x
this.grid[x][y].homing_velocity.x -= diffusion_const*this.grid[x][y].homing_velocity.x
neigh.homing_velocity.y += diffusion_const*this.grid[x][y].homing_velocity.y
this.grid[x][y].homing_velocity.y -= diffusion_const*this.grid[x][y].homing_velocity.y
neigh.food_velocity.x += diffusion_const*this.grid[x][y].food_velocity.x
this.grid[x][y].food_velocity.x -= diffusion_const*this.grid[x][y].food_velocity.x
neigh.food_velocity.y += diffusion_const*this.grid[x][y].food_velocity.y
this.grid[x][y].food_velocity.y -= diffusion_const*this.grid[x][y].food_velocity.y
}
}
sim.grid.update = function() {
this.asynchronous()
}
sim.inbounds = function(x,y){
return (x > 3 && y> 1 && x < sim.ncol-3 && y < sim.nrow-1)
}
sim.flock.update = function(){
if(sim.time%5 == 0 && sim.flock.boids.length < num_ants) sim.spawnAnt()
for(let boid of this.boids) {
let x = Math.floor(boid.position.x)
let y = Math.floor(boid.position.y)
if(!boid.food && sim.grid.grid[x][y].food > 0){
if(depletable)sim.grid.grid[x][y].food -= food_depletion_rate
if(sim.grid.grid[x][y].food <= 0) sim.grid.grid[x][y].food = undefined
boid.food = "white"
boid.velocity.x *= -1
boid.velocity.y *= -1
}
else if(boid.food =="white"){
let dx = boid.position.x - sim.nest.x
let dy = boid.position.y - sim.nest.y
let dist = Math.sqrt(dx*dx+dy*dy)
if(dist<5) {
boid.food = undefined
sim.nest.food++
boid.foraging_time=0
boid.velocity.x *=-1
boid.velocity.y *=-1
}
}
// Pheromone tracking
let nest_pherx = sim.grid.grid[x][y].homing_velocity.x
let nest_phery = sim.grid.grid[x][y].homing_velocity.y
let nest_pheromone_amount = this.lengthVector(sim.grid.grid[x][y].homing_velocity)
let food_pherx = sim.grid.grid[x][y].food_velocity.x
let food_phery = sim.grid.grid[x][y].food_velocity.y
let food_pheromone_amount = this.lengthVector(sim.grid.grid[x][y].food_velocity)
// Randomly rotate a bit
let angle = sim.rng.randomGaus(0,10)
boid.velocity = this.rotateVector(boid.velocity,angle)
// Follow home
if(boid.food =="white"){
let pheromone_angle = Math.atan2(nest_phery,nest_pherx)
boid.acceleration.x += Math.cos(pheromone_angle) * nest_pheromone_amount * -1
boid.acceleration.y += Math.sin(pheromone_angle) * nest_pheromone_amount * -1
}
else{ // Follow food
let pheromone_angle = Math.atan2(food_phery,food_pherx)
boid.acceleration.x += Math.cos(pheromone_angle) * food_pheromone_amount * -1
boid.acceleration.y += Math.sin(pheromone_angle) * food_pheromone_amount * -1
}
if(sim.inbounds(x,y)){
if(!boid.food){
sim.grid.grid[x][y].homing_velocity.x += boid.velocity.x*10
sim.grid.grid[x][y].homing_velocity.y += boid.velocity.y*10
}
else{
sim.grid.grid[x][y].food_velocity.x += boid.velocity.x*10
sim.grid.grid[x][y].food_velocity.y += boid.velocity.y*10
}
}
boid.foraging_time++
if(boid.foraging_time>max_foraging_time){
let xpos = Math.max(3+sim.nest.x+sim.rng.randomGaus(0,3),0)
let ypos = Math.max(3+sim.nest.y+sim.rng.randomGaus(0,3),0)
let dir = sim.rng.random()*Math.PI*0.5
boid.position = {x:xpos,y:ypos}
boid.velocity = {x:Math.sin(dir),y:Math.cos(dir)}
boid.acceleration = {x:0,y:0}
boid.food = false
boid.foraging_time = 0
}
}
sim.canvases[0].overlay = function(){
this.ctx.fillStyle ="#555555"
this.ctx.beginPath()
this.ctx.arc(sim.nest.x*sim.scale,sim.nest.y*sim.scale,20,0,Math.PI*2)
this.ctx.fill()
this.ctx.fillStyle="white"
this.ctx.textAlign = "center";
this.ctx.textBaseline = 'middle';
this.ctx.font = "16px sans";
this.ctx.fillText(sim.nest.food,sim.nest.x*sim.scale,sim.nest.y*sim.scale)
this.ctx.closePath()
}
sim.canvases[0].underlay = function(){
if(showpher){
this.ctx.lineWidth = 1
let scale_vector = 1
for(let x=0;x<sim.ncol;x++){
for(let y=0;y<sim.nrow;y++){
red = Math.min(sim.flock.lengthVector(sim.grid.grid[x][y].homing_velocity)/30,0.5)
this.ctx.fillStyle = `rgba(255,0,0,${red})`;
this.ctx.fillRect(x*sim.scale,y*sim.scale,sim.scale,sim.scale)
blue = Math.min(sim.flock.lengthVector(sim.grid.grid[x][y].food_velocity)/30,0.5)
this.ctx.fillStyle = `rgba(0,0,255,${blue})`;
this.ctx.fillRect(x*sim.scale,y*sim.scale,sim.scale,sim.scale)
}
}
}
if(showvector){
this.ctx.lineWidth = 1
let scale_vector = 1
for(let x=0;x<sim.ncol;x++){
for(let y=0;y<sim.nrow;y++){
this.ctx.strokeStyle = "red"
let scaled = sim.flock.limitVector(sim.grid.grid[x][y].homing_velocity,10)
this.ctx.beginPath()
this.ctx.moveTo(this.scale*0.5+x*this.scale,this.scale*0.5+y*this.scale)
this.ctx.lineTo(this.scale*0.5+x*this.scale+scaled.x*scale_vector,this.scale*0.5+y*this.scale+scaled.y*scale_vector)
this.ctx.stroke()
this.ctx.closePath()
scaled = sim.flock.limitVector(sim.grid.grid[x][y].food_velocity,10)
this.ctx.strokeStyle = "blue"
this.ctx.beginPath()
this.ctx.moveTo(this.scale*0.5+x*this.scale,this.scale*0.5+y*this.scale)
this.ctx.lineTo(this.scale*0.5+x*this.scale+scaled.x*scale_vector,this.scale*0.5+y*this.scale+scaled.y*scale_vector)
this.ctx.stroke()
this.ctx.closePath()
}
}
}
}
}
sim.start()
sim.addButton("Play/pause", function () { sim.toggle_play() })
sim.addButton("Add food", function () { sim.addFood() })
sim.addToggle("showvector","Show pheromone field")
sim.addToggle("showpher","Show pheromone concentration")
sim.addToggle("depletable","Food is depletable")
}
</script>
<body onload="cacatoo()">
<div class="header" id="header">
<h2>Cacatoo </h2>
</div>
<div class="content" id="canvas_holder"></div>
<div class="content" id="form_holder"></div>
<div class="content" id="graph_holder"> </div>
<div class="footer" id="footer"></div>
</body>
</html>