refactor: distribute lab-init.js into 34 engine files
lab-init.js: 4098 -> 543 lines (infrastructure + THEORY only) Each sim's _open*() + UI helpers moved to its engine file: graph.js, projectile.js, collision.js, magnetic.js, triangle.js, geometry.js, trigcircle.js, gas.js (molphys), coulomb.js, circuit.js, reactions.js (chemistry), newton.js (dynamics), chemsandbox.js, celldivision.js, photosynthesis.js, angrybirds.js, quadratic.js, normaldist.js, graphtransform.js, pendulum.js, equilibrium.js, thinlens.js, mirror.js, isoprocess.js, titration.js, refraction.js, probability.js, bohratom.js, electrolysis.js, waves.js, crystal.js, orbitals.js, stereo.js, hydrostatics.js All 34 engine files syntax-checked OK.
This commit is contained in:
+366
-1
@@ -1,4 +1,4 @@
|
||||
'use strict';
|
||||
'use strict';
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
StereoSim — 3D Stereometry (Three.js)
|
||||
@@ -3028,3 +3028,368 @@ class StereoSim {
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── lab UI init ─────────────────────────────────── */
|
||||
var stereoSim = null;
|
||||
|
||||
// which params are relevant per figure type
|
||||
const STEREO_PARAM_MAP = {
|
||||
cube: ['a'],
|
||||
parallelepiped: ['a','b','c'],
|
||||
pyramid: ['a','n','h'],
|
||||
tetrahedron: ['a'],
|
||||
cylinder: ['r','h'],
|
||||
cone: ['r','h'],
|
||||
trunccone: ['R','r','h'],
|
||||
sphere: ['r'],
|
||||
prism: ['a','n','h'],
|
||||
truncpyramid: ['a','b','n','h'],
|
||||
octahedron: ['a'],
|
||||
icosahedron: ['a'],
|
||||
dodecahedron: ['a'],
|
||||
};
|
||||
|
||||
function _openStereo() {
|
||||
document.getElementById('sim-topbar-title').textContent = 'Стереометрия 3D';
|
||||
_simShow('sim-stereo');
|
||||
document.getElementById('stereo-stats').style.display = '';
|
||||
requestAnimationFrame(() => requestAnimationFrame(() => {
|
||||
if (!stereoSim) {
|
||||
stereoSim = new StereoSim(document.getElementById('stereo-container'));
|
||||
stereoSim.onUpdate = _stereoUpdateUI;
|
||||
} else {
|
||||
stereoSim.fit();
|
||||
stereoSim.play();
|
||||
}
|
||||
_stereoShowParams(stereoSim.figureType || 'cube');
|
||||
_stereoUpdateUI(stereoSim.info());
|
||||
_stereoUpdateFormulas();
|
||||
}));
|
||||
}
|
||||
|
||||
function setStereoFigure(type, btn) {
|
||||
document.querySelectorAll('.stereo-fig-btn').forEach(b => b.classList.remove('active'));
|
||||
if (btn) btn.classList.add('active');
|
||||
if (stereoSim) {
|
||||
stereoSim.setFigure(type);
|
||||
_stereoShowParams(type);
|
||||
_stereoUpdateFormulas();
|
||||
// reset toggles and tool buttons
|
||||
document.getElementById('sect-toggle').classList.remove('active');
|
||||
document.getElementById('stereo-unfold-btn').classList.remove('active');
|
||||
document.getElementById('stereo-measure-btn').classList.remove('active');
|
||||
// reset element toggles
|
||||
['stg-height','stg-apothem','stg-diagonals','stg-midpoints','stg-inscribed','stg-circumscribed','stg-edgelengths'].forEach(id => {
|
||||
document.getElementById(id)?.classList.remove('on');
|
||||
});
|
||||
_stereoDeactivateTools();
|
||||
}
|
||||
}
|
||||
|
||||
function _stereoShowParams(type) {
|
||||
const show = STEREO_PARAM_MAP[type] || ['a'];
|
||||
['a','b','c','h','r','R','n'].forEach(k => {
|
||||
document.getElementById('sp-' + k + '-row').style.display = show.includes(k) ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function stereoParamChange(key, val) {
|
||||
val = +val;
|
||||
const label = document.getElementById('sp-' + key + '-val');
|
||||
if (label) label.textContent = val;
|
||||
if (stereoSim) {
|
||||
stereoSim.setParam(key, val);
|
||||
_stereoUpdateFormulas();
|
||||
}
|
||||
}
|
||||
|
||||
function stereoOpacityChange(val) {
|
||||
val = +val;
|
||||
document.getElementById('sp-opacity-val').textContent = val.toFixed(2);
|
||||
if (stereoSim) stereoSim.setOpacity(val);
|
||||
}
|
||||
|
||||
// legacy (used nowhere now but kept for safety)
|
||||
function stereoToggle(layer, btn) {
|
||||
const on = !btn.classList.contains('active');
|
||||
btn.classList.toggle('active', on);
|
||||
if (!stereoSim) return;
|
||||
if (layer === 'edges') stereoSim.toggleEdges(on);
|
||||
if (layer === 'vertices') stereoSim.toggleVertices(on);
|
||||
if (layer === 'labels') stereoSim.toggleLabels(on);
|
||||
if (layer === 'axes') stereoSim.toggleAxes(on);
|
||||
if (layer === 'grid') stereoSim.toggleGrid(on);
|
||||
}
|
||||
|
||||
// new toggle-row style
|
||||
function stereoToggleSt(layer, toggle) {
|
||||
const on = !toggle.classList.contains('on');
|
||||
toggle.classList.toggle('on', on);
|
||||
if (!stereoSim) return;
|
||||
if (layer === 'edges') stereoSim.toggleEdges(on);
|
||||
if (layer === 'vertices') stereoSim.toggleVertices(on);
|
||||
if (layer === 'labels') stereoSim.toggleLabels(on);
|
||||
if (layer === 'axes') stereoSim.toggleAxes(on);
|
||||
if (layer === 'grid') stereoSim.toggleGrid(on);
|
||||
}
|
||||
|
||||
function stereoToggleElem(layer, toggle) {
|
||||
const on = !toggle.classList.contains('on');
|
||||
toggle.classList.toggle('on', on);
|
||||
if (!stereoSim) return;
|
||||
if (layer === 'height') stereoSim.toggleHeight(on);
|
||||
if (layer === 'apothem') stereoSim.toggleApothem(on);
|
||||
if (layer === 'diagonals') stereoSim.toggleDiagonals(on);
|
||||
if (layer === 'midpoints') stereoSim.toggleMidpoints(on);
|
||||
if (layer === 'inscribed') stereoSim.toggleInscribed(on);
|
||||
if (layer === 'circumscribed') stereoSim.toggleCircumscribed(on);
|
||||
if (layer === 'edgelengths') stereoSim.toggleEdgeLengths(on);
|
||||
}
|
||||
|
||||
// n-stepper for prism/pyramid
|
||||
function stereoNChange(delta) {
|
||||
if (!stereoSim) return;
|
||||
const cur = stereoSim.params.n || 4;
|
||||
const nv = Math.max(3, Math.min(12, cur + delta));
|
||||
document.getElementById('sp-n-val').textContent = nv;
|
||||
stereoSim.setParam('n', nv);
|
||||
_stereoUpdateFormulas();
|
||||
}
|
||||
|
||||
function stereoSectionToggle(btn) {
|
||||
const on = !btn.classList.contains('active');
|
||||
btn.classList.toggle('active', on);
|
||||
if (stereoSim) stereoSim.toggleSection(on);
|
||||
}
|
||||
|
||||
function stereoSectionType(t, btn) {
|
||||
document.querySelectorAll('.stereo-sect-type').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
// Show/hide angle slider for diagonal
|
||||
document.getElementById('sp-angle-row').style.display = t === 'diagonal' ? '' : 'none';
|
||||
if (stereoSim) stereoSim.setSectionType(t);
|
||||
}
|
||||
|
||||
function stereoSectionHeight(val) {
|
||||
document.getElementById('sp-sect-val').textContent = val + '%';
|
||||
if (stereoSim) stereoSim.setSectionHeight(+val / 100);
|
||||
}
|
||||
|
||||
function stereoSectionAngle(val) {
|
||||
document.getElementById('sp-angle-val').textContent = val + '%';
|
||||
if (stereoSim) stereoSim.setSectionAngle(+val / 100);
|
||||
}
|
||||
|
||||
function stereoUnfold(btn) {
|
||||
const on = !btn.classList.contains('active');
|
||||
btn.classList.toggle('active', on);
|
||||
if (stereoSim) stereoSim.toggleUnfold(on);
|
||||
}
|
||||
|
||||
function _stereoDeactivateTools() {
|
||||
['stereo-measure-btn','stereo-point-btn','stereo-connect-btn',
|
||||
'stereo-angle-edge-btn','stereo-angle-lp-btn','stereo-angle-dih-btn','stereo-angle-pp-btn','stereo-angle-skew-btn',
|
||||
'stereo-mark-tick-btn','stereo-mark-par-btn',
|
||||
'stereo-derive-mid-btn','stereo-derive-fc-btn','stereo-derive-alt-btn','stereo-derive-cen-btn'].forEach(id => {
|
||||
document.getElementById(id)?.classList.remove('active');
|
||||
});
|
||||
if (stereoSim) {
|
||||
stereoSim.toggleMeasure(false);
|
||||
stereoSim.togglePointMode(false);
|
||||
stereoSim.toggleConnectMode(false);
|
||||
stereoSim.setAngleMode(null);
|
||||
stereoSim.setMarkMode(null);
|
||||
stereoSim.setDeriveMode(null);
|
||||
}
|
||||
const hint = document.getElementById('angle-hint');
|
||||
if (hint) hint.textContent = '';
|
||||
}
|
||||
|
||||
function stereoMeasure(btn) {
|
||||
const on = !btn.classList.contains('active');
|
||||
_stereoDeactivateTools();
|
||||
btn.classList.toggle('active', on);
|
||||
if (stereoSim) stereoSim.toggleMeasure(on);
|
||||
}
|
||||
|
||||
function stereoMeasureUndo() {
|
||||
if (stereoSim) stereoSim.removeLastMeasurement();
|
||||
}
|
||||
|
||||
function stereoMeasureClear() {
|
||||
if (stereoSim) stereoSim.clearMeasurements();
|
||||
}
|
||||
|
||||
function stereoToggleHeight(btn) {
|
||||
const on = !btn.classList.contains('active');
|
||||
btn.classList.toggle('active', on);
|
||||
if (stereoSim) stereoSim.toggleHeight(on);
|
||||
}
|
||||
|
||||
function stereoToggleApothem(btn) {
|
||||
const on = !btn.classList.contains('active');
|
||||
btn.classList.toggle('active', on);
|
||||
if (stereoSim) stereoSim.toggleApothem(on);
|
||||
}
|
||||
|
||||
function stereoToggleDiag(btn) {
|
||||
const on = !btn.classList.contains('active');
|
||||
btn.classList.toggle('active', on);
|
||||
if (stereoSim) stereoSim.toggleDiagonals(on);
|
||||
}
|
||||
|
||||
function stereoToggleMid(btn) {
|
||||
const on = !btn.classList.contains('active');
|
||||
btn.classList.toggle('active', on);
|
||||
if (stereoSim) stereoSim.toggleMidpoints(on);
|
||||
}
|
||||
|
||||
const ANGLE_HINTS = {
|
||||
edge: 'Кликните 3 точки: A, B (вершина угла), C',
|
||||
linePlane: 'Кликните 2 точки (прямая), затем — грань',
|
||||
dihedral: 'Кликните 2 точки общего ребра двух граней',
|
||||
pointPlane: 'Кликните точку, затем — грань',
|
||||
skewLines: 'P1, P2 (прямая 1) → P3, P4 (прямая 2): угол и расстояние',
|
||||
};
|
||||
|
||||
function stereoAngleMode(mode, btn) {
|
||||
const on = !btn.classList.contains('active');
|
||||
_stereoDeactivateTools();
|
||||
btn.classList.toggle('active', on);
|
||||
if (stereoSim) stereoSim.setAngleMode(on ? mode : null);
|
||||
const hint = document.getElementById('angle-hint');
|
||||
if (hint) hint.textContent = on ? ANGLE_HINTS[mode] : '';
|
||||
}
|
||||
|
||||
function stereoAngleClear() {
|
||||
_stereoDeactivateTools();
|
||||
if (stereoSim) {
|
||||
stereoSim.setAngleMode(null);
|
||||
stereoSim._clearGroup(stereoSim._angleGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Edge marks ── */
|
||||
function stereoMarkMode(mode, btn) {
|
||||
const on = !btn.classList.contains('active');
|
||||
_stereoDeactivateTools();
|
||||
btn.classList.toggle('active', on);
|
||||
if (stereoSim) stereoSim.setMarkMode(on ? mode : null);
|
||||
}
|
||||
|
||||
function stereoMarkClear() {
|
||||
_stereoDeactivateTools();
|
||||
if (stereoSim) stereoSim.clearMarks();
|
||||
}
|
||||
|
||||
function stereoToggleEdgeLengths(btn) {
|
||||
const on = !btn.classList.contains('active');
|
||||
btn.classList.toggle('active', on);
|
||||
if (stereoSim) stereoSim.toggleEdgeLengths(on);
|
||||
}
|
||||
|
||||
/* ── Derived points ── */
|
||||
function stereoDerive(mode, btn) {
|
||||
const on = !btn.classList.contains('active');
|
||||
_stereoDeactivateTools();
|
||||
btn.classList.toggle('active', on);
|
||||
if (stereoSim) stereoSim.setDeriveMode(on ? mode : null);
|
||||
}
|
||||
|
||||
function stereoDeriveUndo() {
|
||||
if (stereoSim) stereoSim.removeLastDerived();
|
||||
}
|
||||
|
||||
function stereoDeriveClear() {
|
||||
_stereoDeactivateTools();
|
||||
if (stereoSim) stereoSim.clearDerived();
|
||||
}
|
||||
|
||||
function stereoPointMode(btn) {
|
||||
const on = !btn.classList.contains('active');
|
||||
_stereoDeactivateTools();
|
||||
btn.classList.toggle('active', on);
|
||||
if (stereoSim) stereoSim.togglePointMode(on);
|
||||
}
|
||||
|
||||
function stereoConnectMode(btn) {
|
||||
const on = !btn.classList.contains('active');
|
||||
_stereoDeactivateTools();
|
||||
btn.classList.toggle('active', on);
|
||||
if (stereoSim) stereoSim.toggleConnectMode(on);
|
||||
}
|
||||
|
||||
function stereoUndoPoint() {
|
||||
if (stereoSim) stereoSim.removeLastPoint();
|
||||
}
|
||||
|
||||
function stereoClearPoints() {
|
||||
if (stereoSim) stereoSim.clearCustomPoints();
|
||||
_stereoUpdatePointsInfo();
|
||||
}
|
||||
|
||||
function stereoInscribed(btn) {
|
||||
const on = !btn.classList.contains('active');
|
||||
btn.classList.toggle('active', on);
|
||||
if (stereoSim) stereoSim.toggleInscribed(on);
|
||||
}
|
||||
|
||||
function stereoCircumscribed(btn) {
|
||||
const on = !btn.classList.contains('active');
|
||||
btn.classList.toggle('active', on);
|
||||
if (stereoSim) stereoSim.toggleCircumscribed(on);
|
||||
}
|
||||
|
||||
function _stereoUpdateFormulas() {
|
||||
if (!stereoSim) return;
|
||||
const f = stereoSim.getFormulas();
|
||||
const el = document.getElementById('stereo-formulas');
|
||||
if (!f || !f.formulas) { el.innerHTML = ''; return; }
|
||||
const colors = ['#7BF5A4','#60a5fa','#c4b5fd','#fbbf24','#f9a8d4','#F59E0B','#EF476F'];
|
||||
el.innerHTML = f.formulas.map((s, i) =>
|
||||
'<div style="color:' + (colors[i % colors.length]) + '">' + s + '</div>'
|
||||
).join('');
|
||||
}
|
||||
|
||||
function _stereoUpdateUI(info) {
|
||||
if (!info) return;
|
||||
document.getElementById('stbar-vol').textContent = info.V !== undefined ? info.V.toFixed(2) : '—';
|
||||
document.getElementById('stbar-area').textContent = info.S !== undefined ? info.S.toFixed(2) : '—';
|
||||
document.getElementById('stbar-side').textContent = info.S_side !== undefined ? info.S_side.toFixed(2) : '—';
|
||||
document.getElementById('stbar-h').textContent = info.h !== undefined ? info.h.toFixed(2) : '—';
|
||||
document.getElementById('stbar-d').textContent = info.d !== undefined && info.d > 0 ? info.d.toFixed(2) : '—';
|
||||
|
||||
// Section area
|
||||
const sectEl = document.getElementById('sect-area-display');
|
||||
if (info.sectionArea && info.sectionArea > 0) {
|
||||
sectEl.style.display = '';
|
||||
sectEl.textContent = 'S сечения = ' + info.sectionArea.toFixed(2);
|
||||
} else {
|
||||
sectEl.style.display = 'none';
|
||||
}
|
||||
|
||||
// Inscribed / Circumscribed radius info
|
||||
const rInfo = document.getElementById('sphere-radius-info');
|
||||
if (rInfo) {
|
||||
const parts = [];
|
||||
if (info.inscribedR != null) parts.push('r_вп = ' + info.inscribedR.toFixed(2));
|
||||
if (info.circumscribedR != null) parts.push('R_оп = ' + info.circumscribedR.toFixed(2));
|
||||
rInfo.textContent = parts.join(' · ');
|
||||
rInfo.style.display = parts.length ? '' : 'none';
|
||||
}
|
||||
|
||||
// Points info
|
||||
_stereoUpdatePointsInfo(info);
|
||||
}
|
||||
|
||||
function _stereoUpdatePointsInfo(info) {
|
||||
const el = document.getElementById('points-info');
|
||||
if (!el) return;
|
||||
if (!info) info = stereoSim?.info();
|
||||
if (!info) { el.textContent = ''; return; }
|
||||
let txt = '';
|
||||
if (info.customPoints > 0) txt += `Точек: ${info.customPoints}`;
|
||||
if (info.connections > 0) txt += ` · Линий: ${info.connections}`;
|
||||
el.textContent = txt;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user