fix(opticsbench): источник — вертикальное положение + фикс плавающего FX
- источник можно двигать по вертикали: слайдер «Положение ↕» (для любого типа) + вертикальное перетаскивание; эмиссия/отрисовка/хит-тест через _sy() - фикс бага: FX-вспышка рисовалась на ay−source.h даже для точечного источника (h оставалась 70) → «звезда» улетала вверх; теперь FX привязан к реальной точке источника (поднятая вершина только у стрелки-предмета) - object «Высота» → «Размер стрелки» (чтобы не путать с вертик. положением) - bump opticsbench.js?v=8 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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 = '<div class="gp-section-title" style="margin:4px 0 6px">Источник</div>';
|
||||
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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -4847,7 +4847,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=7"></script>
|
||||
<script src="/js/labs/opticsbench.js?v=8"></script>
|
||||
<script src="/js/labs/isoprocess.js"></script>
|
||||
<script src="/js/labs/titration.js"></script>
|
||||
<script src="/js/labs/probability.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user