add bbox checks for pathfinding
This commit is contained in:
+46
-29
@@ -158,26 +158,6 @@ export default class Entity {
|
||||
set x(value) { this.position.x = value }
|
||||
set y(value) { this.position.y = value }
|
||||
|
||||
get unadjustedWaypoints() {
|
||||
const numberOfWaypoints = 8
|
||||
const margin = 1
|
||||
const enclosingRegularPolygonRadius = SATX.enclosingRegularPolygonRadius(numberOfWaypoints)
|
||||
const radius = this.radius * enclosingRegularPolygonRadius + margin
|
||||
const baseWaypoint = new Vector2(radius, 0)
|
||||
const waypoints = []
|
||||
|
||||
const origin = new Vector2
|
||||
const unitOfRotation = (Math.PI * 2 / numberOfWaypoints)
|
||||
for (let i = 0; i < numberOfWaypoints; i++) {
|
||||
waypoints.push(baseWaypoint.clone().rotateAround(origin, unitOfRotation * i))
|
||||
}
|
||||
|
||||
return waypoints.map((w) => [
|
||||
w.clone().add(this.position),
|
||||
w.clone().normalize().multiplyScalar(enclosingRegularPolygonRadius),
|
||||
])
|
||||
}
|
||||
|
||||
attackAction(cursor) {
|
||||
this.moveAction(cursor, true)
|
||||
}
|
||||
@@ -424,8 +404,8 @@ export default class Entity {
|
||||
const bboxCheckedObstacles = terrains.filter((it) => SATX.bboxCheck(bbox, it.bbox))
|
||||
if (bboxCheckedObstacles.length < 1) { return true }
|
||||
|
||||
const posCollider = Entity.collider(this.position.x, this.position.y, 1) // TODO: magic number for radius
|
||||
const posBbox = Entity.bbox(this.position.x, this.position.y, 1) // TODO: magic number for radius
|
||||
const posCollider = Entity.collider(this.position.x, this.position.y, 0)
|
||||
const posBbox = Entity.bbox(this.position.x, this.position.y, 0)
|
||||
const unpassableTerrain = bboxCheckedObstacles.filter((it) => !(SATX.bboxCheck(posBbox, it.bbox) && it.colliders().some((c) => SATX.collideObject(posCollider, c))))
|
||||
|
||||
const colliders = unpassableTerrain.map((it) => it.colliders()).flat()
|
||||
@@ -472,9 +452,29 @@ export default class Entity {
|
||||
this.setPosition(this.fixFuturePosition(cursor))
|
||||
}
|
||||
|
||||
unadjustedWaypoints() {
|
||||
const numberOfWaypoints = 8
|
||||
const margin = 1
|
||||
const enclosingRegularPolygonRadius = SATX.enclosingRegularPolygonRadius(numberOfWaypoints)
|
||||
const radius = this.radius * enclosingRegularPolygonRadius + margin
|
||||
const baseWaypoint = new Vector2(radius, 0)
|
||||
const waypoints = []
|
||||
|
||||
const origin = new Vector2
|
||||
const unitOfRotation = (Math.PI * 2 / numberOfWaypoints)
|
||||
for (let i = 0; i < numberOfWaypoints; i++) {
|
||||
waypoints.push(baseWaypoint.clone().rotateAround(origin, unitOfRotation * i))
|
||||
}
|
||||
|
||||
return waypoints.map((w) => [
|
||||
w.clone().add(this.position),
|
||||
w.clone().normalize().multiplyScalar(enclosingRegularPolygonRadius),
|
||||
])
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.dead) {
|
||||
// TODO: do something while the entity is dead
|
||||
// TODO: do something while the entity is dead (and disallow casting, etc)
|
||||
}
|
||||
else {
|
||||
this.#cast()
|
||||
@@ -537,6 +537,7 @@ export default class Entity {
|
||||
ability.effect(this, this.casting.cursor)
|
||||
|
||||
this.casting = null
|
||||
// TODO: only spawn castingVision if slightly outside regular vision (or obstructed)
|
||||
Ability.castingVision.effect(this, this.position)
|
||||
return true
|
||||
}
|
||||
@@ -590,16 +591,33 @@ export default class Entity {
|
||||
if (pathfinding && (this.#path.length < 1 || (this.#path.at(-1)?.distanceTo(fixedDest) ?? 0) > 0.01)) {
|
||||
const start = SATX.vectorToFloat32Array(this.position)
|
||||
const goal = SATX.vectorToFloat32Array(fixedDest)
|
||||
const obstacles = []
|
||||
const obstacles = new Map() // TODO: limit number of obstacles for non-important entities (property on the class?)
|
||||
const obstacleWaypoints = new Map()
|
||||
const obstacleColliders = new Map()
|
||||
|
||||
// TODO: pathfinding takes longer after bbox check implementation (maybe separate obstacleColliders into two, and index match)
|
||||
for (let failsafe = 0; failsafe < 1000; failsafe++) {
|
||||
const obstaclesArray = Array.from(obstacles.values())
|
||||
|
||||
for (const obstacle of obstaclesArray) {
|
||||
if (!obstacleWaypoints.has(obstacle.id)) {
|
||||
const waypoint = obstacle.unadjustedWaypoints().map(([w, d]) => SATX.vectorToFloat32Array(this.adjustWaypoint(w, d)))
|
||||
obstacleWaypoints.set(obstacle.id, waypoint)
|
||||
}
|
||||
if (!obstacleColliders.has(obstacle.id)) {
|
||||
const bbox = obstacle.bbox
|
||||
const colliders = obstacle.colliders()
|
||||
obstacleColliders.set(obstacle.id, [bbox, colliders])
|
||||
}
|
||||
}
|
||||
|
||||
const waypoints = [
|
||||
start,
|
||||
goal,
|
||||
...obstacles.map((e) => e.unadjustedWaypoints.map(([w, d]) => SATX.vectorToFloat32Array(this.adjustWaypoint(w, d)))).flat()
|
||||
...obstaclesArray.map((it) => obstacleWaypoints.get(it.id)).flat()
|
||||
]
|
||||
|
||||
const colliders = obstacles.map((e) => e.colliders()).flat()
|
||||
const colliders = Array.from(obstacleColliders.values())
|
||||
const graph = Pathfind.buildGraph(waypoints, colliders, this.radius)
|
||||
const path = Pathfind.shortestPath(graph, start, goal).map((waypoint) => new Vector2(waypoint[0], waypoint[1]))
|
||||
|
||||
@@ -611,10 +629,9 @@ export default class Entity {
|
||||
const sectionObstacles = this.obstaclesInStraightPath(section, lastSection)
|
||||
if (sectionObstacles.length > 0) {
|
||||
obstacleInPath = true
|
||||
const obstacleIds = obstacles.map((o) => o.id)
|
||||
for (const obstacle of sectionObstacles) {
|
||||
if (!obstacleIds.includes(obstacle.id)) {
|
||||
obstacles.push(obstacle)
|
||||
if (!obstacles.has(obstacle.id)) {
|
||||
obstacles.set(obstacle.id, obstacle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-7
@@ -36,10 +36,6 @@ export default class Game {
|
||||
get tickBudget() { return this.#tickBudget }
|
||||
set logic(value) { this.#logic = value }
|
||||
|
||||
get unadjustedWaypoints() {
|
||||
return this.terrains.map((t) => t.unadjustedWaypoints).concat(this.entities.map((e) => e.unadjustedWaypoints)).flat()
|
||||
}
|
||||
|
||||
action(id, options) {
|
||||
const entity = this.entities.find((it) => it.id == id)
|
||||
if (entity == null) {
|
||||
@@ -104,7 +100,7 @@ export default class Game {
|
||||
if (this.gameLoopIntervalId != null) { return }
|
||||
|
||||
this.startTimestamp = performance.now() + (this.currentTick * this.tickBudget)
|
||||
console.log(`Started game ${this.id} with ${this.tickRate} tps. Starting on tick ${this.currentTick}.`)
|
||||
console.info(`Started game ${this.id} with ${this.tickRate} tps. Starting on tick ${this.currentTick}.`)
|
||||
this.gameLoopIntervalId = setInterval(this.#gameLoopCall.bind(this), this.tickBudget / 5)
|
||||
}
|
||||
|
||||
@@ -113,7 +109,7 @@ export default class Game {
|
||||
|
||||
clearInterval(this.gameLoopIntervalId)
|
||||
this.gameLoopIntervalId = null
|
||||
console.log(`Stopped game ${this.id}. Stopped on tick ${this.currentTick}.`)
|
||||
console.info(`Stopped game ${this.id}. Stopped on tick ${this.currentTick}.`)
|
||||
}
|
||||
|
||||
subscription(websocket, id) {
|
||||
@@ -158,7 +154,6 @@ export default class Game {
|
||||
return entityVisionSources.concat(projectileVisionSources)
|
||||
}
|
||||
|
||||
// TODO: castingVision should not reveal casting in non-lanes (= only spawn castingVision if slightly outside regular vision [or obstructeed])
|
||||
visionByTeam(team) {
|
||||
const visionSources = this.visionSources(team)
|
||||
const visibleEntities = new Set(visionSources.map((it) => it.entitiesInVision()).flat())
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ app.ws('/ws', async (req, res) => {
|
||||
})
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server started! Visit http://localhost:${port}`)
|
||||
console.info(`Server started! Visit http://localhost:${port}`)
|
||||
|
||||
Dungeon.scenario(game)
|
||||
})
|
||||
|
||||
+21
-8
@@ -83,12 +83,18 @@ export default class Pathfind {
|
||||
|
||||
if (radius > 0) {
|
||||
for (const waypoint of waypoints) {
|
||||
const bbox = Entity.bbox(waypoint[0], waypoint[1], radius) // TODO: duplicate bbox calculation logic for speed
|
||||
const bboxCheckedObstacles = colliders.filter((it) => SATX.bboxCheck(bbox, it[0]))
|
||||
if (bboxCheckedObstacles.length > 0) {
|
||||
const collider = Entity.collider(waypoint[0], waypoint[1], radius)
|
||||
const waypointAvailable = !colliders.some((it) => SATX.collideObject(collider, it))
|
||||
if (waypointAvailable) {
|
||||
filteredWaypoints.push(waypoint)
|
||||
const colliding = bboxCheckedObstacles.some((it) => it[1].some((c) => SATX.collideObject(collider, c)))
|
||||
if (colliding) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
filteredWaypoints.push(waypoint)
|
||||
}
|
||||
}
|
||||
|
||||
const mergedWaypoints = new Float32Array(filteredWaypoints.length * 2)
|
||||
@@ -111,14 +117,23 @@ export default class Pathfind {
|
||||
}
|
||||
|
||||
const key = Pathfind.floatKey4(mergedWaypoints[i], mergedWaypoints[i + 1], mergedWaypoints[j], mergedWaypoints[j + 1])
|
||||
if (!checked.has(key)) {
|
||||
if (checked.has(key)) {
|
||||
continue
|
||||
}
|
||||
|
||||
checked.add(key)
|
||||
checked.add(Pathfind.floatKey4(mergedWaypoints[j], mergedWaypoints[j + 1], mergedWaypoints[i], mergedWaypoints[i + 1]))
|
||||
|
||||
// TODO: optimize tunnelCollider using bounding boxes
|
||||
const bbox = Entity.tunnelBbox(mergedWaypoints[i], mergedWaypoints[i + 1], mergedWaypoints[j], mergedWaypoints[j + 1], radius) // TODO: duplicate bbox calculation logic for speed
|
||||
const bboxCheckedObstacles = colliders.filter((it) => SATX.bboxCheck(bbox, it[0]))
|
||||
if (bboxCheckedObstacles.length > 0) {
|
||||
const tunnel = Entity.tunnelCollider(mergedWaypoints[i], mergedWaypoints[i + 1], mergedWaypoints[j], mergedWaypoints[j + 1], radius)
|
||||
const colliding = bboxCheckedObstacles.some((it) => it[1].some((c) => SATX.collideObject(tunnel, c)))
|
||||
if (colliding) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (!colliders.some((it) => SATX.collideObject(tunnel, it))) {
|
||||
const node = new Float32Array(5)
|
||||
node[0] = mergedWaypoints[i]
|
||||
node[1] = mergedWaypoints[i + 1]
|
||||
@@ -136,8 +151,6 @@ export default class Pathfind {
|
||||
nodes.push(reverseNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mergeNodes) {
|
||||
return nodes
|
||||
|
||||
+2
-2
@@ -75,8 +75,8 @@ export default class Projectile {
|
||||
const bboxCheckedObstacles = terrains.filter((it) => SATX.bboxCheck(bbox, it.bbox))
|
||||
if (bboxCheckedObstacles.length < 1) { return true }
|
||||
|
||||
const posCollider = Entity.collider(this.position.x, this.position.y, 1) // TODO: magic number for radius
|
||||
const posBbox = Entity.bbox(this.position.x, this.position.y, 1) // TODO: magic number for radius
|
||||
const posCollider = Entity.collider(this.position.x, this.position.y, 0)
|
||||
const posBbox = Entity.bbox(this.position.x, this.position.y, 0)
|
||||
const unpassableTerrain = bboxCheckedObstacles.filter((it) => !(SATX.bboxCheck(posBbox, it.bbox) && it.colliders().some((c) => SATX.collideObject(posCollider, c))))
|
||||
|
||||
const colliders = unpassableTerrain.map((it) => it.colliders()).flat()
|
||||
|
||||
@@ -37,8 +37,6 @@ export default class Template {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: incremental pathfinding stuck in thicker than recalculateDestRadius walls
|
||||
// TODO: minions despawn prematurely (too large checkpointSize?)
|
||||
static #minionLogic(route = []) {
|
||||
const checkpointSize = 300
|
||||
const maxDestDistance = 100
|
||||
@@ -82,7 +80,6 @@ export default class Template {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: proper respawn
|
||||
static #playerLogic() {
|
||||
const entity = this
|
||||
if (entity.dead) {
|
||||
|
||||
+1
-2
@@ -32,8 +32,6 @@ export default class Terrain {
|
||||
this.#calculateUnadjustedWaypoints()
|
||||
this.#calculateBbox()
|
||||
}
|
||||
|
||||
get unadjustedWaypoints() { return this.#unadjustedWaypoints }
|
||||
get vertices() { return this.#vertices }
|
||||
|
||||
static waypointsForSide(fromVertex, toVertex, isClockwise = false) {
|
||||
@@ -54,6 +52,7 @@ export default class Terrain {
|
||||
}
|
||||
|
||||
colliders() { return this.#colliders }
|
||||
unadjustedWaypoints() { return this.#unadjustedWaypoints }
|
||||
|
||||
#shape() {
|
||||
const complexShape = new Shape()
|
||||
|
||||
Reference in New Issue
Block a user