3D shape with a bad seam

Hello all,

I produced a 3D quadt_strip shape, but not only one row but multiple.

It’s kind of a circle (Moebius strip).

Unfortunately I have a big seam (horizontal right side) I cannot get rid of.

Can someone check my logic please? This gets called from setup() and is shown later.

Chrisir


// defining a QUAD_STRIP shape 

int i2_span=100; 

void initMainItem() {

  // https://de.wikipedia.org/wiki/M%C3%B6biusband

  boolean grid=false; 

  moebius1=createShape(); 
  moebius1.beginShape(QUAD_STRIP);

  if (grid) {
    // only grid 
    moebius1.noFill();
    moebius1.stroke(0);
  } else {
    // full fill
    moebius1.fill(255, 0, 0);
    moebius1.noStroke();
  }

  // i2 -> radius (how wide the red strip is) 
  // i3 / i  -> alpha (angle) 

  for (int i2=-i2_span; i2<i2_span; i2++) {
    for (int i3=0; i3<360+1; i3++) {

      int i=i3; 
      if (i>360) 
        i=i-360; 

      makeVertex3D(i2, i); 

      makeVertex3D(i2+1, i); 

      //
    }//for
    //
  }//for 


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

  /*
   for (int i2=-i2_span; i2<i2_span-1; i2+=1) {
   
   float r = i2/i2_span; 
   float alpha=radians(359); 
   PVector v = new PVector(
   cos(alpha) * ( 1+ ((r/2) * cos ( alpha / 2 )  )  ), 
   sin(alpha) * ( 1+ ((r/2) * cos ( alpha / 2 )  )  ), 
   (r/2) * sin(alpha/2));
   v.mult(factor1);
   v.y+=yadd1;
   //  moebius1.vertex(v.x, v.y, v.z);
   
   r = (i2+1)/i2_span;  
   alpha=radians(0);
   v = new PVector(
   cos(alpha) * ( 1+ ((r/2) * cos ( alpha / 2 )  )  ), 
   sin(alpha) * ( 1+ ((r/2) * cos ( alpha / 2 )  )  ), 
   (r/2) * sin(alpha/2));
   v.mult(factor1);
   v.y+=yadd1;
   //  moebius1.vertex(v.x, v.y, v.z);
   //
   }//for
   
   */

  moebius1.endShape();

  println("");
  println (">>>>"
    +leaves.size());
}//func

void makeVertex3D( int i2, int i) {

  float factor1=220; 
  float yadd1= 200; 

  float r = i2/i2_span; 
  float alpha=radians(i); 
  PVector v = new PVector(
    cos(alpha) * ( 1+ ((r/2) * cos ( alpha / 2 )  )  ), 
    sin(alpha) * ( 1+ ((r/2) * cos ( alpha / 2 )  )  ), 
    (r/2) * sin(alpha/2));
  v.mult(factor1);
  v.y+=yadd1;
  moebius1.vertex(v.x, v.y, v.z);
}
//
2 Likes

I was tinkering with this and added a vertex after for loops.

This cleaned it up but may not be the best solution:

      makeVertex3D(i2, i); 
      makeVertex3D(i2+1, i);
      }
    //moebius1.stroke(0);
    makeVertex3D(-50, 1);  //Added
    } 
  moebius1.endShape(); 
  }

I did something similar in past and recall issues with closing my Moebius strip:

:slight_smile:

Update:

I revisited my code from a year ago and updated it based on my suggestion to you!
I was overlapping to close shape but replaced it with a clean “close” of the shape:

  beginShape(QUAD_STRIP);
  for (float th1 = th_ini; th1<th_fin + 0; th1+=TAU/100) // was th_fin + TAU/100 to overlap
    {
    R = 350;
    float v = 60;
    float u = th1;
    mobPlot = mobius2(R, v, u); 
    vertex(mobPlot[0], mobPlot[1], mobPlot[2]);   
    mobPlot = mobius2(R, -v, u);
    vertex(mobPlot[0], mobPlot[1], mobPlot[2]);  
    }
    
  if (close)
    {
    mobPlot = mobius2(R, -60, 0); 
    vertex(mobPlot[0], mobPlot[1], mobPlot[2]);   
    mobPlot = mobius2(R, 60, 0);
    vertex(mobPlot[0], mobPlot[1], mobPlot[2]);  
    }
  endShape(); 
  }

Mobius2() based on formulas from:

2 Likes

very good, i had the same problem at first when i play with the

klein bottle

see here

2 Likes

It is almost certain that it is due to rounding errors on the floating point numbers I would change the loop to something like this

float nbrSlicess;
float delta = TAU / (nbrSteps +1); 
for(int s = 0; s < nbrSteps; s++){
  th1 = s * delta + th_ini
  ...

You would have to sort out how this fits your program but th1+=TAU/100 will generate rounding errors every time it is executed.

1 Like

Bringing back focus to original post:

@Chrisir Thanks for inspiring me to visit this topic again! I spent a few hours on my original versions last night and they are in a much better state.

:slight_smile:

Thank you all for the inspiring discussion!

I have to look into it later.

@glv
so your new line is between the two brackets of the for-loops?

It’s much better but I still have a weak seam.

Is this correct?


// defining a QUAD_STRIP shape 

int i2_span=100; 

void initMainItem() {

  // https://de.wikipedia.org/wiki/M%C3%B6biusband

  boolean grid=false; 

  moebius1=createShape(); 
  moebius1.beginShape(QUAD_STRIP);

  if (grid) {
    // only grid 
    moebius1.noFill();
    moebius1.stroke(0);
  } else {
    // full fill
    moebius1.fill(255, 0, 0);
    moebius1.noStroke();
  }

  // i2 -> radius (how wide the red strip is) 
  // i3 / i  -> alpha (angle) 

  for (int i2=-i2_span; i2<i2_span; i2++) {
    for (int i3=0; i3<361; i3++) {

      int i=i3; 
      if (i>360) 
        i=i-360; 

      makeVertex3D(i2, i); 
      makeVertex3D(i2+1, i); 
      //
    }//for
    //
    makeVertex3D(-50, 1);  //Added !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    //
  }//for

  // ---------------------------------
  
  moebius1.endShape();

  println("");
  println (">>>>"
    +leaves.size());
}//func

void makeVertex3D( int i2, int i) {

  float factor1 = 220; 
  float yadd1   = 200; 

  float r = i2/i2_span; 
  float alpha=radians(i); 
  PVector v = new PVector(
    cos(alpha) * ( 1+ ((r/2) * cos ( alpha / 2 )  )  ), 
    sin(alpha) * ( 1+ ((r/2) * cos ( alpha / 2 )  )  ), 
    (r/2) * sin(alpha/2));
  v.mult(factor1);
  v.y+=yadd1;
  moebius1.vertex(v.x, v.y, v.z);
}
//

I just tinkered with your code and did not present a solution.

I posted my Mobius strip work in the Gallery for the masses to enjoy viewing.

:slight_smile:

1 Like

I suggested the gap was caused by rounding errors due to repeated addition of irrational numbers. I did provide some starting code but have decided to provide a full solution showing how to avoid the gap. The code below produced this Mobius Strip.
18

import peasy.*;

PeasyCam pcam;

PVector[] s;
// Radius of Mobius strip
float r = 100;
// Width of mobius strip
float w = 40;
// Number of slices round the strip
int nbrSlices = 20;

void setup() {
  size(400, 400, P3D);
  pcam = new PeasyCam(this, 300);
  // Calculate strip
  s = new PVector[nbrSlices * 2];
  float deltaT = TWO_PI / (nbrSlices - 1);
  // Calculate strip vertices
  for (int i = 0; i < nbrSlices; i++) {
    float t = i * deltaT;
    float t2 = t / 2;
    float cos_t2 = cos(t2);
    float sin_t2 = sin(t2);
    float cos_t = cos(t);
    float sin_t = sin(t);
    s[i] = new PVector( (r + w*cos_t2)*cos_t, (r + w*cos_t2)*sin_t, w * sin_t2 );
    s[nbrSlices + i] = new PVector( (r - w*cos_t2)*cos_t, (r - w*cos_t2)*sin_t, -w * sin_t2 );
  }
  // Draw mobius
}

void draw() {
  background(0);
  lights();
  stroke(255);
  strokeWeight(2);
  fill(200, 30, 30);
  beginShape(QUAD_STRIP);
  for (int i = 0; i < nbrSlices; i++) {
    vertex(s[i].x, s[i].y, s[i].z);
    vertex(s[i+nbrSlices].x, s[i+nbrSlices].y, s[i+nbrSlices].z);
  }
  endShape();
}
2 Likes

suggested the gap was caused by rounding errors due to repeated addition of irrational numbers

Thanks a ton.

I didn’t think this was referring to my code since I don’t add up things.

No, I was referring to glv’s code.

Even so it highlights a problem many beginners don’t know about i.e. limited accuracy of floating point numbers in computing

1 Like

@Chrisir

Some nice work in the Gallery! Pleased to see you closed the gap.

I explored this further with my code (updated since last posts) so just adding to topic:

  1. There is no gap at the seam (first and last QUAD_STRIP joining).
  2. There is a “lighting issue” at the last “QUAD_STRIP” at the seam.
  3. I suspect the “lighting” of the “QUAD_STRIP” is related to this topic:
    https://processing.org/reference/normal_.html
  4. I used some normal() methods in code and it displays a smooth transition but the lighting was not correct (see link above). I will explore this further.
  5. I extended my Möbius strip shape by adding additional QUAD_STRIPS that were transparent and this provided a clean transition at the seam.
  6. I coloured the Möbius strip with a gradient (color wheel) and used the “extra transparent QUAD_STRIPS” for a smooth transition for this effect.
  7. I have fixed location lights() (start of loop) and rotate my Möbius shape after and this is when I see the “lighting issues” at the seam. If the light is rotated along with Möbius shape the lighting issues do not present themselves (expected since this view appeared seamless).

Your post helped me with my SPHEREs:

:slight_smile:

2 Likes

Integers in loops are indeed preferred practice:
https://help.semmle.com/wiki/display/CCPPOBJ/Avoid+floats+in+for+loops
https://chortle.ccsu.edu/Java5/Notes/chap42/ch42_6.html
https://www.quora.com/Can-we-use-floating-point-control-variables-in-the-case-of-iteration-in-C++-Is-it-always-wise-to-use-them

This did not affect my code (I did not have a gap) but I will be transitioning to this preferred practice.

:slight_smile:

1 Like

Could you show me how this is achieved? How do you rotate the lights() (and what kind?) ?

Thanks for the great Input!

Using quark’s example:

  1. PeasyCam is rotating everything including the lights(); try the example with noStrokes() and nbrSlices = 50. There is a noticeable issue at seam ((QUAD_STRIP) but not so apparent as next step 2.

  2. Remove all the references to PeasyCam and add this and increase nbrSlices = 50:

float theta;
void draw() {
background(0);
lights();
translate(width/2, height/2);
rotateX(theta);
theta += TAU/1000;
noStroke();

// stroke(255);
// strokeWeight(2);

And I see the same “lighting issues” that I discussed:
image

I wanted to see if the “lighting issues” were present in this example and they are for my use of lights().

The description here discusses this issue:

Sets the current normal vector. Used for drawing three dimensional shapes and surfaces, normal() specifies a vector perpendicular to a shape’s surface which, in turn, determines how lighting affects it. Processing attempts to automatically assign normals to shapes, but since that’s imperfect, this is a better option when you want more control.

:slight_smile:

1 Like

If you only use ambient light you don’t have the problem because the surface normals are ignored. If you un-comment the directional light line the problem appears because of the way Processing estimates the normal vector.

float theta;

PVector[] s;
// Radius of Mobius strip
float r = 100;
// Width of mobius strip
float w = 40;
// Number of slices round the strip
int nbrSlices = 50;

void setup() {
  size(400, 400, P3D);
  // Calculate strip
  s = new PVector[nbrSlices * 2];
  float deltaT = TWO_PI / (nbrSlices - 1);
  // Calculate strip vertices
  for (int i = 0; i < nbrSlices; i++) {
    float t = i * deltaT;
    float t2 = t / 2;
    float cos_t2 = cos(t2);
    float sin_t2 = sin(t2);
    float cos_t = cos(t);
    float sin_t = sin(t);
    s[i] = new PVector( (r + w*cos_t2)*cos_t, (r + w*cos_t2)*sin_t, w * sin_t2 );
    s[nbrSlices + i] = new PVector( (r - w*cos_t2)*cos_t, (r - w*cos_t2)*sin_t, -w * sin_t2 );
  }
}

void draw() {
  translate(width/2, height/2);
  background(0);
  ambientLight(200,200,200);
  // directionalLight(100,100,100, 0,1,1);
  noStroke();
  fill(200, 15, 15);
  rotateX(theta);
  theta += TAU / 200;
  beginShape(QUAD_STRIP);
  for (int i = 0; i < nbrSlices; i++) {
    vertex(s[i].x, s[i].y, s[i].z);
    vertex(s[i+nbrSlices].x, s[i+nbrSlices].y, s[i+nbrSlices].z);
  }
  endShape();
}```
3 Likes

Elaborating on what I did to work around using lights() which includes ambient() and directionall():

public void mobius2(int steps, float r, float v, float u)
  { 
  m2 = createShape();
  m2.beginShape(QUAD_STRIP);
    
  for (int step = 0; step < (steps+1)+2; step++)
    {
    u = step*(TAU/steps);  
           
   if (step > steps) 
     m2.fill(255, 255, 0, 0);
   else 
     m2.fill(255, 255, 0, 255);  
 
    mobEq(r, -v, u);
    m2.vertex(x, y, z);
    mobEq(r, v, u);
    m2.vertex(x, y, z);
    }
  m2.endShape();
  }

I will post my example code later; time to go for a hike in the woods for some fresh air!

:slight_smile:

2 Likes

Thanks for the insight.

That narrows it down to directionalLight(100,100,100, 0,1,1);
and normals (see description for anyone visiting topic):
https://processing.org/reference/normal_.html

I much prefer lights() and have my workaround working.

No one viewing sketch will ever know what I did. :zipper_mouth_face:

:slight_smile:

1 Like

The example is posted in the gallery:

1 Like