Punti su una curva quadratica utilizzando Processing (p5.js)

Aug 22 2020

Sto usando questa formula per calcolare i punti su una curva quadratica:

  cPx2 = (1-t)*(1-t)* x1+2 * (1-t)*t*qcX + t*t*x2;
  cPy2 = (1-t)*(1-t)* y1+2 * (1-t)*t*qcY + t*t*y2;

Quando imposto t = 10 e scorro le curve, ottengo questo:

Sembra che stia ottenendo i punti sulla curva (la forma del fiore), ma anche tutti i punti sui "punti di controllo".

Ho usato questa formula per generare i punti:

    flowerArray=[]
    for(let i = 0; i < numVertices+1; i++) {
    angle = i * spacing;
    x = centerX + cos(radians(angle)) * 180;
    y = centerY+ sin(radians(angle)) * 180;

    if(i == 0) {
      flowerArray.push(x,y);
    }else {
        cAngle = angle - spacing/2;
          cX = centerX + cos(radians(cAngle)) * 100;
          cY = centerY+  sin(radians(cAngle)) * 100;
      
    flowerArray.push(cX,cY,x,y)
    }
   }

Domanda: È possibile ottenere solo i punti sul "fiore" e non sulla forma esterna?

Ho provato a saltare l'array in diversi modi, ma non sono riuscito a farlo funzionare come speravo.

AGGIORNAMENTO Lo sto usando per disegnare i punti:

    for (i = 0; i < flowerArray.length; i+=2){
        x1=flowerArray[i] 
        y1=flowerArray[i+1]  
        qcX=flowerArray[i+2] 
        qcY=flowerArray[i+3] 
        x2=flowerArray[i+4]
        y2=flowerArray[i+5] 
    for (k=0; k<= steps; k++) {   
      t = k/steps
      cPx2 = (1-t)*(1-t)* x1+2 * (1-t)*t*qcX + t*t*x2;
      cPy2 = (1-t)*(1-t)* y1+2 * (1-t)*t*qcY + t*t*y2;
        circle(cPx2, cPy2,3);    
}
}

Risposte

4 GeorgeProfenza Aug 22 2020 at 06:19

Che bella domanda.

L'unica cosa che spicca è questa parte:

if(i == 0) {
      flowerArray.push(x,y);
    }else {
        cAngle = angle - spacing/2;
          cX = centerX + cos(radians(cAngle)) * 100;
          cY = centerY+  sin(radians(cAngle)) * 100;
      
    flowerArray.push(cX,cY,x,y)
    }

Si noti che si chiama flowerArray.push(x,y);dove, come in ogni altro caso si preme 4 invece di due valori: flowerArray.push(cX,cY,x,y). Non è chiaro perché questa condizione sia necessaria in primo luogo:if(i == 0)

Il codice funziona come previsto senza di esso:

function setup() {
  
  createCanvas(512, 512);
  background(226, 255, 204);
  
  let flowerArray = [];
  let centerX = 256;
  let centerY = 256;
  let numVertices = 7;
  let steps = 11;
  let spacing = 360 / numVertices;

  
  for (let i = 0; i < numVertices + 1; i++) {
    
    angle = i * spacing;
    
    x = centerX + cos(radians(angle)) * 180;
    y = centerY + sin(radians(angle)) * 180;
  
    cAngle = angle - spacing/2;
      
    cX = centerX + cos(radians(cAngle)) * 100;
    cY = centerY+  sin(radians(cAngle)) * 100;
  
    flowerArray.push(cX, cY, x, y);
  }

  for (i = 0; i < flowerArray.length; i+=2) {
    
    x1=flowerArray[i];
    y1=flowerArray[i+1];  
    
    qcX=flowerArray[i+2];
    qcY=flowerArray[i+3];
    
    x2=flowerArray[i+4];
    y2=flowerArray[i+5];
    
    for (k=0; k <= steps; k++) {
      t = k/steps;
      cPx2 = (1-t)*(1-t)* x1+2 * (1-t)*t*qcX + t*t*x2;
      cPy2 = (1-t)*(1-t)* y1+2 * (1-t)*t*qcY + t*t*y2;
      
      circle(cPx2, cPy2, 3);
    }
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>

Personalmente consiglio di prendere l'abitudine di formattare il codice: rende più facile leggere il codice e individuare i problemi. Più programmi e più grandi sono i programmi, più tempo dedichi alla lettura del codice, quindi rendere il codice leggibile sarà sicuramente ripagato.

Un altro suggerimento è incapsulare la formula di Bézier quadratica in una funzione:

function quadLerp(p0, p1, p2, t){
  return ((1-t)*(1-t)) * p0 + 2 * ((1-t) * t * p1) + t * t * p2;
}

chiamandolo così:

  cPx2 = quadLerp(x1, qcX, x2, t);
  cPy2 = quadLerp(y1, qcY, y2, t);

Una cosa interessante delle curve di Bézier quadratiche è che puoi calcolarle interpolando due interpolazioni lineari:

Illustrazione delle curve di Bézier quadratiche nell'arte delle corde. In ogni caso, i punti finali contrassegnati da cerchi neri e il punto di controllo contrassegnato da una X definiscono la curva di Bézier quadratica mostrata come una linea tratteggiata dall'utente di Wikipedia Cmglee

Dato che è possibile calcolare l'interpolazione lineare in p5.js tramite lerp()è possibile calcolare l'interpolazione quadratica come:

function quadLerp(p0, p1, p2, t){
  return lerp(lerp(p0, p1, t),
              lerp(p1, p2, t),
              t);
}

È bello che p5.js supporti varie funzioni di disegno di curve come bezier()o curve()(e funzioni simili come bezierPoint()/ curvePoint()per calcolare il valore interpolato che potresti usare per il rendering personalizzato)

Aggiorna In base al tuo commento ho capito che vuoi disegnare solo la forma interna.

Il codice gestisce i punti esterni di un poligono regolare e i punti centrali interni, disegnando una forma simile a una stella ei successivi punti esterni e li utilizza come punti di ancoraggio / controllo per disegnare cerchi su curve di Bézier quadratiche tra questi punti. Come se questa non fosse abbastanza complessità, c'è un array che memorizza tutti i punti di ancoraggio e di controllo mescolati in un unico elenco e devi tenere traccia degli indici per disegnare correttamente. Oh, e stai anche usando la conversione del sistema di coordinate da polare a cartesiano per disegnare il poligono / stella regolare in primo luogo.

C'è molto da fare, quindi proviamo a scomporlo.

A partire dal disegnare la stella e la matematica dietro di essa: questo è simile alla domanda di islia e puoi vedere la mia risposta dettagliata qui .

Notate l' esempio principale nella sua domanda: non è un brutto punto di partenza poiché non dobbiamo preoccuparci dei punti di bezier quadratici. Introduce push()/ pop()che potresti non essere ancora familiare. È utile sapere, ma per ora puoi saltarlo. Diamo un'occhiata a una versione semplificata di quello snippet:

function setup() {
  createCanvas(512, 512);
}

function draw() {
  background(102);

  star(width * 0.5, height * 0.5, 80, 100, 7);
}

function star(x, y, radius1, radius2, npoints) {
  let angle = TWO_PI / npoints;
  let halfAngle = angle / 2.0;
  beginShape();
  for (let a = 0; a < TWO_PI; a += angle) {
    let sx = x + cos(a) * radius2;
    let sy = y + sin(a) * radius2;
    vertex(sx, sy);
    sx = x + cos(a + halfAngle) * radius1;
    sy = y + sin(a + halfAngle) * radius1;
    vertex(sx, sy);
  }
  endShape(CLOSE);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>

Ora diamo un'occhiata alla stessa cosa sono i nomi delle variabili più ovvi:

function setup() {
  createCanvas(512, 512);
}

function draw() {
  background(102);

  star(width * 0.5, height * 0.5, 80, 100, 7);
}

function star(x, y, innerRadius, outerRadius, npoints) {
  let angle = TWO_PI / npoints;
  let halfAngle = angle / 2.0;
  beginShape();
  
  for (let a = 0; a < TWO_PI; a += angle) {
    
    let xOuter = x + cos(a) * outerRadius;
    let yOuter = y + sin(a) * outerRadius;
    vertex(xOuter, yOuter);
    
    let xInner = x + cos(a + halfAngle) * innerRadius;
    let yInner = y + sin(a + halfAngle) * innerRadius;
    vertex(xInner, yInner);
  }
  
  endShape();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>

Si spera che questo renda più facile capire qual è il punto.

Per disegnare i punti Bézier quadratici avresti bisogno del punto esterno corrente e del punto esterno successivo come punti di ancoraggio e del punto interno corrente (tra di loro con il raggio più piccolo) come punto di controllo.

Ecco una versione modificata dello schizzo in cui la star()funzione viene riproposta per disegnare il fiore:

function setup() {
  createCanvas(512, 512);
}

function draw() {
  background(226, 255, 204);

  flower(width * 0.5, height * 0.5, mouseX, 100, 7);
  
  text("innerRadius = " + mouseX, 10, 15);  
}

function flower(x, y, innerRadius, outerRadius, npoints) {
  let angleIncrement = TWO_PI / npoints;
  let halfAngle = angleIncrement / 2.0;
  // increment by point index
  for (let i = 0; i < npoints; i++) {
    // calculate the current angle around the circle
    let angle = angleIncrement * i;
    // calculate current outer point
    let xOuter = x + cos(angle) * outerRadius;
    let yOuter = y + sin(angle) * outerRadius;
    // calculate current inner point
    let xInner = x + cos(angle + halfAngle) * innerRadius;
    let yInner = y + sin(angle + halfAngle) * innerRadius;
    
    // next angle increment
    let angleNext = angleIncrement * (i+1);
    // calculate next outer point
    let xOuterNext = x + cos(angleNext) * outerRadius;
    let yOuterNext = y + sin(angleNext) * outerRadius;
    // draw quad bezier between current and outer points with inner point as control point
    quadBezierCircles(xOuter, yOuter, xInner, yInner, xOuterNext, yOuterNext, 11);
    
    // for debug purposes only: render 
    if(mouseIsPressed){
      circle(xInner,yInner,9);
      circle(xOuter,yOuter,9);
    }
  }
}

function quadBezierCircles(anchorX1, anchorY1, controlX, controlY, anchorX2, anchorY2, steps){
  for (let k = 0 ; k <= steps; k++) {
    
    t = k / steps;
    
    x = quadLerp(anchorX1, controlX, anchorX2, t);
    y = quadLerp(anchorY1, controlY, anchorY2, t);
      
    circle(x, y, 3);
  }
}

function quadLerp(p0, p1, p2, t){
  return lerp(lerp(p0, p1, t),
              lerp(p1, p2, t),
              t);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>

Puoi muovere il mouse per controllare il raggio interno. Se tieni premuto il mouse puoi vedere i punti di ancoraggio / controllo.

Lo stesso avrebbe potuto essere disegnato come punti di bezier quadrupli tra i punti interni correnti e successivi come punti di ancoraggio con anche il punto esterno corrente come ancoraggio.