feat(opticsbench): конструктор Фаза 2 — призма со Снеллиусом и дисперсией

- _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) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-30 12:38:40 +03:00
parent 832efc0907
commit 353a6cb8a9
3 changed files with 21 additions and 14 deletions
+17 -10
View File
@@ -2616,7 +2616,11 @@ class BenchSim {
const ay = this._ay(); const ay = this._ay();
const sx = this.source.xf * this.W; const sx = this.source.xf * this.W;
const rays = []; 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') { if (this.source.kind === 'parallel') {
const n = 9, hh = 90; const n = 9, hh = 90;
for (let i = 0; i < n; i++) { for (let i = 0; i < n; i++) {
@@ -2710,14 +2714,16 @@ class BenchSim {
return true; return true;
} }
// Placeholder until Phase 2 (Snell + dispersion); acts as a weak deflector. // Thin-prism deviation δ = (n1)·A toward the base, with chromatic dispersion
// via n(λ) — different wavelengths bend differently → spectrum fan.
_prismInteract(ray, el, yRel) { _prismInteract(ray, el, yRel) {
if (Math.abs(yRel) > el.size) return false; 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 sgn = Math.sign(ray.dx) || 1;
const dev = (el.apex / 60) * (el.n - 1) * 0.5; // crude downward deviation toward the base const ang = Math.atan2(ray.dy, ray.dx) + sgn * dev;
const x = sgn, y = ray.dy / Math.abs(ray.dx) + dev; ray.dx = Math.cos(ang); ray.dy = Math.sin(ang);
const l = Math.hypot(x, y) || 1;
ray.dx = x / l; ray.dy = y / l;
return true; return true;
} }
@@ -2730,13 +2736,14 @@ class BenchSim {
ctx.strokeStyle = 'rgba(255,255,255,0.12)'; ctx.lineWidth = 1; ctx.setLineDash([6, 4]); 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([]); 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 rays = this._emitRays();
const rayColor = (typeof _obRayColor === 'function') ? _obRayColor(window._obWavelength || 540) : '#06D6E0'; const white = !!window._obWhiteLight;
ctx.lineWidth = 1.1; ctx.lineWidth = 1.1;
for (const ray of rays) { for (const ray of rays) {
this._traceRay(ray); 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(); ctx.beginPath();
ray.pts.forEach((p, i) => i ? ctx.lineTo(p.x, p.y) : ctx.moveTo(p.x, p.y)); ray.pts.forEach((p, i) => i ? ctx.lineTo(p.x, p.y) : ctx.moveTo(p.x, p.y));
ctx.stroke(); ctx.stroke();
@@ -3801,7 +3808,7 @@ function _obRedraw() {
if (_obMode === 'mirror' && mirrorSim) { mirrorSim.draw(); } if (_obMode === 'mirror' && mirrorSim) { mirrorSim.draw(); }
if (_obMode === 'refraction' && refrSim) { refrSim.draw(); } if (_obMode === 'refraction' && refrSim) { refrSim.draw(); }
if (_obMode === 'prism' && prismSim) { prismSim.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 === 'waves' && diffrSim) { diffrSim.draw(); diffrSim._updateHUD(); }
if (_obMode === 'interf' && ifSim) { ifSim.draw(); } if (_obMode === 'interf' && ifSim) { ifSim.draw(); }
_obDrawSpectrometer(); _obDrawSpectrometer();
+1 -1
View File
@@ -4841,7 +4841,7 @@
<script src="/js/labs/graphtransform.js"></script> <script src="/js/labs/graphtransform.js"></script>
<script src="/js/labs/pendulum.js"></script> <script src="/js/labs/pendulum.js"></script>
<script src="/js/labs/equilibrium.js"></script> <script src="/js/labs/equilibrium.js"></script>
<script src="/js/labs/opticsbench.js?v=2"></script> <script src="/js/labs/opticsbench.js?v=3"></script>
<script src="/js/labs/isoprocess.js"></script> <script src="/js/labs/isoprocess.js"></script>
<script src="/js/labs/titration.js"></script> <script src="/js/labs/titration.js"></script>
<script src="/js/labs/probability.js"></script> <script src="/js/labs/probability.js"></script>
+3 -3
View File
@@ -30,9 +30,9 @@
- [x] 1.5 Встроено в режим `freebuild` (вкладка «Конструктор»), пресеты систем (микроскоп/телескоп/проектор/зеркальная), сохранение состояния в снимок. - [x] 1.5 Встроено в режим `freebuild` (вкладка «Конструктор»), пресеты систем (микроскоп/телескоп/проектор/зеркальная), сохранение состояния в снимок.
- Призма пока — грубый дефлектор-placeholder (настоящий Снеллиус в 2.2). - Призма пока — грубый дефлектор-placeholder (настоящий Снеллиус в 2.2).
### Фаза 2 — Сферические зеркала + призма + дисперсия — [ ] ### Фаза 2 — Сферические зеркала + призма + дисперсия — [x]
- [ ] 2.1 Вогнутое/выпуклое зеркало (кик f=R/2, разворот хода, лимит отражений). - [x] 2.1 Вогнутое/выпуклое зеркало — сделано ещё в Фазе 1 (кик f=R/2, разворот хода, лимит отражений).
- [ ] 2.2 Призма: Снеллиус на 2 гранях, дисперсия по длине волны, белый свет. - [x] 2.2 Призма: тонкопризменное отклонение δ=(n−1)·A к основанию + хроматическая дисперсия n(λ). Белый свет — пучки по `OB_SPECTRAL`, каждый луч красится по λ (`wavelengthToRGB`); до призмы совпадают, после — расходятся в спектр. Управление через общий λ-бар скамьи. Проверено численно (фиолетовый отклоняется сильнее красного).
### Фаза 3 — Сохранение состояния + полировка — [ ] ### Фаза 3 — Сохранение состояния + полировка — [ ]
- [ ] 3.1 Расширить `_obGetState/_obApplyState` на конструктор (снимок/embed). - [ ] 3.1 Расширить `_obGetState/_obApplyState` на конструктор (снимок/embed).