game_actor_component_collision_component.js

import ActorComponent from "./actor_component.js";
import Bounds from "../../vector/bounds.js";
import Triangle from "../../vector/triangle.js";

/**
 * @module CollisionComponent
 * @fileoverview Contains CollisionComponent class.
 */

/**
 * @class
 * @extends ActorComponent
 * Handles collision checks.
 */
class CollisionComponent extends ActorComponent {
    /**
     * Create a new collision component.
     * @param {Bounds} bounds Bounding box for collision
     * @param {string[]} layers Layers to collide with
     * @param {string[]} ignoreLayers Layers to ignore
     * @constructor
     */
    constructor({
        bounds = new Bounds(),
        layers = ["default"],
        ignoreLayers = []
    } = {}) {
        super();

        this.triangles = [];

        this._bounds = bounds.copy();
        this.layers = new Set(layers);
        this.ignoreLayers = new Set(ignoreLayers);

        this.triangles.push(
            new Triangle(this._bounds.min, this._bounds.topRight, this._bounds.max),
            new Triangle(this._bounds.min, this._bounds.bottomLeft, this._bounds.max)
        );
    }

    init() {
        if (this.actor.initialized)
            this.actor.findOverlappingChunks();
    }


    //
    // Getters
    //

    /**
     * Get the bounds of the collision.
     * @returns {Bounds} Bounding box of collision
     */
    get bounds() {
        return this._bounds.copy();
    }


    //
    // Checks
    //

    /**
     * Check if the collision should occur with another component based on layers
     * @param {CollisionComponent} other Collision component to check with
     * @returns {boolean} Whether collision can occur
     * @private
     */
    _checkLayers(other) {
        const blacklist1 = new Set([...this.ignoreLayers].filter(a => other.layers.has(a)));
        if (blacklist1.size > 0)
            return false;
        const blacklist2 = new Set([...other.ignoreLayers].filter(a => this.layers.has(a)));
        if (blacklist2.size > 0)
            return false;

        const whitelist = new Set([...other.layers].filter(a => other.layers.has(a)));
        return whitelist.size > 0;
    }

    /**
     * Check for collision with another component.
     * @param {CollisionComponent} other Collision component to check with
     * @returns {Object} Object containing collision result
     * @param {Vec2} pos The position to attempt to move to
     */
    check(other, pos) {
        const result = {
            result: false
        };

        if (!this._checkLayers(other))
            return result;

        for (let t1 of this.triangles) {
            t1 = t1.transform(this.actor.getTransformMatrix());
            for (let t2 of other.triangles) {
                const mat = other.actor.getTransformMatrix(false);
                mat3.multiply(mat, pos.toTranslationMatrix(), mat);
                t2 = t2.transform(mat);
                const r = t1.check(t2);

                if (r.result) {
                    r.actor = this.actor;
                    this.actor.doEvent("collide", other.actor);
                    other.actor.doEvent("collide", this.actor);

                    return r;
                }
            }
        }

        return result;
    }

    drawDebug(ctx) {
        const transform = this.actor.getTransformMatrix(false);

        for (const triangle of this.triangles)
            triangle.transform(transform).drawDebug(ctx);
    }
}

export default CollisionComponent;