feat(opticsbench): конструктор Фаза 3 — изображение на экране + экспорт PNG
- _drawScreenHits: светящиеся пятна (additive) в точках попадания лучей на экран, по длине волны — видно формирование изображения и спектр - benchExportPng + кнопка «Снимок PNG»; подсказка про λ/белый свет - bump opticsbench.js?v=4 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user