add unoptimized pathfinding
This commit is contained in:
+3
-2
@@ -16,7 +16,7 @@ const terrainMaterial = new THREE.MeshToonMaterial({ color: 0xffd700 })
|
|||||||
const minimapCamera = new THREE.OrthographicCamera(-10, 10, 10, -10)
|
const minimapCamera = new THREE.OrthographicCamera(-10, 10, 10, -10)
|
||||||
const minimapRenderer = new THREE.WebGLRenderer()
|
const minimapRenderer = new THREE.WebGLRenderer()
|
||||||
|
|
||||||
minimapRenderer.setSize(300, 300)
|
minimapRenderer.setSize(600, 600)
|
||||||
minimapRenderer.setAnimationLoop(minimapRender)
|
minimapRenderer.setAnimationLoop(minimapRender)
|
||||||
minimapCamera.position.set(10, 10, 10)
|
minimapCamera.position.set(10, 10, 10)
|
||||||
|
|
||||||
@@ -101,7 +101,8 @@ function connectWebSocket() {
|
|||||||
entity = entities[e.id]
|
entity = entities[e.id]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
entity = new THREE.Mesh(new THREE.SphereGeometry(e.radius / 100), entityMaterial)
|
entity = new THREE.Mesh(new THREE.CylinderGeometry(e.radius / 100, e.radius / 100, e.radius / 50), entityMaterial)
|
||||||
|
entity.rotation.x = Math.PI / 2
|
||||||
entity.userData.type = 'entity'
|
entity.userData.type = 'entity'
|
||||||
entity.userData.id = e.id
|
entity.userData.id = e.id
|
||||||
scene.add(entity)
|
scene.add(entity)
|
||||||
|
|||||||
+44
-10
@@ -1,6 +1,7 @@
|
|||||||
import { Vector2 } from 'three'
|
import { Vector2 } from 'three'
|
||||||
import SAT from 'sat'
|
import SAT from 'sat'
|
||||||
import SATX from './satx.js'
|
import SATX from './satx.js'
|
||||||
|
import Pathfind from './pathfind.js'
|
||||||
|
|
||||||
export default class Entity {
|
export default class Entity {
|
||||||
id = crypto.randomUUID()
|
id = crypto.randomUUID()
|
||||||
@@ -10,6 +11,7 @@ export default class Entity {
|
|||||||
#position = new Vector2()
|
#position = new Vector2()
|
||||||
#dest = null
|
#dest = null
|
||||||
#game = null
|
#game = null
|
||||||
|
#path = []
|
||||||
|
|
||||||
static collider(x, y, radius) {
|
static collider(x, y, radius) {
|
||||||
return new SAT.Circle(new SAT.Vector(x, y), radius)
|
return new SAT.Circle(new SAT.Vector(x, y), radius)
|
||||||
@@ -42,6 +44,26 @@ export default class Entity {
|
|||||||
return [this.collider]
|
return [this.collider]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
isColliding(...colliders) {
|
isColliding(...colliders) {
|
||||||
return SATX.collideObjects(this.collider, colliders)
|
return SATX.collideObjects(this.collider, colliders)
|
||||||
}
|
}
|
||||||
@@ -64,18 +86,23 @@ export default class Entity {
|
|||||||
this.position.set(x, y)
|
this.position.set(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
takeStep() {
|
takeStep(distanceTraveled = 0) {
|
||||||
const speed = this.speed / (this.game?.tickBudget ?? 1000)
|
const speed = (this.speed / (this.game?.tickBudget ?? 1000)) - distanceTraveled
|
||||||
if (this.#dest != null) {
|
if (this.#dest != null) {
|
||||||
const fixedDest = new Vector2(
|
const fixedDest = SATX.clamp(SATX.fixCollisions(this.#dest, this.collidables, this.radius), this.game?.width, this.game?.height, this.radius)
|
||||||
Math.min(Math.max(this.radius, this.#dest.x), (this.game?.width ?? Infinity) - this.radius),
|
|
||||||
Math.min(Math.max(this.radius, this.#dest.y), (this.game?.height ?? Infinity) - this.radius),
|
|
||||||
)
|
|
||||||
|
|
||||||
const distance = this.position.clone().sub(fixedDest).length()
|
if (this.#path.length < 1 || !this.#path.at(-1).equals(this.#dest)) {
|
||||||
const direction = fixedDest.clone().sub(this.position).normalize()
|
const waypoints = (this.game?.unadjustedWaypoints.map(([unadjusted, direction]) => unadjusted.clone().add(direction.clone().multiplyScalar(this.radius))) ?? []).concat([this.position, fixedDest])
|
||||||
|
const graph = Pathfind.buildGraph(waypoints, this.collidables, this.radius)
|
||||||
|
this.#path = Pathfind.shortestPath(graph, this.position, fixedDest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#path.length > 0) {
|
||||||
|
const destination = this.#path.at(0)
|
||||||
|
const distance = this.position.clone().sub(destination).length()
|
||||||
|
const direction = destination.clone().sub(this.position).normalize()
|
||||||
const stepTaken = this.position.clone().add(direction.multiplyScalar(speed))
|
const stepTaken = this.position.clone().add(direction.multiplyScalar(speed))
|
||||||
const position = distance <= speed ? fixedDest : stepTaken
|
const position = distance <= speed ? destination : stepTaken
|
||||||
|
|
||||||
const collider = Entity.collider(position.x, position.y, this.radius)
|
const collider = Entity.collider(position.x, position.y, this.radius)
|
||||||
const isColliding = SATX.collideObjects(collider, this.collidables)
|
const isColliding = SATX.collideObjects(collider, this.collidables)
|
||||||
@@ -84,11 +111,18 @@ export default class Entity {
|
|||||||
this.position.copy(position)
|
this.position.copy(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.x == this.#dest?.x && this.y == this.#dest?.y) {
|
if (this.position.equals(destination)) {
|
||||||
|
this.#path = this.#path.slice(1)
|
||||||
|
if (this.#path.length > 0) {
|
||||||
|
this.takeStep(distance)
|
||||||
|
}
|
||||||
|
else {
|
||||||
this.#dest = null
|
this.#dest = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async update() {
|
async update() {
|
||||||
await Promise.allSettled([
|
await Promise.allSettled([
|
||||||
|
|||||||
+11
-2
@@ -3,8 +3,8 @@ import { EventEmitter } from 'node:events'
|
|||||||
export default class Game {
|
export default class Game {
|
||||||
tickRate = 30
|
tickRate = 30
|
||||||
currentTick = 0
|
currentTick = 0
|
||||||
width = 4000
|
width = 2000
|
||||||
height = 4000
|
height = 2000
|
||||||
|
|
||||||
#entities = []
|
#entities = []
|
||||||
#eventEmitter = new EventEmitter()
|
#eventEmitter = new EventEmitter()
|
||||||
@@ -16,11 +16,20 @@ export default class Game {
|
|||||||
get terrains() { return this.#terrains }
|
get terrains() { return this.#terrains }
|
||||||
get tickBudget() { return this.#tickBudget }
|
get tickBudget() { return this.#tickBudget }
|
||||||
|
|
||||||
|
get unadjustedWaypoints() {
|
||||||
|
return this.terrains.map((t) => t.unadjustedWaypoints).concat(this.entities.map((e) => e.unadjustedWaypoints)).flat()
|
||||||
|
}
|
||||||
|
|
||||||
spawn_entity(entity) {
|
spawn_entity(entity) {
|
||||||
this.#entities.push(entity)
|
this.#entities.push(entity)
|
||||||
entity.game = this
|
entity.game = this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
despawn(entity) {
|
||||||
|
this.#entities = this.#entities.filter((e) => e.id != entity.id)
|
||||||
|
entity.game = null
|
||||||
|
}
|
||||||
|
|
||||||
add_terrain(terrain) {
|
add_terrain(terrain) {
|
||||||
this.#terrains.push(terrain)
|
this.#terrains.push(terrain)
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-8
@@ -4,6 +4,10 @@ import Game from './game.js'
|
|||||||
import Entity from './entity.js'
|
import Entity from './entity.js'
|
||||||
import Terrain from './terrain.js'
|
import Terrain from './terrain.js'
|
||||||
|
|
||||||
|
import { Vector2 } from 'three'
|
||||||
|
import Pathfind from './pathfind.js'
|
||||||
|
import SATX from './satx.js'
|
||||||
|
|
||||||
const app = new WebSocketExpress()
|
const app = new WebSocketExpress()
|
||||||
const port = 1280
|
const port = 1280
|
||||||
const game = new Game()
|
const game = new Game()
|
||||||
@@ -42,17 +46,17 @@ app.listen(port, () => {
|
|||||||
|
|
||||||
const entity1 = new Entity()
|
const entity1 = new Entity()
|
||||||
entity1.id = '1'
|
entity1.id = '1'
|
||||||
entity1.teleport(350, 500)
|
entity1.teleport(200, 500)
|
||||||
entity1.radius = 50
|
entity1.radius = 50
|
||||||
game.spawn_entity(entity1)
|
game.spawn_entity(entity1)
|
||||||
|
|
||||||
const entity2 = new Entity()
|
const entity2 = new Entity()
|
||||||
entity2.id = '2'
|
entity2.id = '2'
|
||||||
entity2.teleport(35, 35)
|
entity2.teleport(110, 110)
|
||||||
entity2.radius = 35
|
entity2.radius = 50
|
||||||
game.spawn_entity(entity2)
|
game.spawn_entity(entity2)
|
||||||
|
|
||||||
const vertices = [
|
const horseshoe = new Terrain([
|
||||||
{ x: 400, y: 200 },
|
{ x: 400, y: 200 },
|
||||||
{ x: 600, y: 200 },
|
{ x: 600, y: 200 },
|
||||||
{ x: 700, y: 300 },
|
{ x: 700, y: 300 },
|
||||||
@@ -62,11 +66,50 @@ app.listen(port, () => {
|
|||||||
{ x: 600, y: 500 },
|
{ x: 600, y: 500 },
|
||||||
{ x: 600, y: 300 },
|
{ x: 600, y: 300 },
|
||||||
{ x: 400, y: 300 },
|
{ x: 400, y: 300 },
|
||||||
]
|
])
|
||||||
|
horseshoe.id = 'horseshoe'
|
||||||
|
game.add_terrain(horseshoe)
|
||||||
|
|
||||||
const terrain1 = new Terrain(vertices)
|
const stopsign = new Terrain([
|
||||||
terrain1.id = 'a'
|
{ x: 800, y: 800 },
|
||||||
game.add_terrain(terrain1)
|
{ x: 900, y: 900 },
|
||||||
|
{ x: 900, y: 1000 },
|
||||||
|
{ x: 800, y: 1100 },
|
||||||
|
{ x: 800, y: 1100 },
|
||||||
|
{ x: 700, y: 1100 },
|
||||||
|
{ x: 600, y: 1000 },
|
||||||
|
{ x: 600, y: 900 },
|
||||||
|
{ x: 700, y: 800 },
|
||||||
|
])
|
||||||
|
stopsign.id = 'stopsign'
|
||||||
|
game.add_terrain(stopsign)
|
||||||
|
|
||||||
|
const box = new Terrain([
|
||||||
|
{ x: 1200, y: 700 },
|
||||||
|
{ x: 1200, y: 800 },
|
||||||
|
{ x: 1300, y: 800 },
|
||||||
|
{ x: 1300, y: 700 },
|
||||||
|
])
|
||||||
|
box.id = 'box'
|
||||||
|
game.add_terrain(box)
|
||||||
|
|
||||||
|
const diamond = new Terrain([
|
||||||
|
{ x: 1000, y: 300 },
|
||||||
|
{ x: 1100, y: 400 },
|
||||||
|
{ x: 1000, y: 500 },
|
||||||
|
{ x: 900, y: 400 },
|
||||||
|
])
|
||||||
|
diamond.id = 'diamond'
|
||||||
|
game.add_terrain(diamond)
|
||||||
|
|
||||||
|
const pole = new Terrain([
|
||||||
|
{ x: 400, y: 1000 },
|
||||||
|
{ x: 410, y: 1000 },
|
||||||
|
{ x: 410, y: 1010 },
|
||||||
|
{ x: 400, y: 1010 },
|
||||||
|
])
|
||||||
|
pole.id = 'pole'
|
||||||
|
game.add_terrain(pole)
|
||||||
|
|
||||||
game.start()
|
game.start()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import Entity from "./entity.js"
|
||||||
|
import PriorityQueue from "./priority-queue.js"
|
||||||
|
import SATX from "./satx.js"
|
||||||
|
|
||||||
|
export default class Pathfind {
|
||||||
|
static shortestPath(graph, start, goal) {
|
||||||
|
const key = (pos) => `${pos.x},${pos.y}`
|
||||||
|
const queue = new PriorityQueue((a, b) => a[1] < b[1])
|
||||||
|
const visited = new Map()
|
||||||
|
|
||||||
|
queue.push([[start], 0])
|
||||||
|
|
||||||
|
while (!queue.isEmpty()) {
|
||||||
|
const [path, cost] = queue.pop()
|
||||||
|
const waypoint = path.at(-1)
|
||||||
|
|
||||||
|
if (waypoint.equals(goal)) {
|
||||||
|
path.shift()
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!visited.has(key(waypoint)) || visited.get(key(waypoint)) > cost) {
|
||||||
|
visited.set(key(waypoint), cost)
|
||||||
|
|
||||||
|
for (const { to, distance } of graph.filter(e => e.from.equals(waypoint))) {
|
||||||
|
if (!visited.has(key(to)) || visited.get(key(to)) > cost + distance) {
|
||||||
|
queue.push([[...path, to], cost + distance])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
static buildGraph(waypoints = [], colliders = [], radius = 0) {
|
||||||
|
const graph = []
|
||||||
|
|
||||||
|
for (const from of waypoints) {
|
||||||
|
for (const to of waypoints) {
|
||||||
|
if (from.equals(to)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const tunnel = SATX.entityTunnel(from, to, radius)
|
||||||
|
const collider = Entity.collider(from.x, from.y, radius)
|
||||||
|
|
||||||
|
const tunnelClear = !SATX.collideObjects(tunnel, colliders)
|
||||||
|
const waypointAvailable = !SATX.collideObjects(collider, colliders)
|
||||||
|
|
||||||
|
if (waypointAvailable && tunnelClear) {
|
||||||
|
const distance = from.distanceTo(to)
|
||||||
|
graph.push({ from, to, distance })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return graph
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
const top = 0;
|
||||||
|
const parent = i => ((i + 1) >>> 1) - 1;
|
||||||
|
const left = i => (i << 1) + 1;
|
||||||
|
const right = i => (i + 1) << 1;
|
||||||
|
|
||||||
|
export default class PriorityQueue {
|
||||||
|
#heap
|
||||||
|
#comparator
|
||||||
|
|
||||||
|
constructor(comparator = (a, b) => a > b) {
|
||||||
|
this.#heap = []
|
||||||
|
this.#comparator = comparator
|
||||||
|
}
|
||||||
|
|
||||||
|
get length() { return this.#heap.length }
|
||||||
|
|
||||||
|
isEmpty() {
|
||||||
|
return this.length < 1
|
||||||
|
}
|
||||||
|
|
||||||
|
peek() {
|
||||||
|
return this.#heap[top]
|
||||||
|
}
|
||||||
|
|
||||||
|
push(...values) {
|
||||||
|
values.forEach(value => {
|
||||||
|
this.#heap.push(value)
|
||||||
|
this.#siftUp();
|
||||||
|
});
|
||||||
|
return this.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
pop() {
|
||||||
|
const poppedValue = this.peek()
|
||||||
|
const bottom = this.length - 1
|
||||||
|
if (bottom > top) {
|
||||||
|
this.#swap(top, bottom)
|
||||||
|
}
|
||||||
|
this.#heap.pop()
|
||||||
|
this.#siftDown()
|
||||||
|
return poppedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
replace(value) {
|
||||||
|
const replacedValue = this.peek()
|
||||||
|
this.#heap[top] = value
|
||||||
|
this.#siftDown()
|
||||||
|
return replacedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
#greater(i, j) {
|
||||||
|
return this.#comparator(this.#heap[i], this.#heap[j])
|
||||||
|
}
|
||||||
|
|
||||||
|
#swap(i, j) {
|
||||||
|
[this.#heap[i], this.#heap[j]] = [this.#heap[j], this.#heap[i]]
|
||||||
|
}
|
||||||
|
|
||||||
|
#siftUp() {
|
||||||
|
let node = this.length - 1
|
||||||
|
while (node > top && this.#greater(node, parent(node))) {
|
||||||
|
this.#swap(node, parent(node))
|
||||||
|
node = parent(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#siftDown() {
|
||||||
|
let node = top;
|
||||||
|
while (
|
||||||
|
(left(node) < this.length && this.#greater(left(node), node)) ||
|
||||||
|
(right(node) < this.length && this.#greater(right(node), node))
|
||||||
|
) {
|
||||||
|
let maxChild = (right(node) < this.length && this.#greater(right(node), left(node))) ? right(node) : left(node)
|
||||||
|
this.#swap(node, maxChild)
|
||||||
|
node = maxChild
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+64
-5
@@ -1,21 +1,41 @@
|
|||||||
import SAT from 'sat'
|
import SAT from 'sat'
|
||||||
|
import { Vector2 } from 'three'
|
||||||
|
import Entity from './entity.js'
|
||||||
|
|
||||||
export default class SATX {
|
export default class SATX {
|
||||||
static collideObject(collider1, collider2) {
|
static clamp(vectorOrObject, maxX = Infinity, maxY = Infinity, radius = 0) {
|
||||||
|
let modified = null
|
||||||
|
if (vectorOrObject instanceof Vector2) {
|
||||||
|
modified = vectorOrObject.clone()
|
||||||
|
}
|
||||||
|
else if (vectorOrObject instanceof SAT.Vector) {
|
||||||
|
modified = new SAT.Vector(vectorOrObject.x, vectorOrObject.y)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
modified = { x: vectorOrObject.x, y: vectorOrObject.y }
|
||||||
|
}
|
||||||
|
|
||||||
|
modified.x = Math.min(Math.max(radius, vectorOrObject.x), (maxX ?? Infinity) - radius)
|
||||||
|
modified.y = Math.min(Math.max(radius, vectorOrObject.y), (maxY ?? Infinity) - radius)
|
||||||
|
|
||||||
|
return modified
|
||||||
|
}
|
||||||
|
|
||||||
|
static collideObject(collider1, collider2, result = null) {
|
||||||
if (collider1 instanceof SAT.Circle && collider2 instanceof SAT.Circle) {
|
if (collider1 instanceof SAT.Circle && collider2 instanceof SAT.Circle) {
|
||||||
return SAT.testCircleCircle(collider1, collider2)
|
return SAT.testCircleCircle(collider1, collider2, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (collider1 instanceof SAT.Circle && collider2 instanceof SAT.Polygon) {
|
if (collider1 instanceof SAT.Circle && collider2 instanceof SAT.Polygon) {
|
||||||
return SAT.testCirclePolygon(collider1, collider2)
|
return SAT.testCirclePolygon(collider1, collider2, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (collider1 instanceof SAT.Polygon && collider2 instanceof SAT.Circle) {
|
if (collider1 instanceof SAT.Polygon && collider2 instanceof SAT.Circle) {
|
||||||
return SAT.testPolygonCircle(collider1, collider2)
|
return SAT.testPolygonCircle(collider1, collider2, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (collider1 instanceof SAT.Polygon && collider2 instanceof SAT.Polygon) {
|
if (collider1 instanceof SAT.Polygon && collider2 instanceof SAT.Polygon) {
|
||||||
return SAT.testPolygonPolygon(collider1, collider2)
|
return SAT.testPolygonPolygon(collider1, collider2, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@@ -24,4 +44,43 @@ export default class SATX {
|
|||||||
static collideObjects(collider1, colliders) {
|
static collideObjects(collider1, colliders) {
|
||||||
return colliders.some((c) => this.collideObject(collider1, c))
|
return colliders.some((c) => this.collideObject(collider1, c))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static enclosingRegularPolygonRadius(numberOfVertices) {
|
||||||
|
return 1 / Math.cos(Math.PI / numberOfVertices)
|
||||||
|
}
|
||||||
|
|
||||||
|
static entityTunnel(from, to, radius) {
|
||||||
|
const length = to.clone().sub(from)
|
||||||
|
const halfWidth = length.clone().normalize().multiplyScalar(radius).rotateAround(new Vector2(), Math.PI / 2)
|
||||||
|
const width = halfWidth.clone().multiplyScalar(2)
|
||||||
|
|
||||||
|
const origin = from.clone().sub(halfWidth)
|
||||||
|
const satPoints = [
|
||||||
|
new SAT.Vector(...origin.toArray()),
|
||||||
|
new SAT.Vector(...from.clone().sub(halfWidth).add(length).sub(origin).toArray()),
|
||||||
|
new SAT.Vector(...from.clone().sub(halfWidth).add(length.clone().add(width)).sub(origin).toArray()),
|
||||||
|
new SAT.Vector(...from.clone().sub(halfWidth).add(width).sub(origin).toArray()),
|
||||||
|
]
|
||||||
|
|
||||||
|
return new SAT.Polygon(satPoints[0], [new SAT.Vector(), ...satPoints.slice(1)])
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return position
|
||||||
|
}
|
||||||
|
|
||||||
|
static polygonToThreeVector2(polygon) {
|
||||||
|
const position = new Vector2(polygon.pos.x, polygon.pos.y)
|
||||||
|
return polygon.points.map((p) => new Vector2(p.x, p.y).add(position))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-2
@@ -9,30 +9,58 @@ export default class Terrain {
|
|||||||
|
|
||||||
#colliders = []
|
#colliders = []
|
||||||
#vertices = []
|
#vertices = []
|
||||||
|
#unadjustedWaypoints = []
|
||||||
|
|
||||||
constructor(vertices) {
|
constructor(vertices) {
|
||||||
this.#vertices = vertices.map((v) => new Vector2(v.x, v.y))
|
this.#vertices = vertices.map((v) => new Vector2(v.x, v.y))
|
||||||
|
if (ShapeUtils.isClockWise(this.#vertices)) {
|
||||||
|
this.#vertices.reverse()
|
||||||
|
}
|
||||||
|
|
||||||
this.#calculateColliders()
|
this.#calculateColliders()
|
||||||
this.#calculatePosition()
|
this.#calculatePosition()
|
||||||
this.#calculateRelativeVertices()
|
this.#calculateRelativeVertices()
|
||||||
|
this.#calculateUnadjustedWaypoints()
|
||||||
}
|
}
|
||||||
|
|
||||||
get colliders() { return this.#colliders }
|
get colliders() { return this.#colliders }
|
||||||
|
get unadjustedWaypoints() { return this.#unadjustedWaypoints }
|
||||||
get vertices() { return this.#vertices }
|
get vertices() { return this.#vertices }
|
||||||
|
|
||||||
|
static waypointsForSide(fromVertex, toVertex, isClockwise = false) {
|
||||||
|
const from = isClockwise ? toVertex : fromVertex
|
||||||
|
const to = isClockwise ? fromVertex : toVertex
|
||||||
|
const origin = new Vector2()
|
||||||
|
const sideNormal = to.clone().sub(from).clone().normalize()
|
||||||
|
|
||||||
|
const margin = sideNormal.clone().rotateAround(origin, -3 * Math.PI / 4)
|
||||||
|
const offset = margin.clone().multiplyScalar(Math.SQRT2)
|
||||||
|
const inverseMargin = sideNormal.clone().negate().rotateAround(origin, 3 * Math.PI / 4)
|
||||||
|
const inverseOffset = inverseMargin.clone().multiplyScalar(Math.SQRT2)
|
||||||
|
|
||||||
|
return [
|
||||||
|
[margin.clone().add(from), offset],
|
||||||
|
[inverseMargin.clone().add(to), inverseOffset],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
state() {
|
state() {
|
||||||
return {
|
return {
|
||||||
...this,
|
...this,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#calculateColliders() {
|
#shape() {
|
||||||
const complexShape = new Shape()
|
const complexShape = new Shape()
|
||||||
|
|
||||||
complexShape.moveTo(this.#vertices.at(0).x, this.#vertices.at(0).y)
|
complexShape.moveTo(this.#vertices.at(0).x, this.#vertices.at(0).y)
|
||||||
this.#vertices.slice(1).forEach((v) => complexShape.lineTo(v.x, v.y))
|
this.#vertices.slice(1).forEach((v) => complexShape.lineTo(v.x, v.y))
|
||||||
|
|
||||||
const points = complexShape.extractPoints(16)
|
return complexShape
|
||||||
|
}
|
||||||
|
|
||||||
|
#calculateColliders() {
|
||||||
|
const points = this.#shape().extractPoints(16)
|
||||||
|
|
||||||
const indicesToPolygon = (indices) => {
|
const indicesToPolygon = (indices) => {
|
||||||
const satPoints = [
|
const satPoints = [
|
||||||
@@ -54,4 +82,8 @@ export default class Terrain {
|
|||||||
#calculateRelativeVertices() {
|
#calculateRelativeVertices() {
|
||||||
this.relativeVertices = this.#vertices.map((v) => v.clone().sub(this.position))
|
this.relativeVertices = this.#vertices.map((v) => v.clone().sub(this.position))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#calculateUnadjustedWaypoints() {
|
||||||
|
this.#unadjustedWaypoints = this.#vertices.map((v, i, arr) => Terrain.waypointsForSide(v, i + 1 < arr.length ? arr[i + 1] : arr[0])).flat()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user