Files
Learn_System/plans/STEREO_3D_IMPROVEMENT.md
T
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

17 KiB
Raw Blame History

План улучшения симуляции «Стереометрия 3D»

Файлы: frontend/js/labs/stereo.js (StereoSim, ~3720 строк), панель в frontend/lab.html (#sim-stereo), роутинг frontend/js/labs/lab-init.js.

Статус-легенда: [ ] не начато · [~] в работе · [x] готово


Фаза 0 — Производительность и гигиена (быстрый эффект, низкий риск) — ГОТОВО

  • 0.1 Останавливать loop при переключении симуляций. Добавлен _pauseAllSims() в lab-init.js, вызывается в начале openSim() — все rAF-симы (включая стерео) паузятся при переходе. Раньше предыдущий сим рендерил невидимый canvas вечно.
  • 0.2 Pointer/touch-слушатели перенесены с window на renderer.domElement, с pointer-capture для драга вне холста; все слушатели трекаются и снимаются в dispose(). Canvas получил tabindex/role/aria-label.
  • 0.3 Render-on-demand: _invalidate() + dirty-флаг _needsRender; loop засыпает (_rafId=null), просыпается по взаимодействию/изменению сцены. Хук в _clearGroup() покрывает все rebuild/clear; защита от двойного rAF.
  • 0.4 Обработка webglcontextlost/webglcontextrestored (пересборка сцены); метод dispose() (renderer, ResizeObserver, слушатели, текстуры). Бонус: _clearGroup стал рекурсивным — устранена утечка вложенных групп (измерения и т.п.).

Фаза 1 — Камера и навигация — ГОТОВО

  • 1.1 Инерция орбиты (плавное затухание после отпускания) + панорамирование: правая/средняя кнопка или Shift+ЛКМ на десктопе, два пальца на тач. Орбита вокруг сдвигаемого таргета (_panOffset). (zoom-to-cursor отложен — pan покрывает рецентрирование; перенесён в бэклог Фазы 2.)
  • 1.2 Overlay-тулбар в правом верхнем углу viewport: сброс вида + пресеты ракурса (Изо / Спереди / Сбоку / Сверху). Пресет «держит» вид (спин выключается).
  • 1.3 Тумблер авто-вращения (с реальным засыпанием loop при выключении), fullscreen (по .graph-canvas-outer), снимок PNG (preserveDrawingBuffer + синхронный рендер → download). a11y: aria-pressed/aria-label на кнопках.

Фаза 2 — Геометрия и пикинг — ГОТОВО

  • 2.1 Аналитические сечения кривых тел _sliceCurvedByNormal(): шар → точная окружность (плоскость∩сфера), цилиндр/конус/усеч.конус → гладкая кривая через точное решение y(θ) для образующей r(y)=r0+k·y. Старый пороговый сэмплинг оставлен как fallback (почти вертикальные плоскости). Проверено численно (диапазон y цилиндра и радиус окружности шара совпадают с формулами).
  • 2.2 Общий хелпер _edgePickNDC() (расстояние точка-отрезок в NDC); _pickNearestEdge и _pickNearestEdgeIdx переведены с «по середине ребра» на корректный пикинг по всей длине.
  • 2.3 HiDPI-метки: _makeTextSprite рендерит на canvas с учётом DPR, корректный аспект по ширине текста, тёмная обводка для читаемости, анизотропная фильтрация. Тип сечения для кривых = «окружность»/«эллипс», вершинные маркеры не плодятся (cap ≤12 точек).

Бэклог: zoom-to-cursor (перенесён из 1.1); SDF-шрифт и пул текстур (текущая резкость достаточна).

Фаза 3 — Педагогика сечений — ГОТОВО (частично, см. бэклог)

  • 3.1 Подписи вершин сечения буквами (K, L, M…) в наклонном/произвольном сечении и сечении-по-3-точкам (для настоящих многоугольников ≤12 вершин). Пошаговый режим сечения-по-3-точкам уже был. (Полное «построение по следам» — в бэклоге, крупная отдельная фича.)
  • 3.2 Точки на произвольной точке грани: _raycastFace() — в режиме «точки» клик по грани (не у ребра/вершины) ставит точку на поверхности. Через произвольное сечение (3 кастомные точки) это даёт плоскость через внутренние точки граней.
  • 3.3 Live-readout overlay (#stereo-readout, низ-слева): тип сечения, площадь S, периметр P, последнее измерение. Обновляется через info().readout при любом изменении сечения/измерения.

Бэклог: полное «построение сечения по следам» (продление рёбер, след на грани); readout углов (двугранный/линия-плоскость) — сейчас угол только в 3D-метке.

Фаза 4 — Визуал — ГОТОВО (см. бэклог)

  • 4.1 Свечение вершин (мягкий additive-ореол без текстур, безопасно с _clearGroup); рёбра контрастнее (opacity 0.9, renderOrder поверх полупрозрачного тела), вершины поверх рёбер. (Подсветка грани по ховеру отложена — текущий centroid-пикинг граней грубоват для плавного hover.)
  • 4.2 Подписи осей X/Y/Z, цвета совпадают с AxesHelper (X крас., Y зел., Z син.).

Бэклог: подсветка грани по ховеру (нужен точный raycast по логическим граням); градиентный/бумажный фон (учесть захват в скриншоте).

Фаза 5 — Интеграция и архитектура — ГОТОВО (без дробления файла, по решению пользователя)

  • [~] 5.1 Дробление stereo.js на модули — отложено по решению пользователя (риск регрессий, не видно пользователю). Остаётся в бэклоге.
  • 5.2 Deep-link фигуры из учебников без изменения общего hash-роутера: openSim('stereo:<figure>') и /lab?stereofig=<figure>#sim/stereo. _openStereo(figureType) применяет фигуру и подсвечивает кнопку. Допустимые: cube, parallelepiped, prism, pyramid, truncpyramid, tetrahedron, octahedron, icosahedron, dodecahedron, cylinder, cone, trunccone, sphere.
  • 5.3 a11y: клавиатурная навигация по сфокусированному canvas — стрелки (орбита), +/− (зум), R/Home (сброс); tabindex/role/aria-label на canvas (Фаза 0), aria-pressed/aria-label на кнопках вида (Фаза 1), aria-live на readout.

Бэклог Фазы 5: модульное дробление файла; deep-link конкретного сечения/инструмента (не только фигуры).

Фаза 6 — Построение сечения «по следам» (метод следов) — ГОТОВО (путь b)

Реализован гибрид: финальный полигон считается надёжно (Фаза 2), а след и вспомогательные точки выводятся аналитически — без риска несходимости конструктивного алгоритма. Scope: тела с основанием (куб, параллелепипед, призма, пирамида, усеч. пирамида, тетраэдр).

  • 6.1 _hasBase() + _traceLine(data) — след = π ∩ плоскость основания (y=0): n.x·x + n.z·z + D = 0; возвращает {p0, dir} или null (плоскость параллельна основанию). Проверено численно (точка следа на плоскости, остаток 0).
  • 6.2 _auxiliaryPoints(polygon) — продление боковых сторон сечения до y=0; точка пересечения лежит ровно на следе (численно dist=0). Сортировка по «дальности продления», берём 2 ближайшие.
  • 6.3 Настоящий пошаговый _drawSection3PStep (заменил бутафорию): 6 подписанных шагов — 3 точки → стороны в одной грани (_sameFace) → след → вспом. точки T₁,T₂ → вершины+замыкание → итог. В step-режиме финальное сечение скрыто до шага 5 (showFull), пиковые линии тоже скрыты. Подписи шагов в #sect3p-hint через _stepCaption. Для тел без основания — деградация к простым шагам с пояснением.
  • bump stereo.js?v=9

Бэклог Фазы 6: «честный» конструктивный алгоритм (шаг по граням через след для поиска каждой новой вершины); анимация перехода между шагами; ветка для плоскости, параллельной основанию.


Раунд «Конструктор» (2026-06-17) — упор на ученика-самоучку (песочница)

Цель: превратить отличный визуализатор в полноценный конструктор для самостоятельных построений. Приоритеты, выбранные пользователем: Фаза A (конструкторное ядро) и Фаза C (сечения+). A — фундамент C (сечение через прямую+точку, параллельно прямой/плоскости опираются на объекты-прямые/плоскости).

Фаза A — Конструкторное ядро

Прямые и плоскости как объекты первого класса + пересечения + параллели/перпендикуляры + общий undo/redo + дерево именованных объектов.

  • A1 — Объектная модель + базовые построения. _lines[] (имена a,b,c…), _planes[] (имена α,β,γ…), группа _constructGroup, сериализуемое хранение {x,y,z}. Инструменты «Прямая по 2 точкам» и «Плоскость по 3 точкам» (пикинг вершин/точек). Плоскость рисует полупрозрачный квад + пунктирную рамку + сечение тела этой плоскостью (через _sliceByPlane, делает плоскость осмысленной сразу). Панель «Построения», список объектов с уравнением плоскости.
  • A2 — Пересечения (прямая∩плоскость → точка _cpoints имена M,N,K…; плоскость∩плоскость → прямая; прямая∩прямая → точка или «скрещиваются») — выбор 2 объектов в дереве (setIntersectMode/ pickConstructObject). Интерактивное дерево: видимость (глаз)/удаление (×) по объекту, выбор для пересечения. Точки-пересечения пикабельны → по ним строятся новые прямые/плоскости.
  • 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 — Умные точки

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

Фаза C — Сечения+

  • C1 — Сечение плоскостью-объектом (из Фазы A): клик по плоскости в дереве (нормальный режим) → setSectionPlane показывает её заливкой + подписи вершин K,L,M… + площадь/периметр в readout (_activeSectionPolygon, getReadout). Удаление/очистка/смена фигуры сбрасывают сечение.
  • C2 — Покрыто Фазой A (отдельный код не нужен): сечение через прямую+точку = плоскость по 3 точкам (2 с прямой + 1); сечение ∥ плоскости через точку = rel-операция ppar → затем клик как сечение. Дополнительный UI признан избыточным.
  • C3 — «Натуральная величина» сечения (getTrueShape): разворот многоугольника в его плоскость (ортонормированный базис от нормали) с сохранением истинных длин → 2D-мини-панель (SVG, штриховка <pattern>, подписи вершин, длины сторон, S/P). Проверено сохранение длин и площади для прямого и наклонного сечений.
  • C4 — Честный конструктивный алгоритм следов с анимацией (из бэклога Ф6) — отложено (крупная отдельная фича; текущий гибрид Ф6 + сечения-объекты Фазы A/C закрывают практику).

История: создан 2026-05-30. Фаза 6 добавлена 2026-05-30. Раунд «Конструктор» (Фазы A,C) — 2026-06-17.