Annuaire › Code ouvert
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/decor.js · 13.2 Ko
/* ============================================================
decor.js - le paysage qui change selon l'heure
============================================================
Ce fichier ne contient que des FONCTIONS DE DESSIN (aucun
setup/draw). C'est sketch.js qui les appelle, dans l'ordre :
ciel -> étoiles -> astre -> mer -> colline -> palmiers.
On dessine "du fond vers l'avant" : ce qui est dessiné après
se superpose à ce qui est dessiné avant (comme des calques).
RAPPEL : système de coordonnées de p5
- l'origine (0, 0) est en HAUT à GAUCHE du canevas ;
- x augmente vers la DROITE ;
- y augmente vers le BAS (attention : à l'envers des maths !).
============================================================ */
/* ------------------------------------------------------------
preparerEtapes()
But : convertir les couleurs hexadécimales de ETAPES_BRUTES en
objets couleur p5 utilisables avec lerpColor().
Retour : un tableau d'étapes {h, haut, bas, mer, etoiles}.
À appeler UNE fois, dans setup().
------------------------------------------------------------ */
function preparerEtapes() {
// .map() fabrique un nouveau tableau en transformant chaque élément.
return ETAPES_BRUTES.map(function (e) {
return {
h: e.h,
haut: color(e.haut), // color('#rrggbb') -> objet couleur p5
bas: color(e.bas),
mer: color(e.mer),
etoiles: e.etoiles
};
});
}
/* ------------------------------------------------------------
creerEtoiles(n)
But : fabriquer n étoiles à des positions aléatoires.
On stocke les positions en FRACTIONS (0 à 1) de la taille du
canevas : ainsi elles restent bien placées si l'écran change
de taille.
Paramètre : n = nombre d'étoiles.
Retour : tableau d'objets {x, y, taille, phase}.
------------------------------------------------------------ */
function creerEtoiles(n) {
const liste = [];
for (let i = 0; i < n; i++) {
liste.push({
x: random(), // random() sans argument : entre 0 et 1
y: random(POS_HORIZON * 0.95), // seulement dans le ciel (au-dessus de l'horizon)
taille: random(1, 2.6),
phase: random(TWO_PI) // décalage pour que les étoiles ne scintillent pas toutes ensemble
});
}
return liste;
}
/* ------------------------------------------------------------
couleursPourHeure(heureDecimale, etapes)
But : trouver les couleurs du ciel à une heure donnée.
C'est ici qu'on utilise DEUX fonctions importantes de p5 :
- map() : convertit une valeur d'un intervalle à un autre ;
- lerpColor() : mélange deux couleurs (0 = la 1re, 1 = la 2e).
Paramètres :
heureDecimale : ex. 14.5 pour 14 h 30
etapes : tableau préparé par preparerEtapes()
Retour : {haut, bas, mer, etoiles}
------------------------------------------------------------ */
function couleursPourHeure(heureDecimale, etapes) {
// On cherche les deux étapes qui encadrent l'heure actuelle.
let i = 0;
while (i < etapes.length - 2 && heureDecimale >= etapes[i + 1].h) {
i++;
}
const a = etapes[i]; // étape juste avant
const b = etapes[i + 1]; // étape juste après
// map(valeur, minAvant, maxAvant, minAprès, maxAprès)
// Exemple : à 7,5 h, entre a.h = 6,5 et b.h = 8,5, on obtient 0,5
// (on est à mi-chemin). constrain() garantit un résultat entre 0 et 1.
const t = constrain(map(heureDecimale, a.h, b.h, 0, 1), 0, 1);
return {
haut: lerpColor(a.haut, b.haut, t),
bas: lerpColor(a.bas, b.bas, t),
mer: lerpColor(a.mer, b.mer, t),
etoiles: lerp(a.etoiles, b.etoiles, t) // lerp() = même idée pour de simples nombres
};
}
/* ------------------------------------------------------------
nomDeLaPhase(heureDecimale)
But : donner le nom (« Aube », « Jour »...) de l'heure actuelle.
Retour : une chaîne de caractères.
------------------------------------------------------------ */
function nomDeLaPhase(heureDecimale) {
let nom = PHASES[0].nom;
for (const p of PHASES) {
if (heureDecimale >= p.de) nom = p.nom; // la dernière phase dépassée gagne
}
return nom;
}
/* ------------------------------------------------------------
dessinerCiel(w, hauteurCiel, couleurs)
But : dessiner le dégradé vertical du ciel.
p5 n'a pas de "dégradé" tout fait : on dessine donc une ligne
horizontale par pixel de hauteur, chacune d'une couleur un peu
différente (lerpColor entre le haut et le bas).
Paramètres : w = largeur, hauteurCiel = hauteur de la zone,
couleurs = résultat de couleursPourHeure().
------------------------------------------------------------ */
function dessinerCiel(w, hauteurCiel, couleurs) {
strokeWeight(1);
for (let y = 0; y <= hauteurCiel; y++) {
const t = y / hauteurCiel; // 0 en haut -> 1 à l'horizon
stroke(lerpColor(couleurs.haut, couleurs.bas, t));
line(0, y, w, y); // line(x1, y1, x2, y2)
}
}
/* ------------------------------------------------------------
dessinerEtoiles(etoiles, w, hauteurCiel, visibilite)
But : dessiner les étoiles qui scintillent la nuit.
Paramètres :
etoiles : tableau créé par creerEtoiles()
visibilite : 0 à 1 (venant de l'étape en cours)
------------------------------------------------------------ */
function dessinerEtoiles(etoiles, w, hauteurCiel, visibilite) {
if (visibilite <= 0.01) return; // de jour : rien à dessiner, on sort de la fonction
noStroke();
for (const e of etoiles) {
// sin() oscille entre -1 et 1 : on s'en sert pour faire "respirer" la luminosité.
// millis() = nombre de millisecondes écoulées depuis le début du programme.
const scintillement = 0.65 + 0.35 * sin(millis() / 500 + e.phase);
fill(255, 255, 240, 255 * visibilite * scintillement); // fill(r, g, b, transparence)
circle(e.x * w, e.y * hauteurCiel / POS_HORIZON, e.taille);
}
}
/* ------------------------------------------------------------
positionAstre(t, w, yHorizon)
But : calculer où se trouve le soleil ou la lune.
L'astre suit un ARC de cercle : il se lève à gauche, monte, puis
se couche à droite.
Paramètres :
t : progression du trajet, de 0 (lever) à 1 (coucher)
w : largeur du canevas
yHorizon : hauteur (y) de l'horizon
Retour : {x, y}
------------------------------------------------------------ */
function positionAstre(t, w, yHorizon) {
const marge = w * 0.08;
const x = map(t, 0, 1, marge, w - marge); // glisse de gauche à droite
const hauteurArc = yHorizon * 0.8;
// sin(t * PI) vaut 0 aux deux extrémités et 1 au milieu : parfait pour un arc.
// On SOUSTRAIT car y augmente vers le bas.
const y = yHorizon - sin(t * PI) * hauteurArc;
return { x: x, y: y };
}
/* ------------------------------------------------------------
dessinerSoleilEtLune(heureDecimale, w, yHorizon)
But : afficher le soleil (6 h -> 18 h) ou la lune (18 h -> 6 h).
Le soleil est derrière l'horizon la nuit ; on dessine donc la
mer et la colline PAR-DESSUS l'astre pour le "cacher".
------------------------------------------------------------ */
function dessinerSoleilEtLune(heureDecimale, w, yHorizon) {
noStroke();
const rayon = w * 0.05;
if (heureDecimale >= 6 && heureDecimale < 18) {
// --- SOLEIL ---
const t = (heureDecimale - 6) / 12; // 0 à 6 h, 1 à 18 h
const p = positionAstre(t, w, yHorizon);
// Plus le soleil est haut, plus il est jaune pâle.
const c = lerpColor(color(COULEUR_SOLEIL_BAS), color(COULEUR_SOLEIL_HAUT), sin(t * PI));
// Halo : plusieurs cercles transparents de plus en plus grands.
for (let k = 4; k >= 1; k--) {
fill(red(c), green(c), blue(c), 28);
circle(p.x, p.y, rayon * 2 + k * rayon * 0.9);
}
fill(c);
circle(p.x, p.y, rayon * 2);
} else {
// --- LUNE ---
// On "décale" l'heure pour que la lune se lève à 18 h : (h + 6) modulo 24.
const hn = (heureDecimale + 6) % 24; // 18 h -> 0 ; 0 h -> 6 ; 6 h -> 12
const t = hn / 12;
const p = positionAstre(t, w, yHorizon);
fill(red(color(COULEUR_LUNE)), green(color(COULEUR_LUNE)), blue(color(COULEUR_LUNE)), 40);
circle(p.x, p.y, rayon * 3.2); // halo
fill(COULEUR_LUNE);
circle(p.x, p.y, rayon * 1.7);
// Croissant : un disque de la couleur du ciel qui "mord" la lune.
fill(red(color('#0d1636')), green(color('#0d1636')), blue(color('#0d1636')), 180);
circle(p.x + rayon * 0.5, p.y - rayon * 0.2, rayon * 1.5);
}
}
/* ------------------------------------------------------------
dessinerMer(w, h, yHorizon, couleurs, heureDecimale)
But : dessiner la mer avec de petits reflets animés.
------------------------------------------------------------ */
function dessinerMer(w, h, yHorizon, couleurs, heureDecimale) {
// Dégradé : la mer est plus claire à l'horizon, plus sombre au bord bas.
const sombre = lerpColor(couleurs.mer, color(0, 20, 40), 0.55);
strokeWeight(1);
for (let y = yHorizon; y <= h; y++) {
stroke(lerpColor(couleurs.mer, sombre, (y - yHorizon) / (h - yHorizon)));
line(0, y, w, y);
}
// Petits reflets : des traits clairs qui ondulent avec le temps.
const nuit = couleurs.etoiles; // 0 de jour, 1 la nuit
strokeWeight(max(1, w * 0.004));
for (let i = 0; i < 14; i++) {
const y = yHorizon + (h - yHorizon) * (0.08 + i * 0.065);
const decalage = sin(millis() / 900 + i * 1.7) * w * 0.03;
const x = w * (0.12 + ((i * 0.37) % 0.76)) + decalage;
const longueur = w * (0.05 + 0.002 * i * 4);
stroke(255, 255, 255, lerp(70, 35, nuit));
line(x - longueur / 2, y, x + longueur / 2, y);
}
}
/* ------------------------------------------------------------
dessinerColline(w, yHorizon, couleurs)
But : dessiner une silhouette de colline à droite (comme celles
qui entourent la baie d'Antalaha).
On utilise beginShape()/vertex()/endShape() pour tracer un
polygone point par point.
------------------------------------------------------------ */
function dessinerColline(w, yHorizon, couleurs) {
// Silhouette = couleur de la mer très assombrie.
const c = lerpColor(couleurs.mer, color(5, 15, 15), 0.7);
noStroke();
fill(c);
beginShape();
vertex(w * 0.45, yHorizon);
for (let x = w * 0.45; x <= w; x += w / 40) {
// map() : x va de 0,45 w à w ; on le convertit en angle de 0 à PI
// pour obtenir une bosse douce grâce à sin().
const a = map(x, w * 0.45, w, 0, PI);
vertex(x, yHorizon - sin(a) * w * 0.13 - sin(a * 3) * w * 0.012);
}
vertex(w, yHorizon);
endShape(CLOSE);
}
/* ------------------------------------------------------------
dessinerPalmier(x, ySol, hauteur, couleur)
But : dessiner un palmier simplifié (tronc courbe + palmes).
Ici on utilise push() / pop() / translate() / rotate() :
- push() sauvegarde l'état de dessin (position, rotation...) ;
- translate(x, y) déplace l'origine (0, 0) à cet endroit ;
- rotate(angle) tourne le repère (en RADIANS) ;
- pop() restaure l'état sauvegardé.
Ainsi, on dessine les palmes "autour de (0, 0)" sans se soucier
de leur place réelle.
Paramètres : x, ySol = pied du tronc ; hauteur ; couleur = p5 color
------------------------------------------------------------ */
function dessinerPalmier(x, ySol, hauteur, couleur) {
const sommetX = x + hauteur * 0.18; // le tronc penche un peu vers la droite
const sommetY = ySol - hauteur;
// Tronc : une courbe de Bézier (2 points + 2 points de contrôle).
noFill();
stroke(couleur);
strokeWeight(max(2, hauteur * 0.05));
bezier(x, ySol, x - hauteur * 0.05, ySol - hauteur * 0.4,
x + hauteur * 0.05, ySol - hauteur * 0.7, sommetX, sommetY);
// Palmes : 7 courbes qui partent du sommet, chacune tournée différemment.
push();
translate(sommetX, sommetY); // le sommet devient l'origine
strokeWeight(max(1.5, hauteur * 0.035));
for (let i = 0; i < 7; i++) {
push();
rotate(map(i, 0, 6, -PI * 0.95, PI * 0.05)); // étale les palmes en éventail
bezier(0, 0, hauteur * 0.25, -hauteur * 0.12,
hauteur * 0.45, -hauteur * 0.02, hauteur * 0.5, hauteur * 0.22);
pop();
}
pop(); // on revient au repère normal
}
/* ------------------------------------------------------------
dessinerDecor(heureDecimale, w, h, couleurs, etoiles)
But : assembler tout le paysage, dans le bon ordre.
C'est la seule fonction de ce fichier appelée par draw().
------------------------------------------------------------ */
function dessinerDecor(heureDecimale, w, h, couleurs, etoiles) {
const yHorizon = h * POS_HORIZON;
dessinerCiel(w, yHorizon, couleurs);
dessinerEtoiles(etoiles, w, yHorizon, couleurs.etoiles);
dessinerSoleilEtLune(heureDecimale, w, yHorizon);
dessinerMer(w, h, yHorizon, couleurs, heureDecimale);
dessinerColline(w, yHorizon, couleurs);
// Les palmiers sont des silhouettes sombres sur le côté gauche.
const silhouette = lerpColor(couleurs.mer, color(5, 15, 15), 0.75);
dessinerPalmier(w * 0.06, h * 0.97, h * 0.42, silhouette);
dessinerPalmier(w * 0.16, h * 1.0, h * 0.3, silhouette);
}