From 1c7d8e9d959cbb64cf3b93ba591dcdb51a20f6d8 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 12:40:37 +0300 Subject: [PATCH] =?UTF-8?q?feat(opticsbench):=20=D0=BA=D0=BE=D0=BD=D1=81?= =?UTF-8?q?=D1=82=D1=80=D1=83=D0=BA=D1=82=D0=BE=D1=80=20=D0=A4=D0=B0=D0=B7?= =?UTF-8?q?=D0=B0=203=20=E2=80=94=20=D0=B8=D0=B7=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BD=D0=B0=20=D1=8D=D0=BA?= =?UTF-8?q?=D1=80=D0=B0=D0=BD=D0=B5=20+=20=D1=8D=D0=BA=D1=81=D0=BF=D0=BE?= =?UTF-8?q?=D1=80=D1=82=20PNG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _drawScreenHits: светящиеся пятна (additive) в точках попадания лучей на экран, по длине волны — видно формирование изображения и спектр - benchExportPng + кнопка «Снимок PNG»; подсказка про λ/белый свет - bump opticsbench.js?v=4 Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/js/labs/opticsbench.js | 37 ++++++++++++++++++++++++++++++++- frontend/lab.html | 9 +++++--- plans/OPTICS_CONSTRUCTOR.md | 10 +++++---- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/frontend/js/labs/opticsbench.js b/frontend/js/labs/opticsbench.js index e97cb92..f77db02 100644 --- a/frontend/js/labs/opticsbench.js +++ b/frontend/js/labs/opticsbench.js @@ -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 = { diff --git a/frontend/lab.html b/frontend/lab.html index 0249e02..f1eaf0e 100644 --- a/frontend/lab.html +++ b/frontend/lab.html @@ -3205,8 +3205,11 @@ - -
Тащи элементы и источник по оси. Клик — выбрать и настроить.
+
+ + +
+
Тащи элементы и источник по оси. Клик — выбрать и настроить. λ и «Белый свет» — сверху.