waves/public/assets/g/burgerandfrights/js/entities/overlay.class.js
2025-04-09 17:11:14 -05:00

409 lines
13 KiB
JavaScript

/**
* @file overlay.class.js
* @version 1.0.0
*/
import Entity from '../framework/entity.class.js';
import ImageManager from '../framework/imagemanager.class.js';
import BrightnessMaterial from '../framework/three.js/brightnessmaterial.js';
import * as THREE from '../lib/three.js/three.module.js';
export default class Overlay extends Entity {
static _init() {
Overlay._tmpV20 = new THREE.Vector2();
}
constructor(context, scene, options = Object.create(null)) {
options.updatePriority = 11;
super(context, scene, options);
context.renderer.outputEncoding = THREE.sRGBEncoding;
this._renderTarget = new THREE.WebGLRenderTarget(1024, 1024, {
encoding: context.renderer.outputEncoding,
});
this._camera = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5, -1, 1);
this._player = null;
this._tSceneOverlay = new THREE.Scene();
this._tSceneOverlay.name = 'SceneOverlay';
this._canvas = document.createElement('canvas');
this._ctx2d = this._canvas.getContext('2d');
this._imageData = null;
const geometry = new THREE.PlaneGeometry();
this._texture = new THREE.CanvasTexture(this._canvas);
this._texture.generateMipmaps = false;
this._texture.minFilter = THREE.LinearFilter;
this._texture.encoding = THREE.sRGBEncoding;
this._materialEffect = new THREE.MeshBasicMaterial({
map: this._texture,
transparent: true,
depthTest: false,
});
const meshEffect = new THREE.Mesh(geometry, this._materialEffect);
this._tSceneOverlay.add(meshEffect);
this._tScenePost = new THREE.Scene();
this._tScenePost.name = 'ScenePost';
this._materialBrightness = new BrightnessMaterial();
this._materialBrightness.uniforms.brightness.value.set(0.25, 0.25, 0.25);
this._materialBrightness.uniforms.contrast.value.set(0.5, 0.5, 0.5);
this._contrastOriginal = new THREE.Vector3();
this._contrastTarget = new THREE.Vector3();
this._contrastChangeDuration = null;
this._contrastChangeSeconds = null;
const meshBrightness = new THREE.Mesh(geometry, this._materialBrightness);
this._tScenePost.add(meshBrightness);
this._sceneName = null;
this._sceneSeconds = null;
this._sceneDuration = null;
this._finishedCallback = null;
this._frameIndex = null;
this.setContrast();
}
handleResize(context) {
super.handleResize(context);
const size = context.renderer.getSize(Overlay._tmpV20);
this._renderTarget.setSize(size.x, size.y);
this._materialBrightness.setRenderTarget(this._renderTarget);
this._canvas.width = size.x;
this._canvas.height = size.y;
this._imageData = this._ctx2d.createImageData(this._canvas.width, this._canvas.height);
this._frameIndex = 0;
}
update(context) {
super.update(context);
if (this._contrastChangeDuration !== null) {
this._contrastChangeSeconds += context.time.elapsedSeconds;
const t = Math.min(1, this._contrastChangeSeconds / this._contrastChangeDuration);
this._materialBrightness.uniforms.contrast.value.copy(
this._contrastOriginal).lerp(this._contrastTarget, t);
if (t === 1) {
this._contrastChangeDuration = null;
}
}
if (this._sceneName === null) {
return;
}
this._renderScene(context);
if (this._sceneSeconds >= this._sceneDuration) {
this._sceneName = null;
if (this._finishedCallback !== null) {
const callback = this._finishedCallback;
this._finishedCallback = null;
callback();
}
}
}
playScene(context, effectName, duration = 0, finishedCallback = null) {
const callback = this._finishedCallback;
this._sceneName = effectName;
this._sceneSeconds = 0;
this._sceneDuration = duration;
this._finishedCallback = finishedCallback;
this._frameIndex = 0;
if (callback !== null) {
callback();
}
this._clear();
this._materialEffect.opacity = 1;
this._texture.needsUpdate = true;
this._renderScene(context);
}
setContrast(r = 0.5, g = 0.5, b = 0.5, duration = 0) {
this._contrastOriginal.copy(this._materialBrightness.uniforms.contrast.value);
this._contrastTarget.set(r, g, b);
this._contrastChangeDuration = duration;
this._contrastChangeSeconds = 0;
}
isPlayingScene() {
return this._sceneName !== null;
}
render(context) {
context.renderer.setRenderTarget(this._renderTarget);
context.renderer.clear();
if (this._player === null) {
this._player = this.getScene().findEntityOfType('Player');
} else {
context.renderer.render(this.getScene().getThreeScene(), this._player.getCamera());
}
if (this._sceneName !== null) {
context.renderer.render(this._tSceneOverlay, this._camera);
}
context.renderer.setRenderTarget(null);
context.renderer.render(this._tScenePost, this._camera);
}
_renderScene(context) {
const canvas = this._canvas;
const ctx = this._ctx2d;
const t = context.time.totalSeconds;
const dt = context.time.elapsedSeconds;
const l = Math.min(1, this._sceneSeconds / this._sceneDuration);
switch (this._sceneName) {
case 'clear':
break;
case 'brightness':
if (this._frameIndex++ === 0) {
this._clear('#000');
const image = ImageManager.getImage('brightness');
ctx.drawImage(image, 0, 0);
this._texture.needsUpdate = true;
}
break;
case 'letterbox':
if (this._frameIndex++ === 0) {
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height * 0.1);
ctx.fillRect(0, canvas.height * 0.9, canvas.width, canvas.height * 0.1);
this._texture.needsUpdate = true;
}
this._materialEffect.opacity = Math.min(this._sceneSeconds, this._sceneDuration - this._sceneSeconds, 1);
break;
case 'enough':
case 'belong':
case 'controls':
if (this._frameIndex++ === 0) {
this._drawImage(this._sceneName, 0.5, 0.6, 0, this._sceneName === 'belong' ? 0.4 : 0.7);
this._texture.needsUpdate = true;
}
break;
case 'loop_1':
case 'loop_2':
case 'loop_3':
case 'loop_4':
case 'loop_5':
case 'title':
if (this._frameIndex++ === 0) {
this._drawImage(this._sceneName, 0.5, 0.5, 0, this._sceneName === 'title' ? 0.5 : 0.4);
this._texture.needsUpdate = true;
}
this._materialEffect.opacity = Math.min(this._sceneSeconds, this._sceneDuration - this._sceneSeconds, 1);
break;
case 'outro':
if (this._frameIndex++ === 0) {
this._clear('#000');
this._drawImage('outro', 0.5, 0.5, 0, 0.1);
this._texture.needsUpdate = true;
}
this._materialEffect.opacity = Math.min(1, this._sceneSeconds * 0.3);
break;
case 'fade_in_black':
case 'fade_in_white':
case 'fade_out_black':
if (this._frameIndex++ === 0) {
this._clear(this._sceneName === 'fade_in_white' ? '#fff' : '#000');
this._texture.needsUpdate = true;
}
this._materialEffect.opacity = this._sceneName === 'fade_out_black' ? l : 1 - l;
break;
case 'fade_out_red_splatter':
case 'fade_out_black_splatter':
case 'fade_out_black_splatter_stop':
const particlesMax = this._sceneName !== 'fade_out_red_splatter' ? 200 : 400;
while (this._frameIndex <= l * particlesMax) {
this._frameIndex++;
const black = this._sceneName !== 'fade_out_red_splatter' || l > 0.5;
this._drawImage(black ? 'blood_splatter_black' : 'blood_splatter_red',
Math.random(), Math.random(), Math.random() * Math.PI * 2, 1);
if (this._sceneName === 'fade_out_black_splatter_stop') {
this._drawImage('stop', 0.5, 0.5, 0, 0.3);
}
this._texture.needsUpdate = true;
}
break;
case 'fade_in_noise':
while (this._frameIndex < this._sceneSeconds * 10) {
this._frameIndex++;
const data = this._imageData.data;
const opacity = 1 - l;
for (let i = 0, n = data.length; i < n; i += 4) {
const c = 7 + Math.sin(i / 40000 + t / 16);
data[i] = data[i + 1] = data[i + 2] = 5 * Math.random() * c;
data[i + 3] = opacity * 255;
}
ctx.putImageData(this._imageData, 0, 0);
this._texture.needsUpdate = true;
}
break;
case 'pulse_red':
if (this._frameIndex++ === 0) {
this._clear('#a008');
const image = ImageManager.getImage('vignette');
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
this._texture.needsUpdate = true;
}
this._materialEffect.opacity = Math.min(1, (1 - l) * 5) * (0.1 + Math.sin(t * 5) * 0.05);
break;
case 'fade_in_game_over':
while (this._frameIndex <= this._sceneSeconds * 10) {
this._frameIndex++;
this._clear(`rgba(0, 0, 0, ${Math.min(1, (1 - l) * 8)})`);
for (let i = 0; i < 16; i++) {
const a = i / 16 * Math.PI * 2 + t;
const r = Math.max(0, 1 - l * 2);
const alpha = (1 - Math.abs(l - 0.5) * 2) * 0.05;
this._drawImage('game_over', 0.5 + Math.sin(a) * 0.1 * r, 0.5 + Math.random() * 0.02, 0, 0.7, alpha);
}
this._texture.needsUpdate = true;
}
break;
case 'hurt':
if (this._frameIndex++ < 5) {
this._drawImage('blood_splatter_red',
Math.random(), Math.random(), Math.random() * Math.PI * 2, 1);
this._texture.needsUpdate = true;
}
break;
case 'spiral':
while (this._frameIndex <= this._sceneSeconds * 10) {
this._frameIndex++;
this._clear('#fff');
for (let i = 0; i < 3; i++) {
const alpha = Math.min(0.3, l);
this._drawImage('spiral', 0.5, 0.5, -(i + 1) * l * l * 20 + context.time.totalSeconds, 6, alpha);
}
this._texture.needsUpdate = true;
}
this._materialEffect.opacity = Math.min(1, (1 - l) * 5 - 1);
break;
default:
throw new Error(`Overlay effect "${this._sceneName}" does not exist`);
}
this._sceneSeconds += dt;
}
_clear(color = null) {
this._ctx2d.clearRect(0, 0, this._canvas.width, this._canvas.height);
if (color !== null) {
this._ctx2d.fillStyle = color;
this._ctx2d.fillRect(0, 0, this._canvas.width, this._canvas.height);
}
}
_drawImage(imageName, xFraction, yFraction, rotation, heightFraction = 0.5, opacity = 1) {
const image = ImageManager.getImage(imageName);
const scale = this._canvas.height / image.height * heightFraction;
if (opacity < 1) {
this._ctx2d.globalAlpha = opacity;
}
this._ctx2d.setTransform(scale, 0, 0, scale, xFraction * this._canvas.width, yFraction * this._canvas.height);
this._ctx2d.rotate(rotation);
this._ctx2d.drawImage(image, -image.width / 2, -image.height / 2);
this._ctx2d.setTransform(1, 0, 0, 1, 0, 0);
if (opacity < 1) {
this._ctx2d.globalAlpha = 1;
}
}
}
Overlay._init();
Overlay.p_register();