feat(opticsbench): конструктор Фаза 3 — изображение на экране + экспорт PNG
- _drawScreenHits: светящиеся пятна (additive) в точках попадания лучей на экран, по длине волны — видно формирование изображения и спектр - benchExportPng + кнопка «Снимок PNG»; подсказка про λ/белый свет - bump opticsbench.js?v=4 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2705,7 +2705,7 @@ class BenchSim {
|
||||
return true;
|
||||
}
|
||||
if (el.type === 'screen') {
|
||||
ray.hitY = ray.y; ray.alive = false; // absorbed, hit recorded
|
||||
ray.hitY = ray.y; ray.hitEl = el.id; ray.alive = false; // absorbed, hit recorded
|
||||
return true;
|
||||
}
|
||||
if (el.type === 'prism') {
|
||||
@@ -2754,11 +2754,34 @@ class BenchSim {
|
||||
this._drawSource(ctx, ay);
|
||||
for (const el of this.elements) this._drawElement(ctx, el, ay);
|
||||
|
||||
// image formed on screens (where rays land)
|
||||
this._drawScreenHits(ctx, rays);
|
||||
|
||||
if (typeof _drawOBFXLayer === 'function') {
|
||||
_drawOBFXLayer(ctx, 'freebuild', { srcX: this.source.xf * W, srcY: ay - (this.source.h || 0) });
|
||||
}
|
||||
}
|
||||
|
||||
// Glowing spots on each screen where rays land → the image becomes visible.
|
||||
_drawScreenHits(ctx, rays) {
|
||||
const screens = this.elements.filter(e => e.type === 'screen');
|
||||
if (!screens.length) return;
|
||||
ctx.save();
|
||||
ctx.globalCompositeOperation = 'lighter'; // additive → overlapping rays brighten
|
||||
for (const sc of screens) {
|
||||
const x = this._ex(sc);
|
||||
for (const r of rays) {
|
||||
if (r.hitEl !== sc.id) continue;
|
||||
const col = (typeof wavelengthToRGB === 'function') ? wavelengthToRGB(r.wl) : '#fff';
|
||||
const g = ctx.createRadialGradient(x, r.hitY, 0, x, r.hitY, 9);
|
||||
g.addColorStop(0, col); g.addColorStop(1, 'rgba(0,0,0,0)');
|
||||
ctx.fillStyle = g; ctx.globalAlpha = 0.5;
|
||||
ctx.beginPath(); ctx.arc(x, r.hitY, 9, 0, Math.PI * 2); ctx.fill();
|
||||
}
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
_drawSource(ctx, ay) {
|
||||
const sx = this.source.xf * this.W;
|
||||
ctx.save();
|
||||
@@ -2880,6 +2903,10 @@ class BenchSim {
|
||||
on(cv, 'pointerup', e => { this._drag = null; try { cv.releasePointerCapture(e.pointerId); } catch (_) {} });
|
||||
}
|
||||
|
||||
exportPng() {
|
||||
try { return this.canvas.toDataURL('image/png'); } catch (_) { return null; }
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this._ro) { this._ro.disconnect(); this._ro = null; }
|
||||
if (this._listeners) { for (const [t, ty, fn, o] of this._listeners) t.removeEventListener(ty, fn, o); this._listeners = []; }
|
||||
@@ -4404,6 +4431,14 @@ function benchClear() {
|
||||
if (!benchSim) return;
|
||||
benchSim.elements = []; benchSim.selectedId = null; benchSim._changed(); _benchUpdateUI();
|
||||
}
|
||||
function benchExportPng() {
|
||||
if (!benchSim) return;
|
||||
const url = benchSim.exportPng();
|
||||
if (!url) return;
|
||||
const a = document.createElement('a');
|
||||
a.href = url; a.download = 'optical-bench.png';
|
||||
document.body.appendChild(a); a.click(); document.body.removeChild(a);
|
||||
}
|
||||
function benchPreset(name) {
|
||||
if (!benchSim) return;
|
||||
const P = {
|
||||
|
||||
+6
-3
@@ -3205,8 +3205,11 @@
|
||||
<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 style="display:flex;gap:4px;margin-bottom:6px">
|
||||
<button class="preset-btn" style="flex:1" onclick="benchClear()">Очистить</button>
|
||||
<button class="preset-btn" style="flex:1" onclick="benchExportPng()">Снимок PNG</button>
|
||||
</div>
|
||||
<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">
|
||||
@@ -4841,7 +4844,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?v=3"></script>
|
||||
<script src="/js/labs/opticsbench.js?v=4"></script>
|
||||
<script src="/js/labs/isoprocess.js"></script>
|
||||
<script src="/js/labs/titration.js"></script>
|
||||
<script src="/js/labs/probability.js"></script>
|
||||
|
||||
@@ -34,10 +34,12 @@
|
||||
- [x] 2.1 Вогнутое/выпуклое зеркало — сделано ещё в Фазе 1 (кик f=R/2, разворот хода, лимит отражений).
|
||||
- [x] 2.2 Призма: тонкопризменное отклонение δ=(n−1)·A к основанию + хроматическая дисперсия n(λ). Белый свет — пучки по `OB_SPECTRAL`, каждый луч красится по λ (`wavelengthToRGB`); до призмы совпадают, после — расходятся в спектр. Управление через общий λ-бар скамьи. Проверено численно (фиолетовый отклоняется сильнее красного).
|
||||
|
||||
### Фаза 3 — Сохранение состояния + полировка — [ ]
|
||||
- [ ] 3.1 Расширить `_obGetState/_obApplyState` на конструктор (снимок/embed).
|
||||
- [ ] 3.2 Пресеты систем (микроскоп, телескоп, глаз, проектор), экспорт PNG.
|
||||
- [ ] 3.3 Полировка: апертурное отсечение, подписи, тач, a11y.
|
||||
### Фаза 3 — Сохранение состояния + полировка — [x]
|
||||
- [x] 3.1 Сохранение состояния конструктора — сделано в Фазе 1 (`_obGetState/_obApplyState` + `bench`).
|
||||
- [x] 3.2 Пресеты систем (микроскоп/телескоп/проектор/зеркальная) — Фаза 1; экспорт PNG (`benchExportPng`, кнопка «Снимок PNG»).
|
||||
- [x] 3.3 Экран ловит изображение: светящиеся пятна (additive `lighter`) в точках попадания лучей, по λ — видно формирование изображения и спектр.
|
||||
|
||||
Бэклог: точная двухгранная призма (Снеллиус на гранях вместо тонкопризменного), апертурное отсечение лучей вне линзы (сейчас проходят прямо), профиль интенсивности на экране, поворот элементов, удаление legacy `FreeBuildSim`.
|
||||
|
||||
---
|
||||
История: создан 2026-05-30.
|
||||
|
||||
Reference in New Issue
Block a user