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/main.js · 9.1 Ko
/* ==========================================================================
* js/main.js — ASSEMBLAGE : scène, interface et boucle d'animation
* --------------------------------------------------------------------------
* Ce fichier est le « chef d'orchestre » :
* 1. crée l'Illustration Zdog (adaptée à la taille de l'écran) ;
* 2. assemble le monde : sol, maison, véranda, escalier, arbre ;
* 3. branche l'interface : boutons de vue, zoom, couleur de façade ;
* 4. lance la boucle d'animation.
*
* Ordre des scripts (voir index.html) :
* zdog.min.js, config.js, outils.js, sol.js, maison.js, veranda.js,
* arbre.js, camera.js, main.js
* ========================================================================== */
(function () {
'use strict';
var CAM = Maison.CAMERA;
// ---- 1. ÉTAT ------------------------------------------------------------
var etat = {
zoomBase: 1, // zoom calculé selon la taille du canevas
zoomUtilisateur: 1, // multiplicateur choisi avec le curseur
dernierTemps: 0,
};
// ---- 2. STOCKAGE DU CHOIX DE FAÇADE -------------------------------------
// IMPORTANT : ce site est exécuté dans un « bac à sable » (origine opaque).
// Dans ce cas, localStorage LÈVE une exception SecurityError dès qu'on y
// touche. On entoure donc chaque accès d'un try/catch, et on garde la
// valeur en mémoire (variable) comme solution de repli.
var memoire = {};
function lireStockage(cle, defaut) {
try {
var v = window.localStorage.getItem(cle);
return v === null ? defaut : v;
} catch (e) {
return memoire.hasOwnProperty(cle) ? memoire[cle] : defaut;
}
}
function ecrireStockage(cle, valeur) {
memoire[cle] = valeur; // repli toujours à jour
try { window.localStorage.setItem(cle, valeur); } catch (e) { /* bac à sable : on ignore */ }
}
// ---- 3. CALCUL DU ZOOM ADAPTATIF ----------------------------------------
/**
* Zoom de base : on veut que la scène (largeurScene × hauteurScene unités)
* tienne en entier dans le canevas, qu'il soit large (ordinateur) ou
* étroit (téléphone). On prend donc le PLUS PETIT des deux rapports.
* @param {number} largeur Largeur CSS du canevas (px).
* @param {number} hauteur Hauteur CSS du canevas (px).
*/
function calculerZoomBase(largeur, hauteur) {
return Math.min(largeur / CAM.largeurScene, hauteur / CAM.hauteurScene);
}
/** Applique zoom de base × zoom du curseur à l'illustration. */
function appliquerZoom() {
illo.zoom = etat.zoomBase * etat.zoomUtilisateur;
}
// ---- 4. L'ILLUSTRATION --------------------------------------------------
var illo = new Zdog.Illustration({
element: '#maison-canvas',
dragRotate: true, // glisser pour tourner librement
resize: true, // le canevas épouse son conteneur CSS ; Zdog multiplie
// sa résolution par window.devicePixelRatio => rendu net
// sur les écrans haute densité (téléphones, Retina…)
rotate: { x: Maison.VUE_DEPART.x, y: Maison.VUE_DEPART.y }, // vue 3/4 de départ
onDragStart: function () {
// Si l'utilisateur glisse, on arrête la caméra automatique / la transition.
camera.arreter();
majBoutons();
},
onResize: function (largeur, hauteur) {
// `this` désigne l'illustration. Appelée aussi quand on tourne le téléphone.
etat.zoomBase = calculerZoomBase(largeur, hauteur);
this.zoom = etat.zoomBase * etat.zoomUtilisateur;
},
});
etat.zoomBase = calculerZoomBase(illo.width, illo.height);
appliquerZoom();
// ---- 5. LE MONDE --------------------------------------------------------
// Une ancre qui contient toute la scène, descendue de 62 unités pour que le
// centre de rotation soit à mi-hauteur de la maison (et non au sol).
var monde = new Zdog.Anchor({
addTo: illo,
translate: { y: CAM.decalageVertical },
});
// Choix de façade au démarrage (mémorisé si possible).
var cleFacade = lireStockage('maison-facade', 'vanille');
if (!Maison.FACADES[cleFacade]) { cleFacade = 'vanille'; }
Maison.creerSol(monde);
// Le groupe de la maison : ses enfants sont triés ensemble du plus loin au
// plus proche (updateSort: true) pour un rendu correct sous tous les angles.
var groupeMaison = new Zdog.Group({ addTo: monde, updateSort: true });
var corps = Maison.creerCorps(groupeMaison, Maison.FACADES[cleFacade]);
Maison.creerVeranda(groupeMaison);
Maison.creerEscalier(groupeMaison);
// L'arbre, à gauche de la maison et un peu en avant.
Maison.creerArbre(monde, { x: -100, z: 22, hauteur: 42, graine: 11, boules: 8 });
// Un deuxième plus petit à droite, avec une autre graine => une autre forme.
Maison.creerArbre(monde, { x: 100, z: -62, hauteur: 30, graine: 5, boules: 6 });
// ---- 6. LA CAMÉRA -------------------------------------------------------
var texteVue = document.getElementById('info-vue');
var camera = Maison.creerCamera(illo, function (texte) {
if (texteVue) { texteVue.textContent = 'Affichage : ' + texte; }
});
// ---- 7. INTERFACE -------------------------------------------------------
var boutonsVue = {
face: document.getElementById('btn-face'),
cote: document.getElementById('btn-cote'),
dessus: document.getElementById('btn-dessus'),
};
var boutonAuto = document.getElementById('btn-auto');
/** Met à jour l'état visuel (aria-pressed) du bouton de rotation automatique. */
function majBoutons() {
if (boutonAuto) { boutonAuto.setAttribute('aria-pressed', camera.estAuto() ? 'true' : 'false'); }
}
// Un bouton par vue : on parcourt l'objet boutonsVue avec une boucle for…in.
Object.keys(boutonsVue).forEach(function (cle) {
var bouton = boutonsVue[cle];
if (!bouton) { return; }
bouton.addEventListener('click', function () {
camera.allerVers(cle);
majBoutons();
});
});
if (boutonAuto) {
boutonAuto.addEventListener('click', function () {
camera.basculerAuto();
majBoutons();
});
}
// Curseur de zoom.
var curseurZoom = document.getElementById('ctrl-zoom');
var texteZoom = document.getElementById('txt-zoom');
if (curseurZoom) {
curseurZoom.addEventListener('input', function () {
etat.zoomUtilisateur = parseFloat(curseurZoom.value); // la valeur est du texte : on convertit
appliquerZoom();
if (texteZoom) { texteZoom.textContent = Math.round(etat.zoomUtilisateur * 100) + ' %'; }
});
}
// Boutons de couleur de façade : GÉNÉRÉS à partir de Maison.FACADES.
var zoneFacade = document.getElementById('choix-facade');
if (zoneFacade) {
Object.keys(Maison.FACADES).forEach(function (cle) {
var facade = Maison.FACADES[cle];
var b = document.createElement('button');
b.type = 'button';
b.className = 'pastille';
b.textContent = facade.nom;
b.style.setProperty('--pastille', facade.mur); // la couleur sert d'accent CSS
b.setAttribute('aria-pressed', cle === cleFacade ? 'true' : 'false');
b.addEventListener('click', function () {
cleFacade = cle;
Maison.peindreFacade(corps, facade); // on repeint, sans reconstruire
ecrireStockage('maison-facade', cle);
var tous = zoneFacade.querySelectorAll('button');
for (var i = 0; i < tous.length; i++) {
tous[i].setAttribute('aria-pressed', tous[i] === b ? 'true' : 'false');
}
});
zoneFacade.appendChild(b);
});
}
// Information pédagogique : la densité de pixels de l'écran.
var infoDpr = document.getElementById('info-dpr');
if (infoDpr) {
infoDpr.textContent = 'Densité de pixels de votre écran : ×' + (window.devicePixelRatio || 1) +
' (le canevas est dessiné en ' + Math.round(illo.width * (window.devicePixelRatio || 1)) + ' × ' +
Math.round(illo.height * (window.devicePixelRatio || 1)) + ' pixels).';
}
// Rotation automatique au démarrage, sauf si l'utilisateur préfère moins d'animations.
var moinsAnimations = false;
try {
moinsAnimations = !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
} catch (e) { /* on garde false */ }
if (!moinsAnimations) {
camera.basculerAuto();
majBoutons();
}
// ---- 8. BOUCLE D'ANIMATION ----------------------------------------------
/**
* Appelée par le navigateur avant chaque affichage (~60 fois par seconde).
* @param {number} maintenant Horodatage en millisecondes.
*/
function animer(maintenant) {
// deltaTime en secondes, borné pour éviter les sauts après un onglet en pause.
var dt = Math.min(Math.max((maintenant - etat.dernierTemps) / 1000, 0), 0.1);
etat.dernierTemps = maintenant;
camera.mettreAJour(dt); // fait avancer la transition ou la rotation auto
illo.updateRenderGraph(); // met à jour puis dessine toute la scène
requestAnimationFrame(animer);
}
requestAnimationFrame(animer);
// Pour les curieux : on expose la scène et la caméra pour les essayer dans la
// console du navigateur, par exemple Maison.illo.rotate.y = 1;
Maison.illo = illo;
Maison.camera = camera;
})();