ditch THREE raycasting for SAT again

This commit is contained in:
2024-12-23 09:46:26 +09:00
parent 054d22d01a
commit e23978ea90
6 changed files with 61 additions and 29 deletions
+7
View File
@@ -9,6 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"sat": "^0.9.0",
"three": "^0.171.0", "three": "^0.171.0",
"websocket-express": "^3.1.2" "websocket-express": "^3.1.2"
} }
@@ -798,6 +799,12 @@
"license": "MIT", "license": "MIT",
"peer": true "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": { "node_modules/send": {
"version": "0.19.0", "version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+1
View File
@@ -10,6 +10,7 @@
"license": "UNLICENSED", "license": "UNLICENSED",
"description": "", "description": "",
"dependencies": { "dependencies": {
"sat": "^0.9.0",
"three": "^0.171.0", "three": "^0.171.0",
"websocket-express": "^3.1.2" "websocket-express": "^3.1.2"
} }
+1 -6
View File
@@ -1,5 +1,4 @@
import * as THREE from 'three' import * as THREE from 'three'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
const global = (0,eval)("this") const global = (0,eval)("this")
const scene = new THREE.Scene() const scene = new THREE.Scene()
@@ -23,16 +22,12 @@ directionalLight.position.set(-0.3, 0.1, 1)
directionalLight.power = 3000 directionalLight.power = 3000
scene.add(directionalLight) 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.THREE = THREE
global.renderer = renderer global.renderer = renderer
global.camera = camera global.camera = camera
global.scene = scene global.scene = scene
camera.position.set(3, -12, 10) camera.position.set(3, -12, 10)
camera.rotation.set((60 / 180) * Math.PI, 0, 0) camera.rotation.set((60 / 180) * Math.PI, 0, 0)
// camera.lookAt(0, 0, 0)
function animate() { function animate() {
renderer.render(scene, camera) renderer.render(scene, camera)
@@ -81,7 +76,7 @@ function connectWebSocket() {
entities[e.id] = entity 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) document.getElementById('state').innerHTML = JSON.stringify(state, null, 2)
+40 -22
View File
@@ -1,32 +1,42 @@
import * as THREE from 'three' import * as THREE from 'three'
import SAT from 'sat'
import SATX from './satx.js'
export default class Entity { export default class Entity {
id = crypto.randomUUID() id = crypto.randomUUID()
speed = 400 speed = 400
radius = 0
#position = new THREE.Vector2(0, 0)
#dest = null #dest = null
#game = null #game = null
#mesh = null
static collider(x, y, radius) {
return new SAT.Circle(new SAT.Vector(x, y), radius)
}
constructor(...options) { constructor(...options) {
Object.entries(options).forEach((value, key) => this[key] = value) 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 game() { return this.#game }
get mesh() { return this.#mesh } get position() { return this.#position }
get pos() { return this.#mesh.position } get x() { return this.position.x }
get radius() { return this.#mesh.userData.radius } get y() { return this.position.y }
get x() { return this.#mesh.position.x }
get y() { return this.#mesh.position.y }
set game(value) { this.#game = value } set game(value) { this.#game = value }
set x(value) { this.#mesh.position.x = value } set x(value) { this.position.x = value }
set y(value) { this.#mesh.position.y = value } set y(value) { this.position.y = value }
set radius(value) { get collidables() {
this.#mesh.geometry = new THREE.CircleGeometry(value) return this.game?.entities.filter((e) => e.id != this.id).map((e) => e.collider)
this.#mesh.userData.radius = value }
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) { moveAction(x, y) {
@@ -36,30 +46,38 @@ export default class Entity {
state() { state() {
return { return {
...this, ...this,
pos: { position: {
x: this.x, x: this.x,
y: this.y, y: this.y,
}, },
radius: this.radius,
} }
} }
teleport(x, y) { teleport(x, y) {
this.#mesh.position.set(x, y, 0) this.position.set(x, y)
} }
takeStep() { takeStep() {
const speed = this.speed / (this.game?.tickBudget ?? 1000) const speed = this.speed / (this.game?.tickBudget ?? 1000)
if (this.#dest != null) { if (this.#dest != null) {
const fixedDest = new THREE.Vector3( const fixedDest = new THREE.Vector2(
Math.min(Math.max(this.radius, this.#dest.x), this.game?.height ?? Infinity), 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), 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)) const distance = this.position.clone().sub(fixedDest).length()
if (this.pos.clone().sub(fixedDest).length() <= speed) { const direction = fixedDest.clone().sub(this.position).normalize()
this.pos.copy(fixedDest) 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 this.#dest = null
} }
} }
+1 -1
View File
@@ -47,7 +47,7 @@ app.listen(port, () => {
const entity2 = new Entity() const entity2 = new Entity()
entity2.id = '2' entity2.id = '2'
entity.teleport(200, 100) entity2.teleport(100, 100)
entity2.radius = 35 entity2.radius = 35
game.spawn_entity(entity2) game.spawn_entity(entity2)
+11
View File
@@ -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
}
}