diff --git a/src/App.ts b/src/App.ts index cba9a9f..0b31c45 100644 --- a/src/App.ts +++ b/src/App.ts @@ -37,18 +37,29 @@ export default new class App { this.simulationStepsPerSecond = Config.simulationStepsPerSecond; this.updateSimulationInterval(); + + this.gui.on('antsCount', () => { + this.resetRenderer(); + }); + + this.gui.on('worldSize', () => { + this.resetRenderer(); + }); + + this.gui.on('simulationStepsPerSecond', () => { + this.simulationStepsPerSecond = Config.simulationStepsPerSecond; + this.updateSimulationInterval(); + }); + } + + private resetRenderer() { + this.renderer.reset(); } private updateSimulationInterval() { clearInterval(this.simInterval); this.simInterval = setInterval(() => { - if (Config.simulationStepsPerSecond !== this.simulationStepsPerSecond) { - this.simulationStepsPerSecond = Config.simulationStepsPerSecond; - this.updateSimulationInterval(); - return; - } - this.simulationStep(); this.simStarted = true; diff --git a/src/Config.ts b/src/Config.ts index 86f297a..51562f9 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -3,8 +3,8 @@ export default { antsCount: 64 ** 2, simulationStepsPerSecond: 60, scentThreshold: 0.01, - scentFadeOutFactor: 0.998, - scentBlurRadius: 0.2, + scentFadeOutFactor: 0.999, + scentBlurRadius: 0.1, scentMaxStorage: 1e6, scentPerMarker: 1000, antSpeed: 1, diff --git a/src/GUI.ts b/src/GUI.ts index 95ff06c..91c7268 100644 --- a/src/GUI.ts +++ b/src/GUI.ts @@ -1,17 +1,26 @@ import * as dat from 'dat.gui'; import Config from "./Config"; +import EventEmitter from "events"; -export default class GUI { +export default class GUI extends EventEmitter { private gui: dat.GUI = new dat.GUI({ width: 400 }); constructor() { + super(); + const simFolder = this.gui.addFolder('Simulation'); - simFolder.add(Config, 'worldSize', 256, 8096); - simFolder.add(Config, 'antsCount', 1, 1e6); - simFolder.add(Config, 'simulationStepsPerSecond', 1, 300); + simFolder.add(Config, 'worldSize', 256, 8096).onChange(() => { + this.emit('worldSize'); + }); + simFolder.add(Config, 'antsCount', 1, 1e6).onChange(() => { + this.emit('antsCount'); + }); + simFolder.add(Config, 'simulationStepsPerSecond', 1, 300).onChange(() => { + this.emit('simulationStepsPerSecond'); + }); const controlsFolder = this.gui.addFolder('Controls'); diff --git a/src/Renderer.ts b/src/Renderer.ts index 1462980..8289986 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -37,7 +37,7 @@ export default class Renderer { type: THREE.FloatType, depthBuffer: false, magFilter: THREE.NearestFilter, - minFilter: THREE.LinearFilter, + minFilter: THREE.NearestFilter, }), worldBlurredRenderTarget: new THREE.WebGLRenderTarget(Config.worldSize, Config.worldSize, { format: THREE.RGBAFormat, @@ -137,7 +137,7 @@ export default class Renderer { }; } - public destroy() { + public reset() { } diff --git a/src/index.html b/src/index.html index 9087f91..89288ee 100644 --- a/src/index.html +++ b/src/index.html @@ -9,9 +9,23 @@ margin: 0; overflow: hidden; } + + #info { + position: absolute; + top: 0; + left: 0; + width: 100%; + text-align: left; + font-family: monospace; + color: white; + padding: 16px; + text-shadow: 0 0 3px black; + pointer-events: none; + } +
Controls:
Q - draw home cells
W - draw food cells
E - draw obstacle
Drag and scroll to move the camera
diff --git a/src/scenes/ScreenScene.ts b/src/scenes/ScreenScene.ts index c735c4f..969bebe 100644 --- a/src/scenes/ScreenScene.ts +++ b/src/scenes/ScreenScene.ts @@ -29,8 +29,7 @@ export default class ScreenScene extends AbstractScene { new THREE.PlaneBufferGeometry(1, 1), new THREE.ShaderMaterial({ uniforms: { - map: {value: this.renderer.resources.worldRenderTarget.texture}, - tDiscreteAnts: {value: this.renderer.resources.antsDiscreteRenderTarget.texture}, + map: {value: this.renderer.resources.worldRenderTarget.texture} }, vertexShader: vertexShaderGround, fragmentShader: fragmentShaderGround, diff --git a/src/shaders/antsCompute.frag b/src/shaders/antsCompute.frag index 50ca8b2..93c07de 100644 --- a/src/shaders/antsCompute.frag +++ b/src/shaders/antsCompute.frag @@ -28,15 +28,25 @@ vec2 roundUvToCellCenter(vec2 uv) { } bool tryGetFood(vec2 pos) { - return texture(tWorld, roundUvToCellCenter(pos)).x == 1.; + float value = texture(tWorld, roundUvToCellCenter(pos)).x; + + return (int(value) & 1) == 1; } bool tryDropFood(vec2 pos) { - return texture(tWorld, roundUvToCellCenter(pos)).y == 1.; + float value = texture(tWorld, roundUvToCellCenter(pos)).x; + + return ((int(value) & 2) >> 1) == 1; +} + +bool isObstacle(vec2 pos) { + float value = texture(tWorld, roundUvToCellCenter(pos)).x; + + return ((int(value) & 4) >> 2) == 1; } float smell(vec2 pos, float isCarrying) { - vec2 value = texture(tWorld, pos).zw; + vec2 value = texture(tWorld, pos).yz; if (isCarrying > 0.5) { return value.y; @@ -46,10 +56,13 @@ float smell(vec2 pos, float isCarrying) { } vec2 applyOffsetToPos(vec2 pos, vec2 offsetDirectaion) { - return vec2( - clamp(pos.x + offsetDirectaion.x * cellSize, 0., 1.), - clamp(pos.y + offsetDirectaion.y * cellSize, 0., 1.) - ); + vec2 newPos = clamp(pos + offsetDirectaion * cellSize, 0., 1.); + + if (!isObstacle(pos) && isObstacle(newPos)) { + return pos; + } + + return newPos; } float getMaxScentStorage(vec2 antDataUv) { @@ -67,6 +80,7 @@ void main() { float angle = lastState.z; float isCarrying = float(int(lastState.w) & 1); float storage = float(int(lastState.w) >> 1); + bool wasObstacle = isObstacle(pos); bool movementProcessed = false; @@ -162,7 +176,7 @@ void main() { vec2 offset = vec2(cos(angle), sin(angle)); pos = applyOffsetToPos(pos, offset); - if (fract(pos.x) == 0. || fract(pos.y) == 0.) { + if (fract(pos.x) == 0. || fract(pos.y) == 0. || (!wasObstacle && isObstacle(pos + offset * cellSize))) { angle += PI * (noise - 0.5); } diff --git a/src/shaders/draw.frag b/src/shaders/draw.frag index 95d193f..294ecd7 100644 --- a/src/shaders/draw.frag +++ b/src/shaders/draw.frag @@ -13,16 +13,20 @@ uniform float brushRadius; void main() { vec4 lastState = texture(tWorld, vUv); - float isFood = lastState.x; - float isHome = lastState.y; + int cellData = int(lastState.x); + int isFood = cellData & 1; + int isHome = (cellData & 2) >> 1; + int isObstacle = (cellData & 4) >> 2; if (distance(pointerPosition, vUv) < brushRadius / WORLD_SIZE) { if (drawMode == 1.) { - isFood = 1.; + isFood = 1; } else if (drawMode == 2.) { - isHome = 1.; + isHome = 1; + } else if (drawMode == 3.) { + isObstacle = 1; } } - FragColor = vec4(isFood, isHome, lastState.zw); + FragColor = vec4(float(isFood + (isHome << 1) + (isObstacle << 2)), lastState.yzw); } \ No newline at end of file diff --git a/src/shaders/screenWorld.frag b/src/shaders/screenWorld.frag index 86ab9a1..8849fd0 100644 --- a/src/shaders/screenWorld.frag +++ b/src/shaders/screenWorld.frag @@ -6,15 +6,16 @@ in vec2 vUv; out vec4 FragColor; uniform sampler2D map; -uniform sampler2D tDiscreteAnts; void main() { - vec4 value = clamp(texture(map, vUv), 0., 1.); + vec4 value = texture(map, vUv); - float isFood = value.r; - float isHome = value.g; - float toFood = value.b; - float toHome = value.a; + int cellData = int(value.x); + int isFood = cellData & 1; + int isHome = (cellData & 2) >> 1; + int isObstacle = (cellData & 4) >> 2; + float toFood = clamp(value.y, 0., 1.); + float toHome = clamp(value.z, 0., 1.); // The part below doen't seem right. // I could figure out a better way to make pheromone colors blend properly on white background :( @@ -28,12 +29,12 @@ void main() { vec3 color = mix(vec3(1, 1, 1), t, a * 0.7); - if (isFood == 1.) { + if (isFood == 1) { color = vec3(1, 0.1, 0.1); - } - - if (isHome == 1.) { + } else if (isHome == 1) { color = vec3(0.1, 0.1, 1); + } else if (isObstacle == 1) { + color = vec3(0.6, 0.6, 0.6); } FragColor = vec4(color, 1); diff --git a/src/shaders/world.frag b/src/shaders/world.frag index cbf6876..8de54f6 100644 --- a/src/shaders/world.frag +++ b/src/shaders/world.frag @@ -12,14 +12,16 @@ void main() { vec4 lastState = texture(tLastState, vUv); vec3 discreteAnts = texture(tDiscreteAnts, vUv).xyz; - float isFood = lastState.x; - float isHome = lastState.y; - float scentToHome = min(10., lastState.z + discreteAnts.x * 2.); - float scentToFood = min(10., lastState.w + discreteAnts.y * 2.); + int cellData = int(lastState.x); + int isFood = cellData & 1; + int isHome = (cellData & 2) >> 1; + int isObstacle = (cellData & 4) >> 2; + float scentToHome = lastState.y + discreteAnts.x * 10.; + float scentToFood = lastState.z + discreteAnts.y * 10.; if (discreteAnts.z == 1.) { - isFood = 0.; + isFood = 0; } - FragColor = vec4(isFood, isHome, scentToHome, scentToFood); + FragColor = vec4(float(isFood + (isHome << 1) + (isObstacle << 2)), scentToHome, scentToFood, 0); } \ No newline at end of file diff --git a/src/shaders/worldBlur.frag b/src/shaders/worldBlur.frag index 10c65ad..b52db82 100644 --- a/src/shaders/worldBlur.frag +++ b/src/shaders/worldBlur.frag @@ -16,13 +16,13 @@ void main() { 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. * SCENT_FADE_OUT_FACTOR; - float scentToFood = (s0.w + s1.w + s2.w + s3.w + s4.w) / 5. * SCENT_FADE_OUT_FACTOR; + float scentToHome = (s0.y + s1.y + s2.y + s3.y + s4.y) / 5. * SCENT_FADE_OUT_FACTOR; + float scentToFood = (s0.z + s1.z + s2.z + s3.z + s4.z) / 5. * SCENT_FADE_OUT_FACTOR; FragColor = vec4( s0.x, - s0.y, scentToHome < SCENT_THRESHOLD ? 0. : scentToHome, - scentToFood < SCENT_THRESHOLD ? 0. : scentToFood + scentToFood < SCENT_THRESHOLD ? 0. : scentToFood, + 0 ); } \ No newline at end of file