From e23978ea900e36f452c1e1275fcb640d83d49471 Mon Sep 17 00:00:00 2001 From: Thayol Date: Mon, 23 Dec 2024 09:46:26 +0900 Subject: [PATCH] ditch THREE raycasting for SAT again --- package-lock.json | 7 ++++++ package.json | 1 + public/main.js | 7 +----- src/entity.js | 62 ++++++++++++++++++++++++++++++----------------- src/index.js | 2 +- src/satx.js | 11 +++++++++ 6 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 src/satx.js diff --git a/package-lock.json b/package-lock.json index df9ae8f..3bd67f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "UNLICENSED", "dependencies": { + "sat": "^0.9.0", "three": "^0.171.0", "websocket-express": "^3.1.2" } @@ -798,6 +799,12 @@ "license": "MIT", "peer": true }, + "node_modules/sat": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/sat/-/sat-0.9.0.tgz", + "integrity": "sha512-mxdv5RZJO4tdMnUURGU3gAMcnDUEwcNJwE+lPO0/V+rBeDvFLH3wEZEOR0fH7cTN0zQaNxBEbHnyQL9DzupwQQ==", + "license": "MIT" + }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", diff --git a/package.json b/package.json index 463a8c3..ce98d2d 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "license": "UNLICENSED", "description": "", "dependencies": { + "sat": "^0.9.0", "three": "^0.171.0", "websocket-express": "^3.1.2" } diff --git a/public/main.js b/public/main.js index 041fe35..4c67461 100644 --- a/public/main.js +++ b/public/main.js @@ -1,5 +1,4 @@ import * as THREE from 'three' -import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js' const global = (0,eval)("this") const scene = new THREE.Scene() @@ -23,16 +22,12 @@ directionalLight.position.set(-0.3, 0.1, 1) directionalLight.power = 3000 scene.add(directionalLight) -// const loader = new GLTFLoader() -// loader.load('player.gltf', (gltf) => scene.add(gltf.scene), undefined, (err) => console.log(err)) - global.THREE = THREE global.renderer = renderer global.camera = camera global.scene = scene camera.position.set(3, -12, 10) camera.rotation.set((60 / 180) * Math.PI, 0, 0) -// camera.lookAt(0, 0, 0) function animate() { renderer.render(scene, camera) @@ -81,7 +76,7 @@ function connectWebSocket() { entities[e.id] = entity } - entity.position.set(e.pos.x / 100, e.pos.y / 100, e.radius / 100) + entity.position.set(e.position.x / 100, e.position.y / 100, e.radius / 100) } document.getElementById('state').innerHTML = JSON.stringify(state, null, 2) diff --git a/src/entity.js b/src/entity.js index 94d89a5..3b7345f 100644 --- a/src/entity.js +++ b/src/entity.js @@ -1,32 +1,42 @@ import * as THREE from 'three' +import SAT from 'sat' +import SATX from './satx.js' export default class Entity { id = crypto.randomUUID() speed = 400 + radius = 0 + #position = new THREE.Vector2(0, 0) #dest = null #game = null - #mesh = null + + static collider(x, y, radius) { + return new SAT.Circle(new SAT.Vector(x, y), radius) + } constructor(...options) { Object.entries(options).forEach((value, key) => this[key] = value) - const geometry = new THREE.CircleGeometry(options.radius ?? 0) - this.#mesh = new THREE.Mesh(geometry) } get game() { return this.#game } - get mesh() { return this.#mesh } - get pos() { return this.#mesh.position } - get radius() { return this.#mesh.userData.radius } - get x() { return this.#mesh.position.x } - get y() { return this.#mesh.position.y } + get position() { return this.#position } + get x() { return this.position.x } + get y() { return this.position.y } set game(value) { this.#game = value } - set x(value) { this.#mesh.position.x = value } - set y(value) { this.#mesh.position.y = value } + set x(value) { this.position.x = value } + set y(value) { this.position.y = value } - set radius(value) { - this.#mesh.geometry = new THREE.CircleGeometry(value) - this.#mesh.userData.radius = value + get collidables() { + return this.game?.entities.filter((e) => e.id != this.id).map((e) => e.collider) + } + + get collider() { + return new SAT.Circle(new SAT.Vector(this.x, this.y), this.radius) + } + + isColliding(collider) { + return SATX.colliding(this.collider, collider) } moveAction(x, y) { @@ -36,30 +46,38 @@ export default class Entity { state() { return { ...this, - pos: { + position: { x: this.x, y: this.y, }, - radius: this.radius, } } teleport(x, y) { - this.#mesh.position.set(x, y, 0) + this.position.set(x, y) } takeStep() { const speed = this.speed / (this.game?.tickBudget ?? 1000) if (this.#dest != null) { - const fixedDest = new THREE.Vector3( - Math.min(Math.max(this.radius, this.#dest.x), this.game?.height ?? Infinity), + const fixedDest = new THREE.Vector2( + Math.min(Math.max(this.radius, this.#dest.x), this.game?.width ?? Infinity), Math.min(Math.max(this.radius, this.#dest.y), this.game?.height ?? Infinity), - 0, ) - this.pos.add(fixedDest.clone().sub(this.pos).normalize().multiplyScalar(speed)) - if (this.pos.clone().sub(fixedDest).length() <= speed) { - this.pos.copy(fixedDest) + const distance = this.position.clone().sub(fixedDest).length() + const direction = fixedDest.clone().sub(this.position).normalize() + const stepTaken = this.position.clone().add(direction.multiplyScalar(speed)) + const position = distance <= speed ? fixedDest : stepTaken + + const collider = Entity.collider(position.x, position.y, this.radius) + const isColliding = this.collidables.some((c) => SATX.colliding(collider, c)) + + if (!isColliding) { + this.position.copy(position) + } + + if (this.x == this.#dest?.x && this.y == this.#dest?.y) { this.#dest = null } } diff --git a/src/index.js b/src/index.js index aa795ed..2e14444 100644 --- a/src/index.js +++ b/src/index.js @@ -47,7 +47,7 @@ app.listen(port, () => { const entity2 = new Entity() entity2.id = '2' - entity.teleport(200, 100) + entity2.teleport(100, 100) entity2.radius = 35 game.spawn_entity(entity2) diff --git a/src/satx.js b/src/satx.js new file mode 100644 index 0000000..ca74c2f --- /dev/null +++ b/src/satx.js @@ -0,0 +1,11 @@ +import SAT from 'sat' + +export default class SATX { + static colliding(collider1, collider2) { + if (collider1 instanceof SAT.Circle && collider2 instanceof SAT.Circle) { + return SAT.testCircleCircle(collider1, collider2) + } + + return false + } +}