Source: GlobalEngine.ts

import EffectController from "./EffectController";
import Loop from "./Loop";
import PipelineManager from "./Pipe/PipelineManager";
import MeshGeneratorManager from "./MeshGenerators/MeshGeneratorManager";
import Screen from "./Utility/Screen";
import * as THREE from "three";
import Video from "./Video";
import OnScreen from "./OnScreen";
import { Tracker } from "./Tracker";
import Globals from "./Globals";
import ControllableObjects from "./ControllableObjects";
import { FrameProvider } from "./FrameProvider";
import { ModulesManager } from "./ModulesManager";
import {
    BoundingBox,
    Effect,
    FeatureTemplate,
    FinishTemplate,
} from "./FeatureTemplate";

type Dependencies = {
    modulesManager: ModulesManager;
    frameProvider: FrameProvider;
};

function CreateRenderer(
    width: number,
    height: number,
    preserveDrawingBuffer: boolean,
) {
    const renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true,
        preserveDrawingBuffer,
    });

    renderer.setSize(width, height);
    renderer.autoClear = false;

    return renderer;
}

function CreateLoopAndController(
    modulesManager: ModulesManager,
    frameProvider: FrameProvider,
    renderer: THREE.WebGLRenderer,
) {
    const video = new Video();
    const onScreen = new OnScreen();

    const meshGeneratorManager = new MeshGeneratorManager(modulesManager);
    const pipelineManager = new PipelineManager(video.filter, onScreen);
    const controller = new EffectController(
        pipelineManager,
        meshGeneratorManager,
    );

    const loop = new Loop(
        frameProvider,
        renderer,
        pipelineManager,
        meshGeneratorManager,
        video,
        controller,
        onScreen,
    );

    return [loop, controller] as const;
}

/**
 * A glorified wrapper around loop and effectController.
 * Only a single instance of it makes sense, and may not be initialized multiple times.
 * This class is be internal, MakeupEngine and CMSMakeupEngine need to be used externally.
 */
export class GlobalEngine {
    static instance: GlobalEngine | null;

    static Initialize(
        deps: Dependencies,
        screen: { width: number; height: number },
        preserveDrawingBuffer: boolean,
    ) {
        if (GlobalEngine.instance) {
            throw new Error("Already initialized.");
        }

        Tracker.Create(deps.modulesManager);

        Globals.controllableObjects = new ControllableObjects();

        const renderer = CreateRenderer(
            screen.width,
            screen.height,
            preserveDrawingBuffer,
        );
        Screen.SetRenderer(renderer);

        const [loop, effectController] = CreateLoopAndController(
            deps.modulesManager,
            deps.frameProvider,
            renderer,
        );

        GlobalEngine.instance = new GlobalEngine(effectController, loop);
    }

    private constructor(
        private effectController: EffectController,
        private loop: Loop,
    ) {}

    async CreateMakeup(
        featureTemplate: FeatureTemplate,
        finishTemplate: FinishTemplate,
        boundingBox: BoundingBox = [0.0, 0.0, 1.0, 1.0],
    ): Promise<Effect> {
        const effect = await this.effectController.Create(
            featureTemplate,
            finishTemplate,
            boundingBox,
        );
        return effect;
    }

    UpdateMakeup(effect: Effect): Promise<void> {
        return this.effectController.Refresh(effect);
    }

    IsMakeupDisplayed(effect: Effect): boolean {
        return this.effectController.IsMakeupDisplayed(effect);
    }

    DisplayMakeup(effect: Effect): void {
        this.effectController.Display(effect);
    }

    HideMakeup(effect: Effect): void {
        this.effectController.Hide(effect);
    }

    RemoveMakeup(effect: Effect): void {
        this.effectController.Remove(effect);
    }

    IntersectMakeup(effect: Effect, x: number, y: number) {
        const pixelCoords = { x, y };
        return intersectMakeup(effect, this.effectController, pixelCoords);
    }

    onIterationComplete(callback: VoidFunction): void {
        this.loop.onIterationComplete(callback);
    }
}

/**
 * Returns the uv area texture coordinates corresponding to position on the output view canvas that displays the makeup effect.
 * @param {Effect} effect - makeup effect
 * @param {EffectController} effectController - the controller that controls the effect
 * @param {{x:number, y: number}} pixelCoords - pixel distance from the left ant top output view canvas edge
 */
function intersectMakeup(
    effect: Effect,
    effectController: EffectController,
    pixelCoords: { x: number; y: number },
) {
    // NOTE: previously, the initial passed width and height were used here if that makes any difference
    // that seems it would have broken if size ever changed.
    const { width, height } = Screen.size;

    // calculate distance in normalized device coordinates where (-1,-1) point is in the lower-left corner and (1,1) in top-right corner
    const x_ndc_space = (2.0 * pixelCoords.x) / width - 1.0; // In range [-1, 1]
    const y_ndc_space = 1.0 - (2.0 * pixelCoords.y) / height; // In range [-1, 1]

    return effectController.Intersect(effect, x_ndc_space, y_ndc_space);
}