410eb8a862
Два дефекта, из-за которых 3D читался как плоская диаграмма: - painter-сортировка была по возрастанию z (ближние первыми) — дальние атомы рисовались поверх ближних. Теперь единый список примитивов (атомы + половинки связей) сортируется по убыванию z (дальние первыми). - связи были тонкими плоскими линиями. Теперь — затенённые «цилиндры»: толстый штрих с поперечным градиентом (центр светлее, края темнее), двухцветные (каждая половина под цвет своего атома) — фирменный вид ball-and-stick. Ширина зависит от перспективы (ближе — толще). - усилена перспектива (fov 900→700), добавлен тёмный ободок сфер для объёма. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
102 lines
3.8 KiB
JavaScript
102 lines
3.8 KiB
JavaScript
'use strict';
|
|
/*
|
|
* LabRegistry — единый реестр симуляций лаборатории (контент-движок).
|
|
*
|
|
* Цель: симуляции описываются декларативным манифестом и сами себя регистрируют,
|
|
* вместо захардкоженных массивов (SIMS), if-цепочек (openSim) и объектов (THEORY).
|
|
*
|
|
* Манифест:
|
|
* {
|
|
* id: 'pendulum', // уникальный, без ':arg'
|
|
* cat: 'phys', // math | phys | chem | bio | game
|
|
* title: 'Маятник',
|
|
* desc: 'Колебания, период…',
|
|
* preview: string | function(), // SVG-разметка карточки (функция вычисляется лениво)
|
|
* theory: { title, sections[] },// объект для панели теории (как в THEORY)
|
|
* bodyId: 'sim-pendulum', // (опц.) id тела; mount() — для ленивого создания DOM (Фаза 2)
|
|
* mount: function(host){}, // (опц.) ленивое монтирование тела
|
|
* open: function(ctx){}, // ctx = { id, arg } — открыть/инициализировать
|
|
* stop: function(){}, // (опц.) остановить анимации (не разрушая)
|
|
* destroy: function(){}, // (опц.) полностью закрыть; по умолчанию == stop
|
|
* subject, grade, topics // (опц.) курикулумные поля (Фаза 5)
|
|
* }
|
|
*
|
|
* Загружается ПЕРВЫМ среди labs-скриптов, чтобы window.LabRegistry существовал
|
|
* к моменту исполнения тел остальных модулей.
|
|
*/
|
|
(function () {
|
|
var _list = []; // манифесты в порядке регистрации
|
|
var _byId = {}; // id -> манифест
|
|
var _active = null; // текущая открытая симуляция
|
|
|
|
function _baseId(id) {
|
|
return id == null ? id : String(id).split(':')[0];
|
|
}
|
|
|
|
function register(m) {
|
|
if (!m || !m.id) return null;
|
|
if (Object.prototype.hasOwnProperty.call(_byId, m.id)) {
|
|
// перерегистрация: заменить на месте, сохранив позицию
|
|
for (var i = 0; i < _list.length; i++) {
|
|
if (_list[i].id === m.id) { _list[i] = m; break; }
|
|
}
|
|
} else {
|
|
_list.push(m);
|
|
}
|
|
_byId[m.id] = m;
|
|
return m;
|
|
}
|
|
|
|
function get(id) {
|
|
var b = _baseId(id);
|
|
return Object.prototype.hasOwnProperty.call(_byId, b) ? _byId[b] : null;
|
|
}
|
|
|
|
function has(id) { return !!get(id); }
|
|
|
|
function all() { return _list.slice(); }
|
|
|
|
function setActive(m) { _active = m || null; }
|
|
|
|
function stopActive() {
|
|
if (_active && typeof _active.stop === 'function') {
|
|
try { _active.stop(); } catch (e) { /* noop */ }
|
|
}
|
|
}
|
|
|
|
function destroyActive() {
|
|
if (_active) {
|
|
if (typeof _active.destroy === 'function') {
|
|
try { _active.destroy(); } catch (e) { /* noop */ }
|
|
} else if (typeof _active.stop === 'function') {
|
|
try { _active.stop(); } catch (e) { /* noop */ }
|
|
}
|
|
}
|
|
_active = null;
|
|
}
|
|
|
|
function active() { return _active; }
|
|
|
|
// Разрешить preview (строка или функция) в готовую разметку.
|
|
function resolvePreview(m) {
|
|
if (!m) return '';
|
|
var p = m.preview;
|
|
if (typeof p === 'function') {
|
|
try { return p() || ''; } catch (e) { return ''; }
|
|
}
|
|
return p || '';
|
|
}
|
|
|
|
window.LabRegistry = {
|
|
register: register,
|
|
get: get,
|
|
has: has,
|
|
all: all,
|
|
setActive: setActive,
|
|
stopActive: stopActive,
|
|
destroyActive: destroyActive,
|
|
active: active,
|
|
resolvePreview: resolvePreview
|
|
};
|
|
})();
|