feat(opticsbench): конструктор оптических систем — Фаза 1 (общий трассировщик)

Режим «Цепочка линз» → «Конструктор» на базе нового класса BenchSim:
- общий 2D-трассировщик: линза, зеркало (плоск./вогн./выпукл.), диафрагма,
  экран; источники предмет/точка/параллель; лимит отражений
- фокус линзы в x+f и терминация зеркала проверены численно
- динамический инспектор: палитра элементов, список схемы, свойства
  выбранного, удаление; слайдеры перерисовывают только холст (не ломают drag)
- pointer-слушатели на canvas (capture, dispose), выбор/перетаскивание
- пресеты: микроскоп/телескоп/проектор/зеркальная; сохранение состояния
  в снимок (_obGetState/_obApplyState); bump opticsbench.js?v=2
- призма — пока грубый placeholder (Снеллиус/дисперсия в Фазе 2)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-30 12:35:41 +03:00
parent 471171b77c
commit 832efc0907
3 changed files with 537 additions and 28 deletions
+21 -22
View File
@@ -2934,7 +2934,7 @@
<button id="ob-tab-lens" onclick="obSwitchMode('lens')" class="ob-tab active" style="flex:1;padding:8px 0;border:none;background:transparent;color:#ccc;font-size:.78rem;font-weight:600;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s">Тонкая линза</button>
<button id="ob-tab-mirror" onclick="obSwitchMode('mirror')" class="ob-tab" style="flex:1;padding:8px 0;border:none;background:transparent;color:#ccc;font-size:.78rem;font-weight:600;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s">Зеркала</button>
<button id="ob-tab-refraction" onclick="obSwitchMode('refraction')" class="ob-tab" style="flex:1;padding:8px 0;border:none;background:transparent;color:#ccc;font-size:.78rem;font-weight:600;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s">Преломление</button>
<button id="ob-tab-freebuild" onclick="obSwitchMode('freebuild')" class="ob-tab" style="flex:1;padding:8px 0;border:none;background:transparent;color:#ccc;font-size:.78rem;font-weight:600;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s">Цепочка линз</button>
<button id="ob-tab-freebuild" onclick="obSwitchMode('freebuild')" class="ob-tab" style="flex:1;padding:8px 0;border:none;background:transparent;color:#ccc;font-size:.78rem;font-weight:600;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s">Конструктор</button>
<button id="ob-tab-prism" onclick="obSwitchMode('prism')" class="ob-tab" style="flex:1;padding:8px 0;border:none;background:transparent;color:#ccc;font-size:.78rem;font-weight:600;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s">Призма</button>
<button id="ob-tab-interf" onclick="obSwitchMode('interf')" class="ob-tab" style="flex:1;padding:8px 0;border:none;background:transparent;color:#ccc;font-size:.78rem;font-weight:600;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s">Интерференция</button>
<button id="ob-tab-waves" onclick="obSwitchMode('waves')" class="ob-tab" style="flex:1;padding:8px 0;border:none;background:transparent;color:#ccc;font-size:.78rem;font-weight:600;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s">Волны</button>
@@ -3186,28 +3186,27 @@
</div>
</div>
<!-- ── Free-build multi-lens control panel (Agent OB-A3) ── -->
<div id="ob-ctrl-freebuild" class="proj-panel" style="width:220px;gap:0;flex-shrink:0;display:none">
<div class="gp-section-title" style="margin-bottom:8px">Цепочка линз</div>
<div style="display:flex;gap:4px;margin-bottom:10px">
<button class="preset-btn" onclick="freeAddLens()" style="flex:1">+ Линза</button>
<button class="preset-btn" onclick="freeRemoveLens()" style="flex:1">&#8722; Линза</button>
</div>
<div class="proj-slider-row" style="margin-bottom:6px">
<label style="font-size:.78rem;color:#ccc;width:72px">Лин.1 f=<span id="free-lens0-fval" style="color:var(--cyan);font-weight:700">120</span></label>
<input type="range" id="sl-free-f0" min="-300" max="300" step="5" value="120" oninput="freeLensF(0,this.value)" style="flex:1">
</div>
<div class="proj-slider-row" style="margin-bottom:6px">
<label style="font-size:.78rem;color:#ccc;width:72px">Лин.2 f=<span id="free-lens1-fval" style="color:var(--cyan);font-weight:700">90</span></label>
<input type="range" id="sl-free-f1" min="-300" max="300" step="5" value="90" oninput="freeLensF(1,this.value)" style="flex:1">
</div>
<div style="margin-top:8px"></div>
<div class="gp-section-title" style="margin-bottom:6px">Пресеты</div>
<div id="ob-ctrl-freebuild" class="proj-panel" style="width:230px;gap:0;flex-shrink:0;display:none;overflow-y:auto">
<div class="gp-section-title" style="margin-bottom:6px">Добавить элемент</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px">
<button class="preset-btn" onclick="freePreset('microscope')">Микроскоп</button>
<button class="preset-btn" onclick="freePreset('telescope')">Телескоп</button>
<button class="preset-btn" onclick="freePreset('relay')">Рел. цепочка</button>
<button class="preset-btn" style="font-size:.68rem" onclick="benchAdd('lens')">+ Линза</button>
<button class="preset-btn" style="font-size:.68rem" onclick="benchAdd('mirror')">+ Зеркало</button>
<button class="preset-btn" style="font-size:.68rem" onclick="benchAdd('aperture')">+ Диафрагма</button>
<button class="preset-btn" style="font-size:.68rem" onclick="benchAdd('screen')">+ Экран</button>
<button class="preset-btn" style="font-size:.68rem" onclick="benchAdd('prism')">+ Призма</button>
</div>
<div class="pp-hint">Тащи линзы или предмет по оси мышью</div>
<div class="gp-section-title" style="margin-bottom:6px">Схема</div>
<div id="bench-list" style="display:flex;flex-wrap:wrap;gap:3px;margin-bottom:8px"></div>
<div id="bench-props" style="margin-bottom:8px"></div>
<div class="gp-section-title" style="margin-bottom:6px">Системы</div>
<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:6px">
<button class="preset-btn" style="font-size:.68rem" onclick="benchPreset('microscope')">Микроскоп</button>
<button class="preset-btn" style="font-size:.68rem" onclick="benchPreset('telescope')">Телескоп</button>
<button class="preset-btn" style="font-size:.68rem" onclick="benchPreset('projector')">Проектор</button>
<button class="preset-btn" style="font-size:.68rem" onclick="benchPreset('folded')">Зеркальная</button>
</div>
<button class="preset-btn" style="width:100%;margin-bottom:6px" onclick="benchClear()">Очистить</button>
<div class="pp-hint">Тащи элементы и источник по оси. Клик — выбрать и настроить.</div>
</div>
<!-- ── Interference control panel (Agent C) ── -->
<div id="ob-ctrl-interf" class="proj-panel" style="width:240px;gap:0;flex-shrink:0;display:none">
@@ -4842,7 +4841,7 @@
<script src="/js/labs/graphtransform.js"></script>
<script src="/js/labs/pendulum.js"></script>
<script src="/js/labs/equilibrium.js"></script>
<script src="/js/labs/opticsbench.js"></script>
<script src="/js/labs/opticsbench.js?v=2"></script>
<script src="/js/labs/isoprocess.js"></script>
<script src="/js/labs/titration.js"></script>
<script src="/js/labs/probability.js"></script>