Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <canvas>
              
            
!

CSS

              
                body {
  margin: 0;
  padding: 0;
}

canvas {
  display: block;
}
              
            
!

JS

              
                // system settings
const NB_AGENTS = 2000
// sim settings
const SETTINGS = {
  stepSize: 4,
  sensorOffset: 20,
  sensorAngle: 0.8,
  deposit: 0.5,
  decay: 0.9,
  turnAngle: 0.4,
  randomAngle: 0.2,
  renderAgents: true
}

// weights for the blur pass
const weight = [
	1/16, 1/8, 1/16,
	 1/8, 1/4,  1/8,
	1/16, 1/8, 1/16,
];

// GUI setup
const GUI = new dat.GUI()
GUI.add(SETTINGS, "stepSize", 0, 10)
GUI.add(SETTINGS, "sensorOffset", 0, 40)
GUI.add(SETTINGS, "sensorAngle", 0, Math.PI)
GUI.add(SETTINGS, "turnAngle", 0, Math.PI)
GUI.add(SETTINGS, "randomAngle", 0, Math.PI)
GUI.add(SETTINGS, "deposit", 0, 1)
GUI.add(SETTINGS, "decay", 0.9, 1)
GUI.add(SETTINGS, "renderAgents")

// document setup
const cvs = document.querySelector("canvas")
const ctx = cvs.getContext("2d")
let W, H
cvs.width = W = window.innerWidth
cvs.height = H = window.innerHeight


// toroidal coordinates from any 2D coordinates
// not ANY coordinates, but faster and enough for this purpose
const toroidalCoords = (x, y) => {
  if (x < 0) x = W + x
  else if (x >= W) x-= W
  if (y < 0) y = H + y
  else if (y >= H) y-= H
  return { x, y }
}

// 2d toroidail coordinates to 1D coordinates, for the substrate lookup
const coords2dTo1d = (x, y) => (x + y * W)


// initialisation of the system
const agents = []
for (let i = 0; i < NB_AGENTS; i++) {
  agents.push({
    x: Math.random() * W,
    y: Math.random() * H,
    ang: Math.random() * 2 * Math.PI
  })
}
const substrate = new Float32Array(W*H)
const substrateImageArray = new Uint8ClampedArray(W*H*4)


// performs a blur pass on the substrate array [diffusion]
const diffusion = () => {
  let old = Float32Array.from(substrate)
		for (let y = 1; y < H-1; y++) {
			for (let x = 1; x < W-1; ++x) {
				const diffused_value = (
					old[coords2dTo1d(x-1, y-1)] * weight[0] +
					old[coords2dTo1d(x  , y-1)] * weight[1] +
					old[coords2dTo1d(x+1, y-1)] * weight[2] +
					old[coords2dTo1d(x-1, y  )] * weight[3] +
					old[coords2dTo1d(x  , y  )] * weight[4] +
					old[coords2dTo1d(x+1, y  )] * weight[5] +
					old[coords2dTo1d(x-1, y+1)] * weight[6] +
					old[coords2dTo1d(x  , y+1)] * weight[7] +
					old[coords2dTo1d(x+1, y+1)] * weight[8]
				)
				substrate[coords2dTo1d(x, y)] = diffused_value * SETTINGS.decay
			}
		}
}

const sensor = (agent, ang) => {
  const sx = agent.x + Math.cos(agent.ang + ang) * SETTINGS.sensorOffset
  const sy = agent.y + Math.sin(agent.ang + ang) * SETTINGS.sensorOffset
  const { x, y } = toroidalCoords(sx, sy)
  return substrate[coords2dTo1d(x|0, y|0)]
}

// update of the system
const update = () => {
  // diffusion / evaporation step
  diffusion()
  
  // agents update
  let c1d, SL, SF, SR
  for (const agent of agents) {
    // agent angle based on substrate sampling
    SL = sensor(agent, -SETTINGS.sensorAngle)
    SF = sensor(agent, 0)
    SR = sensor(agent, SETTINGS.sensorAngle)
    
    if (SF > SL && SF > SR) {
      // no change
    }
    else if (SL > SR) {
      agent.ang-= SETTINGS.turnAngle
    }
    else if (SR > SL) {
      agent.ang+= SETTINGS.turnAngle
    }
    else {
      agent.ang+= (Math.random()-0.5) * SETTINGS.randomAngle
    }
    
    // update agent position based on its heading
    let dir = {
      x: Math.cos(agent.ang),
      y: Math.sin(agent.ang)
    }
    const { x, y } = toroidalCoords(
      agent.x + dir.x * SETTINGS.stepSize, 
      agent.y + dir.y * SETTINGS.stepSize
    )
    agent.x = x
    agent.y = y
    
    // agent deposit substrate
    if ((x|0) !== 0 && (x|0) < W-1 && (y|0) !== 0 && (y|0) < H-1) {
      c1d = coords2dTo1d(x|0, y|0)
      substrate[c1d]+= SETTINGS.deposit
    }
  }
}


// draw the system
const draw = () => {
  ctx.fillStyle = "black"
  ctx.fillRect(0, 0, W, H)
  
  // draw the substrate
  let idx, brightness
  for (let x = 0; x < W; x++) {
    for (let y = 0; y < H; y++) {
      idx = coords2dTo1d(x, y)
      brightness = (substrate[idx] * 255) | 0
      idx*=4
      substrateImageArray[idx] = brightness
      substrateImageArray[idx+1] = brightness
      substrateImageArray[idx+2] = brightness
      substrateImageArray[idx+3] = 255
    }
  }
  let substrateImage = new ImageData(substrateImageArray, W, H)
  ctx.putImageData(substrateImage, 0, 0)
  
  
  // draw the agents
  if (SETTINGS.renderAgents) {
    ctx.fillStyle = "red"
    for (const agent of agents) {
      ctx.beginPath()
      ctx.arc(agent.x, agent.y, 1, 0, Math.PI*2)
      ctx.fill()
    }
  }
}

const loop = () => {
  requestAnimationFrame(loop)
  update()
  draw()
}

loop()
              
            
!
999px

Console