Туторіал · Спортивна фізика · Аеродинаміка · JavaScript
📅 Липень 2026 ⏱ ≈ 20 хв 🎯 Середній рівень

Симуляція польоту м'яча з ефектом Магнуса на JavaScript

Крива подача у бейсболі, «банан»-удар вільним у футболі та топспін-форхенд у тенісі — усі спираються на одну й ту саму фізику: силу Магнуса, яку створює обертовий м'яч, рухаючись крізь повітря. Цей туторіал будує невеликий, точний симулятор польоту м'яча з рівняння опору, сили Магнуса та інтегрування RK4.

1. Сили, що діють на обертовий м'яч

Щойно м'яч залишає ногу, биту чи ракетку, у повітрі на нього діють рівно три сили (без урахування вітру):

Інтуїція Кутта-Жуковського: спін через тертя тягне тонкий шар повітря разом з м'ячем, прискорюючи потік з одного боку і сповільнюючи з іншого. За принципом Бернуллі, швидший потік означає нижчий тиск — тож м'яч штовхає в бік низького тиску (швидшого потоку).

2. Сила опору

Стандартне квадратичне рівняння опору, напрямок завжди протилежний вектору швидкості:

Fd = 0.5 · Cd · rho · A · v² (напрямок: −v̂)
function dragForce(vel, Cd, rho, area) {
  const speed = Math.hypot(vel.x, vel.y, vel.z);
  if (speed === 0) return { x: 0, y: 0, z: 0 };
  const mag = 0.5 * Cd * rho * area * speed ** 2;
  // напрямок протилежний швидкості
  return {
    x: -mag * (vel.x / speed),
    y: -mag * (vel.y / speed),
    z: -mag * (vel.z / speed),
  };
}

3. Сила Магнуса від спіну

Сила Магнуса пропорційна векторному добутку кутової швидкості (спіну) ω і лінійної швидкості v — вона завжди перпендикулярна площині, що містить обидва вектори:

F_Magnus = S · (ω × v) S = ½ · Cl · ρ · A · r (коефіцієнт підйомної сили Cl, радіус м'яча r)
function magnusForce(vel, omega, Cl, rho, area, radius) {
  // векторний добуток: omega × vel
  const cross = {
    x: omega.y * vel.z - omega.z * vel.y,
    y: omega.z * vel.x - omega.x * vel.z,
    z: omega.x * vel.y - omega.y * vel.x,
  };
  const S = 0.5 * Cl * rho * area * radius;
  return { x: S * cross.x, y: S * cross.y, z: S * cross.z };
}
Backspin, topspin і sidespin: вісь спіну визначає напрямок кривизни. Backspin (верх м'яча обертається назад відносно руху) з ω уздовж горизонтальної бокової осі створює висхідну силу Магнуса — саме тому вдало вдарений м'яч для гольфу чи чіп летить далі, ніж снаряд без спіну. Sidespin (ω приблизно вертикальна) вигинає м'яч ліворуч чи праворуч — класичний «банан»-удар у футболі.

4. Вектор стану і рівняння руху

Стан м'яча — це позиція і швидкість; спін ω вважається приблизно сталим протягом польоту (загасання спіну повільне порівняно з тривалістю польоту для більшості видів спорту):

function acceleration(vel, params) {
  const { mass, Cd, Cl, rho, area, radius, omega, g } = params;
  const fDrag   = dragForce(vel, Cd, rho, area);
  const fMagnus = magnusForce(vel, omega, Cl, rho, area, radius);

  return {
    x: (fDrag.x + fMagnus.x) / mass,
    y: (fDrag.y + fMagnus.y) / mass - g,
    z: (fDrag.z + fMagnus.z) / mass,
  };
}

5. Інтегрування RK4

Оскільки і опір, і сила Магнуса нелінійно залежать від швидкості, простий метод Ейлера накопичує помітну похибку за довгий політ, якщо тільки крок не зробити крихітним. Рунге-Кутта 4-го порядку (RK4) дає набагато кращу точність при тому самому обчислювальному бюджеті:

function stepRK4(pos, vel, dt, params) {
  const a1 = acceleration(vel, params);
  const v1 = vel;

  const v2 = addScaled(vel, a1, dt / 2);
  const a2 = acceleration(v2, params);

  const v3 = addScaled(vel, a2, dt / 2);
  const a3 = acceleration(v3, params);

  const v4 = addScaled(vel, a3, dt);
  const a4 = acceleration(v4, params);

  // зважене середнє чотирьох оцінок нахилу
  const vAvg = {
    x: (v1.x + 2*v2.x + 2*v3.x + v4.x) / 6,
    y: (v1.y + 2*v2.y + 2*v3.y + v4.y) / 6,
    z: (v1.z + 2*v2.z + 2*v3.z + v4.z) / 6,
  };
  const aAvg = {
    x: (a1.x + 2*a2.x + 2*a3.x + a4.x) / 6,
    y: (a1.y + 2*a2.y + 2*a3.y + a4.y) / 6,
    z: (a1.z + 2*a2.z + 2*a3.z + a4.z) / 6,
  };

  return {
    pos: addScaled(pos, vAvg, dt),
    vel: addScaled(vel, aAvg, dt),
  };
}

function addScaled(v, d, s) {
  return { x: v.x + d.x*s, y: v.y + d.y*s, z: v.z + d.z*s };
}

6. Порівняння траєкторій зі спіном і без

Запуск симуляції двічі з тими самими умовами старту — раз з omega = {x:0, y:0, z:0}, і раз з реалістичною швидкістю обертання — показує точно, скільки кривизни чи додаткової дальності додає спін:

function simulateTrajectory(initialVel, omega, params, dt = 0.005) {
  let pos = { x: 0, y: 0, z: 0 };
  let vel = initialVel;
  const path = [{ ...pos }];
  params.omega = omega;

  while (pos.y >= 0 && path.length < 5000) {
    const next = stepRK4(pos, vel, dt, params);
    pos = next.pos;
    vel = next.vel;
    path.push({ ...pos });
  }
  return path;
}

// прямий удар проти кривого "банан"-удару, та сама початкова швидкість
const straight = simulateTrajectory(launchVel, { x: 0, y: 0, z: 0 }, params);
const curved   = simulateTrajectory(launchVel, { x: 0, y: 45, z: 0 }, params); // ~7.2 об/с бокового спіну
Реальний масштаб: футбольний м'яч, вдарений з сильним боковим спіном (~8–10 об/с) на вільному ударі з 20 м, може відхилитися вбік на 1–1.5 м порівняно з ударом без спіну — достатньо, щоб обійти стіну захисників і влучити в кут воріт.

7. Рендер траєкторії на Canvas

Для простого вигляду збоку чи зверху спроектуйте 3D-точки траєкторії на координати 2D canvas і намалюйте їх як ламану лінію, оновлюючи щокадр анімації для ефекту «м'яча в польоті» в реальному часі:

function drawTrajectory(ctx, path, scale, originX, originY) {
  ctx.beginPath();
  path.forEach((p, i) => {
    // вигляд збоку: x = дистанція по дальності, y = висота
    const screenX = originX + p.x * scale;
    const screenY = originY - p.y * scale;
    if (i === 0) ctx.moveTo(screenX, screenY);
    else ctx.lineTo(screenX, screenY);
  });
  ctx.strokeStyle = "#4ade80";
  ctx.lineWidth = 2;
  ctx.stroke();
}

8. Налаштування констант для реальних видів спорту

Реалістичні значення Cd, коефіцієнта підйомної сили та маси/радіуса важливіші за інтегратор для відповідності реальним траєкторіям:

М'ячМаса (г)Радіус (см)CdТиповий спін
Футбольний м'яч43011.00.20–0.250–10 об/с
Тенісний м'яч583.30.55–0.65до 100 об/с (топспін)
Бейсбольний м'яч1453.60.3–0.420–40 об/с
М'яч для гольфу462.10.24–0.3050–80 об/с (backspin)

Швидкість обертання зазвичай вимірюють в об/хв (RPM) у спортивно-науковій літературі — переведіть у рад/с за формулою ω = RPM · 2π / 60 перед тим, як подати у модель вище. Коефіцієнт підйомної сили Cl визначити важче, ніж Cd, і зазвичай його підбирають емпірично з даних аеродинамічної труби чи трекінгу типу Trackman для кожного типу м'яча; 0.1–0.3 — розумний початковий діапазон для більшості сферичних спортивних м'ячів при помірних співвідношеннях спіну (ωr/v).

Дивіться також: для випадку без спіну, лише з опором (кулі, стріли, скинуті об'єкти), дивіться нашу статтю Балістика з опором повітря. Про те, як саме вимірюється Cd для різних форм, дивіться Коефіцієнт опору Cd: обтічні форми.

🌀 Спробуйте симуляцію ефекту Магнуса

Подивіться, як backspin і topspin вигинають траєкторію м'яча в реальному часі, і порівняйте її поруч з траєкторією без спіну.

Відкрити симуляцію →

Часті запитання

Чого я навчуся в цьому уроці?

Побудуйте симулятор кривого польоту м'яча з нуля: сила опору, підіймальна сила Магнуса від backspin/topspin, інтегрування RK4 та порівняння траєкторій зі спіном і без.

Які теми розглядаються в цьому уроці?

Цей урок охоплює такі теми: Сили, що діють на обертовий м'яч, Сила опору, Сила Магнуса від спіну, Вектор стану і рівняння руху, Інтегрування RK4, Порівняння траєкторій зі спіном і без, Рендер траєкторії на Canvas, Налаштування констант для реальних видів спорту.

Скільки часу займає цей урок?

Цей урок займає приблизно 20 хв.

Які попередні знання потрібні?

Це урок рівня «Середній рівень» — окрема попередня підготовка, крім базового JavaScript, не потрібна.