feat(geom11 ch4 wave2): §10 «Координаты и векторы 3D» + 3D-визуализатор
This commit is contained in:
@@ -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',
|
||||
'<p><b>Декартова система координат.</b> Три взаимно перпендикулярные оси $Ox$, $Oy$, $Oz$ с общим началом $O$. Каждая точка пространства задаётся тройкой координат $(x;\\, y;\\, z)$.</p>'
|
||||
+ '<p><b>Вектор</b> $\\vec{v}$ в пространстве — упорядоченная тройка координат:</p>'
|
||||
+ '<p style="text-align:center;margin:6px 0">$\\vec{v} = (x;\\, y;\\, z)$</p>'
|
||||
+ '<p><b>Длина вектора:</b></p>'
|
||||
+ '<p style="text-align:center;margin:6px 0">$|\\vec{v}| = \\sqrt{x^2 + y^2 + z^2}$</p>'
|
||||
+ '<p><b>Расстояние между точками</b> $A(x_1, y_1, z_1)$ и $B(x_2, y_2, z_2)$:</p>'
|
||||
+ '<p style="text-align:center;margin:6px 0">$|AB| = \\sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2 + (z_2 - z_1)^2}$</p>'
|
||||
+ '<p><b>Координаты середины отрезка</b> $AB$:</p>'
|
||||
+ '<p style="text-align:center;margin:6px 0">$M = \\left(\\dfrac{x_1+x_2}{2};\\, \\dfrac{y_1+y_2}{2};\\, \\dfrac{z_1+z_2}{2}\\right)$</p>'
|
||||
+ '<details class="spoiler"><summary>Пример: $A(1, 2, 3)$, $B(4, 6, 7)$</summary><div class="spoiler-body">'
|
||||
+ '<p>$|AB| = \\sqrt{(4-1)^2 + (6-2)^2 + (7-3)^2} = \\sqrt{9 + 16 + 16} = \\sqrt{41} \\approx 6{,}40$.</p>'
|
||||
+ '<p>Середина: $M = \\left(\\dfrac{5}{2};\\, 4;\\, 5\\right) = (2{,}5;\\, 4;\\, 5)$.</p>'
|
||||
+ '</div></details>');
|
||||
|
||||
html += makeCard('rule', 'Скалярное произведение и угол между векторами', '§ 10.2',
|
||||
'<p><b>Операции с векторами</b> (поэлементно):</p>'
|
||||
+ '<ul style="margin:6px 0 10px 22px;line-height:1.7">'
|
||||
+ '<li>Сумма: $\\vec{a} + \\vec{b} = (a_1 + b_1;\\, a_2 + b_2;\\, a_3 + b_3)$.</li>'
|
||||
+ '<li>Разность: $\\vec{a} - \\vec{b} = (a_1 - b_1;\\, a_2 - b_2;\\, a_3 - b_3)$.</li>'
|
||||
+ '<li>Умножение на число $k$: $k\\vec{a} = (ka_1;\\, ka_2;\\, ka_3)$.</li>'
|
||||
+ '</ul>'
|
||||
+ '<p><b>Скалярное произведение</b> (две эквивалентные формулы):</p>'
|
||||
+ '<p style="text-align:center;margin:6px 0">$\\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$</p>'
|
||||
+ '<p><b>Косинус угла</b> между векторами:</p>'
|
||||
+ '<p style="text-align:center;margin:6px 0">$\\cos\\alpha = \\dfrac{\\vec{a} \\cdot \\vec{b}}{|\\vec{a}| \\cdot |\\vec{b}|}$</p>'
|
||||
+ '<p><b>Условие перпендикулярности:</b> $\\vec{a} \\perp \\vec{b} \\Leftrightarrow \\vec{a} \\cdot \\vec{b} = 0$.</p>'
|
||||
+ '<details class="spoiler"><summary>Пример: $\\vec{a} = (1, 2, 2)$, $\\vec{b} = (2, -1, 2)$</summary><div class="spoiler-body">'
|
||||
+ '<p>$\\vec{a} \\cdot \\vec{b} = 1\\cdot 2 + 2\\cdot(-1) + 2\\cdot 2 = 2 - 2 + 4 = 4$.</p>'
|
||||
+ '<p>$|\\vec{a}| = \\sqrt{1 + 4 + 4} = 3$, $|\\vec{b}| = \\sqrt{4 + 1 + 4} = 3$.</p>'
|
||||
+ '<p>$\\cos\\alpha = \\dfrac{4}{3 \\cdot 3} = \\dfrac{4}{9} \\approx 0{,}444$, откуда $\\alpha \\approx 63{,}6°$.</p>'
|
||||
+ '</div></details>');
|
||||
|
||||
/* === ИНТЕРАКТИВ 1 — 3D-визуализатор векторов === */
|
||||
html += '<div class="wg" id="p10-iv1">'
|
||||
+ '<div class="wg-header"><span class="wg-badge">3D · визуализатор</span><div class="wg-title">Векторы $\\vec{a}$ и $\\vec{b}$ в пространстве</div></div>'
|
||||
+ '<div class="wg-help">Перетащи мышью, чтобы вращать сцену. Меняй координаты слайдерами. После <b>4 разных конфигураций</b> — +10 XP.</div>'
|
||||
+ '<div class="g3d-tools">'
|
||||
+ '<button class="btn" data-view="iso">Изо</button>'
|
||||
+ '<button class="btn" data-view="front">Спереди</button>'
|
||||
+ '<button class="btn" data-view="top">Сверху</button>'
|
||||
+ '<button class="btn" data-view="side">Сбоку</button>'
|
||||
+ '</div>'
|
||||
+ '<div style="background:var(--card);border-radius:9px;padding:10px;text-align:center;margin-top:6px"><svg id="p10-iv1-svg" viewBox="0 0 480 400" width="100%" style="max-width:480px;height:auto;display:block;margin:0 auto"></svg></div>'
|
||||
+ '<div class="sliders" style="margin-top:10px;display:grid;grid-template-columns:1fr 1fr;gap:14px">'
|
||||
+ '<div style="background:rgba(234,88,12,0.08);border:1px solid rgba(234,88,12,0.3);border-radius:9px;padding:8px 10px">'
|
||||
+ '<div style="font-weight:700;color:#ea580c;margin-bottom:6px">$\\vec{a} = (a_1;\\, a_2;\\, a_3)$</div>'
|
||||
+ '<label style="display:block;font-size:.85rem;margin:3px 0">$a_1$: <input type="range" id="p10-iv1-a1" min="-3" max="3" step="0.5" value="2" style="width:55%;vertical-align:middle"> <span id="p10-iv1-a1-v" class="kx">2</span></label>'
|
||||
+ '<label style="display:block;font-size:.85rem;margin:3px 0">$a_2$: <input type="range" id="p10-iv1-a2" min="-3" max="3" step="0.5" value="1" style="width:55%;vertical-align:middle"> <span id="p10-iv1-a2-v" class="kx">1</span></label>'
|
||||
+ '<label style="display:block;font-size:.85rem;margin:3px 0">$a_3$: <input type="range" id="p10-iv1-a3" min="-3" max="3" step="0.5" value="0" style="width:55%;vertical-align:middle"> <span id="p10-iv1-a3-v" class="kx">0</span></label>'
|
||||
+ '</div>'
|
||||
+ '<div style="background:rgba(147,51,234,0.08);border:1px solid rgba(147,51,234,0.3);border-radius:9px;padding:8px 10px">'
|
||||
+ '<div style="font-weight:700;color:#9333ea;margin-bottom:6px">$\\vec{b} = (b_1;\\, b_2;\\, b_3)$</div>'
|
||||
+ '<label style="display:block;font-size:.85rem;margin:3px 0">$b_1$: <input type="range" id="p10-iv1-b1" min="-3" max="3" step="0.5" value="0" style="width:55%;vertical-align:middle"> <span id="p10-iv1-b1-v" class="kx">0</span></label>'
|
||||
+ '<label style="display:block;font-size:.85rem;margin:3px 0">$b_2$: <input type="range" id="p10-iv1-b2" min="-3" max="3" step="0.5" value="2" style="width:55%;vertical-align:middle"> <span id="p10-iv1-b2-v" class="kx">2</span></label>'
|
||||
+ '<label style="display:block;font-size:.85rem;margin:3px 0">$b_3$: <input type="range" id="p10-iv1-b3" min="-3" max="3" step="0.5" value="1.5" style="width:55%;vertical-align:middle"> <span id="p10-iv1-b3-v" class="kx">1.5</span></label>'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '<div id="p10-iv1-out" style="margin-top:10px;padding:10px 14px;background:var(--card);border:1px solid var(--border);border-radius:9px;font-size:.92rem;line-height:1.75"></div>'
|
||||
+ '<div style="font-size:.78rem;color:var(--muted);margin-top:6px">Изучено конфигураций: <b id="p10-iv1-cnt">0</b> / 4</div>'
|
||||
+ '</div>';
|
||||
|
||||
/* === ИНТЕРАКТИВ 2 — Калькулятор векторов === */
|
||||
html += '<div class="wg" id="p10-iv2">'
|
||||
+ '<div class="wg-header"><span class="wg-badge">калькулятор</span><div class="wg-title">Операции над векторами</div></div>'
|
||||
+ '<div class="wg-help">Введи координаты, выбери операцию. После <b>4 операций</b> — +15 XP.</div>'
|
||||
+ '<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:8px">'
|
||||
+ '<div style="background:rgba(234,88,12,0.08);border:1px solid rgba(234,88,12,0.3);border-radius:9px;padding:8px 10px">'
|
||||
+ '<div style="font-weight:700;color:#ea580c;margin-bottom:6px">$\\vec{a}$</div>'
|
||||
+ '<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">'
|
||||
+ '<span class="kx">$a_1$</span>=<input type="text" class="tinp" id="p10-iv2-a1" value="1" style="width:55px">'
|
||||
+ '<span class="kx">$a_2$</span>=<input type="text" class="tinp" id="p10-iv2-a2" value="2" style="width:55px">'
|
||||
+ '<span class="kx">$a_3$</span>=<input type="text" class="tinp" id="p10-iv2-a3" value="2" style="width:55px">'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '<div style="background:rgba(147,51,234,0.08);border:1px solid rgba(147,51,234,0.3);border-radius:9px;padding:8px 10px">'
|
||||
+ '<div style="font-weight:700;color:#9333ea;margin-bottom:6px">$\\vec{b}$</div>'
|
||||
+ '<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">'
|
||||
+ '<span class="kx">$b_1$</span>=<input type="text" class="tinp" id="p10-iv2-b1" value="2" style="width:55px">'
|
||||
+ '<span class="kx">$b_2$</span>=<input type="text" class="tinp" id="p10-iv2-b2" value="-1" style="width:55px">'
|
||||
+ '<span class="kx">$b_3$</span>=<input type="text" class="tinp" id="p10-iv2-b3" value="2" style="width:55px">'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '<div style="display:flex;gap:6px;flex-wrap:wrap;margin-top:10px">'
|
||||
+ '<button class="btn primary" data-op="sum">Сумма $\\vec{a}+\\vec{b}$</button>'
|
||||
+ '<button class="btn primary" data-op="diff">Разность $\\vec{a}-\\vec{b}$</button>'
|
||||
+ '<button class="btn primary" data-op="dot">Скалярное произв.</button>'
|
||||
+ '<button class="btn primary" data-op="cos">Угол ($\\cos\\alpha$, $\\alpha°$)</button>'
|
||||
+ '<button class="btn primary" data-op="len">Длина $|\\vec{a}|$</button>'
|
||||
+ '</div>'
|
||||
+ '<div id="p10-iv2-out" style="margin-top:10px;padding:10px 14px;background:var(--card);border:1px solid var(--border);border-radius:9px;font-size:.92rem;line-height:1.75;min-height:40px">Выбери операцию.</div>'
|
||||
+ '<div style="font-size:.78rem;color:var(--muted);margin-top:6px">Операций использовано: <b id="p10-iv2-cnt">0</b> / 4</div>'
|
||||
+ '</div>';
|
||||
|
||||
/* === ИНТЕРАКТИВ 3 — Тренажёр векторов === */
|
||||
html += '<div class="wg" id="p10-iv3">'
|
||||
+ '<div class="wg-header"><span class="wg-badge">тренажёр · 5 задач</span><div class="wg-title">Векторы и координаты</div></div>'
|
||||
+ '<div class="wg-help">Введи числовой ответ. Допуск $\\pm 0{,}05$. После всех — +15 XP.</div>'
|
||||
+ '<div id="p10-iv3-list"></div>'
|
||||
+ '<div class="score-display" style="margin-top:10px">Решено: <b id="p10-iv3-score">0</b> / 5</div>'
|
||||
+ '</div>';
|
||||
|
||||
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 = '<text x="240" y="200" text-anchor="middle" fill="#888">G3D engine not loaded</text>';
|
||||
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 += '<line x1="'+pNeg.x.toFixed(1)+'" y1="'+pNeg.y.toFixed(1)+'" x2="'+pO.x.toFixed(1)+'" y2="'+pO.y.toFixed(1)+'" stroke="'+col+'" stroke-width="1" stroke-dasharray="3,3" opacity=".55"/>';
|
||||
/* положительная часть — толсто */
|
||||
s += '<line x1="'+pO.x.toFixed(1)+'" y1="'+pO.y.toFixed(1)+'" x2="'+pPos.x.toFixed(1)+'" y2="'+pPos.y.toFixed(1)+'" stroke="'+col+'" stroke-width="2" opacity=".9"/>';
|
||||
/* стрелка-треугольник на конце положительной части */
|
||||
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 += '<text x="'+(pPos.x + dir.x*8).toFixed(1)+'" y="'+(pPos.y - dir.y*10 + 4).toFixed(1)+'" fill="'+col+'" font-size="14" font-weight="700">'+label+'</text>';
|
||||
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 '<polygon points="'+pHead.x.toFixed(1)+','+pHead.y.toFixed(1)+' '+pS1.x.toFixed(1)+','+pS1.y.toFixed(1)+' '+pS2.x.toFixed(1)+','+pS2.y.toFixed(1)+'" fill="'+col+'" stroke="'+col+'" stroke-width="1"/>';
|
||||
}
|
||||
|
||||
/* Рендер вектора-стрелки от 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 += '<line x1="'+pO.x.toFixed(1)+'" y1="'+pO.y.toFixed(1)+'" x2="'+pV.x.toFixed(1)+'" y2="'+pV.y.toFixed(1)+'" stroke="'+col+'" stroke-width="3" stroke-linecap="round"/>';
|
||||
s += renderArrowHead(M, {x:0,y:0,z:0}, v, col, 0.28);
|
||||
/* подпись чуть в стороне от наконечника */
|
||||
s += '<text x="'+(pV.x + 10).toFixed(1)+'" y="'+(pV.y - 6).toFixed(1)+'" fill="'+col+'" font-size="13" font-weight="700" font-style="italic">'+label+'</text>';
|
||||
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 += '<line x1="'+p1.x.toFixed(1)+'" y1="'+p1.y.toFixed(1)+'" x2="'+p2.x.toFixed(1)+'" y2="'+p2.y.toFixed(1)+'" stroke="#94a3b8" stroke-width=".5" opacity=".25"/>';
|
||||
if(q1 && q2) s += '<line x1="'+q1.x.toFixed(1)+'" y1="'+q1.y.toFixed(1)+'" x2="'+q2.x.toFixed(1)+'" y2="'+q2.y.toFixed(1)+'" stroke="#94a3b8" stroke-width=".5" opacity=".25"/>';
|
||||
}
|
||||
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 += '<p><b style="color:'+VA_COL+'">$\\vec{a}$</b> = ('+fmt(a.x)+'; '+fmt(a.y)+'; '+fmt(a.z)+'), <b style="color:'+VB_COL+'">$\\vec{b}$</b> = ('+fmt(b.x)+'; '+fmt(b.y)+'; '+fmt(b.z)+')</p>';
|
||||
h += '<p>$|\\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)+'$</p>';
|
||||
h += '<p>$\\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)+'$</p>';
|
||||
if(cosA !== null){
|
||||
h += '<p>$\\cos\\alpha = \\dfrac{'+dot.toFixed(3)+'}{'+la.toFixed(3)+'\\cdot '+lb.toFixed(3)+'} \\approx '+cosA.toFixed(3)+'$, $\\alpha \\approx '+ang.toFixed(1)+'°$</p>';
|
||||
} else {
|
||||
h += '<p style="color:var(--muted)">Один из векторов нулевой — угол не определён.</p>';
|
||||
}
|
||||
if(perp && la > 1e-6 && lb > 1e-6){
|
||||
h += '<p style="color:#10b981;font-weight:700">$\\vec{a} \\perp \\vec{b}$ ✓ (скалярное произведение равно 0)</p>';
|
||||
}
|
||||
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 = '<span style="color:var(--bad,#ef4444)">Введи числовые координаты во все поля.</span>';
|
||||
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 = '<p>$\\vec{a} + \\vec{b} = ('+fmt(a.x)+'+'+fmt(b.x)+';\\, '+fmt(a.y)+'+'+fmt(b.y)+';\\, '+fmt(a.z)+'+'+fmt(b.z)+') = '+vecStr(r)+'$</p>';
|
||||
} else if(op === 'diff'){
|
||||
const r = { x:a.x-b.x, y:a.y-b.y, z:a.z-b.z };
|
||||
h = '<p>$\\vec{a} - \\vec{b} = ('+fmt(a.x)+'-('+fmt(b.x)+');\\, '+fmt(a.y)+'-('+fmt(b.y)+');\\, '+fmt(a.z)+'-('+fmt(b.z)+')) = '+vecStr(r)+'$</p>';
|
||||
} else if(op === 'dot'){
|
||||
const d = a.x*b.x + a.y*b.y + a.z*b.z;
|
||||
h = '<p>$\\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)+'$</p>';
|
||||
if(Math.abs(d) < 1e-6) h += '<p style="color:#10b981;font-weight:700">$\\vec{a} \\perp \\vec{b}$ ✓</p>';
|
||||
} 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 = '<p style="color:var(--bad,#ef4444)">Один из векторов нулевой — угол не определён.</p>'; }
|
||||
else {
|
||||
const c = d/(la*lb);
|
||||
const ang = Math.acos(Math.max(-1,Math.min(1,c))) * 180/Math.PI;
|
||||
h = '<p>$|\\vec{a}| \\approx '+la.toFixed(3)+'$, $|\\vec{b}| \\approx '+lb.toFixed(3)+'$, $\\vec{a}\\cdot\\vec{b} = '+d.toFixed(3)+'$</p>'
|
||||
+ '<p>$\\cos\\alpha = \\dfrac{'+d.toFixed(3)+'}{'+la.toFixed(3)+'\\cdot '+lb.toFixed(3)+'} \\approx '+c.toFixed(3)+'$, $\\alpha \\approx '+ang.toFixed(2)+'°$</p>';
|
||||
}
|
||||
} else if(op === 'len'){
|
||||
const la = Math.sqrt(a.x*a.x + a.y*a.y + a.z*a.z);
|
||||
h = '<p>$|\\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)+'$</p>';
|
||||
}
|
||||
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 '<div style="background:var(--card);border:1px solid var(--border);border-radius:9px;padding:10px 12px;margin-bottom:8px">'
|
||||
+ '<div style="margin-bottom:6px"><b>Задача '+(i+1)+'.</b> '+t.q+'</div>'
|
||||
+ '<div style="display:flex;gap:6px;flex-wrap:wrap;align-items:center">'
|
||||
+ '<input type="text" class="tinp" id="p10-iv3-inp-'+i+'" placeholder="число" style="width:140px">'
|
||||
+ '<button class="btn primary" data-i="'+i+'">Проверить</button>'
|
||||
+ '</div>'
|
||||
+ '<div class="feedback" id="p10-iv3-fb-'+i+'"></div>'
|
||||
+ '</div>';
|
||||
}).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=[];
|
||||
|
||||
Reference in New Issue
Block a user