excaliburjs / Excalibur

🎮 Your friendly TypeScript 2D game engine for the web 🗡️

Home Page:https://excaliburjs.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Optimise BoundingBox rayCast methods

ikudrickiy opened this issue · comments

Context

public rayCast(ray: Ray, farClipDistance = Infinity): boolean
and
public rayCastTime(ray: Ray, farClipDistance = Infinity): number
blindly follow attached article:

  1. tmin and tmax variables are initiated for no reason
  2. inverted variables are pre-computed for single instance optimisation which is very limited in this context but causing extra rounding error accumulation and decreased readability
  3. tmax >= Math.max(0, tmin) contains the original logic tmax >= 0 && tmax >= tmin, but hides it and so reduces readability
  4. tmin is actually max tmin from a pair or tMaxMin and so it should be named. Similar stands for 'tmax'
  5. https://youtu.be/GqwUHXvQ7oA should be added in comments as a great visualisation

Proposal

Optimize the code to get rid of the above shortcomings:

/**
   * Determines whether a ray intersects with a bounding box
   */
  public rayCast(ray: Ray, farClipDistance = Infinity): boolean {
    // algorithm from https://tavianator.com/fast-branchless-raybounding-box-intersections/
    // principle visualisation: https://youtu.be/GqwUHXvQ7oA
    let tMinMax, tMaxMin;

    const tx1 = (this.left - ray.pos.x) / ray.dir.x;
    const tx2 = (this.right - ray.pos.x) / ray.dir.x;
    tMaxMin = Math.min(tx1, tx2);
    tMinMax = Math.max(tx1, tx2);

    const ty1 = (this.top - ray.pos.y) / ray.dir.y;
    const ty2 = (this.bottom - ray.pos.y) / ray.dir.y;
    tMaxMin = Math.max(tMaxMin, Math.min(ty1, ty2));
    tMinMax = Math.min(tMinMax, Math.max(ty1, ty2));

    return tMinMax >= 0 && tMinMax >= tMaxMin && tMaxMin < farClipDistance;
  }

  public rayCastTime(ray: Ray, farClipDistance = Infinity): number {
    // algorithm from https://tavianator.com/fast-branchless-raybounding-box-intersections/
    // principle visualisation: https://youtu.be/GqwUHXvQ7oA
    let tMinMax, tMaxMin;

    const tx1 = (this.left - ray.pos.x) / ray.dir.x;
    const tx2 = (this.right - ray.pos.x) / ray.dir.x;
    tMaxMin = Math.min(tx1, tx2);
    tMinMax = Math.max(tx1, tx2);

    const ty1 = (this.top - ray.pos.y) / ray.dir.y;
    const ty2 = (this.bottom - ray.pos.y) / ray.dir.y;
    tMaxMin = Math.max(tMaxMin, Math.min(ty1, ty2));
    tMinMax = Math.min(tMinMax, Math.max(ty1, ty2));

    if (tMinMax >= 0 && tMinMax >= tMaxMin && tMaxMin < farClipDistance) {
      return tMaxMin;
    }
    
    return -1;
  }

I had to call it "refactoring" but initially it seemed to me single instance optimisation was totally useless here (it is not, we have atleast 2 divisions per instance) and I just forgot to rename the issue.