Spaces:
Sleeping
Sleeping
| import * as THREE from 'three'; | |
| import { CONFIG, STATE } from '../core/State.js'; | |
| import { createLabelSprite } from './LabelHelper.js'; | |
| import { vertexShader, fragmentShader } from '../engine/Shaders.js'; | |
| import { PALETTE_3D, getActivationColorPerceptual as getActivationColor } from '../engine/palette.js'; | |
| // βββ Helpers CPU ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function lerp(a, b, t) { return a + (b - a) * t; } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| export function initInstancedMesh(count, scene) { | |
| // Redirect to initMacroMode logic | |
| initMacroMode(scene); | |
| return null; | |
| } | |
| export function initMacroMode(scene) { | |
| if (STATE.macroMeshes) { | |
| if (STATE.macroMeshes.lines) scene.remove(STATE.macroMeshes.lines); | |
| if (STATE.macroMeshes.points) scene.remove(STATE.macroMeshes.points); | |
| STATE.macroMeshes = null; | |
| } | |
| } | |
| export function updateMacroCloud(tokens, scene) { | |
| if (!tokens || tokens.length === 0) return; | |
| if (STATE.macroMeshes) { | |
| if (STATE.macroMeshes.lines) { | |
| scene.remove(STATE.macroMeshes.lines); | |
| if (STATE.macroMeshes.lines.geometry) STATE.macroMeshes.lines.geometry.dispose(); | |
| if (STATE.macroMeshes.lines.material) STATE.macroMeshes.lines.material.dispose(); | |
| } | |
| if (STATE.macroMeshes.points) { | |
| scene.remove(STATE.macroMeshes.points); | |
| if (STATE.macroMeshes.points.geometry) STATE.macroMeshes.points.geometry.dispose(); | |
| if (STATE.macroMeshes.points.material) STATE.macroMeshes.points.material.dispose(); | |
| } | |
| STATE.macroMeshes = null; | |
| } | |
| // Cleanup Legacy InstancedMesh | |
| if (STATE.instancedMesh) { | |
| scene.remove(STATE.instancedMesh); | |
| if (STATE.instancedMesh.geometry) STATE.instancedMesh.geometry.dispose(); | |
| if (STATE.instancedMesh.material) STATE.instancedMesh.material.dispose(); | |
| STATE.instancedMesh = null; | |
| } | |
| // βββ Prepare Data Buffers βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| let totalVertices = 0; | |
| tokens.forEach(t => { | |
| const len = t.embedding ? t.embedding.length : 0; | |
| if (len > 0) totalVertices += len; | |
| }); | |
| const posArray = new Float32Array(totalVertices * 3); // posiciones compartidas | |
| const lineColArray = new Float32Array(totalVertices * 3); // color dominante por thread (para la lΓnea) | |
| const intensityArr = new Float32Array(totalVertices); // valor de activaciΓ³n por vΓ©rtice (para el shader de puntos) | |
| const vtxIdxArr = new Float32Array(totalVertices); // Γndice local por vΓ©rtice (para highlight) | |
| const lineIndices = []; | |
| const mode = STATE.currentZMode || 'log'; | |
| const maxID = Math.max(...tokens.map(t => t.tokenID || t.id || 0), 1); | |
| const sortedTokens = [...tokens].sort((a, b) => (a.tokenID || a.id || 0) - (b.tokenID || b.id || 0)); | |
| const rankMap = new Map(); | |
| sortedTokens.forEach((t, i) => rankMap.set(t.id, i)); | |
| let globalVertexIndex = 0; | |
| tokens.forEach((t, tIdx) => { | |
| const vec = t.vector || t.embedding; | |
| if (!vec || vec.length === 0) return; | |
| const len = vec.length; | |
| // Mantener color de identidad solo para labels | |
| if (!t.color) { | |
| const colorHex = CONFIG.identityColors[tIdx % CONFIG.identityColors.length]; | |
| t.color = new THREE.Color(colorHex); | |
| } | |
| const rank = rankMap.get(t.id) ?? 0; | |
| const idVal = t.tokenID || t.id || 0; | |
| let offsetX = 0; | |
| let rotationZ = 0; | |
| const layoutMode = STATE.layoutMode || 'COMPARE'; | |
| if (layoutMode === 'BLANKET') { | |
| if (mode === 'linear') { | |
| offsetX = rank * CONFIG.threadSpacing; | |
| } else if (mode === 'log') { | |
| const ratio = idVal / maxID; | |
| offsetX = ratio * 200.0 * CONFIG.zScale.log * CONFIG.threadSpacing; | |
| } else if (mode === 'radial') { | |
| rotationZ = rank * STATE.radialStep; | |
| offsetX = rank * CONFIG.threadSpacing; | |
| } | |
| } else { | |
| offsetX = rank * (CONFIG.compareSpacing || 0.0); | |
| if (mode === 'radial') rotationZ = rank * STATE.radialStep; | |
| } | |
| // βββ Color dominante del thread: valor de mayor |activaciΓ³n| β color de la lΓnea βββ | |
| let maxAbsVal = 0, dominantVal = 0; | |
| for (let i = 0; i < len; i++) { | |
| if (Math.abs(vec[i]) > maxAbsVal) { maxAbsVal = Math.abs(vec[i]); dominantVal = vec[i]; } | |
| } | |
| const lineColor = getActivationColor(dominantVal); | |
| // Normalizador: garantiza que el valor mΓ‘ximo del token sea Β±1.0 en el shader | |
| const safeMax = maxAbsVal > 0 ? maxAbsVal : 1.0; | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| for (let i = 0; i < len; i++) { | |
| const localY = vec[i] * CONFIG.amplitudeScale; | |
| const localZ = i * CONFIG.pointSpacing * (CONFIG.threadWidth || 1.0); | |
| let finalX = 0, finalY = 0, finalZ = 0; | |
| if (mode === 'radial') { | |
| const v = new THREE.Vector3(0, localY, localZ); | |
| v.applyEuler(new THREE.Euler(0, -Math.PI / 2, 0)); | |
| v.applyAxisAngle(new THREE.Vector3(0, 0, 1), rotationZ); | |
| finalX = v.x; finalY = v.y; finalZ = v.z; | |
| } else { | |
| finalX = offsetX; finalY = localY; finalZ = localZ; | |
| } | |
| const ptr = globalVertexIndex * 3; | |
| posArray[ptr] = finalX; | |
| posArray[ptr + 1] = finalY; | |
| posArray[ptr + 2] = finalZ; | |
| // LΓnea: color dominante (uniforme por thread) | |
| lineColArray[ptr] = lineColor.r; | |
| lineColArray[ptr + 1] = lineColor.g; | |
| lineColArray[ptr + 2] = lineColor.b; | |
| // Puntos: valor de activaciΓ³n NORMALIZADO por vΓ©rtice β el shader GLSL calcula el color | |
| // La normalizaciΓ³n garantiza que los valores lleguen a Β±1.0 independientemente de la escala del modelo | |
| intensityArr[globalVertexIndex] = vec[i] / safeMax; | |
| vtxIdxArr[globalVertexIndex] = i; | |
| if (i < len - 1) lineIndices.push(globalVertexIndex, globalVertexIndex + 1); | |
| globalVertexIndex++; | |
| } | |
| // Labels (color de identidad se mantiene) | |
| if (STATE.scene) { | |
| const isHidden = STATE.hiddenThreads.has(tIdx); | |
| const tipY = vec[0] * CONFIG.amplitudeScale; | |
| let labelX = 0, labelY = 0, labelZ = 0; | |
| if (mode === 'radial') { | |
| const v = new THREE.Vector3(0, tipY, 0); | |
| v.applyEuler(new THREE.Euler(0, -Math.PI / 2, rotationZ)); | |
| labelX = v.x; labelY = v.y; labelZ = v.z; | |
| } else { | |
| labelX = offsetX; labelY = tipY; labelZ = 0; | |
| } | |
| if (t.labelSprite) { | |
| t.labelSprite.position.set(labelX, labelY + 1.5, labelZ); | |
| t.labelSprite.visible = !isHidden; | |
| } else if (!isHidden) { | |
| const sprite = createLabelSprite(`${t.label || tIdx} [${idVal}]`, labelX, labelY + 1.5, labelZ, t.color); | |
| STATE.scene.add(sprite); | |
| t.labelSprite = sprite; | |
| } | |
| } | |
| }); | |
| // βββ GeometrΓa de LΓneas: color dominante por thread ββββββββββββββββββββββββββ | |
| const lineGeo = new THREE.BufferGeometry(); | |
| lineGeo.setAttribute('position', new THREE.BufferAttribute(posArray, 3)); | |
| lineGeo.setAttribute('color', new THREE.BufferAttribute(lineColArray, 3)); | |
| lineGeo.setIndex(lineIndices); | |
| const lineMat = new THREE.LineBasicMaterial({ | |
| vertexColors: true, | |
| opacity: 0.55, | |
| transparent: true, | |
| linewidth: CONFIG.threadThickness || 1.0 | |
| }); | |
| // βββ GeometrΓa de Puntos: ShaderMaterial con intensidad por vΓ©rtice βββββββββββββββ | |
| const pointGeo = new THREE.BufferGeometry(); | |
| pointGeo.setAttribute('position', new THREE.BufferAttribute(posArray, 3)); | |
| pointGeo.setAttribute('intensity', new THREE.BufferAttribute(intensityArr, 1)); | |
| pointGeo.setAttribute('vertexIndex', new THREE.BufferAttribute(vtxIdxArr, 1)); | |
| const pointMat = new THREE.ShaderMaterial({ | |
| uniforms: { | |
| highlightIndex: { value: -1.0 }, | |
| pointSpacing: { value: CONFIG.pointSpacing }, | |
| // dotSize: tamaΓ±o visual del punto, escalado por amplitudeScale para compensar la escala del embedding | |
| dotSize: { value: CONFIG.pointSpacing * CONFIG.amplitudeScale * 0.8 }, | |
| opacity: { value: 1.0 }, | |
| color: { value: new THREE.Color(PALETTE_3D.AMBIENT_LIGHT) } | |
| }, | |
| vertexShader, | |
| fragmentShader, | |
| transparent: true, | |
| depthWrite: false, | |
| blending: THREE.NormalBlending | |
| }); | |
| const lines = new THREE.LineSegments(lineGeo, lineMat); | |
| const points = new THREE.Points(pointGeo, pointMat); | |
| lines.frustumCulled = false; | |
| points.frustumCulled = false; | |
| scene.add(lines); | |
| scene.add(points); | |
| STATE.macroMeshes = { lines, points }; | |
| } | |
| export function updateInstancedCloud(tokens) { | |
| if (STATE.scene) { updateMacroCloud(tokens, STATE.scene); return; } | |
| } | |