improve position fixing
This commit is contained in:
Generated
+7
@@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"quickhull": "^1.0.3",
|
||||||
"sat": "^0.9.0",
|
"sat": "^0.9.0",
|
||||||
"three": "^0.171.0",
|
"three": "^0.171.0",
|
||||||
"websocket-express": "^3.1.2"
|
"websocket-express": "^3.1.2"
|
||||||
@@ -745,6 +746,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/range-parser": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"description": "",
|
"description": "",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"quickhull": "^1.0.3",
|
||||||
"sat": "^0.9.0",
|
"sat": "^0.9.0",
|
||||||
"three": "^0.171.0",
|
"three": "^0.171.0",
|
||||||
"websocket-express": "^3.1.2"
|
"websocket-express": "^3.1.2"
|
||||||
|
|||||||
+46
-1
@@ -43,6 +43,7 @@ global.renderer = renderer
|
|||||||
global.camera = camera
|
global.camera = camera
|
||||||
global.scene = scene
|
global.scene = scene
|
||||||
|
|
||||||
|
|
||||||
const keysDown = {}
|
const keysDown = {}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
@@ -54,8 +55,52 @@ function minimapRender() {
|
|||||||
minimapRenderer.render(scene, minimapCamera)
|
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() {
|
function cameraMovement() {
|
||||||
|
if (cameraLocked) {
|
||||||
|
followCamera()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (keysDown.ArrowLeft) { camera.position.x -= cameraSpeed }
|
if (keysDown.ArrowLeft) { camera.position.x -= cameraSpeed }
|
||||||
else if (keysDown.ArrowRight) { camera.position.x += cameraSpeed }
|
else if (keysDown.ArrowRight) { camera.position.x += cameraSpeed }
|
||||||
|
|
||||||
|
|||||||
+11
-15
@@ -64,23 +64,20 @@ export default class Entity {
|
|||||||
return entityColliders.concat(terrainColliders)
|
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) {
|
isColliding(...colliders) {
|
||||||
return SATX.collideObjects(this.collider, colliders)
|
return SATX.collideObjects(this.collider, colliders)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: calculate convex hulls of collidables because tris handle collisions sequentially
|
|
||||||
moveAction(x, y) {
|
moveAction(x, y) {
|
||||||
const temp = SATX.fixCollisions(new Vector2(x, y), this.collidables(), this.radius)
|
this.#dest = SATX.fixCollisions(new Vector2(x, y), this.collidables(), this.radius, this.game?.width, this.game?.height)
|
||||||
// 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,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state() {
|
state() {
|
||||||
@@ -97,7 +94,6 @@ export default class Entity {
|
|||||||
this.position.set(x, y)
|
this.position.set(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: pathfinding stops if wall is clicked (did you forget to fix the destination?)
|
|
||||||
takeStep(distanceTraveled = 0) {
|
takeStep(distanceTraveled = 0) {
|
||||||
const speed = (this.speed / (this.game?.tickBudget ?? 1000)) - distanceTraveled
|
const speed = (this.speed / (this.game?.tickBudget ?? 1000)) - distanceTraveled
|
||||||
const collidables = this.collidables()
|
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)
|
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)) {
|
if (this.#path.length < 1 || !this.#path.at(-1).equals(fixedDest)) {
|
||||||
console.time('pathfinding')
|
// console.time('pathfinding')
|
||||||
const start = SATX.vectorToFloat32Array(this.position)
|
const start = SATX.vectorToFloat32Array(this.position)
|
||||||
const goal = SATX.vectorToFloat32Array(fixedDest)
|
const goal = SATX.vectorToFloat32Array(fixedDest)
|
||||||
const nonUniqueWaypoints = this.waypoints().map((w) => SATX.vectorToFloat32Array(w)).concat([start, goal])
|
const nonUniqueWaypoints = this.waypoints().map((w) => SATX.vectorToFloat32Array(w)).concat([start, goal])
|
||||||
const waypoints = Pathfind.uniqueWaypoints(nonUniqueWaypoints)
|
const waypoints = Pathfind.uniqueWaypoints(nonUniqueWaypoints)
|
||||||
const graph = Pathfind.buildGraph(waypoints, collidables, this.radius)
|
const graph = Pathfind.buildGraph(waypoints, collidables, this.radius)
|
||||||
this.#path = Pathfind.shortestPath(graph, start, goal).map((waypoint) => new Vector2(waypoint[0], waypoint[1]))
|
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) {
|
if (this.#path.length > 0) {
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ export default class Game {
|
|||||||
this.#terrains.push(terrain)
|
this.#terrains.push(terrain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remove_terrain(terrain) {
|
||||||
|
this.#terrains = this.#terrains.filter((t) => t.id != terrain.id)
|
||||||
|
}
|
||||||
|
|
||||||
state() {
|
state() {
|
||||||
return {
|
return {
|
||||||
...this,
|
...this,
|
||||||
|
|||||||
+10
-7
@@ -25,6 +25,10 @@ app.ws('/ws', async (req, res) => {
|
|||||||
websocket.on('message', (rawData) => {
|
websocket.on('message', (rawData) => {
|
||||||
const message = JSON.parse(rawData)
|
const message = JSON.parse(rawData)
|
||||||
const entity = message.id != null ? game.entities.find((e) => e.id == message.id) : null
|
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)
|
console.log(message)
|
||||||
|
|
||||||
if (message.action == 'teleport') {
|
if (message.action == 'teleport') {
|
||||||
@@ -46,11 +50,11 @@ app.listen(port, () => {
|
|||||||
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(110, 110)
|
// entity2.teleport(110, 110)
|
||||||
entity2.radius = 50
|
// entity2.radius = 50
|
||||||
game.spawn_entity(entity2)
|
// game.spawn_entity(entity2)
|
||||||
|
|
||||||
// const triangle = new Terrain([
|
// const triangle = new Terrain([
|
||||||
// { x: 400, y: 200 },
|
// { x: 400, y: 200 },
|
||||||
@@ -115,8 +119,7 @@ app.listen(port, () => {
|
|||||||
pole.id = 'pole'
|
pole.id = 'pole'
|
||||||
game.add_terrain(pole)
|
game.add_terrain(pole)
|
||||||
|
|
||||||
// entity1.moveAction(1000, 500)
|
// entity1.moveAction(750, 950)
|
||||||
|
|
||||||
// setTimeout(() => entity1.moveAction(100, 400), 10)
|
// setTimeout(() => entity1.moveAction(100, 400), 10)
|
||||||
|
|
||||||
game.start()
|
game.start()
|
||||||
|
|||||||
+25
-11
@@ -69,18 +69,32 @@ export default class SATX {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
static fixCollisions(entityPosition, colliders, radius = 0) {
|
static fixCollisions(entityPosition, colliders, radius = 0, maxX = Infinity, maxY = Infinity) {
|
||||||
const position = entityPosition.clone()
|
if (!this.collideObjects(Entity.collider(entityPosition.x, entityPosition.y, radius), colliders)) {
|
||||||
let collider = Entity.collider(position.x, position.y, radius)
|
return entityPosition
|
||||||
colliders.forEach((c) => {
|
}
|
||||||
let result = new SAT.Response()
|
// console.time('fixCollisions')
|
||||||
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
|
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) {
|
static line(fromX, fromY, toX, toY) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import QuickHull from 'quickhull' // DEPRECATED: hulls were a failed concept for position fixing
|
||||||
import SAT from 'sat'
|
import SAT from 'sat'
|
||||||
import { Shape, ShapeUtils, Vector2 } from 'three'
|
import { Shape, ShapeUtils, Vector2 } from 'three'
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ export default class Terrain {
|
|||||||
relativeVertices = []
|
relativeVertices = []
|
||||||
|
|
||||||
#colliders = []
|
#colliders = []
|
||||||
|
#hull = null
|
||||||
#vertices = []
|
#vertices = []
|
||||||
#unadjustedWaypoints = []
|
#unadjustedWaypoints = []
|
||||||
|
|
||||||
@@ -24,6 +26,7 @@ export default class Terrain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get colliders() { return this.#colliders }
|
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 unadjustedWaypoints() { return this.#unadjustedWaypoints }
|
||||||
get vertices() { return this.#vertices }
|
get vertices() { return this.#vertices }
|
||||||
|
|
||||||
@@ -73,6 +76,16 @@ export default class Terrain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.#colliders = ShapeUtils.triangulateShape(points.shape, points.holes).map(indicesToPolygon)
|
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() {
|
#calculatePosition() {
|
||||||
|
|||||||
Reference in New Issue
Block a user