llm-semantic-visualizer / src /core /ReliefHUDController.js
hbauzan's picture
Release v4.17.7 — sync from GitHub main
819a47f
Raw
History Blame Contribute Delete
6.31 kB
import * as THREE from 'three';
import { STATE, getMaxDimensions, addBookmark } from './State.js';
import { resolveDocByObject, getActiveReliefObjects } from '../visualizer/ReliefRenderer.js';
import { showBookmarkNamingModal } from '../ui/modals/CustomModal.js';
import { updateBookmarksUI } from '../ui/panels/BookmarksPanel.js';
/** Normalizes a renderer record or STATE.reliefData to a uniform relief source. */
export function reliefSourceFromRecord(record) {
if (!record) return null;
return {
matrix: record.matrix,
text_metadata: record.textMetadata || [],
dimensions_count: record.dimensions_count || (record.matrix[0] ? record.matrix[0].length : 768),
filename: record.filename || (STATE.reliefData && STATE.reliefData.filename) || 'documento',
};
}
/** Maps a raycast hit to chunk/dimension indices and the owning relief source. */
export function computeReliefIndices(hit) {
const record = resolveDocByObject(hit.object);
const data = record ? reliefSourceFromRecord(record) : STATE.reliefData;
if (!data || !data.matrix || data.matrix.length === 0) return null;
let vIdx = -1;
if (hit.face) {
const geometry = hit.object.geometry;
const indexAttr = geometry.index;
const positionAttr = geometry.attributes.position;
const vA = indexAttr.getX(hit.face.a);
const vB = indexAttr.getX(hit.face.b);
const vC = indexAttr.getX(hit.face.c);
const localHitPoint = hit.point.clone().applyMatrix4(hit.object.matrixWorld.clone().invert());
const posA = new THREE.Vector3().fromBufferAttribute(positionAttr, vA);
const posB = new THREE.Vector3().fromBufferAttribute(positionAttr, vB);
const posC = new THREE.Vector3().fromBufferAttribute(positionAttr, vC);
const distA = localHitPoint.distanceTo(posA);
const distB = localHitPoint.distanceTo(posB);
const distC = localHitPoint.distanceTo(posC);
if (distA <= distB && distA <= distC) vIdx = vA;
else if (distB <= distA && distB <= distC) vIdx = vB;
else vIdx = vC;
} else if (hit.index !== undefined) {
vIdx = hit.index;
}
if (vIdx === -1 || vIdx === undefined) return null;
const numCols = data.dimensions_count || 768;
const chunkIndex = Math.floor(vIdx / numCols);
const dimIndex = vIdx % numCols;
if (chunkIndex < 0 || chunkIndex >= data.matrix.length) return null;
return { chunkIndex, dimIndex, source: data };
}
/** Updates the floating relief HUD and coordinate readout for a chunk/dimension pair. */
export function fillReliefHUD(chunkIndex, dimIndex, pinned, source = null) {
if (STATE.comparisonMode === 'C') {
const hudEl = document.getElementById('relief-hud');
if (hudEl) hudEl.style.display = 'none';
const coordsEl = document.getElementById('relief-coords');
if (coordsEl) coordsEl.style.display = 'none';
return;
}
const data = source || STATE.reliefData;
if (!data || !data.matrix || !data.matrix[chunkIndex]) return;
const chunkText = (data.text_metadata && data.text_metadata[chunkIndex]) || 'Sin texto';
const rawVal = data.matrix[chunkIndex] ? data.matrix[chunkIndex][dimIndex] : undefined;
const activationValue = typeof rawVal === 'number' && !Number.isNaN(rawVal) ? rawVal : 0;
const hudEl = document.getElementById('relief-hud');
const chunkIdxEl = document.getElementById('relief-hud-chunk-idx');
const dimIdxEl = document.getElementById('relief-hud-dim-idx');
const dimContainer = document.getElementById('relief-hud-dim-container');
const valEl = document.getElementById('relief-hud-val');
const textEl = document.getElementById('relief-hud-text');
const pinEl = document.getElementById('relief-hud-pin');
const exportBtn = document.getElementById('relief-hud-export');
if (hudEl) hudEl.style.display = 'flex';
if (chunkIdxEl) chunkIdxEl.textContent = chunkIndex;
if (dimIdxEl) dimIdxEl.textContent = dimIndex;
if (dimContainer) {
const maxDim = getMaxDimensions() - 1;
dimContainer.title = `Índice de la dimensión latente (0-${maxDim}) a lo largo del eje X`;
}
if (valEl) {
valEl.textContent = activationValue.toFixed(4);
valEl.style.color = activationValue > 0 ? 'var(--color-semantic-success)' : (activationValue < 0 ? 'var(--color-semantic-error)' : 'var(--color-neutral-400)');
}
if (textEl) textEl.textContent = chunkText;
if (pinEl) pinEl.style.display = pinned ? 'inline' : 'none';
if (exportBtn) {
exportBtn.style.display = pinned ? 'block' : 'none';
exportBtn.dataset.dim = String(dimIndex);
}
const coordsEl = document.getElementById('relief-coords');
const coordZEl = document.getElementById('relief-coords-z');
const coordXEl = document.getElementById('relief-coords-x');
const coordYEl = document.getElementById('relief-coords-y');
if (coordsEl) coordsEl.style.display = 'flex';
if (coordZEl) coordZEl.textContent = chunkIndex;
if (coordXEl) coordXEl.textContent = dimIndex;
if (coordYEl) coordYEl.textContent = (activationValue >= 0 ? '+' : '') + activationValue.toFixed(4);
}
/** Registers the B-key shortcut to bookmark the relief point under the crosshair. */
export function setupReliefBookmarkHotkey(camera, controls) {
document.addEventListener('keydown', (e) => {
if (e.key !== 'b' && e.key !== 'B') return;
if (!STATE.isReady) return;
if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA' || document.activeElement.tagName === 'SELECT') return;
if (STATE.comparisonMode === 'C') return;
const raycasterLocal = new THREE.Raycaster();
raycasterLocal.params.Points.threshold = 0.5;
raycasterLocal.setFromCamera(new THREE.Vector2(0, 0), camera);
const reliefObjects = getActiveReliefObjects();
const intersects = raycasterLocal.intersectObjects(reliefObjects, false);
if (intersects.length === 0) return;
const hit = intersects[0];
const indices = computeReliefIndices(hit);
if (!indices) return;
const { chunkIndex, dimIndex, source } = indices;
const value = source.matrix[chunkIndex][dimIndex];
const docId = source.id;
showBookmarkNamingModal({
docId,
chunkIndex,
dimensionIndex: dimIndex,
value,
}, (name) => {
addBookmark(name, docId, chunkIndex, dimIndex, value);
updateBookmarksUI();
}, controls);
});
}