Spaces:
Sleeping
Sleeping
| 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); | |
| }); | |
| } | |