From a97896d293004a7f992631b0cc96192ee83cc95b Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 13:21:30 +0300 Subject: [PATCH] =?UTF-8?q?fix(opticsbench):=20=D0=B8=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D1=87=D0=BD=D0=B8=D0=BA=20=E2=80=94=20=D0=B2=D0=B5=D1=80=D1=82?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B5=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20+=20=D1=84=D0=B8?= =?UTF-8?q?=D0=BA=D1=81=20=D0=BF=D0=BB=D0=B0=D0=B2=D0=B0=D1=8E=D1=89=D0=B5?= =?UTF-8?q?=D0=B3=D0=BE=20FX?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - источник можно двигать по вертикали: слайдер «Положение ↕» (для любого типа) + вертикальное перетаскивание; эмиссия/отрисовка/хит-тест через _sy() - фикс бага: FX-вспышка рисовалась на ay−source.h даже для точечного источника (h оставалась 70) → «звезда» улетала вверх; теперь FX привязан к реальной точке источника (поднятая вершина только у стрелки-предмета) - object «Высота» → «Размер стрелки» (чтобы не путать с вертик. положением) - bump opticsbench.js?v=8 Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/js/labs/opticsbench.js | 31 ++++++++++++++++++++----------- frontend/lab.html | 2 +- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/frontend/js/labs/opticsbench.js b/frontend/js/labs/opticsbench.js index 21a0b4e..e072c2b 100644 --- a/frontend/js/labs/opticsbench.js +++ b/frontend/js/labs/opticsbench.js @@ -2545,7 +2545,7 @@ class BenchSim { this._drag = null; this._nextId = 1; // source: object arrow by default. `ang` (deg) aims point/single/laser/parallel. - this.source = { kind: 'object', xf: 0.07, h: 70, spread: 0.32, rays: 9, ang: 0 }; + this.source = { kind: 'object', xf: 0.07, yf: 0, h: 70, spread: 0.32, rays: 9, ang: 0 }; // elements along the bench, positioned by x-fraction; centred on the axis this.elements = [ this._mk('lens', { xf: 0.40, f: 130, ap: 95 }), @@ -2612,10 +2612,11 @@ class BenchSim { /* ── geometry helpers ── */ _ex(el) { return el.xf * this.W; } _ay() { return this.H / 2; } + _sy() { return this._ay() + (this.source.yf || 0) * (this.H / 2 - 14); } // source y (vertical position) /* Emit the initial rays from the source. */ _emitRays() { - const ay = this._ay(); + const ay = this._sy(); // emission height respects the source vertical position const sx = this.source.xf * this.W; const rays = []; // white light → one sub-ray per spectral sample (they coincide until a prism disperses them) @@ -2795,7 +2796,9 @@ class BenchSim { this._drawScreenHits(ctx, rays); if (typeof _drawOBFXLayer === 'function') { - _drawOBFXLayer(ctx, 'freebuild', { srcX: this.source.xf * W, srcY: ay - (this.source.h || 0) }); + // FX anchored at the actual source point (only the object arrow has a raised tip) + const fxY = this._sy() - (this.source.kind === 'object' ? this.source.h : 0); + _drawOBFXLayer(ctx, 'freebuild', { srcX: this.source.xf * W, srcY: fxY }); } } @@ -2819,7 +2822,8 @@ class BenchSim { ctx.restore(); } - _drawSource(ctx, ay) { + _drawSource(ctx, _ayIgnored) { + const ay = this._sy(); // draw at the source vertical position const sx = this.source.xf * this.W; const aim = (this.source.ang || 0) * Math.PI / 180; ctx.save(); @@ -2938,11 +2942,12 @@ class BenchSim { }; const hit = (mx, my) => { const ay = this._ay(); + // source first (it can sit off-axis), grab around its actual vertical position + const sx = this.source.xf * this.W, sy = this._sy(); + if (Math.abs(mx - sx) < 16 && Math.abs(my - sy) < 70) return { kind: 'src' }; for (const el of this.elements) { if (Math.abs(mx - this._ex(el)) < 14 && Math.abs(my - ay) < 120) return { kind: 'el', id: el.id }; } - const sx = this.source.xf * this.W; - if (Math.abs(mx - sx) < 16 && Math.abs(my - ay) < 120) return { kind: 'src' }; return null; }; on(cv, 'pointerdown', e => { @@ -2957,10 +2962,14 @@ class BenchSim { }); on(cv, 'pointermove', e => { if (!this._drag) { const { mx, my } = pos(e); cv.style.cursor = hit(mx, my) ? 'grab' : 'default'; return; } - const { mx } = pos(e); + const { mx, my } = pos(e); const xf = Math.max(0.02, Math.min(0.98, mx / this.W)); - if (this._drag.kind === 'src') this.source.xf = xf; - else { const el = this.elements.find(x => x.id === this._drag.id); if (el) el.xf = xf; } + if (this._drag.kind === 'src') { + this.source.xf = xf; + // vertical drag too → move the source up/down off the axis + const yf = (my - this._ay()) / (this.H / 2 - 14); + this.source.yf = Math.max(-0.95, Math.min(0.95, yf)); + } else { const el = this.elements.find(x => x.id === this._drag.id); if (el) el.xf = xf; } this._redraw(); // position drag → redraw canvas, keep inspector intact }); on(cv, 'pointerup', e => { this._drag = null; try { cv.releasePointerCapture(e.pointerId); } catch (_) {} }); @@ -4444,9 +4453,9 @@ function _benchPropsHTML() { let h = '
Источник
'; h += _benchBtnRow(['object:Предмет', 'point:Точка', 'parallel:Параллель', 'single:Луч', 'laser:Лазер'], k => s.kind === k, k => "benchSourceKind('" + k + "')"); - if (s.kind === 'object') h += _benchCtl('Высота', 0, 'h', 20, 120, 2, s.h, true); + h += _benchCtl('Положение ↕', 0, 'yf', -0.9, 0.9, 0.02, +(s.yf || 0).toFixed(2), true); // vertical position (any kind) + if (s.kind === 'object') h += _benchCtl('Размер стрелки', 0, 'h', 20, 120, 2, s.h, true); if (s.kind === 'point') h += _benchCtl('Раствор', 0, 'spread', 0.1, 0.6, 0.02, s.spread, true); - if (s.kind !== 'object' && s.kind !== 'parallel') {} // no spread for single/laser if (s.kind !== 'object') h += _benchCtl('Угол°', 0, 'ang', -60, 60, 1, s.ang || 0, true); return h; } diff --git a/frontend/lab.html b/frontend/lab.html index 986c930..2f20142 100644 --- a/frontend/lab.html +++ b/frontend/lab.html @@ -4847,7 +4847,7 @@ - +