hbauzan's picture
Release v4.17.7 β€” sync from GitHub main
819a47f
Raw
History Blame Contribute Delete
9.96 kB
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; }
}