QUAD_STRIP in P3D

I am attempting to convert this ribbon shape that uses lines into using QUAD_STRIP or even TRIANGLE_STRIP.

Here is the code snippet that generates this:

noFill();
pushMatrix();
translate(midPt.x, midPt.y, midPt.z);
for(int i=0; i<numLines; i++){
  stroke(lerpColor(colorArray[ribbonClrA], colorArray[ribbonClrB], ((1/float(numLines)) * i)));
  strokeWeight(1);
  beginShape();
  for(float a=ribbonPhase; a<TWO_PI+ribbonPhase; a+=0.01){
    float currRad = map(i, 0, numLines, innerRad, outerRad);
    float currAmp = map(i, 0, numLines, innerAmp, outerAmp);
    float Xpos = currAmp * sin(map(a, 0, TWO_PI, 0, PI * ribbonFreq));
    float Ypos = currRad * sin(a);
    float Zpos = currRad * cos(a);
    curveVertex((i * lineSpace) + Xpos, Ypos, Zpos);
  }
  endShape(CLOSE);
}
popMatrix();

When using QUAD_STRIP i get this results:
ribbon_of_quads

And here is the code:

fill(colorArray[ribbonClrA], 175);
noStroke();
pushMatrix();
translate(midPt.x, midPt.y, midPt.z);
beginShape(QUAD_STRIP);
for(float a=ribbonPhase; a<=TWO_PI+ribbonPhase; a+=0.01){
  float innerXpos = innerAmp * sin(map(a, 0, TWO_PI, 0, PI * ribbonFreq));
  float innerYpos = innerRad * sin(a);
  float innerZpos = innerRad * cos(a);
  vertex(innerXpos, innerYpos, innerZpos);
  float outerXpos = outerAmp * sin(map(a, 0, TWO_PI, 0, PI * ribbonFreq));
  float outerYpos = outerRad * sin(a);
  float outerZpos = outerRad * cos(a);
  vertex(outerXpos, outerYpos, outerZpos);
}
endShape();
popMatrix();

How might I get this to work? Does QUAD_STRIP even work in P3D (there’s nothing to suggest it won’t)? Is there another solution altogether that is recommended?

1 Like

Please always post entire / runable code

1 Like

in draw() consider

void draw() {
   background(0); 
   avoidClipping(); 
   lights(); 
   ...

and paste this


// Tools 

void avoidClipping() {
  // avoid clipping (at camera): 
  // https : // 
  // forum.processing.org/two/discussion/4128/quick-q-how-close-is-too-close-why-when-do-3d-objects-disappear
  perspective(PI/3.0, (float) width/height, 1, 1000000);
}//func

also, you can use peasyCam to rotate freely in 3D: peasycam

Chrisir

not related QUADS example




final int resolution = 32;

QUAD3D[] points = new  QUAD3D[resolution * resolution];
float scale = 250.0;

void setup() {
  size(1720, 905, P3D);
  //  perspective(PI / 3.0, -width / (float)height, 0.001, 1000.0);
  noStroke();

  int len = points.length;

  // Calc points.
  float toFac = 1.0 / (resolution - 1.0);
  for (int k = 0; k < len; ++k) {
    int i = k / resolution;
    int j = k % resolution;

    float iFac = i * toFac;
    float jFac = j * toFac;

    float iSigned = iFac + iFac - 1.0;
    float jSigned = jFac + jFac - 1.0;
    float fxy = iSigned * iSigned * jSigned;

    float x = iSigned * scale;
    float y = jSigned * scale;
    float z = fxy * scale;

    int red = (int)(0.5 + 255.0 * iFac);
    int green = (int)(0.5 + 255.0 * jFac);
    int blue = (int)(0.5 + 255.0 * (fxy * 0.5 + 0.5));

    points[k] = new  QUAD3D();
    points[k].col = 0xff000000 | (red << 0x10) | (green << 0x08) | blue;
    points[k].pos.set(x, y, z);
  }
}

void draw() {
  surface.setTitle(nfs(frameRate, 1, 1));

  background(#202020);

  camera(
    width/2, -633, 320,
    0.0, 0.0, 0.0,
    0.0, 1, 0);

  avoidClipping();
  lights();

  // coord()

  strokeWeight(1.0);
  stroke(#ff0000);
  line(0.0, 0.0, 0.0, 100.0, 0.0, 0.0);
  stroke(#00ff00);
  line(0.0, 0.0, 0.0, 0.0, 100.0, 0.0);
  stroke(#0000ff);
  line(0.0, 0.0, 0.0, 0.0, 0.0, 100.0);
  noStroke();

  // Draw.
  /*
  if (mousePressed) {
   strokeWeight(1.0);
   stroke(#ffffff);
   } else {
   noStroke();
   }
   */

  rotateZ(frameCount * 0.01);

  beginShape(QUADS);
  for (int i = 0; i < resolution - 1; ++i) {

    int noff0 = i * resolution;
    int noff1 = noff0 + resolution;
    for (int j = 0; j < resolution - 1; ++j) {
      int v00 = noff0 + j;
      int v10 = v00 + 1;
      int v01 = noff1 + j;
      int v11 = v01 + 1;

      points[v00].display();
      points[v10].display();
      points[v11].display();
      points[v01].display();
    }
  }
  endShape(CLOSE);
}

// Tools

void avoidClipping() {
  // avoid clipping (at camera):
  // https : //
  // forum.processing.org/two/discussion/4128/quick-q-how-close-is-too-close-why-when-do-3d-objects-disappear
  perspective(PI/3.0, (float) width/height, 1, 1000000);
}//func

//==============================================================================

class QUAD3D {
  PVector pos=new PVector();
  color col=color(0);

  // no constr

  void display() {
    fill(col);
    vertex(pos.x, pos.y, pos.z);
  }
  //
}//class
//

Quad_strip example


import peasy.*;

final int GENERAL_Y_HEIGHT = 500;

//
PeasyCam camera;
PShape shapeBridgeToCastleGate;

// Array of ArrayList
ArrayList[] arrListBridgeToCastle = new ArrayList[2];

// -----------------------------------------------------

void setup() {
  size (1400, 800, P3D);

  avoidClipping();

  // The bridge over the water
  makeBridgeToCastle();

  camera = new PeasyCam(this, width/2, height/2, 0, 900);
} // func

void draw() {
  background(#12AEFF);

  // apply lights
  lights();

  shape(shapeBridgeToCastleGate);
}

// -----------------------------------------------------

void makeBridgeToCastle() {
  // plate in front - way to castle

  // make ArrayLists
  for (  ArrayList<PVector> a1 : arrListBridgeToCastle) {
    a1 = new ArrayList();
  }//for

  float depthHalf = 100;

  arrListBridgeToCastle[0] = getLineArc2( 115, depthHalf );  // lower right  // arc
  arrListBridgeToCastle[1] = getLineArc2( 115, -depthHalf );  // lower left   // arc

  noStroke();
  // fill(183, 143, 11);//brown
  fill(255, 0, 0);
  shapeBridgeToCastleGate=createShape();
  shapeBridgeToCastleGate.beginShape(QUAD_STRIP);

  // Eval ArrayLists
  for (int i3=0; i3<200-1; i3++) { //
    makeVertex3D2a( arrListBridgeToCastle[0], i3);
    makeVertex3D2a( arrListBridgeToCastle[0], i3+1);

    makeVertex3D2a( arrListBridgeToCastle[1], i3);
    makeVertex3D2a( arrListBridgeToCastle[1], i3+1);
  }//for

  shapeBridgeToCastleGate.endShape();
}//func

void makeVertex3D2a( ArrayList<PVector> arrL_, int i ) {

  // ArrayList version

  PVector v = arrL_.get(i);
  shapeBridgeToCastleGate.vertex(v.x, v.y, v.z);
}

void avoidClipping() {
  // avoid clipping :
  // https : //
  // forum.processing.org/two/discussion/4128/quick-q-how-close-is-too-close-why-when-do-3d-objects-disappear
  perspective(PI/3.0, (float) width/height, 1, 1000000);
}//func

// ---------------------------------------------------------------------------------------

ArrayList<PVector> getLineArc2 ( float y_, float z_ ) {

  // fill ArrayList with Arc

  // make  ArrayList
  ArrayList<PVector> newArrayList = new ArrayList();

  for (int i3=0; i3<800; i3++) { //

    float farAngle=PI/3;
    float angle=map(i3, 0, 800, -farAngle, farAngle);
    angle+=radians(310);
    float r =580 ;
    float x=cos(angle)*r;
    float y=sin(angle)*r+y_;

    // store points in list

    PVector pv1 = new PVector(2.5*x+394+222+111-32-32, 4.1*y+GENERAL_Y_HEIGHT+941+400+22+22+12+22+333+111, zValue2(z_, angle));

    newArrayList.add(pv1);
  } //for

  return newArrayList;
  //
}// func ---

float zValue2(float z_, float angle_) {
  float factor1=3.1;
  if (z_<0)
    return z_-angle_*factor1;
  else
    return z_+angle_*factor1;
}
//

1 Like

As you can see in the last example your usage of the QuadStrip might be wrong imho

In these code snippets

beginShape(QUAD_STRIP);
    // vertex data in correct order
endShape();

and

beginShape(TRIANGLE_STRIP);
    // vertex data in correct order
endShape();

the vertex data order is exactly the same so the only code difference is the first line but there is still a difference between using quads and triangles.

You have probably heard the phrase

three points define a plane

so for any three points in 3D space they will always sit on a flat pane. Quads require 4 points to define them and there is no guarantee that they will all sit on the same flat plane.

In your example I would recommend using triangle strips because the complex curvature in the shape. A quad strip might produce a more angular surface.

So all that remains is to determine the vertex order to use.

Consider the following geometry 7 x 4 points -

In both quad and triangle strip the vertices are specified in pairs like this

// First strip
beginShape(TRIANGLE_STRIP);
    [0 1] [2 3] [4 5]......[12 13]
endShape();
// Second strip
beginShape(TRIANGLE_STRIP);
    [14 15] [16 17] [18 19]......[26 27]
endShape();
// and so on

The numbers represent the order the points should be specified. Note that some represent the same point e.g.
{1 & 14} {13 26} {21 & 34}

To avoid calculating these points twice I suggest that you create a 2D array for the points then

  1. calculate the XYZ position for each point and store it in the array
  2. use the array to render the shape.

Separating the calculation form the rendering has several benefits

  • each vertex is calculated just once and then shared when rendering
  • the vertex calculations only need to be done if the shape changes
  • simplifies the code by compartmentalizing it into responsibilities
  • the array can be used with p5.Geometry for super fast rendering (advanced topic)
2 Likes

can you explain this a bit more please. the link isn’t working. what is happening in the call to perspective and what is it solving for?

void avoidClipping() {
  // avoid clipping (at camera): 
  // https : // 
  // forum.processing.org/two/discussion/4128/quick-q-how-close-is-too-close-why-when-do-3d-objects-disappear
  perspective(PI/3.0, (float) width/height, 1, 1000000);
}

Sure, it avoids clipping/ cut off at the camera

Sometimes shapes are cut off when it’s too near the camera

The last 2 parameters determine the near and far zone distances respectively. Any point closer than the near zone or further than the far zone distance will not be rendered.

So in this statement only points >=1 and <=1000000 will be rendered. This is an extremely wide render zone and could adversely impact performance because distant objects will be rendered even if the display size ends up smaller than a pixel! Similarly very near objects could be outside the field of view and be needlessly rendered.

The default values depend on the width and height of your sketch window and you can calculate them using the reference for perspective. The actual values you need depend on your sketch size and what you want to render. I suggest you calculate them for your sketch size and only increase the render zone if you have to,

3 Likes

Hello @travis.stdenis,

Your code (I added to it) did generate a single ribbon with QUAD_STRIP or TRIANGLE_STRIP:

// Original code:
// https://discourse.processing.org/t/quad-strip-in-p3d/42579

// Some additions and modifications by glv 2023-08-13 here:
// https://discourse.processing.org/t/quad-strip-in-p3d/42579/9

size(500, 500, P3D);  
background(192);  

translate(width/2, height/2, -400);  // z moved back
rotateY(TAU/16);

float innerRad = 150; 
float innerAmp = 150; 

float outerRad = 150+30; 
float outerAmp = 150+30; 

float ribbonFreq = 4;

beginShape(QUAD_STRIP);
//beginShape(TRIANGLE_STRIP);
  
int steps = 50;

for(int j=0; j<=steps; j++) // This replaces the for loop commented below
  {
  float th = j*(TWO_PI/steps);

//for(float th=0; th<TWO_PI; th+=0.05)
//  {
  
  float innerXpos = innerAmp * sin(map(th, 0, TWO_PI, 0, PI * ribbonFreq));
  float innerYpos = innerRad * sin(th);
  float innerZpos = innerRad * cos(th);
  vertex(innerXpos, innerYpos, innerZpos);
  
  float outerXpos = outerAmp * sin(map(th, 0, TWO_PI, 0, PI * ribbonFreq));
  float outerYpos = outerRad * sin(th);
  float outerZpos = outerRad * cos(th);
  vertex(outerXpos, outerYpos, outerZpos);
  }
endShape();  

Output:

I repeated the above ribbon in a nested for() loop and modified Rad and Amp as required:

Scrutinize the code for other changes.

Related:

:)

1 Like

new version with

  • lights(), peasyCam and avoidClipping

// Original code:
// https://discourse.processing.org/t/quad-strip-in-p3d/42579
// Some additions 

import peasy.*;

PeasyCam camera;

// ---------------------------------------------------------------------------------------

void setup() {
  size(500, 500, P3D);
  background(192);

  camera = new PeasyCam(this, width/2, height/2, 0, 900);
}

void draw() {
  background(192);

  avoidClipping();

  lights();
  translate(width/2, height/2, -400);  // z moved back
  rotateY(TAU/16);

  float innerRad = 150;
  float innerAmp = 150;

  float outerRad = 150+30;
  float outerAmp = 150+30;

  float ribbonFreq = 4;


  //translate(midPt.x, midPt.y, midPt.z);


  beginShape(QUAD_STRIP);
  //beginShape(TRIANGLE_STRIP);

  int steps = 50;

  //fill(colorArray[ribbonClrA], 175);
  fill(255, 0, 0, 175);
  noStroke();
  pushMatrix();

  float ribbonPhase=0.83;
  for (float a=ribbonPhase; a<=TWO_PI+ribbonPhase; a+=0.01) {
    float innerXpos = innerAmp * sin(map(a, 0, TWO_PI, 0, PI * ribbonFreq));
    float innerYpos = innerRad * sin(a);
    float innerZpos = innerRad * cos(a);
    vertex(innerXpos, innerYpos, innerZpos);
    float outerXpos = outerAmp * sin(map(a, 0, TWO_PI, 0, PI * ribbonFreq));
    float outerYpos = outerRad * sin(a);
    float outerZpos = outerRad * cos(a);
    vertex(outerXpos, outerYpos, outerZpos);
  }
  endShape();
  popMatrix();
}

// ---------------------------------------------------------------------------------------

void avoidClipping() {
  // avoid clipping :
  // https : //
  // forum.processing.org/two/discussion/4128/quick-q-how-close-is-too-close-why-when-do-3d-objects-disappear
  perspective(PI/3.0, (float) width/height, 1, 1000000);
}//func
// ---------------------------------------------------------------------------------------
//

1 Like

Hello @travis.stdenis,

Integer for() loop:

for(int j=0; j<steps; j++)
  {
  float th = j*(TWO_PI/steps);  // TWO_PI/steps most likely optimized but can be taken out of loop

Output with integer for() loop:

Floating point for() loop:

for(float th=0; th<=TWO_PI; th+=TWO_PI/50)
  {

for(float th=0; th<=TWO_PI; th+=.01)
  {

Zoomed in to see the gap:

Do you need this density of triangles?

Some code to examine the different methods for integer for() loop:

int steps = 100;
float inc = TWO_PI/steps;
float th0=0, th1=0;

for(int j=0; j<=steps; j++)
  {
  th0 = j*inc;  // Preferred!
  println("th0:", th0, "  th1:", th1, "  diff:", th1-th0);
  th1 = th1 += inc; // The Processing example did this!
  }
  
println("TWO_PI", TWO_PI);

Output:

:)