Комментарии 27
очень круто! поставил бы плюс если б мог.
такой вопрос - а этот принцип используется в криптографии для быстрого поиска простых чисел или там есть еше более быстрые, но не очевидные лайфхаки?
делать поиск чисел только по осям возможных спиралей это в разы оптимальнее чем перебирать все числа я считаю. причем формула расчета спиралей позволяет быстро локализовать квадрат поиска если нам нужно например тридцатизначное простое число
как минимум для микроконтроллеров я вижу возможность для быстрой генерации безопасных одноразовых токенов, без оверхеда по расчетам и специальных крипточипров (простая алгебра и геометрия сейчас зачастую вшита уже в камни для оптимизации операций)
поиск чисел только по осям возможных спиралей
Это просто решето Эратосфена, в котором мы использовали только 2 и 11, или 2, 5 и 71. Ничего оптимального в этом нет. Если уж вы зачем-то хотите ограничиться двумя простыми числами, то оптимальнее всего использовать классы вычетов по 6, а не по 44 (так вы оставите для поиска 1/3 часть чисел, а не 5/11). А если тремя, то классы вычетов по 30, а не по 710 (так вы оставите 4/15 чисел, а не 28/71). Но гораздо проще не мучиться и использовать полноценное решето Эратосфена.
Поиск по осям это просто хардкод решета Эратосфена с заранее выкинутыми делителями. В реальной криптографии нужны числа в сотни бит, там этот геометрический подход сожрет всю память на первых же итерациях
Если числа располагать в виде спирали, то почему удивляться что они расположены по спирали?
Любая достаточно развитая технология неотличима от магии. Для кого-то даже школьная математика уже является достаточно развитой для этого :)
Я тоже так сначала подумал, но интересность в том, что числа располагаются на одной спирали, а визуально выстраиваются в другие. Сначала потому, что 6 радиан примерно равны одному полному обороту, а потом потому что 44 радиана примерно равны 7 полным оборотам.
Всё образует спираль, главное правильно нарисовать...
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Universe Rebirth — чёрная дыра и восстановление Вселенной</title>
<style>
:root {
--bg: #030712;
--panel: rgba(15, 23, 42, .78);
--text: #eaf0ff;
--muted: #9ba8c6;
--line: rgba(255,255,255,.12);
--blue: #7aa2ff;
--cyan: #5eead4;
--violet: #a78bfa;
--yellow: #fde68a;
--red: #fb7185;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
overflow: hidden;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
color: var(--text);
background: var(--bg);
}
canvas {
position: fixed;
inset: 0;
width: 100vw;
height: 100vh;
display: block;
background: radial-gradient(circle at center, #0b1020 0%, #030712 56%, #01030a 100%);
}
.panel {
position: fixed;
left: 20px;
top: 20px;
width: min(430px, calc(100vw - 40px));
max-height: calc(100vh - 40px);
overflow: auto;
padding: 18px;
border: 1px solid var(--line);
border-radius: 24px;
background: var(--panel);
backdrop-filter: blur(18px);
box-shadow: 0 28px 80px rgba(0,0,0,.42);
}
h1 {
margin: 0 0 8px;
font-size: 28px;
line-height: 1;
letter-spacing: -0.055em;
}
.lead {
margin: 0 0 16px;
color: var(--muted);
font-size: 13px;
line-height: 1.5;
}
.formula {
margin: 0 0 16px;
padding: 12px;
border: 1px solid rgba(255,255,255,.1);
border-radius: 16px;
background: rgba(0,0,0,.24);
color: #dbeafe;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 12px;
line-height: 1.55;
white-space: pre-wrap;
}
.grid { display: grid; gap: 12px; }
.row {
display: grid;
grid-template-columns: 1fr auto;
gap: 12px;
align-items: center;
}
label {
display: block;
color: var(--muted);
font-size: 12px;
margin-bottom: 6px;
}
input[type="range"] {
width: 100%;
accent-color: #7aa2ff;
}
select, button {
width: 100%;
border: 1px solid var(--line);
border-radius: 14px;
background: rgba(15,23,42,.92);
color: var(--text);
padding: 10px 12px;
font-size: 14px;
outline: none;
}
.value {
min-width: 66px;
text-align: right;
color: #dbeafe;
font-size: 12px;
font-variant-numeric: tabular-nums;
}
button {
border: 0;
cursor: pointer;
font-weight: 800;
background: linear-gradient(135deg, #4f7cff, #8b5cf6 52%, #14b8a6);
box-shadow: 0 18px 40px rgba(79,124,255,.22);
transition: transform .15s ease, filter .15s ease;
margin-top: 2px;
}
button:hover { transform: translateY(-1px); filter: brightness(1.08); }
.two {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-top: 14px;
}
.stat {
padding: 10px;
border: 1px solid rgba(255,255,255,.09);
border-radius: 16px;
background: rgba(255,255,255,.045);
}
.stat .k {
color: var(--muted);
font-size: 11px;
margin-bottom: 2px;
}
.stat .v {
font-size: 17px;
font-weight: 800;
letter-spacing: -0.03em;
}
.hint {
margin-top: 14px;
color: var(--muted);
font-size: 12px;
line-height: 1.45;
}
.pill {
display: inline-flex;
align-items: center;
gap: 6px;
border: 1px solid rgba(255,255,255,.1);
border-radius: 999px;
padding: 4px 8px;
background: rgba(255,255,255,.05);
color: #dbeafe;
font-size: 11px;
margin: 0 4px 4px 0;
}
.rightPanel {
position: fixed;
right: 20px;
bottom: 20px;
width: min(390px, calc(100vw - 40px));
padding: 14px;
border: 1px solid var(--line);
border-radius: 22px;
background: var(--panel);
backdrop-filter: blur(18px);
box-shadow: 0 28px 80px rgba(0,0,0,.42);
}
.rightPanel h2 {
margin: 0 0 8px;
font-size: 16px;
letter-spacing: -0.03em;
}
.rightPanel p {
margin: 0;
color: var(--muted);
font-size: 12px;
line-height: 1.45;
}
.phaseName {
color: #fde68a;
font-weight: 900;
}
@media (max-width: 860px) {
body { overflow: auto; }
canvas { position: fixed; }
.panel, .rightPanel {
position: relative;
left: auto;
right: auto;
top: auto;
bottom: auto;
width: calc(100vw - 24px);
margin: 12px;
}
.rightPanel { margin-top: 0; }
}
</style>
</head>
<body>
<canvas id="universe"></canvas>
<section class="panel">
<h1>Universe Rebirth</h1>
<p class="lead">
Художественная математическая модель: Вселенная расширяется, схлопывается в чёрную дыру,
почти исчезает за горизонтом событий, а затем в последний момент восстанавливается.
</p>
<div class="formula">base: r = expansion · sqrt(i) + wave · sin(i·k + t)
collapse: r = mix(baseR, blackHoleR, collapseFactor)
black: θ = θ + vortex · collapseFactor²
rebirth: r = baseR · rebirthPulse + shockWave</div>
<div class="grid">
<div>
<label for="preset">Сценарий</label>
<select id="preset">
<option value="rebirth">Чёрная дыра → восстановление</option>
<option value="slow">Медленное схлопывание</option>
<option value="violent">Жёсткий гравитационный шторм</option>
<option value="calm">Мягкая космическая пульсация</option>
</select>
</div>
<div>
<label for="particles">Частицы</label>
<div class="row">
<input id="particles" type="range" min="2000" max="70000" step="1000" value="26000">
<div id="particlesVal" class="value">26000</div>
</div>
</div>
<div>
<label for="cycle">Длительность цикла, секунд</label>
<div class="row">
<input id="cycle" type="range" min="8" max="60" step="1" value="24">
<div id="cycleVal" class="value">24</div>
</div>
</div>
<div>
<label for="expansion">Базовое расширение</label>
<div class="row">
<input id="expansion" type="range" min="0.8" max="8" step="0.1" value="3.4">
<div id="expansionVal" class="value">3.4</div>
</div>
</div>
<div>
<label for="blackHole">Сила чёрной дыры</label>
<div class="row">
<input id="blackHole" type="range" min="0" max="10" step="0.1" value="7.4">
<div id="blackHoleVal" class="value">7.4</div>
</div>
</div>
<div>
<label for="vortex">Закручивание</label>
<div class="row">
<input id="vortex" type="range" min="0" max="18" step="0.1" value="9.8">
<div id="vortexVal" class="value">9.8</div>
</div>
</div>
<div>
<label for="wave">Гравитационные волны</label>
<div class="row">
<input id="wave" type="range" min="0" max="100" step="1" value="24">
<div id="waveVal" class="value">24</div>
</div>
</div>
<div>
<label for="rebirthPower">Сила восстановления</label>
<div class="row">
<input id="rebirthPower" type="range" min="0" max="10" step="0.1" value="6.5">
<div id="rebirthPowerVal" class="value">6.5</div>
</div>
</div>
<div class="two">
<button id="randomBtn">Случайная Вселенная</button>
<button id="pauseBtn">Пауза</button>
</div>
</div>
<div class="stats">
<div class="stat"><div class="k">Фаза</div><div id="phase" class="v phaseName">—</div></div>
<div class="stat"><div class="k">Схлопывание</div><div id="collapseStat" class="v">0%</div></div>
<div class="stat"><div class="k">Горизонт</div><div id="horizonStat" class="v">—</div></div>
<div class="stat"><div class="k">FPS</div><div id="fps" class="v">—</div></div>
</div>
<p class="hint">
<span class="pill">Колесо мыши — масштаб</span>
<span class="pill">Движение мыши — параллакс</span>
<span class="pill">Цикл сам повторяется</span>
</p>
</section>
<aside class="rightPanel">
<h2>Как работает эффект</h2>
<p>
На фазе схлопывания координаты частиц смешиваются с координатами около центра. Чем ближе конец цикла,
тем сильнее закручивание и темнее ядро. В момент почти полного поглощения включается «отскок»:
радиус резко восстанавливается, появляется ударная волна, и структура Вселенной собирается обратно.
</p>
</aside>
<script>
const canvas = document.getElementById('universe');
const ctx = canvas.getContext('2d', { alpha: false });
const els = {
preset: document.getElementById('preset'),
particles: document.getElementById('particles'),
cycle: document.getElementById('cycle'),
expansion: document.getElementById('expansion'),
blackHole: document.getElementById('blackHole'),
vortex: document.getElementById('vortex'),
wave: document.getElementById('wave'),
rebirthPower: document.getElementById('rebirthPower'),
randomBtn: document.getElementById('randomBtn'),
pauseBtn: document.getElementById('pauseBtn'),
phase: document.getElementById('phase'),
collapseStat: document.getElementById('collapseStat'),
horizonStat: document.getElementById('horizonStat'),
fps: document.getElementById('fps')
};
const valueEls = {
particles: document.getElementById('particlesVal'),
cycle: document.getElementById('cycleVal'),
expansion: document.getElementById('expansionVal'),
blackHole: document.getElementById('blackHoleVal'),
vortex: document.getElementById('vortexVal'),
wave: document.getElementById('waveVal'),
rebirthPower: document.getElementById('rebirthPowerVal')
};
const GOLDEN_ANGLE = Math.PI * (3 - Math.sqrt(5));
let W = 0;
let H = 0;
let DPR = 1;
let time = 0;
let paused = false;
let zoom = 1;
let mouseX = 0;
let mouseY = 0;
let lastTime = performance.now();
let fpsSmooth = 60;
const presets = {
rebirth: {
particles: 26000,
cycle: 24,
expansion: 3.4,
blackHole: 7.4,
vortex: 9.8,
wave: 24,
rebirthPower: 6.5
},
slow: {
particles: 30000,
cycle: 42,
expansion: 3.0,
blackHole: 5.8,
vortex: 6.2,
wave: 18,
rebirthPower: 4.5
},
violent: {
particles: 46000,
cycle: 18,
expansion: 4.9,
blackHole: 9.6,
vortex: 16.5,
wave: 82,
rebirthPower: 9.2
},
calm: {
particles: 18000,
cycle: 30,
expansion: 2.6,
blackHole: 4.8,
vortex: 4.5,
wave: 14,
rebirthPower: 3.8
}
};
function resize() {
DPR = Math.max(1, Math.min(window.devicePixelRatio || 1, 2));
W = window.innerWidth;
H = window.innerHeight;
canvas.width = Math.floor(W * DPR);
canvas.height = Math.floor(H * DPR);
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
}
function num(id) {
return Number(els[id].value);
}
function clamp01(x) {
return Math.min(1, Math.max(0, x));
}
function smoothstep(edge0, edge1, x) {
const v = clamp01((x - edge0) / Math.max(1e-9, edge1 - edge0));
return v * v * (3 - 2 * v);
}
function mix(a, b, t) {
return a + (b - a) * t;
}
function syncLabels() {
for (const key of Object.keys(valueEls)) {
valueEls[key].textContent = els[key].value;
}
}
function applyPreset(name) {
const p = presets[name];
for (const key of Object.keys(p)) els[key].value = p[key];
syncLabels();
}
function randomUniverse() {
els.particles.value = Math.round((8000 + Math.random() * 60000) / 1000) * 1000;
els.cycle.value = Math.round(10 + Math.random() * 42);
els.expansion.value = (1.2 + Math.random() * 6.5).toFixed(1);
els.blackHole.value = (3 + Math.random() * 7).toFixed(1);
els.vortex.value = (2 + Math.random() * 16).toFixed(1);
els.wave.value = Math.round(Math.random() * 100);
els.rebirthPower.value = (2 + Math.random() * 8).toFixed(1);
syncLabels();
}
function getPhase(p) {
// p — прогресс внутри цикла 0..1
const expansionPhase = 1 - smoothstep(0.10, 0.38, p);
const collapsePhase = smoothstep(0.28, 0.72, p) * (1 - smoothstep(0.82, 0.96, p));
const nearSingularity = smoothstep(0.66, 0.82, p) * (1 - smoothstep(0.84, 0.91, p));
const rebirth = smoothstep(0.78, 0.91, p) * (1 - smoothstep(0.91, 1.0, p));
const restoration = smoothstep(0.86, 1.0, p);
let name = 'расширение';
if (p > 0.28 && p <= 0.66) name = 'схлопывание';
if (p > 0.66 && p <= 0.82) name = 'горизонт';
if (p > 0.82 && p <= 0.91) name = 'отскок';
if (p > 0.91) name = 'восстановление';
return { expansionPhase, collapsePhase, nearSingularity, rebirth, restoration, name };
}
function colorFor(index, zNorm, radiusNorm, phase, p) {
let hue = (210 + 90 * Math.sin(index * 0.0008 + time * 0.5) + 80 * radiusNorm) % 360;
if (phase.nearSingularity > 0.1) {
hue = mix(hue, 18, phase.nearSingularity);
}
if (phase.rebirth > 0.1) {
hue = mix(hue, 190 + 80 * Math.sin(index), phase.rebirth);
}
const sat = 70 + 25 * zNorm;
const light = 48 + 30 * zNorm + phase.rebirth * 18 - phase.collapsePhase * 10;
return `hsl(${hue} ${sat}% ${light}%)`;
}
function drawBackground(phase) {
const cx = W / 2;
const cy = H / 2;
const danger = phase.nearSingularity;
const rebirth = phase.rebirth;
const grad = ctx.createRadialGradient(cx, cy, 0, cx, cy, Math.max(W, H) * 0.76);
grad.addColorStop(0, `rgba(${Math.round(mix(11, 255, rebirth))}, ${Math.round(mix(16, 255, rebirth))}, ${Math.round(mix(32, 255, rebirth))}, 1)`);
grad.addColorStop(0.08, danger > 0.2 ? '#050008' : '#0b1020');
grad.addColorStop(0.48, danger > 0.2 ? '#09020a' : '#030712');
grad.addColorStop(1, '#01030a');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, W, H);
// Сетка пространства-времени, которая искажается к центру.
ctx.save();
ctx.globalAlpha = 0.10 + rebirth * 0.28;
ctx.strokeStyle = rebirth > 0.2 ? 'rgba(94,234,212,.38)' : 'rgba(122,162,255,.16)';
ctx.lineWidth = 1;
const maxR = Math.min(W, H) * 0.50 * zoom;
const wobble = Math.sin(time * 4) * 8 * phase.collapsePhase;
for (let i = 1; i <= 9; i++) {
ctx.beginPath();
ctx.arc(cx + mouseX * wobble, cy + mouseY * wobble, maxR * i / 9, 0, Math.PI * 2);
ctx.stroke();
}
ctx.restore();
}
function drawBlackHole(cx, cy, phase, baseScale) {
const collapse = phase.collapsePhase;
const danger = phase.nearSingularity;
const rebirth = phase.rebirth;
const blackHole = num('blackHole');
const horizon = (38 + blackHole * 9 + danger * 80) * baseScale;
const diskR = horizon * (2.5 + collapse * 1.8);
const angle = time * (1.5 + num('vortex') * 0.08);
els.horizonStat.textContent = Math.round(horizon) + ' px';
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(angle);
ctx.globalCompositeOperation = 'lighter';
// Аккреционный диск.
for (let i = 0; i < 7; i++) {
ctx.globalAlpha = (0.05 + collapse * 0.09 + danger * 0.06) * (1 - i * 0.08);
ctx.strokeStyle = i % 2 ? 'rgba(251,113,133,.75)' : 'rgba(250,204,21,.72)';
ctx.lineWidth = Math.max(1, 2.5 * baseScale);
ctx.beginPath();
ctx.ellipse(0, 0, diskR * (1 + i * 0.13), diskR * (0.23 + i * 0.018), 0, 0, Math.PI * 2);
ctx.stroke();
}
ctx.restore();
// Горизонт событий — абсолютная чёрная область.
ctx.save();
ctx.globalCompositeOperation = 'source-over';
const shadow = ctx.createRadialGradient(cx, cy, 0, cx, cy, horizon * 2.4);
shadow.addColorStop(0, 'rgba(0,0,0,1)');
shadow.addColorStop(0.55, 'rgba(0,0,0,.98)');
shadow.addColorStop(0.72, 'rgba(15,3,18,.8)');
shadow.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = shadow;
ctx.beginPath();
ctx.arc(cx, cy, horizon * 2.4, 0, Math.PI * 2);
ctx.fill();
// Светящийся край горизонта.
ctx.globalCompositeOperation = 'lighter';
ctx.globalAlpha = 0.35 + danger * 0.45 - rebirth * 0.2;
ctx.strokeStyle = 'rgba(255,220,160,.8)';
ctx.lineWidth = 2.2 * baseScale;
ctx.beginPath();
ctx.arc(cx, cy, horizon * 1.02, 0, Math.PI * 2);
ctx.stroke();
ctx.restore();
}
function drawRebirthShock(cx, cy, phase, baseScale) {
if (phase.rebirth <= 0.01 && phase.restoration <= 0.01) return;
const power = num('rebirthPower');
const pulse = phase.rebirth;
const restore = phase.restoration;
const shockR = (80 + pulse * Math.min(W, H) * 0.62 + restore * Math.min(W, H) * 0.18) * baseScale;
ctx.save();
ctx.globalCompositeOperation = 'lighter';
const grad = ctx.createRadialGradient(cx, cy, Math.max(1, shockR * 0.72), cx, cy, shockR * 1.08);
grad.addColorStop(0, 'rgba(94,234,212,0)');
grad.addColorStop(0.5, `rgba(125,211,252,${0.12 + pulse * 0.34})`);
grad.addColorStop(0.72, `rgba(255,255,255,${0.10 + pulse * 0.55})`);
grad.addColorStop(1, 'rgba(167,139,250,0)');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(cx, cy, shockR * (1 + power * 0.015), 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 0.35 * pulse;
ctx.strokeStyle = 'rgba(255,255,255,.9)';
ctx.lineWidth = 2.5 * baseScale;
ctx.beginPath();
ctx.arc(cx, cy, shockR, 0, Math.PI * 2);
ctx.stroke();
ctx.restore();
}
function drawUniverse(dt) {
const particles = num('particles');
const cycle = num('cycle');
const expansion = num('expansion');
const blackHole = num('blackHole');
const vortex = num('vortex');
const wave = num('wave');
const rebirthPower = num('rebirthPower');
if (!paused) time += dt * 0.001;
const p = (time % cycle) / cycle;
const phase = getPhase(p);
els.phase.textContent = phase.name;
els.collapseStat.textContent = Math.round(phase.collapsePhase * 100) + '%';
drawBackground(phase);
const cx = W / 2 + mouseX * 26;
const cy = H / 2 + mouseY * 26;
const baseScale = Math.min(W, H) / 920 * zoom;
const k = 0.014 + blackHole * 0.0018;
const q = 0.009 + wave * 0.00012;
const collapse = phase.collapsePhase;
const danger = phase.nearSingularity;
const rebirth = phase.rebirth;
const restore = phase.restoration;
ctx.save();
ctx.globalCompositeOperation = 'lighter';
for (let i = 1; i <= particles; i++) {
const sqrtI = Math.sqrt(i);
const baseR = (expansion * sqrtI + wave * Math.sin(i * k + time * 1.6)) * baseScale;
const baseTheta = i * GOLDEN_ANGLE + 0.42 * Math.sin(time + i * q);
// Во время схлопывания частица стремится к чёрной дыре, но не строго по прямой — она закручивается.
const sinkR = (10 + 42 * Math.sin(i * 0.019 + time * 3) ** 2) * baseScale;
const collapseR = mix(baseR, sinkR, collapse);
const twistedTheta = baseTheta + vortex * collapse * collapse * (1.5 + i / particles * 5.5) + time * collapse * 2.4;
// В момент восстановления радиус возвращается к базовой структуре через ударную волну.
const waveFront = smoothstep(0.0, 1.0, rebirth);
const particleDelay = i / particles;
const rebirthGate = smoothstep(particleDelay * 0.55, particleDelay * 0.55 + 0.22, waveFront);
const shock = rebirthPower * 30 * Math.sin((particleDelay - waveFront) * 18) * Math.exp(-Math.abs(particleDelay - waveFront) * 6) * baseScale;
let r = mix(collapseR, baseR + shock, Math.max(rebirthGate, restore));
r *= 1 + 0.08 * Math.sin(time * 3 + i * 0.005) * (rebirth + restore);
let theta = mix(twistedTheta, baseTheta + 0.35 * Math.sin(time * 2 + i * q), Math.max(rebirthGate, restore));
const depth = 120 * Math.sin(i * q + time * 1.4);
const zNorm = (Math.sin(i * q + time * 1.4) + 1) / 2;
const x = cx + r * Math.cos(theta) + mouseX * depth * 0.13;
const y = cy + r * Math.sin(theta) + mouseY * depth * 0.13;
const radiusNorm = Math.min(1, r / (Math.min(W, H) * 0.58));
const swallowed = collapse > 0.7 && r < (54 + blackHole * 9) * baseScale;
const swallowFade = swallowed ? 0.25 : 1;
const size = Math.max(0.38, 0.52 + zNorm * 1.55 - radiusNorm * 0.18 + rebirth * 0.9);
const alpha = Math.max(0.02, (0.13 + zNorm * 0.62 - radiusNorm * 0.15 + rebirth * 0.4 - danger * 0.08) * swallowFade);
ctx.globalAlpha = alpha;
ctx.fillStyle = colorFor(i, zNorm, radiusNorm, phase, p);
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fill();
// Следы падения в чёрную дыру.
if (collapse > 0.28 && i % 17 === 0) {
const tail = 1 + collapse * 18;
const tx = cx + (r + tail) * Math.cos(theta - collapse * 0.18);
const ty = cy + (r + tail) * Math.sin(theta - collapse * 0.18);
ctx.globalAlpha = alpha * 0.22 * collapse;
ctx.strokeStyle = colorFor(i, zNorm, radiusNorm, phase, p);
ctx.lineWidth = Math.max(0.35, size * 0.45);
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(tx, ty);
ctx.stroke();
}
}
ctx.restore();
drawBlackHole(cx, cy, phase, baseScale);
drawRebirthShock(cx, cy, phase, baseScale);
// Финальная вспышка в момент отскока.
if (rebirth > 0.05) {
ctx.save();
ctx.globalCompositeOperation = 'lighter';
ctx.globalAlpha = 0.12 + rebirth * 0.28;
ctx.fillStyle = 'rgba(255,255,255,.65)';
ctx.fillRect(0, 0, W, H);
ctx.restore();
}
}
function frame(now) {
const dt = now - lastTime;
lastTime = now;
fpsSmooth = fpsSmooth * 0.92 + (1000 / Math.max(1, dt)) * 0.08;
els.fps.textContent = Math.round(fpsSmooth);
drawUniverse(dt);
requestAnimationFrame(frame);
}
for (const key of ['particles', 'cycle', 'expansion', 'blackHole', 'vortex', 'wave', 'rebirthPower']) {
els[key].addEventListener('input', syncLabels);
}
els.preset.addEventListener('change', () => applyPreset(els.preset.value));
els.randomBtn.addEventListener('click', randomUniverse);
els.pauseBtn.addEventListener('click', () => {
paused = !paused;
els.pauseBtn.textContent = paused ? 'Продолжить' : 'Пауза';
});
window.addEventListener('resize', resize);
window.addEventListener('mousemove', (e) => {
mouseX = (e.clientX / Math.max(1, W) - 0.5) * 2;
mouseY = (e.clientY / Math.max(1, H) - 0.5) * 2;
});
window.addEventListener('wheel', (e) => {
e.preventDefault();
const factor = e.deltaY < 0 ? 1.08 : 0.92;
zoom = Math.min(5, Math.max(0.35, zoom * factor));
}, { passive: false });
resize();
applyPreset('rebirth');
syncLabels();
requestAnimationFrame(frame);
</script>
</body>
</html>
Пэйстнул не в тот чат?)
Не тактично вводить математику такие "перлы". Существуют ссылки. Оформляйте ими и возьмите в дальнейшем это за правило. Просто необходимо уважать любое сообщество, тем более вы у автора в гостях, поэтому нельзя выглядеть в такой ситуации "аля" теоретиком, философом, а оказываться просто - невеждой. Или создайте на основе своих изложений, свой личный, привлекающий внимание, пост. Желаю успехов.
Геометрический факт («6 радиан близко к полному обороту») скрещивается с алгебраическим фактом («простые числа не могут принадлежать классам вычетов с общими делителями»). Результат скрещивания — нетривиальное объяснение паттерна.
Трёхмерное обобщение («паттерн знака бесконечности») осталось необъяснённым.
Насколько же глубоко можно зарыться в обычный числовой ряд, если есть уйма свободного времени.. Жаль только что трехмерную бесконечность так и оставили без объяснения)
простые числа определяются целостностью всех чисел всех их отношений между собой , то есть из недостижимого рациональным умом конца бесконечности
Осталось отобразить распределение простых чисел в 3D, 5D, 7D, 11D, ... проекциях.
Картины будут ощеломляюще прекрасными рапределениями.
Спасибо за статью! Очень интересный подход к визуализации распределения простых чисел.
Я сам раньше много возился с простыми числами и начинал с классического ряда 2k+1 — то есть с нечётных чисел. Довольно быстро понял, что этого недостаточно. Чтобы хоть как-то подобраться к пониманию того, как простые числа распределены на самом деле, пришлось переключиться на числа, делящиеся на 2, потом на 3, потом на 4 и так далее. Постепенно, шаг за шагом.
Ведь простые числа не существуют в вакууме. Они всегда окружены числами, которые делятся на что-то ещё, кроме себя и единицы. И без анализа этой «составной среды» их природу не понять. Ваш подход со спиралями и классами вычетов по модулю 44, 710 — в каком-то смысле про то же самое: простые видны только на фоне того, что ими не является.
Вопрос автору: не пробовали ли вы строить аналогичные спирали не для самих простых чисел, а для их «расстояний до ближайшего составного»? Мне кажется, это могло бы дать дополнительный слой закономерностей.


Почему простые числа собираются в спирали?