◆ Portail Antalaha

Annuaire › Code ouvert

Baie d'Antalaha : diorama 3D animé jour et nuit

tutorial · Voir le site ↗
Connectez-vous pour copier ce site

Code publié par son auteur, en lecture seule. « Copier dans mon espace » crée un nouveau site à vous (non publié) que vous pouvez modifier librement.

js/mer.js · 7.7 Ko

/* ==========================================================================
 * js/mer.js — LA MER QUI ONDULE
 * --------------------------------------------------------------------------
 * Comment dessiner une mer avec des formes Zdog ?
 *
 * On pose des « RANGÉES » parallèles (alignées sur l'axe x) à intervalles
 * réguliers le long de l'axe z. Chaque rangée est un Shape dont le `path`
 * contient une quinzaine de points reliés par des segments, et dont le trait
 * (`stroke`) est TRÈS épais (22) : les rangées se chevauchent et forment
 * une surface continue, comme des bandes de peinture.
 *
 *     vue de dessus                     vue de côté (une rangée)
 *     ─────────────  z = -150            ╭─╮   ╭──╮
 *     ─────────────  z = -134      y →  ─╯  ╰──╯  ╰─  les points montent
 *     ─────────────  z = -118            et descendent avec le temps
 *
 * L'ONDULATION : à chaque image, on recalcule la hauteur y de chaque point
 * avec une somme de sinus (voir hauteur()). Pour ne PAS créer de nouveaux
 * objets dans la boucle, on modifie directement les points existants :
 * forme.pathCommands[i].points[0].y = … (voir mettreAJour()).
 *
 * Dépend de : Zdog, Baie.CONFIG, Baie.QUALITE, Baie.tirerVersLeFond,
 *             Baie.Palette (config.js et palette.js).
 * ========================================================================== */

(function () {
  'use strict';

  // ---- Constantes réglables -------------------------------------------------
  var EPAISSEUR_TRAIT = 22;   // épaisseur des rangées (doit dépasser l'espacement)
  var AMPLITUDE_1 = 3.2;      // hauteur de la grande houle
  var AMPLITUDE_2 = 2.0;      // hauteur de la houle croisée
  var AMPLITUDE_3 = 1.0;      // petites vaguelettes

  /**
   * Hauteur de la surface de la mer au point (x, z) à l'instant t.
   * C'est une SOMME DE TROIS SINUS de fréquences et directions différentes :
   * le résultat paraît naturel et jamais répétitif.
   * Cette fonction est aussi utilisée par la pirogue pour flotter à la bonne hauteur !
   *
   * @param {number} x Position sur l'axe x.
   * @param {number} z Position sur l'axe z.
   * @param {number} t Temps écoulé, en secondes.
   * @returns {number} Hauteur y (négatif = au-dessus du niveau 0).
   */
  function hauteur(x, z, t) {
    return AMPLITUDE_1 * Math.sin(x * 0.05 + t * 1.2)
         + AMPLITUDE_2 * Math.sin(z * 0.07 - t * 0.9 + x * 0.02)
         + AMPLITUDE_3 * Math.sin((x + z) * 0.11 + t * 1.9);
  }

  /**
   * Construit la mer.
   *
   * @param {Zdog.Anchor} parent  Où l'accrocher (le « monde »).
   * @returns {Object} { hauteur, mettreAJour(t), appliquerPalette(p) }
   */
  function creerMer(parent) {
    var R = Baie.CONFIG.rayonMer;
    var espacement = Baie.QUALITE.espacementMer;
    var nbPoints = Baie.QUALITE.pointsMer;
    var nbRangees = Math.floor((2 * R) / espacement) + 1;

    // 0) LE FOND DE LA MER : un grand disque plat couché (rotate x = TAU/4) qui
    //    comble les trous entre les rangées et donne un bord net et rond.
    //    Il est tiré tout au fond (avant même les rangées).
    var fond = new Zdog.Ellipse({
      addTo: parent,
      diameter: 2 * R,
      rotate: { x: Zdog.TAU / 4 },
      translate: { y: 5 },
      fill: true,
      stroke: false,
      color: '#168bb3',            // remplacée par appliquerPalette()
    });
    Baie.tirerVersLeFond(fond, -1100000);

    // Tableaux construits UNE FOIS ; la boucle d'animation ne fait que les lire.
    var rangees = [];   // { bande, crete } pour chaque rangée (pour les couleurs)
    var points = [];    // { cmd, cmdCrete, x, z } pour chaque point (pour l'ondulation)

    for (var r = 0; r < nbRangees; r++) {
      var z = -R + r * espacement;

      // Les rangées proches du bord sont plus courtes : le résultat est un
      // DISQUE. Pythagore : demi-longueur = racine(R² - z²).
      var demi = Math.sqrt(Math.max(R * R - z * z, 0));
      demi = Math.max(demi - EPAISSEUR_TRAIT / 2, 6);   // on retire le rayon du trait arrondi

      // Le tracé : nbPoints points répartis de -demi à +demi sur l'axe x.
      var chemin = [];
      for (var k = 0; k < nbPoints; k++) {
        chemin.push({ x: -demi + (2 * demi * k) / (nbPoints - 1), y: 0, z: z });
      }

      // Un Group par rangée : il dessine ses enfants DANS L'ORDRE
      // (la bande d'abord, puis la crête d'écume par-dessus).
      var groupe = new Zdog.Group({ addTo: parent });

      var bande = new Zdog.Shape({
        addTo: groupe,
        path: chemin,
        closed: false,            // on ne referme pas le tracé : c'est une ligne
        stroke: EPAISSEUR_TRAIT,
        color: '#168bb3',         // remplacée par appliquerPalette()
      });

      // Une rangée sur trois reçoit une fine ligne d'écume claire.
      // (On recopie le chemin : chaque forme a ses propres points.)
      var crete = null;
      if (r % 3 === 0) {
        crete = new Zdog.Shape({
          addTo: groupe,
          path: chemin.map(function (p) { return { x: p.x, y: p.y - 3, z: p.z }; }),
          closed: false,
          stroke: 2.2,
          color: '#ffffff',
        });
      }

      // La mer doit TOUJOURS être dessinée avant le reste (île, bateau…).
      // Voir Baie.tirerVersLeFond() dans config.js.
      Baie.tirerVersLeFond(groupe, -1000000);

      rangees.push({ bande: bande, crete: crete });

      // On garde une référence directe vers chaque PathCommand modifiable.
      // Zdog fabrique un objet PathCommand par point du chemin ;
      // sa propriété points[0] est le vecteur 3D réellement dessiné.
      for (var j = 0; j < nbPoints; j++) {
        points.push({
          cmd: bande.pathCommands[j],
          cmdCrete: crete ? crete.pathCommands[j] : null,
          x: chemin[j].x,
          z: z,
        });
      }
    }

    /**
     * Fait onduler la mer. À appeler à chaque image.
     * AUCUNE création d'objet ici : seulement des nombres modifiés.
     * @param {number} t Temps écoulé en secondes.
     */
    function mettreAJour(t) {
      for (var i = 0; i < points.length; i++) {
        var pt = points[i];
        var y = hauteur(pt.x, pt.z, t);
        pt.cmd.points[0].y = y;                     // on déplace le point…
        if (pt.cmdCrete) { pt.cmdCrete.points[0].y = y - 3; } // …et la crête juste au-dessus
      }
      // Pourquoi pas forme.updatePath() ? Cette méthode RECONSTRUIT tous les
      // objets PathCommand : coûteux 60 fois par seconde. Ici on change juste
      // un nombre, Zdog le relira au rendu (reset() recopie points -> renderPoints).
    }

    // Tableaux de travail réutilisés (évite de créer des tableaux à chaque appel).
    var tmp = [0, 0, 0];

    /**
     * Recolore la mer d'après la palette (appelée seulement quand l'heure change).
     * Les rangées lointaines prennent la couleur mer1, les proches mer2 ;
     * une rangée sur deux est un peu plus claire pour faire ressortir les vagues.
     * @param {Object} p Palette calculée par Baie.Palette.calculer().
     */
    function appliquerPalette(p) {
      fond.color = p.css.mer1;                       // le disque de fond
      for (var r = 0; r < rangees.length; r++) {
        var t = r / (rangees.length - 1);          // 0 = loin, 1 = proche
        var base = Baie.Palette.melanger(p.rgb.mer1, p.rgb.mer2, t, tmp);
        rangees[r].bande.color = (r % 2 === 0) ? base : Baie.Palette.nuancer(tmp, 0.07, tmp);
        if (rangees[r].crete) { rangees[r].crete.color = p.css.ecume; }
      }
    }

    return { hauteur: hauteur, mettreAJour: mettreAJour, appliquerPalette: appliquerPalette };
  }

  Baie.creerMer = creerMer;
  Baie.hauteurMer = hauteur;
})();