Interaction improvements and minor fixes
This commit is contained in:
parent
fb772db717
commit
27c835b8e5
16 changed files with 262 additions and 95 deletions
|
|
@ -25,7 +25,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config ./webpack.config.js --mode=production",
|
"build": "webpack --config ./webpack.config.js --mode=production",
|
||||||
"dev": "webpack serve --config ./webpack.config.js --mode=development",
|
"dev": "webpack serve --config ./webpack.config.js --mode=development",
|
||||||
"gh-pages": "push-dir --dir=build --branch=gh-pages"
|
"gh-pages": "npm run build && push-dir --dir=build --branch=gh-pages"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
|
|
|
||||||
14
src/App.ts
14
src/App.ts
|
|
@ -6,6 +6,7 @@ import AntsDiscretizeScene from "./scenes/AntsDiscretizeScene";
|
||||||
import WorldBlurScene from "./scenes/WorldBlurScene";
|
import WorldBlurScene from "./scenes/WorldBlurScene";
|
||||||
import Config from "./Config";
|
import Config from "./Config";
|
||||||
import GUI from "./GUI";
|
import GUI from "./GUI";
|
||||||
|
import DrawScene from "./scenes/DrawScene";
|
||||||
|
|
||||||
export interface SceneCollection {
|
export interface SceneCollection {
|
||||||
ants: AntsComputeScene;
|
ants: AntsComputeScene;
|
||||||
|
|
@ -13,6 +14,7 @@ export interface SceneCollection {
|
||||||
worldBlur: WorldBlurScene;
|
worldBlur: WorldBlurScene;
|
||||||
discretize: AntsDiscretizeScene;
|
discretize: AntsDiscretizeScene;
|
||||||
screen: ScreenScene;
|
screen: ScreenScene;
|
||||||
|
draw: DrawScene;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new class App {
|
export default new class App {
|
||||||
|
|
@ -22,6 +24,7 @@ export default new class App {
|
||||||
private renderLoop = (deltaTime: number): void => this.render(deltaTime);
|
private renderLoop = (deltaTime: number): void => this.render(deltaTime);
|
||||||
private simInterval: NodeJS.Timer;
|
private simInterval: NodeJS.Timer;
|
||||||
private simulationStepsPerSecond: number = 0;
|
private simulationStepsPerSecond: number = 0;
|
||||||
|
private simStarted: boolean = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.initScenes();
|
this.initScenes();
|
||||||
|
|
@ -47,6 +50,8 @@ export default new class App {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.simulationStep();
|
this.simulationStep();
|
||||||
|
|
||||||
|
this.simStarted = true;
|
||||||
}, 1000 / this.simulationStepsPerSecond);
|
}, 1000 / this.simulationStepsPerSecond);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,12 +62,13 @@ export default new class App {
|
||||||
worldBlur: new WorldBlurScene(this.renderer),
|
worldBlur: new WorldBlurScene(this.renderer),
|
||||||
discretize: new AntsDiscretizeScene(this.renderer),
|
discretize: new AntsDiscretizeScene(this.renderer),
|
||||||
screen: new ScreenScene(this.renderer),
|
screen: new ScreenScene(this.renderer),
|
||||||
|
draw: new DrawScene(this.renderer)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private resize() {
|
private resize() {
|
||||||
const width = window.innerWidth;
|
const width = window.innerWidth * window.devicePixelRatio;
|
||||||
const height = window.innerHeight;
|
const height = window.innerHeight * window.devicePixelRatio;
|
||||||
|
|
||||||
this.renderer.resizeCanvas(width, height);
|
this.renderer.resizeCanvas(width, height);
|
||||||
|
|
||||||
|
|
@ -82,6 +88,10 @@ export default new class App {
|
||||||
private render(deltaTime: number) {
|
private render(deltaTime: number) {
|
||||||
requestAnimationFrame(this.renderLoop);
|
requestAnimationFrame(this.renderLoop);
|
||||||
|
|
||||||
|
if (!this.simStarted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.renderer.renderToScreen(this.scenes);
|
this.renderer.renderToScreen(this.scenes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,5 +8,6 @@ export default {
|
||||||
scentMaxStorage: 1e6,
|
scentMaxStorage: 1e6,
|
||||||
scentPerMarker: 1000,
|
scentPerMarker: 1000,
|
||||||
antSpeed: 1,
|
antSpeed: 1,
|
||||||
antRotationAngle: Math.PI / 50
|
antRotationAngle: Math.PI / 50,
|
||||||
|
brushRadius: 20,
|
||||||
};
|
};
|
||||||
|
|
@ -15,6 +15,8 @@ export default class GUI {
|
||||||
|
|
||||||
const controlsFolder = this.gui.addFolder('Controls');
|
const controlsFolder = this.gui.addFolder('Controls');
|
||||||
|
|
||||||
|
controlsFolder.add(Config, 'brushRadius', 1, 100);
|
||||||
|
|
||||||
simFolder.open();
|
simFolder.open();
|
||||||
controlsFolder.open();
|
controlsFolder.open();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import Config from "./Config";
|
||||||
|
|
||||||
interface Resources {
|
interface Resources {
|
||||||
worldRenderTarget: THREE.WebGLRenderTarget;
|
worldRenderTarget: THREE.WebGLRenderTarget;
|
||||||
|
worldRenderTargetCopy: THREE.WebGLRenderTarget;
|
||||||
worldBlurredRenderTarget: THREE.WebGLRenderTarget;
|
worldBlurredRenderTarget: THREE.WebGLRenderTarget;
|
||||||
antsDataRenderTarget0: THREE.WebGLRenderTarget;
|
antsDataRenderTarget0: THREE.WebGLRenderTarget;
|
||||||
antsDataRenderTarget1: THREE.WebGLRenderTarget;
|
antsDataRenderTarget1: THREE.WebGLRenderTarget;
|
||||||
|
|
@ -31,12 +32,19 @@ export default class Renderer {
|
||||||
magFilter: THREE.LinearFilter,
|
magFilter: THREE.LinearFilter,
|
||||||
minFilter: THREE.LinearFilter,
|
minFilter: THREE.LinearFilter,
|
||||||
}),
|
}),
|
||||||
|
worldRenderTargetCopy: new THREE.WebGLRenderTarget(Config.worldSize, Config.worldSize, {
|
||||||
|
format: THREE.RGBAFormat,
|
||||||
|
type: THREE.FloatType,
|
||||||
|
depthBuffer: false,
|
||||||
|
magFilter: THREE.NearestFilter,
|
||||||
|
minFilter: THREE.LinearFilter,
|
||||||
|
}),
|
||||||
worldBlurredRenderTarget: new THREE.WebGLRenderTarget(Config.worldSize, Config.worldSize, {
|
worldBlurredRenderTarget: new THREE.WebGLRenderTarget(Config.worldSize, Config.worldSize, {
|
||||||
format: THREE.RGBAFormat,
|
format: THREE.RGBAFormat,
|
||||||
type: THREE.FloatType,
|
type: THREE.FloatType,
|
||||||
depthBuffer: false,
|
depthBuffer: false,
|
||||||
magFilter: THREE.LinearFilter,
|
magFilter: THREE.NearestFilter,
|
||||||
minFilter: THREE.LinearFilter,
|
minFilter: THREE.NearestFilter,
|
||||||
}),
|
}),
|
||||||
antsDataRenderTarget0: new THREE.WebGLRenderTarget(antTextureSize, antTextureSize, {
|
antsDataRenderTarget0: new THREE.WebGLRenderTarget(antTextureSize, antTextureSize, {
|
||||||
format: THREE.RGBAFormat,
|
format: THREE.RGBAFormat,
|
||||||
|
|
@ -65,39 +73,47 @@ export default class Renderer {
|
||||||
public renderSimulation(scenes: SceneCollection) {
|
public renderSimulation(scenes: SceneCollection) {
|
||||||
const [antsComputeSource, antsComputeTarget] = scenes.ants.getRenderTargets();
|
const [antsComputeSource, antsComputeTarget] = scenes.ants.getRenderTargets();
|
||||||
|
|
||||||
|
this.renderer.setViewport(0, 0, scenes.worldBlur.renderWidth, scenes.worldBlur.renderHeight);
|
||||||
|
|
||||||
|
this.renderer.setRenderTarget(this.resources.worldBlurredRenderTarget);
|
||||||
|
scenes.worldBlur.material.uniforms.tWorld.value = this.resources.worldRenderTarget.texture;
|
||||||
|
this.renderer.render(scenes.worldBlur, scenes.worldBlur.camera);
|
||||||
|
|
||||||
this.renderer.setViewport(0, 0, scenes.ants.renderWidth, scenes.ants.renderHeight);
|
this.renderer.setViewport(0, 0, scenes.ants.renderWidth, scenes.ants.renderHeight);
|
||||||
|
|
||||||
this.renderer.setRenderTarget(antsComputeTarget);
|
this.renderer.setRenderTarget(antsComputeTarget);
|
||||||
scenes.ants.material.uniforms.tLastState.value = antsComputeSource.texture;
|
scenes.ants.material.uniforms.tLastState.value = antsComputeSource.texture;
|
||||||
scenes.ants.material.uniforms.tWorld.value = scenes.worldBlur.getRenderTarget().texture;
|
scenes.ants.material.uniforms.tWorld.value = this.resources.worldBlurredRenderTarget.texture;
|
||||||
this.renderer.render(scenes.ants, scenes.ants.camera);
|
this.renderer.render(scenes.ants, scenes.ants.camera);
|
||||||
|
|
||||||
this.renderer.setViewport(0, 0, scenes.discretize.renderWidth, scenes.discretize.renderHeight);
|
this.renderer.setViewport(0, 0, scenes.discretize.renderWidth, scenes.discretize.renderHeight);
|
||||||
|
|
||||||
this.renderer.setRenderTarget(scenes.discretize.getRenderTarget());
|
this.renderer.setRenderTarget(this.resources.antsDiscreteRenderTarget);
|
||||||
scenes.discretize.material.uniforms.tDataCurrent.value = antsComputeTarget.texture;
|
scenes.discretize.material.uniforms.tDataCurrent.value = antsComputeTarget.texture;
|
||||||
scenes.discretize.material.uniforms.tDataLast.value = antsComputeSource.texture;
|
scenes.discretize.material.uniforms.tDataLast.value = antsComputeSource.texture;
|
||||||
this.renderer.render(scenes.discretize, scenes.discretize.camera);
|
this.renderer.render(scenes.discretize, scenes.discretize.camera);
|
||||||
|
|
||||||
this.renderer.setViewport(0, 0, scenes.world.renderWidth, scenes.world.renderHeight);
|
this.renderer.setViewport(0, 0, scenes.world.renderWidth, scenes.world.renderHeight);
|
||||||
|
|
||||||
this.renderer.setRenderTarget(scenes.world.getRenderTarget());
|
this.renderer.setRenderTarget(this.resources.worldRenderTarget);
|
||||||
scenes.world.material.uniforms.tLastState.value = scenes.worldBlur.getRenderTarget().texture;
|
scenes.world.material.uniforms.tLastState.value = this.resources.worldBlurredRenderTarget.texture;
|
||||||
scenes.world.material.uniforms.tDiscreteAnts.value = scenes.discretize.getRenderTarget().texture;
|
scenes.world.material.uniforms.tDiscreteAnts.value = this.resources.antsDiscreteRenderTarget.texture;
|
||||||
scenes.world.material.uniforms.pointerData.value = scenes.screen.getPointerData();
|
|
||||||
this.renderer.render(scenes.world, scenes.world.camera);
|
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 = scenes.world.getRenderTarget().texture;
|
|
||||||
this.renderer.render(scenes.worldBlur, scenes.worldBlur.camera);
|
|
||||||
|
|
||||||
scenes.screen.material.uniforms.tData.value = antsComputeTarget.texture;
|
scenes.screen.material.uniforms.tData.value = antsComputeTarget.texture;
|
||||||
scenes.screen.groundMaterial.uniforms.map.value = scenes.worldBlur.getRenderTarget().texture;
|
scenes.screen.groundMaterial.uniforms.map.value = this.resources.worldRenderTargetCopy.texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderToScreen(scenes: SceneCollection) {
|
public renderToScreen(scenes: SceneCollection) {
|
||||||
|
this.renderer.setViewport(0, 0, scenes.draw.renderWidth, scenes.draw.renderHeight);
|
||||||
|
this.renderer.setRenderTarget(this.resources.worldRenderTargetCopy);
|
||||||
|
scenes.draw.material.uniforms.tWorld.value = this.resources.worldRenderTarget.texture;
|
||||||
|
scenes.draw.material.uniforms.pointerPosition.value = scenes.screen.pointerPosition;
|
||||||
|
scenes.draw.material.uniforms.drawMode.value = scenes.screen.drawMode;
|
||||||
|
scenes.draw.material.uniforms.brushRadius.value = Config.brushRadius;
|
||||||
|
this.renderer.render(scenes.draw, scenes.draw.camera);
|
||||||
|
this.renderer.copyFramebufferToTexture(new THREE.Vector2(), this.resources.worldRenderTarget.texture);
|
||||||
|
|
||||||
this.renderer.setViewport(0, 0, scenes.screen.renderWidth, scenes.screen.renderHeight);
|
this.renderer.setViewport(0, 0, scenes.screen.renderWidth, scenes.screen.renderHeight);
|
||||||
this.renderer.setRenderTarget(null);
|
this.renderer.setRenderTarget(null);
|
||||||
this.renderer.render(scenes.screen, scenes.screen.camera);
|
this.renderer.render(scenes.screen, scenes.screen.camera);
|
||||||
|
|
|
||||||
49
src/scenes/DrawScene.ts
Normal file
49
src/scenes/DrawScene.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import Renderer from "../Renderer";
|
||||||
|
import AbstractScene from "./AbstractScene";
|
||||||
|
import FullScreenTriangleGeometry from "../utils/FullScreenTriangleGeometry";
|
||||||
|
import fragmentShader from '../shaders/draw.frag';
|
||||||
|
import vertexShader from '../shaders/draw.vert';
|
||||||
|
import {WebGLRenderTarget} from "three";
|
||||||
|
|
||||||
|
export default class DrawScene extends AbstractScene {
|
||||||
|
public readonly camera: THREE.OrthographicCamera = new THREE.OrthographicCamera();
|
||||||
|
public readonly material: THREE.RawShaderMaterial;
|
||||||
|
|
||||||
|
constructor(renderer: Renderer) {
|
||||||
|
super(renderer);
|
||||||
|
|
||||||
|
const geometry = new FullScreenTriangleGeometry();
|
||||||
|
const material = new THREE.RawShaderMaterial({
|
||||||
|
uniforms: {
|
||||||
|
tWorld: {value: null},
|
||||||
|
pointerPosition: {value: new THREE.Vector2()},
|
||||||
|
drawMode: {value: 0},
|
||||||
|
brushRadius: {value: 0},
|
||||||
|
},
|
||||||
|
vertexShader,
|
||||||
|
fragmentShader,
|
||||||
|
defines: this.renderer.getCommonMaterialDefines(),
|
||||||
|
glslVersion: THREE.GLSL3
|
||||||
|
});
|
||||||
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
this.add(mesh);
|
||||||
|
|
||||||
|
this.material = material;
|
||||||
|
|
||||||
|
this.renderWidth = this.renderer.resources.worldRenderTarget.width;
|
||||||
|
this.renderHeight = this.renderer.resources.worldRenderTarget.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRenderTarget(): WebGLRenderTarget {
|
||||||
|
return this.renderer.resources.worldRenderTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public resize(width: number, height: number) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(deltaTime: number) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,25 +8,29 @@ import fragmentShaderGround from "../shaders/screenWorld.frag";
|
||||||
|
|
||||||
enum PointerState {
|
enum PointerState {
|
||||||
None,
|
None,
|
||||||
LMB,
|
Food,
|
||||||
RMB
|
Home,
|
||||||
|
Obstacle
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ScreenScene extends AbstractScene {
|
export default class ScreenScene extends AbstractScene {
|
||||||
public readonly camera: THREE.PerspectiveCamera;
|
public readonly camera: THREE.OrthographicCamera;
|
||||||
public readonly material: THREE.ShaderMaterial;
|
public readonly material: THREE.ShaderMaterial;
|
||||||
public readonly groundMaterial: THREE.ShaderMaterial;
|
public readonly groundMaterial: THREE.ShaderMaterial;
|
||||||
public readonly pointerPosition: THREE.Vector2 = new THREE.Vector2();
|
public readonly pointerPosition: THREE.Vector2 = new THREE.Vector2();
|
||||||
public pointerState: PointerState = PointerState.None;
|
public drawMode: PointerState = PointerState.None;
|
||||||
|
private cameraZoomLinear: number = 0;
|
||||||
|
private isPointerDown: boolean = false;
|
||||||
|
|
||||||
constructor(renderer: Renderer) {
|
constructor(renderer: Renderer) {
|
||||||
super(renderer);
|
super(renderer);
|
||||||
|
|
||||||
const ground = new THREE.Mesh(
|
const ground = new THREE.Mesh(
|
||||||
new THREE.PlaneBufferGeometry(10, 10),
|
new THREE.PlaneBufferGeometry(1, 1),
|
||||||
new THREE.ShaderMaterial({
|
new THREE.ShaderMaterial({
|
||||||
uniforms: {
|
uniforms: {
|
||||||
map: {value: this.renderer.resources.worldRenderTarget.texture},
|
map: {value: this.renderer.resources.worldRenderTarget.texture},
|
||||||
|
tDiscreteAnts: {value: this.renderer.resources.antsDiscreteRenderTarget.texture},
|
||||||
},
|
},
|
||||||
vertexShader: vertexShaderGround,
|
vertexShader: vertexShaderGround,
|
||||||
fragmentShader: fragmentShaderGround,
|
fragmentShader: fragmentShaderGround,
|
||||||
|
|
@ -37,8 +41,8 @@ export default class ScreenScene extends AbstractScene {
|
||||||
|
|
||||||
this.groundMaterial = ground.material;
|
this.groundMaterial = ground.material;
|
||||||
|
|
||||||
ground.position.x += 5;
|
//ground.position.x = 0.5;
|
||||||
ground.position.y += 5;
|
//ground.position.y = 0.5;
|
||||||
|
|
||||||
this.add(ground);
|
this.add(ground);
|
||||||
|
|
||||||
|
|
@ -60,28 +64,31 @@ export default class ScreenScene extends AbstractScene {
|
||||||
});
|
});
|
||||||
|
|
||||||
const ants = new THREE.InstancedMesh(
|
const ants = new THREE.InstancedMesh(
|
||||||
new THREE.PlaneBufferGeometry(0.15, 0.15),
|
new THREE.PlaneBufferGeometry(0.015, 0.015),
|
||||||
this.material,
|
this.material,
|
||||||
this.renderer.resources.antsDataRenderTarget0.width * this.renderer.resources.antsDataRenderTarget0.height
|
this.renderer.resources.antsDataRenderTarget0.width * this.renderer.resources.antsDataRenderTarget0.height
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ants.position.x = ants.position.y = -0.5;
|
||||||
|
|
||||||
this.add(ants);
|
this.add(ants);
|
||||||
|
|
||||||
this.camera = new THREE.PerspectiveCamera(50, 1, 0.1, 10000);
|
this.camera = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5);
|
||||||
|
|
||||||
this.add(this.camera);
|
this.add(this.camera);
|
||||||
|
|
||||||
this.camera.position.z = 12;
|
this.camera.position.z = 12;
|
||||||
this.camera.position.x = this.camera.position.y = 5;
|
|
||||||
|
|
||||||
const raycastVector = new THREE.Vector2(0, 0);
|
const raycastVector = new THREE.Vector2(0, 0);
|
||||||
const raycaster = new THREE.Raycaster();
|
const raycaster = new THREE.Raycaster();
|
||||||
|
|
||||||
this.renderer.canvas.addEventListener('contextmenu', (e) => {
|
this.renderer.canvas.addEventListener('contextmenu', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.renderer.canvas.addEventListener('pointerdown', (e) => {
|
this.renderer.canvas.addEventListener('pointerdown', e => {
|
||||||
|
this.isPointerDown = true;
|
||||||
|
|
||||||
raycastVector.x = (e.clientX / window.innerWidth) * 2 - 1;
|
raycastVector.x = (e.clientX / window.innerWidth) * 2 - 1;
|
||||||
raycastVector.y = -(e.clientY / window.innerHeight) * 2 + 1;
|
raycastVector.y = -(e.clientY / window.innerHeight) * 2 + 1;
|
||||||
|
|
||||||
|
|
@ -92,45 +99,80 @@ export default class ScreenScene extends AbstractScene {
|
||||||
if (intersects.length > 0) {
|
if (intersects.length > 0) {
|
||||||
const uv = intersects[0].uv;
|
const uv = intersects[0].uv;
|
||||||
this.pointerPosition.copy(uv);
|
this.pointerPosition.copy(uv);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (e.button === 0) {
|
this.renderer.canvas.addEventListener('pointermove', e => {
|
||||||
this.pointerState = PointerState.LMB;
|
if (this.isPointerDown) {
|
||||||
} else if (e.button === 2) {
|
const dx = e.movementX;
|
||||||
this.pointerState = PointerState.RMB;
|
const dy = e.movementY;
|
||||||
|
|
||||||
|
this.camera.position.x -= dx / window.innerHeight / this.camera.zoom;
|
||||||
|
this.camera.position.y += dy / window.innerHeight / this.camera.zoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.isPointerDown = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.renderer.canvas.addEventListener('pointerleave', e => {
|
||||||
|
this.isPointerDown = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.renderer.canvas.addEventListener('wheel', e => {
|
||||||
|
this.cameraZoomLinear -= e.deltaY * 0.001;
|
||||||
|
|
||||||
|
this.updateCameraZoom();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('keydown', e => {
|
||||||
|
switch (e.code) {
|
||||||
|
case 'KeyQ': {
|
||||||
|
this.drawMode = PointerState.Home;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'KeyW': {
|
||||||
|
this.drawMode = PointerState.Food;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'KeyE': {
|
||||||
|
this.drawMode = PointerState.Obstacle;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.renderer.canvas.addEventListener('pointermove', (e) => {
|
window.addEventListener('keyup', e => {
|
||||||
raycastVector.x = (e.clientX / window.innerWidth) * 2 - 1;
|
this.drawMode = PointerState.None;
|
||||||
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 {
|
private updateCameraZoom() {
|
||||||
return new THREE.Vector4(
|
this.camera.zoom = 2 ** this.cameraZoomLinear;
|
||||||
+(this.pointerState === PointerState.LMB),
|
this.camera.updateProjectionMatrix();
|
||||||
+(this.pointerState === PointerState.RMB),
|
|
||||||
this.pointerPosition.x,
|
|
||||||
this.pointerPosition.y
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public resize(width: number, height: number) {
|
public resize(width: number, height: number) {
|
||||||
this.camera.aspect = width / height;
|
const aspect = width / height;
|
||||||
|
|
||||||
|
this.camera.left = -0.5 * aspect;
|
||||||
|
this.camera.right = 0.5 * aspect;
|
||||||
|
this.camera.top = 0.5;
|
||||||
|
this.camera.bottom = -0.5;
|
||||||
|
|
||||||
this.camera.updateProjectionMatrix();
|
this.camera.updateProjectionMatrix();
|
||||||
|
|
||||||
this.renderWidth = width;
|
this.renderWidth = width;
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,8 @@ export default class WorldComputeScene extends AbstractScene {
|
||||||
const geometry = new FullScreenTriangleGeometry();
|
const geometry = new FullScreenTriangleGeometry();
|
||||||
const material = new THREE.RawShaderMaterial({
|
const material = new THREE.RawShaderMaterial({
|
||||||
uniforms: {
|
uniforms: {
|
||||||
tLastState: {value: this.renderer.resources.worldRenderTarget.texture},
|
tLastState: {value: null},
|
||||||
tDiscreteAnts: {value: this.renderer.resources.antsDiscreteRenderTarget.texture},
|
tDiscreteAnts: {value: null}
|
||||||
pointerData: {value: new THREE.Vector4()},
|
|
||||||
},
|
},
|
||||||
vertexShader,
|
vertexShader,
|
||||||
fragmentShader,
|
fragmentShader,
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ void main() {
|
||||||
|
|
||||||
vec4 dataSample = texture(tData, vec2(sampleX, sampleY) / dataTextureSize);
|
vec4 dataSample = texture(tData, vec2(sampleX, sampleY) / dataTextureSize);
|
||||||
|
|
||||||
vec2 offset = dataSample.xy * 10.;
|
vec2 offset = dataSample.xy;
|
||||||
vec2 rotatedPosition = rotate(position.xy, -dataSample.z + PI * 0.5);
|
vec2 rotatedPosition = rotate(position.xy, -dataSample.z + PI * 0.5);
|
||||||
|
|
||||||
vIsCarryingFood = float(int(dataSample.w) & 1);
|
vIsCarryingFood = float(int(dataSample.w) & 1);
|
||||||
|
|
|
||||||
|
|
@ -77,21 +77,6 @@ void main() {
|
||||||
storage = 0.;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCarrying == 0.) {
|
if (isCarrying == 0.) {
|
||||||
if (noise < 0.33) {
|
if (noise < 0.33) {
|
||||||
vec2 offset = vec2(cos(angle), sin(angle)) * sampleDistance;
|
vec2 offset = vec2(cos(angle), sin(angle)) * sampleDistance;
|
||||||
|
|
@ -181,6 +166,21 @@ void main() {
|
||||||
angle += PI * (noise - 0.5);
|
angle += PI * (noise - 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FragColor = vec4(
|
FragColor = vec4(
|
||||||
pos.x,
|
pos.x,
|
||||||
pos.y,
|
pos.y,
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ void main() {
|
||||||
vIsCellCleared = isCellCleared;
|
vIsCellCleared = isCellCleared;
|
||||||
|
|
||||||
gl_Position = vec4(
|
gl_Position = vec4(
|
||||||
(position.xy * cellSize * 0.1 + floor(offset * WORLD_SIZE) / WORLD_SIZE + cellSize * 0.5) * 2. - 1.,
|
(position.xy * cellSize * 0.01 + floor(offset * WORLD_SIZE) / WORLD_SIZE + cellSize * 0.5) * 2. - 1.,
|
||||||
0,
|
0,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
|
|
|
||||||
28
src/shaders/draw.frag
Normal file
28
src/shaders/draw.frag
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
precision highp float;
|
||||||
|
precision highp int;
|
||||||
|
|
||||||
|
in vec2 vUv;
|
||||||
|
|
||||||
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
uniform sampler2D tWorld;
|
||||||
|
uniform vec2 pointerPosition;
|
||||||
|
uniform float drawMode;
|
||||||
|
uniform float brushRadius;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 lastState = texture(tWorld, vUv);
|
||||||
|
|
||||||
|
float isFood = lastState.x;
|
||||||
|
float isHome = lastState.y;
|
||||||
|
|
||||||
|
if (distance(pointerPosition, vUv) < brushRadius / WORLD_SIZE) {
|
||||||
|
if (drawMode == 1.) {
|
||||||
|
isFood = 1.;
|
||||||
|
} else if (drawMode == 2.) {
|
||||||
|
isHome = 1.;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FragColor = vec4(isFood, isHome, lastState.zw);
|
||||||
|
}
|
||||||
12
src/shaders/draw.vert
Normal file
12
src/shaders/draw.vert
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
@ -6,21 +6,35 @@ in vec2 vUv;
|
||||||
out vec4 FragColor;
|
out vec4 FragColor;
|
||||||
|
|
||||||
uniform sampler2D map;
|
uniform sampler2D map;
|
||||||
|
uniform sampler2D tDiscreteAnts;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 value = clamp(texture(map, vUv), 0., 1.);
|
vec4 value = clamp(texture(map, vUv), 0., 1.);
|
||||||
vec3 bg = vec3(0.9);
|
|
||||||
|
|
||||||
bg = mix(bg, vec3(0.2, 0.2, 0.8), clamp(value.a, 0., 1.));
|
float isFood = value.r;
|
||||||
bg = mix(bg, vec3(0.8, 0.2, 0.2), clamp(value.b, 0., 1.));
|
float isHome = value.g;
|
||||||
|
float toFood = value.b;
|
||||||
|
float toHome = value.a;
|
||||||
|
|
||||||
if (value.r == 1.) {
|
// The part below doen't seem right.
|
||||||
bg = vec3(1, 0.2, 0.2);
|
// I could figure out a better way to make pheromone colors blend properly on white background :(
|
||||||
|
|
||||||
|
vec3 t = vec3(0.95, 0.2, 0.2) * toFood + vec3(0.2, 0.2, 0.95) * toHome;
|
||||||
|
float a = clamp(toHome + toFood, 0., 1.);
|
||||||
|
|
||||||
|
t /= a;
|
||||||
|
|
||||||
|
if (a == 0.) t = vec3(0);
|
||||||
|
|
||||||
|
vec3 color = mix(vec3(1, 1, 1), t, a * 0.7);
|
||||||
|
|
||||||
|
if (isFood == 1.) {
|
||||||
|
color = vec3(1, 0.1, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.g == 1.) {
|
if (isHome == 1.) {
|
||||||
bg = vec3(0.2, 0.2, 1);
|
color = vec3(0.1, 0.1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
FragColor = vec4(bg, 1);
|
FragColor = vec4(color, 1);
|
||||||
}
|
}
|
||||||
|
|
@ -7,7 +7,6 @@ out vec4 FragColor;
|
||||||
|
|
||||||
uniform sampler2D tLastState;
|
uniform sampler2D tLastState;
|
||||||
uniform sampler2D tDiscreteAnts;
|
uniform sampler2D tDiscreteAnts;
|
||||||
uniform vec4 pointerData;
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 lastState = texture(tLastState, vUv);
|
vec4 lastState = texture(tLastState, vUv);
|
||||||
|
|
@ -15,17 +14,12 @@ void main() {
|
||||||
|
|
||||||
float isFood = lastState.x;
|
float isFood = lastState.x;
|
||||||
float isHome = lastState.y;
|
float isHome = lastState.y;
|
||||||
float scentToHome = lastState.z + discreteAnts.x * 2.;
|
float scentToHome = min(10., lastState.z + discreteAnts.x * 2.);
|
||||||
float scentToFood = lastState.w + discreteAnts.y * 2.;
|
float scentToFood = min(10., lastState.w + discreteAnts.y * 2.);
|
||||||
|
|
||||||
if (discreteAnts.z == 1.) {
|
if (discreteAnts.z == 1.) {
|
||||||
isFood = 0.;
|
isFood = 0.;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (distance(pointerData.zw, vUv) < 0.02) {
|
|
||||||
isFood = max(isFood, pointerData.x);
|
|
||||||
isHome = max(isHome, pointerData.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
FragColor = vec4(isFood, isHome, scentToHome, scentToFood);
|
FragColor = vec4(isFood, isHome, scentToHome, scentToFood);
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 229 B After Width: | Height: | Size: 229 B |
Loading…
Reference in a new issue