22/03/2025 @mg

GridWave


Personnalisation de la forme

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));
			
			
		}; 
Image
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 !");
}