// F9. Конструктор моста (§28 в ch3) — drag грузов, расчёт реакций опор. (function(){ 'use strict'; const B = () => window.PHYS9_FLAG_BASE; const C = () => window.PHYS9_COLORS || {}; function init(secId){ if (!B()) return false; const body = '' + '
Перетащи грузы с верхней палитры на балку. Реакции опор $N_1$, $N_2$ рассчитываются автоматически.
' + '
' + '
50 кг
' + '
100 кг
' + '
200 кг
' + '
500 кг
' + '' + '' + '
' + '' + '
' + '
Грузов на балке0
' + '
Σ масс0 кг
' + '
$N_1$ (левая опора)0 Н
' + '
$N_2$ (правая опора)0 Н
' + '
' + '
'; const card = B().makeCard(secId, 'F9. Конструктор моста', 'Через систему: $\\Sigma F = 0$, $\\Sigma M = 0$. Реакции опор $N_1, N_2$ балансируют все грузы. Лимит балки — 8000 Н на каждую опору.', body); if (!card) return false; const cv = document.getElementById('F9-cv'); const ctx = cv.getContext('2d'); const W = cv.width, H = cv.height; const beamY = 180, beamH = 16; const supL = 70, supR = W - 70; const beamLen = supR - supL; let loads = []; /* {x, mass} */ let broken = false; let dragging = null; /* {mass, x, y} */ function compute(){ if (loads.length === 0) return { N1: 0, N2: 0, sum: 0, brokenSide: null }; const g = 9.8; const L = supR - supL; /* Σ M вокруг левой опоры (supL): N2 * L - Σ(m*g*(x - supL)) = 0 */ let sumMass = 0, sumMoment = 0; loads.forEach(l => { sumMass += l.mass; sumMoment += l.mass * g * (l.x - supL); }); const N2 = sumMoment / L; const N1 = sumMass * g - N2; return { N1, N2, sum: sumMass*g, brokenSide: (N1 > 8000) ? 'left' : (N2 > 8000) ? 'right' : null }; } function update(){ const r = compute(); document.getElementById('F9-n').textContent = loads.length; document.getElementById('F9-m').textContent = loads.reduce((s,l)=>s+l.mass,0) + ' кг'; document.getElementById('F9-n1').textContent = r.N1.toFixed(0) + ' Н'; document.getElementById('F9-n2').textContent = r.N2.toFixed(0) + ' Н'; broken = !!r.brokenSide; const fb = document.getElementById('F9-fb'); if (broken){ fb.className = 'flag-feedback fail show'; fb.innerHTML = '✗ МОСТ СЛОМАН! ' + (r.brokenSide === 'left' ? 'Левая' : 'Правая') + ' опора перегружена ('+(r.brokenSide==='left'?r.N1:r.N2).toFixed(0)+' Н > 8000 Н).'; } else if (loads.length > 0){ fb.className = 'flag-feedback ok show'; fb.innerHTML = '✓ Балка выдерживает. Σ реакций = '+(r.N1+r.N2).toFixed(0)+' Н = $\\Sigma m g$ = '+r.sum.toFixed(0)+' Н.'; try { if(window.renderMathInElement) window.renderMathInElement(fb, { delimiters:[{left:'$',right:'$',display:false}], throwOnError:false }); } catch(e){} } else { fb.className = 'flag-feedback'; } draw(); } function getPos(e){ const rect = cv.getBoundingClientRect(); const sx = cv.width / rect.width, sy = cv.height / rect.height; const x = ((e.touches ? e.touches[0].clientX : e.clientX) - rect.left) * sx; const y = ((e.touches ? e.touches[0].clientY : e.clientY) - rect.top) * sy; return { x, y }; } function draw(){ const col = C(); ctx.fillStyle = col.bg || '#fafafa'; ctx.fillRect(0, 0, W, H); /* земля */ ctx.fillStyle = col.surface || '#a16207'; ctx.fillRect(0, H - 30, W, 30); /* опоры */ ctx.fillStyle = col.body || '#475569'; ctx.beginPath(); ctx.moveTo(supL, beamY + beamH); ctx.lineTo(supL - 22, H - 30); ctx.lineTo(supL + 22, H - 30); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.moveTo(supR, beamY + beamH); ctx.lineTo(supR - 22, H - 30); ctx.lineTo(supR + 22, H - 30); ctx.closePath(); ctx.fill(); /* балка */ const tilt = broken ? 0 : 0; ctx.fillStyle = broken ? col.fail : col.bodyAccent || '#1e293b'; if (broken){ const r = compute(); const breakX = r.brokenSide === 'left' ? supL + beamLen*0.3 : supL + beamLen*0.7; ctx.fillStyle = col.bodyAccent || '#1e293b'; ctx.fillRect(supL - 30, beamY, breakX - supL + 30, beamH); ctx.save(); ctx.translate(breakX + 30, beamY + beamH); ctx.rotate(0.3); ctx.fillRect(0, -beamH, supR - breakX + 30, beamH); ctx.restore(); } else { ctx.fillRect(supL - 30, beamY, beamLen + 60, beamH); } /* подписи опор */ ctx.fillStyle = col.text || '#0f172a'; ctx.font = 'bold 13px Inter,sans-serif'; const r = compute(); ctx.fillText('N₁ = '+r.N1.toFixed(0)+' Н', supL - 50, H - 8); ctx.fillText('N₂ = '+r.N2.toFixed(0)+' Н', supR - 50, H - 8); /* стрелки реакций */ if (!broken && loads.length > 0){ B().arrow(ctx, supL, H - 35, supL, beamY + beamH + 2, col.force || '#10b981', 2.5); B().arrow(ctx, supR, H - 35, supR, beamY + beamH + 2, col.force || '#10b981', 2.5); } /* грузы */ loads.forEach(l => { const size = Math.min(36, 14 + Math.sqrt(l.mass) * 0.8); ctx.fillStyle = col.forceGravity || '#2563eb'; ctx.fillRect(l.x - size/2, beamY - size, size, size); ctx.strokeStyle = col.bodyAccent || '#1e293b'; ctx.lineWidth = 1.5; ctx.strokeRect(l.x - size/2, beamY - size, size, size); ctx.fillStyle = '#fff'; ctx.font = 'bold 11px Inter,sans-serif'; ctx.textAlign = 'center'; ctx.fillText(l.mass+' кг', l.x, beamY - size/2 + 4); ctx.textAlign = 'left'; }); /* dragging shadow */ if (dragging){ const size = Math.min(36, 14 + Math.sqrt(dragging.mass) * 0.8); ctx.globalAlpha = 0.6; ctx.fillStyle = col.forceGravity || '#2563eb'; ctx.fillRect(dragging.x - size/2, dragging.y - size/2, size, size); ctx.globalAlpha = 1; } } /* DRAG from palette */ card.querySelectorAll('[data-mass]').forEach(btn => { btn.addEventListener('mousedown', e => { const mass = +btn.dataset.mass; const onMove = ev => { const p = getPos(ev); dragging = { mass, x: p.x, y: p.y }; draw(); }; const onUp = ev => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); const p = getPos(ev); if (p.y > beamY - 50 && p.y < beamY + 50 && p.x > supL - 20 && p.x < supR + 20){ loads.push({ x: p.x, mass }); } dragging = null; update(); }; document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp); }); }); /* Click on canvas to remove load */ cv.addEventListener('click', e => { const p = getPos(e); for (let i = loads.length - 1; i >= 0; i--){ if (Math.abs(loads[i].x - p.x) < 20 && p.y > beamY - 40 && p.y < beamY + 5){ loads.splice(i, 1); update(); return; } } }); document.getElementById('F9-clear').addEventListener('click', () => { loads = []; update(); }); document.getElementById('F9-test').addEventListener('click', () => { loads.push({ x: supL + beamLen/2, mass: 1000 }); update(); }); update(); draw(); return true; } if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F9', { init: init, cleanup: function(){} }); else document.addEventListener('DOMContentLoaded', ()=>{ if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F9', { init: init, cleanup: function(){} }); }); })();