Spaces:
Sleeping
Sleeping
| import * as THREE from 'three'; | |
| import { CONFIG, STATE } from '../core/State.js'; | |
| import { clearRelief, isReliefActive } from './ReliefRenderer.js'; | |
| export function clearThreads(scene) { | |
| if (!STATE.threads) return; | |
| if (CONFIG.debug) console.log("[VISUALIZER] Clearing all threads"); | |
| STATE.threads.forEach(t => { | |
| if (t.lineMesh) { | |
| scene.remove(t.lineMesh); | |
| if (t.lineMesh.geometry) t.lineMesh.geometry.dispose(); | |
| if (t.lineMesh.material) t.lineMesh.material.dispose(); | |
| } | |
| if (t.pointsMesh) { | |
| scene.remove(t.pointsMesh); | |
| if (t.pointsMesh.material) t.pointsMesh.material.dispose(); | |
| } | |
| if (t.labelSprite) { | |
| scene.remove(t.labelSprite); | |
| if (t.labelSprite.material) { | |
| if (t.labelSprite.material.map) t.labelSprite.material.map.dispose(); | |
| t.labelSprite.material.dispose(); | |
| } | |
| } | |
| }); | |
| STATE.threads = []; | |
| STATE.focusedThreads.clear(); | |
| STATE.hiddenThreads.clear(); | |
| // Clear Instanced Mesh if exists | |
| 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; | |
| } | |
| // Clear Macro Meshes (New GPU Mode) | |
| 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; | |
| } | |
| } | |
| export function updateThreadVisibility() { | |
| if (!STATE.threads) return; | |
| const isRelief = (STATE.blanketSubMode === 'RELIEF'); | |
| // Automatically clean up active relief if we switch away from RELIEF submode | |
| if (!isRelief && isReliefActive() && STATE.scene) { | |
| clearRelief(STATE.scene); | |
| } | |
| // Update GPU Macro Meshes visibility | |
| if (STATE.macroMeshes) { | |
| if (STATE.macroMeshes.lines) STATE.macroMeshes.lines.visible = !isRelief; | |
| if (STATE.macroMeshes.points) STATE.macroMeshes.points.visible = !isRelief; | |
| } | |
| const hasFocus = STATE.focusedThreads.size > 0; | |
| STATE.threads.forEach((t, index) => { | |
| const isFocused = STATE.focusedThreads.has(index); | |
| const isHidden = STATE.hiddenThreads.has(index); | |
| // Visibility (Hidden) Logic | |
| if (isRelief || isHidden) { | |
| if (t.lineMesh) t.lineMesh.visible = false; | |
| if (t.pointsMesh) t.pointsMesh.visible = false; | |
| if (t.labelSprite) t.labelSprite.visible = false; | |
| return; // Skip opacity logic if hidden or in relief submode | |
| } else { | |
| if (t.lineMesh) t.lineMesh.visible = true; | |
| if (t.pointsMesh) t.pointsMesh.visible = true; | |
| if (t.labelSprite) t.labelSprite.visible = true; | |
| } | |
| // Polarity Filter Logic (Points Only) | |
| // We filter points by moving them out of view if they don't match the polarity | |
| const polarity = STATE.polarityMode || 'ALL'; | |
| if (t.pointsMesh && t.pointsMesh.geometry) { | |
| const positions = t.pointsMesh.geometry.attributes.position; | |
| const count = positions.count; | |
| let needsUpdate = false; | |
| for (let i = 0; i < count; i++) { | |
| const val = t.embedding[i]; | |
| let visible = true; | |
| if (polarity === 'POS' && val <= 0) visible = false; | |
| if (polarity === 'NEG' && val >= 0) visible = false; | |
| const currentY = positions.getY(i); | |
| // FIX: Flatten to 0 instead of moving to infinity | |
| const targetY = visible ? (val * CONFIG.amplitudeScale) : 0; | |
| // Optimization: Only update if changed | |
| if (Math.abs(currentY - targetY) > 0.001) { | |
| positions.setY(i, targetY); | |
| needsUpdate = true; | |
| } | |
| } | |
| if (needsUpdate) { | |
| positions.needsUpdate = true; | |
| } | |
| } | |
| // Focus (Opacity) Logic | |
| let targetOpacity = 1.0; | |
| let targetLineWidth = 1; | |
| if (hasFocus) { | |
| if (isFocused) { | |
| targetOpacity = 1.0; | |
| targetLineWidth = 2; | |
| } else { | |
| targetOpacity = 0.05; | |
| targetLineWidth = 1; | |
| } | |
| } else { | |
| targetOpacity = 1.0; | |
| targetLineWidth = 1; | |
| } | |
| // Apply to Line | |
| if (t.lineMesh && t.lineMesh.material) { | |
| t.lineMesh.material.opacity = targetOpacity; | |
| t.lineMesh.material.linewidth = targetLineWidth; | |
| t.lineMesh.material.needsUpdate = true; | |
| } | |
| // Apply to Points (Shader) | |
| if (t.pointsMesh && t.pointsMesh.material && t.pointsMesh.material.uniforms) { | |
| t.pointsMesh.material.uniforms.opacity.value = targetOpacity; | |
| } | |
| // Apply to Sprite | |
| if (t.labelSprite) { | |
| t.labelSprite.material.opacity = targetOpacity; | |
| } | |
| }); | |
| } | |
| export function updateThreadColor(thread, colorHex) { | |
| if (!thread) return; | |
| const newColor = new THREE.Color(colorHex); | |
| thread.color = newColor; | |
| // 1. Update Line | |
| if (thread.lineMesh && thread.lineMesh.material) { | |
| thread.lineMesh.material.color.set(newColor); | |
| thread.lineMesh.material.needsUpdate = true; | |
| } | |
| // 2. Update Points | |
| if (thread.pointsMesh && thread.pointsMesh.material) { | |
| if (thread.pointsMesh.material.uniforms && thread.pointsMesh.material.uniforms.color) { | |
| thread.pointsMesh.material.uniforms.color.value.set(newColor); | |
| } else if (thread.pointsMesh.material.color) { | |
| thread.pointsMesh.material.color.set(newColor); | |
| } | |
| thread.pointsMesh.material.needsUpdate = true; | |
| } | |
| // 3. Update Label (Fast Tinting) | |
| if (thread.labelSprite && thread.labelSprite.material) { | |
| thread.labelSprite.material.color.set(newColor); | |
| thread.labelSprite.material.needsUpdate = true; | |
| } | |
| } | |