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);
}