hbauzan's picture
Release v4.17.7 — sync from GitHub main
819a47f
Raw
History Blame Contribute Delete
4.95 kB
import * as THREE from 'three';
import { CONFIG, STATE } from '../core/State.js';
import { updateInstancedCloud } from './Instancer.js';
export function updateAllThreadPositions() {
if (STATE.engineMode === 'MACRO') {
// Update Instanced Mesh
if (STATE.threads) {
updateInstancedCloud(STATE.threads);
}
return;
}
if (!STATE.threads) return;
if (CONFIG.debug) console.log(`[VISUALIZER] Updating Positions (Mode: ${STATE.currentZMode})`);
// Calculate Max ID for Normalization
// Ensure at least 1 to avoid division by zero
const maxID = Math.max(...STATE.threads.map(t => t.tokenID || 0), 1);
// Sort first to determine order (Rank-Based Distribution)
// This ensures threads with similar Token IDs are visually adjacent
const sortedThreads = [...STATE.threads].sort((a, b) => (a.tokenID || 0) - (b.tokenID || 0));
sortedThreads.forEach((t, rank) => {
updateThreadTransform(t, rank, maxID);
});
}
export function updateThreadTransform(thread, rank, maxID = 1) {
const mode = STATE.currentZMode || 'log';
const layoutMode = STATE.layoutMode || 'COMPARE';
// 1. Update Geometry (Internal Z-Spacing and Width)
updateGeometryZ(thread);
// 2. Calculate Mesh Position & Rotation
let xPos = 0;
let yPos = 0;
let zPos = 0;
let zRot = 0;
if (layoutMode === 'BLANKET') {
if (mode === 'linear') {
// Linear Mode: Simple spacing side-by-side along X
xPos = (rank !== undefined ? rank : 0) * CONFIG.threadSpacing;
} else if (mode === 'log') {
// Token ID Mode (Normalized Horizontal)
const ratio = (thread.tokenID || 0) / maxID;
const width = 200.0;
xPos = ratio * width * CONFIG.zScale.log * CONFIG.threadSpacing;
} else if (mode === 'radial') {
zRot = (rank !== undefined ? rank : 0) * STATE.radialStep;
xPos = (rank !== undefined ? rank : 0) * CONFIG.threadSpacing; // Offset radially? Just offset X for now
}
} else {
// COMPARE Mode: Threads overlap by default, or spread by compareSpacing
xPos = (rank !== undefined ? rank : 0) * (CONFIG.compareSpacing || 0.0);
if (mode === 'radial') {
zRot = (rank !== undefined ? rank : 0) * STATE.radialStep;
}
}
// 3. Apply to Meshes
if (thread.lineMesh) {
thread.lineMesh.position.set(xPos, yPos, zPos);
if (mode === 'radial') {
thread.lineMesh.rotation.set(0, -Math.PI / 2, zRot);
} else {
thread.lineMesh.rotation.set(0, 0, zRot);
}
}
if (thread.pointsMesh) {
thread.pointsMesh.position.set(xPos, yPos, zPos);
if (mode === 'radial') {
thread.pointsMesh.rotation.set(0, -Math.PI / 2, zRot);
} else {
thread.pointsMesh.rotation.set(0, 0, zRot);
}
}
// 4. Update Label Sprite Position
if (thread.labelSprite) {
// The label should be at the "START" of the thread (Index 0)
// Local position of the start (relative to mesh origin)
const tipLocal = new THREE.Vector3(0, 0, 0);
// Apply Rotation
if (mode === 'radial') {
// Match the mesh rotation
tipLocal.applyEuler(new THREE.Euler(0, -Math.PI / 2, zRot));
} else {
// Linear/Log usually have 0 rotation
tipLocal.applyEuler(new THREE.Euler(0, 0, zRot));
}
// Apply Translation (Mesh Position)
// Note: Mesh position is (xPos, yPos, zPos)
// So global tip = tipLocal + MeshPosition
const tipGlobal = tipLocal.clone().add(new THREE.Vector3(xPos, yPos, zPos));
// Adjust Y for label height (Amplitude of FIRST point)
const firstY = (thread.embedding && thread.embedding.length > 0)
? (thread.embedding[0] * CONFIG.amplitudeScale)
: 0.0;
// Global Position (Float 1.5 units above the start)
thread.labelSprite.position.set(tipGlobal.x, firstY + 1.5, tipGlobal.z);
}
}
export function updateGeometryZ(thread) {
if (!thread.lineMesh || !thread.lineMesh.geometry) return;
const geometry = thread.lineMesh.geometry;
const positions = geometry.attributes.position;
const count = positions.count;
// CONSTANT GEOMETRY: The thread shape is always the same.
// X: 0
// Y: Amplitude
// Z: Linear Length (0 to Length) scaled by threadWidth
const spacing = CONFIG.pointSpacing * (CONFIG.threadWidth || 1.0);
for (let i = 0; i < count; i++) {
// X is always 0
positions.setX(i, 0);
// Y is Amplitude (Preserved)
// We assume Y is already correct from creation/updates.
// Z is always linear internal length scaled
positions.setZ(i, i * spacing);
}
positions.needsUpdate = true;
geometry.computeBoundingSphere();
}