import * as THREE from 'three' const global = (0,eval)("this") const scene = new THREE.Scene() const raycaster = new THREE.Raycaster() const camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.1, 1000) const renderer = new THREE.WebGLRenderer() renderer.setSize(window.innerWidth, window.innerHeight) renderer.setAnimationLoop(render) camera.position.set(5, -12, 10) camera.rotation.set((60 / 180) * Math.PI, 0, 0) const entityMaterial = new THREE.MeshToonMaterial({ color: 0xffffff }) const terrainMaterial = new THREE.MeshToonMaterial({ color: 0xffd700 }) const minimapCamera = new THREE.OrthographicCamera(-10, 10, 10, -10) const minimapRenderer = new THREE.WebGLRenderer() minimapRenderer.setSize(300, 300) minimapRenderer.setAnimationLoop(minimapRender) minimapCamera.position.set(10, 10, 10) const entities = {} const terrains = {} const geometry = new THREE.PlaneGeometry(0, 0) const material = new THREE.MeshToonMaterial({ color: 0x115011 }) const ground = new THREE.Mesh(geometry, material) scene.add(ground) const ambientLight = new THREE.AmbientLight(0x404040) scene.add(ambientLight) const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5) directionalLight.position.set(-0.3, 0.1, 1) directionalLight.power = 3000 scene.add(directionalLight) global.THREE = THREE global.renderer = renderer global.camera = camera global.scene = scene const keysDown = {} function render() { cameraMovement() renderer.render(scene, camera) } function minimapRender() { minimapRenderer.render(scene, minimapCamera) } const cameraSpeed = 0.1 function cameraMovement() { if (keysDown.ArrowLeft) { camera.position.x -= cameraSpeed } else if (keysDown.ArrowRight) { camera.position.x += cameraSpeed } if (keysDown.ArrowUp) { camera.position.y += cameraSpeed } else if (keysDown.ArrowDown) { camera.position.y -= cameraSpeed } if (keysDown.Space) { camera.position.set(entities['1'].position.x, entities['1'].position.y - 17, 10) } } var websocket = null global.websocket = null var timerId = null function connectWebSocket() { websocket = new WebSocket('ws://127.0.0.1:1280/ws') global.websocket = websocket websocket.onerror = () => websocket.close() websocket.onopen = () => { document.getElementById('connection').innerHTML = 'open' clearInterval(timerId) } websocket.onclose = () => { websocket = null document.getElementById('connection').innerHTML = 'closed' timerId = setInterval(() => { if (websocket == null) { connectWebSocket() } }, 2000) } websocket.onmessage = (event) => { let state = JSON.parse(event.data) if (state.width != null && state.height != null && (ground.geometry.attributes.width != state.width || ground.geometry.attributes.height != state.height)) { ground.geometry = new THREE.PlaneGeometry(state.width / 100, state.height / 100) ground.position.set(state.width / 200, state.height / 200, 0) } for (const e of state.entities ?? []) { let entity if (e.id in entities) { entity = entities[e.id] } else { entity = new THREE.Mesh(new THREE.SphereGeometry(e.radius / 100), entityMaterial) entity.userData.type = 'entity' entity.userData.id = e.id scene.add(entity) entities[e.id] = entity } entity.position.set(e.position.x / 100, e.position.y / 100, e.radius / 100) } for (const t of state.terrains ?? []) { let terrain if (t.id in terrains) { terrain = terrains[t.id] } else { const vertices = t.relativeVertices const shape = new THREE.Shape() shape.moveTo(vertices.at(0).x / 100, vertices.at(0).y / 100) vertices.slice(1).forEach((v) => shape.lineTo(v.x / 100, v.y / 100)) terrain = new THREE.Mesh(new THREE.ExtrudeGeometry(shape, { bevelEnabled: false, depth: 0.5 }), terrainMaterial) terrain.userData.type = 'terrain' terrain.userData.id = t.id scene.add(terrain) terrains[t.id] = terrain } terrain.position.set(t.position.x / 100, t.position.y / 100, 0) } document.getElementById('state').innerHTML = JSON.stringify(state, null, 2) } } window.addEventListener('load', () => { connectWebSocket() const canvas = renderer.domElement canvas.classList.add('canvas') canvas.addEventListener('mousedown', (event) => { raycaster.setFromCamera(new THREE.Vector2((event.clientX / canvas.clientWidth) * 2 - 1, (event.clientY / canvas.clientHeight) * -2 + 1), camera) const intersect = raycaster.intersectObject(ground).at(0)?.point if (intersect != null) { if (event.button == 0) { const x = Math.round(intersect.x * 100) const y = Math.round(intersect.y * 100) websocket.send(JSON.stringify({ action: 'move', id: '2', x, y })) } if (event.button == 2) { const x = Math.round(intersect.x * 100) const y = Math.round(intersect.y * 100) websocket.send(JSON.stringify({ action: 'move', id: '1', x, y })) } } }) document.addEventListener('wheel', (event) => { if (event.deltaY < 0) { camera.zoom += 0.2 if (camera.zoom > 3) { camera.zoom = 3 } camera.updateProjectionMatrix() } if (event.deltaY > 0) { camera.zoom -= 0.2 if (camera.zoom < 1) { camera.zoom = 1 } camera.updateProjectionMatrix() } }) window.addEventListener('resize', (event) => { camera.aspect = window.innerWidth / window.innerHeight camera.updateProjectionMatrix() renderer.setSize(window.innerWidth, window.innerHeight) }) document.addEventListener('contextmenu', (event) => event.preventDefault()) window.addEventListener('keydown', (event) => keysDown[event.code] = true) window.addEventListener('keyup', (event) => keysDown[event.code] = false) document.body.appendChild(canvas) const minimap = minimapRenderer.domElement minimap.classList.add('minimap') document.body.appendChild(minimap) })