feat: Phase 3 планиметрии — дуги углов, маркер 90°, инструменты foot/circumcircle/incircle
- _drawAngleMeasures(): реальные дуги через биссектрису (midAngle±halfSpread), маркер правого угла квадратом при |angle-90°|<2° - gIncircle(): функция вписанной окружности треугольника - GeoEngine: поддержка constr='foot' (точка), constr='circumcircle'/'incircle' (окружность) в _dependsOn и recompute; cascadeDelete через derived circles - _drawCircle(): обработка derived=true (circumcircle/incircle) — dashed + cx/cy/r - getStats(): исправлена статистика для производных окружностей - constructions учитывает derivedCircles - lab.html: 3 новые кнопки (Основание, Описанная, Вписанная) + хинты Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+201
-22
@@ -58,6 +58,18 @@ function gCircumcircle(A, B, C) {
|
|||||||
return { cx, cy, r: gDist({x:cx,y:cy}, A) };
|
return { cx, cy, r: gDist({x:cx,y:cy}, A) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Вписанная окружность треугольника */
|
||||||
|
function gIncircle(A, B, C) {
|
||||||
|
const a = gDist(B, C), b = gDist(A, C), c = gDist(A, B);
|
||||||
|
const s = a + b + c;
|
||||||
|
if (s < 1e-12) return null;
|
||||||
|
const cx = (a*A.x + b*B.x + c*C.x) / s;
|
||||||
|
const cy = (a*A.y + b*B.y + c*C.y) / s;
|
||||||
|
const area = gPolygonArea([A, B, C]);
|
||||||
|
const r = area / (s / 2);
|
||||||
|
return { cx, cy, r };
|
||||||
|
}
|
||||||
|
|
||||||
/** Угол ABC (в градусах, вершина B) */
|
/** Угол ABC (в градусах, вершина B) */
|
||||||
function gAngleDeg(A, B, C) {
|
function gAngleDeg(A, B, C) {
|
||||||
const v1 = gSub(A, B), v2 = gSub(C, B);
|
const v1 = gSub(A, B), v2 = gSub(C, B);
|
||||||
@@ -149,14 +161,21 @@ class GeoEngine {
|
|||||||
switch (obj.type) {
|
switch (obj.type) {
|
||||||
case 'segment': case 'line': case 'ray':
|
case 'segment': case 'line': case 'ray':
|
||||||
return obj.p1Id === id || obj.p2Id === id;
|
return obj.p1Id === id || obj.p2Id === id;
|
||||||
case 'circle':
|
|
||||||
return obj.centerId === id || obj.edgeId === id;
|
|
||||||
case 'polygon':
|
case 'polygon':
|
||||||
return obj.pointIds.includes(id);
|
return obj.pointIds.includes(id);
|
||||||
|
case 'circle':
|
||||||
|
if (obj.derived) return obj.ptA === id || obj.ptB === id || obj.ptC === id;
|
||||||
|
return obj.centerId === id || obj.edgeId === id;
|
||||||
case 'point':
|
case 'point':
|
||||||
if (!obj.derived) return false;
|
if (!obj.derived) return false;
|
||||||
if (obj.constr === 'midpoint') return obj.srcA === id || obj.srcB === id;
|
if (obj.constr === 'midpoint') return obj.srcA === id || obj.srcB === id;
|
||||||
if (obj.constr === 'intersect') return obj.src1 === id || obj.src2 === id;
|
if (obj.constr === 'intersect') return obj.src1 === id || obj.src2 === id;
|
||||||
|
if (obj.constr === 'foot') {
|
||||||
|
if (obj.srcPt === id || obj.srcLine === id) return true;
|
||||||
|
// Если srcLine — обычная прямая, зависим и от её точек
|
||||||
|
const sl = this._objects.get(obj.srcLine);
|
||||||
|
return !!(sl && (sl.p1Id === id || sl.p2Id === id));
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
case 'derived_line':
|
case 'derived_line':
|
||||||
switch (obj.constr) {
|
switch (obj.constr) {
|
||||||
@@ -189,6 +208,34 @@ class GeoEngine {
|
|||||||
if (pt) { obj.x = pt.x; obj.y = pt.y; obj.valid = true; }
|
if (pt) { obj.x = pt.x; obj.y = pt.y; obj.valid = true; }
|
||||||
else obj.valid = false;
|
else obj.valid = false;
|
||||||
}
|
}
|
||||||
|
} else if (obj.constr === 'foot') {
|
||||||
|
const srcPt = _g(obj.srcPt);
|
||||||
|
const sl = _g(obj.srcLine);
|
||||||
|
if (!srcPt || !sl) return;
|
||||||
|
let L1, L2;
|
||||||
|
if (sl.type === 'derived_line') {
|
||||||
|
L1 = { x: sl.ptX, y: sl.ptY };
|
||||||
|
L2 = { x: sl.ptX + sl.dirX, y: sl.ptY + sl.dirY };
|
||||||
|
} else {
|
||||||
|
L1 = _g(sl.p1Id); L2 = _g(sl.p2Id);
|
||||||
|
}
|
||||||
|
if (L1 && L2) {
|
||||||
|
const f = gFoot({ x: srcPt.x, y: srcPt.y }, L1, L2);
|
||||||
|
obj.x = f.x; obj.y = f.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (obj.type === 'circle' && obj.derived) {
|
||||||
|
const pA = _g(obj.ptA), pB = _g(obj.ptB), pC = _g(obj.ptC);
|
||||||
|
if (!pA || !pB || !pC) return;
|
||||||
|
const A = { x: pA.x, y: pA.y }, B = { x: pB.x, y: pB.y }, C = { x: pC.x, y: pC.y };
|
||||||
|
if (obj.constr === 'circumcircle') {
|
||||||
|
const cc = gCircumcircle(A, B, C);
|
||||||
|
if (cc) { obj.cx = cc.cx; obj.cy = cc.cy; obj.r = cc.r; obj.valid = true; }
|
||||||
|
else { obj.valid = false; }
|
||||||
|
} else if (obj.constr === 'incircle') {
|
||||||
|
const ic = gIncircle(A, B, C);
|
||||||
|
if (ic) { obj.cx = ic.cx; obj.cy = ic.cy; obj.r = ic.r; obj.valid = true; }
|
||||||
|
else { obj.valid = false; }
|
||||||
}
|
}
|
||||||
} else if (obj.type === 'derived_line') {
|
} else if (obj.type === 'derived_line') {
|
||||||
if (obj.constr === 'perpbisect') {
|
if (obj.constr === 'perpbisect') {
|
||||||
@@ -731,23 +778,33 @@ class GeoSim {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_drawCircle(ctx, obj) {
|
_drawCircle(ctx, obj) {
|
||||||
const c = this._p(obj.centerId), e = this._p(obj.edgeId);
|
let cx, cy, r;
|
||||||
if (!c || !e) return;
|
const isDerived = obj.derived && (obj.constr === 'circumcircle' || obj.constr === 'incircle');
|
||||||
const r = gDist(c, e);
|
if (isDerived) {
|
||||||
|
if (!obj.valid) return;
|
||||||
|
const c = this.vp.toCanvas(obj.cx, obj.cy);
|
||||||
|
cx = c.x; cy = c.y;
|
||||||
|
r = this.vp.toCanvasDist(obj.r);
|
||||||
|
} else {
|
||||||
|
const c = this._p(obj.centerId), e = this._p(obj.edgeId);
|
||||||
|
if (!c || !e) return;
|
||||||
|
cx = c.x; cy = c.y;
|
||||||
|
r = gDist(c, e);
|
||||||
|
}
|
||||||
const col = obj.style?.color || '#FFB347';
|
const col = obj.style?.color || '#FFB347';
|
||||||
const sel = this._isSelected(obj);
|
const sel = this._isSelected(obj);
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.strokeStyle = col;
|
ctx.strokeStyle = col;
|
||||||
ctx.lineWidth = obj.style?.width || (sel ? 2.5 : 2);
|
ctx.lineWidth = obj.style?.width || (sel ? 2.5 : 2);
|
||||||
ctx.shadowColor = col; ctx.shadowBlur = sel ? 10 : 4;
|
ctx.shadowColor = col; ctx.shadowBlur = sel ? 10 : 4;
|
||||||
ctx.globalAlpha = 0.9;
|
ctx.globalAlpha = isDerived ? 0.75 : 0.9;
|
||||||
// Лёгкая заливка
|
if (isDerived) ctx.setLineDash([7, 4]);
|
||||||
ctx.fillStyle = obj.style?.fillColor || `rgba(255,179,71,0.05)`;
|
ctx.fillStyle = obj.style?.fillColor || `rgba(255,179,71,0.04)`;
|
||||||
ctx.beginPath(); ctx.arc(c.x, c.y, r, 0, Math.PI*2);
|
ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI*2);
|
||||||
ctx.fill(); ctx.stroke();
|
ctx.fill(); ctx.stroke();
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
if (this.showLabels && obj.label)
|
if (this.showLabels && obj.label)
|
||||||
this._drawObjLabel(ctx, obj.label, { x: c.x, y: c.y - r - 8 }, col);
|
this._drawObjLabel(ctx, obj.label, { x: cx, y: cy - r - 8 }, col);
|
||||||
}
|
}
|
||||||
|
|
||||||
_drawObjLabel(ctx, label, pos, col) {
|
_drawObjLabel(ctx, label, pos, col) {
|
||||||
@@ -796,22 +853,71 @@ class GeoSim {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_drawAngleMeasures(ctx) {
|
_drawAngleMeasures(ctx) {
|
||||||
|
const ARC_R = 22; // радиус дуги угла, пикселей
|
||||||
|
const SQ_SZ = 11; // сторона квадрата прямого угла, пикселей
|
||||||
|
const LBL_D = 14; // отступ подписи от дуги/квадрата, пикселей
|
||||||
|
|
||||||
for (const poly of this.eng.byType('polygon')) {
|
for (const poly of this.eng.byType('polygon')) {
|
||||||
const ids = poly.pointIds;
|
const ids = poly.pointIds;
|
||||||
const n = ids.length;
|
const n = ids.length;
|
||||||
for (let i = 0; i < n; i++) {
|
for (let i = 0; i < n; i++) {
|
||||||
const A = this._mpt(ids[(i-1+n)%n]);
|
const A = this._mpt(ids[(i-1+n)%n]);
|
||||||
const B = this._mpt(ids[i]);
|
const B = this._mpt(ids[i]);
|
||||||
const C = this._mpt(ids[(i+1)%n]);
|
const C = this._mpt(ids[(i+1)%n]);
|
||||||
if (!A||!B||!C) continue;
|
if (!A || !B || !C) continue;
|
||||||
|
|
||||||
const angle = gAngleDeg(A, B, C);
|
const angle = gAngleDeg(A, B, C);
|
||||||
|
const Apx = this.vp.toCanvas(A.x, A.y);
|
||||||
const Bpx = this.vp.toCanvas(B.x, B.y);
|
const Bpx = this.vp.toCanvas(B.x, B.y);
|
||||||
|
const Cpx = this.vp.toCanvas(C.x, C.y);
|
||||||
|
const col = poly.style?.color || '#FFE066';
|
||||||
|
|
||||||
|
// Единичные векторы B→A и B→C в пиксельных координатах
|
||||||
|
const dAx = Apx.x - Bpx.x, dAy = Apx.y - Bpx.y;
|
||||||
|
const dCx = Cpx.x - Bpx.x, dCy = Cpx.y - Bpx.y;
|
||||||
|
const lenA = Math.hypot(dAx, dAy), lenC = Math.hypot(dCx, dCy);
|
||||||
|
if (lenA < 1e-4 || lenC < 1e-4) continue;
|
||||||
|
const uAx = dAx/lenA, uAy = dAy/lenA;
|
||||||
|
const uCx = dCx/lenC, uCy = dCy/lenC;
|
||||||
|
|
||||||
|
// Биссектриса угла в пиксельных координатах
|
||||||
|
const bisX = uAx + uCx, bisY = uAy + uCy;
|
||||||
|
const bisLen = Math.hypot(bisX, bisY);
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.font = '10px Manrope,sans-serif';
|
ctx.strokeStyle = col;
|
||||||
ctx.fillStyle = '#FFE066';
|
ctx.lineWidth = 1.5;
|
||||||
ctx.textAlign = 'center';
|
|
||||||
ctx.shadowColor = 'rgba(0,0,0,0.9)'; ctx.shadowBlur = 3;
|
if (Math.abs(angle - 90) < 2) {
|
||||||
ctx.fillText(angle.toFixed(1)+'°', Bpx.x, Bpx.y + 18);
|
// Прямой угол — маленький квадрат
|
||||||
|
ctx.globalAlpha = 0.8;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(Bpx.x + uAx*SQ_SZ, Bpx.y + uAy*SQ_SZ);
|
||||||
|
ctx.lineTo(Bpx.x + uAx*SQ_SZ + uCx*SQ_SZ, Bpx.y + uAy*SQ_SZ + uCy*SQ_SZ);
|
||||||
|
ctx.lineTo(Bpx.x + uCx*SQ_SZ, Bpx.y + uCy*SQ_SZ);
|
||||||
|
ctx.stroke();
|
||||||
|
} else if (bisLen > 1e-6) {
|
||||||
|
// Дуга угла — от midAngle-halfSpread до midAngle+halfSpread через биссектрису
|
||||||
|
const midAngle = Math.atan2(bisY, bisX);
|
||||||
|
const halfSpread = Math.acos(Math.max(-1, Math.min(1, uAx*uCx + uAy*uCy))) / 2;
|
||||||
|
ctx.globalAlpha = 0.7;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(Bpx.x, Bpx.y, ARC_R, midAngle - halfSpread, midAngle + halfSpread, false);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Подпись угла вдоль биссектрисы
|
||||||
|
if (bisLen > 1e-6) {
|
||||||
|
const ldist = (Math.abs(angle - 90) < 2 ? SQ_SZ : ARC_R) + LBL_D;
|
||||||
|
const bx = bisX / bisLen, by = bisY / bisLen;
|
||||||
|
ctx.font = '10px Manrope,sans-serif';
|
||||||
|
ctx.fillStyle = col;
|
||||||
|
ctx.globalAlpha = 0.9;
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.shadowColor = 'rgba(0,0,0,0.9)'; ctx.shadowBlur = 3;
|
||||||
|
ctx.fillText(angle.toFixed(1) + '°', Bpx.x + bx*ldist, Bpx.y + by*ldist + 3);
|
||||||
|
}
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1324,6 +1430,75 @@ class GeoSim {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ══ Phase 3: foot, circumcircle, incircle ══ */
|
||||||
|
|
||||||
|
case 'foot': {
|
||||||
|
if (!this._pendingLineRef) {
|
||||||
|
// Первый клик: выбрать прямую
|
||||||
|
const hit = this._hitTestLine(px, py);
|
||||||
|
if (hit) {
|
||||||
|
this._pendingLineRef = hit;
|
||||||
|
if (this.onHintChange) this.onHintChange('foot', 2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Второй клик: выбрать точку, с которой опускаем перпендикуляр
|
||||||
|
this._pushUndo();
|
||||||
|
const srcPt = this._ensurePoint(snapped);
|
||||||
|
const sl = this._pendingLineRef;
|
||||||
|
let L1, L2;
|
||||||
|
if (sl.type === 'derived_line') {
|
||||||
|
L1 = { x: sl.ptX, y: sl.ptY };
|
||||||
|
L2 = { x: sl.ptX + sl.dirX, y: sl.ptY + sl.dirY };
|
||||||
|
} else {
|
||||||
|
L1 = this._mpt(sl.p1Id); L2 = this._mpt(sl.p2Id);
|
||||||
|
}
|
||||||
|
if (L1 && L2) {
|
||||||
|
const f = gFoot({ x: srcPt.x, y: srcPt.y }, L1, L2);
|
||||||
|
const lbl = this._nextLabel();
|
||||||
|
this.eng.add({
|
||||||
|
type: 'point', derived: true, constr: 'foot',
|
||||||
|
srcLine: sl.id, srcPt: srcPt.id,
|
||||||
|
x: f.x, y: f.y,
|
||||||
|
label: lbl, style: { color: '#4ADE80', size: 4 }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._pendingLineRef = null; this._pending = []; this._preview = null;
|
||||||
|
if (this.onUpdate) this.onUpdate(this.getStats());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'circumcircle':
|
||||||
|
case 'incircle': {
|
||||||
|
this._pending.push(snapped);
|
||||||
|
if (this._pending.length === 3) {
|
||||||
|
this._pushUndo();
|
||||||
|
const pts = this._pending.map(p => this._ensurePoint(p));
|
||||||
|
const A = { x: pts[0].x, y: pts[0].y };
|
||||||
|
const B = { x: pts[1].x, y: pts[1].y };
|
||||||
|
const C = { x: pts[2].x, y: pts[2].y };
|
||||||
|
const cc = this.tool === 'circumcircle'
|
||||||
|
? gCircumcircle(A, B, C)
|
||||||
|
: gIncircle(A, B, C);
|
||||||
|
if (cc) {
|
||||||
|
const sameConstr = this.eng.byType('circle').filter(c => c.constr === this.tool).length;
|
||||||
|
const isCircum = this.tool === 'circumcircle';
|
||||||
|
this.eng.add({
|
||||||
|
type: 'circle', derived: true, constr: this.tool,
|
||||||
|
ptA: pts[0].id, ptB: pts[1].id, ptC: pts[2].id,
|
||||||
|
cx: cc.cx, cy: cc.cy, r: cc.r, valid: true,
|
||||||
|
label: isCircum
|
||||||
|
? (sameConstr ? 'C' + (sameConstr+1) : 'C₁')
|
||||||
|
: (sameConstr ? 'I' + (sameConstr+1) : 'I₁'),
|
||||||
|
style: { color: isCircum ? '#38BDF8' : '#34D399' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._pending = []; this._preview = null;
|
||||||
|
if (this.onUpdate) this.onUpdate(this.getStats());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
@@ -1478,7 +1653,8 @@ class GeoSim {
|
|||||||
const segs = this.eng.byType('segment').length + this.eng.byType('polygon').reduce((s,p)=>s+p.pointIds.length,0);
|
const segs = this.eng.byType('segment').length + this.eng.byType('polygon').reduce((s,p)=>s+p.pointIds.length,0);
|
||||||
const circs= this.eng.byType('circle').length;
|
const circs= this.eng.byType('circle').length;
|
||||||
const polys= this.eng.byType('polygon').length;
|
const polys= this.eng.byType('polygon').length;
|
||||||
const constructions = this.eng.byType('derived_line').length + derivedPts;
|
const derivedCircles = this.eng.byType('circle').filter(c => c.derived).length;
|
||||||
|
const constructions = this.eng.byType('derived_line').length + derivedPts + derivedCircles;
|
||||||
|
|
||||||
// Статистика для выбранного объекта
|
// Статистика для выбранного объекта
|
||||||
let sel = null;
|
let sel = null;
|
||||||
@@ -1488,11 +1664,14 @@ class GeoSim {
|
|||||||
const m1 = this._mpt(obj.p1Id), m2 = this._mpt(obj.p2Id);
|
const m1 = this._mpt(obj.p1Id), m2 = this._mpt(obj.p2Id);
|
||||||
if (m1&&m2) sel = { type:'segment', len: gDist(m1,m2).toFixed(3), mid: gMid(m1,m2) };
|
if (m1&&m2) sel = { type:'segment', len: gDist(m1,m2).toFixed(3), mid: gMid(m1,m2) };
|
||||||
} else if (obj.type === 'circle') {
|
} else if (obj.type === 'circle') {
|
||||||
const mc = this._mpt(obj.centerId), me = this._mpt(obj.edgeId);
|
let r = null;
|
||||||
if (mc&&me) {
|
if (obj.derived && (obj.constr === 'circumcircle' || obj.constr === 'incircle')) {
|
||||||
const r = gDist(mc,me);
|
if (obj.valid) r = obj.r;
|
||||||
sel = { type:'circle', r:r.toFixed(3), perimeter:(2*Math.PI*r).toFixed(3), area:(Math.PI*r*r).toFixed(3) };
|
} else {
|
||||||
|
const mc = this._mpt(obj.centerId), me = this._mpt(obj.edgeId);
|
||||||
|
if (mc && me) r = gDist(mc, me);
|
||||||
}
|
}
|
||||||
|
if (r != null) sel = { type:'circle', r:r.toFixed(3), perimeter:(2*Math.PI*r).toFixed(3), area:(Math.PI*r*r).toFixed(3) };
|
||||||
} else if (obj.type === 'polygon') {
|
} else if (obj.type === 'polygon') {
|
||||||
const pts2 = obj.pointIds.map(id=>this._mpt(id)).filter(Boolean);
|
const pts2 = obj.pointIds.map(id=>this._mpt(id)).filter(Boolean);
|
||||||
if (pts2.length >= 3) {
|
if (pts2.length >= 3) {
|
||||||
|
|||||||
@@ -3826,6 +3826,18 @@
|
|||||||
<svg viewBox="0 0 24 24" fill="none"><line x1="4" y1="20" x2="20" y2="4" stroke-width="2"/><line x1="4" y1="4" x2="20" y2="20" stroke-width="2"/><circle cx="12" cy="12" r="3.5" fill="currentColor"/></svg>
|
<svg viewBox="0 0 24 24" fill="none"><line x1="4" y1="20" x2="20" y2="4" stroke-width="2"/><line x1="4" y1="4" x2="20" y2="20" stroke-width="2"/><circle cx="12" cy="12" r="3.5" fill="currentColor"/></svg>
|
||||||
Пересеч.
|
Пересеч.
|
||||||
</button>
|
</button>
|
||||||
|
<button id="geo-btn-foot" class="geo-tool-btn" onclick="geoSetTool('foot',this)" title="Основание перпендикуляра — клик на прямую, затем на точку">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none"><line x1="3" y1="18" x2="21" y2="18" stroke-width="2"/><line x1="12" y1="18" x2="12" y2="4" stroke-width="1.5" stroke-dasharray="3,2"/><path d="M12 18 L15 18 L15 15" stroke-width="1.5" fill="none"/><circle cx="12" cy="4" r="2.5" fill="currentColor"/></svg>
|
||||||
|
Основание
|
||||||
|
</button>
|
||||||
|
<button id="geo-btn-circumcircle" class="geo-tool-btn geo-tool-wide" onclick="geoSetTool('circumcircle',this)" title="Описанная окружность — 3 точки треугольника">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke-width="1.5" stroke-dasharray="4,3"/><polygon points="6,18 18,18 12,6" stroke-width="1.5" fill="none"/></svg>
|
||||||
|
Описанная ☉
|
||||||
|
</button>
|
||||||
|
<button id="geo-btn-incircle" class="geo-tool-btn geo-tool-wide" onclick="geoSetTool('incircle',this)" title="Вписанная окружность — 3 точки треугольника">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none"><polygon points="4,20 20,20 12,4" stroke-width="1.5" fill="none"/><circle cx="12" cy="15" r="5" stroke-width="1.5" stroke-dasharray="4,3"/></svg>
|
||||||
|
Вписанная ○
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Display options -->
|
<!-- Display options -->
|
||||||
@@ -5313,6 +5325,9 @@
|
|||||||
parallel: 'Сначала кликни на прямую/отрезок, затем на точку',
|
parallel: 'Сначала кликни на прямую/отрезок, затем на точку',
|
||||||
perpendicular:'Сначала кликни на прямую/отрезок, затем на точку',
|
perpendicular:'Сначала кликни на прямую/отрезок, затем на точку',
|
||||||
intersect: 'Кликни на первую прямую, затем на вторую',
|
intersect: 'Кликни на первую прямую, затем на вторую',
|
||||||
|
foot: 'Сначала кликни на прямую/отрезок',
|
||||||
|
circumcircle: 'Кликни 3 точки треугольника — получи описанную окружность',
|
||||||
|
incircle: 'Кликни 3 точки треугольника — получи вписанную окружность',
|
||||||
};
|
};
|
||||||
|
|
||||||
function geoSetTool(name, btnEl) {
|
function geoSetTool(name, btnEl) {
|
||||||
@@ -5331,6 +5346,7 @@
|
|||||||
parallel: 'Теперь кликни на точку — через неё проведём прямую',
|
parallel: 'Теперь кликни на точку — через неё проведём прямую',
|
||||||
perpendicular: 'Теперь кликни на точку — через неё проведём перпендикуляр',
|
perpendicular: 'Теперь кликни на точку — через неё проведём перпендикуляр',
|
||||||
intersect: 'Теперь кликни на вторую прямую',
|
intersect: 'Теперь кликни на вторую прямую',
|
||||||
|
foot: 'Теперь кликни на точку — найдём основание перпендикуляра',
|
||||||
};
|
};
|
||||||
hint.textContent = phase2hints[name] || _GEO_HINTS[name] || '';
|
hint.textContent = phase2hints[name] || _GEO_HINTS[name] || '';
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user