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); }); }