Source: shadows/shadow_receiver.js

/*
 * File: shadow_receiver.js
 * Shadow support
 * 
 * Instance variables:
 *     mReceiver: Reference to any GameObject
 *                Treats this target for shadow receiver
 *     mCasters: Reference to an array of Renderables that are at least LightRenderable
 *     
 * Draws the mReceiver, and the shadows of mCasters on this mReceiver
 */
"use strict";  // Operate in Strict mode such that variables must be declared before used!

import * as shaderResources from "../core/shader_resources.js";
import ShadowCaster from "./shadow_caster.js";
import * as glSys from "../core/gl.js";

class ShadowReceiver {
    /**
     * @classdesc Support class for a GameObject having shadows cast on it.
     * Has only one target object, but supports several casters
     * <p>Found in Chapter 8, page 507 of the textbook</p>
     * Example:
     * {@link https://mylesacd.github.io/build-your-own-2d-game-engine-2e-doc/BookSourceCode/chapter8/8.7.shadow_shaders/index.html 8.7 Shadow Shaders}
     * @constructor
     * @param {GameObject} theReceiverObject - the GameObject that will have shadows cast onto it
     * @returns {ShadowReceiver} a new ShadowReceiver instance
     */
    constructor(theReceiverObject) {
        this.kShadowStencilBit = 0x01;              // The stencil bit to switch on/off for shadow
        this.kShadowStencilMask = 0xFF;             // The stencil mask 
        this.mReceiverShader = shaderResources.getShadowReceiverShader();

        this.mReceiver = theReceiverObject;

        // To support shadow drawing
        this.mShadowCaster = [];                    // array of ShadowCasters
    }

    /**
     * Using argument lgtRenderable as casting object for a new ShadowCaster to add to this ShadowReceiver's list 
     * @method
     * @param {GameObject} lgtRenderable - GameObject that contains at least a LightRenderable to cast shadow
     */
    addShadowCaster(lgtRenderable) {
        let c = new ShadowCaster(lgtRenderable, this.mReceiver);
        this.mShadowCaster.push(c);
    }
    // for now, cannot remove shadow casters


    /**
     * Draw the receiver GameObject of this ShadowReceicer with the shadow cast onto it
     * @method
     * @param {Camera} aCamera - the Camera to draw to 
     */
    draw(aCamera) {
        let c;

        // Step A: draw receiver as a regular renderable
        this.mReceiver.draw(aCamera);

        // Step B: draw the receiver into the stencil buffer to enable corresponding pixels
        glSys.beginDrawToStencil(this.kShadowStencilBit, this.kShadowStencilMask);
        //        Step B1: swap receiver shader to a ShadowReceiverShader
        let s = this.mReceiver.getRenderable().swapShader(this.mReceiverShader);
        //        Step B2: draw the receiver again to the stencil buffer
        this.mReceiver.draw(aCamera);
        this.mReceiver.getRenderable().swapShader(s);
        glSys.endDrawToStencil(this.kShadowStencilBit, this.kShadowStencilMask);

        // Step C: draw shadow color to the pixels in the stencil that are switched on
        for (c = 0; c < this.mShadowCaster.length; c++) {
            this.mShadowCaster[c].draw(aCamera);
        }

        // switch off stencil checking
        glSys.disableDrawToStencil();
    }

    /**
     * Update the receiving GameObject
     * @method
     */
    update() {
        this.mReceiver.update();
    }
}

export default ShadowReceiver;