Three.js
Travelling
2026 - 2027
Déplacer un objet le long d'une courbe
Étapes
- Setup three.js + un cube
- Ajouter une courbe catmull
- Visualiser la courbe
- Préparer un mesh-wagon
- Positionner sur la courbe
- Animer le wagon
- 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
- Créer une nouvelle caméra
- Positionner la caméra sur la courbe
- Animer la caméra sur la courbe
- 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
- Installer GSAP
- Créer des boutons de contrôle
- Ajouter de l'interaction
Importer une courbe depuis Blender
Étapes
- Setup Blender
- Exporter la scene
- Importer la scene
- Courbe de bézier en JSON
- Recréer la courbe
- 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