import { Shape, ShapeUtils, Vector2 } from 'three' import SAT from 'sat' export default class Terrain { id = crypto.randomUUID() position = new Vector2() relativeVertices = [] #colliders = [] #vertices = [] #unadjustedWaypoints = [] constructor(vertices) { this.#vertices = vertices.map((v) => new Vector2(v.x, v.y)) if (ShapeUtils.isClockWise(this.#vertices)) { this.#vertices.reverse() } this.#calculateColliders() this.#calculatePosition() this.#calculateRelativeVertices() this.#calculateUnadjustedWaypoints() } get unadjustedWaypoints() { return this.#unadjustedWaypoints } 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], ] } colliders() { return this.#colliders } state() { return { ...this, } } #shape() { const complexShape = new Shape() complexShape.moveTo(this.#vertices.at(0).x, this.#vertices.at(0).y) this.#vertices.slice(1).forEach((v) => complexShape.lineTo(v.x, v.y)) return complexShape } #calculateColliders() { const points = this.#shape().extractPoints(16) const indicesToPolygon = (indices) => { const satPoints = [ new SAT.Vector(...points.shape[indices[0]].toArray()), new SAT.Vector(...points.shape[indices[1]].clone().sub(points.shape[indices[0]]).toArray()), new SAT.Vector(...points.shape[indices[2]].clone().sub(points.shape[indices[0]]).toArray()), ] return new SAT.Polygon(satPoints[0], [new SAT.Vector(), satPoints[1], satPoints[2]]) } this.#colliders = ShapeUtils.triangulateShape(points.shape, points.holes).map(indicesToPolygon) } #calculatePosition() { this.position = this.#vertices.reduce(((sum, v) => sum.add(v)), new Vector2()).divideScalar(this.#vertices.length) } #calculateRelativeVertices() { 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() } }