Image() on a 3d canvas on draw() and setup()

Hi!

I’m continuing with my efforts to replicate early works of computer art.

I made this nice sketch to replicate Manfred Mohr’s Pascal triangle:
http://p5js.sketchpad.cc/NbccVM8Nvk

And then I made an animated version, saving the parameters of each cube in order to make it on the draw() function:
https://editor.p5js.org/jgmy/sketches/-045EqDcU

Well, it seems that the code works different on setup() and draw().
I will copy below the relevant part, adding some comments.

draw () function on working version 1:

/* r is width/3, it is, 25 since I'm using a 300x300 WebGL canvas */

  pg = createGraphics(r*3, r*3, WEBGL);
  pg.ortho(-pg.width / 2, pg.width / 2, -pg.height / 2, pg.height / 2, 0, 500);   
  pg.rotateX(PI / 4);
  pg.rotateY(PI / 3);
  
  pg.stroke(255);
  pg.strokeWeight(r/10);
  pg.background(0);
  
  for(let piso=0;piso<Arista.length;piso++){
    for (let columna=0; columna<=piso; columna++){
      pg.background(0);
      for (let paso=0;paso<(Arista.length-piso);paso++){
        f=paso+columna;
        pg.line(
/* Arista[] is a shared array with x and y positions for the edges of a cube */
          Arista[f].x1, Arista[f].y1,Arista[f].z1,
          Arista[f].x2,Arista[f].y2,Arista[f].z2
        );
        image(pg,(columna-0.5*piso)*3*r, (piso-6)*3*r);
        }
    }

End of setup() and complete draw() on (failing) version 2:

setup() {
/* ... */
  for(let piso=0;piso<Arista.length;piso++){
    for (let columna=0; columna<=piso; columna++){
    
      Cube.unshift({
        "x":(columna-0.5*piso)*3*r,
        "y":(piso-6)*3*r,
        "piso":piso,
      "columna":columna,
      });
    }
  } 
}


function draw() {
/*
 I have checked all the shared variables to make sure 
 "r" lands here with a working value.
*/
  numCube=int(frameCount/10) % (Cube.length);
  piso=Cube[numCube].piso;
  columna=Cube[numCube].columna;
  xc=Cube[numCube].x;
  yc=Cube[numCube].y;
  
  console.log(numCube);
  let pg2 = createGraphics(r*3, r*3, WEBGL);
  pg2.ortho(-pg2.width / 2, pg2.width / 2,
           -pg2.height / 2,pg2.height / 2, 0,500);   
  pg2.rotateX(PI / 4);
  pg2.rotateY(PI / 3);
  pg2.stroke(255);
  pg2.strokeWeight(r/10);
  pg2.background(0);
      for (let paso=0;paso<(Arista.length-piso);paso++){
  
        f=paso+columna;
        pg2.line(
          Arista[f].x1, Arista[f].y1,Arista[f].z1,
          Arista[f].x2,Arista[f].y2,Arista[f].z2
        );
      }
      image(pg2,xc, yc);
/* If you run the next instruction, you'll see image fills the entire canvas, 
while its dimensions are exactly the same than in version 1 (25*25 pixels).
*/
//      image(pg2,0,0);

  
}

Result of version 1 (drawing on setup()):

Cube[2] of version 2 (drawing on draw()) if you uncomment image(pg2,0,0):


Notice the cube edge should be as small as in the previous version, but it is bigger.

It seems that pgraphics pasted at setup() produces 2D images, while pgraphics pasted at draw() are pasted on a 3D space.
Also, from error messages I get, it seems that minimal value of clipping at setup() seems to be zero, but clipping at draw() seems to have a minimal value of .01.

I understand I could use translate() and scale(), but I want to understand how things work first.

Well, it seems the empty frames were due to memory issues (despite of javascript having automatic garbage collection, removing the pg2=pgraphics() and replacing it with calls to pg solved the white frames issue).

Also, the value of “r” (cube size and pgraphics size) R was wrong. “r” was being initialized before createCanvas. And misteriously that worked for setup(). But not for draw(). Now I found that the right value for R should be width/12*3.

Also I reduced slightly the values of Arista[f].x1/x2/y1/y2/z1/z2. These values were initialized with values from -r2 to +r2. Now they are initialized with slightly smaller values.

But I still have far more clipping on draw() than on setup, EVEN when using resize() or translate()
Here is the result on a modified copy:

animation=false:


animation=true:

You can see the modified sketch at:
https://editor.p5js.org/jgmy/sketches/Yf3Y1HD-j

Hi @JoseMY. Please dont forget to format your code with CTRL+E, this will help us by simplifying code readability and access, as there is a quick copy code button for formatted text.

Thanks.

Sorry. I did not found such button. I used < code> and < /code>. But it seems that did not work.
I will try to edit the first post.

1 Like

OK, it seems the problem is with BACKGROUND. Background fills the shape, so you can’t delete it.

Here is a demo of this:

/*
I need to draw something by either:
a) using many instances of PGraphics, OR
b) deleting the PGraphics and using it.

But every time I use background() to delete the PGraphics,
the 3d shape is "filled".
*/

// To compare between using PGraphics or 
// drawing straight to canvas.
var USE_BUFFER=true; /* or false */

// To compare between using new PGraphics or
// using background();
var USE_BACKGROUND=true; /* or false */
var USE_NEW_BUFFER=false; /* or false */


/* shared object=pgraphics */
let pg;
/* Array of cube edges */
Arista = [];
/* Array of cube positions and edge numbers */

Cube=[];
var P = [
  {"x": +1,"y": -1,"z": -1},
  {"x": -1, "y": +1,"z": -1},
  {"x": -1,"y": -1,"z": +1},
  {"x": +1,"y": +1,"z": +1},
];
var L = [
  {"x": -2,"y": 0,"z": 0},
  {"x": 0,"y": -2,"z": 0},
  {"x": 0,"y": 0,"z": -2}
];
  
function setup() {
  //makeImage(10);
  pg=createGraphics(500,500,WEBGL);
  createCanvas(500,500,WEBGL);
  
}
var rr=5;
function draw(){
  if (USE_BUFFER){
    if (USE_BACKGROUND) {
         pg.background(255);
    }  else if (USE_NEW_BUFFER) {
      pg=createGraphics(500,500,WEBGL);
    } else {
      pg.clear();
    }
    
    makePGImage(rr);
    imageMode(CENTER);
    background(255);
    image(pg,0,0);
  } else {
    background(255);
    makeImage(rr);
    
  }
  rr+=5;
  if (frameCount==10) save();
}


function makeImage(r){
  var p,l,r;  
  
  push();
  //scale(width/50);
  colorMode(HSB);
   ortho(-width / 2, width / 2, height / 2, -height / 2, 0, 1E9);
  rotateY(PI/6);
  rotateX(PI/6);
      
  for(let f=0;f<P.length;f++){
    stroke(f*63,255,255-(f*50));
    strokeWeight(5);
    noFill();
  
    for (let g=0;g<L.length;g++){
      p=P[f];
      l=L[g];
      line(r*p.x, r*p.y, r*p.z, r*(p.x+p.x*l.x), r*(p.y+p.y*l.y), r*(p.z+p.z*l.z));
      t="line("+p.x+", "+p.y+", "+p.z+", "+(p.x+p.x*l.x)+","+ (p.y+p.y*l.y)+","+( p.z+p.z*l.z)+");";
      
      console.log(t )
    }
  }
  pop();
}


function makePGImage(r){
  var p,l,r;  
  
  pg.push();
  //scale(width/50);
  pg.colorMode(HSB);
   pg.ortho(-width / 2, width / 2, height / 2, -height / 2, 0, 1E9);
  pg.rotateY(PI/6);
  pg.rotateX(PI/6);
      
  for(let f=0;f<P.length;f++){
    pg.stroke(f*63,255,255-(f*50));
    pg.strokeWeight(5);
    pg.noFill();
  
    for (let g=0;g<L.length;g++){
      p=P[f];
      l=L[g];
      pg.line(r*p.x, r*p.y, r*p.z, r*(p.x+p.x*l.x), r*(p.y+p.y*l.y), r*(p.z+p.z*l.z));
      t="line("+p.x+", "+p.y+", "+p.z+", "+(p.x+p.x*l.x)+","+ (p.y+p.y*l.y)+","+( p.z+p.z*l.z)+");";
      
      console.log(t )
    }
  }
  pg.pop();
}

Direct painting: background() clears the Canvas:
direct-painting

Using background() on a PGraphics creates a color rectangle inside the PGraphics, so anything painted under that rectangle won’t be rendered:
using-background

Using clear() on the PGraphics will erase everything on it, making a transparent PGraphics. then you should use backgound() on your canvas if you want to remove things under your image.
using-clear

Using a new buffer will work as using clear(),
buffer-painting-new-buffer
… but you will run out of memory soon and nothing new will be rendered (change rr++=5 for rr+=1 and see the last frames).

I finally decided to avoid pGraphics.

Final result:
https://editor.p5js.org/jgmy/sketches/4nsa4Ttjk

/* 

  Preliminary study for replicating
  Manfred Mohr's Pascal Triangle, 
  part of his "Cubic limit I" series (1973-1975), 
  where he used the edges of a cube as an alphabet.
  
  You can see the original work of that artist at: 
  http://emohr.com/manfredCL.html
  
   Based on my previous sketch on sketchpad,
   http://p5js.sketchpad.cc/NbccVM8Nvk
 
*/
/* frames to wait before painting a line */
espera= 2; /* minimal value=2 */
/* Array of cube edges */
Arista = [];
/* Array of cube positions and edge numbers */
Cube=[];
// Array of 4 opposite vertices
P = [{x:1,y:-1,z:-1},{x:1,y:1,z:1},
     {x:-1,y:1,z:-1},{x:-1,y:-1,z:1},];
// Directions to make 3 edges from each vertex
L = [{x:-2,y:0,z:0},{x:0,y:-2,z:0},{x:0,y:0,z:-2}];

/* this variable is shared */
var r;

// this- is --run once.   
function setup() {
  
  createCanvas(1024, 1024, WEBGL);
  frameRate(10);
  //createCanvas(300, 300);
  r = width / (12*3);
  r2=r*.9;
  colorMode(HSB);
  background(100, 0, 0);
  
  
  for (let f = 0; f < P.length; f++) {
    stroke(f * 64, 255, 100);
    strokeWeight(r / 10);
    for (let g = 0; g < 3; g++) {
      Arista.push({
        "x1": P[f].x * r2,
        "y1": P[f].y * r2,
        "z1": P[f].z * r2,
        "x2": (P[f].x + P[f].x * L[g].x) * r2,
        "y2": (P[f].y + P[f].y * L[g].y) * r2,
        "z2": (P[f].z + P[f].z * L[g].z) * r2
      });
    }
  }
  
  /*
  En 0,-6 haz de 0 a 11
  En -1.5,-5 haz de 0 a 10, y en +1.5,5 haz de 1 a 11  
  */
  for(let piso=0;piso<Arista.length;piso++){
    for (let columna=0; columna<=piso; columna++){
      Cube.unshift({
        "x":(columna-0.5*piso)*3*r,
        "y":(piso-6)*3*r,
        "piso":piso,
      "columna":columna,
      });
    }
      
  }

  
  
}


// this is run repeatedly.  
function draw() {
  ortho(-width / 2, width / 2, -height / 2, height / 2, 0, width); // Same as ortho()
  translate(5*r,-2*r*8);
  rotateX(PI / 4);
  rotateY(PI / 3);
  
  noFill();
  strokeWeight(2);
  
  if (espera<2) espera=2;
  bright=frameCount %espera;
  stroke(bright*255/espera);
  numCube=int(frameCount/espera) % (Cube.length);
  piso=Cube[numCube].piso;
  columna=Cube[numCube].columna;
  xc=Cube[numCube].x;
  yc=Cube[numCube].y;
  
translate(columna*2*r,piso*2*r,(columna-piso)*2*r);
  //console.log(xc,yc,numCube);
  beginShape(LINES);
      for (let paso=0;paso<(Arista.length-piso);paso++){
         
        f=paso+columna;
        vertex(Arista[f].x1,Arista[f].y1,Arista[f].z1);
        vertex(Arista[f].x2,Arista[f].y2,Arista[f].z2);
     
      }
  endShape();
  
 / if (piso==0 && bright==espera-1) save(); 
}

nice! I just changed the topic category to fit to the content.

Finally I found where the trouble with PImage version was. Painting on canvas, the z clipping of (0,500) was enough, as it was in the createShape() version (clipping=max width, since size of shapes is derived from it).

Changing clipping to (0,1e9) solves the issue. I have modified my V2 version

https://editor.p5js.org/jgmy/sketches/Yf3Y1HD-j

(Note: 1e9 is an arbitrary big number, one followed by 9 zeroes. I use this style for big numbers from ZX Spectrum days, when PAUSE 4e4 meant PAUSE FOREVER).