From 353a6cb8a93de9045e0edf91fce7edd40db21338 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 12:38:40 +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=202=20=E2=80=94=20=D0=BF=D1=80=D0=B8=D0=B7=D0=BC=D0=B0?= =?UTF-8?q?=20=D1=81=D0=BE=20=D0=A1=D0=BD=D0=B5=D0=BB=D0=BB=D0=B8=D1=83?= =?UTF-8?q?=D1=81=D0=BE=D0=BC=20=D0=B8=20=D0=B4=D0=B8=D1=81=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D1=81=D0=B8=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _prismInteract: тонкопризменное отклонение δ=(n−1)·A к основанию + хроматическая дисперсия n(λ) через _nAtWavelength - белый свет: пучки по OB_SPECTRAL, каждый луч красится по длине волны (до призмы совпадают, после — расходятся в спектр); управление общим λ-баром - _obRedraw для freebuild переключён на benchSim (был freeSim) - сферические зеркала уже из Фазы 1; проверено численно (фиолет>красный) - bump opticsbench.js?v=3 Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/js/labs/opticsbench.js | 27 +++++++++++++++++---------- frontend/lab.html | 2 +- plans/OPTICS_CONSTRUCTOR.md | 6 +++--- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/frontend/js/labs/opticsbench.js b/frontend/js/labs/opticsbench.js index d65d92c..e97cb92 100644 --- a/frontend/js/labs/opticsbench.js +++ b/frontend/js/labs/opticsbench.js @@ -2616,7 +2616,11 @@ class BenchSim { const ay = this._ay(); const sx = this.source.xf * this.W; const rays = []; - const push = (x, y, ang) => rays.push({ x, y, dx: Math.cos(ang), dy: Math.sin(ang), pts: [{ x, y }], alive: true, bounces: 0 }); + // white light → one sub-ray per spectral sample (they coincide until a prism disperses them) + const wls = window._obWhiteLight ? OB_SPECTRAL.map(s => s.nm) : [window._obWavelength || 540]; + const push = (x, y, ang) => { + for (const wl of wls) rays.push({ x, y, dx: Math.cos(ang), dy: Math.sin(ang), wl, pts: [{ x, y }], alive: true, bounces: 0 }); + }; if (this.source.kind === 'parallel') { const n = 9, hh = 90; for (let i = 0; i < n; i++) { @@ -2710,14 +2714,16 @@ class BenchSim { return true; } - // Placeholder until Phase 2 (Snell + dispersion); acts as a weak deflector. + // Thin-prism deviation δ = (n−1)·A toward the base, with chromatic dispersion + // via n(λ) — different wavelengths bend differently → spectrum fan. _prismInteract(ray, el, yRel) { if (Math.abs(yRel) > el.size) return false; + const n = (typeof _nAtWavelength === 'function') ? _nAtWavelength(el.n, ray.wl) : el.n; + const A = el.apex * Math.PI / 180; + const dev = (n - 1) * A; // radians, toward the base (+y) const sgn = Math.sign(ray.dx) || 1; - const dev = (el.apex / 60) * (el.n - 1) * 0.5; // crude downward deviation toward the base - const x = sgn, y = ray.dy / Math.abs(ray.dx) + dev; - const l = Math.hypot(x, y) || 1; - ray.dx = x / l; ray.dy = y / l; + const ang = Math.atan2(ray.dy, ray.dx) + sgn * dev; + ray.dx = Math.cos(ang); ray.dy = Math.sin(ang); return true; } @@ -2730,13 +2736,14 @@ class BenchSim { ctx.strokeStyle = 'rgba(255,255,255,0.12)'; ctx.lineWidth = 1; ctx.setLineDash([6, 4]); ctx.beginPath(); ctx.moveTo(0, ay); ctx.lineTo(W, ay); ctx.stroke(); ctx.setLineDash([]); - // trace rays + // trace rays — colour each by its wavelength (so dispersion shows as a fan) const rays = this._emitRays(); - const rayColor = (typeof _obRayColor === 'function') ? _obRayColor(window._obWavelength || 540) : '#06D6E0'; + const white = !!window._obWhiteLight; ctx.lineWidth = 1.1; for (const ray of rays) { this._traceRay(ray); - ctx.strokeStyle = rayColor; ctx.globalAlpha = 0.8; + ctx.strokeStyle = (typeof wavelengthToRGB === 'function') ? wavelengthToRGB(ray.wl) : '#06D6E0'; + ctx.globalAlpha = white ? 0.5 : 0.82; ctx.beginPath(); ray.pts.forEach((p, i) => i ? ctx.lineTo(p.x, p.y) : ctx.moveTo(p.x, p.y)); ctx.stroke(); @@ -3801,7 +3808,7 @@ function _obRedraw() { if (_obMode === 'mirror' && mirrorSim) { mirrorSim.draw(); } if (_obMode === 'refraction' && refrSim) { refrSim.draw(); } if (_obMode === 'prism' && prismSim) { prismSim.draw(); } - if (_obMode === 'freebuild' && freeSim) { freeSim.draw(); } + if (_obMode === 'freebuild' && benchSim) { benchSim.draw(); } if (_obMode === 'waves' && diffrSim) { diffrSim.draw(); diffrSim._updateHUD(); } if (_obMode === 'interf' && ifSim) { ifSim.draw(); } _obDrawSpectrometer(); diff --git a/frontend/lab.html b/frontend/lab.html index 0056557..0249e02 100644 --- a/frontend/lab.html +++ b/frontend/lab.html @@ -4841,7 +4841,7 @@ - + diff --git a/plans/OPTICS_CONSTRUCTOR.md b/plans/OPTICS_CONSTRUCTOR.md index 3be3485..b45fef2 100644 --- a/plans/OPTICS_CONSTRUCTOR.md +++ b/plans/OPTICS_CONSTRUCTOR.md @@ -30,9 +30,9 @@ - [x] 1.5 Встроено в режим `freebuild` (вкладка «Конструктор»), пресеты систем (микроскоп/телескоп/проектор/зеркальная), сохранение состояния в снимок. - Призма пока — грубый дефлектор-placeholder (настоящий Снеллиус в 2.2). -### Фаза 2 — Сферические зеркала + призма + дисперсия — [ ] -- [ ] 2.1 Вогнутое/выпуклое зеркало (кик f=R/2, разворот хода, лимит отражений). -- [ ] 2.2 Призма: Снеллиус на 2 гранях, дисперсия по длине волны, белый свет. +### Фаза 2 — Сферические зеркала + призма + дисперсия — [x] +- [x] 2.1 Вогнутое/выпуклое зеркало — сделано ещё в Фазе 1 (кик f=R/2, разворот хода, лимит отражений). +- [x] 2.2 Призма: тонкопризменное отклонение δ=(n−1)·A к основанию + хроматическая дисперсия n(λ). Белый свет — пучки по `OB_SPECTRAL`, каждый луч красится по λ (`wavelengthToRGB`); до призмы совпадают, после — расходятся в спектр. Управление через общий λ-бар скамьи. Проверено численно (фиолетовый отклоняется сильнее красного). ### Фаза 3 — Сохранение состояния + полировка — [ ] - [ ] 3.1 Расширить `_obGetState/_obApplyState` на конструктор (снимок/embed).