From 788d61271661940e9f650b98e4dbf37a5fd9b21c Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Fri, 29 May 2026 15:00:13 +0300 Subject: [PATCH] =?UTF-8?q?feat(geom11=20ch4=20wave2):=20=C2=A710=20=C2=AB?= =?UTF-8?q?=D0=9A=D0=BE=D0=BE=D1=80=D0=B4=D0=B8=D0=BD=D0=B0=D1=82=D1=8B=20?= =?UTF-8?q?=D0=B8=20=D0=B2=D0=B5=D0=BA=D1=82=D0=BE=D1=80=D1=8B=203D=C2=BB?= =?UTF-8?q?=20+=203D-=D0=B2=D0=B8=D0=B7=D1=83=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=82=D0=BE=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/textbooks/geometry_11_ch4.html | 418 +++++++++++++++++++++++- 1 file changed, 417 insertions(+), 1 deletion(-) diff --git a/frontend/textbooks/geometry_11_ch4.html b/frontend/textbooks/geometry_11_ch4.html index 978a60a..f25182b 100644 --- a/frontend/textbooks/geometry_11_ch4.html +++ b/frontend/textbooks/geometry_11_ch4.html @@ -400,7 +400,7 @@ function buildParaSelector(){ } const BUILT=new Set(); -const BUILDERS = { p8:()=>buildP8(), p9:()=>buildP9(), p10:()=>buildStub('p10'), p11:()=>buildStub('p11'), final4:()=>buildStub('final4') }; +const BUILDERS = { p8:()=>buildP8(), p9:()=>buildP9(), p10:()=>buildP10(), p11:()=>buildStub('p11'), final4:()=>buildStub('final4') }; function ensureBuilt(id){ if(BUILT.has(id)) return; const fn=BUILDERS[id]; if(fn){ fn(); BUILT.add(id); } } function goTo(id){ STATE.current=id; ensureBuilt(id); @@ -1191,6 +1191,422 @@ function buildP9(){ wireReadBtn('p9'); } +/* ===== §10 «Координаты и векторы» ===== */ + +function buildP10(){ + const box = document.getElementById('p10-body'); + if(!box) return; + let html = ''; + + /* === ТЕОРИЯ === */ + + html += makeCard('theory', 'Координаты в пространстве и длина вектора', '§ 10.1', + '

Декартова система координат. Три взаимно перпендикулярные оси $Ox$, $Oy$, $Oz$ с общим началом $O$. Каждая точка пространства задаётся тройкой координат $(x;\\, y;\\, z)$.

' + + '

Вектор $\\vec{v}$ в пространстве — упорядоченная тройка координат:

' + + '

$\\vec{v} = (x;\\, y;\\, z)$

' + + '

Длина вектора:

' + + '

$|\\vec{v}| = \\sqrt{x^2 + y^2 + z^2}$

' + + '

Расстояние между точками $A(x_1, y_1, z_1)$ и $B(x_2, y_2, z_2)$:

' + + '

$|AB| = \\sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2 + (z_2 - z_1)^2}$

' + + '

Координаты середины отрезка $AB$:

' + + '

$M = \\left(\\dfrac{x_1+x_2}{2};\\, \\dfrac{y_1+y_2}{2};\\, \\dfrac{z_1+z_2}{2}\\right)$

' + + '
Пример: $A(1, 2, 3)$, $B(4, 6, 7)$
' + + '

$|AB| = \\sqrt{(4-1)^2 + (6-2)^2 + (7-3)^2} = \\sqrt{9 + 16 + 16} = \\sqrt{41} \\approx 6{,}40$.

' + + '

Середина: $M = \\left(\\dfrac{5}{2};\\, 4;\\, 5\\right) = (2{,}5;\\, 4;\\, 5)$.

' + + '
'); + + html += makeCard('rule', 'Скалярное произведение и угол между векторами', '§ 10.2', + '

Операции с векторами (поэлементно):

' + + '' + + '

Скалярное произведение (две эквивалентные формулы):

' + + '

$\\vec{a} \\cdot \\vec{b} = a_1 b_1 + a_2 b_2 + a_3 b_3 = |\\vec{a}| \\cdot |\\vec{b}| \\cdot \\cos\\alpha$

' + + '

Косинус угла между векторами:

' + + '

$\\cos\\alpha = \\dfrac{\\vec{a} \\cdot \\vec{b}}{|\\vec{a}| \\cdot |\\vec{b}|}$

' + + '

Условие перпендикулярности: $\\vec{a} \\perp \\vec{b} \\Leftrightarrow \\vec{a} \\cdot \\vec{b} = 0$.

' + + '
Пример: $\\vec{a} = (1, 2, 2)$, $\\vec{b} = (2, -1, 2)$
' + + '

$\\vec{a} \\cdot \\vec{b} = 1\\cdot 2 + 2\\cdot(-1) + 2\\cdot 2 = 2 - 2 + 4 = 4$.

' + + '

$|\\vec{a}| = \\sqrt{1 + 4 + 4} = 3$, $|\\vec{b}| = \\sqrt{4 + 1 + 4} = 3$.

' + + '

$\\cos\\alpha = \\dfrac{4}{3 \\cdot 3} = \\dfrac{4}{9} \\approx 0{,}444$, откуда $\\alpha \\approx 63{,}6°$.

' + + '
'); + + /* === ИНТЕРАКТИВ 1 — 3D-визуализатор векторов === */ + html += '
' + + '
3D · визуализатор
Векторы $\\vec{a}$ и $\\vec{b}$ в пространстве
' + + '
Перетащи мышью, чтобы вращать сцену. Меняй координаты слайдерами. После 4 разных конфигураций — +10 XP.
' + + '
' + + '' + + '' + + '' + + '' + + '
' + + '
' + + '
' + + '
' + + '
$\\vec{a} = (a_1;\\, a_2;\\, a_3)$
' + + '' + + '' + + '' + + '
' + + '
' + + '
$\\vec{b} = (b_1;\\, b_2;\\, b_3)$
' + + '' + + '' + + '' + + '
' + + '
' + + '
' + + '
Изучено конфигураций: 0 / 4
' + + '
'; + + /* === ИНТЕРАКТИВ 2 — Калькулятор векторов === */ + html += '
' + + '
калькулятор
Операции над векторами
' + + '
Введи координаты, выбери операцию. После 4 операций — +15 XP.
' + + '
' + + '
' + + '
$\\vec{a}$
' + + '
' + + '$a_1$=' + + '$a_2$=' + + '$a_3$=' + + '
' + + '
' + + '
' + + '
$\\vec{b}$
' + + '
' + + '$b_1$=' + + '$b_2$=' + + '$b_3$=' + + '
' + + '
' + + '
' + + '
' + + '' + + '' + + '' + + '' + + '' + + '
' + + '
Выбери операцию.
' + + '
Операций использовано: 0 / 4
' + + '
'; + + /* === ИНТЕРАКТИВ 3 — Тренажёр векторов === */ + html += '
' + + '
тренажёр · 5 задач
Векторы и координаты
' + + '
Введи числовой ответ. Допуск $\\pm 0{,}05$. После всех — +15 XP.
' + + '
' + + '
Решено: 0 / 5
' + + '
'; + + html += secNavFor('p10'); + html += readButton('p10'); + + box.innerHTML = html; + renderMath(box); + + /* === IV1 — 3D-визуализатор === */ + (function(){ + const svg = document.getElementById('p10-iv1-svg'); + if(!svg) return; + + /* Если G3D недоступен — graceful fallback */ + if(!window.G3D){ + svg.innerHTML = 'G3D engine not loaded'; + return; + } + + const scene = G3D.createScene({W:480, H:400, scale:38, camDist:9, rotX:-0.42, rotY:0.78}); + const AX_COL = { X:'#dc2626', Y:'#10b981', Z:'#2563eb' }; + const VA_COL = '#ea580c'; /* оранжевый */ + const VB_COL = '#9333ea'; /* фиолетовый */ + const AX_LEN = 4; + const GRID_LEN = 4; + + let a = { x:2, y:1, z:0 }; + let b = { x:0, y:2, z:1.5 }; + const seen = new Set(); + let xpGiven = false; + + /* Проекция 3D-точки на 2D-плоскость SVG */ + function P(v, M){ + const r = G3D.vApply(M, v); + return G3D.projectPersp(r, scene.camDist, scene.cx, scene.cy, scene.scale); + } + + /* Рендер одной координатной оси (двусторонняя линия + стрелка + подпись) */ + function renderAxis(M, dir, col, label){ + const len = AX_LEN; + const pPos = P({x:dir.x*len, y:dir.y*len, z:dir.z*len}, M); + const pNeg = P({x:-dir.x*len*0.6, y:-dir.y*len*0.6, z:-dir.z*len*0.6}, M); + const pO = P({x:0,y:0,z:0}, M); + if(!pPos || !pNeg || !pO) return ''; + let s = ''; + /* отрицательная часть — тонко, пунктир */ + s += ''; + /* положительная часть — толсто */ + s += ''; + /* стрелка-треугольник на конце положительной части */ + s += renderArrowHead(M, {x:0,y:0,z:0}, {x:dir.x*len, y:dir.y*len, z:dir.z*len}, col, 0.18); + /* подпись */ + s += ''+label+''; + return s; + } + + /* Стрелка-треугольник в конце вектора */ + function renderArrowHead(M, tail, head, col, size){ + size = size || 0.22; + /* направление в 3D */ + const dx = head.x - tail.x, dy = head.y - tail.y, dz = head.z - tail.z; + const L = Math.sqrt(dx*dx + dy*dy + dz*dz) || 1; + const ux = dx/L, uy = dy/L, uz = dz/L; + /* выбираем «вспомогательную» ось, не параллельную направлению */ + let helper = (Math.abs(uy) < 0.9) ? {x:0,y:1,z:0} : {x:1,y:0,z:0}; + /* perp1 = u x helper */ + const px = uy*helper.z - uz*helper.y; + const py = uz*helper.x - ux*helper.z; + const pz = ux*helper.y - uy*helper.x; + const pl = Math.sqrt(px*px+py*py+pz*pz) || 1; + const npx = px/pl, npy = py/pl, npz = pz/pl; + /* основание стрелки = head - u*size */ + const baseX = head.x - ux*size, baseY = head.y - uy*size, baseZ = head.z - uz*size; + /* две точки сбоку */ + const sx1 = baseX + npx*size*0.55, sy1 = baseY + npy*size*0.55, sz1 = baseZ + npz*size*0.55; + const sx2 = baseX - npx*size*0.55, sy2 = baseY - npy*size*0.55, sz2 = baseZ - npz*size*0.55; + const pHead = P(head, M); + const pS1 = P({x:sx1,y:sy1,z:sz1}, M); + const pS2 = P({x:sx2,y:sy2,z:sz2}, M); + if(!pHead || !pS1 || !pS2) return ''; + return ''; + } + + /* Рендер вектора-стрелки от O до v */ + function renderVector(M, v, col, label){ + const L = Math.sqrt(v.x*v.x + v.y*v.y + v.z*v.z); + if(L < 0.01) return ''; + const pO = P({x:0,y:0,z:0}, M); + const pV = P(v, M); + if(!pO || !pV) return ''; + let s = ''; + s += ''; + s += renderArrowHead(M, {x:0,y:0,z:0}, v, col, 0.28); + /* подпись чуть в стороне от наконечника */ + s += ''+label+''; + return s; + } + + /* Рендер сетки на плоскости XOZ (пол) */ + function renderGrid(M){ + let s = ''; + const step = 1, N = GRID_LEN; + for(let i = -N; i <= N; i++){ + const p1 = P({x:i,y:-0.001,z:-N}, M); + const p2 = P({x:i,y:-0.001,z: N}, M); + const q1 = P({x:-N,y:-0.001,z:i}, M); + const q2 = P({x: N,y:-0.001,z:i}, M); + if(p1 && p2) s += ''; + if(q1 && q2) s += ''; + } + return s; + } + + function draw(){ + const M = G3D.buildRotMatrix(scene); + let s = ''; + s += renderGrid(M); + s += renderAxis(M, {x:1,y:0,z:0}, AX_COL.X, 'X'); + s += renderAxis(M, {x:0,y:1,z:0}, AX_COL.Y, 'Y'); + s += renderAxis(M, {x:0,y:0,z:1}, AX_COL.Z, 'Z'); + s += renderVector(M, a, VA_COL, 'a⃗'); + s += renderVector(M, b, VB_COL, 'b⃗'); + svg.innerHTML = s; + } + + function updateOut(){ + const out = document.getElementById('p10-iv1-out'); + const la = Math.sqrt(a.x*a.x + a.y*a.y + a.z*a.z); + const lb = Math.sqrt(b.x*b.x + b.y*b.y + b.z*b.z); + const dot = a.x*b.x + a.y*b.y + a.z*b.z; + const denom = la * lb; + const cosA = denom > 1e-9 ? dot / denom : null; + const ang = cosA !== null ? Math.acos(Math.max(-1, Math.min(1, cosA))) * 180 / Math.PI : null; + const perp = Math.abs(dot) < 1e-6; + let h = ''; + h += '

$\\vec{a}$ = ('+fmt(a.x)+'; '+fmt(a.y)+'; '+fmt(a.z)+'),   $\\vec{b}$ = ('+fmt(b.x)+'; '+fmt(b.y)+'; '+fmt(b.z)+')

'; + h += '

$|\\vec{a}| = \\sqrt{'+fmt(a.x*a.x)+'+'+fmt(a.y*a.y)+'+'+fmt(a.z*a.z)+'} \\approx '+la.toFixed(3)+'$,   $|\\vec{b}| \\approx '+lb.toFixed(3)+'$

'; + h += '

$\\vec{a} \\cdot \\vec{b} = '+fmt(a.x)+'\\cdot '+fmt(b.x)+' + '+fmt(a.y)+'\\cdot '+fmt(b.y)+' + '+fmt(a.z)+'\\cdot '+fmt(b.z)+' = '+dot.toFixed(3)+'$

'; + if(cosA !== null){ + h += '

$\\cos\\alpha = \\dfrac{'+dot.toFixed(3)+'}{'+la.toFixed(3)+'\\cdot '+lb.toFixed(3)+'} \\approx '+cosA.toFixed(3)+'$,   $\\alpha \\approx '+ang.toFixed(1)+'°$

'; + } else { + h += '

Один из векторов нулевой — угол не определён.

'; + } + if(perp && la > 1e-6 && lb > 1e-6){ + h += '

$\\vec{a} \\perp \\vec{b}$ ✓ (скалярное произведение равно 0)

'; + } + out.innerHTML = h; + renderMath(out); + } + + function bumpSeen(){ + const key = [a.x,a.y,a.z,b.x,b.y,b.z].map(v=>v.toFixed(1)).join('|'); + seen.add(key); + const cnt = document.getElementById('p10-iv1-cnt'); + if(cnt) cnt.textContent = Math.min(seen.size, 4); + if(seen.size >= 4 && !xpGiven){ + xpGiven = true; + addXp(10, 'p10-iv1'); + bumpProgress('p10', 20); + } + } + + function bindSlider(id, axis, key){ + const el = document.getElementById('p10-iv1-'+id); + const lbl = document.getElementById('p10-iv1-'+id+'-v'); + if(!el || !lbl) return; + el.addEventListener('input', function(){ + const v = parseFloat(el.value); + lbl.textContent = (v === Math.round(v)) ? String(v) : v.toFixed(1); + if(axis === 'a') a[key] = v; else b[key] = v; + draw(); updateOut(); bumpSeen(); + }); + } + bindSlider('a1','a','x'); bindSlider('a2','a','y'); bindSlider('a3','a','z'); + bindSlider('b1','b','x'); bindSlider('b2','b','y'); bindSlider('b3','b','z'); + + draw(); updateOut(); + G3D.attachOrbit(svg, scene, draw); + + document.querySelectorAll('#p10-iv1 .g3d-tools .btn').forEach(function(btn){ + btn.addEventListener('click', function(){ + G3D.presetView(scene, btn.dataset.view, draw); + }); + }); + })(); + + /* === IV2 — Калькулятор векторов === */ + (function(){ + const out = document.getElementById('p10-iv2-out'); + const cntEl = document.getElementById('p10-iv2-cnt'); + const used = new Set(); + let xpGiven = false; + + function rd(id){ + const raw = (document.getElementById('p10-iv2-'+id).value || '').replace(',', '.').trim(); + return parseFloat(raw); + } + function readVec(p){ + return { x:rd(p+'1'), y:rd(p+'2'), z:rd(p+'3') }; + } + function vecOk(v){ return isFinite(v.x) && isFinite(v.y) && isFinite(v.z); } + function vecStr(v){ return '('+fmt(v.x)+';\\, '+fmt(v.y)+';\\, '+fmt(v.z)+')'; } + + document.querySelectorAll('#p10-iv2 button[data-op]').forEach(function(btn){ + btn.addEventListener('click', function(){ + const a = readVec('a'), b = readVec('b'); + if(!vecOk(a) || !vecOk(b)){ + out.innerHTML = 'Введи числовые координаты во все поля.'; + return; + } + const op = btn.dataset.op; + let h = ''; + if(op === 'sum'){ + const r = { x:a.x+b.x, y:a.y+b.y, z:a.z+b.z }; + h = '

$\\vec{a} + \\vec{b} = ('+fmt(a.x)+'+'+fmt(b.x)+';\\, '+fmt(a.y)+'+'+fmt(b.y)+';\\, '+fmt(a.z)+'+'+fmt(b.z)+') = '+vecStr(r)+'$

'; + } else if(op === 'diff'){ + const r = { x:a.x-b.x, y:a.y-b.y, z:a.z-b.z }; + h = '

$\\vec{a} - \\vec{b} = ('+fmt(a.x)+'-('+fmt(b.x)+');\\, '+fmt(a.y)+'-('+fmt(b.y)+');\\, '+fmt(a.z)+'-('+fmt(b.z)+')) = '+vecStr(r)+'$

'; + } else if(op === 'dot'){ + const d = a.x*b.x + a.y*b.y + a.z*b.z; + h = '

$\\vec{a} \\cdot \\vec{b} = '+fmt(a.x)+'\\cdot '+fmt(b.x)+' + '+fmt(a.y)+'\\cdot '+fmt(b.y)+' + '+fmt(a.z)+'\\cdot '+fmt(b.z)+' = '+d.toFixed(3)+'$

'; + if(Math.abs(d) < 1e-6) h += '

$\\vec{a} \\perp \\vec{b}$ ✓

'; + } else if(op === 'cos'){ + const d = a.x*b.x + a.y*b.y + a.z*b.z; + const la = Math.sqrt(a.x*a.x + a.y*a.y + a.z*a.z); + const lb = Math.sqrt(b.x*b.x + b.y*b.y + b.z*b.z); + if(la < 1e-9 || lb < 1e-9){ h = '

Один из векторов нулевой — угол не определён.

'; } + else { + const c = d/(la*lb); + const ang = Math.acos(Math.max(-1,Math.min(1,c))) * 180/Math.PI; + h = '

$|\\vec{a}| \\approx '+la.toFixed(3)+'$,   $|\\vec{b}| \\approx '+lb.toFixed(3)+'$,   $\\vec{a}\\cdot\\vec{b} = '+d.toFixed(3)+'$

' + + '

$\\cos\\alpha = \\dfrac{'+d.toFixed(3)+'}{'+la.toFixed(3)+'\\cdot '+lb.toFixed(3)+'} \\approx '+c.toFixed(3)+'$,   $\\alpha \\approx '+ang.toFixed(2)+'°$

'; + } + } else if(op === 'len'){ + const la = Math.sqrt(a.x*a.x + a.y*a.y + a.z*a.z); + h = '

$|\\vec{a}| = \\sqrt{'+fmt(a.x*a.x)+'+'+fmt(a.y*a.y)+'+'+fmt(a.z*a.z)+'} = \\sqrt{'+(a.x*a.x+a.y*a.y+a.z*a.z).toFixed(3)+'} \\approx '+la.toFixed(3)+'$

'; + } + out.innerHTML = h; + renderMath(out); + used.add(op); + cntEl.textContent = Math.min(used.size, 4); + if(used.size >= 4 && !xpGiven){ + xpGiven = true; + addXp(15, 'p10-iv2'); + bumpProgress('p10', 30); + } + }); + }); + })(); + + /* === IV3 — Тренажёр === */ + (function(){ + const tasks = [ + { q:'Найди длину вектора $\\vec{a} = (2,\\, 3,\\, 6)$.', a:7, tol:0.05 }, + { q:'Скалярное произведение $\\vec{a} = (1,\\, 2,\\, 3)$ и $\\vec{b} = (4,\\, 5,\\, 6)$.', a:32, tol:0.05 }, + { q:'Расстояние между точками $A(1, 2, 3)$ и $B(4, 6, 7)$.', a:6.40, tol:0.05 }, + { q:'Если $\\vec{a} = (3,\\, 0,\\, 4)$, найди $|\\vec{a}|$.', a:5, tol:0.05 }, + { q:'Найди $\\cos\\alpha$ между $\\vec{a} = (1,\\, 0,\\, 0)$ и $\\vec{b} = (1,\\, 1,\\, 0)$.', a:0.71, tol:0.05 } + ]; + const list = document.getElementById('p10-iv3-list'); + const scoreEl = document.getElementById('p10-iv3-score'); + const solved = new Set(); + let xpGiven = false; + + list.innerHTML = tasks.map(function(t, i){ + return '
' + + '
Задача '+(i+1)+'. '+t.q+'
' + + '
' + + '' + + '' + + '
' + + '' + + '
'; + }).join(''); + renderMath(list); + + list.querySelectorAll('button[data-i]').forEach(function(b){ + b.addEventListener('click', function(){ + const i = +b.dataset.i, t = tasks[i]; + const inp = document.getElementById('p10-iv3-inp-'+i); + const fb = document.getElementById('p10-iv3-fb-'+i); + const raw = (inp.value || '').replace(',', '.').trim(); + const val = parseFloat(raw); + if(!isFinite(val)){ feedback(fb, false, '✗ Введи число'); return; } + if(Math.abs(val - t.a) <= t.tol){ + feedback(fb, true, '✓ Верно!'); + if(!solved.has(i)){ + solved.add(i); + scoreEl.textContent = solved.size; + if(solved.size === tasks.length && !xpGiven){ + xpGiven = true; + addXp(15, 'p10-iv3'); + bumpProgress('p10', 30); + setTimeout(function(){ achievement('p10_done'); }, 400); + } + } + } else { + feedback(fb, false, '✗ Не точно. Пересчитай аккуратно.'); + } + }); + }); + })(); + + wireReadBtn('p10'); +} + /* ===== Search ===== */ const SEARCH_INDEX = (function(){ const arr=[];