feat(sim-builder): фаза 2 — физический интегратор (SimPhysics: гравитация/пружины/столкновения, drag тел)
This commit is contained in:
@@ -90,37 +90,115 @@
|
||||
]
|
||||
};
|
||||
|
||||
/* ── Фаза 2: физ-демо за флагом ── */
|
||||
|
||||
// Маятник на пружине: груз (тело) подвешен пружиной к неподвижному якорю.
|
||||
// Гравитация тянет вниз, пружина возвращает -> колебания. Груз можно тащить.
|
||||
var PENDULUM_DEMO = {
|
||||
id: 'customphys',
|
||||
cat: 'phys',
|
||||
meta: { title: 'Демо: пружинный маятник', desc: 'Спек-физика (Фаза 2): тело + пружина + гравитация, drag тела.' },
|
||||
viewport: { xmin: -6, xmax: 6, ymin: -10, ymax: 2, grid: true, axes: true, bg: '#0D0D1A' },
|
||||
time: { autoplay: true, loop: false, speed: 1 },
|
||||
params: [
|
||||
{ name: 'k', label: 'Жёсткость k', min: 5, max: 120, step: 1, value: 40, unit: 'Н/м' },
|
||||
{ name: 'm', label: 'Масса m', min: 0.5, max: 5, step: 0.1, value: 1, unit: 'кг' },
|
||||
{ name: 'L', label: 'Длина L', min: 2, max: 8, step: 0.1, value: 5, unit: 'м' }
|
||||
],
|
||||
physics: {
|
||||
enabled: true,
|
||||
gravity: { x: 0, y: -9.8 },
|
||||
friction: 0.15,
|
||||
restitution: 0.7,
|
||||
walls: [{ side: 'bottom' }],
|
||||
springs: [{ a: [0, 0], b: 'bob', k: 'k', length: 'L', damping: 0.4 }]
|
||||
},
|
||||
objects: [
|
||||
// якорь подвеса
|
||||
{ type: 'circle', x: 0, y: 0, r: 0.18, color: '#FFD166', fill: '#FFD166' },
|
||||
// груз — физическое тело, стартует сбоку (выведено из равновесия), след включён
|
||||
{
|
||||
id: 'bob', type: 'circle', r: 0.6, color: '#06D6E0', fill: 'rgba(6,214,224,0.25)',
|
||||
x: '3', y: '-4', trail: true, trailColor: '#9B5DE5',
|
||||
body: { mass: 'm', vx: 0, vy: 0 }
|
||||
},
|
||||
{ type: 'label', latex: true, x: 'bob.x', y: 'bob.y - 1.1', text: 'm', color: '#06D6E0', size: 14 },
|
||||
// живые показания скорости
|
||||
{ type: 'readout', label: 'v_y', unit: 'м/с', precision: 2, color: '#FFD166', expr: 'bob.vy' },
|
||||
{ type: 'readout', label: 'y', unit: 'м', precision: 2, color: '#06D6E0', expr: 'bob.y' }
|
||||
]
|
||||
};
|
||||
|
||||
// Note: масса груза задаётся выражением 'm' (param). При reset тело пересобирается
|
||||
// с актуальной массой/нач.условиями; пружина length='L', k='k' пересчитываются тоже.
|
||||
|
||||
// Упругие шары: 3 тела в коробке из стен, разные начальные скорости, упругие
|
||||
// столкновения друг с другом и со стенами. Гравитация мягкая.
|
||||
var BALLS_DEMO = {
|
||||
id: 'customballs',
|
||||
cat: 'phys',
|
||||
meta: { title: 'Демо: упругие шары', desc: 'Спек-физика (Фаза 2): столкновения круг-круг и круг-стена.' },
|
||||
viewport: { xmin: 0, xmax: 12, ymin: 0, ymax: 9, grid: true, axes: false, bg: '#0D0D1A' },
|
||||
time: { autoplay: true, loop: false, speed: 1 },
|
||||
params: [
|
||||
{ name: 'g', label: 'Гравитация', min: 0, max: 12, step: 0.5, value: 4, unit: 'м/с²' },
|
||||
// NB: имя 'e' зарезервировано (число Эйлера в SimExpr) — используем 'el' для упругости.
|
||||
{ name: 'el', label: 'Упругость', min: 0.5, max: 1, step: 0.02, value: 0.96 }
|
||||
],
|
||||
physics: {
|
||||
enabled: true,
|
||||
gravity: { x: 0, y: '-g' }, // gravity.y — выражение от param g (вычисляется на reset)
|
||||
friction: 0,
|
||||
restitution: 'el', // упругость от param el
|
||||
walls: [{ side: 'bottom' }, { side: 'top' }, { side: 'left' }, { side: 'right' }]
|
||||
},
|
||||
objects: [
|
||||
{ id: 'b1', type: 'circle', r: 0.7, color: '#06D6E0', fill: 'rgba(6,214,224,0.3)',
|
||||
x: 2, y: 4.5, body: { mass: 1, vx: 6, vy: 2.4 }, trail: true, trailColor: '#06D6E0' },
|
||||
{ id: 'b2', type: 'circle', r: 1.0, color: '#9B5DE5', fill: 'rgba(155,93,229,0.3)',
|
||||
x: 8, y: 6, body: { mass: 2, vx: -4, vy: -3 }, trail: true, trailColor: '#9B5DE5' },
|
||||
{ id: 'b3', type: 'circle', r: 0.5, color: '#FFD166', fill: 'rgba(255,209,102,0.3)',
|
||||
x: 6, y: 2, body: { mass: 0.6, vx: 3, vy: 5 }, trail: true, trailColor: '#FFD166' },
|
||||
{ type: 'readout', label: 'b2.vx', precision: 2, color: '#9B5DE5', expr: 'b2.vx' }
|
||||
]
|
||||
};
|
||||
|
||||
var DEMOS = [PROJECTILE_DEMO, PENDULUM_DEMO, BALLS_DEMO];
|
||||
|
||||
function tryRegister() {
|
||||
if (!demoEnabled()) return;
|
||||
if (typeof global.registerSpecSim !== 'function') {
|
||||
if (global.console) console.warn('[sim-demo] registerSpecSim недоступен');
|
||||
return;
|
||||
}
|
||||
global.registerSpecSim(PROJECTILE_DEMO);
|
||||
for (var i = 0; i < DEMOS.length; i++) global.registerSpecSim(DEMOS[i]);
|
||||
|
||||
// Если каталог уже отрисован, добавить карточку демо вручную (минимально-
|
||||
// Если каталог уже отрисован, добавить карточки демо вручную (минимально-
|
||||
// инвазивно: только когда флаг включён; не трогаем SIMS/каталожный рендер).
|
||||
addDemoCardIfNeeded();
|
||||
addDemoCardsIfNeeded();
|
||||
}
|
||||
|
||||
function addDemoCardIfNeeded() {
|
||||
function addDemoCardsIfNeeded() {
|
||||
var grid = document.getElementById('sim-grid');
|
||||
if (!grid) return;
|
||||
if (document.getElementById('sim-card-customdemo')) return;
|
||||
var m = global.LabRegistry && global.LabRegistry.get('customdemo');
|
||||
if (!m) return;
|
||||
var preview = global.LabRegistry.resolvePreview(m);
|
||||
var card = document.createElement('div');
|
||||
card.id = 'sim-card-customdemo';
|
||||
card.className = 'sim-card';
|
||||
card.setAttribute('onclick', "openSim('customdemo')");
|
||||
card.innerHTML = preview +
|
||||
'<div class="sim-body">' +
|
||||
'<span class="sim-cat ' + (m.cat || 'phys') + '">демо</span>' +
|
||||
'<div class="sim-title">' + esc(m.title) + '</div>' +
|
||||
'<div class="sim-desc">' + esc(m.desc || '') + '</div>' +
|
||||
'</div>';
|
||||
grid.appendChild(card);
|
||||
for (var i = 0; i < DEMOS.length; i++) {
|
||||
var id = DEMOS[i].id;
|
||||
if (document.getElementById('sim-card-' + id)) continue;
|
||||
var m = global.LabRegistry && global.LabRegistry.get(id);
|
||||
if (!m) continue;
|
||||
var preview = global.LabRegistry.resolvePreview(m);
|
||||
var card = document.createElement('div');
|
||||
card.id = 'sim-card-' + id;
|
||||
card.className = 'sim-card';
|
||||
card.setAttribute('onclick', "openSim('" + id + "')");
|
||||
card.innerHTML = preview +
|
||||
'<div class="sim-body">' +
|
||||
'<span class="sim-cat ' + (m.cat || 'phys') + '">демо</span>' +
|
||||
'<div class="sim-title">' + esc(m.title) + '</div>' +
|
||||
'<div class="sim-desc">' + esc(m.desc || '') + '</div>' +
|
||||
'</div>';
|
||||
grid.appendChild(card);
|
||||
}
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
|
||||
Reference in New Issue
Block a user