Use Bhijn's algorithm for corner assist
This commit is contained in:
		| @@ -144,6 +144,57 @@ export class Size { | ||||
|   } | ||||
| } | ||||
|  | ||||
| export class Circle { | ||||
|   readonly center: Point; | ||||
|   readonly radius: number; | ||||
|  | ||||
|   constructor(center: Point, radius: number) { | ||||
|     this.center = center; | ||||
|     this.radius = radius; | ||||
|   } | ||||
|  | ||||
|   getContactWithRect(rect: Rect): Point | null { | ||||
|     // port: https://www.jeffreythompson.org/collision-detection/circle-rect.php | ||||
|     let cx = this.center.x; | ||||
|     let cy = this.center.y; | ||||
|     let testX = this.center.x; | ||||
|     let testY = this.center.y; | ||||
|  | ||||
|     let rx = rect.top.x; | ||||
|     let ry = rect.top.y; | ||||
|     let rw = rect.size.w; | ||||
|     let rh = rect.size.h; | ||||
|  | ||||
|     if (cx < rx) { testX = rx; } | ||||
|     else if (cx > rx + rw) { testX = rx + rw; } | ||||
|     if (cy < ry) { testY = ry; } | ||||
|     else if (cy > ry + rh) { testY = ry + rh; } | ||||
|  | ||||
|     let distX = cx - testX; | ||||
|     let distY = cy - testY; | ||||
|     let sqDistance = (distX * distX) + (distY * distY); | ||||
|  | ||||
|     if (sqDistance <= this.radius * this.radius) { | ||||
|       return new Point(testX, testY); | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   overlappedCells(size: Size): Rect[] { | ||||
|     let meAsRect = new Rect( | ||||
|       this.center.offset(new Point(-this.radius, -this.radius)), | ||||
|       new Size(this.radius * 2, this.radius * 2) | ||||
|     ); | ||||
|     let all: Rect[] = []; | ||||
|     for (let cell of meAsRect.overlappedCells(size).values()) { | ||||
|       if (this.getContactWithRect(cell) != null) { | ||||
|         all.push(cell); | ||||
|       } | ||||
|     } | ||||
|     return all; | ||||
|   } | ||||
| } | ||||
|  | ||||
| export class Rect { | ||||
|   readonly top: Point; | ||||
|   readonly size: Size; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import { BreakableBlockPickupCallbacks } from "./pickups.ts"; | ||||
| import { Point, Rect, Size } from "./engine/datatypes.ts"; | ||||
| import {Circle, Point} from "./engine/datatypes.ts"; | ||||
| import { displace } from "./physics.ts"; | ||||
| import { getHuntMode } from "./huntmode.ts"; | ||||
| import { DrawPile } from "./drawpile.ts"; | ||||
| @@ -36,7 +36,7 @@ export class Floater { | ||||
|     let { displacement, dxy } = displace( | ||||
|       bbox, | ||||
|       this.velocity, | ||||
|       (r) => getHuntMode().isBlocked(r), | ||||
|       (r) => getHuntMode().getContact(r), | ||||
|       { bounce: 0.6 }, | ||||
|     ); | ||||
|  | ||||
| @@ -119,10 +119,10 @@ export class Floater { | ||||
|     return !this.collected && this.frame < 1440; | ||||
|   } | ||||
|  | ||||
|   get bbox(): Rect { | ||||
|   get bbox(): Circle { | ||||
|     let w = 0.25; | ||||
|     let h = 0.25; | ||||
|     return new Rect(this.xy.offset(new Point(-w / 2, -h / 2)), new Size(w, h)); | ||||
|     return new Circle(this.xy, w / 2); | ||||
|   } | ||||
|   drawParticle(projected: Point, isShadow: boolean): any { | ||||
|     this.#callbacks.drawParticle( | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { Point, Rect, Size } from "./engine/datatypes.ts"; | ||||
| import {Circle, Point, Rect, Size} from "./engine/datatypes.ts"; | ||||
| import { DrawPile } from "./drawpile.ts"; | ||||
| import { D, I } from "./engine/public.ts"; | ||||
| import { sprThrallLore } from "./sprites.ts"; | ||||
| @@ -201,39 +201,12 @@ export class HuntMode { | ||||
|  | ||||
|     this.velocity = new Point(dx, dy); | ||||
|  | ||||
|     // try to push us away from walls if we're close | ||||
|     for (let offset of CARDINAL_DIRECTIONS.values()) { | ||||
|       let bigBbox = new Rect( | ||||
|         this.floatingPlayer | ||||
|           .offset(offset.scale(new Size(0.06, 0.06))) | ||||
|           .offset(new Point(-szX / 2, -szY / 2)), | ||||
|         new Size(szX, szY), | ||||
|       ); | ||||
|  | ||||
|       let hitsWall = false; | ||||
|       for (let cell of bigBbox.overlappedCells(new Size(1, 1)).values()) { | ||||
|         if (this.#blocksMovement(cell.top)) { | ||||
|           hitsWall = true; | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|       if (hitsWall) { | ||||
|         this.velocity = this.velocity.offset( | ||||
|           offset.scale(new Point(0.005, 0.005)).negate(), | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     let origin = new Point(szX / 2, szY / 2); | ||||
|     let bbox = new Rect( | ||||
|       this.floatingPlayer.offset(origin.negate()), | ||||
|       new Size(szX, szY), | ||||
|     ); | ||||
|     let { displacement, dxy } = displace(bbox, this.velocity, (b: Rect) => | ||||
|       this.isBlocked(b), | ||||
|     let bbox = new Circle( this.floatingPlayer, szX / 2); | ||||
|     let { displacement, dxy } = displace(bbox, this.velocity, (b: Circle) => | ||||
|       this.getContact(b) | ||||
|     ); | ||||
|     this.floatingPlayer = this.floatingPlayer.offset(displacement); | ||||
|     // this.velocity = dxy; // don't let the wall break our speed, this sucks on corners | ||||
|     this.velocity = dxy; | ||||
|     // let friction do it | ||||
|     getPlayerProgress().spendBlood(displacement.distance(new Point(0, 0)) * 10); | ||||
|   } | ||||
| @@ -455,13 +428,13 @@ export class HuntMode { | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   isBlocked(bbox: Rect): boolean { | ||||
|   getContact(bbox: Circle): Point | null { | ||||
|     for (let cell of bbox.overlappedCells(new Size(1, 1)).values()) { | ||||
|       if (this.#blocksMovement(cell.top)) { | ||||
|         return true; | ||||
|         return bbox.getContactWithRect(cell)!; | ||||
|       } | ||||
|     } | ||||
|     return false; | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   #blocksMovement(xy: Point) { | ||||
|   | ||||
| @@ -1,36 +1,45 @@ | ||||
| import { Point, Rect } from "./engine/datatypes.ts"; | ||||
| import {Circle, lerp, Point, Rect} from "./engine/datatypes.ts"; | ||||
|  | ||||
| export function displace( | ||||
|   bbox: Rect, | ||||
|   bbox: Circle, | ||||
|   dxy: Point, | ||||
|   blocked: (where: Rect) => boolean, | ||||
|   getContact: (where: Circle) => Point | null, | ||||
|   options?: { bounce?: number }, | ||||
| ): { bbox: Rect; displacement: Point; dxy: Point } { | ||||
| ): { bbox: Circle; displacement: Point; dxy: Point } { | ||||
|   let nSteps = 40; | ||||
|   let nRedirections = 40; | ||||
|   let bounce = options?.bounce ?? 0; | ||||
|  | ||||
|   let xy = bbox.top; | ||||
|   let xy = bbox.center; | ||||
|   let redirections = 0; | ||||
|   for (let i = 0; i < nSteps; i++) { | ||||
|     let trialXy = xy.offset(new Point(dxy.x / nSteps, 0)); | ||||
|     let trialBbox = new Rect(trialXy, bbox.size); | ||||
|     let trialXy = xy.offset(new Point(dxy.x / nSteps, dxy.y / nSteps)); | ||||
|     let trialBbox = new Circle(trialXy, bbox.radius); | ||||
|  | ||||
|     if (blocked(trialBbox)) { | ||||
|       dxy = new Point(bounce * -dxy.x, dxy.y); | ||||
|     } else { | ||||
|       xy = trialXy; | ||||
|     } | ||||
|     let contact = getContact(trialBbox); | ||||
|     if (contact) { | ||||
|       let normal = contact.offset(trialXy.negate()); | ||||
|       let mag = normal.distance(new Point(0, 0)); | ||||
|       let nx = mag == 0 ? 0 : normal.x / mag; | ||||
|       let ny = mag == 0 ? 0 : normal.y / mag; | ||||
|  | ||||
|     trialXy = xy.offset(new Point(0, dxy.y / nSteps)); | ||||
|     trialBbox = new Rect(trialXy, bbox.size); | ||||
|     if (blocked(trialBbox)) { | ||||
|       dxy = new Point(dxy.x, bounce * -dxy.y); | ||||
|       let dot = dxy.x * nx + dxy.y * ny; | ||||
|       if (redirections < nRedirections) { | ||||
|         dxy = new Point(dxy.x - lerp(bounce, 1, 2) * dot * nx, dxy.y - lerp(bounce, 1, 2) * dot * ny); | ||||
|         i -= 1; // try again with reflection | ||||
|         redirections += 1; | ||||
|       } else { | ||||
|         dxy = new Point(0, 0); | ||||
|         break; | ||||
|       } | ||||
|     } else { | ||||
|       xy = trialXy; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return { | ||||
|     bbox: new Rect(xy, bbox.size), | ||||
|     displacement: xy.offset(bbox.top.negate()), | ||||
|     bbox: new Circle(xy, bbox.radius), | ||||
|     displacement: xy.offset(bbox.center.negate()), | ||||
|     dxy, | ||||
|   }; | ||||
| } | ||||
|   | ||||
| @@ -281,7 +281,7 @@ export class LadderPickup { | ||||
|   } | ||||
|  | ||||
|   blocksMovement() { | ||||
|     return true; | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   obstructsVision() { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user