Initial implementation
This commit is contained in:
parent
1588105e0b
commit
f0c253c7fb
30 changed files with 4325 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules
|
||||||
|
.idea
|
||||||
|
build
|
||||||
3184
package-lock.json
generated
Normal file
3184
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
30
package.json
Normal file
30
package.json
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"name": "ants-simulation",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"dependencies": {
|
||||||
|
"dat.gui": "^0.7.9",
|
||||||
|
"push-dir": "^0.4.1",
|
||||||
|
"three": "^0.141.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/dat.gui": "^0.7.7",
|
||||||
|
"@types/node": "^18.0.0",
|
||||||
|
"@types/three": "^0.141.0",
|
||||||
|
"clean-webpack-plugin": "^4.0.0",
|
||||||
|
"html-webpack-plugin": "^5.5.0",
|
||||||
|
"raw-loader": "^4.0.2",
|
||||||
|
"ts-loader": "^9.3.1",
|
||||||
|
"typescript": "^4.7.4",
|
||||||
|
"webpack": "^5.73.0",
|
||||||
|
"webpack-cli": "^4.10.0",
|
||||||
|
"webpack-dev-server": "^4.9.2"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack --config ./webpack.config.js --mode=production",
|
||||||
|
"dev": "webpack serve --config ./webpack.config.js --mode=development"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
BIN
public/ant.png
Normal file
BIN
public/ant.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 366 B |
BIN
public/food.png
Normal file
BIN
public/food.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 229 B |
62
src/App.ts
Normal file
62
src/App.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import AntsComputeScene from "./scenes/AntsComputeScene";
|
||||||
|
import ScreenScene from "./scenes/ScreenScene";
|
||||||
|
import Renderer from "./Renderer";
|
||||||
|
import WorldComputeScene from "./scenes/WorldComputeScene";
|
||||||
|
import AntsDiscretizeScene from "./scenes/AntsDiscretizeScene";
|
||||||
|
import WorldBlurScene from "./scenes/WorldBlurScene";
|
||||||
|
|
||||||
|
export interface SceneCollection {
|
||||||
|
ants: AntsComputeScene;
|
||||||
|
world: WorldComputeScene;
|
||||||
|
worldBlur: WorldBlurScene;
|
||||||
|
discretize: AntsDiscretizeScene;
|
||||||
|
screen: ScreenScene;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.initScenes();
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => this.resize());
|
||||||
|
|
||||||
|
this.resize();
|
||||||
|
|
||||||
|
this.update(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initScenes() {
|
||||||
|
this.scenes = {
|
||||||
|
ants: new AntsComputeScene(this.renderer),
|
||||||
|
world: new WorldComputeScene(this.renderer),
|
||||||
|
worldBlur: new WorldBlurScene(this.renderer),
|
||||||
|
discretize: new AntsDiscretizeScene(this.renderer),
|
||||||
|
screen: new ScreenScene(this.renderer),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private resize() {
|
||||||
|
const width = window.innerWidth;
|
||||||
|
const height = window.innerHeight;
|
||||||
|
|
||||||
|
this.renderer.resizeCanvas(width, height);
|
||||||
|
|
||||||
|
for (const scene of Object.values(this.scenes)) {
|
||||||
|
scene.resize(width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
121
src/Renderer.ts
Normal file
121
src/Renderer.ts
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import {SceneCollection} from "./App";
|
||||||
|
|
||||||
|
const Config = {
|
||||||
|
worldSize: 1024,
|
||||||
|
antsCount: 64 ** 2
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Resources {
|
||||||
|
worldRenderTarget0: THREE.WebGLRenderTarget;
|
||||||
|
worldRenderTarget1: THREE.WebGLRenderTarget;
|
||||||
|
worldBlurredRenderTarget: THREE.WebGLRenderTarget;
|
||||||
|
antsDataRenderTarget0: THREE.WebGLRenderTarget;
|
||||||
|
antsDataRenderTarget1: THREE.WebGLRenderTarget;
|
||||||
|
antsDiscreteRenderTarget: THREE.WebGLRenderTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Renderer {
|
||||||
|
private renderer: THREE.WebGLRenderer;
|
||||||
|
public resources: Resources;
|
||||||
|
|
||||||
|
constructor(public canvas: HTMLCanvasElement) {
|
||||||
|
this.renderer = new THREE.WebGLRenderer({canvas})
|
||||||
|
|
||||||
|
this.initResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initResources() {
|
||||||
|
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, {
|
||||||
|
format: THREE.RGBAFormat,
|
||||||
|
type: THREE.FloatType,
|
||||||
|
depthBuffer: false,
|
||||||
|
magFilter: THREE.LinearFilter,
|
||||||
|
minFilter: THREE.LinearFilter,
|
||||||
|
}),
|
||||||
|
worldBlurredRenderTarget: new THREE.WebGLRenderTarget(Config.worldSize, Config.worldSize, {
|
||||||
|
format: THREE.RGBAFormat,
|
||||||
|
type: THREE.FloatType,
|
||||||
|
depthBuffer: false,
|
||||||
|
magFilter: THREE.LinearFilter,
|
||||||
|
minFilter: THREE.LinearFilter,
|
||||||
|
}),
|
||||||
|
antsDataRenderTarget0: new THREE.WebGLRenderTarget(antTextureSize, antTextureSize, {
|
||||||
|
format: THREE.RGBAFormat,
|
||||||
|
type: THREE.FloatType,
|
||||||
|
depthBuffer: false,
|
||||||
|
magFilter: THREE.NearestFilter,
|
||||||
|
minFilter: THREE.NearestFilter,
|
||||||
|
}),
|
||||||
|
antsDataRenderTarget1: new THREE.WebGLRenderTarget(antTextureSize, antTextureSize, {
|
||||||
|
format: THREE.RGBAFormat,
|
||||||
|
type: THREE.FloatType,
|
||||||
|
depthBuffer: false,
|
||||||
|
magFilter: THREE.NearestFilter,
|
||||||
|
minFilter: THREE.NearestFilter,
|
||||||
|
}),
|
||||||
|
antsDiscreteRenderTarget: new THREE.WebGLRenderTarget(Config.worldSize, Config.worldSize, {
|
||||||
|
format: THREE.RGBAFormat,
|
||||||
|
type: THREE.UnsignedByteType,
|
||||||
|
depthBuffer: false,
|
||||||
|
magFilter: THREE.NearestFilter,
|
||||||
|
minFilter: THREE.NearestFilter,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public renderScenes(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;
|
||||||
|
this.renderer.render(scenes.ants, scenes.ants.camera);
|
||||||
|
|
||||||
|
this.renderer.setViewport(0, 0, scenes.discretize.renderWidth, scenes.discretize.renderHeight);
|
||||||
|
|
||||||
|
this.renderer.setRenderTarget(scenes.discretize.getRenderTarget());
|
||||||
|
scenes.discretize.material.uniforms.tDataCurrent.value = antsComputeTarget.texture;
|
||||||
|
scenes.discretize.material.uniforms.tDataLast.value = antsComputeSource.texture;
|
||||||
|
this.renderer.render(scenes.discretize, scenes.discretize.camera);
|
||||||
|
|
||||||
|
this.renderer.setViewport(0, 0, scenes.world.renderWidth, scenes.world.renderHeight);
|
||||||
|
|
||||||
|
this.renderer.setRenderTarget(worldComputeTarget);
|
||||||
|
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();
|
||||||
|
this.renderer.render(scenes.world, scenes.world.camera);
|
||||||
|
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
this.renderer.render(scenes.screen, scenes.screen.camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
public resizeCanvas(width: number, height: number) {
|
||||||
|
this.canvas.width = width;
|
||||||
|
this.canvas.height = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/global.d.ts
vendored
Normal file
8
src/global.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
declare module '*.vert' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
declare module '*.frag' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
17
src/index.html
Normal file
17
src/index.html
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||||
|
<title>Ants simulation</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="canvas"></canvas>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
19
src/scenes/AbstractScene.ts
Normal file
19
src/scenes/AbstractScene.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import Renderer from "../Renderer";
|
||||||
|
|
||||||
|
export default abstract class AbstractScene extends THREE.Scene {
|
||||||
|
protected readonly renderer: Renderer;
|
||||||
|
public readonly camera: THREE.Camera;
|
||||||
|
public renderWidth: number = 1;
|
||||||
|
public renderHeight: number = 1;
|
||||||
|
|
||||||
|
protected constructor(renderer: Renderer) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.renderer = renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract resize(width: number, height: number): void;
|
||||||
|
|
||||||
|
public abstract update(deltaTime: number): void;
|
||||||
|
}
|
||||||
54
src/scenes/AntsComputeScene.ts
Normal file
54
src/scenes/AntsComputeScene.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import * as THREE 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();
|
||||||
|
public material: THREE.RawShaderMaterial;
|
||||||
|
private renderTargets: [WebGLRenderTarget, WebGLRenderTarget];
|
||||||
|
|
||||||
|
constructor(renderer: Renderer) {
|
||||||
|
super(renderer);
|
||||||
|
|
||||||
|
const geometry = new FullScreenTriangleGeometry();
|
||||||
|
const material = new THREE.RawShaderMaterial({
|
||||||
|
uniforms: {
|
||||||
|
uTime: {value: 0},
|
||||||
|
tLastState: {value: this.renderer.resources.antsDataRenderTarget0.texture},
|
||||||
|
tWorld: {value: this.renderer.resources.worldRenderTarget0.texture},
|
||||||
|
},
|
||||||
|
vertexShader,
|
||||||
|
fragmentShader
|
||||||
|
});
|
||||||
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
this.add(mesh);
|
||||||
|
|
||||||
|
this.material = material;
|
||||||
|
|
||||||
|
this.renderTargets = [
|
||||||
|
this.renderer.resources.antsDataRenderTarget0,
|
||||||
|
this.renderer.resources.antsDataRenderTarget1
|
||||||
|
];
|
||||||
|
|
||||||
|
this.renderWidth = this.renderer.resources.antsDataRenderTarget0.width;
|
||||||
|
this.renderHeight = this.renderer.resources.antsDataRenderTarget0.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRenderTargets(): [WebGLRenderTarget, WebGLRenderTarget] {
|
||||||
|
this.renderTargets.reverse();
|
||||||
|
|
||||||
|
return this.renderTargets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public resize(width: number, height: number) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(deltaTime: number) {
|
||||||
|
this.material.uniforms.uTime.value = performance.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/scenes/AntsDiscretizeScene.ts
Normal file
51
src/scenes/AntsDiscretizeScene.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import {WebGLRenderTarget} from 'three';
|
||||||
|
import Renderer from "../Renderer";
|
||||||
|
import AbstractScene from "./AbstractScene";
|
||||||
|
import fragmentShader from '../shaders/antsDiscretize.frag';
|
||||||
|
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);
|
||||||
|
|
||||||
|
const geometry = new THREE.BoxBufferGeometry(1, 1, 1);
|
||||||
|
const material = new THREE.RawShaderMaterial({
|
||||||
|
uniforms: {
|
||||||
|
tDataCurrent: {value: null},
|
||||||
|
tDataLast: {value: null},
|
||||||
|
},
|
||||||
|
vertexShader,
|
||||||
|
fragmentShader
|
||||||
|
});
|
||||||
|
const mesh = new THREE.InstancedMesh(
|
||||||
|
geometry,
|
||||||
|
material,
|
||||||
|
this.renderer.resources.antsDataRenderTarget0.width * this.renderer.resources.antsDataRenderTarget0.height
|
||||||
|
);
|
||||||
|
this.add(mesh);
|
||||||
|
|
||||||
|
this.material = material;
|
||||||
|
|
||||||
|
this.renderTarget = this.renderer.resources.antsDiscreteRenderTarget;
|
||||||
|
|
||||||
|
this.renderWidth = this.renderer.resources.worldRenderTarget0.width;
|
||||||
|
this.renderHeight = this.renderer.resources.worldRenderTarget0.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRenderTarget(): WebGLRenderTarget {
|
||||||
|
return this.renderTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public resize(width: number, height: number) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(deltaTime: number) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
141
src/scenes/ScreenScene.ts
Normal file
141
src/scenes/ScreenScene.ts
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import Renderer from "../Renderer";
|
||||||
|
import AbstractScene from "./AbstractScene";
|
||||||
|
import vertexShaderAnts from "../shaders/ants.vert";
|
||||||
|
import fragmentShaderAnts from "../shaders/ants.frag";
|
||||||
|
import vertexShaderGround from "../shaders/screenWorld.vert";
|
||||||
|
import fragmentShaderGround from "../shaders/screenWorld.frag";
|
||||||
|
|
||||||
|
enum PointerState {
|
||||||
|
None,
|
||||||
|
LMB,
|
||||||
|
RMB
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ScreenScene extends AbstractScene {
|
||||||
|
public readonly camera: THREE.PerspectiveCamera;
|
||||||
|
public readonly material: THREE.ShaderMaterial;
|
||||||
|
public readonly groundMaterial: THREE.ShaderMaterial;
|
||||||
|
public readonly pointerPosition: THREE.Vector2 = new THREE.Vector2();
|
||||||
|
public pointerState: PointerState = PointerState.None;
|
||||||
|
|
||||||
|
constructor(renderer: Renderer) {
|
||||||
|
super(renderer);
|
||||||
|
|
||||||
|
const ground = new THREE.Mesh(
|
||||||
|
new THREE.PlaneBufferGeometry(10, 10),
|
||||||
|
new THREE.ShaderMaterial({
|
||||||
|
uniforms: {
|
||||||
|
map: {value: this.renderer.resources.worldRenderTarget0.texture},
|
||||||
|
},
|
||||||
|
vertexShader: vertexShaderGround,
|
||||||
|
fragmentShader: fragmentShaderGround
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.groundMaterial = ground.material;
|
||||||
|
|
||||||
|
ground.position.x += 5;
|
||||||
|
ground.position.y += 5;
|
||||||
|
|
||||||
|
this.add(ground);
|
||||||
|
|
||||||
|
const antTexture = new THREE.TextureLoader().load('ant.png');
|
||||||
|
const foodTexture = new THREE.TextureLoader().load('food.png');
|
||||||
|
|
||||||
|
antTexture.magFilter = foodTexture.magFilter = THREE.NearestFilter;
|
||||||
|
antTexture.minFilter = foodTexture.minFilter = THREE.LinearMipMapLinearFilter;
|
||||||
|
|
||||||
|
this.material = new THREE.ShaderMaterial({
|
||||||
|
uniforms: {
|
||||||
|
tData: {value: this.renderer.resources.antsDataRenderTarget0.texture},
|
||||||
|
tAnt: {value: antTexture},
|
||||||
|
tFood: {value: foodTexture}
|
||||||
|
},
|
||||||
|
vertexShader: vertexShaderAnts,
|
||||||
|
fragmentShader: fragmentShaderAnts,
|
||||||
|
transparent: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const ants = new THREE.InstancedMesh(
|
||||||
|
new THREE.PlaneBufferGeometry(0.15, 0.15),
|
||||||
|
this.material,
|
||||||
|
this.renderer.resources.antsDataRenderTarget0.width * this.renderer.resources.antsDataRenderTarget0.height
|
||||||
|
)
|
||||||
|
|
||||||
|
this.add(ants);
|
||||||
|
|
||||||
|
this.camera = new THREE.PerspectiveCamera(50, 1, 0.1, 10000);
|
||||||
|
|
||||||
|
this.add(this.camera);
|
||||||
|
|
||||||
|
this.camera.position.z = 12;
|
||||||
|
this.camera.position.x = this.camera.position.y = 5;
|
||||||
|
|
||||||
|
const raycastVector = new THREE.Vector2(0, 0);
|
||||||
|
const raycaster = new THREE.Raycaster();
|
||||||
|
|
||||||
|
this.renderer.canvas.addEventListener('contextmenu', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.renderer.canvas.addEventListener('pointerdown', (e) => {
|
||||||
|
raycastVector.x = (e.clientX / window.innerWidth) * 2 - 1;
|
||||||
|
raycastVector.y = -(e.clientY / window.innerHeight) * 2 + 1;
|
||||||
|
|
||||||
|
raycaster.setFromCamera(raycastVector, this.camera);
|
||||||
|
|
||||||
|
const intersects = raycaster.intersectObjects([ground]);
|
||||||
|
|
||||||
|
if (intersects.length > 0) {
|
||||||
|
const uv = intersects[0].uv;
|
||||||
|
this.pointerPosition.copy(uv);
|
||||||
|
|
||||||
|
if (e.button === 0) {
|
||||||
|
this.pointerState = PointerState.LMB;
|
||||||
|
} else if (e.button === 2) {
|
||||||
|
this.pointerState = PointerState.RMB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.renderer.canvas.addEventListener('pointermove', (e) => {
|
||||||
|
raycastVector.x = (e.clientX / window.innerWidth) * 2 - 1;
|
||||||
|
raycastVector.y = -(e.clientY / window.innerHeight) * 2 + 1;
|
||||||
|
|
||||||
|
raycaster.setFromCamera(raycastVector, this.camera);
|
||||||
|
|
||||||
|
const intersects = raycaster.intersectObjects([ground]);
|
||||||
|
|
||||||
|
if (intersects.length > 0) {
|
||||||
|
const uv = intersects[0].uv;
|
||||||
|
this.pointerPosition.copy(uv);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.renderer.canvas.addEventListener('pointerup', (e) => {
|
||||||
|
this.pointerState = PointerState.None;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPointerData(): THREE.Vector4 {
|
||||||
|
return new THREE.Vector4(
|
||||||
|
+(this.pointerState === PointerState.LMB),
|
||||||
|
+(this.pointerState === PointerState.RMB),
|
||||||
|
this.pointerPosition.x,
|
||||||
|
this.pointerPosition.y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public resize(width: number, height: number) {
|
||||||
|
this.camera.aspect = width / height;
|
||||||
|
this.camera.updateProjectionMatrix();
|
||||||
|
|
||||||
|
this.renderWidth = width;
|
||||||
|
this.renderHeight = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(deltaTime: number) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/scenes/WorldBlurScene.ts
Normal file
46
src/scenes/WorldBlurScene.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import Renderer from "../Renderer";
|
||||||
|
import AbstractScene from "./AbstractScene";
|
||||||
|
import FullScreenTriangleGeometry from "../utils/FullScreenTriangleGeometry";
|
||||||
|
import fragmentShader from '../shaders/worldBlur.frag';
|
||||||
|
import vertexShader from '../shaders/worldBlur.vert';
|
||||||
|
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);
|
||||||
|
|
||||||
|
const geometry = new FullScreenTriangleGeometry();
|
||||||
|
const material = new THREE.RawShaderMaterial({
|
||||||
|
uniforms: {
|
||||||
|
tWorld: {value: this.renderer.resources.worldRenderTarget0.texture},
|
||||||
|
},
|
||||||
|
vertexShader,
|
||||||
|
fragmentShader
|
||||||
|
});
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRenderTarget(): WebGLRenderTarget {
|
||||||
|
return this.renderTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public resize(width: number, height: number) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(deltaTime: number) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/scenes/WorldComputeScene.ts
Normal file
54
src/scenes/WorldComputeScene.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import Renderer from "../Renderer";
|
||||||
|
import AbstractScene from "./AbstractScene";
|
||||||
|
import FullScreenTriangleGeometry from "../utils/FullScreenTriangleGeometry";
|
||||||
|
import fragmentShader from '../shaders/world.frag';
|
||||||
|
import vertexShader from '../shaders/world.vert';
|
||||||
|
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);
|
||||||
|
|
||||||
|
const geometry = new FullScreenTriangleGeometry();
|
||||||
|
const material = new THREE.RawShaderMaterial({
|
||||||
|
uniforms: {
|
||||||
|
tLastState: {value: this.renderer.resources.worldRenderTarget0.texture},
|
||||||
|
tDiscreteAnts: {value: this.renderer.resources.antsDiscreteRenderTarget.texture},
|
||||||
|
pointerData: {value: new THREE.Vector4()},
|
||||||
|
},
|
||||||
|
vertexShader,
|
||||||
|
fragmentShader
|
||||||
|
});
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRenderTargets(): [WebGLRenderTarget, WebGLRenderTarget] {
|
||||||
|
this.renderTargets.reverse();
|
||||||
|
|
||||||
|
return this.renderTargets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public resize(width: number, height: number) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(deltaTime: number) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/shaders/ants.frag
Normal file
21
src/shaders/ants.frag
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
precision highp float;
|
||||||
|
precision highp int;
|
||||||
|
|
||||||
|
varying vec2 vUv;
|
||||||
|
varying 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);
|
||||||
|
|
||||||
|
gl_FragColor = mix(antColor, foodColor, foodColor.a * vIsCarryingFood);
|
||||||
|
}
|
||||||
35
src/shaders/ants.vert
Normal file
35
src/shaders/ants.vert
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
precision highp float;
|
||||||
|
precision highp int;
|
||||||
|
|
||||||
|
#define PI 3.1415926535897932384626433832795
|
||||||
|
|
||||||
|
varying vec2 vUv;
|
||||||
|
varying float vIsCarryingFood;
|
||||||
|
|
||||||
|
uniform sampler2D tData;
|
||||||
|
|
||||||
|
vec2 rotate(vec2 v, float a) {
|
||||||
|
float s = sin(a);
|
||||||
|
float c = cos(a);
|
||||||
|
mat2 m = mat2(c, -s, s, c);
|
||||||
|
return m * v;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
|
||||||
|
float dataTextureSize = vec2(textureSize(tData, 0)).x;
|
||||||
|
|
||||||
|
float id = float(gl_InstanceID);
|
||||||
|
float sampleY = floor(id / dataTextureSize);
|
||||||
|
float sampleX = id - sampleY * dataTextureSize;
|
||||||
|
|
||||||
|
vec4 dataSample = texture2D(tData, vec2(sampleX, sampleY) / dataTextureSize);
|
||||||
|
|
||||||
|
vec2 offset = dataSample.xy * 10.;
|
||||||
|
vec2 rotatedPosition = rotate(position.xy, -dataSample.z + PI * 0.5);
|
||||||
|
|
||||||
|
vIsCarryingFood = float(int(dataSample.w) & 1);
|
||||||
|
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(vec2(rotatedPosition + offset), 0, 1);
|
||||||
|
}
|
||||||
200
src/shaders/antsCompute.frag
Normal file
200
src/shaders/antsCompute.frag
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
#version 300 es
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
precision highp int;
|
||||||
|
|
||||||
|
#define PI 3.1415926535897932384626433832795
|
||||||
|
#define MAX_STORAGE 1000000.
|
||||||
|
|
||||||
|
in vec2 vUv;
|
||||||
|
|
||||||
|
out highp 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;
|
||||||
|
|
||||||
|
float goldNoise(in vec2 xy, in float seed) {
|
||||||
|
return fract(tan(distance(xy*PHI, xy)*seed)*xy.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tryGetFood(vec2 pos) {
|
||||||
|
return texture(tWorld, pos).x == 1.;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tryDropFood(vec2 pos) {
|
||||||
|
return texture(tWorld, pos).y == 1.;
|
||||||
|
}
|
||||||
|
|
||||||
|
float smell(vec2 pos, float isCarrying) {
|
||||||
|
vec2 value = texture(tWorld, pos).zw;
|
||||||
|
|
||||||
|
if (isCarrying > 0.5) {
|
||||||
|
return value.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 applyOffsetToPos(vec2 pos, vec2 offsetDirectaion) {
|
||||||
|
return vec2(
|
||||||
|
fract(pos.x + offsetDirectaion.x * cellSize),
|
||||||
|
fract(pos.y + offsetDirectaion.y * cellSize)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float rotAngle = PI / 50.;
|
||||||
|
|
||||||
|
float constrainAngle(float x){
|
||||||
|
x = mod(x,360.);
|
||||||
|
if (x < 0.)
|
||||||
|
x += 360.;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 lastState = texture(tLastState, vUv);
|
||||||
|
|
||||||
|
float rand = goldNoise(vUv * 1000., fract(uTime / 1000.));
|
||||||
|
|
||||||
|
vec2 pos = lastState.xy;
|
||||||
|
float angle = lastState.z;
|
||||||
|
float isCarrying = float(int(lastState.w) & 1);
|
||||||
|
float storage = float(int(lastState.w) >> 1);
|
||||||
|
|
||||||
|
bool movementProcessed = false;
|
||||||
|
|
||||||
|
if (pos == vec2(0)) { // init new ant
|
||||||
|
pos = vec2(0.5);
|
||||||
|
angle = goldNoise(vUv * 10000., 1.) * 2. * PI;
|
||||||
|
isCarrying = 0.;
|
||||||
|
storage = MAX_STORAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tryDropFood(pos)) {
|
||||||
|
if (isCarrying == 1.) {
|
||||||
|
isCarrying = 0.;
|
||||||
|
angle += PI;
|
||||||
|
}
|
||||||
|
storage = MAX_STORAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCarrying == 0.) {
|
||||||
|
if (rand < 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;
|
||||||
|
vec2 point = applyOffsetToPos(pos, offset);
|
||||||
|
|
||||||
|
if (tryGetFood(point)) {
|
||||||
|
isCarrying = 1.;
|
||||||
|
storage = MAX_STORAGE;
|
||||||
|
pos = point;
|
||||||
|
movementProcessed = true;
|
||||||
|
angle -= rotAngle;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vec2 offset = vec2(cos(angle + rotAngle), sin(angle + rotAngle)) * sampleDistance;
|
||||||
|
vec2 point = applyOffsetToPos(pos, offset);
|
||||||
|
|
||||||
|
if (tryGetFood(point)) {
|
||||||
|
isCarrying = 1.;
|
||||||
|
storage = MAX_STORAGE;
|
||||||
|
pos = point;
|
||||||
|
movementProcessed = true;
|
||||||
|
angle += rotAngle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (isCarrying == 1.) {
|
||||||
|
if (rand < 0.33) {
|
||||||
|
vec2 offset = vec2(cos(angle), sin(angle)) * 10.;
|
||||||
|
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;
|
||||||
|
vec2 point = applyOffsetToPos(pos, offset);
|
||||||
|
|
||||||
|
if (tryDropFood(point)) {
|
||||||
|
isCarrying = 0.;
|
||||||
|
storage = MAX_STORAGE;
|
||||||
|
pos = point;
|
||||||
|
movementProcessed = true;
|
||||||
|
angle -= rotAngle;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vec2 offset = vec2(cos(angle + rotAngle), sin(angle + rotAngle)) * sampleDistance;
|
||||||
|
vec2 point = applyOffsetToPos(pos, offset);
|
||||||
|
|
||||||
|
if (tryDropFood(point)) {
|
||||||
|
isCarrying = 0.;
|
||||||
|
storage = MAX_STORAGE;
|
||||||
|
pos = point;
|
||||||
|
movementProcessed = true;
|
||||||
|
angle += rotAngle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!movementProcessed) {
|
||||||
|
float noise2 = goldNoise(vUv * 10000., 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);
|
||||||
|
|
||||||
|
if (sampleAhead > sampleLeft && sampleAhead > sampleRight) {
|
||||||
|
// don't change direction
|
||||||
|
} else if (sampleLeft > sampleAhead && sampleLeft > sampleRight) {
|
||||||
|
angle -= rotAngle; // 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noise2 > 0.5) {
|
||||||
|
angle += PI / 30.;
|
||||||
|
} else {
|
||||||
|
angle -= PI / 30.;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 offset = vec2(cos(angle), sin(angle));
|
||||||
|
pos = applyOffsetToPos(pos, offset);
|
||||||
|
} else {
|
||||||
|
angle += PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fract(pos.x) == 0. || fract(pos.y) == 0.) {
|
||||||
|
//angle += PI / 2.;
|
||||||
|
}
|
||||||
|
|
||||||
|
FragColor = vec4(
|
||||||
|
pos.x,
|
||||||
|
pos.y,
|
||||||
|
angle,
|
||||||
|
float((uint(max(storage - 1000., 0.)) << 1) + uint(isCarrying))
|
||||||
|
);
|
||||||
|
}
|
||||||
14
src/shaders/antsCompute.vert
Normal file
14
src/shaders/antsCompute.vert
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
#version 300 es
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
precision highp int;
|
||||||
|
|
||||||
|
in vec3 position;
|
||||||
|
in vec2 uv;
|
||||||
|
|
||||||
|
out vec2 vUv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = vec4(position, 1.0);
|
||||||
|
}
|
||||||
15
src/shaders/antsDiscretize.frag
Normal file
15
src/shaders/antsDiscretize.frag
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#version 300 es
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
precision highp int;
|
||||||
|
|
||||||
|
in vec2 vUv;
|
||||||
|
in float vIsCarryingFood;
|
||||||
|
in float vScentFactor;
|
||||||
|
in float vIsCellCleared;
|
||||||
|
|
||||||
|
out highp vec4 FragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
FragColor = vec4(vIsCarryingFood * vScentFactor, (1. - vIsCarryingFood) * vScentFactor, vIsCellCleared, 1);
|
||||||
|
}
|
||||||
48
src/shaders/antsDiscretize.vert
Normal file
48
src/shaders/antsDiscretize.vert
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
#version 300 es
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
precision highp int;
|
||||||
|
|
||||||
|
in vec3 position;
|
||||||
|
in vec2 uv;
|
||||||
|
|
||||||
|
out vec2 vUv;
|
||||||
|
out float vIsCarryingFood;
|
||||||
|
out float vScentFactor;
|
||||||
|
out float vIsCellCleared;
|
||||||
|
|
||||||
|
uniform sampler2D tDataCurrent;
|
||||||
|
uniform sampler2D tDataLast;
|
||||||
|
|
||||||
|
const float worldSize = 1024.;
|
||||||
|
const float cellSize = 1. / worldSize;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
|
||||||
|
float dataTextureSize = vec2(textureSize(tDataCurrent, 0)).x;
|
||||||
|
|
||||||
|
float id = float(gl_InstanceID);
|
||||||
|
float sampleY = floor(id / dataTextureSize);
|
||||||
|
float sampleX = id - sampleY * dataTextureSize;
|
||||||
|
|
||||||
|
vec4 dataSampleCurrent = texture(tDataCurrent, vec2(sampleX, sampleY) / dataTextureSize);
|
||||||
|
vec4 dataSampleLast = texture(tDataLast, vec2(sampleX, sampleY) / dataTextureSize);
|
||||||
|
vec2 offset = dataSampleCurrent.xy;
|
||||||
|
|
||||||
|
float isCarrying = float(int(dataSampleCurrent.w) & 1);
|
||||||
|
float wasCarrying = float(int(dataSampleLast.w) & 1);
|
||||||
|
float isCellCleared = float(wasCarrying == 0. && isCarrying == 1.);
|
||||||
|
|
||||||
|
float storage = float(int(dataSampleCurrent.w) >> 1);
|
||||||
|
|
||||||
|
vIsCarryingFood = isCarrying;
|
||||||
|
vScentFactor = storage / 1000000.;
|
||||||
|
vIsCellCleared = isCellCleared;
|
||||||
|
|
||||||
|
gl_Position = vec4(
|
||||||
|
(position.xy * cellSize * 0.5 + floor(offset * worldSize) / worldSize + cellSize * 0.5) * 2. - 1.,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
24
src/shaders/screenWorld.frag
Normal file
24
src/shaders/screenWorld.frag
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
precision highp float;
|
||||||
|
precision highp int;
|
||||||
|
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
uniform sampler2D map;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 value = clamp(texture2D(map, vUv), 0., 1.);
|
||||||
|
vec4 bg = vec4(1);
|
||||||
|
|
||||||
|
//bg = mix(bg, vec4(0, 0, 1, 1), value.a * 0.7);
|
||||||
|
//bg = mix(bg, vec4(1, 0, 0, 1), value.b * 0.7);
|
||||||
|
|
||||||
|
if (value.r == 1.) {
|
||||||
|
bg = vec4(1, 0, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.g == 1.) {
|
||||||
|
bg = vec4(0.1, 0.1, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_FragColor = bg;
|
||||||
|
}
|
||||||
10
src/shaders/screenWorld.vert
Normal file
10
src/shaders/screenWorld.vert
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
precision highp float;
|
||||||
|
precision highp int;
|
||||||
|
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1);
|
||||||
|
}
|
||||||
29
src/shaders/world.frag
Normal file
29
src/shaders/world.frag
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
precision highp float;
|
||||||
|
precision highp int;
|
||||||
|
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
uniform sampler2D tLastState;
|
||||||
|
uniform sampler2D tDiscreteAnts;
|
||||||
|
uniform vec4 pointerData;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 lastState = texture2D(tLastState, vUv);
|
||||||
|
vec3 discreteAnts = texture2D(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.;
|
||||||
|
|
||||||
|
if (discreteAnts.z == 1.) {
|
||||||
|
isFood = 0.;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (distance(pointerData.zw, vUv) < 0.02) {
|
||||||
|
isFood = max(isFood, pointerData.x);
|
||||||
|
isHome = max(isHome, pointerData.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_FragColor = vec4(isFood, isHome, scentToHome, scentToFood);
|
||||||
|
}
|
||||||
12
src/shaders/world.vert
Normal file
12
src/shaders/world.vert
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
precision highp float;
|
||||||
|
precision highp int;
|
||||||
|
|
||||||
|
attribute vec3 position;
|
||||||
|
attribute vec2 uv;
|
||||||
|
|
||||||
|
varying vec2 vUv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = vec4(position, 1.0);
|
||||||
|
}
|
||||||
28
src/shaders/worldBlur.frag
Normal file
28
src/shaders/worldBlur.frag
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#version 300 es
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
precision highp int;
|
||||||
|
|
||||||
|
in vec2 vUv;
|
||||||
|
|
||||||
|
uniform sampler2D tWorld;
|
||||||
|
|
||||||
|
out highp vec4 FragColor;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
14
src/shaders/worldBlur.vert
Normal file
14
src/shaders/worldBlur.vert
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
#version 300 es
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
precision highp int;
|
||||||
|
|
||||||
|
in vec3 position;
|
||||||
|
in vec2 uv;
|
||||||
|
|
||||||
|
out vec2 vUv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
|
gl_Position = vec4(position, 1.0);
|
||||||
|
}
|
||||||
28
src/utils/FullScreenTriangleGeometry.ts
Normal file
28
src/utils/FullScreenTriangleGeometry.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import * as THREE from "three";
|
||||||
|
|
||||||
|
const positionBuffer = new Float32Array([
|
||||||
|
-1, 3, 0,
|
||||||
|
-1, -1, 0,
|
||||||
|
3, -1, 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const uvBuffer = new Float32Array([
|
||||||
|
0, 2,
|
||||||
|
0, 0,
|
||||||
|
2, 0
|
||||||
|
]);
|
||||||
|
|
||||||
|
export default class FullScreenTriangleGeometry extends THREE.BufferGeometry {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.setAttribute(
|
||||||
|
'position',
|
||||||
|
new THREE.BufferAttribute(positionBuffer, 3)
|
||||||
|
);
|
||||||
|
this.setAttribute(
|
||||||
|
'uv',
|
||||||
|
new THREE.BufferAttribute(uvBuffer, 2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
tsconfig.json
Normal file
21
tsconfig.json
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "es6",
|
||||||
|
"target": "es6",
|
||||||
|
"lib": [
|
||||||
|
"es2019",
|
||||||
|
"dom"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"alwaysStrict": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"strictBindCallApply": true,
|
||||||
|
"strictFunctionTypes": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
36
webpack.config.js
Normal file
36
webpack.config.js
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
const path = require('path');
|
||||||
|
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
|
||||||
|
module.exports = [{
|
||||||
|
entry: './src/App.ts',
|
||||||
|
output: {
|
||||||
|
filename: './js/main.js',
|
||||||
|
path: path.resolve(__dirname, 'build')
|
||||||
|
},
|
||||||
|
devtool: 'inline-source-map',
|
||||||
|
plugins: [
|
||||||
|
new CleanWebpackPlugin(),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
filename: 'index.html',
|
||||||
|
template: './src/index.html',
|
||||||
|
minify: false
|
||||||
|
})
|
||||||
|
],
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.ts|.tsx$/,
|
||||||
|
loader: 'ts-loader',
|
||||||
|
options: {configFile: 'tsconfig.json'},
|
||||||
|
exclude: /node_modules/
|
||||||
|
}, {
|
||||||
|
test: /\.(frag|vert)$/i,
|
||||||
|
use: 'raw-loader'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.js']
|
||||||
|
}
|
||||||
|
}];
|
||||||
Loading…
Reference in a new issue