◆ 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/ciel.js · 12.9 Ko

/* ==========================================================================
 * js/ciel.js — LE CIEL : soleil, lune, étoiles, nuages et oiseaux
 * --------------------------------------------------------------------------
 * Quatre fonctions fabriques indépendantes, assemblées par creerCiel() :
 *
 *   creerAstres()   le soleil (avec son halo) et la lune (avec ses cratères),
 *                   qui décrivent un arc dans le ciel selon l'HEURE ;
 *   creerEtoiles()  des points blancs visibles seulement la nuit ;
 *   creerNuages()   des nuages (amas de sphères) qui dérivent et reviennent ;
 *   creerOiseaux()  des oiseaux qui volent en cercle en battant des ailes.
 *
 * Le ciel est « dans » le diorama, derrière l'île (z négatif) : si on tourne
 * la scène, on voit le soleil passer d'un côté ou de l'autre, comme sur une
 * maquette. Le DÉGRADÉ de couleurs du fond, lui, est du CSS (voir main.js).
 *
 * Dépend de : Zdog, Baie.CONFIG, Baie.QUALITE, Baie.Palette.
 * ========================================================================== */

(function () {
  'use strict';

  var TAU = Zdog.TAU;

  // ---- Constantes réglables -------------------------------------------------
  var ARC_RX = 150;        // demi-largeur de l'arc du soleil (axe x)
  var ARC_RY = 125;        // hauteur de l'arc du soleil (axe y, vers le haut)
  var ARC_Y0 = 8;          // niveau du « centre » de l'arc : un peu SOUS la mer
  var ARC_Z = -125;        // profondeur : derrière l'île
  var HORIZON = -4;        // un astre dont y > -4 est sous l'horizon : on le cache
  var NUAGE_LIMITE = 230;  // un nuage qui sort à droite (x > 230) revient à gauche

  /** Hasard reproductible (voir palmier.js pour l'explication). */
  function hasardAvecGraine(graine) {
    var a = graine >>> 0;
    return function () {
      a = (a + 0x6D2B79F5) >>> 0;
      var t = a;
      t = Math.imul(t ^ (t >>> 15), t | 1);
      t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
      return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
    };
  }

  // ==========================================================================
  // 1) SOLEIL ET LUNE
  // ==========================================================================
  /**
   * Construit le soleil et la lune.
   * @param {Zdog.Anchor} parent Où les accrocher.
   * @returns {Object} { placer(heure), appliquerPalette(p) }
   */
  function creerAstres(parent) {

    // Le SOLEIL : un Group contenant un grand halo transparent PUIS le disque.
    // (Group = ordre de dessin garanti : le halo est dessiné en premier.)
    var groupeSoleil = new Zdog.Group({ addTo: parent, translate: { z: ARC_Z } });
    var halo = new Zdog.Shape({
      addTo: groupeSoleil,
      stroke: 58,                       // un point épais = un grand disque
      color: 'rgba(255,230,120,0.3)',  // rgba : le dernier nombre est l'opacité
    });
    var soleil = new Zdog.Shape({
      addTo: groupeSoleil,
      stroke: 26,
      color: '#ffe36a',
    });

    // La LUNE : un disque et trois cratères plus sombres, légèrement devant.
    var groupeLune = new Zdog.Group({ addTo: parent, translate: { z: ARC_Z } });
    var lune = new Zdog.Shape({
      addTo: groupeLune,
      stroke: 22,
      color: '#e8eefc',
    });
    new Zdog.Shape({ addTo: groupeLune, stroke: 5, translate: { x: -4, y: -3, z: 6 }, color: '#c3cbe0' });
    new Zdog.Shape({ addTo: groupeLune, stroke: 3.5, translate: { x: 4, y: 3, z: 6 }, color: '#c3cbe0' });
    new Zdog.Shape({ addTo: groupeLune, stroke: 3, translate: { x: 3, y: -5, z: 6 }, color: '#c3cbe0' });

    /**
     * Place les astres sur leur arc selon l'heure.
     *
     * Le soleil se lève à 6 h (à gauche), culmine à 12 h (en haut) et se
     * couche à 18 h (à droite). On utilise un angle de 0 à π (demi-cercle) :
     *     angle = (heure - 6) / 12 × π
     *     x = -RX × cos(angle)      y = Y0 - RY × sin(angle)
     * La lune fait exactement l'inverse : elle est à l'opposé sur le cercle.
     *
     * @param {number} heure Heure décimale (0 à 24).
     */
    function placer(heure) {
      var angle = ((heure - 6) / 12) * Math.PI;
      var c = Math.cos(angle);
      var s = Math.sin(angle);

      groupeSoleil.translate.x = -ARC_RX * c;
      groupeSoleil.translate.y = ARC_Y0 - ARC_RY * s;
      groupeSoleil.visible = groupeSoleil.translate.y < HORIZON;   // caché sous l'horizon

      groupeLune.translate.x = ARC_RX * c;                         // opposé du soleil
      groupeLune.translate.y = ARC_Y0 + ARC_RY * s;
      groupeLune.visible = groupeLune.translate.y < HORIZON;
    }

    /** Colore les astres selon la palette. */
    function appliquerPalette(p) {
      soleil.color = p.css.soleil;
      halo.color = Baie.Palette.cssAlpha(p.rgb.soleil, 0.3);
      lune.color = p.css.lune;
    }

    return { placer: placer, appliquerPalette: appliquerPalette };
  }

  // ==========================================================================
  // 2) ÉTOILES
  // ==========================================================================
  /**
   * Construit un champ d'étoiles (points blancs au loin, derrière la scène).
   * @param {Zdog.Anchor} parent Où l'accrocher.
   * @param {number} nombre Nombre d'étoiles (moins sur mobile).
   * @returns {Object} { appliquerPalette(p) } — affiche ou cache selon la nuit.
   */
  function creerEtoiles(parent, nombre) {
    var hasard = hasardAvecGraine(42);
    // Un seul Group : on peut le montrer / cacher d'un coup avec `visible`.
    var groupe = new Zdog.Group({ addTo: parent, translate: { z: -150 } });

    for (var i = 0; i < nombre; i++) {
      new Zdog.Shape({
        addTo: groupe,
        // Répartition aléatoire dans le ciel (x de -200 à 200, y de -170 à -35).
        translate: { x: (hasard() - 0.5) * 400, y: -35 - hasard() * 135 },
        stroke: 1.5 + hasard() * 2,       // des étoiles de tailles variées
        color: '#ffffff',
      });
    }

    function appliquerPalette(p) {
      groupe.visible = p.etoiles > 0.35;
    }
    return { appliquerPalette: appliquerPalette };
  }

  // ==========================================================================
  // 3) NUAGES
  // ==========================================================================
  /**
   * Construit des nuages qui dérivent lentement de gauche à droite.
   *
   * Chaque nuage = un Group de 4 sphères (Shape sans path, stroke épais)
   * de tailles différentes, côte à côte.
   *
   * @param {Zdog.Anchor} parent Où les accrocher.
   * @param {number} nombre Nombre de nuages.
   * @returns {Object} { mettreAJour(dt), appliquerPalette(p) }
   */
  function creerNuages(parent, nombre) {
    var hasard = hasardAvecGraine(9);
    var nuages = [];       // { groupe, vitesse }
    var spheres = [];      // toutes les sphères, pour les recolorer d'un coup

    for (var n = 0; n < nombre; n++) {
      var groupe = new Zdog.Group({
        addTo: parent,
        translate: {
          x: -NUAGE_LIMITE + (n / nombre) * 2 * NUAGE_LIMITE + hasard() * 40,   // étalés au départ
          y: -(98 + hasard() * 36),                                             // en altitude
          z: -(30 + hasard() * 80),                                             // derrière l'île
        },
        updateSort: true,
      });

      // Les 4 sphères : positions et diamètres fixes (relatifs au groupe).
      var parts = [
        { x: -22, y: 3, d: 24 },
        { x: -6, y: -3, d: 34 },
        { x: 14, y: 0, d: 30 },
        { x: 30, y: 4, d: 20 },
      ];
      for (var k = 0; k < parts.length; k++) {
        spheres.push(new Zdog.Shape({
          addTo: groupe,
          translate: { x: parts[k].x, y: parts[k].y },
          stroke: parts[k].d,
          color: '#ffffff',
        }));
      }
      nuages.push({ groupe: groupe, vitesse: 4 + hasard() * 5 });   // 4 à 9 unités/s
    }

    /**
     * Fait dériver les nuages. dt = 0 en pause : les nuages s'arrêtent.
     * @param {number} dt Temps depuis l'image précédente (secondes).
     */
    function mettreAJour(dt) {
      for (var i = 0; i < nuages.length; i++) {
        var g = nuages[i].groupe;
        g.translate.x += nuages[i].vitesse * dt;
        if (g.translate.x > NUAGE_LIMITE) { g.translate.x = -NUAGE_LIMITE; }   // on boucle
      }
    }

    function appliquerPalette(p) {
      for (var i = 0; i < spheres.length; i++) { spheres[i].color = p.css.nuage; }
    }

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

  // ==========================================================================
  // 4) OISEAUX
  // ==========================================================================
  /**
   * Construit des oiseaux qui tournent autour de l'île en battant des ailes.
   *
   * Un oiseau = une ancre contenant un corps (point épais) et deux ailes.
   * Chaque aile est une ancre (pour pouvoir la faire pivoter) contenant une
   * ligne brisée en forme de « v » aplati.
   *
   * Repère de l'oiseau : il avance vers +x ; ses ailes s'étendent sur l'axe z.
   * Battre des ailes = faire tourner chaque aile autour de l'axe x.
   *
   * @param {Zdog.Anchor} parent Où les accrocher.
   * @param {number} nombre Nombre d'oiseaux.
   * @returns {Object} { mettreAJour(t, dt), appliquerPalette(p) }
   */
  function creerOiseaux(parent, nombre) {
    var hasard = hasardAvecGraine(77);
    var COULEUR = '#27283a';

    // Un Group : on peut cacher tous les oiseaux d'un coup (la nuit).
    var groupe = new Zdog.Group({ addTo: parent, updateSort: true });
    var oiseaux = [];

    for (var n = 0; n < nombre; n++) {
      var corps = new Zdog.Anchor({ addTo: groupe });
      new Zdog.Shape({ addTo: corps, stroke: 3.2, color: COULEUR });   // le corps

      // Aile gauche (z négatif) et aile droite (z positif) : formes miroirs.
      var aileG = new Zdog.Anchor({ addTo: corps });
      new Zdog.Shape({
        addTo: aileG,
        path: [{ z: 0, y: 0 }, { z: -6, y: -2 }, { z: -12, y: 1 }],
        closed: false, stroke: 1.6, color: COULEUR,
      });
      var aileD = new Zdog.Anchor({ addTo: corps });
      new Zdog.Shape({
        addTo: aileD,
        path: [{ z: 0, y: 0 }, { z: 6, y: -2 }, { z: 12, y: 1 }],
        closed: false, stroke: 1.6, color: COULEUR,
      });

      oiseaux.push({
        corps: corps, aileG: aileG, aileD: aileD,
        rayon: 55 + hasard() * 65,           // rayon de son cercle de vol
        altitude: -(88 + hasard() * 22),     // hauteur de vol (y négatif)
        vitesse: (0.22 + hasard() * 0.2) * (hasard() < 0.5 ? 1 : -1),   // sens aléatoire
        phase: hasard() * TAU,               // position de départ sur le cercle
        battement: 7 + hasard() * 3,         // fréquence des battements d'ailes
      });
    }

    var angleCourant = 0;   // temps « de vol » cumulé (s'arrête en pause)

    /**
     * Fait voler les oiseaux.
     * @param {number} t   Temps total (pour les battements, déjà arrêté en pause).
     * @param {number} dt  Temps depuis l'image précédente.
     */
    function mettreAJour(t, dt) {
      angleCourant += dt;
      for (var i = 0; i < oiseaux.length; i++) {
        var o = oiseaux[i];
        var a = o.phase + o.vitesse * angleCourant;       // angle sur son cercle
        var x = o.rayon * Math.cos(a);
        var z = o.rayon * Math.sin(a);
        o.corps.translate.x = x;
        o.corps.translate.z = z;
        o.corps.translate.y = o.altitude + 3 * Math.sin(t * 0.9 + o.phase);   // petit ondoiement

        // Cap : même formule que pour la pirogue (dérivée de la position).
        var sens = o.vitesse > 0 ? 1 : -1;
        o.corps.rotate.y = Math.atan2(-o.rayon * Math.cos(a) * sens, -o.rayon * Math.sin(a) * sens);

        // Battement : les deux ailes montent et descendent en miroir.
        var bat = 0.7 * Math.sin(t * o.battement + o.phase);
        o.aileG.rotate.x = -bat;
        o.aileD.rotate.x = bat;
      }
    }

    function appliquerPalette(p) {
      // On cache les oiseaux la nuit (ils dorment).
      groupe.visible = p.etoiles < 0.5;
    }

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

  // ==========================================================================
  // ASSEMBLAGE
  // ==========================================================================
  /**
   * Construit tout le ciel.
   * @param {Zdog.Anchor} parent Où l'accrocher (le « monde »).
   * @returns {Object} { placerAstres(heure), mettreAJour(t, dt), appliquerPalette(p) }
   */
  function creerCiel(parent) {
    var Q = Baie.QUALITE;
    var astres = creerAstres(parent);
    var etoiles = creerEtoiles(parent, Q.etoiles);
    var nuages = creerNuages(parent, Q.nuages);
    var oiseaux = creerOiseaux(parent, Q.oiseaux);

    return {
      placerAstres: astres.placer,
      mettreAJour: function (t, dt) {
        nuages.mettreAJour(dt);
        oiseaux.mettreAJour(t, dt);
      },
      appliquerPalette: function (p) {
        astres.appliquerPalette(p);
        etoiles.appliquerPalette(p);
        nuages.appliquerPalette(p);
        oiseaux.appliquerPalette(p);
      },
    };
  }

  Baie.creerCiel = creerCiel;
})();