Source: collisions.js


/**
 * @license MIT
 * Copyright © 2026 Steve Butler (henspace.com)
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the “Software”),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

/**
 * @module hcje/collisions
 * @description
 * Module providing support for sprite collisions.
 */

import * as utils from './utils.js';

/**
 * Function that is called when a collision has been detected.
 * @callback notifyCollision
 * @param {module:hcje/sprites.Sprite} masterSprite - the sprite that detected the collision.
 * @param {module:hcje/sprites.Sprite} otherSprite - the other sprite involved in the collision.
 */

/**
 * Object involved in collisions.
 * @typedef {Object} Collider
 * @property {module:hcje/sprites.Sprite} sprite - the sprite that is colliding.
 * @property {module:hcje/utils~Shrinkage} [shrinkage] - shrinkage to apply to derive collision rectangle.
 * @property {module:hcje/collisions~notifyCollision} [callback] - function to call when collision is detected.
 */

/**
 * Class to manage collisions. Instances can be added to an {@link module:hcje/sprites.Sprite}.
 * @implements module:hcje/sprites~AnimationTarget
 */ 
export class CollisionDetector {
  /**
   * Controlling sprite.
   * @type {module:hcje/collisions~Collider}
   */
  #master;

  /**
   * Collision targets. Note they are keyed by the Sprite.
   * @type {Map<module:hcje/sprites.Sprite, module:hcje/collisions~Collider>}
   */ 
  #targets
  /**
   * Construct the collider.
   * @param {module:hcje/collisions~Collider} masterCollider - main collider which tests for collisions.
   */ 
  constructor(masterCollider) {
    this.#master = masterCollider;
    this.#targets = new Map();
  }

  /**
   * Add target. The target is added to a Map using the target's sprite property as a key. As such, each sprite can 
   * only occur once even if a different callback is used.
   * @param {module:hcje/collisions~Collider} targetCollider - the collision target which will be checked when
   * collisions are detected.
   */
  addTarget(targetCollider) {
    this.#targets.set(targetCollider.sprite, targetCollider);  
  }

  /**
   * Remove a target sprite. Note that targets are keyed by the sprite, so only the sprite object is required to
   * remove it.
   * @param {module:hcje/sprites~Sprite} sprite - the sprite to remove.
   */
  removeTargetSprite(sprite) {
    this.#targets.delete(sprite);
  }

  /**
   * Look for collisions. Note that if a sprite is marked as killed, it is removed from the list of targets. Likewise
   * no collisions are detected if the controlling sprite has been killed. In that case all targets are removed from
   * its map.
   */
  detect() {
    if (this.#targets.size === 0) {
      return;
    } else if (this.#master.sprite.isKilled() ) {
      this.#targets.clear();
      return;
    }

    this.#targets.forEach((target) => {
      if (target.sprite.isKilled()) {
        this.#targets.delete(target.sprite);
      } else if (utils.areRectsOverlapping(utils.shrinkRect(this.#master.sprite.bounds, this.#master.shrinkage),
            utils.shrinkRect(target.sprite.bounds, target.shrinkage))) {
        this.#master.callback?.(this.#master.sprite, target.sprite);
        target.callback?.(this.#master.sprite, target.sprite);
      }
    });
  }
}