Cleanup; make ants bounce off the world bounds
This commit is contained in:
parent
fb248c33ee
commit
fb772db717
21 changed files with 265 additions and 197 deletions
26
README.md
26
README.md
|
|
@ -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
|
||||
45
src/App.ts
45
src/App.ts
|
|
@ -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
12
src/Config.ts
Normal 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
21
src/GUI.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue