Cleanup; make ants bounce off the world bounds

This commit is contained in:
vHawk 2022-06-26 19:23:23 +03:00
parent fb248c33ee
commit fb772db717
21 changed files with 265 additions and 197 deletions

View file

@ -1,2 +1,26 @@
# ants-simulation
# Ants simulation
A simple ant colony GPU-accelerated simulation made with Three.js.
**[Live demo](https://vhawk.github.io/ants-simulation/)**
## Rules
Ants can emit two types of pheromones: to-home pheromone and to-food pheromone. To-home pheromones are emitted by those ants searching for food and to-food pheromones are emitted by those carrying food. Ants searching for food are attracted to to-food pheromones, while ants searching for home are attracted to to-home pheromones.
If an ant searching for food detects a food cell nearby, then it picks it up, and drops it after reaching home.
If an ant senses the desirable pheromones nearby, then it turns to the direction of these pheromones, and if no pheromones are detected nearby or the ant can't decide at which direction pheromones are stronger, then is moves randomly.
It is important to prevent ants from following pheromone trails left by those ants who wandered too far from home or a food source. Each individual ant has an inventory for storing pheromones. Each time an ant leaves a pheromone marker anywhere on the map a small portion of the stored pheromones is used. And each time it picks up food or reaches home its inventory gets fully refilled.
Pheromone trails left by ants evaporate and diffuse over time.
## Compute pipeline overview
TODO
## References
- https://softologyblog.wordpress.com/2020/03/21/ant-colony-simulations/
- https://github.com/johnBuffer/AntSimulator

View file

@ -4,6 +4,8 @@ import Renderer from "./Renderer";
import WorldComputeScene from "./scenes/WorldComputeScene";
import AntsDiscretizeScene from "./scenes/AntsDiscretizeScene";
import WorldBlurScene from "./scenes/WorldBlurScene";
import Config from "./Config";
import GUI from "./GUI";
export interface SceneCollection {
ants: AntsComputeScene;
@ -16,7 +18,10 @@ export interface SceneCollection {
export default new class App {
private renderer: Renderer = new Renderer(<HTMLCanvasElement>document.getElementById('canvas'));
private scenes: SceneCollection;
private loop = (deltaTime: number): void => this.update(deltaTime);
private gui: GUI = new GUI();
private renderLoop = (deltaTime: number): void => this.render(deltaTime);
private simInterval: NodeJS.Timer;
private simulationStepsPerSecond: number = 0;
constructor() {
this.initScenes();
@ -25,7 +30,24 @@ export default new class App {
this.resize();
this.update(0);
this.renderLoop(0);
this.simulationStepsPerSecond = Config.simulationStepsPerSecond;
this.updateSimulationInterval();
}
private updateSimulationInterval() {
clearInterval(this.simInterval);
this.simInterval = setInterval(() => {
if (Config.simulationStepsPerSecond !== this.simulationStepsPerSecond) {
this.simulationStepsPerSecond = Config.simulationStepsPerSecond;
this.updateSimulationInterval();
return;
}
this.simulationStep();
}, 1000 / this.simulationStepsPerSecond);
}
private initScenes() {
@ -49,14 +71,17 @@ export default new class App {
}
}
private update(deltaTime: number) {
requestAnimationFrame(this.loop);
for (let i = 0; i < 3; i++) {
for (const scene of Object.values(this.scenes)) {
scene.update(deltaTime);
}
this.renderer.renderScenes(this.scenes);
private simulationStep() {
for (const scene of Object.values(this.scenes)) {
scene.update(0);
}
this.renderer.renderSimulation(this.scenes);
}
private render(deltaTime: number) {
requestAnimationFrame(this.renderLoop);
this.renderer.renderToScreen(this.scenes);
}
}

12
src/Config.ts Normal file
View file

@ -0,0 +1,12 @@
export default {
worldSize: 1024,
antsCount: 64 ** 2,
simulationStepsPerSecond: 60,
scentThreshold: 0.01,
scentFadeOutFactor: 0.998,
scentBlurRadius: 0.2,
scentMaxStorage: 1e6,
scentPerMarker: 1000,
antSpeed: 1,
antRotationAngle: Math.PI / 50
};

21
src/GUI.ts Normal file
View file

@ -0,0 +1,21 @@
import * as dat from 'dat.gui';
import Config from "./Config";
export default class GUI {
private gui: dat.GUI = new dat.GUI({
width: 400
});
constructor() {
const simFolder = this.gui.addFolder('Simulation');
simFolder.add(Config, 'worldSize', 256, 8096);
simFolder.add(Config, 'antsCount', 1, 1e6);
simFolder.add(Config, 'simulationStepsPerSecond', 1, 300);
const controlsFolder = this.gui.addFolder('Controls');
simFolder.open();
controlsFolder.open();
}
}

View file

@ -1,14 +1,9 @@
import * as THREE from 'three';
import {SceneCollection} from "./App";
const Config = {
worldSize: 1024,
antsCount: 64 ** 2
};
import Config from "./Config";
interface Resources {
worldRenderTarget0: THREE.WebGLRenderTarget;
worldRenderTarget1: THREE.WebGLRenderTarget;
worldRenderTarget: THREE.WebGLRenderTarget;
worldBlurredRenderTarget: THREE.WebGLRenderTarget;
antsDataRenderTarget0: THREE.WebGLRenderTarget;
antsDataRenderTarget1: THREE.WebGLRenderTarget;
@ -29,14 +24,7 @@ export default class Renderer {
const antTextureSize = Math.round(Math.sqrt(Config.antsCount));
this.resources = {
worldRenderTarget0: new THREE.WebGLRenderTarget(Config.worldSize, Config.worldSize, {
format: THREE.RGBAFormat,
type: THREE.FloatType,
depthBuffer: false,
magFilter: THREE.LinearFilter,
minFilter: THREE.LinearFilter,
}),
worldRenderTarget1: new THREE.WebGLRenderTarget(Config.worldSize, Config.worldSize, {
worldRenderTarget: new THREE.WebGLRenderTarget(Config.worldSize, Config.worldSize, {
format: THREE.RGBAFormat,
type: THREE.FloatType,
depthBuffer: false,
@ -74,15 +62,14 @@ export default class Renderer {
};
}
public renderScenes(scenes: SceneCollection) {
public renderSimulation(scenes: SceneCollection) {
const [antsComputeSource, antsComputeTarget] = scenes.ants.getRenderTargets();
const [worldComputeSource, worldComputeTarget] = scenes.world.getRenderTargets();
this.renderer.setViewport(0, 0, scenes.ants.renderWidth, scenes.ants.renderHeight);
this.renderer.setRenderTarget(antsComputeTarget);
scenes.ants.material.uniforms.tLastState.value = antsComputeSource.texture;
scenes.ants.material.uniforms.tWorld.value = worldComputeSource.texture;
scenes.ants.material.uniforms.tWorld.value = scenes.worldBlur.getRenderTarget().texture;
this.renderer.render(scenes.ants, scenes.ants.camera);
this.renderer.setViewport(0, 0, scenes.discretize.renderWidth, scenes.discretize.renderHeight);
@ -94,7 +81,7 @@ export default class Renderer {
this.renderer.setViewport(0, 0, scenes.world.renderWidth, scenes.world.renderHeight);
this.renderer.setRenderTarget(worldComputeTarget);
this.renderer.setRenderTarget(scenes.world.getRenderTarget());
scenes.world.material.uniforms.tLastState.value = scenes.worldBlur.getRenderTarget().texture;
scenes.world.material.uniforms.tDiscreteAnts.value = scenes.discretize.getRenderTarget().texture;
scenes.world.material.uniforms.pointerData.value = scenes.screen.getPointerData();
@ -103,14 +90,16 @@ export default class Renderer {
this.renderer.setViewport(0, 0, scenes.worldBlur.renderWidth, scenes.worldBlur.renderHeight);
this.renderer.setRenderTarget(scenes.worldBlur.getRenderTarget());
scenes.worldBlur.material.uniforms.tWorld.value = worldComputeTarget.texture;
scenes.worldBlur.material.uniforms.tWorld.value = scenes.world.getRenderTarget().texture;
this.renderer.render(scenes.worldBlur, scenes.worldBlur.camera);
this.renderer.setViewport(0, 0, scenes.screen.renderWidth, scenes.screen.renderHeight);
this.renderer.setRenderTarget(null);
scenes.screen.material.uniforms.tData.value = antsComputeTarget.texture;
scenes.screen.groundMaterial.uniforms.map.value = scenes.worldBlur.getRenderTarget().texture;
}
public renderToScreen(scenes: SceneCollection) {
this.renderer.setViewport(0, 0, scenes.screen.renderWidth, scenes.screen.renderHeight);
this.renderer.setRenderTarget(null);
this.renderer.render(scenes.screen, scenes.screen.camera);
}
@ -118,4 +107,25 @@ export default class Renderer {
this.canvas.width = width;
this.canvas.height = height;
}
public getCommonMaterialDefines(): Record<string, string> {
return {
WORLD_SIZE: Renderer.convertNumberToFloatString(Config.worldSize),
SCENT_THRESHOLD: Renderer.convertNumberToFloatString(Config.scentThreshold),
SCENT_FADE_OUT_FACTOR: Renderer.convertNumberToFloatString(Config.scentFadeOutFactor),
SCENT_BLUR_RADIUS: Renderer.convertNumberToFloatString(Config.scentBlurRadius),
SCENT_MAX_STORAGE: Renderer.convertNumberToFloatString(Config.scentMaxStorage),
SCENT_PER_MARKER: Renderer.convertNumberToFloatString(Config.scentPerMarker),
ANT_SPEED: Renderer.convertNumberToFloatString(Config.antSpeed),
ANT_ROTATION_ANGLE: Renderer.convertNumberToFloatString(Config.antRotationAngle)
};
}
public destroy() {
}
static convertNumberToFloatString(n: number): string {
return n.toFixed(8);
}
}

View file

@ -1,10 +1,10 @@
import * as THREE from 'three';
import {WebGLRenderTarget} from 'three';
import Renderer from "../Renderer";
import AbstractScene from "./AbstractScene";
import FullScreenTriangleGeometry from "../utils/FullScreenTriangleGeometry";
import fragmentShader from '../shaders/antsCompute.frag';
import vertexShader from '../shaders/antsCompute.vert';
import {WebGLRenderTarget} from "three";
export default class AntsComputeScene extends AbstractScene {
public camera: THREE.OrthographicCamera = new THREE.OrthographicCamera();
@ -19,10 +19,12 @@ export default class AntsComputeScene extends AbstractScene {
uniforms: {
uTime: {value: 0},
tLastState: {value: this.renderer.resources.antsDataRenderTarget0.texture},
tWorld: {value: this.renderer.resources.worldRenderTarget0.texture},
tWorld: {value: this.renderer.resources.worldRenderTarget.texture},
},
vertexShader,
fragmentShader
fragmentShader,
defines: this.renderer.getCommonMaterialDefines(),
glslVersion: THREE.GLSL3
});
const mesh = new THREE.Mesh(geometry, material);
this.add(mesh);

View file

@ -8,7 +8,6 @@ import vertexShader from '../shaders/antsDiscretize.vert';
export default class AntsDiscretizeScene extends AbstractScene {
public readonly camera: THREE.OrthographicCamera = new THREE.OrthographicCamera();
public readonly material: THREE.RawShaderMaterial;
private readonly renderTarget: WebGLRenderTarget;
constructor(renderer: Renderer) {
super(renderer);
@ -20,7 +19,9 @@ export default class AntsDiscretizeScene extends AbstractScene {
tDataLast: {value: null},
},
vertexShader,
fragmentShader
fragmentShader,
defines: this.renderer.getCommonMaterialDefines(),
glslVersion: THREE.GLSL3
});
const mesh = new THREE.InstancedMesh(
geometry,
@ -31,14 +32,12 @@ export default class AntsDiscretizeScene extends AbstractScene {
this.material = material;
this.renderTarget = this.renderer.resources.antsDiscreteRenderTarget;
this.renderWidth = this.renderer.resources.worldRenderTarget0.width;
this.renderHeight = this.renderer.resources.worldRenderTarget0.height;
this.renderWidth = this.renderer.resources.worldRenderTarget.width;
this.renderHeight = this.renderer.resources.worldRenderTarget.height;
}
public getRenderTarget(): WebGLRenderTarget {
return this.renderTarget;
return this.renderer.resources.antsDiscreteRenderTarget;
}
public resize(width: number, height: number) {

View file

@ -26,10 +26,12 @@ export default class ScreenScene extends AbstractScene {
new THREE.PlaneBufferGeometry(10, 10),
new THREE.ShaderMaterial({
uniforms: {
map: {value: this.renderer.resources.worldRenderTarget0.texture},
map: {value: this.renderer.resources.worldRenderTarget.texture},
},
vertexShader: vertexShaderGround,
fragmentShader: fragmentShaderGround
fragmentShader: fragmentShaderGround,
defines: this.renderer.getCommonMaterialDefines(),
glslVersion: THREE.GLSL3
})
);

View file

@ -9,7 +9,6 @@ import {WebGLRenderTarget} from "three";
export default class WorldBlurScene extends AbstractScene {
public readonly camera: THREE.OrthographicCamera = new THREE.OrthographicCamera();
public readonly material: THREE.RawShaderMaterial;
private readonly renderTarget: WebGLRenderTarget;
constructor(renderer: Renderer) {
super(renderer);
@ -17,23 +16,24 @@ export default class WorldBlurScene extends AbstractScene {
const geometry = new FullScreenTriangleGeometry();
const material = new THREE.RawShaderMaterial({
uniforms: {
tWorld: {value: this.renderer.resources.worldRenderTarget0.texture},
tWorld: {value: this.renderer.resources.worldRenderTarget.texture},
},
vertexShader,
fragmentShader
fragmentShader,
defines: this.renderer.getCommonMaterialDefines(),
glslVersion: THREE.GLSL3
});
const mesh = new THREE.Mesh(geometry, material);
this.add(mesh);
this.material = material;
this.renderTarget = this.renderer.resources.worldBlurredRenderTarget;
this.renderWidth = this.renderer.resources.worldRenderTarget0.width;
this.renderHeight = this.renderer.resources.worldRenderTarget0.height;
this.renderWidth = this.renderer.resources.worldRenderTarget.width;
this.renderHeight = this.renderer.resources.worldRenderTarget.height;
}
public getRenderTarget(): WebGLRenderTarget {
return this.renderTarget;
return this.renderer.resources.worldBlurredRenderTarget;
}
public resize(width: number, height: number) {

View file

@ -9,7 +9,6 @@ import {WebGLRenderTarget} from "three";
export default class WorldComputeScene extends AbstractScene {
public readonly camera: THREE.OrthographicCamera = new THREE.OrthographicCamera();
public readonly material: THREE.RawShaderMaterial;
private readonly renderTargets: [WebGLRenderTarget, WebGLRenderTarget];
constructor(renderer: Renderer) {
super(renderer);
@ -17,31 +16,26 @@ export default class WorldComputeScene extends AbstractScene {
const geometry = new FullScreenTriangleGeometry();
const material = new THREE.RawShaderMaterial({
uniforms: {
tLastState: {value: this.renderer.resources.worldRenderTarget0.texture},
tLastState: {value: this.renderer.resources.worldRenderTarget.texture},
tDiscreteAnts: {value: this.renderer.resources.antsDiscreteRenderTarget.texture},
pointerData: {value: new THREE.Vector4()},
},
vertexShader,
fragmentShader
fragmentShader,
defines: this.renderer.getCommonMaterialDefines(),
glslVersion: THREE.GLSL3
});
const mesh = new THREE.Mesh(geometry, material);
this.add(mesh);
this.material = material;
this.renderTargets = [
this.renderer.resources.worldRenderTarget0,
this.renderer.resources.worldRenderTarget1
];
this.renderWidth = this.renderer.resources.worldRenderTarget0.width;
this.renderHeight = this.renderer.resources.worldRenderTarget0.height;
this.renderWidth = this.renderer.resources.worldRenderTarget.width;
this.renderHeight = this.renderer.resources.worldRenderTarget.height;
}
public getRenderTargets(): [WebGLRenderTarget, WebGLRenderTarget] {
this.renderTargets.reverse();
return this.renderTargets;
public getRenderTarget(): WebGLRenderTarget {
return this.renderer.resources.worldRenderTarget;
}
public resize(width: number, height: number) {

View file

@ -1,21 +1,15 @@
precision highp float;
precision highp int;
varying vec2 vUv;
varying float vIsCarryingFood;
in vec2 vUv;
in float vIsCarryingFood;
uniform sampler2D tAnt;
uniform sampler2D tFood;
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main() {
vec4 antColor = texture2D(tAnt, vUv);
vec4 foodColor = texture2D(tFood, vUv);
vec4 antColor = texture(tAnt, vUv);
vec4 foodColor = texture(tFood, vUv);
gl_FragColor = mix(antColor, foodColor, foodColor.a * vIsCarryingFood);
pc_fragColor = mix(antColor, foodColor, foodColor.a * vIsCarryingFood);
}

View file

@ -24,7 +24,7 @@ void main() {
float sampleY = floor(id / dataTextureSize);
float sampleX = id - sampleY * dataTextureSize;
vec4 dataSample = texture2D(tData, vec2(sampleX, sampleY) / dataTextureSize);
vec4 dataSample = texture(tData, vec2(sampleX, sampleY) / dataTextureSize);
vec2 offset = dataSample.xy * 10.;
vec2 rotatedPosition = rotate(position.xy, -dataSample.z + PI * 0.5);

View file

@ -1,36 +1,38 @@
#version 300 es
precision highp float;
precision highp int;
#define PI 3.1415926535897932384626433832795
#define MAX_STORAGE 1000000.
in vec2 vUv;
out highp vec4 FragColor;
out vec4 FragColor;
uniform float uTime;
uniform sampler2D tLastState;
uniform sampler2D tWorld;
const float PHI = 1.61803398874989484820459;
const float speed = 1.;
const float sampleDistance = 20.;
const float worldSize = 1024.;
const float cellSize = 1. / worldSize;
const float cellSize = 1. / WORLD_SIZE;
float goldNoise(in vec2 xy, in float seed) {
return fract(tan(distance(xy*PHI, xy)*seed)*xy.x);
float rand(vec2 co) {
float a = 12.9898;
float b = 78.233;
float c = 43758.5453;
float dt = dot(co.xy ,vec2(a,b));
float sn = mod(dt, 3.14);
return fract(sin(sn) * c);
}
vec2 roundUvToCellCenter(vec2 uv) {
return floor(uv * WORLD_SIZE) / WORLD_SIZE;
}
bool tryGetFood(vec2 pos) {
return texture(tWorld, pos).x == 1.;
return texture(tWorld, roundUvToCellCenter(pos)).x == 1.;
}
bool tryDropFood(vec2 pos) {
return texture(tWorld, pos).y == 1.;
return texture(tWorld, roundUvToCellCenter(pos)).y == 1.;
}
float smell(vec2 pos, float isCarrying) {
@ -45,24 +47,21 @@ float smell(vec2 pos, float isCarrying) {
vec2 applyOffsetToPos(vec2 pos, vec2 offsetDirectaion) {
return vec2(
fract(pos.x + offsetDirectaion.x * cellSize),
fract(pos.y + offsetDirectaion.y * cellSize)
clamp(pos.x + offsetDirectaion.x * cellSize, 0., 1.),
clamp(pos.y + offsetDirectaion.y * cellSize, 0., 1.)
);
}
const float rotAngle = PI / 50.;
float getMaxScentStorage(vec2 antDataUv) {
float factor = 0.8 + rand(antDataUv * 100.) * 0.2;
float constrainAngle(float x){
x = mod(x,360.);
if (x < 0.)
x += 360.;
return x;
return SCENT_MAX_STORAGE * factor;
}
void main() {
vec4 lastState = texture(tLastState, vUv);
float rand = goldNoise(vUv * 1000., fract(uTime / 1000.));
float noise = rand(vUv * 1000. + fract(uTime / 1000.));
vec2 pos = lastState.xy;
float angle = lastState.z;
@ -73,128 +72,119 @@ void main() {
if (pos == vec2(0)) { // init new ant
pos = vec2(0.5);
angle = goldNoise(vUv * 10000., 1.) * 2. * PI;
angle = rand(vUv * 10000.) * 2. * PI;
isCarrying = 0.;
storage = MAX_STORAGE;
storage = 0.;
}
if (tryGetFood(pos) && isCarrying == 0.) {
isCarrying = 1.;
angle += PI;
storage = getMaxScentStorage(vUv);
}
if (tryDropFood(pos)) {
storage = getMaxScentStorage(vUv);
if (isCarrying == 1.) {
isCarrying = 0.;
angle += PI;
}
storage = MAX_STORAGE;
}
if (isCarrying == 0.) {
if (rand < 0.33) {
if (noise < 0.33) {
vec2 offset = vec2(cos(angle), sin(angle)) * sampleDistance;
vec2 point = applyOffsetToPos(pos, offset);
if (tryGetFood(point)) {
isCarrying = 1.;
storage = MAX_STORAGE;
pos = point;
movementProcessed = true;
}
} else if (rand < 0.66) {
vec2 offset = vec2(cos(angle - rotAngle), sin(angle - rotAngle)) * sampleDistance;
} else if (noise < 0.66) {
float newAngle = angle - ANT_ROTATION_ANGLE;
vec2 offset = vec2(cos(newAngle), sin(newAngle)) * sampleDistance;
vec2 point = applyOffsetToPos(pos, offset);
if (tryGetFood(point)) {
isCarrying = 1.;
storage = MAX_STORAGE;
pos = point;
movementProcessed = true;
angle -= rotAngle;
angle = newAngle;
}
} else {
vec2 offset = vec2(cos(angle + rotAngle), sin(angle + rotAngle)) * sampleDistance;
float newAngle = angle + ANT_ROTATION_ANGLE;
vec2 offset = vec2(cos(newAngle), sin(newAngle)) * sampleDistance;
vec2 point = applyOffsetToPos(pos, offset);
if (tryGetFood(point)) {
isCarrying = 1.;
storage = MAX_STORAGE;
pos = point;
movementProcessed = true;
angle += rotAngle;
angle = newAngle;
}
}
} else if (isCarrying == 1.) {
if (rand < 0.33) {
vec2 offset = vec2(cos(angle), sin(angle)) * 10.;
if (noise < 0.33) {
vec2 offset = vec2(cos(angle), sin(angle)) * sampleDistance;
vec2 point = applyOffsetToPos(pos, offset);
if (tryDropFood(point)) {
isCarrying = 0.;
storage = MAX_STORAGE;
pos = point;
movementProcessed = true;
}
} else if (rand < 0.66) {
vec2 offset = vec2(cos(angle - rotAngle), sin(angle - rotAngle)) * sampleDistance;
} else if (noise < 0.66) {
float newAngle = angle - ANT_ROTATION_ANGLE;
vec2 offset = vec2(cos(newAngle), sin(newAngle)) * sampleDistance;
vec2 point = applyOffsetToPos(pos, offset);
if (tryDropFood(point)) {
isCarrying = 0.;
storage = MAX_STORAGE;
pos = point;
movementProcessed = true;
angle -= rotAngle;
angle = newAngle;
}
} else {
vec2 offset = vec2(cos(angle + rotAngle), sin(angle + rotAngle)) * sampleDistance;
float newAngle = angle + ANT_ROTATION_ANGLE;
vec2 offset = vec2(cos(newAngle), sin(newAngle)) * sampleDistance;
vec2 point = applyOffsetToPos(pos, offset);
if (tryDropFood(point)) {
isCarrying = 0.;
storage = MAX_STORAGE;
pos = point;
movementProcessed = true;
angle += rotAngle;
angle = newAngle;
}
}
}
if (!movementProcessed) {
float noise2 = goldNoise(vUv * 10000., uTime / 1000. + 0.2);
float noise2 = rand(vUv * 1000. + fract(uTime / 1000.) + 0.2);
float sampleAhead = smell(applyOffsetToPos(pos, vec2(cos(angle), sin(angle)) * sampleDistance), isCarrying);
float sampleLeft = smell(applyOffsetToPos(pos, vec2(cos(angle - rotAngle), sin(angle - rotAngle)) * sampleDistance), isCarrying);
float sampleRight = smell(applyOffsetToPos(pos, vec2(cos(angle + rotAngle), sin(angle + rotAngle)) * sampleDistance), isCarrying);
float sampleLeft = smell(applyOffsetToPos(pos, vec2(cos(angle - ANT_ROTATION_ANGLE), sin(angle - ANT_ROTATION_ANGLE)) * sampleDistance), isCarrying);
float sampleRight = smell(applyOffsetToPos(pos, vec2(cos(angle + ANT_ROTATION_ANGLE), sin(angle + ANT_ROTATION_ANGLE)) * sampleDistance), isCarrying);
if (sampleAhead > sampleLeft && sampleAhead > sampleRight) {
// don't change direction
} else if (sampleLeft > sampleAhead && sampleLeft > sampleRight) {
angle -= rotAngle; // steer left
angle -= ANT_ROTATION_ANGLE; // steer left
} else if (sampleRight > sampleAhead && sampleRight > sampleLeft) {
angle += rotAngle; // steer right
} else if (rand < 0.33) {
angle += rotAngle; // no smell detected, do random movement
} else if (rand < 0.66) {
angle -= rotAngle;
angle += ANT_ROTATION_ANGLE; // steer right
} else if (noise < 0.33) {
angle += ANT_ROTATION_ANGLE; // no smell detected, do random movement
} else if (noise < 0.66) {
angle -= ANT_ROTATION_ANGLE;
}
if (noise2 > 0.5) {
angle += PI / 30.;
angle += ANT_ROTATION_ANGLE * 2.;
} else {
angle -= PI / 30.;
angle -= ANT_ROTATION_ANGLE * 2.;
}
vec2 offset = vec2(cos(angle), sin(angle));
pos = applyOffsetToPos(pos, offset);
} else {
angle += PI;
}
vec2 offset = vec2(cos(angle), sin(angle));
pos = applyOffsetToPos(pos, offset);
if (fract(pos.x) == 0. || fract(pos.y) == 0.) {
//angle += PI / 2.;
angle += PI * (noise - 0.5);
}
FragColor = vec4(
pos.x,
pos.y,
angle,
float((uint(max(storage - 1000., 0.)) << 1) + uint(isCarrying))
float((uint(max(storage - SCENT_PER_MARKER, 0.)) << 1) + uint(isCarrying))
);
}

View file

@ -1,5 +1,3 @@
#version 300 es
precision highp float;
precision highp int;

View file

@ -1,5 +1,3 @@
#version 300 es
precision highp float;
precision highp int;
@ -8,7 +6,7 @@ in float vIsCarryingFood;
in float vScentFactor;
in float vIsCellCleared;
out highp vec4 FragColor;
out vec4 FragColor;
void main() {
FragColor = vec4(vIsCarryingFood * vScentFactor, (1. - vIsCarryingFood) * vScentFactor, vIsCellCleared, 1);

View file

@ -1,5 +1,3 @@
#version 300 es
precision highp float;
precision highp int;
@ -14,8 +12,7 @@ out float vIsCellCleared;
uniform sampler2D tDataCurrent;
uniform sampler2D tDataLast;
const float worldSize = 1024.;
const float cellSize = 1. / worldSize;
const float cellSize = 1. / WORLD_SIZE;
void main() {
vUv = uv;
@ -41,7 +38,7 @@ void main() {
vIsCellCleared = isCellCleared;
gl_Position = vec4(
(position.xy * cellSize * 0.5 + floor(offset * worldSize) / worldSize + cellSize * 0.5) * 2. - 1.,
(position.xy * cellSize * 0.1 + floor(offset * WORLD_SIZE) / WORLD_SIZE + cellSize * 0.5) * 2. - 1.,
0,
1
);

View file

@ -1,24 +1,26 @@
precision highp float;
precision highp int;
varying vec2 vUv;
in vec2 vUv;
out vec4 FragColor;
uniform sampler2D map;
void main() {
vec4 value = clamp(texture2D(map, vUv), 0., 1.);
vec4 bg = vec4(1);
vec4 value = clamp(texture(map, vUv), 0., 1.);
vec3 bg = vec3(0.9);
//bg = mix(bg, vec4(0, 0, 1, 1), value.a * 0.7);
//bg = mix(bg, vec4(1, 0, 0, 1), value.b * 0.7);
bg = mix(bg, vec3(0.2, 0.2, 0.8), clamp(value.a, 0., 1.));
bg = mix(bg, vec3(0.8, 0.2, 0.2), clamp(value.b, 0., 1.));
if (value.r == 1.) {
bg = vec4(1, 0, 0, 1);
bg = vec3(1, 0.2, 0.2);
}
if (value.g == 1.) {
bg = vec4(0.1, 0.1, 1, 1);
bg = vec3(0.2, 0.2, 1);
}
gl_FragColor = bg;
FragColor = vec4(bg, 1);
}

View file

@ -1,20 +1,22 @@
precision highp float;
precision highp int;
varying vec2 vUv;
in vec2 vUv;
out vec4 FragColor;
uniform sampler2D tLastState;
uniform sampler2D tDiscreteAnts;
uniform vec4 pointerData;
void main() {
vec4 lastState = texture2D(tLastState, vUv);
vec3 discreteAnts = texture2D(tDiscreteAnts, vUv).xyz;
vec4 lastState = texture(tLastState, vUv);
vec3 discreteAnts = texture(tDiscreteAnts, vUv).xyz;
float isFood = lastState.x;
float isHome = lastState.y;
float scentToHome = lastState.z + discreteAnts.x * 4.;
float scentToFood = lastState.w + discreteAnts.y * 4.;
float scentToHome = lastState.z + discreteAnts.x * 2.;
float scentToFood = lastState.w + discreteAnts.y * 2.;
if (discreteAnts.z == 1.) {
isFood = 0.;
@ -25,5 +27,5 @@ void main() {
isHome = max(isHome, pointerData.y);
}
gl_FragColor = vec4(isFood, isHome, scentToHome, scentToFood);
FragColor = vec4(isFood, isHome, scentToHome, scentToFood);
}

View file

@ -1,10 +1,10 @@
precision highp float;
precision highp int;
attribute vec3 position;
attribute vec2 uv;
in vec3 position;
in vec2 uv;
varying vec2 vUv;
out vec2 vUv;
void main() {
vUv = uv;

View file

@ -1,28 +1,28 @@
#version 300 es
precision highp float;
precision highp int;
in vec2 vUv;
out vec4 FragColor;
uniform sampler2D tWorld;
out highp vec4 FragColor;
const float offset = 1. / WORLD_SIZE * SCENT_BLUR_RADIUS;
void main() {
float cellSize = 1. / vec2(textureSize(tWorld, 0)).x * 0.2;
vec4 s0 = texture(tWorld, vUv);
vec4 s1 = texture(tWorld, vUv + vec2(1, 1) * cellSize);
vec4 s2 = texture(tWorld, vUv + vec2(-1, -1) * cellSize);
vec4 s3 = texture(tWorld, vUv + vec2(-1, 1) * cellSize);
vec4 s4 = texture(tWorld, vUv + vec2(1, -1) * cellSize);
vec4 s1 = texture(tWorld, vUv + vec2(1, 1) * offset);
vec4 s2 = texture(tWorld, vUv + vec2(-1, -1) * offset);
vec4 s3 = texture(tWorld, vUv + vec2(-1, 1) * offset);
vec4 s4 = texture(tWorld, vUv + vec2(1, -1) * offset);
float scentToHome = (s0.z + s1.z + s2.z + s3.z + s4.z) / 5. * 0.998;
float scentToFood = (s0.w + s1.w + s2.w + s3.w + s4.w) / 5. * 0.998;
float scentToHome = (s0.z + s1.z + s2.z + s3.z + s4.z) / 5. * SCENT_FADE_OUT_FACTOR;
float scentToFood = (s0.w + s1.w + s2.w + s3.w + s4.w) / 5. * SCENT_FADE_OUT_FACTOR;
float threshold = 0.01;
FragColor = vec4(s0.x, s0.y, scentToHome < threshold ? 0. : scentToHome, scentToFood < threshold ? 0. : scentToFood);
//FragColor = vec4(s0.x, s0.y, s0.z * 0.995, s0.w * 0.995);
FragColor = vec4(
s0.x,
s0.y,
scentToHome < SCENT_THRESHOLD ? 0. : scentToHome,
scentToFood < SCENT_THRESHOLD ? 0. : scentToFood
);
}

View file

@ -1,5 +1,3 @@
#version 300 es
precision highp float;
precision highp int;