Source: game_objects/game_object.js

/*
 * File: game_object.js
 *
 * defines the behavior and appearance of a game object
 * 
 */
"use strict";
import BoundingBox from "../utils/bounding_box.js";


class GameObject {
    /**
     * @classdesc Template for the behavior and appearance of a game object, intened to be extended for game specific use. Has support for pixel-perfect collision and physics
     * <p>Found in Chapter 6, page 268 of the textbook</p>
     * Example:
     * {@link https://mylesacd.github.io/build-your-own-2d-game-engine-2e-doc/BookSourceCode/chapter6/6.1.game_objects/index.html 6.1 Game Objects}
     * 
     * @constructor
     * @param {Renderable} renderable - the renderable to be associated with this GameObject
     * @returns {GameObject} a new instance of GameObject
     */
    constructor(renderable) {
        this.mRenderComponent = renderable;
        this.mVisible = true;
        this.mCurrentFrontDir = vec2.fromValues(0, 1);  // this is the current front direction of the object
        this.mRigidBody = null;
        this.mDrawRenderable = true;
        this.mDrawRigidShape = false;
    }
    /**
     * Returns the Transform of the Renderable associated with this GameObject
     * @instance
     * @returns {Transform} the Transform of this GameObject
     */
    getXform() { return this.mRenderComponent.getXform(); }

    /**
     * Returns the BoundingBox for this gameObject
     * @method
     * @returns {BoundingBox} a new copy of the bounding box of this GameObject
     */
    getBBox() {
        let xform = this.getXform();
        let b = new BoundingBox(xform.getPosition(), xform.getWidth(), xform.getHeight());
        return b;
    }

    /**
     * Sets the visibility of this gameObject to true or false
     * @method
     * @param {boolean} f - boolean to set if this gameObject is visibile or not
     */
    setVisibility(f) { this.mVisible = f; }

    /**
     * Returns the visibility of this gameObject
     * @method
     * @returns {boolean} mVisible - the visivility of this gameObject
     */
    isVisible() { return this.mVisible; }

    /**
     * Changes this gameObject's current facing direction
     * @method
     * @param {vec2} f - vector that will be put into mCurrentFrontDir after being normalized
     */
    setCurrentFrontDir(f) { vec2.normalize(this.mCurrentFrontDir, f); }

    /**
     * Returns the front direction of this gameObject
     * @method
     * @returns {vec2} mCurrentFrontDir - the current front direction of this gameObject
     */
    getCurrentFrontDir() { return this.mCurrentFrontDir; }

    /**
     * Returns the renderable associated with this gameObject
     * @returns {Renderable} mRenderComponent - the renderable associated with this gameObject
     */
    getRenderable() { return this.mRenderComponent; }

    /**
     * Sets the rigid body this gameObject will use
     * @method
     * @param {RigidShape} r - the rigid body
     */
    setRigidBody(r) { this.mRigidBody = r; }

    /**
     * Returns the RigidShape of this gameObject 
     * @method
     * @returns {RigidShape} mRigidBody - the RigidShape of this gameObject 
     */
    getRigidBody() { return this.mRigidBody; }

    /**
     * Switches whether this gameObject's renderable is drawn
     * @method
     */
    toggleDrawRenderable() { this.mDrawRenderable = !this.mDrawRenderable; }

    /**
     * Switches whether this gameObject's rigid shape is drawn
     * @method
     */
    toggleDrawRigidShape() { this.mDrawRigidShape = !this.mDrawRigidShape; }
    
    /**
     * Draws this gameObject if it is visible the draw flags are set to true
     * @param {Camera} aCamera  - The camera 
     */
    draw(aCamera) {
        if (this.isVisible()) {
            if (this.mDrawRenderable)
                this.mRenderComponent.draw(aCamera);
            if ((this.mRigidBody !== null) && (this.mDrawRigidShape))
                this.mRigidBody.draw(aCamera);
        }
    }
    /**
     * Method called by Gameloop, updates the rigid body of this gameObject
     * @method
     */
    update() {
        // simple default behavior
        if (this.mRigidBody !== null)
            this.mRigidBody.update();
    }

    // Support for per-pixel collision
    /**
     * Determines if this GameObject has an overlapping pixel with another GameObject
     * @method
     * @param {GameObject} otherObj  - the other GameObject
     * @param {vec2} wcTouchPos  - vector to store the first world coordinates where the pixels touch
     * @returns {boolean} whether this GameObject has a pixel overlapping otherObj
     */
    pixelTouches(otherObj, wcTouchPos) {
        // only continue if both objects have getColorArray defined 
        // if defined, should have other texture intersection support!
        let pixelTouch = false;
        let myRen = this.getRenderable();
        let otherRen = otherObj.getRenderable();

        if ((typeof myRen.pixelTouches === "function") && (typeof otherRen.pixelTouches === "function")) {
            if ((myRen.getXform().getRotationInRad() === 0) && (otherRen.getXform().getRotationInRad() === 0)) {
                // no rotation, we can use bbox ...
                let otherBbox = otherObj.getBBox();
                if (otherBbox.intersectsBound(this.getBBox())) {
                    myRen.setColorArray();
                    otherRen.setColorArray();
                    pixelTouch = myRen.pixelTouches(otherRen, wcTouchPos);
                }
            } else {
                // One or both are rotated, compute an encompassing circle
                // by using the hypotenuse as radius
                let mySize = myRen.getXform().getSize();
                let otherSize = otherRen.getXform().getSize();
                let myR = Math.sqrt(0.5 * mySize[0] * 0.5 * mySize[0] + 0.5 * mySize[1] * 0.5 * mySize[1]);
                let otherR = Math.sqrt(0.5 * otherSize[0] * 0.5 * otherSize[0] + 0.5 * otherSize[1] * 0.5 * otherSize[1]);
                let d = [];
                vec2.sub(d, myRen.getXform().getPosition(), otherRen.getXform().getPosition());
                if (vec2.length(d) < (myR + otherR)) {
                    myRen.setColorArray();
                    otherRen.setColorArray();
                    pixelTouch = myRen.pixelTouches(otherRen, wcTouchPos);
                }
            }
        }
        return pixelTouch;
    }
}

export default GameObject;