Dans la fonction getHeighte(x, y, t), définir n’importe quelle équation pour calculer la hauteur en fonction des coordonnées (x,y)(x,y). Par exemple :
Un simple plateau avec une colline unique :
GridWave.prototype.getHeighte = function(x, y, t) {
//une colline
let A = 250, sigma = 80;
let h = A * Math.exp(-(x ** 2 + y ** 2) / (2 * sigma ** 2));
console.log("h",h);
return -h;
};
Des vagues superposées :
GridWave.prototype.getHeighte = function(x, y, t) {
let alpha = 150, beta = 25;
let h = Math.sin(alpha * x + t) + Math.sin(beta * y + t);
console.log("h",h);
return h*10;
};
un relief simple
GridWave.prototype.getHeighte = function(x, y, t) {
// Relief simple combinant sin et cos
let freq = 0.02; // fréquence de la sinusoïde
let amp = 30; // amplitude des collines
return amp * (Math.sin(freq * x + t) + 0.5 * Math.cos(freq * y + t));
};
Des reliefs plus complexes (avec du bruit pseudo-aléatoire, fractales, etc.).
Pour obtenir une forme « fixe » (non animée), supprimer la dépendance au temps (t) et fixer la hauteur une bonne fois pour toutes dans init().
Rendu visuel
Varier la taille, la couleur ou la transparence des points en fonction de proj.scale, de z2, ou même de la distance au centre pour accentuer l’effet de profondeur.
Au lieu de dessiner des cercles, utiliser d’autres primitives (petits rectangles, lignes, etc.) ou tracer des segments entre les points pour un rendu « wireframe ».
Interaction souris
Ici, la rotation dépend du déplacement de la souris. Ajuster les coefficients (/ 300, etc.) pour rendre le terrain plus ou moins sensible.
Egalement implémenter un « zoom » via la molette de la souris (en ajustant this.focus).
// ----------------------------------------------------------------
// Movieclip GridWave : "sol" en 3D, avec des collines animées
// + attraction des points vers la souris (si pas de clic)
// + texture via quadrilatères entre 4 points
// ----------------------------------------------------------------
const GridWave = function() {
this.resolveRoot(); // Accès à _root et méthodes du moteur
this.init();
};
GridWave.prototype.init = function() {
// Paramètres d'échantillonnage (taille du terrain, espacement des points)
this.widthRange = 400; // demi-largeur (en + / -) du terrain sur l'axe X
this.heightRange = 400; // demi-longueur (en + / -) du terrain sur l'axe Y
this.spacing = 20; // espacement entre deux points
// Paramètres d'animation
this.time = 0; // phase pour animer la hauteur
// Paramètres de projection
this.focus = 700; // distance focale (pour la perspective)
// Angles de rotation
this.rotX = Math.toRadians(-45);
this.rotY = 0;
// Calcul du nombre de colonnes/lignes
this.numColumns = Math.floor((this.widthRange * 2) / this.spacing) + 1;
this.numRows = Math.floor((this.heightRange * 2) / this.spacing) + 1;
// Construction du tableau de points 3D (baseX, baseY)
this.points = [];
// Organisation en lignes/colonnes : p(i,j) = this.points[i + j * numColumns]
for (let j = 0; j < this.numRows; j++) {
for (let i = 0; i < this.numColumns; i++) {
// Conversion (i,j) -> coordonnées réelles
let x = -this.widthRange + i * this.spacing;
let y = -this.heightRange + j * this.spacing;
// z sera calculé dynamiquement dans update()
//on donne un paramètre aléatoire à un point sur 10
let param = Math.random() < 0.1 ? true : false;
this.points.push({ baseX: x, baseY: y, z: 0, selected: param});
}
}
this.registerEvent("resize");
this.subscribe("resize", this, this.onResize, []);
};
// ----------------------------------------------------------------
// Méthode pour calculer la hauteur (z) d'un point
// en fonction de x, y et d'un paramètre de temps t.
// ----------------------------------------------------------------
GridWave.prototype.getHeighte = function(x, y, t) {
// Base : une colline gaussienne qui évolue dans le temps
let A = -250 * Math.cos(t);
let sigma = 80;
let base = A * Math.exp(-(x ** 2 + y ** 2) / (2 * sigma ** 2));
// Ajout de détails fractals
let detail = 0;
let octaves = 3; // nombre d'octaves pour les détails
let frequency = 0.02; // fréquence initiale pour les détails
let amplitude = 15; // amplitude initiale pour les détails
for (let i = 0; i < octaves; i++) {
detail += amplitude * Math.sin(frequency * x + t) * Math.cos(frequency * y + t);
frequency *= 2;
amplitude /= 2;
}
return base + detail;
};
// ----------------------------------------------------------------
// Mise à jour de l'animation + attraction vers la souris
// ----------------------------------------------------------------
GridWave.prototype.update = function() {
// Faire avancer le temps pour animer la hauteur
this.time += 0.02;
// Récupérer la position de la souris et centre
ENV.centreX = this._root._width / 2;
ENV.centreY = this._root._height / 2;
let mouseX = this._root.mouseX || ENV.centreX;
let mouseY = this._root.mouseY || ENV.centreY;
// Mise à jour de la rotation (si clic enfoncé)
if (this._root.modeClic) {
// Ajustez ces coefficients pour modifier la sensibilité
let variationY = (ENV.centreX - mouseX) / 6000;
let variationX = (ENV.centreY - mouseY) / 6000;
this.rotY += -variationY;
this.rotX += -variationX;
}
// Calculer la hauteur (z) de chaque point
// et ajouter un « bump » local si le clic n’est pas enfoncé
for (let i = 0; i < this.points.length; i++) {
let p = this.points[i];
// Hauteur de base : ondes sin/cos
p.z = this.getHeighte(p.baseX, p.baseY, this.time);
// Si la souris n'est pas cliquée, on attire localement les points
// en fonction de la distance au pointeur dans l'espace 2D projeté
if (!this._root.modeClic) {
// On projette le point temporairement pour obtenir ses coordonnées écran
let projTemp = this.projectTemp(p, this.rotX, this.rotY);
let dx = mouseX - projTemp.x;
let dy = mouseY - projTemp.y;
let dist = Math.sqrt(dx * dx + dy * dy);
// Bump additionnel, exponentiel décroissant
// Ajuster bumpAmp et bumpSize pour l’intensité et la zone d’influence
let bumpAmp = 80;
let bumpSize = 5000; // plus grand => attraction plus « large »
p.z += bumpAmp * Math.exp(-dist * dist / bumpSize);
}
}
};
// ----------------------------------------------------------------
// Projection 3D -> 2D avec rotation
// (version temporaire, sans affecter rotX/rotY de l'instance)
// pour calculer la distance au pointeur avant d'ajuster p.z
// ----------------------------------------------------------------
GridWave.prototype.projectTemp = function(point, rx, ry) {
// Rotation autour de l'axe Y
let cosY = Math.cos(ry), sinY = Math.sin(ry);
let x1 = point.baseX * cosY - point.z * sinY;
let z1 = point.baseX * sinY + point.z * cosY;
// Rotation autour de l'axe X
let cosX = Math.cos(rx), sinX = Math.sin(rx);
let y1 = point.baseY * cosX - z1 * sinX;
let z2 = point.baseY * sinX + z1 * cosX;
// Projection perspective
let k = this.focus / (this.focus + z2);
let screenX = x1 * k + ENV.centreX;
let screenY = y1 * k + ENV.centreY;
return { x: screenX, y: screenY, scale: k };
};
// ----------------------------------------------------------------
// Projection 3D -> 2D "officielle" pour l'affichage
// ----------------------------------------------------------------
GridWave.prototype.project = function(point) {
return this.projectTemp(point, this.rotX, this.rotY);
};
// ----------------------------------------------------------------
// Dessin de la surface par quadrilatères + dessin des points
// ----------------------------------------------------------------
GridWave.prototype.draw = function() {
// 1) Dessin de la « texture » via quadrilatères
// On parcourt la grille en 2D : (i,j) -> (i+1,j), (i,j+1), (i+1,j+1)
let ctx = this._root._canvas.getContext("2d");
for (let j = 0; j < this.numRows - 1; j++) {
for (let i = 0; i < this.numColumns - 1; i++) {
// Récupérer les 4 points du quadrilatère
let p1 = this.points[i + j * this.numColumns];
let p2 = this.points[i + 1 + j * this.numColumns];
let p3 = this.points[i + (j + 1) * this.numColumns];
let p4 = this.points[i + 1 + (j + 1) * this.numColumns];
// Projeter en 2D
let pr1 = this.project(p1);
let pr2 = this.project(p2);
let pr3 = this.project(p3);
let pr4 = this.project(p4);
// Choisir une couleur/alpha en fonction de la profondeur moyenne
let avgScale = (pr1.scale + pr2.scale + pr3.scale + pr4.scale) / 4;
let fillColor = "rgba(0, 255, 0, " + (0.09 * avgScale) + ")";
// Dessiner un polygone
ctx.beginPath();
ctx.moveTo(pr1.x, pr1.y);
ctx.lineTo(pr2.x, pr2.y);
ctx.lineTo(pr4.x, pr4.y);
ctx.lineTo(pr3.x, pr3.y);
ctx.closePath();
if(p1.selected || p2.selected || p3.selected || p4.selected ){
ctx.fillStyle = fillColor;
ctx.fill();
}else{
ctx.lineWidth = avgScale;
ctx.strokeStyle = fillColor;
ctx.stroke();
}
}
}
// 2) Dessin des points (pour souligner le maillage)
// On peut trier les points par z pour dessiner du plus éloigné au plus proche (optionnel).
// Ici, on dessine simplement en l'état.
for (let i = 0; i < this.points.length; i++) {
let proj = this.project(this.points[i]);
let size = Math.abs(2.5 * proj.scale);
let alpha;// = 0.9 * proj.scale + 0.1;
alpha = Math.map(proj.scale, 0.2, 1.2, 0, 1)
//console.log(alpha,proj.scale);
let color = "rgba(0,255,0," + alpha + ")";
//function(x = 0, y = 0, radiusX = this._radiusX, radiusY = this._radiusY, isFill = true, color = "red", _strokeWeight = 1, _stroke = 51, plein = true)
if(this.points[i].selected){
this.drawEllipse(proj.x, proj.y, size/1.5, size/1.5, true, color, proj.scale, color, true);
}else{
this.drawEllipse(proj.x, proj.y, size, size, true, color, proj.scale, color, false);
}
}
};
//redim on resize
GridWave.prototype.onResize = function() {
// 1) Récupérer les dimensions précédentes (pour calculer un ratio de redimensionnement)
let oldWidth = this._root._width;
let oldHeight = this._root._height;
// 2) Mettre à jour la taille du canvas
let canvas = this._root._canvas;
canvas.width = window.innerWidth * 0.98;
canvas.height = Math.round(canvas.width / (16/7));
this._root._width = canvas.width;
this._root._height = canvas.height;
/* // 3) Ajuster la focale (optionnel) en fonction du ratio
// pour conserver une échelle similaire. On peut choisir
// min(ratioW, ratioH) pour un zoom uniforme.
let ratioW = canvas.width / oldWidth;
let ratioH = canvas.height / oldHeight;
let ratio = Math.min(ratioW, ratioH);
// Par exemple, on adapte la focale :
this.focus *= ratio; */
// NB : On n’appelle pas init(), on ne touche pas à numColumns/numRows
// ni à this.points. La grille reste identique, seul le "zoom" varie.
};
// ----------------------------------------------------------------
// Méthode principale par frame
// ----------------------------------------------------------------
GridWave.prototype.onFrame = function() {
this.update();
this.draw();
};
// ----------------------------------------------------------------
// Intégration dans le moteur
// ----------------------------------------------------------------
if (_root["canvas_gridWave"]) {
let _rootGrid = ENV.CanvasManager.getRoot("canvas_gridWave");
_rootGrid._canvas.style.background = "black"; // Fond noir
// Configuration initiale du canvas
let dpr = window.devicePixelRatio || 1;
_rootGrid._canvas.width = window.innerWidth * 0.98;
_rootGrid._canvas.height = Math.round(_rootGrid._canvas.width / (16/7));
_rootGrid._width = _rootGrid._canvas.width;
_rootGrid._height = _rootGrid._canvas.height;
// Boucle d'animation
_rootGrid.onFrame = function() {
// Créer le movieclip GridWave une seule fois
if (!this.gridWaveCreated) {
this.createMovieClip("GridWave", GridWave, true, [50, 50, 5]);
this.gridWaveCreated = true;
}
// Le moteur appellera automatiquement la méthode onFrame() de chaque movieclip
};
// Gestion clic / relâché : modeClic true/false
_rootGrid.onPress = function() {
this.cursor = 7; // curseur de déplacement
this.manager.setCursor(this.cursor);
this.modeClic = true;
};
//Attention modification de la gestion du proxy pour cela
_rootGrid.onReleased = function() {
this.cursor = 2; // curseur par defaut
this.manager.setCursor(this.cursor);
this.modeClic = false;
};
// Gestion du redimensionnement du canvas
window.addEventListener("resize", function() {
_rootGrid.callEventAll("resize");
});
} else {
console.warn("canvas_gridWave introuvable dans _root !");
}