5 Commits

Author SHA1 Message Date
Maxim Dolgolyov 9547a20875 feat(stereo): B — умные точки (деление m:n, координаты, перетаскивание)
Фаза B раунда «Конструктор» (умные точки для построений).

B1 — деление отрезка m:n: задаёшь m,n, кликаешь 2 точки A,B → точка делит
AB как AM:MB = m:n (t=m/(m+n)), создаётся как точка-построение M,N,K…
B2 — точка по координатам: поля x/y/z + кнопка → addPointAt.
B3 — перетаскивание построенных точек мышью: drag в плоскости, обращённой
к камере (нормаль фиксируется на старте), приоритет над орбитой; снапшот
истории на старте → undo откатывает весь drag. Непараметрично: downstream-
объекты за перетаскиванием не следуют (параметрический граф — бэклог).

- StereoSim: setDivideMode/setDivideRatio (+ ветка в _onConstructClick),
  addPointAt; setDragPointMode/_pickCPointAt/_beginCPointDrag/_rayPlaneHit/
  _dragCPointWithRay/_dragCPointAt/_endCPointDrag; pointer-хендлеры
  (down=начать drag, move=тащить, up=завершить); сброс в setFigure;
  интеграция в _stereoDeactivateTools.
- Панель: блок «Точки» (кнопки Деление/Тащить, поля m:n, поля x,y,z +
  «Точка (x,y,z)»); glue stereoDivideMode/DivideRatio/AddCoordPoint/
  DragPointMode.

Верификация: node --check OK; headless-смоук 25/25 (деление 1:1/1:2/3:1,
координатная точка + отказ NaN, ray∩plane вкл. parallel/behind, drag begin→
move→end с проверкой позиции и снапшота истории + undo, взаимоисключение
режимов, setFigure-сброс, dispose); эмодзи/eval/new Function — 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:28:22 +03:00
Maxim Dolgolyov 24403718bf feat(stereo): C1+C3 — плоскость как сечение + «натуральная величина»
Фаза C раунда «Конструктор» (C2 покрыта Фазой A, C4 отложена).

C1 — любую построенную плоскость можно показать сечением тела: клик по
плоскости в дереве (нормальный режим) → setSectionPlane: заливка
многоугольника + подписи вершин K,L,M… + площадь и периметр в readout-
панели. Удаление плоскости / очистка / смена фигуры сбрасывают сечение.

C3 — «Натуральная величина» сечения (getTrueShape): многоугольник сечения
разворачивается в свою плоскость (ортонормированный базис от нормали) с
сохранением истинных длин → 2D-SVG мини-панель со штриховкой (pattern),
подписями вершин, длинами сторон и S/P. Появляется автоматически при
активном сечении.

- StereoSim: _sectionPlaneId, setSectionPlane, _activeSectionPolygon,
  _sectionVertexLabel, getTrueShape; _drawPlaneObject заливает+подписывает
  активное сечение; getReadout добавляет S/P; getConstructions отдаёт
  sectionId + per-plane section; pickConstructObject в нормальном режиме
  тогглит сечение по плоскости.
- Панель: контейнер #construct-trueshape + подсказка; glue
  _stereoUpdateTrueShape (SVG-рендер) вызывается из _stereoUpdateUI; строки
  плоскостей в дереве всегда кликабельны, тег «(сечение)».

Верификация: node --check OK; headless-смоук 26/26 (квадрат y=2: S=16,P=16;
readout/дерево/тоггл; true-shape длины K,L,M,N=4, площадь=16; сохранение
длин и площади для прямого И наклонного сечения; 2D-shoelace=S; удаление/
очистка/setFigure сбрасывают сечение; dispose); эмодзи/eval/new Function — 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:15:22 +03:00
Maxim Dolgolyov 9382b063aa feat(stereo): A3 — параллели/перпендикуляры + общий undo/redo построений
Фаза A3 раунда «Конструктор». Построения через точку, опираясь на объект:
- lpar: прямая ∥ выбранной прямой;
- lperp: прямая ⟂ выбранной плоскости (вдоль нормали);
- ppar: плоскость ∥ выбранной плоскости;
- pperp: плоскость ⟂ выбранной прямой (= плоскость по точке+нормали,
  через _createPlaneFromPointNormal — мост к Фазе C).
Поток: кнопка op → выбор опоры в дереве → клик точки.

Общий undo/redo конструкторного слоя: JSON-снапшоты _undoStack/_redoStack
(кап 60), хуки _pushHistory в create/remove/clear; Ctrl+Z / Ctrl+Shift+Z /
Ctrl+Y + кнопки «Отменить»/«Вернуть». Видимость объекта — не шаг истории.

- StereoSim: setRelMode/_pickRelRef/_onRelClick/_createPlaneFromPointNormal;
  _snapshot/_pushHistory/_restoreSnapshot/undo/redo/canUndo/canRedo;
  pickConstructObject диспатчит rel/intersect; getConstructions отдаёт
  relMode + selected по опоре; _lastConstructMsg → flash в подсказку.
  Сброс rel/истории в setFigure, очистка в clearConstructions.
- Панель: 4 кнопки (∥/⟂ прямая/плоск.) + «Отменить»/«Вернуть»; интеграция в
  _stereoDeactivateTools; glue stereoRelMode/HistUndo/HistRedo; дерево —
  строки выбираемы и в rel-режиме.

Верификация: node --check OK; headless-смоук 30/30 (4 rel-операции с
проверкой параллельности направлений/нормалей, гард типа опоры, undo/redo
одиночный/многошаговый/redo-сброс/clear-undoable/vis-не-шаг/кап, setFigure-
сброс истории, dispose); эмодзи/eval/new Function — 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:07:43 +03:00
Maxim Dolgolyov abd1af2653 feat(stereo): A2 — пересечения построений + интерактивное дерево объектов
Фаза A2 раунда «Конструктор». Пересечения как list-based операция:
- прямая ∩ плоскость → точка (_cpoints, имена M,N,K…);
- плоскость ∩ плоскость → прямая;
- прямая ∩ прямая → точка либо «скрещиваются»/«параллельны».
Точки-пересечения пикабельны — по ним строятся новые прямые/плоскости.

- StereoSim: setIntersectMode/pickConstructObject (выбор 2 объектов),
  _computeIntersection + _intersectLinePlane/_intersectPlanePlane/
  _intersectLineLine, _createCPoint/_drawCPointObject/_cpointLabel,
  removeConstruction(id)/toggleConstructionVis(id), getConstructions
  переписан в дерево (id/type/hidden/selected/info), _pickConstructPoint
  теперь учитывает точки-пересечения. Сброс в setFigure, очистка/clear.
- Панель: кнопка «Пересечение»; список — интерактивные строки (выбор для
  пересечения, глаз=видимость, ×=удаление) через glue stereoIntersectMode/
  ConstructSelect/ConstructVis/ConstructDelete; интеграция в _stereoDeactivateTools.

Верификация: node --check OK; headless-смоук 34/34 (точная геометрия
line∩plane / plane∩plane / line∩line, параллельные/скрещ. без объекта,
list-pick поток, гард точки, дерево, видимость/удаление/remove-last,
setFigure-сброс, dispose); эмодзи/eval/new Function — 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:59:20 +03:00
Maxim Dolgolyov 53ac45bccd feat(stereo): конструкторное ядро A1 — прямые и плоскости как объекты
Фаза A раунда «Конструктор» (под ученика-самоучку). Прямая по 2 точкам
(имена a,b,c…) и плоскость по 3 точкам (имена α,β,γ…) как именованные
объекты сцены. Плоскость рисует полупрозрачный квад + пунктирную рамку +
сечение тела этой плоскостью (через _sliceByPlane) — сразу осмысленна.

- StereoSim: _lines/_planes (сериализуемые {x,y,z}), _constructGroup,
  setLineMode/setPlaneMode, _onConstructClick, _createLine/_createPlane,
  _rebuildConstructions/_drawLineObject/_drawPlaneObject, removeLast/clear,
  getConstructions (с уравнением плоскости). Сброс в setFigure, очистка в
  dispose, перерисовка подписей в toggleLabels, счётчик в info().
- Панель «Построения» в labs-bodies.html + glue (stereoLineMode/PlaneMode/
  ConstructUndo/Clear, _stereoUpdateConstructList); интеграция в
  _stereoDeactivateTools и _stereoUpdateUI.
- План: Фазы A и C в plans/STEREO_3D_IMPROVEMENT.md.

Верификация: node --check OK; headless-смоук 35/35 (создание/имена/нормаль/
коллинеарность/rebuild/summary/remove-last/clear/click-путь/setFigure-сброс/
dispose); эмодзи/eval/new Function — 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:27:27 +03:00
3 changed files with 1115 additions and 5 deletions
File diff suppressed because it is too large Load Diff
+61
View File
@@ -3672,6 +3672,67 @@
</div>
<div id="points-info" style="font-size:0.65rem;color:rgba(255,255,255,0.4);margin-top:2px"></div>
<!-- ── Построения (прямые / плоскости) ── -->
<div class="gp-section-title" style="margin-top:8px;margin-bottom:6px">Построения</div>
<div class="st-tool-grid">
<button class="st-tool-btn" id="stereo-line-btn" onclick="stereoLineMode(this)" title="Прямая через 2 точки — кликните две вершины или точки">
<svg viewBox="0 0 24 24"><line x1="3" y1="21" x2="21" y2="3"/><circle cx="6" cy="18" r="2" fill="currentColor"/><circle cx="18" cy="6" r="2" fill="currentColor"/></svg>Прямая
</button>
<button class="st-tool-btn" id="stereo-plane-btn" onclick="stereoPlaneMode(this)" title="Плоскость через 3 точки — кликните три вершины или точки">
<svg viewBox="0 0 24 24"><polygon points="3,16 13,20 21,8 11,4" fill="none"/><circle cx="3" cy="16" r="1.8" fill="currentColor"/><circle cx="21" cy="8" r="1.8" fill="currentColor"/><circle cx="11" cy="4" r="1.8" fill="currentColor"/></svg>Плоскость
</button>
<button class="st-tool-btn st-tool-btn-wide" id="stereo-intersect-btn" onclick="stereoIntersectMode(this)" title="Пересечение: выберите 2 объекта в списке (прямая∩плоскость → точка, плоскость∩плоскость → прямая)">
<svg viewBox="0 0 24 24"><line x1="3" y1="7" x2="21" y2="17"/><line x1="3" y1="17" x2="21" y2="7"/><circle cx="12" cy="12" r="2.4" fill="currentColor"/></svg>Пересечение
</button>
</div>
<div class="st-tool-grid" style="margin-top:3px">
<button class="st-tool-btn" id="stereo-rel-lpar-btn" onclick="stereoRelMode('lpar',this)" title="Прямая, параллельная выбранной прямой, через точку">
<svg viewBox="0 0 24 24"><line x1="4" y1="8" x2="20" y2="6"/><line x1="4" y1="18" x2="20" y2="16"/></svg>∥ прямая
</button>
<button class="st-tool-btn" id="stereo-rel-lperp-btn" onclick="stereoRelMode('lperp',this)" title="Прямая, перпендикулярная выбранной плоскости, через точку">
<svg viewBox="0 0 24 24"><ellipse cx="12" cy="17" rx="9" ry="3" fill="none"/><line x1="12" y1="3" x2="12" y2="17"/><path d="M12 14 L15 14 L15 17" fill="none"/></svg>⟂ прямая
</button>
<button class="st-tool-btn" id="stereo-rel-ppar-btn" onclick="stereoRelMode('ppar',this)" title="Плоскость, параллельная выбранной плоскости, через точку">
<svg viewBox="0 0 24 24"><polygon points="3,11 12,14 21,6 12,3" fill="none"/><polygon points="3,18 12,21 21,13 12,10" fill="none"/></svg>∥ плоск.
</button>
<button class="st-tool-btn" id="stereo-rel-pperp-btn" onclick="stereoRelMode('pperp',this)" title="Плоскость, перпендикулярная выбранной прямой, через точку">
<svg viewBox="0 0 24 24"><polygon points="3,14 12,17 21,9 12,6" fill="none"/><line x1="12" y1="2" x2="12" y2="21"/></svg>⟂ плоск.
</button>
</div>
<div class="st-tool-grid" style="margin-top:3px">
<button class="st-tool-btn" id="stereo-divide-btn" onclick="stereoDivideMode(this)" title="Точка, делящая отрезок AB в отношении m:n — задайте m,n и кликните 2 точки">
<svg viewBox="0 0 24 24"><line x1="3" y1="12" x2="21" y2="12"/><circle cx="3" cy="12" r="1.8" fill="currentColor"/><circle cx="21" cy="12" r="1.8" fill="currentColor"/><circle cx="11" cy="12" r="2.6" fill="currentColor"/></svg>Деление
</button>
<button class="st-tool-btn" id="stereo-dragpt-btn" onclick="stereoDragPointMode(this)" title="Перетаскивать построенные точки мышью (в плоскости экрана)">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3" fill="currentColor"/><path d="M12 3v3M12 18v3M3 12h3M18 12h3"/></svg>Тащить
</button>
</div>
<div style="display:flex;align-items:center;gap:5px;margin-top:4px;font-size:.72rem;color:rgba(255,255,255,.55)">
<span>m</span>
<input id="st-div-m" type="number" min="0" step="1" value="1" oninput="stereoDivideRatio()" style="width:40px;background:#1a1030;color:#f0e8ff;border:1px solid rgba(255,255,255,.12);border-radius:6px;padding:3px 4px;font-size:.72rem">
<span>:</span>
<input id="st-div-n" type="number" min="0" step="1" value="1" oninput="stereoDivideRatio()" style="width:40px;background:#1a1030;color:#f0e8ff;border:1px solid rgba(255,255,255,.12);border-radius:6px;padding:3px 4px;font-size:.72rem">
<span>n</span><span style="opacity:.55">(AM:MB)</span>
</div>
<div style="display:flex;align-items:center;gap:4px;margin-top:4px">
<input id="st-pt-x" type="number" step="0.5" value="0" title="x" style="width:42px;background:#1a1030;color:#f0e8ff;border:1px solid rgba(255,255,255,.12);border-radius:6px;padding:3px 4px;font-size:.72rem">
<input id="st-pt-y" type="number" step="0.5" value="0" title="y" style="width:42px;background:#1a1030;color:#f0e8ff;border:1px solid rgba(255,255,255,.12);border-radius:6px;padding:3px 4px;font-size:.72rem">
<input id="st-pt-z" type="number" step="0.5" value="0" title="z" style="width:42px;background:#1a1030;color:#f0e8ff;border:1px solid rgba(255,255,255,.12);border-radius:6px;padding:3px 4px;font-size:.72rem">
<button class="st-action-btn" onclick="stereoAddCoordPoint()" style="flex:1" title="Поставить точку по координатам">Точка (x,y,z)</button>
</div>
<div class="st-action-grid" style="margin-top:3px">
<button class="st-action-btn" onclick="stereoConstructHistUndo()" title="Отменить (Ctrl+Z)">Отменить</button>
<button class="st-action-btn" onclick="stereoConstructHistRedo()" title="Вернуть (Ctrl+Shift+Z)">Вернуть</button>
</div>
<div class="st-action-grid" style="margin-top:3px">
<button class="st-action-btn" onclick="stereoConstructUndo()">Удалить последнее</button>
<button class="st-action-btn" onclick="stereoConstructClear()">Очистить</button>
</div>
<div id="construct-hint" style="font-size:0.63rem;color:rgba(255,255,255,0.38);margin-top:3px;line-height:1.4"></div>
<div style="font-size:0.6rem;color:rgba(255,255,255,0.3);margin-top:2px;line-height:1.35">Клик по плоскости в списке — показать её сечением (заливка, площадь, периметр, натуральная величина).</div>
<div id="construct-list" style="font-size:0.7rem;margin-top:4px;line-height:1.6"></div>
<div id="construct-trueshape" style="display:none;margin-top:6px;background:rgba(6,214,224,0.05);border:1px solid rgba(6,214,224,0.18);border-radius:8px;padding:6px"></div>
<!-- ── Метки рёбер ── -->
<div class="gp-section-title" style="margin-top:8px;margin-bottom:6px">Метки рёбер</div>
<div class="st-tool-grid">
+57 -1
View File
@@ -64,4 +64,60 @@
---
История: создан 2026-05-30. Фаза 6 добавлена 2026-05-30.
## Раунд «Конструктор» (2026-06-17) — упор на ученика-самоучку (песочница)
Цель: превратить отличный **визуализатор** в полноценный **конструктор** для самостоятельных
построений. Приоритеты, выбранные пользователем: **Фаза A (конструкторное ядро)** и
**Фаза C (сечения+)**. A — фундамент C (сечение через прямую+точку, параллельно прямой/плоскости
опираются на объекты-прямые/плоскости).
### Фаза A — Конструкторное ядро
Прямые и плоскости как объекты первого класса + пересечения + параллели/перпендикуляры +
общий undo/redo + дерево именованных объектов.
- [x] A1 — **Объектная модель + базовые построения.** `_lines[]` (имена a,b,c…), `_planes[]`
(имена α,β,γ…), группа `_constructGroup`, сериализуемое хранение `{x,y,z}`. Инструменты
«Прямая по 2 точкам» и «Плоскость по 3 точкам» (пикинг вершин/точек). Плоскость рисует
полупрозрачный квад + пунктирную рамку + **сечение тела этой плоскостью** (через `_sliceByPlane`,
делает плоскость осмысленной сразу). Панель «Построения», список объектов с уравнением плоскости.
- [x] A2 — **Пересечения** (прямая∩плоскость → точка `_cpoints` имена M,N,K…; плоскость∩плоскость
→ прямая; прямая∩прямая → точка или «скрещиваются») — выбор 2 объектов в дереве (`setIntersectMode`/
`pickConstructObject`). **Интерактивное дерево**: видимость (глаз)/удаление (×) по объекту, выбор
для пересечения. Точки-пересечения пикабельны → по ним строятся новые прямые/плоскости.
- [x] A3 — **Параллели/перпендикуляры** через точку (`setRelMode`/`_onRelClick`): `lpar` прямая ∥
прямой; `lperp` прямая ⟂ плоскости; `ppar` плоскость ∥ плоскости; `pperp` плоскость ⟂ прямой
(= «плоскость по точке и нормали» через `_createPlaneFromPointNormal` — мост к Фазе C). Поток:
кнопка op → выбор опоры в дереве → клик точки. **Общий undo/redo** конструкторного слоя (JSON-
снапшот `_undoStack`/`_redoStack`, кап 60; хуки в create/remove/clear; Ctrl+Z / Ctrl+Shift+Z /
Ctrl+Y + кнопки «Отменить»/«Вернуть»). Видимость — не шаг истории (намеренно).
### Фаза B — Умные точки
- [x] B1 — **Деление отрезка m:n** (`setDivideMode`/`setDivideRatio`): задаёшь m,n → кликаешь 2 точки A,B
→ точка делит AB как AM:MB = m:n (`t = m/(m+n)`), создаётся как `_cpoints` (M,N,K…).
- [x] B2 — **Точка по координатам** (`addPointAt(x,y,z)`): поля x/y/z + кнопка → точка-построение.
- [x] B3 — **Перетаскивание точек** (`setDragPointMode`): pointerdown по точке-построению (приоритет над
орбитой) → drag в плоскости, обращённой к камере (нормаль = направление камеры, фикс. на старте);
`_beginCPointDrag`/`_dragCPointWithRay`/`_rayPlaneHit`/`_endCPointDrag`; снапшот истории на старте
(undo откатывает весь drag). Точки-пересечения/деления непараметрические (downstream-объекты
копируют позицию при создании и за перетаскиванием НЕ следуют — параметрический граф = бэклог).
### Фаза C — Сечения+
- [x] C1 — Сечение **плоскостью-объектом** (из Фазы A): клик по плоскости в дереве (нормальный режим)
`setSectionPlane` показывает её заливкой + подписи вершин K,L,M… + площадь/периметр в readout
(`_activeSectionPolygon`, `getReadout`). Удаление/очистка/смена фигуры сбрасывают сечение.
- [x] C2 — **Покрыто Фазой A** (отдельный код не нужен): сечение через прямую+точку = плоскость по
3 точкам (2 с прямой + 1); сечение ∥ плоскости через точку = rel-операция `ppar` → затем клик
как сечение. Дополнительный UI признан избыточным.
- [x] C3 — **«Натуральная величина» сечения** (`getTrueShape`): разворот многоугольника в его
плоскость (ортонормированный базис от нормали) с сохранением истинных длин → 2D-мини-панель
(SVG, штриховка `<pattern>`, подписи вершин, длины сторон, S/P). Проверено сохранение длин и
площади для прямого и наклонного сечений.
- [ ] C4 — Честный конструктивный алгоритм следов с анимацией (из бэклога Ф6) — **отложено**
(крупная отдельная фича; текущий гибрид Ф6 + сечения-объекты Фазы A/C закрывают практику).
---
История: создан 2026-05-30. Фаза 6 добавлена 2026-05-30. Раунд «Конструктор» (Фазы A,C) — 2026-06-17.