Add obstacles

This commit is contained in:
vHawk 2022-06-29 15:45:31 +03:00
parent 27c835b8e5
commit a2b5d45a52
11 changed files with 103 additions and 49 deletions

View file

@ -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;

View file

@ -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,

View file

@ -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');

View file

@ -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() {
}

View file

@ -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;
}
</style>
</head>
<body>
<div id="info">Controls:<br/>Q - draw home cells<br/>W - draw food cells<br/>E - draw obstacle<br/>Drag and scroll to move the camera</div>
<canvas id="canvas"></canvas>
</body>
</html>

View file

@ -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,

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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
);
}