// F4. Орбитальный конструктор (§17-18) — запусти спутник, увидь орбиту. (function(){ 'use strict'; const B = () => window.PHYS9_FLAG_BASE; const C = () => window.PHYS9_COLORS || {}; function init(secId){ if (!B()) return false; const body = '' + '
' + '' + '' + '' + '
' + '' + '
' + '' + '' + '' + '
' + '
' + '
Тип орбиты
' + '
$T$ (период, если эллипс)
' + '
$|r|$ от центра200 px
' + '
$|v|$120 px/с
' + '
' + '
'; const card = B().makeCard(secId, 'F4. Орбитальный конструктор', 'Запусти спутник с разной начальной скоростью. Слишком мало — упадёт. Точно — круговая. Чуть больше — эллипс. Слишком много — улетит навсегда.', body); if (!card) return false; const cv = document.getElementById('F4-cv'); const ctx = cv.getContext('2d'); const W = cv.width, H = cv.height; const cx = W/2, cy = H/2; const planetR = 30; const startR = 200; /* начальное расстояние от центра */ let st = { x: cx + startR, y: cy, vx: 0, vy: -120, trail: [], running: false, crashed: false, escaped: false, /* для определения периода — фиксируем угол при старте */ lastAngle: 0, revolutions: 0, tStart: 0, t: 0 }; function readSliders(){ document.getElementById('F4-Mv').textContent = (+document.getElementById('F4-M').value).toFixed(0); document.getElementById('F4-v0v').textContent = (+document.getElementById('F4-v0').value).toFixed(0); document.getElementById('F4-anv').textContent = (+document.getElementById('F4-an').value).toFixed(0); } function reset(){ const v0 = +document.getElementById('F4-v0').value; const an = +document.getElementById('F4-an').value; st.x = cx + startR; st.y = cy; st.vx = v0 * Math.cos(an*Math.PI/180); st.vy = -v0 * Math.sin(an*Math.PI/180); st.trail = []; st.crashed = false; st.escaped = false; st.running = false; st.t = 0; st.revolutions = 0; st.tStart = 0; st.lastAngle = Math.atan2(st.y - cy, st.x - cx); document.getElementById('F4-fb').className = 'flag-feedback'; document.getElementById('F4-go').textContent = 'Запустить'; draw(); } function setCircular(){ /* для круговой v = sqrt(G*M/r) — у нас G=1, M из slider'a */ const M = +document.getElementById('F4-M').value; const vCirc = Math.sqrt(M / startR); document.getElementById('F4-v0').value = Math.round(vCirc); document.getElementById('F4-an').value = 90; readSliders(); reset(); } function tick(dt){ if (!st.running || st.crashed || st.escaped) { draw(); return; } /* Гравитация: F = G*M*m/r², a = G*M/r² к центру */ const M = +document.getElementById('F4-M').value; /* RK4 was bы лучше но Эйлер с малым шагом норм */ const N = 8; const ddt = dt / N; for (let i = 0; i < N; i++){ const dx = cx - st.x, dy = cy - st.y; const r2 = dx*dx + dy*dy; const r = Math.sqrt(r2); if (r < planetR){ st.crashed = true; break; } if (r > 2000){ st.escaped = true; break; } const aMag = M / r2; const ax = aMag * dx / r, ay = aMag * dy / r; st.vx += ax * ddt; st.vy += ay * ddt; st.x += st.vx * ddt; st.y += st.vy * ddt; st.t += ddt; /* trail */ if (i === N-1) st.trail.push({ x: st.x, y: st.y }); if (st.trail.length > 1500) st.trail.shift(); /* определение периода — переход через ось x */ const ang = Math.atan2(st.y - cy, st.x - cx); if (st.lastAngle > Math.PI*0.9 && ang < -Math.PI*0.9 || st.lastAngle < -Math.PI*0.9 && ang > Math.PI*0.9){ /* skip — wrap */ } else if (st.lastAngle < 0 && ang >= 0){ st.revolutions++; if (st.revolutions === 1) st.tStart = st.t; } st.lastAngle = ang; } const fb = document.getElementById('F4-fb'); if (st.crashed){ st.running = false; document.getElementById('F4-go').textContent = 'Запустить'; fb.className = 'flag-feedback fail show'; fb.innerHTML = '✗ Спутник упал на планету! Нужна бо́льшая начальная скорость.'; } else if (st.escaped){ st.running = false; document.getElementById('F4-go').textContent = 'Запустить'; fb.className = 'flag-feedback warn show'; fb.innerHTML = 'Спутник улетел! Это вторая космическая (~$\\sqrt 2 v_{круг}$). Уменьши скорость.'; } /* статистика */ const dx = st.x - cx, dy = st.y - cy; const r = Math.sqrt(dx*dx + dy*dy); const v = Math.sqrt(st.vx*st.vx + st.vy*st.vy); document.getElementById('F4-r').textContent = r.toFixed(0) + ' px'; document.getElementById('F4-v').textContent = v.toFixed(0) + ' px/с'; /* классификация */ const Mclassify = +document.getElementById('F4-M').value; const vCirc = Math.sqrt(Mclassify / startR); const ratio = +document.getElementById('F4-v0').value / vCirc; let type; if (st.crashed) type = '✗ падение'; else if (st.escaped) type = '↗ убегание'; else if (Math.abs(ratio - 1) < 0.05) type = '○ круговая'; else if (ratio < 0.95) type = '⬭ эллипс (ближе к планете)'; else if (ratio < 1.41) type = '⬮ эллипс (дальше)'; else type = '↗ убегание'; document.getElementById('F4-type').textContent = type; if (st.revolutions >= 2 && st.tStart > 0){ const T = (st.t - st.tStart) / (st.revolutions - 1); document.getElementById('F4-T').textContent = T.toFixed(2) + ' с'; } draw(); } function draw(){ const col = C(); ctx.fillStyle = col.bg || '#fafafa'; ctx.fillRect(0, 0, W, H); /* звёзды для красоты */ ctx.fillStyle = col.textMuted || '#94a3b8'; for (let i = 0; i < 60; i++){ const sx = (i * 37 + 17) % W; const sy = (i * 61 + 23) % H; ctx.fillRect(sx, sy, 1, 1); } /* планета (Земля) */ ctx.fillStyle = col.forceGravity || '#2563eb'; ctx.beginPath(); ctx.arc(cx, cy, planetR, 0, Math.PI*2); ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; ctx.stroke(); ctx.fillStyle = '#fff'; ctx.font = 'bold 12px Inter,sans-serif'; ctx.textAlign = 'center'; ctx.fillText('M', cx, cy + 4); ctx.textAlign = 'left'; /* trail */ if (st.trail.length > 1){ ctx.strokeStyle = col.velocity || '#0891b2'; ctx.lineWidth = 1.8; ctx.beginPath(); ctx.moveTo(st.trail[0].x, st.trail[0].y); for (let i = 1; i < st.trail.length; i++) ctx.lineTo(st.trail[i].x, st.trail[i].y); ctx.stroke(); } /* спутник */ ctx.fillStyle = st.crashed ? col.fail : (col.acceleration || '#ea580c'); ctx.beginPath(); ctx.arc(st.x, st.y, 7, 0, Math.PI*2); ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.2; ctx.stroke(); /* вектор v */ if (st.running || st.t === 0){ const v = Math.sqrt(st.vx*st.vx + st.vy*st.vy); const k = v > 0 ? 40 / v : 0; B().arrow(ctx, st.x, st.y, st.x + st.vx*k, st.y + st.vy*k, col.velocity || '#0891b2', 2); } } document.getElementById('F4-go').addEventListener('click', ()=>{ if (st.crashed || st.escaped) reset(); if (!st.running) { reset(); st.running = true; document.getElementById('F4-go').textContent = 'Пауза'; } else { st.running = false; document.getElementById('F4-go').textContent = 'Запустить'; } }); document.getElementById('F4-reset').addEventListener('click', reset); document.getElementById('F4-circle').addEventListener('click', setCircular); ['F4-M','F4-v0','F4-an'].forEach(id => document.getElementById(id).addEventListener('input', ()=>{ readSliders(); if (!st.running) reset(); })); readSliders(); reset(); B().startLoop('F4', cv, tick); return true; } if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F4', { init: init, cleanup: function(){} }); else document.addEventListener('DOMContentLoaded', ()=>{ if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F4', { init: init, cleanup: function(){} }); }); })();