improve position fixing

This commit is contained in:
2025-01-11 18:17:44 +09:00
parent f1c191f61f
commit 4aba510ec0
8 changed files with 117 additions and 34 deletions
+7
View File
@@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "UNLICENSED",
"dependencies": {
"quickhull": "^1.0.3",
"sat": "^0.9.0",
"three": "^0.171.0",
"websocket-express": "^3.1.2"
@@ -745,6 +746,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/quickhull": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/quickhull/-/quickhull-1.0.3.tgz",
"integrity": "sha512-AQbLaXdzGDJdO9Mu3qY/NY5JWlDqIutCLW8vJbsQTq+/bydIZeltnMVRKCElp81Y5/uRm4Yw/RsMdcltFYsS6w==",
"license": "MIT"
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+1
View File
@@ -10,6 +10,7 @@
"license": "UNLICENSED",
"description": "",
"dependencies": {
"quickhull": "^1.0.3",
"sat": "^0.9.0",
"three": "^0.171.0",
"websocket-express": "^3.1.2"
+46 -1
View File
@@ -43,6 +43,7 @@ global.renderer = renderer
global.camera = camera
global.scene = scene
const keysDown = {}
function render() {
@@ -54,8 +55,52 @@ function minimapRender() {
minimapRenderer.render(scene, minimapCamera)
}
const cameraSpeed = 0.1
var cameraLocked = true
function followCamera() {
// entity = entities.filter((e) => e.id = '1').at(0)
const entity = entities['1']
if (entity == null) { return }
const offsetX = 0
const offsetY = -15
const distanceX = Math.abs((entity.position.x + offsetX) - camera.position.x)
const distanceY = Math.abs((entity.position.y + offsetY) - camera.position.y)
if (distanceX > 0.01) {
if (entity.position.x + offsetX > camera.position.x) {
camera.position.x += cameraSpeed * distanceX
}
if (entity.position.x + offsetX < camera.position.x) {
camera.position.x -= cameraSpeed * distanceX
}
}
else if (distanceX != 0) {
camera.position.x = entity.position.x + offsetX
}
if (distanceY > 0.01) {
if (entity.position.y + offsetY > camera.position.y) {
camera.position.y += cameraSpeed * distanceY
}
if (entity.position.y + offsetY < camera.position.y) {
camera.position.y -= cameraSpeed * distanceY
}
}
else if (distanceY != 0) {
camera.position.y = entity.position.y + offsetY
}
}
const cameraSpeed = 0.03
function cameraMovement() {
if (cameraLocked) {
followCamera()
return
}
if (keysDown.ArrowLeft) { camera.position.x -= cameraSpeed }
else if (keysDown.ArrowRight) { camera.position.x += cameraSpeed }
+11 -15
View File
@@ -64,23 +64,20 @@ export default class Entity {
return entityColliders.concat(terrainColliders)
}
// DEPRECATED: hulls were a failed concept for position fixing
collidableHulls() {
const entityColliders = (this.game?.entities ?? []).filter((e) => e.id != this.id).map((e) => e.collider)
const terrainColliders = (this.game?.terrains ?? []).map((t) => t.hull)
return entityColliders.concat(terrainColliders)
}
isColliding(...colliders) {
return SATX.collideObjects(this.collider, colliders)
}
// TODO: calculate convex hulls of collidables because tris handle collisions sequentially
moveAction(x, y) {
const temp = SATX.fixCollisions(new Vector2(x, y), this.collidables(), this.radius)
// const entity = new Entity()
// entity.teleport(temp.x, temp.y)
// entity.radius = this.radius
// this.game.spawn_entity(entity)
this.#dest = SATX.clamp(
temp,
this.game?.width,
this.game?.height,
this.radius,
)
this.#dest = SATX.fixCollisions(new Vector2(x, y), this.collidables(), this.radius, this.game?.width, this.game?.height)
}
state() {
@@ -97,7 +94,6 @@ export default class Entity {
this.position.set(x, y)
}
// TODO: pathfinding stops if wall is clicked (did you forget to fix the destination?)
takeStep(distanceTraveled = 0) {
const speed = (this.speed / (this.game?.tickBudget ?? 1000)) - distanceTraveled
const collidables = this.collidables()
@@ -105,14 +101,14 @@ export default class Entity {
const fixedDest = SATX.clamp(SATX.fixCollisions(this.#dest, collidables, this.radius), this.game?.width, this.game?.height, this.radius)
if (this.#path.length < 1 || !this.#path.at(-1).equals(fixedDest)) {
console.time('pathfinding')
// console.time('pathfinding')
const start = SATX.vectorToFloat32Array(this.position)
const goal = SATX.vectorToFloat32Array(fixedDest)
const nonUniqueWaypoints = this.waypoints().map((w) => SATX.vectorToFloat32Array(w)).concat([start, goal])
const waypoints = Pathfind.uniqueWaypoints(nonUniqueWaypoints)
const graph = Pathfind.buildGraph(waypoints, collidables, this.radius)
this.#path = Pathfind.shortestPath(graph, start, goal).map((waypoint) => new Vector2(waypoint[0], waypoint[1]))
console.timeEnd('pathfinding')
// console.timeEnd('pathfinding')
}
if (this.#path.length > 0) {
+4
View File
@@ -34,6 +34,10 @@ export default class Game {
this.#terrains.push(terrain)
}
remove_terrain(terrain) {
this.#terrains = this.#terrains.filter((t) => t.id != terrain.id)
}
state() {
return {
...this,
+10 -7
View File
@@ -25,6 +25,10 @@ app.ws('/ws', async (req, res) => {
websocket.on('message', (rawData) => {
const message = JSON.parse(rawData)
const entity = message.id != null ? game.entities.find((e) => e.id == message.id) : null
if (entity == null) {
console.log({ error: { reason: 'Invalid ID', message } })
return
}
console.log(message)
if (message.action == 'teleport') {
@@ -46,11 +50,11 @@ app.listen(port, () => {
entity1.radius = 50
game.spawn_entity(entity1)
const entity2 = new Entity()
entity2.id = '2'
entity2.teleport(110, 110)
entity2.radius = 50
game.spawn_entity(entity2)
// const entity2 = new Entity()
// entity2.id = '2'
// entity2.teleport(110, 110)
// entity2.radius = 50
// game.spawn_entity(entity2)
// const triangle = new Terrain([
// { x: 400, y: 200 },
@@ -115,8 +119,7 @@ app.listen(port, () => {
pole.id = 'pole'
game.add_terrain(pole)
// entity1.moveAction(1000, 500)
// entity1.moveAction(750, 950)
// setTimeout(() => entity1.moveAction(100, 400), 10)
game.start()
+25 -11
View File
@@ -69,18 +69,32 @@ export default class SATX {
])
}
static fixCollisions(entityPosition, colliders, radius = 0) {
const position = entityPosition.clone()
let collider = Entity.collider(position.x, position.y, radius)
colliders.forEach((c) => {
let result = new SAT.Response()
if (this.collideObject(collider, c, result)) {
position.sub(new Vector2(result.overlapV.x, result.overlapV.y))
collider = Entity.collider(position.x, position.y, radius)
}
})
static fixCollisions(entityPosition, colliders, radius = 0, maxX = Infinity, maxY = Infinity) {
if (!this.collideObjects(Entity.collider(entityPosition.x, entityPosition.y, radius), colliders)) {
return entityPosition
}
// console.time('fixCollisions')
return position
let direction = new Vector2(0, 5)
let multiplier = 1
const rotationSlices = 16
for (let limit = 1; limit <= 10000; limit++) {
const rads = (limit % rotationSlices) * 2 * Math.PI / rotationSlices
const offset = direction.clone().rotateAround(new Vector2(), rads).multiplyScalar(multiplier)
const position = SATX.clamp(entityPosition.clone().add(offset), maxX, maxY, radius)
if (!this.collideObjects(Entity.collider(position.x, position.y, radius), colliders)) {
// console.timeEnd('fixCollisions')
return position
}
if (limit % rotationSlices == 0) {
multiplier++
}
}
// console.timeEnd('fixCollisions')
console.error('ERROR: can\'t fix collision')
}
static line(fromX, fromY, toX, toY) {
+13
View File
@@ -1,3 +1,4 @@
import QuickHull from 'quickhull' // DEPRECATED: hulls were a failed concept for position fixing
import SAT from 'sat'
import { Shape, ShapeUtils, Vector2 } from 'three'
@@ -8,6 +9,7 @@ export default class Terrain {
relativeVertices = []
#colliders = []
#hull = null
#vertices = []
#unadjustedWaypoints = []
@@ -24,6 +26,7 @@ export default class Terrain {
}
get colliders() { return this.#colliders }
get hull() { return this.#hull } // DEPRECATED: hulls were a failed concept for position fixing
get unadjustedWaypoints() { return this.#unadjustedWaypoints }
get vertices() { return this.#vertices }
@@ -73,6 +76,16 @@ export default class Terrain {
}
this.#colliders = ShapeUtils.triangulateShape(points.shape, points.holes).map(indicesToPolygon)
this.#calculateHull()
}
// DEPRECATED: hulls were a failed concept for position fixing
#calculateHull() {
const vertices = QuickHull(this.#vertices.map((v) => ({ x: v.x, y: v.y }))).map((v) => new Vector2(v.x, v.y))
const first = vertices.at(0)
const satPoints = [new SAT.Vector(...first.toArray()), ...vertices.slice(1).map((v) => new SAT.Vector(...v.clone(first).sub(first).toArray()))]
this.#hull = new SAT.Polygon(satPoints[0], [new SAT.Vector(), ...satPoints.slice(1)])
}
#calculatePosition() {