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/pirogue.js · 9.5 Ko
/* ==========================================================================
* js/pirogue.js — LA PIROGUE À BALANCIER (lakana)
* --------------------------------------------------------------------------
* Baie.creerPirogue(parent, mer) construit une pirogue malgache et la fait
* naviguer en rond autour de l'île, en flottant sur les vagues.
*
* Hiérarchie des ancres (c'est la clé du mouvement) :
*
* racine position sur la mer + direction du cap (rotate.y)
* └─ coque tangage et roulis (rotate.z / rotate.x), pour suivre la houle
* ├─ la coque, le pêcheur, le mât et la voile
* └─ le balancier : deux traverses + un flotteur latéral
*
* Pourquoi deux niveaux ? Les rotations de Zdog s'appliquent dans l'ordre
* z, puis y, puis x. Séparer le cap (racine) et la houle (coque) évite que
* le tangage ne se fasse autour du mauvais axe quand le bateau tourne.
*
* Dans le repère de la pirogue : x = avant/arrière (l'avant est à +x),
* y = haut (négatif), z = côté. Le balancier est sur le côté +z.
*
* SILLAGE : on utilise un « POOL » de 8 petites sphères créées UNE FOIS et
* réutilisées en boucle (un tampon circulaire). On ne crée jamais de forme
* pendant l'animation : c'est une bonne pratique de performance.
*
* Dépend de : Zdog, Baie.Palette (palette.js) ; reçoit `mer` (mer.js).
* ========================================================================== */
(function () {
'use strict';
var TAU = Zdog.TAU;
// ---- Constantes réglables -------------------------------------------------
var RAYON_X = 112; // largeur de la trajectoire (ellipse autour de l'île)
var RAYON_Z = 92; // profondeur de la trajectoire
var VITESSE_ANGULAIRE = 0.22; // radians par seconde : un tour en ~28 s
var NB_SILLAGE = 8; // nombre de sphères du sillage
var INTERVALLE_SILLAGE = 0.35;// secondes entre deux « gouttes »
var VIE_SILLAGE = 2.8; // durée de vie d'une goutte, en secondes
var BOIS = '#a0522d'; // coque
var BOIS_CLAIR = '#d29a62'; // bordé et flotteur
var BOIS_FONCE = '#6b4423'; // traverses et mât
var TOILE = '#f4ead2'; // voile
/**
* Construit la pirogue et son sillage.
*
* @param {Zdog.Anchor} parent Où l'accrocher (le « monde »).
* @param {Object} mer Objet renvoyé par Baie.creerMer() (pour mer.hauteur).
* @returns {Object} { racine, mettreAJour(t, dt), appliquerPalette(p) }
*/
function creerPirogue(parent, mer) {
// --- Les deux ancres de base ---
var racine = new Zdog.Anchor({ addTo: parent });
var coque = new Zdog.Anchor({ addTo: racine });
// 1) LA COQUE ----------------------------------------------------------
// Une courbe de Bézier épaisse : de l'arrière (-30) à l'avant (+30), en
// creux au milieu. Avec un trait de 8, elle ressemble à un tronc creusé.
new Zdog.Shape({
addTo: coque,
path: [
{ x: -30, y: -7 },
{ bezier: [{ x: -18, y: 2.5 }, { x: 18, y: 2.5 }, { x: 30, y: -7 }] },
],
closed: false,
stroke: 8,
color: BOIS,
});
// Le bordé : une ligne plus fine et plus claire, juste au-dessus.
new Zdog.Shape({
addTo: coque,
path: [
{ x: -31, y: -10 },
{ bezier: [{ x: -18, y: -1 }, { x: 18, y: -1 }, { x: 31, y: -10 }] },
],
closed: false,
stroke: 2.5,
color: BOIS_CLAIR,
});
// 2) LE PÊCHEUR --------------------------------------------------------
// Corps : un trait vertical épais ; tête : une sphère ; chapeau : un cône.
new Zdog.Shape({
addTo: coque,
path: [{ y: -3 }, { y: -15 }],
closed: false,
stroke: 5,
translate: { x: -8 },
color: '#e0573b', // chemise rouge
});
new Zdog.Shape({ // la tête (un point épais = une sphère)
addTo: coque,
stroke: 5.5,
translate: { x: -8, y: -20 },
color: '#8c5a3c',
});
new Zdog.Cone({ // le chapeau pointu (salakova)
addTo: coque,
diameter: 13,
length: 4,
translate: { x: -8, y: -22.5 },
rotate: { x: TAU / 4 }, // la pointe du cône regarde vers le haut
stroke: false,
color: '#d9b25f',
backface: '#b8913f',
});
// 3) LE MÂT ET LA VOILE ------------------------------------------------
var xMat = 6;
new Zdog.Shape({
addTo: coque,
path: [{ y: -6 }, { y: -44 }],
closed: false,
stroke: 2,
translate: { x: xMat },
color: BOIS_FONCE,
});
// La voile est dans une ancre à part, placée au pied du mât, pour pouvoir
// la faire pivoter doucement dans le vent (voile.rotate.y).
var voile = new Zdog.Anchor({ addTo: coque, translate: { x: xMat, y: -6 } });
var toile = new Zdog.Shape({
addTo: voile,
path: [
{ x: 0, y: -37 }, // haut du mât
{ x: 0, y: -2 }, // bas du mât
{ x: -22, y: -2 }, // bout de la bôme, vers l'arrière
],
fill: true,
stroke: 1.5,
color: TOILE,
});
// 4) LE BALANCIER ------------------------------------------------------
// Deux traverses (lignes de la coque vers le côté, axe z) et le flotteur
// (une longue courbe parallèle à la coque, posée sur l'eau).
[-12, 12].forEach(function (x) {
new Zdog.Shape({
addTo: coque,
path: [{ x: x, y: -6, z: 0 }, { x: x, y: -2, z: 24 }],
closed: false,
stroke: 1.8,
color: BOIS_FONCE,
});
});
new Zdog.Shape({
addTo: coque,
path: [
{ x: -26, y: -3, z: 24 },
{ bezier: [{ x: -14, y: 1.5, z: 24 }, { x: 14, y: 1.5, z: 24 }, { x: 26, y: -3, z: 24 }] },
],
closed: false,
stroke: 5,
color: BOIS_CLAIR,
});
// 5) LE SILLAGE (pool de sphères réutilisées) --------------------------
// Créées UNE fois, hors de la boucle d'animation.
var gouttes = []; // les formes
var gx = [], gz = [], gAge = []; // position et âge de chaque goutte
for (var i = 0; i < NB_SILLAGE; i++) {
gouttes.push(new Zdog.Shape({
addTo: parent,
stroke: 5,
color: '#ffffff',
visible: false,
}));
gx.push(0); gz.push(0);
gAge.push(VIE_SILLAGE); // « déjà vieille » : invisible au départ
}
var prochaine = 0; // index de la prochaine goutte à (ré)utiliser
var chrono = 0; // temps écoulé depuis la dernière goutte
// --- État de la trajectoire ---
var angle = 0.6; // position sur l'ellipse (radians)
/**
* Fait avancer la pirogue. À appeler à chaque image.
*
* @param {number} t Temps écoulé total (secondes) : sert à la houle.
* @param {number} dt Temps depuis l'image précédente (secondes) : sert au déplacement.
* Quand l'animation est en pause, dt vaut 0 : la pirogue s'arrête.
*/
function mettreAJour(t, dt) {
// a) Avancer sur l'ellipse. Vitesse × dt = indépendant du nombre d'images/s.
angle += VITESSE_ANGULAIRE * dt;
var x = RAYON_X * Math.cos(angle);
var z = RAYON_Z * Math.sin(angle);
// b) Le cap : la direction du mouvement est la DÉRIVÉE de la position.
// d(x)/d(angle) = -RAYON_X·sin ; d(z)/d(angle) = RAYON_Z·cos.
var dx = -RAYON_X * Math.sin(angle);
var dz = RAYON_Z * Math.cos(angle);
// Un objet tourné de θ autour de y voit son axe +x pointer vers
// (cos θ, 0, -sin θ). On cherche θ tel que ce soit (dx, dz) : θ = atan2(-dz, dx).
racine.rotate.y = Math.atan2(-dz, dx);
// c) Flotter : la hauteur de l'eau sous la pirogue (fonction de mer.js).
racine.translate.x = x;
racine.translate.z = z;
racine.translate.y = mer.hauteur(x, z, t) - 2;
// d) Tangage (avant/arrière, autour de z) et roulis (côté, autour de x).
coque.rotate.z = 0.07 * Math.sin(t * 1.6);
coque.rotate.x = 0.06 * Math.sin(t * 1.3 + 1);
// e) La voile pivote doucement dans le vent.
voile.rotate.y = 0.18 * Math.sin(t * 0.8);
// f) Le sillage : on pose une goutte à intervalle régulier…
chrono += dt;
if (chrono >= INTERVALLE_SILLAGE) {
chrono = 0;
gx[prochaine] = x;
gz[prochaine] = z;
gAge[prochaine] = 0; // elle redevient « jeune »
prochaine = (prochaine + 1) % NB_SILLAGE; // tampon circulaire : 7 -> 0
}
// …et on vieillit toutes les gouttes (elles rétrécissent puis disparaissent).
for (var i = 0; i < NB_SILLAGE; i++) {
gAge[i] += dt;
var vivante = gAge[i] < VIE_SILLAGE;
var g = gouttes[i];
g.visible = vivante;
if (vivante) {
g.translate.x = gx[i];
g.translate.z = gz[i];
g.translate.y = mer.hauteur(gx[i], gz[i], t) - 2;
g.stroke = 5 * (1 - gAge[i] / VIE_SILLAGE);
}
}
}
var tmp = [0, 0, 0];
var rgbToile = Baie.Palette.hexVersRgb(TOILE);
/**
* Adapte la voile et le sillage à l'ambiance (plus sombres la nuit).
* @param {Object} p Palette (Baie.Palette.calculer()).
*/
function appliquerPalette(p) {
toile.color = Baie.Palette.nuancer(rgbToile, -0.55 * p.etoiles, tmp);
var couleurEcume = p.css.ecume;
for (var i = 0; i < gouttes.length; i++) { gouttes[i].color = couleurEcume; }
}
return { racine: racine, mettreAJour: mettreAJour, appliquerPalette: appliquerPalette };
}
Baie.creerPirogue = creerPirogue;
})();