feat(opticsbench): режим лучей предмета — характеристические vs пучок
- источник «Предмет»: тумблер «Характ. лучи» (по умолчанию) / «Пучок» - характеристические: 3 луча от вершины (параллельный→F', через центр, через F→параллельно) + осевой от основания — как в учебнике; проверено численно (F'=lensX+f, центр прямо, через F выход параллелен) - пучок: прежний физичный веер + ползунок «Лучей» (густота) и «Раствор» - setSource: rayMode как строковый ключ; bump opticsbench.js?v=9 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,45 +0,0 @@
|
||||
'use strict';
|
||||
/*
|
||||
* Пилотная регистрация в LabRegistry (Фаза 0 контент-движка).
|
||||
*
|
||||
* Доказывает паритет: каталог/открытие/теория этих 3 симуляций идут через реестр.
|
||||
* Загружается ПОСЛЕДНИМ среди labs-скриптов, поэтому P_* (lab-glue.js),
|
||||
* THEORY (lab-init.js) и _openXxx (graph/quadratic/pendulum.js) уже определены.
|
||||
* preview задан функцией (ленивое вычисление в renderSims) — безопасно к порядку.
|
||||
*
|
||||
* В Фазе 1 регистрация переедет в сами sim-файлы, а этот файл будет удалён.
|
||||
*/
|
||||
(function () {
|
||||
if (!window.LabRegistry) return;
|
||||
var R = window.LabRegistry;
|
||||
|
||||
R.register({
|
||||
id: 'graph', cat: 'math',
|
||||
title: 'График функции', desc: 'Постройте и исследуйте графики функций',
|
||||
preview: function () { return (typeof P_GRAPH !== 'undefined') ? P_GRAPH : ''; },
|
||||
theory: (typeof THEORY !== 'undefined') ? THEORY.graph : null,
|
||||
open: function () { _openGraph(); },
|
||||
stop: function () { /* нет анимационного цикла */ },
|
||||
destroy: function () { /* нет ресурсов для освобождения */ }
|
||||
});
|
||||
|
||||
R.register({
|
||||
id: 'quadratic', cat: 'math',
|
||||
title: 'Квадратное уравнение', desc: 'Дискриминант, корни, теорема Виета',
|
||||
preview: function () { return (typeof P_QUADRATIC !== 'undefined') ? P_QUADRATIC : ''; },
|
||||
theory: (typeof THEORY !== 'undefined') ? THEORY.quadratic : null,
|
||||
open: function () { _openQuadratic(); },
|
||||
stop: function () { /* нет анимационного цикла */ },
|
||||
destroy: function () { /* нет ресурсов для освобождения */ }
|
||||
});
|
||||
|
||||
R.register({
|
||||
id: 'pendulum', cat: 'phys',
|
||||
title: 'Маятник', desc: 'Колебания, период, затухание',
|
||||
preview: function () { return (typeof P_PENDULUM !== 'undefined') ? P_PENDULUM : ''; },
|
||||
theory: (typeof THEORY !== 'undefined') ? THEORY.pendulum : null,
|
||||
open: function () { _openPendulum(); },
|
||||
stop: function () { if (typeof pendSim !== 'undefined' && pendSim && pendSim.stop) pendSim.stop(); },
|
||||
destroy: function () { if (typeof pendSim !== 'undefined' && pendSim && pendSim.stop) pendSim.stop(); }
|
||||
});
|
||||
})();
|
||||
@@ -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, yf: 0, 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, rayMode: 'char' };
|
||||
// elements along the bench, positioned by x-fraction; centred on the axis
|
||||
this.elements = [
|
||||
this._mk('lens', { xf: 0.40, f: 130, ap: 95 }),
|
||||
@@ -2602,7 +2602,7 @@ class BenchSim {
|
||||
this._redraw(); // canvas only — never rebuild the inspector mid-slider-drag
|
||||
}
|
||||
setSource(key, val) {
|
||||
this.source[key] = (key === 'kind') ? val : +val;
|
||||
this.source[key] = (key === 'kind' || key === 'rayMode') ? val : +val; // string keys vs numeric
|
||||
this._redraw();
|
||||
}
|
||||
getSelected() { return this.elements.find(e => e.id === this.selectedId) || null; }
|
||||
@@ -2644,11 +2644,26 @@ class BenchSim {
|
||||
} else if (this.source.kind === 'point') {
|
||||
const n = this.source.rays, A = this.source.spread;
|
||||
for (let i = 0; i < n; i++) push(sx, ay, aim - A + 2 * A * (i / (n - 1)));
|
||||
} else { // object arrow: fan from tip and base
|
||||
const n = this.source.rays, A = this.source.spread;
|
||||
[ay - this.source.h, ay].forEach(y0 => {
|
||||
for (let i = 0; i < n; i++) push(sx, y0, -A + 2 * A * (i / (n - 1)));
|
||||
});
|
||||
} else { // object arrow
|
||||
const axis = this._ay(); // optical axis (lens centre / focus sit here)
|
||||
const tipY = ay - this.source.h, baseY = ay;
|
||||
const firstLens = this.elements.filter(e => e.type === 'lens').sort((a, b) => a.xf - b.xf)[0];
|
||||
if (this.source.rayMode === 'char' && firstLens) {
|
||||
// textbook construction: 2–3 characteristic rays from the tip + axial ray from the base
|
||||
const lensX = firstLens.xf * this.W, f = firstLens.f;
|
||||
const aimAt = (tx, ty) => Math.atan2(ty - tipY, tx - sx);
|
||||
push(sx, tipY, 0); // 1) parallel to axis → through far focus F'
|
||||
push(sx, tipY, aimAt(lensX, axis)); // 2) through the optical centre → straight
|
||||
const Fx = lensX - f; // front focal point
|
||||
if (f > 0 && Fx > sx + 5) push(sx, tipY, aimAt(Fx, axis)); // 3) through F → emerges parallel
|
||||
push(sx, baseY, 0); // base lies on the axis
|
||||
} else {
|
||||
// physical bundle: a fan from tip and base
|
||||
const n = Math.max(2, this.source.rays | 0), A = this.source.spread;
|
||||
[tipY, baseY].forEach(y0 => {
|
||||
for (let i = 0; i < n; i++) push(sx, y0, -A + 2 * A * (i / (n - 1)));
|
||||
});
|
||||
}
|
||||
}
|
||||
return rays;
|
||||
}
|
||||
@@ -4454,7 +4469,18 @@ function _benchPropsHTML() {
|
||||
h += _benchBtnRow(['object:Предмет', 'point:Точка', 'parallel:Параллель', 'single:Луч', 'laser:Лазер'],
|
||||
k => s.kind === k, k => "benchSourceKind('" + k + "')");
|
||||
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 === 'object') {
|
||||
h += _benchCtl('Размер стрелки', 0, 'h', 20, 120, 2, s.h, true);
|
||||
// ray mode: textbook characteristic rays vs physical bundle
|
||||
h += _benchBtnRow(['char:Характ. лучи', 'bundle:Пучок'],
|
||||
k => (s.rayMode || 'char') === k, k => "benchSourceParam('rayMode','" + k + "');_benchUpdateUI()");
|
||||
if ((s.rayMode || 'char') === 'bundle') {
|
||||
h += _benchCtl('Лучей', 0, 'rays', 3, 15, 1, s.rays, true);
|
||||
h += _benchCtl('Раствор', 0, 'spread', 0.1, 0.6, 0.02, s.spread, true);
|
||||
} else {
|
||||
h += '<div class="pp-hint">2–3 характеристических луча от вершины + осевой от основания (как в учебнике).</div>';
|
||||
}
|
||||
}
|
||||
if (s.kind === 'point') h += _benchCtl('Раствор', 0, 'spread', 0.1, 0.6, 0.02, s.spread, true);
|
||||
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=8"></script>
|
||||
<script src="/js/labs/opticsbench.js?v=9"></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