217 lines
6.7 KiB
TypeScript
217 lines
6.7 KiB
TypeScript
import * as THREE from "three";
|
|
import Config from "../Config";
|
|
import type Renderer from "../Renderer";
|
|
import fragmentShaderAnts from "../shaders/ants.frag";
|
|
import vertexShaderAnts from "../shaders/ants.vert";
|
|
import fragmentShaderGround from "../shaders/screenWorld.frag";
|
|
import vertexShaderGround from "../shaders/screenWorld.vert";
|
|
import AbstractScene from "./AbstractScene";
|
|
|
|
enum PointerState {
|
|
None,
|
|
Food,
|
|
Home,
|
|
Obstacle,
|
|
Erase,
|
|
}
|
|
|
|
export default class ScreenScene extends AbstractScene {
|
|
public readonly camera: THREE.OrthographicCamera;
|
|
public readonly material: THREE.ShaderMaterial;
|
|
public ants!: THREE.InstancedMesh;
|
|
public readonly groundMaterial: THREE.ShaderMaterial;
|
|
public readonly pointerPosition: THREE.Vector2 = new THREE.Vector2();
|
|
public drawMode: PointerState = PointerState.None;
|
|
private cameraZoomLinear: number = 0;
|
|
private isPointerDown: boolean = false;
|
|
public renderWidth: number = 1;
|
|
public renderHeight: number = 1;
|
|
|
|
constructor(renderer: Renderer) {
|
|
super(renderer);
|
|
|
|
const ground = new THREE.Mesh(
|
|
new THREE.PlaneGeometry(1, 1),
|
|
new THREE.ShaderMaterial({
|
|
uniforms: {
|
|
map: {
|
|
value: this.renderer.resources.worldRenderTarget
|
|
.texture,
|
|
},
|
|
},
|
|
vertexShader: vertexShaderGround,
|
|
fragmentShader: fragmentShaderGround,
|
|
defines: this.renderer.getCommonMaterialDefines(),
|
|
glslVersion: THREE.GLSL3,
|
|
}),
|
|
);
|
|
|
|
this.groundMaterial = ground.material;
|
|
|
|
this.add(ground);
|
|
|
|
const antTexture = new THREE.TextureLoader().load("textures/ant.png");
|
|
const foodTexture = new THREE.TextureLoader().load("textures/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.antsComputeTarget0
|
|
.textures[0],
|
|
},
|
|
tAnt: { value: antTexture },
|
|
tFood: { value: foodTexture },
|
|
},
|
|
vertexShader: vertexShaderAnts,
|
|
fragmentShader: fragmentShaderAnts,
|
|
transparent: true,
|
|
});
|
|
|
|
this.createInstancedAntsMesh();
|
|
|
|
this.camera = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5);
|
|
|
|
this.add(this.camera);
|
|
|
|
this.camera.position.z = 12;
|
|
|
|
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) => {
|
|
this.isPointerDown = true;
|
|
|
|
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;
|
|
if (uv) this.pointerPosition.copy(uv);
|
|
}
|
|
});
|
|
|
|
this.renderer.canvas.addEventListener("pointermove", (e) => {
|
|
if (this.isPointerDown) {
|
|
const dx = e.movementX;
|
|
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;
|
|
if (uv) this.pointerPosition.copy(uv);
|
|
}
|
|
});
|
|
|
|
this.renderer.canvas.addEventListener("pointerup", () => {
|
|
this.isPointerDown = false;
|
|
});
|
|
|
|
this.renderer.canvas.addEventListener("pointerleave", () => {
|
|
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;
|
|
}
|
|
case "KeyR": {
|
|
this.drawMode = PointerState.Erase;
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
window.addEventListener("keyup", () => {
|
|
this.drawMode = PointerState.None;
|
|
});
|
|
}
|
|
|
|
private updateCameraZoom() {
|
|
this.camera.zoom = 2 ** this.cameraZoomLinear;
|
|
this.camera.updateProjectionMatrix();
|
|
}
|
|
|
|
private createInstancedAntsMesh() {
|
|
if (this.ants) {
|
|
this.remove(this.ants);
|
|
this.ants.dispose();
|
|
}
|
|
|
|
const scale = 8 / Config.worldSize;
|
|
|
|
const ants = new THREE.InstancedMesh(
|
|
new THREE.PlaneGeometry(scale, scale),
|
|
this.material,
|
|
this.renderer.resources.antsComputeTarget0.width *
|
|
this.renderer.resources.antsComputeTarget0.height,
|
|
);
|
|
|
|
ants.position.x = ants.position.y = -0.5;
|
|
|
|
this.add(ants);
|
|
|
|
this.ants = ants;
|
|
}
|
|
|
|
public recompileMaterials() {
|
|
this.groundMaterial.defines = this.renderer.getCommonMaterialDefines();
|
|
this.groundMaterial.needsUpdate = true;
|
|
this.createInstancedAntsMesh();
|
|
}
|
|
|
|
public resize(width: number, height: number) {
|
|
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.renderWidth = width;
|
|
this.renderHeight = height;
|
|
}
|
|
|
|
public update() {}
|
|
}
|