From fb772db7172eff5a95f4b1772e2bbbce3fe45509 Mon Sep 17 00:00:00 2001 From: vHawk <48140945+vHawk@users.noreply.github.com> Date: Sun, 26 Jun 2022 19:23:23 +0300 Subject: [PATCH] Cleanup; make ants bounce off the world bounds --- README.md | 26 +++++- src/App.ts | 45 +++++++--- src/Config.ts | 12 +++ src/GUI.ts | 21 +++++ src/Renderer.ts | 56 +++++++----- src/scenes/AntsComputeScene.ts | 8 +- src/scenes/AntsDiscretizeScene.ts | 13 ++- src/scenes/ScreenScene.ts | 6 +- src/scenes/WorldBlurScene.ts | 14 +-- src/scenes/WorldComputeScene.ts | 22 ++--- src/shaders/ants.frag | 16 ++-- src/shaders/ants.vert | 2 +- src/shaders/antsCompute.frag | 138 ++++++++++++++---------------- src/shaders/antsCompute.vert | 2 - src/shaders/antsDiscretize.frag | 4 +- src/shaders/antsDiscretize.vert | 7 +- src/shaders/screenWorld.frag | 18 ++-- src/shaders/world.frag | 14 +-- src/shaders/world.vert | 6 +- src/shaders/worldBlur.frag | 30 +++---- src/shaders/worldBlur.vert | 2 - 21 files changed, 265 insertions(+), 197 deletions(-) create mode 100644 src/Config.ts create mode 100644 src/GUI.ts diff --git a/README.md b/README.md index c4ff69f..30cf4c9 100644 --- a/README.md +++ b/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 \ No newline at end of file diff --git a/src/App.ts b/src/App.ts index 1104f3a..4864587 100644 --- a/src/App.ts +++ b/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(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); } } \ No newline at end of file diff --git a/src/Config.ts b/src/Config.ts new file mode 100644 index 0000000..5c55aee --- /dev/null +++ b/src/Config.ts @@ -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 +}; \ No newline at end of file diff --git a/src/GUI.ts b/src/GUI.ts new file mode 100644 index 0000000..f211709 --- /dev/null +++ b/src/GUI.ts @@ -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(); + } +} \ No newline at end of file diff --git a/src/Renderer.ts b/src/Renderer.ts index 2e0bdc0..717f424 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -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 { + 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); + } } \ No newline at end of file diff --git a/src/scenes/AntsComputeScene.ts b/src/scenes/AntsComputeScene.ts index 79fe92c..7d34a69 100644 --- a/src/scenes/AntsComputeScene.ts +++ b/src/scenes/AntsComputeScene.ts @@ -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); diff --git a/src/scenes/AntsDiscretizeScene.ts b/src/scenes/AntsDiscretizeScene.ts index 7a118b2..ed9793e 100644 --- a/src/scenes/AntsDiscretizeScene.ts +++ b/src/scenes/AntsDiscretizeScene.ts @@ -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) { diff --git a/src/scenes/ScreenScene.ts b/src/scenes/ScreenScene.ts index b2a10ef..eaffa38 100644 --- a/src/scenes/ScreenScene.ts +++ b/src/scenes/ScreenScene.ts @@ -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 }) ); diff --git a/src/scenes/WorldBlurScene.ts b/src/scenes/WorldBlurScene.ts index 4d2bf23..ff20fed 100644 --- a/src/scenes/WorldBlurScene.ts +++ b/src/scenes/WorldBlurScene.ts @@ -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) { diff --git a/src/scenes/WorldComputeScene.ts b/src/scenes/WorldComputeScene.ts index 3055ae9..8ef5e85 100644 --- a/src/scenes/WorldComputeScene.ts +++ b/src/scenes/WorldComputeScene.ts @@ -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) { diff --git a/src/shaders/ants.frag b/src/shaders/ants.frag index 829ec36..9bbe746 100644 --- a/src/shaders/ants.frag +++ b/src/shaders/ants.frag @@ -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); } \ No newline at end of file diff --git a/src/shaders/ants.vert b/src/shaders/ants.vert index 222ab53..bf4f27b 100644 --- a/src/shaders/ants.vert +++ b/src/shaders/ants.vert @@ -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); diff --git a/src/shaders/antsCompute.frag b/src/shaders/antsCompute.frag index d2bab53..2231f47 100644 --- a/src/shaders/antsCompute.frag +++ b/src/shaders/antsCompute.frag @@ -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)) ); } \ No newline at end of file diff --git a/src/shaders/antsCompute.vert b/src/shaders/antsCompute.vert index 688e8d9..1103143 100644 --- a/src/shaders/antsCompute.vert +++ b/src/shaders/antsCompute.vert @@ -1,5 +1,3 @@ -#version 300 es - precision highp float; precision highp int; diff --git a/src/shaders/antsDiscretize.frag b/src/shaders/antsDiscretize.frag index f0db55f..00e680d 100644 --- a/src/shaders/antsDiscretize.frag +++ b/src/shaders/antsDiscretize.frag @@ -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); diff --git a/src/shaders/antsDiscretize.vert b/src/shaders/antsDiscretize.vert index 2cf2795..830271f 100644 --- a/src/shaders/antsDiscretize.vert +++ b/src/shaders/antsDiscretize.vert @@ -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 ); diff --git a/src/shaders/screenWorld.frag b/src/shaders/screenWorld.frag index 7b2aece..d48b942 100644 --- a/src/shaders/screenWorld.frag +++ b/src/shaders/screenWorld.frag @@ -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); } \ No newline at end of file diff --git a/src/shaders/world.frag b/src/shaders/world.frag index fc829a1..5449268 100644 --- a/src/shaders/world.frag +++ b/src/shaders/world.frag @@ -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); } \ No newline at end of file diff --git a/src/shaders/world.vert b/src/shaders/world.vert index 45f6884..1103143 100644 --- a/src/shaders/world.vert +++ b/src/shaders/world.vert @@ -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; diff --git a/src/shaders/worldBlur.frag b/src/shaders/worldBlur.frag index dbfc2a1..10c65ad 100644 --- a/src/shaders/worldBlur.frag +++ b/src/shaders/worldBlur.frag @@ -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 + ); } \ No newline at end of file diff --git a/src/shaders/worldBlur.vert b/src/shaders/worldBlur.vert index 688e8d9..1103143 100644 --- a/src/shaders/worldBlur.vert +++ b/src/shaders/worldBlur.vert @@ -1,5 +1,3 @@ -#version 300 es - precision highp float; precision highp int;