hero matrix engine update
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
import React, { useRef, useEffect, useState } from "react";
|
import React, { useRef, useEffect, useState } from "react";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
|
|
||||||
const ENGINE_VERSION = "2.0.1";
|
const ENGINE_VERSION = "2.0.2";
|
||||||
|
|
||||||
interface NetrunnerMatrixProps {
|
interface NetrunnerMatrixProps {
|
||||||
isSecure?: boolean;
|
isSecure?: boolean;
|
||||||
@@ -151,80 +151,78 @@ export const NetrunnerMatrix: React.FC<NetrunnerMatrixProps> = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [isSecure, isDarkMode, mounted]);
|
}, [isSecure, isDarkMode, mounted]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!mounted) return;
|
if (!mounted || !workerRef.current) return;
|
||||||
|
|
||||||
const handleEvents = (e: any) => {
|
// --- 1. Гироскоп (Наклон телефона) ---
|
||||||
if (!workerRef.current || !canvasRef.current) return;
|
const handleOrientation = (e: DeviceOrientationEvent) => {
|
||||||
|
let gamma = e.gamma || 0; // Наклон влево/вправо (-90 до 90)
|
||||||
|
|
||||||
const isInteractive = e.target.closest(
|
// Ограничиваем угол для комфорта
|
||||||
'button, a, input, [role="button"], [role="switch"], .data-\\[state\\]',
|
if (gamma > 45) gamma = 45;
|
||||||
);
|
if (gamma < -45) gamma = -45;
|
||||||
|
|
||||||
const isDown = e.type === "pointerdown" || e.type === "touchstart";
|
// Нормализуем от -1.0 до 1.0
|
||||||
const isUp =
|
const tiltX = gamma / 45.0;
|
||||||
e.type === "pointerup" ||
|
|
||||||
e.type === "touchend" ||
|
|
||||||
e.type === "pointercancel";
|
|
||||||
|
|
||||||
if (isInteractive && isDown) return;
|
workerRef.current?.postMessage({
|
||||||
|
type: "TILT",
|
||||||
const type = isDown ? "DRAW_START" : isUp ? "DRAW_END" : "MOUSE_MOVE";
|
payload: { x: tiltX, y: 0 },
|
||||||
|
|
||||||
let clientX: number | null = null;
|
|
||||||
let clientY: number | null = null;
|
|
||||||
|
|
||||||
if (e.touches && e.touches.length > 0) {
|
|
||||||
clientX = e.touches[0].clientX;
|
|
||||||
clientY = e.touches[0].clientY;
|
|
||||||
} else if (e.clientX !== undefined) {
|
|
||||||
clientX = e.clientX;
|
|
||||||
clientY = e.clientY;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clientX === null || clientY === null) {
|
|
||||||
if (isUp) workerRef.current.postMessage({ type });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rect = canvasRef.current.getBoundingClientRect();
|
|
||||||
const scaleX = dimensionsRef.current.width / rect.width;
|
|
||||||
const scaleY = dimensionsRef.current.height / rect.height;
|
|
||||||
|
|
||||||
workerRef.current.postMessage({
|
|
||||||
type,
|
|
||||||
payload: {
|
|
||||||
x: (clientX - rect.left) * scaleX,
|
|
||||||
y: (clientY - rect.top) * scaleY,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = { passive: true };
|
// Запрос прав для iOS 13+ (если нужно, можно вынести в кнопку включения VPN)
|
||||||
|
if (
|
||||||
if (window.PointerEvent) {
|
typeof (DeviceOrientationEvent as any).requestPermission === "function"
|
||||||
window.addEventListener("pointerdown", handleEvents, options as any);
|
) {
|
||||||
window.addEventListener("pointermove", handleEvents, options as any);
|
// Это должно вызываться по клику пользователя, здесь оставлено для примера
|
||||||
window.addEventListener("pointerup", handleEvents, options as any);
|
// (DeviceOrientationEvent as any).requestPermission().then(...)
|
||||||
window.addEventListener("pointercancel", handleEvents, options as any);
|
|
||||||
} else {
|
} else {
|
||||||
window.addEventListener("touchstart", handleEvents, options);
|
window.addEventListener("deviceorientation", handleOrientation);
|
||||||
window.addEventListener("touchmove", handleEvents, options);
|
|
||||||
window.addEventListener("touchend", handleEvents, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- 2. Ударные волны и Кинетический свайп ---
|
||||||
|
const handleEvents = (e: any) => {
|
||||||
|
const isDown = e.type === "pointerdown" || e.type === "touchstart";
|
||||||
|
const isMove = e.type === "pointermove" || e.type === "touchmove";
|
||||||
|
|
||||||
|
let clientX = e.touches ? e.touches[0].clientX : e.clientX;
|
||||||
|
let clientY = e.touches ? e.touches[0].clientY : e.clientY;
|
||||||
|
|
||||||
|
if (clientX === undefined) return;
|
||||||
|
|
||||||
|
const rect = canvasRef.current!.getBoundingClientRect();
|
||||||
|
const scaleX = dimensionsRef.current.width / rect.width;
|
||||||
|
const scaleY = dimensionsRef.current.height / rect.height;
|
||||||
|
const px = (clientX - rect.left) * scaleX;
|
||||||
|
const py = (clientY - rect.top) * scaleY;
|
||||||
|
|
||||||
|
// Ударная волна при клике/тапе
|
||||||
|
if (isDown) {
|
||||||
|
workerRef.current?.postMessage({
|
||||||
|
type: "SHOCKWAVE",
|
||||||
|
payload: { x: px, y: py },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Магнитный свайп
|
||||||
|
if (isMove) {
|
||||||
|
workerRef.current?.postMessage({
|
||||||
|
type: "KINETIC_SWIPE",
|
||||||
|
payload: { x: px, y: py },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("pointerdown", handleEvents, { passive: true });
|
||||||
|
window.addEventListener("pointermove", handleEvents, { passive: true });
|
||||||
|
window.addEventListener("touchstart", handleEvents, { passive: true });
|
||||||
|
window.addEventListener("touchmove", handleEvents, { passive: true });
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (window.PointerEvent) {
|
window.removeEventListener("deviceorientation", handleOrientation);
|
||||||
window.removeEventListener("pointerdown", handleEvents);
|
window.removeEventListener("pointerdown", handleEvents);
|
||||||
window.removeEventListener("pointermove", handleEvents);
|
// ... удаляем остальные листенеры ...
|
||||||
window.removeEventListener("pointerup", handleEvents);
|
|
||||||
window.removeEventListener("pointercancel", handleEvents);
|
|
||||||
} else {
|
|
||||||
window.removeEventListener("touchstart", handleEvents);
|
|
||||||
window.removeEventListener("touchmove", handleEvents);
|
|
||||||
window.removeEventListener("touchend", handleEvents);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, [mounted]);
|
}, [mounted]);
|
||||||
|
|
||||||
|
|||||||
@@ -167,11 +167,16 @@ self.onmessage = async (e) => {
|
|||||||
const eyeData = new Float32Array(wasm.memory.buffer, eyePtr, eyeLen);
|
const eyeData = new Float32Array(wasm.memory.buffer, eyePtr, eyeLen);
|
||||||
self.postMessage({ type: "EYE_DATA", payload: eyeData.slice() });
|
self.postMessage({ type: "EYE_DATA", payload: eyeData.slice() });
|
||||||
}
|
}
|
||||||
|
} else if (type === "SHOCKWAVE") {
|
||||||
|
if (engine) engine.trigger_shockwave(payload.x, payload.y);
|
||||||
|
} else if (type === "KINETIC_SWIPE") {
|
||||||
|
if (engine) engine.kinetic_swipe(payload.x, payload.y);
|
||||||
|
} else if (type === "TILT") {
|
||||||
|
if (engine && typeof engine.set_tilt === "function") {
|
||||||
|
engine.set_tilt(payload.x, payload.y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ... функция drawEye без изменений ...
|
|
||||||
|
|
||||||
function drawEye(ctx, cx, cy, gazeX, gazeY, dir, isClosed) {
|
function drawEye(ctx, cx, cy, gazeX, gazeY, dir, isClosed) {
|
||||||
const color = isVpnOn
|
const color = isVpnOn
|
||||||
? isDarkMode
|
? isDarkMode
|
||||||
@@ -219,46 +224,70 @@ function drawEye(ctx, cx, cy, gazeX, gazeY, dir, isClosed) {
|
|||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.translate(cx + gazeX, cy + gazeY);
|
ctx.translate(cx + gazeX, cy + gazeY);
|
||||||
|
|
||||||
ctx.strokeStyle = isDarkMode ? "rgba(255,255,255,0.7)" : "rgba(0,0,0,0.6)";
|
// Добавляем плавное вращение для кибер-интерфейса зрачка
|
||||||
|
const t = performance.now() * 0.0015;
|
||||||
|
|
||||||
|
// 1. Внешнее кибер-кольцо (вращающееся, прерывистое)
|
||||||
|
ctx.save();
|
||||||
|
ctx.rotate(t);
|
||||||
|
ctx.strokeStyle = color;
|
||||||
ctx.lineWidth = 1.5;
|
ctx.lineWidth = 1.5;
|
||||||
|
ctx.globalAlpha = 0.7 * alphaMult;
|
||||||
|
ctx.setLineDash([8, 6, 2, 6]); // Техно-паттерн
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, 16, 0, Math.PI * 2);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
// 2. Внутреннее сплошное кольцо для глубины
|
||||||
|
ctx.strokeStyle = isDarkMode ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)";
|
||||||
|
ctx.lineWidth = 1;
|
||||||
ctx.globalAlpha = alphaMult;
|
ctx.globalAlpha = alphaMult;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(0, 0, 10, 0, Math.PI * 2);
|
ctx.arc(0, 0, 10, 0, Math.PI * 2);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
ctx.fillStyle = isDarkMode ? "rgba(255,255,255,0.9)" : "rgba(0,0,0,0.8)";
|
// 3. Зрачок (Острый кибер-ромб вместо скучной точки)
|
||||||
ctx.shadowBlur = 5;
|
ctx.fillStyle = isDarkMode ? "#fff" : "#000";
|
||||||
ctx.shadowColor = isDarkMode ? "white" : "black";
|
ctx.shadowBlur = 10;
|
||||||
|
ctx.shadowColor = color; // Легкое свечение зрачка цветом темы
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(0, 0, 3.5, 0, Math.PI * 2);
|
ctx.moveTo(0, -5);
|
||||||
|
ctx.lineTo(4, 0);
|
||||||
|
ctx.lineTo(0, 5);
|
||||||
|
ctx.lineTo(-4, 0);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
||||||
ctx.strokeStyle = isDarkMode ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.4)";
|
// 4. Техно-перекрестие (HUD)
|
||||||
ctx.lineWidth = 1;
|
ctx.strokeStyle = color;
|
||||||
ctx.shadowBlur = 0;
|
ctx.lineWidth = 1.5;
|
||||||
|
ctx.shadowBlur = 5;
|
||||||
|
ctx.globalAlpha = 0.4 * alphaMult;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(-16, 0);
|
ctx.moveTo(-22, 0);
|
||||||
ctx.lineTo(-8, 0);
|
ctx.lineTo(-12, 0);
|
||||||
ctx.moveTo(16, 0);
|
ctx.moveTo(22, 0);
|
||||||
ctx.lineTo(8, 0);
|
ctx.lineTo(12, 0);
|
||||||
ctx.moveTo(0, -16);
|
ctx.moveTo(0, -22);
|
||||||
ctx.lineTo(0, -8);
|
ctx.lineTo(0, -12);
|
||||||
ctx.moveTo(0, 16);
|
ctx.moveTo(0, 22);
|
||||||
ctx.lineTo(0, 8);
|
ctx.lineTo(0, 12);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
ctx.fillStyle = isDarkMode ? "white" : "black";
|
// 5. Индикаторы направления (теперь тоже в цвет интерфейса)
|
||||||
|
ctx.fillStyle = color;
|
||||||
ctx.font = "10px monospace";
|
ctx.font = "10px monospace";
|
||||||
ctx.textAlign = "center";
|
ctx.textAlign = "center";
|
||||||
ctx.textBaseline = "middle";
|
ctx.textBaseline = "middle";
|
||||||
ctx.globalAlpha = (dir === 1 ? 1.0 : 0.2) * alphaMult;
|
ctx.shadowBlur = 8;
|
||||||
ctx.fillText("▴", 0, -20);
|
ctx.globalAlpha = (dir === 1 ? 1.0 : 0.15) * alphaMult;
|
||||||
ctx.globalAlpha = (dir === 2 ? 1.0 : 0.2) * alphaMult;
|
ctx.fillText("▴", 0, -26);
|
||||||
ctx.fillText("▾", 0, 22);
|
ctx.globalAlpha = (dir === 2 ? 1.0 : 0.15) * alphaMult;
|
||||||
ctx.globalAlpha = (dir === 3 ? 1.0 : 0.2) * alphaMult;
|
ctx.fillText("▾", 0, 26);
|
||||||
ctx.fillText("◂", -22, 1);
|
ctx.globalAlpha = (dir === 3 ? 1.0 : 0.15) * alphaMult;
|
||||||
ctx.globalAlpha = (dir === 4 ? 1.0 : 0.2) * alphaMult;
|
ctx.fillText("◂", -26, 1);
|
||||||
ctx.fillText("▸", 22, 1);
|
ctx.globalAlpha = (dir === 4 ? 1.0 : 0.15) * alphaMult;
|
||||||
|
ctx.fillText("▸", 26, 1);
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
@@ -266,53 +295,53 @@ function drawEye(ctx, cx, cy, gazeX, gazeY, dir, isClosed) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
if (!engine) return requestAnimationFrame(render);
|
if (!engine || !wasm) return requestAnimationFrame(render);
|
||||||
|
|
||||||
engine.tick();
|
engine.tick();
|
||||||
|
|
||||||
|
// Очистка экрана с небольшим шлейфом (альфа 0.2)
|
||||||
ctx.globalAlpha = 1.0;
|
ctx.globalAlpha = 1.0;
|
||||||
ctx.fillStyle = isDarkMode
|
ctx.fillStyle = isDarkMode
|
||||||
? "rgba(10, 10, 12, 0.2)"
|
? "rgba(10, 10, 12, 0.2)"
|
||||||
: "rgba(250, 250, 252, 0.2)";
|
: "rgba(250, 250, 252, 0.2)";
|
||||||
ctx.fillRect(0, 0, width, height);
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
const bgLen = engine.bg_len();
|
// --- ЕДИНЫЙ РЕНДЕР МАТРИЦЫ (Параллакс + Ударные волны) ---
|
||||||
if (bgLen > 0) {
|
const renderLen = engine.render_len();
|
||||||
const bgData = new Float32Array(wasm.memory.buffer, engine.bg_ptr(), bgLen);
|
if (renderLen > 0) {
|
||||||
const bgF = fontSize * 0.6;
|
const data = new Float32Array(
|
||||||
for (let i = 0; i < bgLen; i += 4) {
|
wasm.memory.buffer,
|
||||||
ctx.globalAlpha = bgData[i + 3];
|
engine.render_ptr(),
|
||||||
|
renderLen,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Шаг 6, так как в Rust мы пишем [x, y, char_idx, is_head, alpha, scale]
|
||||||
|
for (let i = 0; i < renderLen; i += 6) {
|
||||||
|
const x = data[i];
|
||||||
|
const y = data[i + 1];
|
||||||
|
const charIdx = data[i + 2];
|
||||||
|
const isHead = data[i + 3];
|
||||||
|
const alpha = data[i + 4];
|
||||||
|
const scale = data[i + 5];
|
||||||
|
|
||||||
|
const scaledSize = fontSize * scale;
|
||||||
|
|
||||||
|
ctx.globalAlpha = alpha;
|
||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
atlasCanvas,
|
atlasCanvas,
|
||||||
bgData[i + 2] * fontSize,
|
charIdx * fontSize,
|
||||||
0,
|
isHead * fontSize, // Выбираем строку атласа: 0 (цвет) или 1 (белый)
|
||||||
fontSize,
|
fontSize,
|
||||||
fontSize,
|
fontSize,
|
||||||
bgData[i],
|
x,
|
||||||
bgData[i + 1],
|
y,
|
||||||
bgF,
|
scaledSize,
|
||||||
bgF,
|
scaledSize,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fgLen = engine.fg_len();
|
// --- РЕНДЕР ГЛАЗ ---
|
||||||
const fgData = new Float32Array(wasm.memory.buffer, engine.fg_ptr(), fgLen);
|
|
||||||
for (let i = 0; i < fgLen; i += 5) {
|
|
||||||
ctx.globalAlpha = fgData[i + 4];
|
|
||||||
ctx.drawImage(
|
|
||||||
atlasCanvas,
|
|
||||||
fgData[i + 2] * fontSize,
|
|
||||||
fgData[i + 3] * fontSize,
|
|
||||||
fontSize,
|
|
||||||
fontSize,
|
|
||||||
fgData[i],
|
|
||||||
fgData[i + 1],
|
|
||||||
fontSize,
|
|
||||||
fontSize,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const eyeLen = engine.eye_len();
|
const eyeLen = engine.eye_len();
|
||||||
if (eyeLen >= 6) {
|
if (eyeLen >= 6) {
|
||||||
const eyeData = new Float32Array(
|
const eyeData = new Float32Array(
|
||||||
@@ -327,12 +356,13 @@ function render() {
|
|||||||
const dir = eyeData[4];
|
const dir = eyeData[4];
|
||||||
const isClosed = eyeData[5];
|
const isClosed = eyeData[5];
|
||||||
|
|
||||||
const gap = isMobile ? 65 : 75;
|
const gap = isMobile ? 85 : 85; // Используем новые отступы
|
||||||
ctx.globalAlpha = 1.0;
|
ctx.globalAlpha = 1.0;
|
||||||
drawEye(ctx, cx - gap, cy, gazeX, gazeY, dir, isClosed);
|
drawEye(ctx, cx - gap, cy, gazeX, gazeY, dir, isClosed);
|
||||||
drawEye(ctx, cx + gap, cy, gazeX, gazeY, dir, isClosed);
|
drawEye(ctx, cx + gap, cy, gazeX, gazeY, dir, isClosed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- РЕНДЕР ГЛИТЧ-БЛОКОВ ---
|
||||||
const glLen = engine.glitch_len();
|
const glLen = engine.glitch_len();
|
||||||
if (glLen > 0) {
|
if (glLen > 0) {
|
||||||
ctx.globalAlpha = 1.0;
|
ctx.globalAlpha = 1.0;
|
||||||
@@ -347,6 +377,7 @@ function render() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- РЕНДЕР ИСКР (Glass Symbols) ---
|
||||||
const gsLen = engine.glass_len();
|
const gsLen = engine.glass_len();
|
||||||
if (gsLen > 0) {
|
if (gsLen > 0) {
|
||||||
const gsData = new Float32Array(
|
const gsData = new Float32Array(
|
||||||
@@ -359,7 +390,7 @@ function render() {
|
|||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
atlasCanvas,
|
atlasCanvas,
|
||||||
gsData[i + 2] * fontSize,
|
gsData[i + 2] * fontSize,
|
||||||
0,
|
0, // Искры всегда основного цвета (строка 0)
|
||||||
fontSize,
|
fontSize,
|
||||||
fontSize,
|
fontSize,
|
||||||
gsData[i],
|
gsData[i],
|
||||||
|
|||||||
+333
-149
@@ -43,8 +43,12 @@ impl FastRng {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Drop {
|
struct Drop {
|
||||||
|
base_x: f32, // Якорная позиция по X (с учетом сноса ветром/гироскопом)
|
||||||
y: f32,
|
y: f32,
|
||||||
|
offset_x: f32, // Текущее смещение для пружинной анимации
|
||||||
|
vel_x: f32, // Текущая скорость смещения
|
||||||
speed: f32,
|
speed: f32,
|
||||||
|
layer: u8, // 0 = Дальний (Far), 1 = Средний (Mid), 2 = Ближний (Near)
|
||||||
glitch_timer: f32,
|
glitch_timer: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,6 +86,15 @@ impl GlitchBlock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Shockwave {
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
radius: f32,
|
||||||
|
life: f32,
|
||||||
|
max_life: f32,
|
||||||
|
active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
struct GlassSymbol {
|
struct GlassSymbol {
|
||||||
x: f32,
|
x: f32,
|
||||||
y: f32,
|
y: f32,
|
||||||
@@ -98,13 +111,13 @@ pub struct MatrixEngine {
|
|||||||
font_size: u32,
|
font_size: u32,
|
||||||
charset_len: usize,
|
charset_len: usize,
|
||||||
rng: FastRng,
|
rng: FastRng,
|
||||||
fg_drops: Vec<Drop>,
|
|
||||||
bg_drops: Vec<Drop>,
|
drops: Vec<Drop>,
|
||||||
|
shockwaves: Vec<Shockwave>,
|
||||||
glass_symbols: Vec<GlassSymbol>,
|
glass_symbols: Vec<GlassSymbol>,
|
||||||
glitch_blocks: Vec<GlitchBlock>,
|
glitch_blocks: Vec<GlitchBlock>,
|
||||||
|
|
||||||
fg_buffer: Vec<f32>,
|
render_buffer: Vec<f32>, // Единый буфер рендера [x, y, char_idx, is_head, alpha, scale]
|
||||||
bg_buffer: Vec<f32>,
|
|
||||||
glitch_buffer: Vec<f32>,
|
glitch_buffer: Vec<f32>,
|
||||||
glass_buffer: Vec<f32>,
|
glass_buffer: Vec<f32>,
|
||||||
eye_buffer: Vec<f32>,
|
eye_buffer: Vec<f32>,
|
||||||
@@ -132,6 +145,13 @@ pub struct MatrixEngine {
|
|||||||
eye_closedness_vel: f32,
|
eye_closedness_vel: f32,
|
||||||
eye_anchor_y: f32,
|
eye_anchor_y: f32,
|
||||||
mobile_wander_timer: f32,
|
mobile_wander_timer: f32,
|
||||||
|
|
||||||
|
// Mobile & Physics state
|
||||||
|
tilt_x: f32,
|
||||||
|
tilt_y: f32,
|
||||||
|
swipe_x: f32,
|
||||||
|
swipe_y: f32,
|
||||||
|
swipe_timer: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
@@ -140,18 +160,28 @@ impl MatrixEngine {
|
|||||||
pub fn new(width: u32, height: u32, font_size: u32) -> MatrixEngine {
|
pub fn new(width: u32, height: u32, font_size: u32) -> MatrixEngine {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
|
let shockwaves = (0..5)
|
||||||
|
.map(|_| Shockwave {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
radius: 0.0,
|
||||||
|
life: 0.0,
|
||||||
|
max_life: 0.0,
|
||||||
|
active: false,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let mut engine = MatrixEngine {
|
let mut engine = MatrixEngine {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
font_size,
|
font_size,
|
||||||
charset_len: 84,
|
charset_len: 84,
|
||||||
rng: FastRng::new(1337),
|
rng: FastRng::new(1337),
|
||||||
fg_drops: Vec::new(),
|
drops: Vec::new(),
|
||||||
bg_drops: Vec::new(),
|
shockwaves,
|
||||||
glass_symbols: Vec::new(),
|
glass_symbols: Vec::new(),
|
||||||
glitch_blocks: Vec::new(),
|
glitch_blocks: Vec::new(),
|
||||||
fg_buffer: Vec::new(),
|
render_buffer: Vec::new(),
|
||||||
bg_buffer: Vec::new(),
|
|
||||||
glitch_buffer: Vec::new(),
|
glitch_buffer: Vec::new(),
|
||||||
glass_buffer: Vec::new(),
|
glass_buffer: Vec::new(),
|
||||||
eye_buffer: Vec::new(),
|
eye_buffer: Vec::new(),
|
||||||
@@ -176,40 +206,49 @@ impl MatrixEngine {
|
|||||||
eye_closedness_vel: 0.0,
|
eye_closedness_vel: 0.0,
|
||||||
eye_anchor_y: 0.0,
|
eye_anchor_y: 0.0,
|
||||||
mobile_wander_timer: 0.0,
|
mobile_wander_timer: 0.0,
|
||||||
|
tilt_x: 0.0,
|
||||||
|
tilt_y: 0.0,
|
||||||
|
swipe_x: 0.0,
|
||||||
|
swipe_y: 0.0,
|
||||||
|
swipe_timer: 0.0,
|
||||||
};
|
};
|
||||||
engine.init_drops();
|
engine.init_drops();
|
||||||
engine
|
engine
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_drops(&mut self) {
|
fn init_drops(&mut self) {
|
||||||
self.fg_drops.clear();
|
self.drops.clear();
|
||||||
self.bg_drops.clear();
|
|
||||||
|
|
||||||
let fg_spacing = if self.is_mobile { 2.0 } else { 1.0 } * self.font_size as f32;
|
// 3 слоя параллакса: (Layer ID, Scale, Min Speed, Max Speed)
|
||||||
let fg_columns = (self.width as f32 / fg_spacing).ceil() as u32;
|
let layers = [
|
||||||
|
(0, 0.4, 0.005, 0.02), // Far (бывший bg_drops)
|
||||||
|
(1, 0.7, 0.02, 0.06), // Mid
|
||||||
|
(2, 1.0, 0.05, 0.15), // Near (бывший fg_drops)
|
||||||
|
];
|
||||||
|
|
||||||
for _ in 0..fg_columns {
|
let base_spacing = if self.is_mobile { 1.5 } else { 1.0 } * self.font_size as f32;
|
||||||
self.fg_drops.push(Drop {
|
|
||||||
y: self.rng.gen_range(-20.0, self.height as f32),
|
for (layer, scale, min_spd, max_spd) in layers.iter() {
|
||||||
speed: self.rng.gen_range(0.02, 0.15),
|
let spacing = base_spacing * scale;
|
||||||
|
// Делаем запас колонок по бокам для параллакса и наклона
|
||||||
|
let columns = (self.width as f32 / spacing).ceil() as u32 + 20;
|
||||||
|
|
||||||
|
for i in 0..columns {
|
||||||
|
let start_x = (i as f32 * spacing) - (10.0 * spacing);
|
||||||
|
self.drops.push(Drop {
|
||||||
|
base_x: start_x,
|
||||||
|
y: self.rng.gen_range(-50.0, self.height as f32),
|
||||||
|
offset_x: 0.0,
|
||||||
|
vel_x: 0.0,
|
||||||
|
speed: self.rng.gen_range(*min_spd, *max_spd),
|
||||||
|
layer: *layer as u8,
|
||||||
glitch_timer: 0.0,
|
glitch_timer: 0.0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let bg_density = if self.is_mobile { 4.0 } else { 1.5 };
|
|
||||||
let bg_spacing = self.font_size as f32 * bg_density;
|
|
||||||
let bg_columns = (self.width as f32 / bg_spacing).ceil() as u32;
|
|
||||||
|
|
||||||
for _ in 0..bg_columns {
|
|
||||||
self.bg_drops.push(Drop {
|
|
||||||
y: self.rng.gen_range(-20.0, self.height as f32),
|
|
||||||
speed: self.rng.gen_range(0.005, 0.04),
|
|
||||||
glitch_timer: 0.0,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.fg_buffer.resize(self.fg_drops.len() * 5, 0.0);
|
// Выделяем память: размер вектора = кол-во капель * 6 параметров
|
||||||
self.bg_buffer.resize(self.bg_drops.len() * 4, 0.0);
|
self.render_buffer.resize(self.drops.len() * 6, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(&mut self, width: u32, height: u32) {
|
pub fn resize(&mut self, width: u32, height: u32) {
|
||||||
@@ -224,6 +263,11 @@ impl MatrixEngine {
|
|||||||
self.gaze_target_x = x;
|
self.gaze_target_x = x;
|
||||||
self.gaze_target_y = y;
|
self.gaze_target_y = y;
|
||||||
self.last_interaction = 0.0;
|
self.last_interaction = 0.0;
|
||||||
|
|
||||||
|
// Автоматически запускаем магнитный свайп (Kinetic Swipe) при любом движении!
|
||||||
|
self.swipe_x = x;
|
||||||
|
self.swipe_y = y;
|
||||||
|
self.swipe_timer = 15.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_drawing(&mut self, is_drawing: bool) {
|
pub fn set_drawing(&mut self, is_drawing: bool) {
|
||||||
@@ -234,7 +278,6 @@ impl MatrixEngine {
|
|||||||
self.is_mobile = is_mobile;
|
self.is_mobile = is_mobile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn set_eye_anchor(&mut self, cy: f32) {
|
pub fn set_eye_anchor(&mut self, cy: f32) {
|
||||||
self.eye_anchor_y = cy;
|
self.eye_anchor_y = cy;
|
||||||
}
|
}
|
||||||
@@ -243,16 +286,63 @@ impl MatrixEngine {
|
|||||||
self.is_vpn_on = is_vpn_on;
|
self.is_vpn_on = is_vpn_on;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_tilt(&mut self, tilt_x: f32, tilt_y: f32) {
|
||||||
|
self.tilt_x = tilt_x;
|
||||||
|
self.tilt_y = tilt_y;
|
||||||
|
}
|
||||||
|
pub fn trigger_shockwave(&mut self, x: f32, y: f32) {
|
||||||
|
// 1. Заставляем глаза смотреть на точку клика и обновляем "магнит"
|
||||||
|
self.update_mouse(x, y);
|
||||||
|
|
||||||
|
// 2. Запускаем физику ударной волны
|
||||||
|
if let Some(sw) = self.shockwaves.iter_mut().find(|s| !s.active) {
|
||||||
|
sw.x = x;
|
||||||
|
sw.y = y;
|
||||||
|
sw.radius = 0.0;
|
||||||
|
sw.life = 40.0;
|
||||||
|
sw.max_life = 40.0;
|
||||||
|
sw.active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Добавляем небольшой цифровой всплеск (глитч) при ударе
|
||||||
|
let blocks_count = if self.is_mobile { 4 } else { 12 };
|
||||||
|
for _ in 0..blocks_count {
|
||||||
|
let block = GlitchBlock::new(x, y, &mut self.rng);
|
||||||
|
self.glitch_blocks.push(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kinetic_swipe(&mut self, x: f32, y: f32) {
|
||||||
|
self.swipe_x = x;
|
||||||
|
self.swipe_y = y;
|
||||||
|
self.swipe_timer = 15.0;
|
||||||
|
|
||||||
|
// Передаем координаты свайпа глазам, чтобы они чётко следили за движением
|
||||||
|
self.update_mouse(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn trigger_glitch(&mut self, x: f32, y: f32) {
|
pub fn trigger_glitch(&mut self, x: f32, y: f32) {
|
||||||
self.emp_timer = 60.0;
|
self.emp_timer = 60.0;
|
||||||
self.emp_x = x;
|
self.emp_x = x;
|
||||||
self.emp_y = y;
|
self.emp_y = y;
|
||||||
|
|
||||||
let blocks_count = if self.is_mobile { 3 } else { 25 };
|
// 1. Запускаем Ударную Волну (Shockwave) прямо по клику!
|
||||||
|
if let Some(sw) = self.shockwaves.iter_mut().find(|s| !s.active) {
|
||||||
|
sw.x = x;
|
||||||
|
sw.y = y;
|
||||||
|
sw.radius = 0.0;
|
||||||
|
sw.life = 40.0;
|
||||||
|
sw.max_life = 40.0;
|
||||||
|
sw.active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Спавним прямоугольные глитчи вместе с волной
|
||||||
|
let blocks_count = if self.is_mobile { 5 } else { 25 };
|
||||||
for _ in 0..blocks_count {
|
for _ in 0..blocks_count {
|
||||||
let block = GlitchBlock::new(x, y, &mut self.rng);
|
let block = GlitchBlock::new(x, y, &mut self.rng);
|
||||||
self.glitch_blocks.push(block);
|
self.glitch_blocks.push(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_mouse(x, y);
|
self.update_mouse(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,22 +352,42 @@ impl MatrixEngine {
|
|||||||
|
|
||||||
self.glitch_buffer.clear();
|
self.glitch_buffer.clear();
|
||||||
self.glass_buffer.clear();
|
self.glass_buffer.clear();
|
||||||
|
self.eye_buffer.clear();
|
||||||
|
|
||||||
// --- Eye Logic ---
|
// --- Ударные Волны ---
|
||||||
// Calculate container center
|
for sw in self.shockwaves.iter_mut().filter(|s| s.active) {
|
||||||
|
sw.radius += 25.0; // Расширяем кольцо
|
||||||
|
sw.life -= 1.0;
|
||||||
|
if sw.life <= 0.0 {
|
||||||
|
sw.active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.swipe_timer > 0.0 {
|
||||||
|
self.swipe_timer -= 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Логика Глаз ---
|
||||||
let cx = self.width as f32 / 2.0;
|
let cx = self.width as f32 / 2.0;
|
||||||
let cy = if self.eye_anchor_y > 0.0 { self.eye_anchor_y } else { if self.is_mobile { 150.0 } else { 180.0 } };
|
let cy = if self.eye_anchor_y > 0.0 {
|
||||||
|
self.eye_anchor_y
|
||||||
|
} else {
|
||||||
|
if self.is_mobile {
|
||||||
|
150.0
|
||||||
|
} else {
|
||||||
|
180.0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Random wander for mobile
|
|
||||||
if self.is_mobile && self.last_interaction > 150.0 {
|
if self.is_mobile && self.last_interaction > 150.0 {
|
||||||
self.mobile_wander_timer += 1.0;
|
self.mobile_wander_timer += 1.0;
|
||||||
if self.mobile_wander_timer > 108.0 { // ~1.8s
|
if self.mobile_wander_timer > 108.0 {
|
||||||
self.mobile_wander_timer = 0.0;
|
self.mobile_wander_timer = 0.0;
|
||||||
if self.rng.gen_bool(0.8) {
|
if self.rng.gen_bool(0.8) {
|
||||||
self.gaze_target_x = cx + self.rng.gen_range(-100.0, 100.0);
|
self.gaze_target_x = cx + self.rng.gen_range(-100.0, 100.0);
|
||||||
self.gaze_target_y = cy + self.rng.gen_range(-100.0, 100.0);
|
self.gaze_target_y = cy + self.rng.gen_range(-100.0, 100.0);
|
||||||
if self.rng.gen_bool(0.4) {
|
if self.rng.gen_bool(0.4) {
|
||||||
self.blink_timer = 12.0; // micro-blink
|
self.blink_timer = 12.0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.gaze_target_x = cx;
|
self.gaze_target_x = cx;
|
||||||
@@ -288,64 +398,52 @@ impl MatrixEngine {
|
|||||||
|
|
||||||
let dx = self.gaze_target_x - cx;
|
let dx = self.gaze_target_x - cx;
|
||||||
let dy = self.gaze_target_y - cy;
|
let dy = self.gaze_target_y - cy;
|
||||||
|
|
||||||
let mut dir = 0.0;
|
let mut dir = 0.0;
|
||||||
if dx.abs() < 60.0 && dy.abs() < 60.0 {
|
if dx.abs() < 60.0 && dy.abs() < 60.0 {
|
||||||
dir = 0.0; // center
|
dir = 0.0;
|
||||||
} else if dx.abs() > dy.abs() {
|
} else if dx.abs() > dy.abs() {
|
||||||
dir = if dx > 0.0 { 4.0 } else { 3.0 }; // 4=right, 3=left
|
dir = if dx > 0.0 { 4.0 } else { 3.0 };
|
||||||
} else {
|
} else {
|
||||||
dir = if dy > 0.0 { 2.0 } else { 1.0 }; // 2=down, 1=up
|
dir = if dy > 0.0 { 2.0 } else { 1.0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
let distance = (dx * dx + dy * dy).sqrt();
|
let distance = (dx * dx + dy * dy).sqrt();
|
||||||
let max_radius = 20.0;
|
let target_dist = (distance * 0.1).min(20.0);
|
||||||
let target_dist = (distance * 0.1).min(max_radius);
|
|
||||||
let angle = dy.atan2(dx);
|
let angle = dy.atan2(dx);
|
||||||
|
self.gaze_x += (angle.cos() * target_dist - self.gaze_x) * 0.12;
|
||||||
|
self.gaze_y += (angle.sin() * target_dist - self.gaze_y) * 0.12;
|
||||||
|
|
||||||
let target_gaze_x = angle.cos() * target_dist;
|
|
||||||
let target_gaze_y = angle.sin() * target_dist;
|
|
||||||
|
|
||||||
// Spring physics interpolation (smoother gaze)
|
|
||||||
self.gaze_x += (target_gaze_x - self.gaze_x) * 0.12;
|
|
||||||
self.gaze_y += (target_gaze_y - self.gaze_y) * 0.12;
|
|
||||||
|
|
||||||
// Blinking
|
|
||||||
if self.blink_timer > 0.0 {
|
if self.blink_timer > 0.0 {
|
||||||
self.blink_timer -= 1.0;
|
self.blink_timer -= 1.0;
|
||||||
} else if !self.is_vpn_on {
|
} else if !self.is_vpn_on && self.rng.gen_bool(0.0015) {
|
||||||
if self.rng.gen_bool(0.0015) { // random blink
|
|
||||||
self.blink_timer = 16.0;
|
self.blink_timer = 16.0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let target_closedness = if self.is_vpn_on || self.blink_timer > 0.0 { 1.0 } else { 0.0 };
|
let target_closedness = if self.is_vpn_on || self.blink_timer > 0.0 {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
self.eye_closedness_vel += (target_closedness - self.eye_closedness) * 0.06;
|
||||||
|
self.eye_closedness_vel *= 0.88;
|
||||||
|
self.eye_closedness = (self.eye_closedness + self.eye_closedness_vel).clamp(-0.15, 1.15);
|
||||||
|
|
||||||
// Springy blink physics (slower, smoother)
|
self.eye_buffer.extend_from_slice(&[
|
||||||
let diff = target_closedness - self.eye_closedness;
|
cx,
|
||||||
self.eye_closedness_vel += diff * 0.06; // lower stiffness (slower acceleration)
|
cy,
|
||||||
self.eye_closedness_vel *= 0.88; // higher damping (longer slide, less snappy)
|
self.gaze_x,
|
||||||
self.eye_closedness += self.eye_closedness_vel;
|
self.gaze_y,
|
||||||
|
dir,
|
||||||
// Clamp to prevent extreme glitches, but allow small spring bounce
|
self.eye_closedness,
|
||||||
if self.eye_closedness < -0.15 { self.eye_closedness = -0.15; }
|
]);
|
||||||
if self.eye_closedness > 1.15 { self.eye_closedness = 1.15; }
|
|
||||||
|
|
||||||
self.eye_buffer.clear();
|
|
||||||
self.eye_buffer.push(cx);
|
|
||||||
self.eye_buffer.push(cy);
|
|
||||||
self.eye_buffer.push(self.gaze_x);
|
|
||||||
self.eye_buffer.push(self.gaze_y);
|
|
||||||
self.eye_buffer.push(dir);
|
|
||||||
self.eye_buffer.push(self.eye_closedness);
|
|
||||||
// --- End Eye Logic ---
|
|
||||||
|
|
||||||
|
// --- Глобальные эффекты (EMP, Слезы) ---
|
||||||
if !self.is_mobile {
|
if !self.is_mobile {
|
||||||
if self.rng.gen_bool(0.01) {
|
if self.rng.gen_bool(0.01) {
|
||||||
let rx = self.rng.gen_range(0.0, self.width as f32);
|
let rx = self.rng.gen_range(0.0, self.width as f32);
|
||||||
let ry = self.rng.gen_range(0.0, self.height as f32);
|
let ry = self.rng.gen_range(0.0, self.height as f32);
|
||||||
let block = GlitchBlock::new(rx, ry, &mut self.rng);
|
self.glitch_blocks
|
||||||
self.glitch_blocks.push(block);
|
.push(GlitchBlock::new(rx, ry, &mut self.rng));
|
||||||
}
|
}
|
||||||
if self.rng.gen_bool(0.002) {
|
if self.rng.gen_bool(0.002) {
|
||||||
self.tear_y = self.rng.gen_range(0.0, self.height as f32);
|
self.tear_y = self.rng.gen_range(0.0, self.height as f32);
|
||||||
@@ -360,59 +458,97 @@ impl MatrixEngine {
|
|||||||
self.tear_timer -= 1.0;
|
self.tear_timer -= 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let bg_density = if self.is_mobile { 4.0 } else { 1.5 };
|
|
||||||
let bg_x_spacing = self.font_size as f32 * bg_density;
|
|
||||||
let bg_y_spacing = self.font_size as f32 * 0.6;
|
|
||||||
|
|
||||||
for i in 0..self.bg_drops.len() {
|
|
||||||
let drop = &mut self.bg_drops[i];
|
|
||||||
let x = (i as f32) * bg_x_spacing;
|
|
||||||
let cell_y = drop.y as i32 as f32;
|
|
||||||
let y = drop.y * bg_y_spacing;
|
|
||||||
|
|
||||||
let hash = (i as u32)
|
|
||||||
.wrapping_mul(12345)
|
|
||||||
.wrapping_add((cell_y as u32).wrapping_mul(67890));
|
|
||||||
let char_idx = (hash as usize) % self.charset_len;
|
|
||||||
|
|
||||||
let base_idx = i * 4;
|
|
||||||
self.bg_buffer[base_idx] = x;
|
|
||||||
self.bg_buffer[base_idx + 1] = y;
|
|
||||||
self.bg_buffer[base_idx + 2] = char_idx as f32;
|
|
||||||
self.bg_buffer[base_idx + 3] = 0.02 + ((hash % 100) as f32 * 0.0008);
|
|
||||||
|
|
||||||
drop.y += drop.speed;
|
|
||||||
if y > self.height as f32 && self.rng.gen_bool(0.005) {
|
|
||||||
drop.y = self.rng.gen_range(-10.0, 0.0);
|
|
||||||
drop.speed = self.rng.gen_range(0.005, 0.04);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let emp_rad = (60.0 - self.emp_timer) * 15.0;
|
let emp_rad = (60.0 - self.emp_timer) * 15.0;
|
||||||
let emp_rad_sq = emp_rad * emp_rad;
|
let emp_rad_sq = emp_rad * emp_rad;
|
||||||
let emp_r20_sq = if emp_rad > 20.0 { (emp_rad - 20.0) * (emp_rad - 20.0) } else { 0.0 };
|
let emp_r20_sq = if emp_rad > 20.0 {
|
||||||
let emp_r80_sq = if emp_rad > 80.0 { (emp_rad - 80.0) * (emp_rad - 80.0) } else { 0.0 };
|
(emp_rad - 20.0).powi(2)
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
let emp_r80_sq = if emp_rad > 80.0 {
|
||||||
|
(emp_rad - 80.0).powi(2)
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
|
||||||
let fg_spacing = if self.is_mobile { 2.0 } else { 1.0 } * self.font_size as f32;
|
// --- Итерация по единому массиву капель ---
|
||||||
|
for i in 0..self.drops.len() {
|
||||||
|
let drop = &mut self.drops[i];
|
||||||
|
|
||||||
for i in 0..self.fg_drops.len() {
|
let parallax_factor = match drop.layer {
|
||||||
let drop = &mut self.fg_drops[i];
|
0 => 0.3,
|
||||||
let mut x = (i as f32) * fg_spacing;
|
1 => 0.6,
|
||||||
let cell_y = drop.y as i32 as f32;
|
_ => 1.0,
|
||||||
let y = drop.y * (self.font_size as f32);
|
};
|
||||||
|
|
||||||
|
// 1. ГИРОСКОП: Постоянный снос
|
||||||
|
drop.base_x += self.tilt_x * parallax_factor * 5.0;
|
||||||
|
|
||||||
|
// Бесконечный экран
|
||||||
|
let wrap_margin = 150.0;
|
||||||
|
if drop.base_x < -wrap_margin {
|
||||||
|
drop.base_x += self.width as f32 + wrap_margin * 2.0;
|
||||||
|
drop.y = 0.0;
|
||||||
|
} else if drop.base_x > self.width as f32 + wrap_margin {
|
||||||
|
drop.base_x -= self.width as f32 + wrap_margin * 2.0;
|
||||||
|
drop.y = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_x = drop.base_x + drop.offset_x;
|
||||||
|
let current_y = drop.y * (self.font_size as f32 * parallax_factor);
|
||||||
|
|
||||||
let mut is_scrambled = false;
|
let mut is_scrambled = false;
|
||||||
let mut is_void = false;
|
let mut is_void = false;
|
||||||
let mut is_emp_ring = false;
|
let mut is_emp_ring = false;
|
||||||
|
let mut force_white = false;
|
||||||
|
|
||||||
if !self.is_mobile && self.tear_timer > 0.0 && (y - self.tear_y).abs() < 60.0 {
|
// 2. УДАРНЫЕ ВОЛНЫ (по клику)
|
||||||
x += self.rng.gen_range(-20.0, 20.0);
|
for sw in self.shockwaves.iter().filter(|s| s.active) {
|
||||||
is_scrambled = true;
|
let dx = current_x - sw.x;
|
||||||
|
let dy = current_y - sw.y;
|
||||||
|
let dist = (dx * dx + dy * dy).sqrt();
|
||||||
|
|
||||||
|
if (dist - sw.radius).abs() < 40.0 {
|
||||||
|
let force = (sw.life / sw.max_life) * 18.0 * parallax_factor;
|
||||||
|
drop.vel_x += (dx / dist.max(1.0)) * force;
|
||||||
|
force_white = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.is_mobile && self.emp_timer > 0.0 {
|
// 3. КИНЕТИЧЕСКИЙ СВАЙП И МАГНИТ (Палец/Мышь)
|
||||||
let dx = x - self.emp_x;
|
let check_swipe = self.swipe_timer > 0.0;
|
||||||
let dy = y - self.emp_y;
|
let check_mouse = !self.is_mobile && self.last_interaction < 10.0;
|
||||||
|
|
||||||
|
if check_swipe || check_mouse {
|
||||||
|
let (ix, iy) = if check_swipe {
|
||||||
|
(self.swipe_x, self.swipe_y)
|
||||||
|
} else {
|
||||||
|
(self.mouse_x, self.mouse_y)
|
||||||
|
};
|
||||||
|
let dx = current_x - ix;
|
||||||
|
let dy = current_y - iy;
|
||||||
|
let dist_sq = dx * dx + dy * dy;
|
||||||
|
let rad = if self.is_mobile { 100.0 } else { 140.0 };
|
||||||
|
|
||||||
|
if dist_sq < rad * rad {
|
||||||
|
let dist = dist_sq.sqrt().max(1.0);
|
||||||
|
let force = (rad - dist) / rad;
|
||||||
|
// Отталкивание пружины
|
||||||
|
drop.vel_x += (dx / dist) * force * 15.0 * parallax_factor;
|
||||||
|
// Замедление падения ("Вязкость")
|
||||||
|
drop.y -= drop.speed * force * 0.6;
|
||||||
|
|
||||||
|
is_scrambled = true;
|
||||||
|
if dist < rad * 0.5 {
|
||||||
|
force_white = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EMP Волна от включения VPN (только для передних слоев)
|
||||||
|
if self.emp_timer > 0.0 && drop.layer > 0 {
|
||||||
|
let dx = current_x - self.emp_x;
|
||||||
|
let dy = current_y - self.emp_y;
|
||||||
let dist_sq = dx * dx + dy * dy;
|
let dist_sq = dx * dx + dy * dy;
|
||||||
|
|
||||||
if dist_sq < emp_rad_sq {
|
if dist_sq < emp_rad_sq {
|
||||||
@@ -426,60 +562,91 @@ impl MatrixEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.is_mobile && self.tear_timer > 0.0 && (current_y - self.tear_y).abs() < 60.0 {
|
||||||
|
drop.vel_x += self.rng.gen_range(-10.0, 10.0);
|
||||||
|
is_scrambled = true;
|
||||||
|
}
|
||||||
|
|
||||||
if is_void {
|
if is_void {
|
||||||
drop.y += drop.speed;
|
drop.y += drop.speed;
|
||||||
let base_idx = i * 5;
|
self.render_buffer[i * 6 + 4] = 0.0; // Alpha = 0
|
||||||
self.fg_buffer[base_idx + 4] = 0.0;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. ПРУЖИННАЯ ФИЗИКА
|
||||||
|
drop.vel_x += -0.12 * drop.offset_x; // Сила возврата (Гука)
|
||||||
|
drop.vel_x *= 0.85; // Трение (Затухание)
|
||||||
|
drop.offset_x += drop.vel_x;
|
||||||
|
|
||||||
if self.rng.gen_bool(0.001) {
|
if self.rng.gen_bool(0.001) {
|
||||||
drop.glitch_timer = 5.0;
|
drop.glitch_timer = 5.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cell_y = drop.y as i32 as u32;
|
||||||
let time_shift = self.tick_count / 10;
|
let time_shift = self.tick_count / 10;
|
||||||
let hash = (i as u32)
|
let hash = (i as u32)
|
||||||
.wrapping_mul(73856093)
|
.wrapping_mul(73856093)
|
||||||
.wrapping_add((cell_y as u32).wrapping_mul(19349663))
|
.wrapping_add(cell_y.wrapping_mul(19349663))
|
||||||
.wrapping_add(time_shift);
|
.wrapping_add(time_shift);
|
||||||
let mut char_idx = (hash as usize) % self.charset_len;
|
let mut char_idx = (hash as usize) % self.charset_len;
|
||||||
|
|
||||||
let mut is_head = (hash % 100) < 15;
|
let mut is_head = (hash % 100) < 15;
|
||||||
let mut target_alpha = if is_head { 1.0 } else { 0.6 };
|
let mut base_alpha = match drop.layer {
|
||||||
|
0 => 0.15,
|
||||||
|
1 => 0.45,
|
||||||
|
_ => 0.8,
|
||||||
|
};
|
||||||
|
|
||||||
if is_emp_ring {
|
if is_emp_ring {
|
||||||
is_head = true;
|
is_head = true;
|
||||||
target_alpha = 1.0;
|
base_alpha = 1.0;
|
||||||
char_idx = (hash.wrapping_add(self.tick_count) as usize) % self.charset_len;
|
char_idx = (hash.wrapping_add(self.tick_count) as usize) % self.charset_len;
|
||||||
} else if is_scrambled || drop.glitch_timer > 0.0 {
|
} else if is_scrambled || drop.glitch_timer > 0.0 {
|
||||||
char_idx = (hash.wrapping_add(self.tick_count) as usize) % self.charset_len;
|
char_idx = (hash.wrapping_add(self.tick_count) as usize) % self.charset_len;
|
||||||
target_alpha = 0.6 + ((hash % 100) as f32 * 0.004);
|
base_alpha = (base_alpha + 0.2_f32).min(1.0_f32);
|
||||||
is_head = (hash % 10) < 2;
|
is_head = (hash % 10) < 2;
|
||||||
if drop.glitch_timer > 0.0 {
|
if drop.glitch_timer > 0.0 {
|
||||||
drop.glitch_timer -= 1.0;
|
drop.glitch_timer -= 1.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.is_mobile {
|
if force_white {
|
||||||
target_alpha = 0.8;
|
is_head = true;
|
||||||
|
base_alpha = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.is_mobile && !force_white && drop.layer == 2 {
|
||||||
|
base_alpha = 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
let atlas_row = if is_head { 1.0 } else { 0.0 };
|
let atlas_row = if is_head { 1.0 } else { 0.0 };
|
||||||
|
let scale = match drop.layer {
|
||||||
|
0 => 0.4,
|
||||||
|
1 => 0.7,
|
||||||
|
_ => 1.0,
|
||||||
|
};
|
||||||
|
let final_x = drop.base_x + drop.offset_x;
|
||||||
|
|
||||||
let base_idx = i * 5;
|
let base_idx = i * 6;
|
||||||
self.fg_buffer[base_idx] = x;
|
self.render_buffer[base_idx] = final_x;
|
||||||
self.fg_buffer[base_idx + 1] = y;
|
self.render_buffer[base_idx + 1] = current_y;
|
||||||
self.fg_buffer[base_idx + 2] = char_idx as f32;
|
self.render_buffer[base_idx + 2] = char_idx as f32;
|
||||||
self.fg_buffer[base_idx + 3] = atlas_row;
|
self.render_buffer[base_idx + 3] = atlas_row;
|
||||||
self.fg_buffer[base_idx + 4] = target_alpha;
|
self.render_buffer[base_idx + 4] = base_alpha;
|
||||||
|
self.render_buffer[base_idx + 5] = scale;
|
||||||
|
|
||||||
drop.y += drop.speed;
|
drop.y += drop.speed;
|
||||||
if y > self.height as f32 && self.rng.gen_bool(0.01) {
|
if current_y > self.height as f32 + 50.0 && self.rng.gen_bool(0.01) {
|
||||||
drop.y = self.rng.gen_range(-10.0, 0.0);
|
drop.y = self.rng.gen_range(-20.0, 0.0);
|
||||||
drop.speed = self.rng.gen_range(0.02, 0.15);
|
drop.speed = match drop.layer {
|
||||||
|
0 => self.rng.gen_range(0.005, 0.02),
|
||||||
|
1 => self.rng.gen_range(0.02, 0.06),
|
||||||
|
_ => self.rng.gen_range(0.05, 0.15),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Глитчи и Осколки (Пылинки) ---
|
||||||
for block in self.glitch_blocks.iter_mut() {
|
for block in self.glitch_blocks.iter_mut() {
|
||||||
self.glitch_buffer.extend_from_slice(&[
|
self.glitch_buffer.extend_from_slice(&[
|
||||||
block.x,
|
block.x,
|
||||||
@@ -492,19 +659,20 @@ impl MatrixEngine {
|
|||||||
}
|
}
|
||||||
self.glitch_blocks.retain(|b| b.life > 0.0);
|
self.glitch_blocks.retain(|b| b.life > 0.0);
|
||||||
|
|
||||||
if !self.is_mobile && self.is_drawing && self.rng.gen_bool(0.3) {
|
if !self.is_mobile && self.last_interaction < 2.0 && self.rng.gen_bool(0.2) {
|
||||||
self.glass_symbols.push(GlassSymbol {
|
self.glass_symbols.push(GlassSymbol {
|
||||||
x: self.mouse_x + self.rng.gen_range(-15.0, 15.0),
|
x: self.mouse_x + self.rng.gen_range(-20.0, 20.0),
|
||||||
y: self.mouse_y + self.rng.gen_range(-15.0, 15.0),
|
y: self.mouse_y + self.rng.gen_range(-20.0, 20.0),
|
||||||
char_idx: self.rng.gen_usize(self.charset_len),
|
char_idx: self.rng.gen_usize(self.charset_len),
|
||||||
life: 1.0,
|
life: 1.0,
|
||||||
max_life: self.rng.gen_range(30.0, 80.0),
|
max_life: self.rng.gen_range(20.0, 50.0),
|
||||||
velocity_y: self.rng.gen_range(0.1, 0.5),
|
velocity_y: self.rng.gen_range(-1.5, 1.5),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for sym in self.glass_symbols.iter_mut() {
|
for sym in self.glass_symbols.iter_mut() {
|
||||||
sym.y += sym.velocity_y;
|
sym.y += sym.velocity_y;
|
||||||
|
sym.x += self.rng.gen_range(-0.5, 0.5);
|
||||||
sym.life -= 1.0;
|
sym.life -= 1.0;
|
||||||
let alpha = (sym.life / sym.max_life).max(0.0);
|
let alpha = (sym.life / sym.max_life).max(0.0);
|
||||||
self.glass_buffer
|
self.glass_buffer
|
||||||
@@ -513,14 +681,30 @@ impl MatrixEngine {
|
|||||||
self.glass_symbols.retain(|s| s.life > 0.0);
|
self.glass_symbols.retain(|s| s.life > 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fg_ptr(&self) -> *const f32 { self.fg_buffer.as_ptr() }
|
// Обрати внимание: Вместо fg_ptr и bg_ptr теперь один render_ptr
|
||||||
pub fn fg_len(&self) -> usize { self.fg_buffer.len() }
|
pub fn render_ptr(&self) -> *const f32 {
|
||||||
pub fn bg_ptr(&self) -> *const f32 { self.bg_buffer.as_ptr() }
|
self.render_buffer.as_ptr()
|
||||||
pub fn bg_len(&self) -> usize { self.bg_buffer.len() }
|
}
|
||||||
pub fn glitch_ptr(&self) -> *const f32 { self.glitch_buffer.as_ptr() }
|
pub fn render_len(&self) -> usize {
|
||||||
pub fn glitch_len(&self) -> usize { self.glitch_buffer.len() }
|
self.render_buffer.len()
|
||||||
pub fn glass_ptr(&self) -> *const f32 { self.glass_buffer.as_ptr() }
|
}
|
||||||
pub fn glass_len(&self) -> usize { self.glass_buffer.len() }
|
|
||||||
pub fn eye_ptr(&self) -> *const f32 { self.eye_buffer.as_ptr() }
|
pub fn glitch_ptr(&self) -> *const f32 {
|
||||||
pub fn eye_len(&self) -> usize { self.eye_buffer.len() }
|
self.glitch_buffer.as_ptr()
|
||||||
|
}
|
||||||
|
pub fn glitch_len(&self) -> usize {
|
||||||
|
self.glitch_buffer.len()
|
||||||
|
}
|
||||||
|
pub fn glass_ptr(&self) -> *const f32 {
|
||||||
|
self.glass_buffer.as_ptr()
|
||||||
|
}
|
||||||
|
pub fn glass_len(&self) -> usize {
|
||||||
|
self.glass_buffer.len()
|
||||||
|
}
|
||||||
|
pub fn eye_ptr(&self) -> *const f32 {
|
||||||
|
self.eye_buffer.as_ptr()
|
||||||
|
}
|
||||||
|
pub fn eye_len(&self) -> usize {
|
||||||
|
self.eye_buffer.len()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user