fix(geom9 ch4): радиусы в реальных единицах вместо пикселей

Все 4 IV1 в Главе 4 показывали R в пикселях (130/120/70/100),
из-за чего S_круга получалось $\pi · 10000 ≈ 31415$ — для
школьника это не геометрия, а абстракция.

§13 IV1: R = 130 px → переинтерпретировано как R = 10 ед.
(K = 13). r тоже в единицах.

§14 IV1: slider R = 50..150 px → R = 2..8 ед. (K = 18 px/ед.).
SVG рисуется через Rpx = R · K, формулы a, r, P, S в единицах.

§15 IV1: slider R = 40..100 px → R = 2..5 ед. (K = 20).
Таблица a₃=R√3, a₄=R√2, a₆=R даёт нормальные числа.

§16 IV1: slider R = 40..150 px → R = 2..6 ед. (K = 25).
C, S, дуга, сектор — все осмысленные значения.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-29 11:03:13 +03:00
parent 52824d8fc9
commit b07da5ee6d
+35 -26
View File
@@ -543,7 +543,7 @@ function buildP15(){
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Три эталонных многоугольника</div></div>
<div class="wg-help">Меняй радиус $R$ — увидишь сразу 3 правильных многоугольника с одинаковой описанной окружностью.</div>
<div class="sliders">
<label>Радиус $R$<b id="p15-iv1-rval">70</b><input type="range" id="p15-iv1-r" min="40" max="100" step="1" value="70"></label>
<label>Радиус $R$<b id="p15-iv1-rval">3.5</b><input type="range" id="p15-iv1-r" min="2" max="5" step="0.1" value="3.5"></label>
</div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p15-iv1-svg" viewBox="0 0 700 260" style="width:100%;max-width:700px;min-width:340px;height:auto;display:block;margin:0 auto"></svg>
@@ -615,13 +615,15 @@ function buildP15(){
{x:350, y:130, n:4, label:'Квадрат', aStr:'R\\sqrt{2}'},
{x:580, y:130, n:6, label:'6-угольник', aStr:'R'}];
function draw(){
const R = +sl.value;
lab.textContent = R;
const R = +sl.value; // в условных единицах (2..5)
lab.textContent = R.toFixed(1);
const K = 20; // px / ед.
const Rpx = R * K;
let s = '';
centers.forEach(c=>{
const pts = regularPoly(c.x, c.y, R, c.n);
const pts = regularPoly(c.x, c.y, Rpx, c.n);
// описанная окр
s += '<circle cx="'+c.x+'" cy="'+c.y+'" r="'+R+'" fill="none" stroke="#94a3b8" stroke-width="1.3" stroke-dasharray="4 4"/>';
s += '<circle cx="'+c.x+'" cy="'+c.y+'" r="'+Rpx.toFixed(2)+'" fill="none" stroke="#94a3b8" stroke-width="1.3" stroke-dasharray="4 4"/>';
// многоугольник
s += '<polygon points="'+pts.map(p=>p.x.toFixed(2)+','+p.y.toFixed(2)).join(' ')+'" fill="rgba(8,145,178,.12)" stroke="#0e7490" stroke-width="2.2" stroke-linejoin="round"/>';
// одна сторона (выделим)
@@ -837,7 +839,7 @@ function buildP16(){
<div class="wg-header"><span class="wg-badge">ИНТЕРАКТИВ 1</span><div class="wg-title">Окружность, круг и сектор</div></div>
<div class="wg-help">Меняй радиус $R$ и угол сектора $\\alpha$ — следи за длиной окружности, площадью круга, длиной дуги и площадью сектора.</div>
<div class="sliders">
<label>Радиус $R$<b id="p16-iv1-rval">100</b><input type="range" id="p16-iv1-r" min="40" max="150" step="1" value="100"></label>
<label>Радиус $R$<b id="p16-iv1-rval">4</b><input type="range" id="p16-iv1-r" min="2" max="6" step="0.1" value="4"></label>
<label>Угол $\\alpha$ (°)<b id="p16-iv1-aval">90</b><input type="range" id="p16-iv1-a" min="0" max="360" step="1" value="90"></label>
</div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
@@ -912,34 +914,36 @@ function buildP16(){
const seen = new Set();
const cx = 200, cy = 200;
function draw(){
const R = +slR.value, alpha = +slA.value;
labR.textContent = R; labA.textContent = alpha;
const R = +slR.value, alpha = +slA.value; // R в условных единицах (2..6)
labR.textContent = R.toFixed(1); labA.textContent = alpha;
const K = 25; // px / ед.
const Rpx = R * K;
let s = '';
// основная окружность (контур)
s += '<circle cx="'+cx+'" cy="'+cy+'" r="'+R+'" fill="rgba(34,211,238,.06)" stroke="#94a3b8" stroke-width="1.4"/>';
s += '<circle cx="'+cx+'" cy="'+cy+'" r="'+Rpx.toFixed(2)+'" fill="rgba(34,211,238,.06)" stroke="#94a3b8" stroke-width="1.4"/>';
// сектор — рисуем path
if(alpha > 0){
if(alpha >= 360){
// целый круг
s += '<circle cx="'+cx+'" cy="'+cy+'" r="'+R+'" fill="rgba(8,145,178,.30)" stroke="#0e7490" stroke-width="2"/>';
s += '<circle cx="'+cx+'" cy="'+cy+'" r="'+Rpx.toFixed(2)+'" fill="rgba(8,145,178,.30)" stroke="#0e7490" stroke-width="2"/>';
} else {
const rad = alpha * Math.PI / 180;
const x1 = cx + R, y1 = cy;
const x2 = cx + R*Math.cos(rad), y2 = cy + R*Math.sin(rad);
const x1 = cx + Rpx, y1 = cy;
const x2 = cx + Rpx*Math.cos(rad), y2 = cy + Rpx*Math.sin(rad);
const large = alpha > 180 ? 1 : 0;
s += '<path d="M '+cx+' '+cy+' L '+x1+' '+y1+' A '+R+' '+R+' 0 '+large+' 1 '+x2.toFixed(2)+' '+y2.toFixed(2)+' Z" fill="rgba(8,145,178,.30)" stroke="#0e7490" stroke-width="2"/>';
s += '<path d="M '+cx+' '+cy+' L '+x1.toFixed(2)+' '+y1+' A '+Rpx.toFixed(2)+' '+Rpx.toFixed(2)+' 0 '+large+' 1 '+x2.toFixed(2)+' '+y2.toFixed(2)+' Z" fill="rgba(8,145,178,.30)" stroke="#0e7490" stroke-width="2"/>';
}
}
// радиус (на 0°)
s += '<line x1="'+cx+'" y1="'+cy+'" x2="'+(cx+R)+'" y2="'+cy+'" stroke="#0891b2" stroke-width="2.2"/>';
s += '<line x1="'+cx+'" y1="'+cy+'" x2="'+(cx+Rpx).toFixed(2)+'" y2="'+cy+'" stroke="#0891b2" stroke-width="2.2"/>';
// радиус на углу alpha
const ax = cx + R*Math.cos(alpha*Math.PI/180), ay = cy + R*Math.sin(alpha*Math.PI/180);
const ax = cx + Rpx*Math.cos(alpha*Math.PI/180), ay = cy + Rpx*Math.sin(alpha*Math.PI/180);
s += '<line x1="'+cx+'" y1="'+cy+'" x2="'+ax.toFixed(2)+'" y2="'+ay.toFixed(2)+'" stroke="#0891b2" stroke-width="2.2"/>';
// центр O
s += '<circle cx="'+cx+'" cy="'+cy+'" r="3.5" fill="#0f172a"/>';
s += '<text x="'+(cx-10)+'" y="'+(cy-8)+'" text-anchor="end" font-family="Inter,sans-serif" font-size="14" font-weight="700" fill="#0f172a">O</text>';
// подпись R
s += '<text x="'+(cx + R/2)+'" y="'+(cy-6)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#0891b2">R</text>';
s += '<text x="'+(cx + Rpx/2).toFixed(2)+'" y="'+(cy-6)+'" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#0891b2">R='+R.toFixed(1)+'</text>';
svg.innerHTML = s;
const PI = Math.PI;
const C = 2*PI*R;
@@ -947,7 +951,7 @@ function buildP16(){
const L = PI*R*alpha/180;
const Ssec = PI*R*R*alpha/360;
out.innerHTML =
'$R = '+R+'$,&nbsp; $\\alpha = '+alpha+'^\\circ$<br>'
'$R = '+R.toFixed(1)+'$,&nbsp; $\\alpha = '+alpha+'^\\circ$<br>'
+'$C = 2\\pi R \\approx '+C.toFixed(2)+'$,&nbsp; $S_{\\text{круга}} = \\pi R^2 \\approx '+Scircle.toFixed(2)+'$<br>'
+'$\\ell_{\\text{дуги}} = \\dfrac{\\pi R \\alpha}{180} \\approx '+L.toFixed(2)+'$,&nbsp; $S_{\\text{сектора}} = \\dfrac{\\pi R^2 \\alpha}{360} \\approx '+Ssec.toFixed(2)+'$';
renderMath(out);
@@ -1247,8 +1251,11 @@ function buildP13(){
s += '<text x="'+((cx+Mx)/2+8).toFixed(2)+'" y="'+((cy+My)/2-4).toFixed(2)+'" font-family="JetBrains Mono,monospace" font-size="12" font-weight="700" fill="#10b981">r</text>';
svg.innerHTML = s;
const beta = 180*(n-2)/n;
// R = 130 px ↔ R = 10 ед., K = 13 px/ед.
const K = 13;
const Ru = R / K, ru = r / K;
out.innerHTML = '$n = '+n+'$,&nbsp; $\\beta = \\dfrac{180^\\circ \\cdot '+(n-2)+'}{'+n+'} = '+beta.toFixed(2)+'^\\circ$<br>'
+ '$R = '+R+'$ px,&nbsp; $r = R\\cos\\dfrac{180^\\circ}{'+n+'} \\approx '+r.toFixed(2)+'$ px';
+ '$R = '+Ru.toFixed(2)+'$,&nbsp; $r = R\\cos\\dfrac{180^\\circ}{'+n+'} \\approx '+ru.toFixed(2)+'$';
renderMath(out);
seen.add(n);
if(seen.size >= 5 && !seen.has('_done')){ addXp(10,'p13-iv1'); bumpProgress('p13', 15); seen.add('_done'); }
@@ -1410,7 +1417,7 @@ function buildP14(){
<div class="wg-help">Меняй $n$ и $R$ — увидишь, как пересчитываются сторона $a$, апофема $r$, периметр $P$ и площадь $S$.</div>
<div class="sliders">
<label>Число сторон $n$<b id="p14-iv1-nval">6</b><input type="range" id="p14-iv1-n" min="3" max="12" step="1" value="6"></label>
<label>Радиус $R$<b id="p14-iv1-rval">120</b><input type="range" id="p14-iv1-r" min="50" max="150" step="1" value="120"></label>
<label>Радиус $R$<b id="p14-iv1-rval">6</b><input type="range" id="p14-iv1-r" min="2" max="8" step="0.1" value="6"></label>
</div>
<div style="background:var(--card);border-radius:10px;padding:10px;overflow-x:auto">
<svg id="p14-iv1-svg" viewBox="0 0 360 360" style="width:100%;max-width:420px;min-width:280px;height:auto;display:block;margin:0 auto"></svg>
@@ -1488,16 +1495,19 @@ function buildP14(){
const cx = 180, cy = 180;
function draw(){
const n = +sln.value;
const R = +slr.value;
labN.textContent = n; labR.textContent = R;
const R = +slr.value; // R в условных единицах (2..8)
labN.textContent = n; labR.textContent = R.toFixed(1);
const K = 18; // px на единицу
const Rpx = R * K;
const a = 2*R*Math.sin(Math.PI/n);
const r = R*Math.cos(Math.PI/n);
const P = n*a;
const S = 0.5*n*R*R*Math.sin(2*Math.PI/n);
const pts = regularPoly(cx, cy, R, n);
const pts = regularPoly(cx, cy, Rpx, n);
const rPx = Rpx*Math.cos(Math.PI/n);
let s = '';
s += '<circle cx="'+cx+'" cy="'+cy+'" r="'+R+'" fill="none" stroke="#94a3b8" stroke-width="1.4" stroke-dasharray="4 4"/>';
s += '<circle cx="'+cx+'" cy="'+cy+'" r="'+r.toFixed(2)+'" fill="none" stroke="#10b981" stroke-width="1.4" stroke-dasharray="3 3"/>';
s += '<circle cx="'+cx+'" cy="'+cy+'" r="'+Rpx.toFixed(2)+'" fill="none" stroke="#94a3b8" stroke-width="1.4" stroke-dasharray="4 4"/>';
s += '<circle cx="'+cx+'" cy="'+cy+'" r="'+rPx.toFixed(2)+'" fill="none" stroke="#10b981" stroke-width="1.4" stroke-dasharray="3 3"/>';
s += '<polygon points="'+pts.map(p=>p.x.toFixed(2)+','+p.y.toFixed(2)).join(' ')+'" fill="rgba(8,145,178,.10)" stroke="#0e7490" stroke-width="2.2" stroke-linejoin="round"/>';
const P0 = pts[0], P1 = pts[1];
s += '<line x1="'+P0.x.toFixed(2)+'" y1="'+P0.y.toFixed(2)+'" x2="'+P1.x.toFixed(2)+'" y2="'+P1.y.toFixed(2)+'" stroke="#dc2626" stroke-width="3.6"/>';
@@ -1506,8 +1516,7 @@ function buildP14(){
s += '<line x1="'+cx+'" y1="'+cy+'" x2="'+Mx.toFixed(2)+'" y2="'+My.toFixed(2)+'" stroke="#10b981" stroke-width="2" stroke-dasharray="5 3"/>';
s += '<circle cx="'+cx+'" cy="'+cy+'" r="3.5" fill="#0f172a"/>';
svg.innerHTML = s;
// в "условных" единицах (px), но показываем как есть
out.innerHTML = '$n = '+n+'$,&nbsp; $R = '+R+'$<br>'
out.innerHTML = '$n = '+n+'$,&nbsp; $R = '+R.toFixed(1)+'$<br>'
+ '$a = 2R\\sin\\dfrac{180^\\circ}{'+n+'} \\approx '+a.toFixed(2)+'$,&nbsp; $r = R\\cos\\dfrac{180^\\circ}{'+n+'} \\approx '+r.toFixed(2)+'$<br>'
+ '$P = n a \\approx '+P.toFixed(2)+'$,&nbsp; $S = \\tfrac{1}{2} n R^2 \\sin\\dfrac{360^\\circ}{'+n+'} \\approx '+S.toFixed(2)+'$';
renderMath(out);