diff --git a/frontend/js/labs/stereo.js b/frontend/js/labs/stereo.js
index 868fab9..11ba084 100644
--- a/frontend/js/labs/stereo.js
+++ b/frontend/js/labs/stereo.js
@@ -917,6 +917,16 @@ class StereoSim {
axes.material.transparent = true;
axes.material.opacity = 0.4;
this._gridGroup.add(axes);
+ // axis letters, coloured to match AxesHelper (X red, Y green, Z blue)
+ const axLabel = (txt, color, pos) => {
+ const s = this._makeTextSprite(txt, color, 38);
+ s.position.copy(pos);
+ s.scale.multiplyScalar(0.85);
+ this._gridGroup.add(s);
+ };
+ axLabel('X', '#ff5b5b', new THREE.Vector3(6.5, 0.15, 0));
+ axLabel('Y', '#5bff8d', new THREE.Vector3(0, 6.6, 0));
+ axLabel('Z', '#5b9bff', new THREE.Vector3(0, 0.15, 6.5));
}
this._invalidate();
}
@@ -1432,13 +1442,15 @@ class StereoSim {
this._figGroup.add(new THREE.Mesh(geo, mat));
}
- _addEdges(opac = 0.8) {
+ _addEdges(opac = 0.9) {
if (!this.showEdges) return;
for (const e of this._edges) {
const pts = [e.from, e.to];
const geo = new THREE.BufferGeometry().setFromPoints(pts);
const mat = new THREE.LineBasicMaterial({ color: 0xFFFFFF, transparent: true, opacity: opac, linewidth: 2 });
- this._figGroup.add(new THREE.Line(geo, mat));
+ const line = new THREE.Line(geo, mat);
+ line.renderOrder = 2; // draw over the translucent solid for crisp contrast
+ this._figGroup.add(line);
if (this.showEdgeLengths) {
const len = e.from.distanceTo(e.to);
@@ -1454,10 +1466,21 @@ class StereoSim {
_addVerticesAndLabels() {
for (const v of this._vertices) {
if (this.showVertices) {
+ // soft additive glow halo (texture-free → safe with _clearGroup disposal)
+ const gGeo = new THREE.SphereGeometry(0.17, 12, 12);
+ const gMat = new THREE.MeshBasicMaterial({
+ color: 0x9B5DE5, transparent: true, opacity: 0.16,
+ blending: THREE.AdditiveBlending, depthWrite: false,
+ });
+ const glow = new THREE.Mesh(gGeo, gMat);
+ glow.position.copy(v.pos);
+ this._figGroup.add(glow);
+
const sGeo = new THREE.SphereGeometry(0.08, 12, 12);
const sMat = new THREE.MeshBasicMaterial({ color: 0xFFFFFF });
const sphere = new THREE.Mesh(sGeo, sMat);
sphere.position.copy(v.pos);
+ sphere.renderOrder = 3;
this._figGroup.add(sphere);
}
diff --git a/frontend/lab.html b/frontend/lab.html
index 3539c85..6e0b251 100644
--- a/frontend/lab.html
+++ b/frontend/lab.html
@@ -4819,7 +4819,7 @@
-
+
diff --git a/plans/STEREO_3D_IMPROVEMENT.md b/plans/STEREO_3D_IMPROVEMENT.md
index 0fe3f5b..b8b31f8 100644
--- a/plans/STEREO_3D_IMPROVEMENT.md
+++ b/plans/STEREO_3D_IMPROVEMENT.md
@@ -36,10 +36,12 @@
Бэклог: полное «построение сечения по следам» (продление рёбер, след на грани); readout углов (двугранный/линия-плоскость) — сейчас угол только в 3D-метке.
-## Фаза 4 — Визуал
+## Фаза 4 — Визуал — ГОТОВО (см. бэклог)
-- [ ] 4.1 Материалы: рёбра с anti-alias, подсветка активной грани, свечение вершин.
-- [ ] 4.2 Аккуратные оси X/Y/Z с подписями, опциональный фон (тёмный/бумага).
+- [x] 4.1 Свечение вершин (мягкий additive-ореол без текстур, безопасно с `_clearGroup`); рёбра контрастнее (opacity 0.9, `renderOrder` поверх полупрозрачного тела), вершины поверх рёбер. _(Подсветка грани по ховеру отложена — текущий centroid-пикинг граней грубоват для плавного hover.)_
+- [x] 4.2 Подписи осей X/Y/Z, цвета совпадают с AxesHelper (X крас., Y зел., Z син.).
+
+Бэклог: подсветка грани по ховеру (нужен точный raycast по логическим граням); градиентный/бумажный фон (учесть захват в скриншоте).
## Фаза 5 — Интеграция и архитектура