
Animer le système Terre-Lune en HTML et JavaScript (2/2)
Dans l’article précédent, nous avons exploré comment créer une scène 3D dynamique du système Terre-Lune en utilisant la librairie Globe.GL. Grâce à cette approche, nous avons pu animer le système en nous appuyant sur des coordonnées GPS, qu’elles soient en temps réel, historiques ou simulées. Cependant, l’accès limité à ces données constitue une contrainte importante. Dans ce nouvel article, nous allons découvrir une méthode alternative pour animer le système Terre-Lune, sans recourir aux coordonnées GPS.
Analyse du système Terre-Lune
De nombreuses informations sur le comportement du système Terre-Lune sont disponibles en ligne. Voici les éléments essentiels à retenir pour notre simulation :
- La Terre effectue une rotation complète sur elle-même en 24 heures
- La Lune orbite autour de la Terre en 27,3 jours
- La Lune réalise également une rotation sur elle-même en 27,3 jours (d’où l’aspect synchronisé qui nous montre toujours la même face de la Lune depuis la Terre)
- L’orbite de la Lune est inclinée de 5,1° par rapport au plan de l’écliptique (le plan dans lequel la Terre orbite autour du Soleil)
- La Lune est inclinée de 6,7° par rapport à son propre plan orbital.
Pour simplifier notre simulation, nous allons volontairement négliger certaines variabilités complexes. Par exemple, bien que l’orbite de la Lune soit en réalité une ellipse, nous la considérons comme un cercle.
Les solutions d’animation envisageables
Avec toutes les informations nécessaires pour modéliser le système Terre-Lune, deux approches principales s’offrent à nous :
- Utiliser une formule mathématique pour calculer les coordonnées spatiales de la Lune en fonction du temps
- Exploiter les fonctionnalités du moteur 3D Three.js, qui permet d’appliquer des rotations aux objets autour des différents axes du système.
Le choix entre ces solutions dépend de vos compétences et de vos préférences. Personnellement, j’opte pour la seconde méthode, car elle est plus simple à mettre en œuvre. En effet, la manipulation d’objets dans l’espace avec Three.js est plus intuitive grâce à ses nombreuses fonctions et classes.
Animer la rotation de la Terre
Pour animer la rotation de la Terre sur elle-même, nous devons appliquer une rotation de l’objet 3D selon l’axe pôle Sud/pôle Nord, dans le sens horaire.
Approches pour animer la rotation
- Utilisation d’
OrbitControls
: la solution la plus simple consiste à utiliser la rotation automatique de l’objetOrbitControls
fourni par Three.js. Cet outil contrôle la rotation de la caméra autour de l’axe d’observation, créant l’illusion visuelle d’une Terre en rotation sans appliquer directement une rotation à l’objet Terre. - Rotation directe de l’objet Terre : une autre solution consiste à accéder directement à l’objet 3D représentant la Terre et à y appliquer une rotation explicite autour de l’axe défini par le vecteur
(0, 1, 0)
, qui correspond à l’axe pôle Sud/pôle Nord.
Limites de la librairie Globe.GL
Limites de l’approche avec OrbitControls
Lors de tests, l’utilisation d’OrbitControls
a révélé une faiblesse majeure : si l’utilisateur interagit avec la caméra (par exemple, via un cliquer-glisser), la rotation de la Terre s’arrête temporairement jusqu’à la reprise automatique de l’animation. Cela entraîne un comportement non souhaité, comme observé dans l’exemple Globe.GL suivant :
Impact sur l’animation de la Lune
Quel que soit le choix, les deux approches affectent le Custom Layer de la Lune, la rendant fixe par rapport à la Terre. Pour résoudre ce problème, il est nécessaire de recalculer la vitesse de rotation de la Lune en prenant la Terre comme référentiel.
Étendre les fonctionnalités avec Three.js
Les limitations de la librairie Globe.GL sont atteintes. Néanmoins, elle permet d’accéder aux objets Three.js sous-jacents, comme la Scene
ou la Camera
. En combinant Globe.GL et Three.js, nous pouvons étendre les fonctionnalités pour répondre à nos besoins spécifiques.
Implémentation dans JavaScript
Modifions notre script pour animer explicitement la Terre tout en supprimant la Lune du Custom Layer :
// /lib/script.js
import {
Mesh,
SphereGeometry,
MeshStandardMaterial,
+ Vector3,
+ Clock,
} from 'https://unpkg.com/three/build/three.module.js';
// ...
const MOON_RADIUS = 1737.4;
+// Animation constants (in second)
+const DAY_IN_SECOND = 24 * 3600;
+const TIME_SCALE = DAY_IN_SECOND / 10; // 1 jour en 10 secondes
+
+const EARTH_ROTATION_VELOCITY = 2 * Math.PI / DAY_IN_SECOND; <1>
+
+
// Configuration de la scene et de la Terre
// ...
.backgroundImageUrl('https://unpkg.com/three-globe/example/img/night-sky.png')
- .customLayerData([{
- lat: -5,
- lng: 277,
- alt: 2.5, // Cette altitude n'est pas à l'échelle
- radius: MOON_RADIUS / EARTH_RADIUS,
- textures: {
- map: MOON_TEXTURE,
- displacementMap: BUMPMOON_TEXTURE,
- }
- }])
- .customThreeObject(moon => new Mesh(
- new SphereGeometry(world.getGlobeRadius() * moon.radius),
- new MeshStandardMaterial(moon.textures),
- ))
- .customThreeObjectUpdate((obj, moon) => {
- Object.assign(obj.position, world.getCoords(moon.lat, moon.lng, moon.alt));
- })
.pointOfView({ altitude: 7 })
+ .onGlobeReady(() => {
+ const earth = scene.children[scene.children.length - 1]; <2>
+
+ const clock = new Clock(); <3>
+ (function animate() {
+ const delta = clock.getDelta(); <3>
+
+ earth.rotateOnAxis(new Vector3(0, 1, 0), delta * TIME_SCALE * EARTH_ROTATION_VELOCITY); <4>
+
+ window.requestAnimationFrame(animate);
+ })(); <5>
+ })
;
// ...
La rotation est exprimée en radian, un tour complet (360°) correspond à 2π radians.
L’objet 3D Terre est le dernier élément ajouté à la scène par la librairie Globe.GL. Nous pouvons donc l’extraire du tableau children
.
La classe Clock
nous est utile pour déterminer la distance angulaire parcourue entre chaque frame.
Nous appliquons la rotation selon la durée entre chaque frame et l’échelle de temps choisie.
Nous déclarons et nous exécutons la fonction animate
en une seule instruction. Elle doit être appelée par window.requestAnimationFrame
à chaque nouvelle frame.
Résultat
Après ces modifications, seule la Terre tourne sur elle-même indépendamment des mouvements de la caméra, préparant le terrain pour une animation correcte de la Lune.
Organiser la scène pour ajouter la Lune
L’ajout de la Lune à la scène nécessite une organisation réfléchie pour gérer ses mouvements et son orientation dans l’espace.
Conception : ajouter et positionner la Lune
Dans Three.js, ajouter un objet 3D à la scène est relativement simple. Il suffit :
- D’instancier un
Mesh
, - De le positionner (coordonnées et rotation),
- De l’ajouter à la scène avec
Scene.add()
.
Le Mesh
Lune a été défini précédemment, deux lignes de code supplémentaires suffiront pour la rendre visible dans la scène !
Limitation : gérer l’orbite de la Lune
En suivant cette approche directe, la Lune deviendrait un objet 3D indépendant qu’il faudrait animer manuellement. Contrairement à la Terre, la Lune doit se déplacer sur une orbite, impliquant des calculs complexes pour actualiser sa position 3D en fonction de sa vitesse angulaire.
Solution : hiérarchie d’objets
Pour éviter ces calculs complexes, nous allons utiliser une hiérarchie d’objets Three.js. Les objets enfants héritent des transformations (déplacements, rotations) appliquées à leurs parents. Ainsi, si un objet parent contient la Lune positionnée à la bonne distance de la Terre, une rotation appliquée au parent déplacera automatiquement la Lune, simulant son orbite autour de la Terre.
L’arborescence suivante simplifiera la gestion des rotations et les inclinaisons :
- Groupe Axe Lunaire : parent qui incline le plan de l’orbite de la Lune
- Groupe orbite : parent qui gère la rotation de la Lune autour de la Terre
- Orbite (Ellipse 3D)
- Groupe Réorientation : parent qui contrebalance la rotation du « groupe orbite » sur l’inclinaison de la Lune
- Groupe Lune : parent qui incline la Lune par rapport au plan de son orbite
- Axe Rotation Lune (Ligne 3D)
- Lune : le
Mesh
de la Lune auquel nous appliquons la rotation sur elle même
- Groupe Lune : parent qui incline la Lune par rapport au plan de son orbite
- Groupe orbite : parent qui gère la rotation de la Lune autour de la Terre
Implémentation
Pour mettre en œuvre cette structure, nous créons une classe Moon
qui encapsule tous les groupes et objets nécessaires. Elle gère :
- L’initialisation,
- Le positionnement,
- L’orientation des éléments.
// /lib/script.js
import {
TextureLoader,
Mesh,
+ Line,
+ BufferGeometry,
SphereGeometry,
+ LineBasicMaterial,
MeshStandardMaterial,
+ Group,
Vector3,
+ EllipseCurve,
Clock,
} from 'https://unpkg.com/three/build/three.module.js';
// ...
const EARTH_RADIUS = 6371;
const MOON_RADIUS = 1737.4;
+const EARTH_TILT = Math.PI * (23.5 + 5.1) / 180; // +5.1° par rapport à l'écliptique
+const MOON_ORBIT_TILT = Math.PI * -6.7 / 180;
+
// Animation constants (in second)
const DAY_IN_SECOND = 24 * 3600;
// ... .load('https://raw.githubusercontent.com/vasturiano/globe.gl/master/example/moon-landing-sites/lunar_bumpmap.jpg');
+// Instanciation de la Lune
+class Moon {
+ constructor(moonRadius, orbitRadius) {
+ this.lineMaterial = new LineBasicMaterial({ color: 0xffffff }); // blanc
+ this.lineMaterial.transparent = true;
+ this.lineMaterial.opacity = 0.2;
+
+ // Création du groupe de l'axe lunaire
+ this.group = new Group();
+ this.orbitGroup = new Group();
+ this.sphereGroup = new Group();
+ this.moonGroup = new Group();
+
+ // Création des Mesh
+ const ellipse = this._createOrbitEllipse(orbitRadius);
+ this.moon = new Mesh(
+ new SphereGeometry(moonRadius),
+ new MeshStandardMaterial({
+ map: MOON_TEXTURE,
+ displacementMap: BUMPMOON_TEXTURE,
+ }),
+ );
+ const moonAxis = new Line(
+ new BufferGeometry().setFromPoints([
+ new Vector3(0, -2 * moonRadius, 0 ),
+ new Vector3(0, 2 * moonRadius, 0 ),
+ ]),
+ this.lineMaterial,
+ );
+
+ // Création de l'arborescence
+ this.group.add(this.orbitGroup);
+ this.orbitGroup.add(ellipse);
+ this.orbitGroup.add(this.sphereGroup);
+ this.sphereGroup.add(this.moonGroup);
+ this.moonGroup.add(this.moon);
+ this.moonGroup.add(moonAxis);
+
+ // Appliquer les déplacements et rotations
+ this.sphereGroup.position.set(-orbitRadius, 0, 0);
+
+ this.group.rotateOnAxis(new Vector3(0, 0, 1), EARTH_TILT);
+ ellipse.rotateOnAxis(new Vector3(1, 0, 0), Math.PI / 2); // rotation du plan x, y vers x, z
+ this.moonGroup.rotateOnAxis(new Vector3(0, 0, 1), MOON_ORBIT_TILT);
+ }
+
+ get object3D() {
+ return this.group;
+ }
+
+ _createOrbitEllipse(orbitRadius) {
+ const curve = new EllipseCurve(
+ 0, 0, // x, y
+ orbitRadius, orbitRadius, // xRadius, yRadius
+ 0, 2 * Math.PI, // startAngle, endAngle
+ false, // clockwise
+ 0, // rotation
+ );
+
+ return new Line(
+ new BufferGeometry().setFromPoints(curve.getPoints(60)),
+ this.lineMaterial,
+ );
+ }
+}
+
+
// Configuration de la scene et de la Terre
const world = Globe()
(document.getElementById('view'))
// ...
.pointOfView({ altitude: 7 })
.onGlobeReady(() => {
const earth = scene.children[scene.children.length - 1];
+ const earthRadius = world.getGlobeRadius();
+
+ const moon = new Moon(earthRadius * MOON_RADIUS / EARTH_RADIUS, earthRadius * 3.5);
+
+ world.scene().add(moon.object3D);
const clock = new Clock();
(function animate() {
// ...
L’axe de rotation de la Lune est matérialisé par une ligne qui dépasse d’une fois le rayon de la Lune au niveau des pôles. Nous définissons le segment avec deux points représentés par des vecteurs sur l’axe Y.
La position x, y, z choisie permet à la texture map
d’être orientée correctement par rapport à la Terre. J’ai observé la sphère dans la scène, et je l’ai comparée aux clichés de la face visible de la Lune pour trouver la bonne position de départ.
L’ellipse est dessinée sur le plan x, y (face à la caméra). Nous appliquons une rotation de 90° autour de l’axe X pour la positionner sur le plan x, z (l’ellipse est alors à l’horizontale par rapport à la caméra).
La fonction _createOrbitEllipse
s’inspire de l’exemple présenté dans la documentation de l’API Three.js.
Résultat
En actualisant la page, la Lune apparaît dans la scène, orientée selon les spécifications. Elle reste immobile pour l’instant. La prochaine étape sera donc d’ajouter l’animation de son orbite et de sa rotation sur elle-même.
Animer la Lune : appliquer les rotations
Précédemment, nous avons construit une hiérarchie logique des objets 3D représentant la Lune et son orbite. Cette organisation nous permet de simplifier les calculs de rotation en réduisant les animations nécessaires à trois transformations principales :
- Rotation de la Lune autour de la Terre
- Rotation pour compenser l’inclinaison de la Lune
- Rotation de la Lune sur elle-même.
Pour ajouter ces animations, nous faisons évoluer le script de la manière suivante :
// /lib/script.js
// ...
const TIME_SCALE = DAY_IN_SECOND / 10; // 1 jour en 10 secondes
const EARTH_ROTATION_VELOCITY = 2 * Math.PI / DAY_IN_SECOND;
+const MOON_ROTATION_VELOCITY = 2 * Math.PI / (27.3 * DAY_IN_SECOND);
+const MOON_ORBIT_VELOCITY = MOON_ROTATION_VELOCITY;
// Instanciation de la Lune
// ...
this.lineMaterial,
);
}
+
+ animate(delta) {
+ // Rotation de la Lune sur elle même
+ this.moon.rotateOnAxis(new Vector3(0, 1, 0), delta * TIME_SCALE * MOON_ROTATION_VELOCITY); <1>
+
+ // Rotation de la Lune autour de la Terre
+ const orbitRotation = delta * TIME_SCALE * MOON_ORBIT_VELOCITY;
+ this.orbitGroup.rotateOnAxis(new Vector3(0, 1, 0), orbitRotation); <2>
+ this.sphereGroup.rotateOnAxis(new Vector3(0, 1, 0), -orbitRotation); <3>
+ }
}
// ...
const delta = clock.getDelta();
earth.rotateOnAxis(new Vector3(0, 1, 0), delta * TIME_SCALE * EARTH_ROTATION_VELOCITY);
+ moon.animate(delta);
window.requestAnimationFrame(animate);
})();
Rotation angulaire autour de l’axe Y faisant tourner la Lune sur elle même.
Rotation angulaire autour de l’axe Y appliquée à l’orbite lunaire.
Rotation angulaire inverse appliquée autour de l’axe Y pour maintenir l’inclinaison de la Lune.
Grâce à cette hiérarchie et ces animations, nous obtenons une simulation fluide et réaliste sans calculer manuellement les positions et orientations à chaque frame :
Conclusion
Dans cet article, nous avons exploré deux approches pour animer une scène 3D représentant la Terre et la Lune, sans recourir aux coordonnées GPS.
- La première approche repose sur le calcul des coordonnées de la Lune en fonction du temps. Cette méthode demande des efforts de calcul à chaque image, ce qui peut compliquer la maintenance et l’évolution du code.
- La seconde approche, plus intuitive, tire parti des fonctionnalités du moteur 3D Three.js pour gérer les rotations et les transformations automatiquement, en organisant les objets dans une hiérarchie logique.
Nous avons constaté les limites de la librairie Globe.GL, impliquant une adaptation de la solution en utilisant Three.js. Cette décision nous a permis d’utiliser les primitives offertes par cette librairie pour structurer et animer les objets 3D efficacement. En confiant les calculs des déplacements et des rotations à Three.js, nous avons réduit la complexité de notre solution tout en augmentant sa modularité et sa clarté.
En résumé, cette expérience démontre l’importance de comprendre les contraintes, les outils disponibles et les objectifs à atteindre. En choisissant une approche adaptée à nos compétences et à nos besoins, nous avons pu obtenir un résultat réaliste et satisfaisant avec un effort maîtrisé.
Pour revenir à notre problématique initiale, simuler ou visualiser une constellation de satellites en orbite autour de la Terre dans une interface Web est tout à fait réalisable grâce aux techniques que nous avons étudié ensemble !