feat(labs): opticsbench round 2 — wave optics + interference + visual depth
Новый режим «Волны» (DiffractionSim, ~400 строк): - Опыт Юнга: I = I₀·cos²(πd·sinθ/λ), полосы Δy = λL/d, концентрические волновые фронты - Однощелевая дифракция: (sin α/α)², центральный максимум 2λ/a, минимумы - Дифракционная решётка: (sin Nψ/N sin ψ)², главные порядки 0,±1,±2,±3, white-light спектр Новый режим «Интерференция» (InterferenceSim): - Кольца Ньютона: top-down + cross-section, r_n = √(nλR) тёмные / √((n+½)λR) светлые - Тонкоплёночная интерференция: integrate I=cos²(π·OPD/λ) по спектру → цвет плёнки пресеты: мыльная плёнка / масло на воде / антибликовое покрытие - Поляризация: P1+P2, закон Малюса I=I₀·cos²θ, анимированные E-векторы, гашение при 90° + связь с Брюстером из refraction mode Визуальные эффекты (5 toggle'ов в <details>): - «Волновые фронты»: перпендикулярные tick-marks вдоль лучей, λ_screen∝1/n в среде - «Туман»: LabFX smoke partikles по всему canvas — лучи видны через дым - «Lens flare»: 6-spike starburst + ghost-reflections + chromatic ring (additive composite) - «Конструкция Гюйгенса»: расходящиеся wavelets на границе для refraction/reflection - «Каустики»: 20-ray trace через линзу с aberration-shifted f_eff → настоящая caustic curve - localStorage persist + zero cost when off THEORY entry расширен 3 секциями (Юнг + однощель + решётка). Каталог теперь: 7 вкладок в оптической скамье (lens / mirror / refraction / freebuild / prism / waves / interf). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1512,3 +1512,47 @@ canvas[data-draggable]:active { cursor: grabbing; }
|
||||
border-radius: 4px;
|
||||
background: #0a0a18;
|
||||
}
|
||||
|
||||
/* ═══ OB_FX Effects panel ═══ */
|
||||
.ob-fx-panel {
|
||||
flex-shrink: 0;
|
||||
background: #0d0d1e;
|
||||
border-bottom: 1px solid #1e1e32;
|
||||
}
|
||||
.ob-fx-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 5px 10px;
|
||||
font-size: .72rem;
|
||||
font-weight: 700;
|
||||
color: #888;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
user-select: none;
|
||||
}
|
||||
.ob-fx-summary::-webkit-details-marker { display: none; }
|
||||
.ob-fx-summary:hover { color: var(--cyan); }
|
||||
.ob-fx-summary .ic { fill: currentColor; opacity: 0.7; flex-shrink: 0; }
|
||||
.ob-fx-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px 14px;
|
||||
padding: 6px 12px 8px;
|
||||
}
|
||||
.ob-fx-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-size: .72rem;
|
||||
color: #bbb;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ob-fx-label:hover { color: #fff; }
|
||||
.ob-fx-label input[type=checkbox] {
|
||||
accent-color: var(--cyan);
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -510,6 +510,9 @@
|
||||
{ head: 'Закон Снеллиуса', formula: 'n_1 \\sin\\theta_1 = n_2 \\sin\\theta_2', text: 'Угол преломления зависит от соотношения показателей преломления двух сред.' },
|
||||
{ head: 'Полное внутреннее отражение', formula: '\\theta_c = \\arcsin\\frac{n_2}{n_1}', text: 'При n₁ > n₂ и θ₁ > θc — свет полностью отражается.' },
|
||||
{ head: 'Показатель преломления', formula: 'n = \\frac{c}{v}', text: 'Воздух ≈ 1.00, вода = 1.33, стекло ≈ 1.5, алмаз = 2.42.' },
|
||||
{ head: 'Волновая оптика — Юнг', formula: 'I(y) = I_0 \\cos^2\\!\\left(\\frac{\\pi d \\sin\\theta}{\\lambda}\\right)', vars: [['d','расстояние между щелями'],['\\lambda','длина волны']], text: 'Расстояние между полосами: Δy = λL/d.' },
|
||||
{ head: 'Однощелевая дифракция', formula: 'I(\\theta) = I_0 \\left(\\frac{\\sin\\alpha}{\\alpha}\\right)^2,\\quad \\alpha = \\frac{\\pi a \\sin\\theta}{\\lambda}', text: 'Угловая ширина центрального максимума: 2λ/a. Минимумы при a·sinθ = nλ.' },
|
||||
{ head: 'Дифракционная решётка', formula: 'd \\sin\\theta = n\\lambda', vars: [['d','период решётки'],['n','порядок'],['\\lambda','длина волны']], text: 'Разрешающая способность R = Nn, где N — число щелей, n — порядок максимума.' },
|
||||
]
|
||||
},
|
||||
thinlens: {
|
||||
|
||||
+1393
-10
File diff suppressed because it is too large
Load Diff
+162
-1
@@ -2533,6 +2533,8 @@
|
||||
<button id="ob-tab-refraction" onclick="obSwitchMode('refraction')" class="ob-tab" style="flex:1;padding:8px 0;border:none;background:transparent;color:#ccc;font-size:.78rem;font-weight:600;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s">Преломление</button>
|
||||
<button id="ob-tab-freebuild" onclick="obSwitchMode('freebuild')" class="ob-tab" style="flex:1;padding:8px 0;border:none;background:transparent;color:#ccc;font-size:.78rem;font-weight:600;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s">Цепочка линз</button>
|
||||
<button id="ob-tab-prism" onclick="obSwitchMode('prism')" class="ob-tab" style="flex:1;padding:8px 0;border:none;background:transparent;color:#ccc;font-size:.78rem;font-weight:600;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s">Призма</button>
|
||||
<button id="ob-tab-interf" onclick="obSwitchMode('interf')" class="ob-tab" style="flex:1;padding:8px 0;border:none;background:transparent;color:#ccc;font-size:.78rem;font-weight:600;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s">Интерференция</button>
|
||||
<button id="ob-tab-waves" onclick="obSwitchMode('waves')" class="ob-tab" style="flex:1;padding:8px 0;border:none;background:transparent;color:#ccc;font-size:.78rem;font-weight:600;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s">Волны</button>
|
||||
</div>
|
||||
<!-- ── Wavelength slider bar (global) ── -->
|
||||
<div id="ob-wavelength-bar" style="display:flex;align-items:center;gap:8px;padding:5px 10px;background:#0c0c1a;border-bottom:1px solid #1a1a30;flex-shrink:0">
|
||||
@@ -2564,6 +2566,35 @@
|
||||
<button class="ob-preset-chip" data-preset="spoon" onclick="obLoadPreset('spoon')" title="Ложка в воде: преломление на границе">Ложка в воде</button>
|
||||
<button class="ob-preset-chip ob-preset-clear" onclick="obClearPreset()" title="Вернуть начальные настройки" style="margin-left:auto">Очистить</button>
|
||||
</div>
|
||||
<!-- ── Эффекты panel (collapsible) ── -->
|
||||
<details id="ob-fx-panel" class="ob-fx-panel">
|
||||
<summary class="ob-fx-summary">
|
||||
<svg class="ic" viewBox="0 0 16 16" width="12" height="12"><path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 2a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3zm0 4.5c1.7 0 3.2.85 4.1 2.15a5 5 0 0 1-8.2 0C4.8 8.35 6.3 7.5 8 7.5z"/></svg>
|
||||
Эффекты
|
||||
</summary>
|
||||
<div class="ob-fx-row">
|
||||
<label class="ob-fx-label">
|
||||
<input type="checkbox" id="obfx-wavefronts" onchange="obFXToggle('wavefronts',this.checked)">
|
||||
Волновые фронты вдоль лучей
|
||||
</label>
|
||||
<label class="ob-fx-label">
|
||||
<input type="checkbox" id="obfx-mist" onchange="obFXToggle('mist',this.checked)">
|
||||
Туман
|
||||
</label>
|
||||
<label class="ob-fx-label">
|
||||
<input type="checkbox" id="obfx-flare" onchange="obFXToggle('flare',this.checked)">
|
||||
Lens flare
|
||||
</label>
|
||||
<label class="ob-fx-label">
|
||||
<input type="checkbox" id="obfx-huygens" onchange="obFXToggle('huygens',this.checked)">
|
||||
Конструкция Гюйгенса
|
||||
</label>
|
||||
<label class="ob-fx-label">
|
||||
<input type="checkbox" id="obfx-caustics" onchange="obFXToggle('caustics',this.checked)">
|
||||
Каустики
|
||||
</label>
|
||||
</div>
|
||||
</details>
|
||||
<!-- Body row: control panels + shared canvas -->
|
||||
<div style="display:flex;flex:1;min-height:0;overflow:hidden">
|
||||
<!-- ── Lens control panel ── -->
|
||||
@@ -2770,13 +2801,134 @@
|
||||
</div>
|
||||
<div class="pp-hint">Тащи линзы или предмет по оси мышью</div>
|
||||
</div>
|
||||
<!-- ── Shared canvas area (all 5 canvases stacked) ── -->
|
||||
<!-- ── Interference control panel (Agent C) ── -->
|
||||
<div id="ob-ctrl-interf" class="proj-panel" style="width:240px;gap:0;flex-shrink:0;display:none">
|
||||
<!-- Sub-mode buttons -->
|
||||
<div class="gp-section-title" style="margin-bottom:6px">Эксперимент</div>
|
||||
<div style="display:flex;gap:3px;margin-bottom:10px;flex-wrap:wrap">
|
||||
<button id="if-sub-newton" class="preset-btn active" onclick="ifSwitchSub('newton')" style="font-size:.7rem;flex:1">Кольца Ньютона</button>
|
||||
<button id="if-sub-thinfilm" class="preset-btn" onclick="ifSwitchSub('thinfilm')" style="font-size:.7rem;flex:1">Тонкая плёнка</button>
|
||||
<button id="if-sub-polarization" class="preset-btn" onclick="ifSwitchSub('polarization')" style="font-size:.7rem;flex:1">Поляризация</button>
|
||||
</div>
|
||||
<!-- Newton rings controls -->
|
||||
<div id="if-ctrl-newton">
|
||||
<div class="gp-section-title" style="margin-bottom:6px">Кольца Ньютона</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:8px">
|
||||
<label style="font-size:.78rem;color:#ccc;width:65px">R = <span id="if-newton-r-val" style="color:var(--cyan);font-weight:700">200</span> мм</label>
|
||||
<input type="range" id="sl-if-newton-r" min="50" max="500" step="10" value="200" oninput="ifNewtParam('R',this.value)" style="flex:1">
|
||||
</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:8px">
|
||||
<label style="font-size:.78rem;color:#ccc;width:65px">n = <span id="if-newton-n-val" style="color:#FFD166;font-weight:700">12</span></label>
|
||||
<input type="range" id="sl-if-newton-n" min="4" max="20" step="1" value="12" oninput="ifNewtParam('nmax',this.value)" style="flex:1">
|
||||
</div>
|
||||
<div class="pp-hint">r_n(dark) = sqrt(n*lambda*R)</div>
|
||||
</div>
|
||||
<!-- Thin film controls -->
|
||||
<div id="if-ctrl-thinfilm" style="display:none">
|
||||
<div class="gp-section-title" style="margin-bottom:6px">Тонкая плёнка</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:8px">
|
||||
<label style="font-size:.78rem;color:#ccc;width:60px">t = <span id="if-tf-t-val" style="color:var(--cyan);font-weight:700">400</span></label>
|
||||
<input type="range" id="sl-if-tf-t" min="50" max="2000" step="10" value="400" oninput="ifThinFilmParam('t',this.value)" style="flex:1">
|
||||
</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:8px">
|
||||
<label style="font-size:.78rem;color:#ccc;width:60px">n = <span id="if-tf-n-val" style="color:#FFD166;font-weight:700">1.33</span></label>
|
||||
<input type="range" id="sl-if-tf-n" min="1.0" max="2.5" step="0.01" value="1.33" oninput="ifThinFilmParam('n',this.value)" style="flex:1">
|
||||
</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:8px">
|
||||
<label style="font-size:.78rem;color:#ccc;width:60px">θ = <span id="if-tf-th-val" style="color:#EF476F;font-weight:700">0</span>°</label>
|
||||
<input type="range" id="sl-if-tf-th" min="0" max="60" step="1" value="0" oninput="ifThinFilmParam('theta',this.value)" style="flex:1">
|
||||
</div>
|
||||
<div class="gp-section-title" style="margin-bottom:4px">Пресет</div>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:3px;margin-bottom:6px">
|
||||
<button class="preset-btn" onclick="ifThinFilmPreset('soap')" style="font-size:.68rem">Мыльная n=1.33</button>
|
||||
<button class="preset-btn" onclick="ifThinFilmPreset('oil')" style="font-size:.68rem">Масло n=1.50</button>
|
||||
<button class="preset-btn" onclick="ifThinFilmPreset('coating')" style="font-size:.68rem">Покрытие n=1.38</button>
|
||||
</div>
|
||||
<div class="pp-hint">2nt·cosθr = (m+0.5)λ — максимум</div>
|
||||
</div>
|
||||
<!-- Polarization controls -->
|
||||
<div id="if-ctrl-polarization" style="display:none">
|
||||
<div class="gp-section-title" style="margin-bottom:6px">Поляризация (Малюс)</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:8px">
|
||||
<label style="font-size:.78rem;color:#ccc;width:60px">θ = <span id="if-pol-th-val" style="color:var(--cyan);font-weight:700">45</span>°</label>
|
||||
<input type="range" id="sl-if-pol-th" min="0" max="90" step="1" value="45" oninput="ifPolParam('theta',this.value)" style="flex:1">
|
||||
</div>
|
||||
<div style="margin-bottom:8px">
|
||||
<label style="font-size:.72rem;color:#ccc;display:flex;align-items:center;gap:6px;cursor:pointer">
|
||||
<input type="radio" name="if-pol-src" value="unpolarized" checked onchange="ifPolSrc(this.value)" style="accent-color:var(--violet)">
|
||||
Неполяризованный
|
||||
</label>
|
||||
<label style="font-size:.72rem;color:#ccc;display:flex;align-items:center;gap:6px;cursor:pointer;margin-top:4px">
|
||||
<input type="radio" name="if-pol-src" value="polarized" onchange="ifPolSrc(this.value)" style="accent-color:var(--violet)">
|
||||
Поляризованный
|
||||
</label>
|
||||
</div>
|
||||
<div class="pp-hint">I = I₀·cos²(θ)</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ── Waves (diffraction) control panel (Agent B1) ── -->
|
||||
<div id="ob-ctrl-waves" class="proj-panel" style="width:230px;gap:0;flex-shrink:0;display:none">
|
||||
<div class="gp-section-title" style="margin-bottom:8px">Опыт</div>
|
||||
<div style="display:flex;gap:3px;margin-bottom:10px">
|
||||
<button id="diffr-sub-young" onclick="diffrSwitchSub('young')" class="preset-btn active" style="flex:1;font-size:.7rem">Юнг</button>
|
||||
<button id="diffr-sub-single" onclick="diffrSwitchSub('single')" class="preset-btn" style="flex:1;font-size:.7rem">Однощелевая</button>
|
||||
<button id="diffr-sub-grating" onclick="diffrSwitchSub('grating')" class="preset-btn" style="flex:1;font-size:.7rem">Решётка</button>
|
||||
</div>
|
||||
<!-- Young sliders -->
|
||||
<div id="ob-diffr-young-params">
|
||||
<div class="gp-section-title" style="margin-bottom:6px">Параметры (Юнг)</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:6px">
|
||||
<label style="font-size:.75rem;color:#ccc;width:72px">d = <span id="diffr-d-young-val" style="color:var(--cyan);font-weight:700">40</span> мкм</label>
|
||||
<input type="range" id="sl-diffr-d-young" min="10" max="100" step="1" value="40"
|
||||
oninput="diffrParam('d_young',this.value)" style="flex:1">
|
||||
</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:8px">
|
||||
<label style="font-size:.75rem;color:#ccc;width:72px">L = <span id="diffr-L-young-val" style="color:var(--violet);font-weight:700">1.0</span> м</label>
|
||||
<input type="range" id="sl-diffr-L-young" min="5" max="20" step="1" value="10"
|
||||
oninput="diffrParam('L_young',this.value/10)" style="flex:1">
|
||||
</div>
|
||||
</div>
|
||||
<!-- Single-slit sliders -->
|
||||
<div id="ob-diffr-single-params" style="display:none">
|
||||
<div class="gp-section-title" style="margin-bottom:6px">Параметры (Однощелевая)</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:8px">
|
||||
<label style="font-size:.75rem;color:#ccc;width:72px">a = <span id="diffr-a-single-val" style="color:#FFD166;font-weight:700">80</span> мкм</label>
|
||||
<input type="range" id="sl-diffr-a-single" min="10" max="200" step="5" value="80"
|
||||
oninput="diffrParam('a_single',this.value)" style="flex:1">
|
||||
</div>
|
||||
</div>
|
||||
<!-- Grating sliders -->
|
||||
<div id="ob-diffr-grating-params" style="display:none">
|
||||
<div class="gp-section-title" style="margin-bottom:6px">Параметры (Решётка)</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:6px">
|
||||
<label style="font-size:.75rem;color:#ccc;width:72px">N = <span id="diffr-N-grating-val" style="color:#7BF5A4;font-weight:700">10</span></label>
|
||||
<input type="range" id="sl-diffr-N-grating" min="2" max="100" step="1" value="10"
|
||||
oninput="diffrParam('N_grating',this.value)" style="flex:1">
|
||||
</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:6px">
|
||||
<label style="font-size:.75rem;color:#ccc;width:72px">d = <span id="diffr-d-grating-val" style="color:var(--cyan);font-weight:700">2.0</span> мкм</label>
|
||||
<input type="range" id="sl-diffr-d-grating" min="5" max="50" step="1" value="20"
|
||||
oninput="diffrParam('d_grating',this.value/10)" style="flex:1">
|
||||
</div>
|
||||
<div class="proj-slider-row" style="margin-bottom:8px">
|
||||
<label style="font-size:.75rem;color:#ccc;width:72px">a = <span id="diffr-a-grating-val" style="color:#FFD166;font-weight:700">0.5</span> мкм</label>
|
||||
<input type="range" id="sl-diffr-a-grating" min="1" max="30" step="1" value="5"
|
||||
oninput="diffrParam('a_grating',this.value/10)" style="flex:1">
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:4px"></div>
|
||||
<button onclick="diffrReset()" style="width:100%;padding:5px 0;border-radius:6px;border:1px solid #333;background:#1a1a2e;color:#888;font-size:.72rem;cursor:pointer;margin-bottom:8px">Сброс</button>
|
||||
<div class="pp-hint">λ берётся из глобального ползунка</div>
|
||||
</div>
|
||||
<!-- ── Shared canvas area (all 6 canvases stacked) ── -->
|
||||
<div class="proj-canvas-outer" style="position:relative;flex:1;min-width:0">
|
||||
<canvas id="ob-lens-canvas" style="position:absolute;top:0;left:0;width:100%;height:100%"></canvas>
|
||||
<canvas id="ob-mirror-canvas" style="position:absolute;top:0;left:0;width:100%;height:100%;display:none"></canvas>
|
||||
<canvas id="ob-refr-canvas" style="position:absolute;top:0;left:0;width:100%;height:100%;display:none"></canvas>
|
||||
<canvas id="ob-prism-canvas" style="position:absolute;top:0;left:0;width:100%;height:100%;display:none"></canvas>
|
||||
<canvas id="ob-free-canvas" style="position:absolute;top:0;left:0;width:100%;height:100%;display:none"></canvas>
|
||||
<canvas id="ob-waves-canvas" style="position:absolute;top:0;left:0;width:100%;height:100%;display:none"></canvas>
|
||||
<canvas id="ob-interf-canvas" style="position:absolute;top:0;left:0;width:100%;height:100%;display:none"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Spectrometer panel (shown only in prism mode) -->
|
||||
@@ -2818,6 +2970,15 @@
|
||||
<div class="pstat"><div class="pstat-label">λ</div><div class="pstat-val" id="prismbar-wl" style="color:#FFFFFF">550 нм</div></div>
|
||||
<div class="pstat"><div class="pstat-label">Режим</div><div class="pstat-val" id="prismbar-mode" style="color:#aaa">Моно</div></div>
|
||||
</div>
|
||||
<div id="ob-stats-interf" style="display:none;flex:1;gap:0">
|
||||
<div class="pstat"><div class="pstat-label">Режим</div><div class="pstat-val" id="ifbar-sub" style="color:var(--cyan)">Кольца</div></div>
|
||||
<div class="pstat"><div class="pstat-label">λ</div><div class="pstat-val" id="ifbar-wl" style="color:#FFFFFF">550 нм</div></div>
|
||||
</div>
|
||||
<div id="ob-stats-waves" style="display:none;flex:1;gap:0">
|
||||
<div class="pstat"><div class="pstat-label">Опыт</div><div class="pstat-val" id="diffbar-sub" style="color:var(--cyan)">Юнг</div></div>
|
||||
<div class="pstat"><div class="pstat-label">λ</div><div class="pstat-val" id="diffbar-wl" style="color:#FFFFFF">550 нм</div></div>
|
||||
<div class="pstat" style="flex:2"><div class="pstat-label">Результат</div><div class="pstat-val" id="diffbar-info" style="color:#FFD166;font-size:.72rem">—</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user