import SAT from 'sat' import { Vector2 } from 'three' import Entity from './entity.js' export default class SATX { 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) { return SAT.testCircleCircle(collider1, collider2, result) } if (collider1 instanceof SAT.Circle && collider2 instanceof SAT.Polygon) { return SAT.testCirclePolygon(collider1, collider2, result) } if (collider1 instanceof SAT.Polygon && collider2 instanceof SAT.Circle) { return SAT.testPolygonCircle(collider1, collider2, result) } if (collider1 instanceof SAT.Polygon && collider2 instanceof SAT.Polygon) { return SAT.testPolygonPolygon(collider1, collider2, result) } return false } static collideObjects(collider1, colliders) { return colliders.some((c) => this.collideObject(collider1, c)) } static enclosingRegularPolygonRadius(numberOfVertices) { return 1 / Math.cos(Math.PI / numberOfVertices) } static entityTunnel(fromX, fromY, toX, toY, radius = 0) { if (radius <= 0) { return this.line(fromX, fromY, toX, toY) } const sides = new Float32Array(5) sides[0] = toX - fromX sides[1] = toY - fromY sides[4] = Math.hypot(sides[0], sides[1]) sides[2] = (sides[1] / sides[4]) * -radius // optimization: negation and swapping rotates sides[3] = (sides[0] / sides[4]) * radius return new SAT.Polygon(new SAT.Vector(fromX - sides[2], fromY - sides[3]), [ new SAT.Vector(), new SAT.Vector(sides[0], sides[1]), new SAT.Vector(sides[0] + (2 * sides[2]), sides[1] + (2 * sides[3])), new SAT.Vector(2 * sides[2], 2 * sides[3]), ]) } 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') 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) { return new SAT.Polygon(new SAT.Vector(fromX, fromY), [new SAT.Vector(), new SAT.Vector(toX - fromX, toY - fromY)]) } static satPolygonToVectors(polygon) { const position = new Vector2(polygon.pos.x, polygon.pos.y) return polygon.points.map((p) => new Vector2(p.x, p.y).add(position)) } static vectorToFloat32Array(vector) { const array = new Float32Array(2) array[0] = vector.x array[1] = vector.y return array } static float32ArrayToVector(array) { return new Vector2(array[0], array[1]) } static float32ArrayWithIndexToVector(array, index) { return new Vector2(array[index], array[index + 1]) } }