578 lines
18 KiB
JavaScript
578 lines
18 KiB
JavaScript
import {
|
|
vsMatrix,
|
|
vsOverlay,
|
|
vsGlitch,
|
|
fsMatrix,
|
|
fsGlitch,
|
|
postProcessSource,
|
|
} from "./shaders.js";
|
|
import { compileShader, createProgram } from "./gl-utils.js";
|
|
|
|
let engine, wasm;
|
|
let gl;
|
|
let atlasCanvas, atlasCtx, atlasTexture;
|
|
let width, height;
|
|
let fontSize = 16;
|
|
let isMobile = false,
|
|
isVpnOn = false,
|
|
isDarkMode = true;
|
|
let lastDrawTime = 0;
|
|
let pixelScale = 1.0;
|
|
let isLowQuality = false;
|
|
let frameCounter = 0;
|
|
let eyeScale = 1.5;
|
|
let interactionTimer = 0;
|
|
|
|
let isPaused = false;
|
|
let rAF_ID = null;
|
|
let showEyes = true;
|
|
|
|
let fpsHistory = [];
|
|
let lastFrameTime = 0;
|
|
let scaleCheckTimer = 0;
|
|
let currentPixelScale = 1.0;
|
|
let maxPixelScale = 1.0;
|
|
const version = "3.0.3";
|
|
|
|
let vpnTimer = 0.0;
|
|
let uVpnTimerLoc;
|
|
|
|
let mouseX = 0;
|
|
let mouseY = 0;
|
|
let uMouseLoc;
|
|
|
|
let postProgram, matrixProgram, glitchProgram;
|
|
let postFramebuffer, postTexture;
|
|
let instanceBuffer, quadBuffer, overlayQuadBuffer, glitchInstanceBuffer;
|
|
let vaoMatrix, vaoOverlay, vaoGlitch;
|
|
let uResLoc, uFontLoc, uAtlasSizeLoc;
|
|
let instanceBufferCapacity = 0;
|
|
|
|
let uPostEyePosLLoc,
|
|
uPostEyePosRLoc,
|
|
uPostEyeClosednessLoc,
|
|
uPostIsVpnOnLoc,
|
|
uPostEyeActiveLoc,
|
|
uShockwaveLoc,
|
|
uPostGazeLoc;
|
|
|
|
const chars =
|
|
"0101アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホ마미무메모야유요라릴레ро와원>_±÷×=≠≈≡≤≥";
|
|
|
|
function initWebGL() {
|
|
matrixProgram = createProgram(gl, vsMatrix, fsMatrix);
|
|
postProgram = createProgram(gl, vsOverlay, postProcessSource);
|
|
glitchProgram = createProgram(gl, vsGlitch, fsGlitch);
|
|
|
|
quadBuffer = gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
|
|
gl.bufferData(
|
|
gl.ARRAY_BUFFER,
|
|
new Float32Array([0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1]),
|
|
gl.STATIC_DRAW,
|
|
);
|
|
|
|
vaoMatrix = gl.createVertexArray();
|
|
gl.bindVertexArray(vaoMatrix);
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
|
|
const aQuadLoc = gl.getAttribLocation(matrixProgram, "a_quad");
|
|
gl.enableVertexAttribArray(aQuadLoc);
|
|
gl.vertexAttribPointer(aQuadLoc, 2, gl.FLOAT, false, 0, 0);
|
|
|
|
instanceBuffer = gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
|
|
instanceBufferCapacity = 10000;
|
|
gl.bufferData(gl.ARRAY_BUFFER, instanceBufferCapacity * 4, gl.DYNAMIC_DRAW);
|
|
const strideBytes = 5 * 4;
|
|
|
|
const aPosLoc = gl.getAttribLocation(matrixProgram, "a_position");
|
|
gl.enableVertexAttribArray(aPosLoc);
|
|
gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, false, strideBytes, 0);
|
|
gl.vertexAttribDivisor(aPosLoc, 1);
|
|
|
|
const aCharIdxLoc = gl.getAttribLocation(matrixProgram, "a_charIdx");
|
|
gl.enableVertexAttribArray(aCharIdxLoc);
|
|
gl.vertexAttribPointer(aCharIdxLoc, 1, gl.FLOAT, false, strideBytes, 2 * 4);
|
|
gl.vertexAttribDivisor(aCharIdxLoc, 1);
|
|
|
|
const aAtlasRowLoc = gl.getAttribLocation(matrixProgram, "a_atlasRow");
|
|
gl.enableVertexAttribArray(aAtlasRowLoc);
|
|
gl.vertexAttribPointer(aAtlasRowLoc, 1, gl.FLOAT, false, strideBytes, 3 * 4);
|
|
gl.vertexAttribDivisor(aAtlasRowLoc, 1);
|
|
|
|
const aScaleLoc = gl.getAttribLocation(matrixProgram, "a_scale");
|
|
gl.enableVertexAttribArray(aScaleLoc);
|
|
gl.vertexAttribPointer(aScaleLoc, 1, gl.FLOAT, false, strideBytes, 4 * 4);
|
|
gl.vertexAttribDivisor(aScaleLoc, 1);
|
|
|
|
vaoGlitch = gl.createVertexArray();
|
|
gl.bindVertexArray(vaoGlitch);
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
|
|
const aQuadGlitch = gl.getAttribLocation(glitchProgram, "a_quad");
|
|
gl.enableVertexAttribArray(aQuadGlitch);
|
|
gl.vertexAttribPointer(aQuadGlitch, 2, gl.FLOAT, false, 0, 0);
|
|
|
|
glitchInstanceBuffer = gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, glitchInstanceBuffer);
|
|
const strideG = 5 * 4;
|
|
const aDataLoc = gl.getAttribLocation(glitchProgram, "a_glitchData");
|
|
gl.enableVertexAttribArray(aDataLoc);
|
|
gl.vertexAttribPointer(aDataLoc, 4, gl.FLOAT, false, strideG, 0);
|
|
gl.vertexAttribDivisor(aDataLoc, 1);
|
|
|
|
const aColorLoc = gl.getAttribLocation(glitchProgram, "a_colorType");
|
|
gl.enableVertexAttribArray(aColorLoc);
|
|
gl.vertexAttribPointer(aColorLoc, 1, gl.FLOAT, false, strideG, 4 * 4);
|
|
gl.vertexAttribDivisor(aColorLoc, 1);
|
|
|
|
vaoOverlay = gl.createVertexArray();
|
|
gl.bindVertexArray(vaoOverlay);
|
|
overlayQuadBuffer = gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, overlayQuadBuffer);
|
|
gl.bufferData(
|
|
gl.ARRAY_BUFFER,
|
|
new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
|
|
gl.STATIC_DRAW,
|
|
);
|
|
const aPosLocOver = gl.getAttribLocation(postProgram, "a_position");
|
|
gl.enableVertexAttribArray(aPosLocOver);
|
|
gl.vertexAttribPointer(aPosLocOver, 2, gl.FLOAT, false, 0, 0);
|
|
|
|
gl.bindVertexArray(null);
|
|
|
|
uResLoc = gl.getUniformLocation(matrixProgram, "u_resolution");
|
|
uFontLoc = gl.getUniformLocation(matrixProgram, "u_fontSize");
|
|
uAtlasSizeLoc = gl.getUniformLocation(matrixProgram, "u_atlasSize");
|
|
uMouseLoc = gl.getUniformLocation(matrixProgram, "u_mouse");
|
|
|
|
atlasTexture = gl.createTexture();
|
|
|
|
uShockwaveLoc = gl.getUniformLocation(postProgram, "u_shockwave");
|
|
uPostEyePosLLoc = gl.getUniformLocation(postProgram, "u_eyePosL");
|
|
uPostEyePosRLoc = gl.getUniformLocation(postProgram, "u_eyePosR");
|
|
uPostEyeClosednessLoc = gl.getUniformLocation(postProgram, "u_eyeClosedness");
|
|
uPostIsVpnOnLoc = gl.getUniformLocation(postProgram, "u_isVpnOn");
|
|
uPostEyeActiveLoc = gl.getUniformLocation(postProgram, "u_eyeActive");
|
|
uPostGazeLoc = gl.getUniformLocation(postProgram, "u_gaze");
|
|
uVpnTimerLoc = gl.getUniformLocation(postProgram, "u_vpnTimer");
|
|
|
|
postTexture = gl.createTexture();
|
|
postFramebuffer = gl.createFramebuffer();
|
|
gl.bindTexture(gl.TEXTURE_2D, postTexture);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
|
|
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
|
|
gl.enable(gl.BLEND);
|
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
|
|
function updateDynamicScaling(now) {
|
|
if (lastFrameTime === 0) lastFrameTime = now;
|
|
const dt = now - lastFrameTime;
|
|
lastFrameTime = now;
|
|
fpsHistory.push(dt);
|
|
if (fpsHistory.length > 60) fpsHistory.shift();
|
|
scaleCheckTimer++;
|
|
if (scaleCheckTimer >= 60) {
|
|
const avgDt = fpsHistory.reduce((a, b) => a + b, 0) / fpsHistory.length;
|
|
let needsResize = false;
|
|
if (avgDt > 25.0 && currentPixelScale > 0.75) {
|
|
currentPixelScale -= 0.1;
|
|
needsResize = true;
|
|
} else if (avgDt < 15.0 && currentPixelScale < maxPixelScale) {
|
|
currentPixelScale = Math.min(maxPixelScale, currentPixelScale + 0.25);
|
|
needsResize = true;
|
|
}
|
|
if (needsResize) {
|
|
gl.canvas.width = width * currentPixelScale;
|
|
gl.canvas.height = height * currentPixelScale;
|
|
gl.bindTexture(gl.TEXTURE_2D, postTexture);
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D,
|
|
0,
|
|
gl.RGBA,
|
|
gl.canvas.width,
|
|
gl.canvas.height,
|
|
0,
|
|
gl.RGBA,
|
|
gl.UNSIGNED_BYTE,
|
|
null,
|
|
);
|
|
}
|
|
scaleCheckTimer = 0;
|
|
}
|
|
}
|
|
|
|
function buildAtlas() {
|
|
atlasCanvas = new OffscreenCanvas(chars.length * fontSize, fontSize * 11);
|
|
atlasCtx = atlasCanvas.getContext("2d", { alpha: true });
|
|
atlasCtx.clearRect(0, 0, atlasCanvas.width, atlasCanvas.height);
|
|
let r, g, b;
|
|
if (isVpnOn) {
|
|
r = isDarkMode ? 178 : 106;
|
|
g = isDarkMode ? 51 : 0;
|
|
b = isDarkMode ? 255 : 209;
|
|
} else {
|
|
r = isDarkMode ? 0 : 0;
|
|
g = isDarkMode ? 230 : 119;
|
|
b = isDarkMode ? 255 : 182;
|
|
}
|
|
atlasCtx.font = `bold ${fontSize}px monospace`;
|
|
atlasCtx.textBaseline = "top";
|
|
atlasCtx.textAlign = "center";
|
|
const halfFs = fontSize / 2;
|
|
for (let row = 0; row <= 9; row++) {
|
|
const alphaBase = isLowQuality ? 1.0 : (row + 1) / 10;
|
|
const alpha = isDarkMode ? alphaBase : alphaBase * 0.8;
|
|
atlasCtx.fillStyle = `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
for (let i = 0; i < chars.length; i++)
|
|
atlasCtx.fillText(chars[i], i * fontSize + halfFs, row * fontSize);
|
|
}
|
|
atlasCtx.fillStyle = "rgb(255, 255, 255)";
|
|
for (let i = 0; i < chars.length; i++)
|
|
atlasCtx.fillText(chars[i], i * fontSize + halfFs, 10 * fontSize);
|
|
if (gl) {
|
|
gl.bindTexture(gl.TEXTURE_2D, atlasTexture);
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D,
|
|
0,
|
|
gl.RGBA,
|
|
gl.RGBA,
|
|
gl.UNSIGNED_BYTE,
|
|
atlasCanvas,
|
|
);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
}
|
|
}
|
|
|
|
function render(currentTime) {
|
|
if (isPaused) {
|
|
rAF_ID = null;
|
|
return;
|
|
}
|
|
rAF_ID = requestAnimationFrame(render);
|
|
|
|
if (!engine || !wasm || !gl) return;
|
|
const now = currentTime || performance.now();
|
|
if (now - lastDrawTime < 16) return;
|
|
lastDrawTime = now;
|
|
updateDynamicScaling(now);
|
|
engine.tick();
|
|
|
|
const bgR = isDarkMode ? 10 / 255 : 250 / 255;
|
|
const bgG = isDarkMode ? 10 / 255 : 250 / 255;
|
|
const bgB = isDarkMode ? 12 / 255 : 252 / 255;
|
|
|
|
gl.bindFramebuffer(gl.FRAMEBUFFER, postFramebuffer);
|
|
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
|
gl.clearColor(bgR, bgG, bgB, 1.0);
|
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
|
|
const renderLen = engine.render_len();
|
|
if (renderLen > 0) {
|
|
const instanceCount = renderLen / 5;
|
|
gl.useProgram(matrixProgram);
|
|
gl.bindVertexArray(vaoMatrix);
|
|
gl.activeTexture(gl.TEXTURE0);
|
|
gl.bindTexture(gl.TEXTURE_2D, atlasTexture);
|
|
gl.uniform1i(gl.getUniformLocation(matrixProgram, "u_atlas"), 0);
|
|
gl.uniform2f(uResLoc, width, height);
|
|
gl.uniform1f(uFontLoc, fontSize);
|
|
gl.uniform2f(uAtlasSizeLoc, atlasCanvas.width, atlasCanvas.height);
|
|
gl.uniform2f(uMouseLoc, mouseX / width, mouseY / height);
|
|
|
|
const renderDataView = new Float32Array(
|
|
wasm.memory.buffer,
|
|
engine.render_ptr(),
|
|
renderLen,
|
|
);
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
|
|
if (renderLen > instanceBufferCapacity) {
|
|
instanceBufferCapacity = Math.max(renderLen * 2, 20000);
|
|
gl.bufferData(
|
|
gl.ARRAY_BUFFER,
|
|
instanceBufferCapacity * 4,
|
|
gl.DYNAMIC_DRAW,
|
|
);
|
|
}
|
|
gl.bufferSubData(gl.ARRAY_BUFFER, 0, renderDataView);
|
|
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
|
|
}
|
|
|
|
const glLen = engine.glitch_len();
|
|
if (glLen > 0) {
|
|
const glitchInstanceCount = glLen / 5;
|
|
gl.useProgram(glitchProgram);
|
|
gl.bindVertexArray(vaoGlitch);
|
|
let tr, tg, tb;
|
|
if (isVpnOn && isDarkMode) {
|
|
tr = 178 / 255;
|
|
tg = 51 / 255;
|
|
tb = 255 / 255;
|
|
} else if (!isVpnOn && isDarkMode) {
|
|
tr = 0;
|
|
tg = 230 / 255;
|
|
tb = 255 / 255;
|
|
} else if (isVpnOn && !isDarkMode) {
|
|
tr = 106 / 255;
|
|
tg = 0;
|
|
tb = 209 / 255;
|
|
} else {
|
|
tr = 0;
|
|
tg = 119 / 255;
|
|
tb = 182 / 255;
|
|
}
|
|
|
|
gl.uniform3f(
|
|
gl.getUniformLocation(glitchProgram, "u_themeMain"),
|
|
tr,
|
|
tg,
|
|
tb,
|
|
);
|
|
gl.uniform3f(
|
|
gl.getUniformLocation(glitchProgram, "u_themeBg"),
|
|
bgR,
|
|
bgG,
|
|
bgB,
|
|
);
|
|
gl.uniform2f(
|
|
gl.getUniformLocation(glitchProgram, "u_resolution"),
|
|
width,
|
|
height,
|
|
);
|
|
|
|
const glDataView = new Float32Array(
|
|
wasm.memory.buffer,
|
|
engine.glitch_ptr(),
|
|
glLen,
|
|
);
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, glitchInstanceBuffer);
|
|
gl.bufferData(gl.ARRAY_BUFFER, glDataView, gl.DYNAMIC_DRAW);
|
|
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, glitchInstanceCount);
|
|
}
|
|
|
|
const eyeLen = engine.eye_len();
|
|
let eyeActive = 0.0,
|
|
eyeClosedness = 1.0,
|
|
posLX = 0,
|
|
posLY = 0,
|
|
posRX = 0,
|
|
posRY = 0,
|
|
normGazeX = 0,
|
|
normGazeY = 0;
|
|
if (eyeLen >= 6 && showEyes) {
|
|
const eyeData = new Float32Array(
|
|
wasm.memory.buffer,
|
|
engine.eye_ptr(),
|
|
eyeLen,
|
|
);
|
|
eyeScale = isMobile ? 1.3 : 1.8;
|
|
eyeClosedness = eyeData[5];
|
|
eyeActive = 1.0;
|
|
let baseGap = isMobile ? (width < 400 ? 55.0 : 65.0) : 75.0;
|
|
posLX = eyeData[0] - baseGap * eyeScale;
|
|
posLY = eyeData[1];
|
|
posRX = eyeData[0] + baseGap * eyeScale;
|
|
posRY = eyeData[1];
|
|
normGazeX = (eyeData[2] * eyeScale) / (15.0 * eyeScale);
|
|
normGazeY = (eyeData[3] * eyeScale) / (-10.0 * eyeScale);
|
|
}
|
|
|
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
|
gl.useProgram(postProgram);
|
|
gl.bindVertexArray(vaoOverlay);
|
|
gl.activeTexture(gl.TEXTURE2);
|
|
gl.bindTexture(gl.TEXTURE_2D, postTexture);
|
|
gl.uniform1i(gl.getUniformLocation(postProgram, "u_mainTex"), 2);
|
|
gl.uniform1f(gl.getUniformLocation(postProgram, "u_time"), now / 1000);
|
|
gl.uniform2f(
|
|
gl.getUniformLocation(postProgram, "u_resolution"),
|
|
gl.canvas.width,
|
|
gl.canvas.height,
|
|
);
|
|
gl.uniform1f(gl.getUniformLocation(postProgram, "u_distortion"), 18.0);
|
|
gl.uniform1f(
|
|
gl.getUniformLocation(postProgram, "u_isDarkMode"),
|
|
isDarkMode ? 1.0 : 0.0,
|
|
);
|
|
gl.uniform1f(
|
|
gl.getUniformLocation(postProgram, "u_isLowQuality"),
|
|
isLowQuality ? 1.0 : 0.0,
|
|
);
|
|
gl.uniform1f(uPostEyeActiveLoc, eyeActive);
|
|
|
|
if (eyeActive > 0.5) {
|
|
gl.uniform2f(
|
|
uPostEyePosLLoc,
|
|
posLX * currentPixelScale,
|
|
posLY * currentPixelScale,
|
|
);
|
|
gl.uniform2f(
|
|
uPostEyePosRLoc,
|
|
posRX * currentPixelScale,
|
|
posRY * currentPixelScale,
|
|
);
|
|
gl.uniform1f(uPostEyeClosednessLoc, eyeClosedness);
|
|
gl.uniform1f(uPostIsVpnOnLoc, isVpnOn ? 1.0 : 0.0);
|
|
gl.uniform2f(uPostGazeLoc, normGazeX, normGazeY);
|
|
}
|
|
|
|
if (vpnTimer > 0.0) vpnTimer = Math.max(0.0, vpnTimer - 0.015);
|
|
gl.uniform1f(uVpnTimerLoc, vpnTimer);
|
|
|
|
let sw_x = -1.0,
|
|
sw_y = -1.0,
|
|
sw_r = 0.0;
|
|
if (engine.has_active_shockwave && engine.has_active_shockwave()) {
|
|
sw_x = engine.get_shockwave_x() / width;
|
|
sw_y = engine.get_shockwave_y() / height;
|
|
sw_r = engine.get_shockwave_radius() / width;
|
|
}
|
|
gl.uniform3f(uShockwaveLoc, sw_x, 1.0 - sw_y, sw_r);
|
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
}
|
|
|
|
self.onmessage = async (e) => {
|
|
const { type, payload } = e.data;
|
|
|
|
if (type === "INIT") {
|
|
gl = payload.canvas.getContext("webgl2", {
|
|
alpha: false,
|
|
antialias: false,
|
|
powerPreference: "high-performance",
|
|
});
|
|
width = payload.logicalWidth;
|
|
height = payload.logicalHeight;
|
|
pixelScale = payload.pixelScale;
|
|
maxPixelScale = pixelScale;
|
|
currentPixelScale = pixelScale;
|
|
isMobile = payload.isMobile;
|
|
isLowQuality = payload.isLowQuality;
|
|
eyeScale = isMobile ? 1.5 : 1.8;
|
|
isVpnOn = payload.isVpnOn;
|
|
isDarkMode = payload.isDarkMode;
|
|
fontSize = payload.fontSize || 16;
|
|
showEyes = payload.showEyes !== false;
|
|
|
|
// ВАЖНО: Устанавливаем физический размер канваса ПЕРЕД инициализацией текстур
|
|
gl.canvas.width = width * currentPixelScale;
|
|
gl.canvas.height = height * currentPixelScale;
|
|
|
|
initWebGL();
|
|
|
|
// Инициализируем текстуру пост-процессинга ПРАВИЛЬНЫМ размером сразу
|
|
gl.bindTexture(gl.TEXTURE_2D, postTexture);
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D,
|
|
0,
|
|
gl.RGBA,
|
|
gl.canvas.width,
|
|
gl.canvas.height,
|
|
0,
|
|
gl.RGBA,
|
|
gl.UNSIGNED_BYTE,
|
|
null,
|
|
);
|
|
gl.bindFramebuffer(gl.FRAMEBUFFER, postFramebuffer);
|
|
gl.framebufferTexture2D(
|
|
gl.FRAMEBUFFER,
|
|
gl.COLOR_ATTACHMENT0,
|
|
gl.TEXTURE_2D,
|
|
postTexture,
|
|
0,
|
|
);
|
|
|
|
buildAtlas();
|
|
|
|
try {
|
|
const cleanPath = payload.assetsPath.endsWith("/")
|
|
? payload.assetsPath
|
|
: `${payload.assetsPath}/`;
|
|
const module = await import(`${cleanPath}matrix_engine.js?v=${version}`);
|
|
wasm = await module.default({
|
|
module_or_path: `${cleanPath}matrix_engine_bg.wasm?v=${version}`,
|
|
});
|
|
engine = new module.MatrixEngine(width, height, fontSize);
|
|
if (typeof engine.set_mobile === "function") engine.set_mobile(isMobile);
|
|
if (typeof engine.set_vpn_status === "function")
|
|
engine.set_vpn_status(isVpnOn);
|
|
if (payload.eyeY && typeof engine.set_eye_anchor === "function")
|
|
engine.set_eye_anchor(payload.eyeY);
|
|
|
|
for (let i = 0; i < 300; i++) engine.tick();
|
|
if (!isPaused) rAF_ID = requestAnimationFrame(render);
|
|
self.postMessage({ type: "READY" });
|
|
} catch (err) {
|
|
console.error("Critical failure loading WASM core:", err);
|
|
}
|
|
} else if (type === "PAUSE") {
|
|
isPaused = true;
|
|
if (rAF_ID !== null) {
|
|
cancelAnimationFrame(rAF_ID);
|
|
rAF_ID = null;
|
|
}
|
|
} else if (type === "RESUME") {
|
|
if (isPaused) {
|
|
isPaused = false;
|
|
const now = performance.now();
|
|
lastDrawTime = now;
|
|
lastFrameTime = now;
|
|
if (rAF_ID === null && engine) rAF_ID = requestAnimationFrame(render);
|
|
}
|
|
} else if (type === "RESIZE") {
|
|
if (!engine) return;
|
|
width = payload.logicalWidth;
|
|
height = payload.logicalHeight;
|
|
currentPixelScale = payload.pixelScale;
|
|
maxPixelScale = currentPixelScale;
|
|
gl.canvas.width = width * currentPixelScale;
|
|
gl.canvas.height = height * currentPixelScale;
|
|
gl.bindTexture(gl.TEXTURE_2D, postTexture);
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D,
|
|
0,
|
|
gl.RGBA,
|
|
gl.canvas.width,
|
|
gl.canvas.height,
|
|
0,
|
|
gl.RGBA,
|
|
gl.UNSIGNED_BYTE,
|
|
null,
|
|
);
|
|
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
|
engine.resize(width, height);
|
|
buildAtlas();
|
|
} else if (type === "THEME") {
|
|
isVpnOn = payload.isVpnOn;
|
|
isDarkMode = payload.isDarkMode;
|
|
vpnTimer = 1.0;
|
|
if (engine) {
|
|
if (typeof engine.set_vpn_status === "function")
|
|
engine.set_vpn_status(isVpnOn);
|
|
buildAtlas();
|
|
}
|
|
} else if (type === "MOUSE_MOVE") {
|
|
mouseX = payload.x;
|
|
mouseY = payload.y;
|
|
if (engine) engine.update_mouse(payload.x, payload.y);
|
|
} else if (type === "DRAW_START") {
|
|
if (engine) {
|
|
engine.set_drawing(true);
|
|
if (Math.random() > 0.5) engine.trigger_glitch(payload.x, payload.y);
|
|
else engine.trigger_shockwave(payload.x, payload.y);
|
|
}
|
|
} else if (type === "DRAW_END") {
|
|
if (engine) engine.set_drawing(false);
|
|
} else if (type === "SHOCKWAVE") {
|
|
if (engine) engine.trigger_shockwave(payload.x, payload.y);
|
|
} else if (type === "TILT") {
|
|
if (engine && typeof engine.set_tilt === "function")
|
|
engine.set_tilt(payload.x, payload.y);
|
|
}
|
|
};
|