scales fixes, rubbish remove
This commit is contained in:
@@ -202,3 +202,15 @@
|
|||||||
background: #333;
|
background: #333;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#matrix-canvas {
|
||||||
|
display: block;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: -1; /* Чтобы фон был ЗА контентом */
|
||||||
|
pointer-events: auto; /* Чтобы мышка и свайпы работали */
|
||||||
|
background-color: #0a0a0c; /* Твой темный цвет из шейдера, чтобы не было белых вспышек */
|
||||||
|
}
|
||||||
|
|||||||
@@ -60,8 +60,23 @@ export const NetrunnerMatrix: React.FC<NetrunnerMatrixProps> = ({
|
|||||||
if (!container || hasTransferred.current) return;
|
if (!container || hasTransferred.current) return;
|
||||||
|
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
canvas.className =
|
|
||||||
"absolute inset-0 w-full h-full block transition-opacity duration-1000 opacity-0 z-0";
|
// Используем zIndex: -1, чтобы канвас был ФОНОМ внутри контейнера
|
||||||
|
Object.assign(canvas.style, {
|
||||||
|
position: "absolute",
|
||||||
|
top: "0",
|
||||||
|
left: "0",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
display: "block",
|
||||||
|
zIndex: "-1",
|
||||||
|
backgroundColor: isDarkMode ? "#0a0a0c" : "#fafafc",
|
||||||
|
pointerEvents: "none", // Чтобы клики проходили сквозь канвас на кнопки
|
||||||
|
transition: "opacity 1.5s ease-in-out",
|
||||||
|
opacity: "0",
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.id = "matrix-canvas";
|
||||||
container.insertBefore(canvas, container.firstChild);
|
container.insertBefore(canvas, container.firstChild);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -73,15 +88,19 @@ export const NetrunnerMatrix: React.FC<NetrunnerMatrixProps> = ({
|
|||||||
const isWeakHardware = cores <= 4 || memory <= 4;
|
const isWeakHardware = cores <= 4 || memory <= 4;
|
||||||
const isLowQuality = isMobile || isWeakHardware;
|
const isLowQuality = isMobile || isWeakHardware;
|
||||||
|
|
||||||
// На слабом ПК и телефонах рендерим 75% от разрешения экрана
|
const isMobileDevice = window.innerWidth <= 768;
|
||||||
const pixelScale = 1;
|
|
||||||
|
const currentDPR = window.devicePixelRatio || 1;
|
||||||
|
const optimalScale = isMobileDevice
|
||||||
|
? Math.min(currentDPR, 1.25)
|
||||||
|
: currentDPR;
|
||||||
// ----------------------------------------------
|
// ----------------------------------------------
|
||||||
|
|
||||||
const logicalWidth = container.clientWidth;
|
const logicalWidth = container.clientWidth;
|
||||||
const logicalHeight = container.clientHeight;
|
const logicalHeight = container.clientHeight;
|
||||||
|
|
||||||
canvas.width = logicalWidth * pixelScale;
|
canvas.width = logicalWidth * optimalScale;
|
||||||
canvas.height = logicalHeight * pixelScale;
|
canvas.height = logicalHeight * optimalScale;
|
||||||
|
|
||||||
const worker = new Worker(
|
const worker = new Worker(
|
||||||
`/wasm-matrix/matrix_worker.js?v=${ENGINE_VERSION}`,
|
`/wasm-matrix/matrix_worker.js?v=${ENGINE_VERSION}`,
|
||||||
@@ -100,7 +119,7 @@ export const NetrunnerMatrix: React.FC<NetrunnerMatrixProps> = ({
|
|||||||
canvas: offscreen,
|
canvas: offscreen,
|
||||||
logicalWidth,
|
logicalWidth,
|
||||||
logicalHeight,
|
logicalHeight,
|
||||||
pixelScale,
|
pixelScale: optimalScale,
|
||||||
eyeY: getEyeY(),
|
eyeY: getEyeY(),
|
||||||
eyeScale,
|
eyeScale,
|
||||||
isMobile,
|
isMobile,
|
||||||
@@ -115,26 +134,32 @@ export const NetrunnerMatrix: React.FC<NetrunnerMatrixProps> = ({
|
|||||||
|
|
||||||
worker.onmessage = (e) => {
|
worker.onmessage = (e) => {
|
||||||
if (e.data.type === "READY") {
|
if (e.data.type === "READY") {
|
||||||
canvas.classList.remove("opacity-0");
|
// Небольшой таймаут гарантирует, что браузер применит transition
|
||||||
canvas.classList.add("opacity-100");
|
requestAnimationFrame(() => {
|
||||||
|
canvas.style.opacity = "1";
|
||||||
|
setIsLoaded(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserver((entries) => {
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
for (let entry of entries) {
|
for (let entry of entries) {
|
||||||
const { width, height } = entry.contentRect;
|
const { width: newWidth, height: newHeight } = entry.contentRect;
|
||||||
if (width === 0 || height === 0) continue;
|
if (newWidth === 0 || newHeight === 0) continue;
|
||||||
|
|
||||||
const isMob = width <= 768;
|
const isMob = newWidth <= 768;
|
||||||
const lowQual = isMob || isWeakHardware;
|
const lowQual = isMob || isWeakHardware;
|
||||||
const pScale = 1;
|
|
||||||
|
// ВАЖНО: Используем тот же коэффициент, что и при INIT
|
||||||
|
const currentDPR = window.devicePixelRatio || 1;
|
||||||
|
const pScale = isMob ? Math.min(currentDPR, 1.25) : currentDPR;
|
||||||
|
|
||||||
workerRef.current?.postMessage({
|
workerRef.current?.postMessage({
|
||||||
type: "RESIZE",
|
type: "RESIZE",
|
||||||
payload: {
|
payload: {
|
||||||
logicalWidth: width,
|
logicalWidth: newWidth,
|
||||||
logicalHeight: height,
|
logicalHeight: newHeight,
|
||||||
pixelScale: pScale,
|
pixelScale: pScale, // Теперь скейл совпадает с физикой канваса
|
||||||
eyeY: getEyeY(),
|
eyeY: getEyeY(),
|
||||||
eyeScale: isMob ? 0.8 : 1.5,
|
eyeScale: isMob ? 0.8 : 1.5,
|
||||||
isMobile: isMob,
|
isMobile: isMob,
|
||||||
@@ -144,7 +169,6 @@ export const NetrunnerMatrix: React.FC<NetrunnerMatrixProps> = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
resizeObserver.observe(container);
|
resizeObserver.observe(container);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -266,9 +290,10 @@ export const NetrunnerMatrix: React.FC<NetrunnerMatrixProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className="absolute inset-0 overflow-hidden z-0"
|
className="absolute inset-0 overflow-hidden z-5" // Убрали z-0, чтобы не путаться
|
||||||
style={{ backgroundColor: isDarkMode ? "#0a0a0c" : "#fafafc" }}
|
style={{ backgroundColor: isDarkMode ? "#0a0a0c" : "#fafafc" }}
|
||||||
>
|
>
|
||||||
|
{/* Оверлей со скан-линиями. Должен быть ВЫШЕ канваса (z-10) */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 pointer-events-none opacity-[0.15] dark:opacity-[0.25] hidden md:block z-10"
|
className="absolute inset-0 pointer-events-none opacity-[0.15] dark:opacity-[0.25] hidden md:block z-10"
|
||||||
style={{
|
style={{
|
||||||
@@ -276,6 +301,7 @@ export const NetrunnerMatrix: React.FC<NetrunnerMatrixProps> = ({
|
|||||||
backgroundSize: "100% 4px, 3px 100%",
|
backgroundSize: "100% 4px, 3px 100%",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{/* Канвас вставится сюда программно с zIndex -1 */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+27
-138
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
let engine, wasm;
|
let engine, wasm;
|
||||||
let gl;
|
let gl;
|
||||||
let overlayCanvas, overlayCtx;
|
|
||||||
let atlasCanvas, atlasCtx, atlasTexture;
|
let atlasCanvas, atlasCtx, atlasTexture;
|
||||||
let width, height;
|
let width, height;
|
||||||
let fontSize = 16;
|
let fontSize = 16;
|
||||||
@@ -286,7 +285,6 @@ let matrixProgram, overlayProgram;
|
|||||||
let instanceBuffer, quadBuffer, overlayQuadBuffer;
|
let instanceBuffer, quadBuffer, overlayQuadBuffer;
|
||||||
let vaoMatrix, vaoOverlay;
|
let vaoMatrix, vaoOverlay;
|
||||||
let uResLoc, uFontLoc, uAtlasSizeLoc;
|
let uResLoc, uFontLoc, uAtlasSizeLoc;
|
||||||
let overlayTexture;
|
|
||||||
let instanceBufferCapacity = 0;
|
let instanceBufferCapacity = 0;
|
||||||
|
|
||||||
// ДОБАВЛЕННЫЕ ПЕРЕМЕННЫЕ ДЛЯ ГЛИТЧЕЙ И ГЛАЗ
|
// ДОБАВЛЕННЫЕ ПЕРЕМЕННЫЕ ДЛЯ ГЛИТЧЕЙ И ГЛАЗ
|
||||||
@@ -739,101 +737,6 @@ function drawEye(ctx, cx, cy, gazeX, gazeY, dir, isClosed) {
|
|||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawOverlayElements() {
|
|
||||||
overlayCtx.clearRect(0, 0, width, height);
|
|
||||||
let hasContent = false;
|
|
||||||
|
|
||||||
const glLen = engine.glitch_len();
|
|
||||||
if (glLen > 0) {
|
|
||||||
hasContent = true;
|
|
||||||
const glData = new Float32Array(
|
|
||||||
wasm.memory.buffer,
|
|
||||||
engine.glitch_ptr(),
|
|
||||||
glLen,
|
|
||||||
);
|
|
||||||
for (let i = 0; i < glLen; i += 5) {
|
|
||||||
overlayCtx.fillStyle = getGlitchColor(glData[i + 4]);
|
|
||||||
overlayCtx.fillRect(
|
|
||||||
glData[i],
|
|
||||||
glData[i + 1],
|
|
||||||
glData[i + 2],
|
|
||||||
glData[i + 3],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const gsLen = engine.glass_len();
|
|
||||||
if (gsLen > 0) {
|
|
||||||
hasContent = true;
|
|
||||||
const gsData = new Float32Array(
|
|
||||||
wasm.memory.buffer,
|
|
||||||
engine.glass_ptr(),
|
|
||||||
gsLen,
|
|
||||||
);
|
|
||||||
for (let i = 0; i < gsLen; i += 4) {
|
|
||||||
overlayCtx.globalAlpha = gsData[i + 3];
|
|
||||||
overlayCtx.drawImage(
|
|
||||||
atlasCanvas,
|
|
||||||
gsData[i + 2] * fontSize,
|
|
||||||
0,
|
|
||||||
fontSize,
|
|
||||||
fontSize,
|
|
||||||
gsData[i],
|
|
||||||
gsData[i + 1],
|
|
||||||
fontSize,
|
|
||||||
fontSize,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const eyeLen = engine.eye_len();
|
|
||||||
if (eyeLen >= 6) {
|
|
||||||
hasContent = true;
|
|
||||||
interactionTimer = 120;
|
|
||||||
// ОПТИМИЗАЦИЯ ДЛЯ СЛАБЫХ УСТРОЙСТВ
|
|
||||||
// На старом ноуте/Xiaomi обновляем тяжелую текстуру глаз в 2 раза реже
|
|
||||||
if (isLowQuality && frameCounter % 2 !== 0) {
|
|
||||||
// Пропускаем очистку и отрисовку в этом кадре,
|
|
||||||
// WebGL просто выведет старую текстуру из кэша
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
overlayCtx.globalAlpha = 1.0;
|
|
||||||
const eyeData = new Float32Array(
|
|
||||||
wasm.memory.buffer,
|
|
||||||
engine.eye_ptr(),
|
|
||||||
eyeLen,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Исправленный gap: на мобилках глаза ближе друг к другу (60 вместо 85)
|
|
||||||
const gap = (isMobile ? 75.0 : 80.0) * eyeScale;
|
|
||||||
|
|
||||||
// Левый глаз
|
|
||||||
drawEye(
|
|
||||||
overlayCtx,
|
|
||||||
eyeData[0] - gap,
|
|
||||||
eyeData[1],
|
|
||||||
eyeData[2],
|
|
||||||
eyeData[3],
|
|
||||||
eyeData[4],
|
|
||||||
eyeData[5],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Правый глаз
|
|
||||||
drawEye(
|
|
||||||
overlayCtx,
|
|
||||||
eyeData[0] + gap,
|
|
||||||
eyeData[1],
|
|
||||||
eyeData[2],
|
|
||||||
eyeData[3],
|
|
||||||
eyeData[4],
|
|
||||||
eyeData[5],
|
|
||||||
);
|
|
||||||
} else if (interactionTimer > 0) {
|
|
||||||
interactionTimer--;
|
|
||||||
}
|
|
||||||
return hasContent;
|
|
||||||
}
|
|
||||||
function render(currentTime) {
|
function render(currentTime) {
|
||||||
requestAnimationFrame(render);
|
requestAnimationFrame(render);
|
||||||
if (!engine || !wasm || !gl) return;
|
if (!engine || !wasm || !gl) return;
|
||||||
@@ -1047,10 +950,15 @@ self.onmessage = async (e) => {
|
|||||||
gl = payload.canvas.getContext("webgl2", {
|
gl = payload.canvas.getContext("webgl2", {
|
||||||
alpha: false,
|
alpha: false,
|
||||||
antialias: false,
|
antialias: false,
|
||||||
|
powerPreference: "high-performance",
|
||||||
});
|
});
|
||||||
|
|
||||||
width = payload.logicalWidth;
|
width = payload.logicalWidth;
|
||||||
height = payload.logicalHeight;
|
height = payload.logicalHeight;
|
||||||
pixelScale = payload.pixelScale;
|
pixelScale = payload.pixelScale;
|
||||||
|
maxPixelScale = pixelScale; // Сохраняем максимум для авто-скейлера
|
||||||
|
currentPixelScale = pixelScale;
|
||||||
|
|
||||||
isMobile = payload.isMobile;
|
isMobile = payload.isMobile;
|
||||||
isLowQuality = payload.isLowQuality;
|
isLowQuality = payload.isLowQuality;
|
||||||
eyeScale = isMobile ? 1.2 : 1.5;
|
eyeScale = isMobile ? 1.2 : 1.5;
|
||||||
@@ -1058,22 +966,17 @@ self.onmessage = async (e) => {
|
|||||||
isDarkMode = payload.isDarkMode;
|
isDarkMode = payload.isDarkMode;
|
||||||
fontSize = payload.fontSize || 16;
|
fontSize = payload.fontSize || 16;
|
||||||
|
|
||||||
overlayCanvas = new OffscreenCanvas(
|
// Инициализируем шейдеры и буферы
|
||||||
width * pixelScale,
|
|
||||||
height * pixelScale,
|
|
||||||
);
|
|
||||||
overlayCtx = overlayCanvas.getContext("2d", { alpha: true });
|
|
||||||
overlayCtx.scale(pixelScale, pixelScale);
|
|
||||||
|
|
||||||
initWebGL();
|
initWebGL();
|
||||||
|
|
||||||
|
// Настройка главной текстуры под текущее разрешение
|
||||||
gl.bindTexture(gl.TEXTURE_2D, postTexture);
|
gl.bindTexture(gl.TEXTURE_2D, postTexture);
|
||||||
gl.texImage2D(
|
gl.texImage2D(
|
||||||
gl.TEXTURE_2D,
|
gl.TEXTURE_2D,
|
||||||
0,
|
0,
|
||||||
gl.RGBA,
|
gl.RGBA,
|
||||||
width * pixelScale,
|
width * currentPixelScale,
|
||||||
height * pixelScale,
|
height * currentPixelScale,
|
||||||
0,
|
0,
|
||||||
gl.RGBA,
|
gl.RGBA,
|
||||||
gl.UNSIGNED_BYTE,
|
gl.UNSIGNED_BYTE,
|
||||||
@@ -1088,7 +991,6 @@ self.onmessage = async (e) => {
|
|||||||
postTexture,
|
postTexture,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
||||||
|
|
||||||
buildAtlas();
|
buildAtlas();
|
||||||
|
|
||||||
@@ -1096,72 +998,59 @@ self.onmessage = async (e) => {
|
|||||||
const module = await import("./matrix_engine.js?v=" + version);
|
const module = await import("./matrix_engine.js?v=" + version);
|
||||||
const init = module.default;
|
const init = module.default;
|
||||||
MatrixEngineClass = module.MatrixEngine;
|
MatrixEngineClass = module.MatrixEngine;
|
||||||
|
|
||||||
// Передаем объект для инициализации (исправление warning)
|
|
||||||
wasm = await init({
|
wasm = await init({
|
||||||
module_or_path: "./matrix_engine_bg.wasm?v=" + version,
|
module_or_path: "./matrix_engine_bg.wasm?v=" + version,
|
||||||
});
|
});
|
||||||
|
|
||||||
engine = new MatrixEngineClass(width, height, fontSize);
|
engine = new MatrixEngineClass(width, height, fontSize);
|
||||||
|
|
||||||
if (typeof engine.set_mobile === "function") engine.set_mobile(isMobile);
|
if (typeof engine.set_mobile === "function") engine.set_mobile(isMobile);
|
||||||
if (typeof engine.set_vpn_status === "function")
|
if (typeof engine.set_vpn_status === "function")
|
||||||
engine.set_vpn_status(isVpnOn);
|
engine.set_vpn_status(isVpnOn);
|
||||||
if (payload.eyeY && typeof engine.set_eye_anchor === "function")
|
if (payload.eyeY && typeof engine.set_eye_anchor === "function")
|
||||||
engine.set_eye_anchor(payload.eyeY);
|
engine.set_eye_anchor(payload.eyeY);
|
||||||
|
|
||||||
// Запускаем главный цикл
|
|
||||||
requestAnimationFrame(render);
|
requestAnimationFrame(render);
|
||||||
self.postMessage({ type: "READY" });
|
self.postMessage({ type: "READY" });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Critical failure loading WASM core in Worker:", err);
|
console.error("Critical failure loading WASM core:", err);
|
||||||
}
|
}
|
||||||
} else if (type === "RESIZE") {
|
} else if (type === "RESIZE") {
|
||||||
if (!engine) return;
|
if (!engine) return;
|
||||||
|
|
||||||
width = payload.logicalWidth;
|
width = payload.logicalWidth;
|
||||||
height = payload.logicalHeight;
|
height = payload.logicalHeight;
|
||||||
pixelScale = payload.pixelScale;
|
pixelScale = payload.pixelScale; // Новое базовое значение DPR
|
||||||
|
maxPixelScale = pixelScale;
|
||||||
|
currentPixelScale = pixelScale;
|
||||||
fontSize = payload.fontSize || 16;
|
fontSize = payload.fontSize || 16;
|
||||||
isLowQuality = payload.isLowQuality;
|
isLowQuality = payload.isLowQuality;
|
||||||
|
isMobile = payload.isMobile;
|
||||||
|
|
||||||
gl.canvas.width = width * pixelScale;
|
// 1. Обновляем физический размер холста
|
||||||
gl.canvas.height = height * pixelScale;
|
gl.canvas.width = width * currentPixelScale;
|
||||||
|
gl.canvas.height = height * currentPixelScale;
|
||||||
overlayCanvas.width = width * pixelScale;
|
|
||||||
overlayCanvas.height = height * pixelScale;
|
|
||||||
overlayCtx.scale(pixelScale, pixelScale);
|
|
||||||
|
|
||||||
if (typeof engine.set_mobile === "function")
|
|
||||||
engine.set_mobile(payload.isMobile);
|
|
||||||
|
|
||||||
eyeScale = isMobile ? 1.2 : 1.5;
|
|
||||||
engine.resize(width, height);
|
|
||||||
|
|
||||||
|
// 2. Обновляем размер текстуры постобработки
|
||||||
gl.bindTexture(gl.TEXTURE_2D, postTexture);
|
gl.bindTexture(gl.TEXTURE_2D, postTexture);
|
||||||
gl.texImage2D(
|
gl.texImage2D(
|
||||||
gl.TEXTURE_2D,
|
gl.TEXTURE_2D,
|
||||||
0,
|
0,
|
||||||
gl.RGBA,
|
gl.RGBA,
|
||||||
width * pixelScale,
|
gl.canvas.width,
|
||||||
height * pixelScale,
|
gl.canvas.height,
|
||||||
0,
|
0,
|
||||||
gl.RGBA,
|
gl.RGBA,
|
||||||
gl.UNSIGNED_BYTE,
|
gl.UNSIGNED_BYTE,
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, postFramebuffer);
|
// 3. Сбрасываем вьюпорт WebGL
|
||||||
gl.framebufferTexture2D(
|
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
||||||
gl.FRAMEBUFFER,
|
|
||||||
gl.COLOR_ATTACHMENT0,
|
eyeScale = isMobile ? 1.2 : 1.5;
|
||||||
gl.TEXTURE_2D,
|
engine.resize(width, height);
|
||||||
postTexture,
|
if (typeof engine.set_mobile === "function") engine.set_mobile(isMobile);
|
||||||
0,
|
|
||||||
);
|
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
||||||
|
|
||||||
if (payload.eyeY && typeof engine.set_eye_anchor === "function")
|
|
||||||
engine.set_eye_anchor(payload.eyeY);
|
|
||||||
buildAtlas();
|
buildAtlas();
|
||||||
} else if (type === "THEME") {
|
} else if (type === "THEME") {
|
||||||
// Сохраняем статус безусловно!
|
// Сохраняем статус безусловно!
|
||||||
|
|||||||
@@ -230,19 +230,25 @@ impl MatrixEngine {
|
|||||||
fn init_drops(&mut self) {
|
fn init_drops(&mut self) {
|
||||||
self.drops.clear();
|
self.drops.clear();
|
||||||
|
|
||||||
let layers = [
|
// Используем Vec вместо статического массива, чтобы динамически добавлять слои
|
||||||
(0, 0.4, 0.005, 0.02), // Far
|
let mut layers = vec![
|
||||||
(1, 0.7, 0.02, 0.06), // Mid
|
(1, 0.7, 0.02, 0.06), // Mid (Средний слой)
|
||||||
(2, 1.0, 0.05, 0.15), // Near
|
(2, 1.0, 0.05, 0.15), // Near (Ближний слой)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Добавляем дальний размытый слой ТОЛЬКО для ПК
|
||||||
|
if !self.is_mobile {
|
||||||
|
layers.insert(0, (0, 0.4, 0.005, 0.02)); // Far (Дальний слой)
|
||||||
|
}
|
||||||
|
|
||||||
let base_spacing = self.font_size as f32;
|
let base_spacing = self.font_size as f32;
|
||||||
|
|
||||||
for (layer, scale, min_spd, max_spd) in layers.iter() {
|
for (layer, scale, min_spd, max_spd) in layers.iter() {
|
||||||
let spacing = base_spacing * scale;
|
let spacing = base_spacing * scale;
|
||||||
|
|
||||||
|
// Формула колонок (с твоей предыдущей оптимизацией для мобилок)
|
||||||
let columns = if self.is_mobile {
|
let columns = if self.is_mobile {
|
||||||
(self.width as f32 / spacing).ceil() as u32 + 5 // Меньше запасных колонок
|
(self.width as f32 / spacing).ceil() as u32 + 5
|
||||||
} else {
|
} else {
|
||||||
(self.width as f32 / spacing).ceil() as u32 + 20
|
(self.width as f32 / spacing).ceil() as u32 + 20
|
||||||
};
|
};
|
||||||
@@ -264,7 +270,6 @@ impl MatrixEngine {
|
|||||||
// Stride 5: [x, y, char_idx, atlas_row, scale]
|
// Stride 5: [x, y, char_idx, atlas_row, scale]
|
||||||
self.instance_buffer.resize(self.drops.len() * 5, 0.0);
|
self.instance_buffer.resize(self.drops.len() * 5, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(&mut self, width: u32, height: u32) {
|
pub fn resize(&mut self, width: u32, height: u32) {
|
||||||
self.width = width;
|
self.width = width;
|
||||||
self.height = height;
|
self.height = height;
|
||||||
@@ -533,7 +538,7 @@ impl MatrixEngine {
|
|||||||
|
|
||||||
let dist = (dx * dx + dy * dy).sqrt();
|
let dist = (dx * dx + dy * dy).sqrt();
|
||||||
if (dist - sw.radius).abs() < 40.0 {
|
if (dist - sw.radius).abs() < 40.0 {
|
||||||
let base_sw_force = if self.is_mobile { 6.0 } else { 18.0 }; // На мобилке импульс в 3 раза слабее
|
let base_sw_force = if self.is_mobile { 3.0 } else { 18.0 }; // На мобилке импульс в 3 раза слабее
|
||||||
let force = (sw.life / sw.max_life) * base_sw_force * parallax_factor;
|
let force = (sw.life / sw.max_life) * base_sw_force * parallax_factor;
|
||||||
drop.vel_x += (dx / dist.max(1.0_f32)) * force;
|
drop.vel_x += (dx / dist.max(1.0_f32)) * force;
|
||||||
force_white = true;
|
force_white = true;
|
||||||
@@ -551,12 +556,16 @@ impl MatrixEngine {
|
|||||||
let dx = current_x - ix;
|
let dx = current_x - ix;
|
||||||
let dy = current_y - iy;
|
let dy = current_y - iy;
|
||||||
let dist_sq = dx * dx + dy * dy;
|
let dist_sq = dx * dx + dy * dy;
|
||||||
let rad = if self.is_mobile { 100.0 } else { 140.0 };
|
let rad = if self.is_mobile { 70.0 } else { 140.0 }; // Радиус захвата в 2 раза меньше
|
||||||
|
|
||||||
if dist_sq < rad * rad {
|
if dist_sq < rad * rad {
|
||||||
let dist = dist_sq.sqrt().max(1.0_f32);
|
let dist = dist_sq.sqrt().max(1.0_f32);
|
||||||
let force = (rad - dist) / rad;
|
let force = (rad - dist) / rad;
|
||||||
drop.vel_x += (dx / dist) * force * 15.0 * parallax_factor;
|
|
||||||
|
// ДОБАВЛЯЕМ ДИНАМИЧЕСКУЮ СИЛУ (на телефоне в 4 раза слабее)
|
||||||
|
let interaction_force = if self.is_mobile { 3.5 } else { 15.0 };
|
||||||
|
|
||||||
|
drop.vel_x += (dx / dist) * force * interaction_force * parallax_factor;
|
||||||
drop.y -= drop.speed * force * 0.6;
|
drop.y -= drop.speed * force * 0.6;
|
||||||
is_scrambled = true;
|
is_scrambled = true;
|
||||||
if dist < rad * 0.5 {
|
if dist < rad * 0.5 {
|
||||||
|
|||||||
Reference in New Issue
Block a user