hbauzan's picture
Release v4.17.7 — sync from GitHub main
819a47f
Raw
History Blame Contribute Delete
7.2 kB
import * as THREE from 'three';
import { CONFIG, STATE } from '../core/State.js';
import { vertexShader, fragmentShader } from '../engine/Shaders.js';
import { updateThreadTransform } from './LayoutEngine.js';
import { updateThreadVisibility } from './ThreadManager.js';
import { createLabelSprite } from './LabelHelper.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 createThread(label, embedding, scene, colorHex, tokenID = 0) {
if (CONFIG.debug) console.log(`[VISUALIZER] Creating thread for: "${label}" (ID: ${tokenID}) with color: ${colorHex.toString(16)}`);
const count = embedding.length;
STATE.vertexCount = count;
// 1. Data Prep
const points = [];
const intensities = [];
const indices = [];
// Color dominante del thread: valor con mayor |activación| → define el color de la línea
let maxAbsVal = 0, dominantVal = 0;
for (let i = 0; i < count; i++) {
const absV = Math.abs(embedding[i]);
if (absV > maxAbsVal) { maxAbsVal = absV; dominantVal = embedding[i]; }
}
const lineActivation = getActivationColor(dominantVal);
// Normalizador: garantiza que el mayor valor llegue a ±1.0 en el shader
const safeMax = maxAbsVal > 0 ? maxAbsVal : 1.0;
for (let i = 0; i < count; i++) {
const val = embedding[i];
const x = 0; // Geometría extendida a lo largo del eje Z
const y = val * CONFIG.amplitudeScale;
const z = i * CONFIG.pointSpacing;
points.push(x, y, z);
intensities.push(val / safeMax); // Normalizado → rango [-1, 1]
indices.push(i);
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(points, 3));
geometry.setAttribute('intensity', new THREE.Float32BufferAttribute(intensities, 1));
geometry.setAttribute('vertexIndex', new THREE.Float32BufferAttribute(indices, 1));
// 2. Línea: color uniforme derivado del valor dominante del thread
const lineMaterial = new THREE.LineBasicMaterial({
color: new THREE.Color(lineActivation.r, lineActivation.g, lineActivation.b),
linewidth: CONFIG.threadThickness || 1.0,
opacity: 0.6,
transparent: true
});
const lineMesh = new THREE.Line(geometry, lineMaterial);
// PREVENT FRUSTRUM CULLING ISSUES (Common cause of disappearing meshes)
lineMesh.frustumCulled = false;
// 3. Points Mesh (Heatmap Shader)
const uniforms = {
highlightIndex: { value: -1.0 },
pointSpacing: { value: CONFIG.pointSpacing },
dotSize: { value: CONFIG.pointSpacing * CONFIG.amplitudeScale * 0.8 },
opacity: { value: 1.0 },
color: { value: new THREE.Color(colorHex) }
};
const pointsMaterial = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: vertexShader,
fragmentShader: fragmentShader,
transparent: true,
depthWrite: false,
blending: THREE.NormalBlending
});
const pointsMesh = new THREE.Points(geometry, pointsMaterial);
pointsMesh.frustumCulled = false;
// 4. Critical: Add to Scene
scene.add(lineMesh);
scene.add(pointsMesh);
// 5. Label Sprite
const labelText = `${label} [${tokenID}]`;
const sprite = createLabelSprite(labelText, 0, embedding[0] * CONFIG.amplitudeScale + 0.5, 0, new THREE.Color(colorHex));
scene.add(sprite);
const thread = {
id: Date.now() + Math.random(),
label: label,
lineMesh,
pointsMesh,
uniforms,
embedding,
color: new THREE.Color(colorHex),
tokenID: tokenID,
labelSprite: sprite
};
// Apply Transform (Position/Rotation) based on current mode
updateThreadTransform(thread);
// Apply initial visibility based on current state
const hasFocus = STATE.focusedThreads.size > 0;
// New threads are not focused by default if focus mode is on
const initialOpacity = hasFocus ? 0.05 : 1.0;
lineMesh.material.opacity = initialOpacity;
uniforms.opacity.value = initialOpacity;
updateThreadVisibility();
return thread;
}
export function createGhostThread(vectorArray, label, scene, tokenID = 0) {
if (CONFIG.debug) console.log(`[VISUALIZER] Creating Ghost Thread: "${label}" (ID: ${tokenID})`);
const count = vectorArray.length;
const points = [];
for (let i = 0; i < count; i++) {
const x = 0;
const y = vectorArray[i] * CONFIG.amplitudeScale;
const z = i * CONFIG.pointSpacing;
points.push(new THREE.Vector3(x, y, z));
}
const geometry = new THREE.BufferGeometry().setFromPoints(points);
// CRITICAL: LineDashedMaterial requires computeLineDistances
// Result Thread Styling: Neon Fuchsia, thicker, on top
const material = new THREE.LineDashedMaterial({
color: PALETTE_3D.THREAD_RESULT, // Neon Fuchsia
dashSize: 3.0,
gapSize: 1.0,
linewidth: 3, // Attempt thicker line
scale: 1,
opacity: 1.0,
transparent: true,
depthTest: false // Always render on top
});
const line = new THREE.Line(geometry, material);
line.frustumCulled = false;
line.renderOrder = 999; // High render order
// Add to Scene
scene.add(line);
line.computeLineDistances(); // Essential for dashes to appear
// Label Sprite for Ghost
// Note: zPos is 0 initially, updated by updateThreadTransform
const labelText = `${label} [${tokenID}]`;
const sprite = createLabelSprite(labelText, 0, vectorArray[0] * CONFIG.amplitudeScale + 1.0, 0, new THREE.Color(PALETTE_3D.THREAD_RESULT));
scene.add(sprite);
const thread = {
id: 'ghost-' + Date.now(),
label: label,
lineMesh: line,
embedding: vectorArray,
isGhost: true,
color: new THREE.Color(PALETTE_3D.THREAD_RESULT),
tokenID: tokenID,
labelSprite: sprite
};
// Apply Transform
updateThreadTransform(thread);
// Apply initial visibility
const hasFocus = STATE.focusedThreads.size > 0;
// Ghost thread is usually the result, so maybe we want to see it?
// But if focus mode is strictly "only focused items", and it's not focused, it should be dimmed.
// However, usually the user wants to see the result.
// For now, adhere to strict logic: if not in focusedThreads, dim it.
// But wait, the user might want to focus the result.
const initialOpacity = hasFocus ? 0.05 : 1.0;
line.material.opacity = initialOpacity;
updateThreadVisibility();
return thread;
}