Three.js

Travelling

2026 - 2027

Déplacer un objet le long d'une courbe

Étapes

  1. Setup three.js + un cube
  2. Ajouter une courbe catmull
  3. Visualiser la courbe
  4. Préparer un mesh-wagon
  5. Positionner sur la courbe
  6. Animer le wagon
  7. Aligner le wagon sur la courbe

Setup three.js + un cube

Ajouter une
courbe catmull

Une courbe de Catmull–Rom est une courbe lisse qui interpole un ensemble de points : elle passe exactement par chacun d’eux tout en créant une trajectoire fluide entre les points grâce à un calcul de tangentes automatiques.

La méthode CatmullRomCurve3 peut avoir 4 paramètres :

  • points : un tableau de vecteur3 (x,y,z)
  • closed : un boolean qui indique si la courbe boucle (optionnel)
  • curveType : le type de courbe (optionnel)
  • points : la tension de la courbe (optionnel)

Ajoutons 4 points à notre courbe, qui se ferme sur elle-même :

const curve = new THREE.CatmullRomCurve3( [
  new THREE.Vector3( 0, 0, -4 ),
  new THREE.Vector3( 4, 0, 0 ),
  new THREE.Vector3( 0, 0, 4 ),
  new THREE.Vector3( -4, 0, 0 ),
], true );

Visualiser la courbe

La courbe est définie et nous donne accès à des méthodes de positionnement. Par contre on ne peut pas la visualiser.

Réglons çà : nous allons créer un mesh de type Line à partir d’une série de points générés le long de la courbe via la méthode getPoints( ).

// On récupère 50 points le long de notre courbe
const points = curve.getPoints( 50 );

// On crée une géometrie à partir de ces points
const curveGeometry = new THREE.BufferGeometry().setFromPoints( points );

// Défini le material de la courbe
const curveMaterial = new THREE.LineBasicMaterial( { color: 'gold' } );

// On crée un mesh de type Line
const curveObject = new THREE.Line( curveGeometry, curveMaterial );

Créer un mesh-wagon

On va prévoir un mesh qui naviguera le long de notre courbe : un cube.

Positionner sur la courbe

La méthode getPoint( progression ) permet de récupérer une position à un endroit donné de notre courbe. La progression est un nombre entre 0 (début) et 1 (fin).

// On récupère un vecteur3 à 50% de la courbe
const pos = curve.getPoint( .5 );
Retourne un vecteur3.

Il suffit maintenant d'utiliser ce vecteur3 pour positionner notre wagon :

const pos = curve.getPoint( .5 );
cube.position.set(pos.x, pos.y, pos.z);

Animer le wagon

Si on modifie la valeur de progression à travers le temps, on va pouvoir déplacer notre wagon le long de notre courbe.

Nous allons prévoir un objet values avec une propriété progression et la lier avec Tweakpane.

N'oubliez pas qu'il faut avoir importé la librairie Tweakpane(c'est déjà le cas dans nos templates).
// Initie le panneau Tweakpane
const pane = new Pane();

// Défini un objet qui contient notre propriété "progression"
const values = {
  progression: 0
};

// Lie la propriété à Tweakpane
pane.addBinding( values, "progression", {
  min: 0,
  max: 2,
  step: 0.001
});

Pour visualiser le changement de valeur, on va mettre en place une fonction animate appelé par un requestAnimationFrame.

Dans cette fonction, nous allons recalculé à chaque frame la nouvelle position de notre wagon via values.progression et demandé un rendu.

N'oublions pas de rendre disponible notre courbe et notre wagon à l'extérieur de notre viewer (ex: this.wagon = wagon).
const animate = () => {

  // Calcule la nouvelle position sur base de la valeur de progression
  const newPos = myViewer.curve.getPoint( values.progression );

  // Modifie la position du wagon
  myViewer.wagon.position.set( newPos.x, newPos.y, newPos.z );

  // Demande un rendu
  myViewer.render();

  // Appelle 'animate' à la prochaine frame disponible
  window.requestAnimationFrame( animate );
};

// Premier appel d'"animate"
window.requestAnimationFrame( animate );

On a la confirmation que notre objet tourne autour de notre courbe, maintenant il nous suffit d'animer la valeur de progression à travers le temps et plus via Tweakpane.

const animate = () => {
  // On modifie la valeur de progression en ajoutant 0.001
  // pour ensuite mapper la valeur sur 1 via l'opérateur remainder(%)
  // cela permet d'avoir toujours une valeur entre 0 -> 1
  values.progression = (values.progression + 0.001) % 1;

  const newPos = myViewer.curve.getPoint( values.progression );
  myViewer.wagon.position.set( newPos.x, newPos.y, newPos.z );
  myViewer.render();
  window.requestAnimationFrame( animate );
};

window.requestAnimationFrame( animate );

Aligner le wagon sur la courbe

Notre cube se déplace sans prendre en compte la direction de la courbe. En combinant la méthode getTangent( progression ) à lookAt( ), on va pouvoir rendre le déplacement plus naturel :

getTangent( ) retourne un vecteur3.
const animate = () => {
  values.progression = (values.progression + 0.001) % 1;
  const newPos = myViewer.curve.getPoint( values.progression );

  // Récupère la tangente basé sur la progression
  const tangent = myViewer.curve.getTangent( values.progression );

  myViewer.wagon.position.set( newPos.x, newPos.y, newPos.z );

  // Clone le vecteur3 "coord" et on ajoute la valeur de "tangent",
  // LookAt attend un vecteur3
  myViewer.wagon.lookAt( coord.clone().add( tangent ) );
  myViewer.render();
  window.requestAnimationFrame( animate );
};

window.requestAnimationFrame( animate );

Remplacer le wagon par une caméra

Étapes

  1. Créer une nouvelle caméra
  2. Positionner la caméra sur la courbe
  3. Animer la caméra sur la courbe
  4. Switcher de caméra

Créer une nouvelle caméra

Nous allons créer une deuxième caméra pour remplacer notre wagon. Il faut rajouter cette caméra dans notre scene.

const cameraWagon = new THREE.PerspectiveCamera(
    fov,
    aspect-ratio,
    near,
    far
);
…
// Ne pas oublier d'ajouter la camera à la scene
scene.add( cameraWagon );

Positionner la caméra sur la courbe

Après la définition de notre courbe, nous allons positionner notre caméra sur cette dernière via la même technique que pour le cube/wagon. On va aussi utiliser lookAt() pour regarder le centre de notre scène.

Il va falloir demander à utiliser notre camera lors de notre rendu.

const coord = curve.getPoint( progression );
cameraWagon.position.set( coord.x, coord.y, coord.z );
cameraWagon.lookAt( 0, 0, 0 );

renderer.render( scene, cameraWagon );

Animer la caméra sur la courbe

Il suffit de mettre à jour notre caméra dans notre boucle d'animation :

Switcher la caméra

En utilisant Tweakpane et une propriété currentCamera, on pourra interchanger la caméra de rendu via un boolean :

Travelling en étapes

Étapes

  1. Installer GSAP
  2. Créer des boutons de contrôle
  3. Ajouter de l'interaction

Importer une courbe depuis Blender

Étapes

  1. Setup Blender
  2. Exporter la scene
  3. Importer la scene
  4. Courbe de bézier en JSON
  5. Recréer la courbe
  6. Ajouter le wagon

Setup Blender

Il sera quand même plus simple d'importer une courbe réalisée directement dans Blender que de la coder à la main. Dans un premier temps nous allons partir d'un exemple contenant une scène type :

Il est important que votre courbe soit de type Bézier pour cette technique.

Exporter la scene

Pour exporter correctement notre scène vers un projet three.js, il faut s'assurer de plusieurs choses :

  • avoir nommer les éléments
  • appliquer les transformations des objets
  • mettre à jour l'origine de la courbe
  • cocher les bonnes options d'exportation en glTF

Nommer les calques

Nommer les calques permet de facilement identifier les différents éléments de notre scène une fois qu'elle sera importée dans notre projet three.js :

Appliquer les transformations

Certaines transformations comme scale, rotation ou location sont définies dans blender lors de la manipulation des éléments. Pour s'assurer une transition sans encombre, il faut les appliquer. En sélectionnant un élément, on trouvera ensuite dans le menu Object l'option Apply avec plusieurs possibilités :

Origine de la courbe

Un autre point important est de redéfinir l'origine de la courbe pour éviter des décalages lors des différents exports. Après sélection, toujours dans le menu Object se trouve le sous-menu Set Origin l'option Origin to 3D Cursor.

On peut maintenant exporter au format glTF depuis l'onglet file > exporter > glTF 2.0. Un menu avec des options apparaîtra, n'oublions pas de cocher 3 options :

  • Transform > + Y Up
  • Mesh > Loose Edges
  • Mesh > Loose Points

Prévisualiser

Ces options permettent de visualiser la courbe une fois dans un environnement threejs. Une bonne pratique est d'aller tester notre fichier directement dans l'éditeur :

Importer la scène

Charger le glTF

Pour impoter la scène dans notre projet, il faut ajouter l'extention GLTFLoader. Ensuite nous pourrons charger notre fichier .glb :

const loader = new GLTFLoader();
const gltf = await loader.loadAsync( "path/to/model.glb" );

Ajouter dans la scène

Au plus simple il suffit d'ajouter directement gltf.scene avec notre objet scene :

scene.add( gltf.scene );
La scene de blender deviendra un Group dans threejs.

Cibler par nom

Nos objets sont noirs, ils ont un MeshStandardMaterial qui est très gourmand en performance et attend au moins une source lumineuse pour être visible.

Dans notre cas nous allons simplement cibler nos objets par leurs noms et modifier leurs matériaux pour des MeshBasicMaterial via la méthode de getObjectByName() :

const rocks = scene.getObjectByName( "rocks" );
rocks.material = new THREE.MeshBasicMaterial( { color: 'grey' } );

Courbe en JSON

Nous avons bien notre courbe mais uniquement au format mesh. Ce qui veut dire que nous ne pouvons plus accèder à la courbe sous forme de données.

Heureusement une extension existe pour venir à notre rescousse.

Extension

La première chose à faire est d'installer Bezier2JSON depuis l'onglet Edit > Preferences… > Add-ons.

Ensuite il faut aller chercher le menu sous une flèche située en haut à droite de la fenêtre et choisir Install from Disk… et choisir le dossier compressé que je vous fourni.

Exporter les données

Grâce à cette extension, nous pouvons maintenant exporter les données de notre courbe au format JSON. Depuis l'onglet File > Export > Export Bezier2JSON.

Attention, on ne peut qu'exporter qu'une seule courbe dans votre scene. Si vous en avez plusieurs, il faudra avoir un fichier .blend dédié.

Le JSON

On obtient un .json contenant un tableau de point représentant chaque point d'ancrage et leurs 2 points de contrôle.

  • px, py, pz : le vecteur3 représentant le point d'ancrage
  • hlx, hly, hlz : le vecteur3 représentant le point de contrôle de gauche
  • hrx, hry, hrz : le vecteur3 représentant le point de contrôle de droite

Recréer la courbe

Charger le JSON

La première chose à faire est de charger le .json dans notre projet via le Fetch API.

const dataUrl = "path/to/data.json";

const loadJson = async ( url ) => {
  const response = await fetch( url );
  const data = await response.json();
  return data;
  console.log(data);
};

const points = await loadJson( dataUrl );

Reconstruire la courbe

Pour recréer la totalité de la courbe, nous allons devoir combiner chaque segment qui l'a compose dans un CurvePath. On y ajoutera chaque pair de points, sous la forme d'un CubicBezierCurve3.

const masterCurve = new THREE.CurvePath();

// Boucle sur tout les points d'ancrages
// de notre courbe exportée depuis Blender
for( const point of points ){

  // Affiche dans la console la coordonnée "x" du point d'ancrage
  console.log(point.px);
}

Dans notre boucle, nous voulons pouvoir relier 2 points d'ancrage entre eux avec 2 points de contrôle. Il faudra donc aller chercher le prochain point dans notre liste pour l'utiliser avec CubicBezierCurve3 et ensuite l'ajouter à notre masterCurve avec la méthode add().

Attention dans notre exemple, notre courbe boucle sur elle même. Cela peut ne pas être le cas et donc il faudra vérifier quand on atteint le dernier point.
let index = 0;
for( const point of points ){

  // On va chercher le prochain point dans la liste.
  // Si l'index est plus grand que la longueur, l'opérateur
  // remainder (%) avec la longueur de notre tableau fera en sorte
  // de revenir au début de la boucle.
  const nextPoint = points[ ( index + 1 ) % points.length ];

  console.log( point, nextPoint );
  index++;
}

Il faut maintenant utiliser chaque coordonnées pour créer des vecteur3 qui représentent nos points d'ancrages et de contrôles.

On crée donc 4 vecteur3 à passer dans CubicBezierCurve3, qu'on ajoute ensuite à la marsterCurve :

Comme nous manipulons des données, il serait bon de pouvoir débugger la courbe en la visualisant. Si nos 2 courbes se superposent cela prouvera que les données sont manipuler correctement.

const curvePoints = masterCurve.getPoints( 50 );
const curveGeo = new THREE.BufferGeometry().setFromPoints( curvePoints );
const curveMat = new THREE.LineBasicMaterial( { color: "slateblue" });
const curveMesh = new THREE.Line( curveGeo, curveMat );
this.scene.add( curveMesh );

Problème : notre courbe semble tournée à 90 degré. Avant de s'arracher les cheveux, n'oublions pas que Blender et three.js n'ont pas les mêmes axes. Il suffit de remplacer l'axe Y par le Z dans la création de la courbe :

Les deux courbes se superposent et créent un Z-fighting : notre courbe est donc correcte.

Ajouter le wagon

1/5

1/1