Skip to content

Commit

Permalink
fix: CircleCollider tangent raycast did not work correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
eonarheim committed Jul 24, 2024
1 parent a503de4 commit d68740e
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ are doing mtv adjustments during precollision.

### Fixed

- Fixed issue where CircleCollider tangent raycast did not work correctly
- Fixed issue where you were required to provide a transition if you provided a loader in the `ex.Engine.start('scene', { loader })`
- Fixed issue where `ex.Scene.onPreLoad(loader: ex.DefaultLoader)` would lock up the engine if there was an empty loader
- Fixed issue where `ex.Scene` scoped input events would preserve state and get stuck causing issues when switching back to the original scene.
Expand Down
25 changes: 16 additions & 9 deletions src/engine/Collision/Colliders/CircleCollider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Transform } from '../../Math/transform';
import { AffineMatrix } from '../../Math/affine-matrix';
import { BodyComponent } from '../Index';
import { RayCastHit } from '../Detection/RayCastHit';
import { approximatelyEqual } from '../../Math/util';

export interface CircleColliderOptions {
/**
Expand Down Expand Up @@ -115,20 +116,24 @@ export class CircleCollider extends Collider {
* @param ray
*/
public rayCast(ray: Ray, max: number = Infinity): RayCastHit | null {
//https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection
const c = this.center;
const dir = ray.dir;
const orig = ray.pos;
// https://en.wikipedia.org/wiki/Intersection_(geometry)#A_line_and_a_circle
const c = this.center; //?
const dir = ray.dir; //?
const orig = ray.pos; //?

const discriminant = Math.sqrt(Math.pow(dir.dot(orig.sub(c)), 2) - Math.pow(orig.sub(c).distance(), 2) + Math.pow(this.radius, 2));
const u = c.sub(orig);

if (discriminant < 0) {
// no intersection
const u1 = dir.scale(u.dot(dir));
const u2 = u.sub(u1);

const d = u2.size;

if (d > this.radius) {
return null;
} else {
let toi = 0;
// tangent case
if (discriminant === 0) {
let toi = 0;
if (approximatelyEqual(d, this.radius, 0.0001)) {
toi = -dir.dot(orig.sub(c));
if (toi > 0 && toi < max) {
const point = ray.getPoint(toi);
Expand All @@ -143,6 +148,8 @@ export class CircleCollider extends Collider {
return null;
} else {
// two point
const discriminant = Math.sqrt(Math.pow(dir.dot(orig.sub(c)), 2) - Math.pow(orig.sub(c).distance(), 2) + Math.pow(this.radius, 2));

const toi1 = -dir.dot(orig.sub(c)) + discriminant;
const toi2 = -dir.dot(orig.sub(c)) - discriminant;

Expand Down
8 changes: 8 additions & 0 deletions src/engine/Math/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ export function clamp(val: number, min: number, max: number) {
return Math.min(Math.max(min, val), max);
}

/**
* Approximately equals
*/
export function approximatelyEqual(val1: number, val2: number, tolerance: number) {
// https://dev.to/alldanielscott/how-to-compare-numbers-correctly-in-javascript-1l4i
return Math.abs(val1 - val2) < tolerance;
}

/**
* Convert an angle to be the equivalent in the range [0, 2PI]
*/
Expand Down
19 changes: 19 additions & 0 deletions src/spec/CollisionShapeSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,25 @@ describe('Collision Shape', () => {
expect(point.y).toBe(0);
});

it('can be raycast at a tangent', () => {
const circle = new ex.CircleCollider({
offset: ex.vec(10, -5),
radius: 5
});
const ray = new ex.Ray(new ex.Vector(0, 0), ex.Vector.Right.clone());
const ray2 = new ex.Ray(new ex.Vector(5, 0), ex.Vector.Up.clone());

const hit = circle.rayCast(ray);

expect(hit.point.x).toBe(10);
expect(hit.point.y).toBe(0);

const hit2 = circle.rayCast(ray2);

expect(hit2.point.x).toBe(5);
expect(hit2.point.y).toBe(-5);
});

it("doesn't have axes", () => {
// technically circles have infinite axes
expect(circle.axes).toEqual([]);
Expand Down

0 comments on commit d68740e

Please sign in to comment.