All files / apps/web/src/components/graph3d gizmo.js

100% Statements 30/30
92.85% Branches 13/14
100% Functions 5/5
100% Lines 28/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93                                  1x             1x   1x             6x   6x 18x 18x 18x       18x                   18x 18x     6x 6x 6x 6x     6x 6x                   4x 2x 2x     2x 2x         2x 1x 5x 5x 5x   1x    
/**
 * Repère du centre d'orbite — repris du mode de navigation 3D de la veille
 * (`/home/simon/GIT/static/veille/src/lib/graph3d.js`) :
 *
 * - trois axes fins + un anneau matérialisent le **point autour duquel tourne la
 *   caméra** (la cible d'OrbitControls) : on comprend où l'on tourne ;
 * - il suit la cible quand on pan ou quand un cadrage la replace ;
 * - taille **constante à l'écran** (mise à l'échelle par la distance caméra),
 *   car c'est un repère d'interface, pas un objet du graphe ;
 * - exclu du raycasting pour ne jamais intercepter un clic.
 *
 * Les couleurs sont celles de nooloom (slate / teal / amber), pas celles de la veille.
 */
import * as THREE from 'three';
import { makeRingSprite } from './ring';
 
/** Axes du repère : X slate, Y teal (accent produit), Z amber. */
export const GIZMO_AXES = [
  { direction: [1, 0, 0], color: '#94a3b8' },
  { direction: [0, 1, 0], color: '#0d9488' },
  { direction: [0, 0, 1], color: '#d97706' },
];
 
/** Taille du repère en fraction de la distance caméra → constante à l'écran. */
const GIZMO_SCREEN_RATIO = 0.09;
 
const noRaycast = () => {};
 
/**
 * Construit le repère (à ajouter à `graph.scene()`).
 * @returns {THREE.Group}
 */
export function createGizmo() {
  const group = new THREE.Group();
 
  for (const { direction, color } of GIZMO_AXES) {
    const geometry = new THREE.BufferGeometry();
    const [x, y, z] = direction;
    geometry.setAttribute(
      'position',
      new THREE.BufferAttribute(new Float32Array([-x, -y, -z, x, y, z]), 3),
    );
    const line = new THREE.Line(
      geometry,
      new THREE.LineBasicMaterial({
        color,
        transparent: true,
        opacity: 0.55,
        depthWrite: false,
        depthTest: false,
      }),
    );
    line.raycast = noRaycast;
    group.add(line);
  }
 
  const ring = makeRingSprite('#0d9488', { opacity: 0.3, thickness: 4, scale: 0.8 });
  Eif (ring) {
    ring.raycast = noRaycast;
    group.add(ring);
  }
 
  group.renderOrder = 10;
  return group;
}
 
/**
 * Positionne et dimensionne le repère sur la cible d'orbite.
 * @param {THREE.Group|null} gizmo
 * @param {{x:number,y:number,z:number}|null} target cible d'OrbitControls
 * @param {{position:{distanceTo:Function}}|null} camera
 */
export function updateGizmo(gizmo, target, camera) {
  if (!gizmo || !target) return;
  gizmo.position.set(target.x, target.y, target.z);
  const distance = camera && typeof camera.position?.distanceTo === 'function'
    ? camera.position.distanceTo(target)
    : 0;
  gizmo.scale.setScalar(distance * GIZMO_SCREEN_RATIO || 20);
  gizmo.visible = true;
}
 
/** Libère les géométries/textures du repère et le retire de la scène. */
export function disposeGizmo(gizmo) {
  if (!gizmo) return;
  gizmo.traverse((object) => {
    object.geometry?.dispose?.();
    object.material?.map?.dispose?.();
    object.material?.dispose?.();
  });
  gizmo.removeFromParent?.();
}