Drawing a flower of life with 6 Vesica Piscis

I’m having trouble getting this to draw the shape nicely. I have one function that evenly draws six petals, rotating them around, and one function to draw a single petal. The issue is not apparent when there is noStroke, but adding a stroke makes it clear. Some of the points are not pointy, and it only happens at certain rotations. Is it some way that the stroke is handled (I tried different joins and caps), should I try P2D instead (the curves are off in that)? Do I need more or less vertices? Any ideas?


float rotateAngle = 90;

void setup() {
  size(800, 800);
  
  // Draw the flower at the center with a radius of 300 pixels
  strokeWeight(10);
  strokeJoin(MITER);
  strokeCap(SQUARE);
  drawFlower(width / 2, height / 2, 300);

}

void drawFlower(float x, float y, float radius) {
  pushMatrix();
  translate(x, y);
  rotate(rotateAngle);

  for (int i = 0; i < 6; i++) {
    pushMatrix();
    rotate(TWO_PI / 6 * i);
    vesicaPiscis(0, 0, radius);
    popMatrix();
  }

  popMatrix();
}


void vesicaPiscis(float x, float y, float r) {
  float apex = r - ((r * sqrt(3)) / 2);

  beginShape();
  vertex(x, y);
  curveVertex(x, y);
  curveVertex(x - apex, y - r / 2);
  curveVertex(x, y - r);
  curveVertex(x, y - r);
  vertex(x, y - r);
  curveVertex(x, y - r);
  curveVertex(x + apex, y - r / 2);
  curveVertex(x, y);
  curveVertex(x, y);
  vertex(x, y);
  endShape(CLOSE);
}         

1 Like

Hello @mortap ,

Very different behavior between renderers JAVA2D (default), P2D and FX2D!

I tried breaking it up between a curve, vertices and a curve to force a clean miter with P2D.

The strokeJoin() reference uses vertex only and tried that:

void vesicaPiscis1(float x, float y, float r) {
  float apex = r - ((r * sqrt(3)) / 2);

  beginShape(POLYGON);
  curveVertex(x, y);
  curveVertex(x, y);
  curveVertex(x - apex, y - r / 2);
  curveVertex(x-30, y+50 - r);
  curveVertex(x-30, y+50 - r);
 
// https://processing.org/reference/strokeJoin_.html
  vertex(x-30, y+50 - r);
  vertex(x, y - r);
  vertex(x+30, y+50 - r);
  
  curveVertex(x+30, y+50 - r);
  curveVertex(x+30, y+50 - r);
  curveVertex(x + apex, y - r / 2);
  curveVertex(x, y);
  curveVertex(x, y);
  endShape(CLOSE);
}

I did not spend much time on this so this will need more work on your part to get some smooth transisitions.

P2D Looks the best! :

JAVA2D (default):

FX2D:

Reference:

Have fun!

With a bit if coercion and finagling of the code:

:)

2 Likes

I suggest using a vector graphics editor – anything where you can inspect the handles of a curve – to help you construct the seed of life with either a radius or diameter of 1.0.

Then, in Processing, I would use bezierVertex instead of curveVertex.

Below, for example, are screen captures from Blender, made with the help of some Python scripts.

This is a circle with six Bezier knots. You’ll start to see geometric relationships as you go, for example, sin(60) = sqrt(3) / 2 = 0.866025 approximately.

This arc spans 60 to 120 degrees; it is located at (0.5, -0.866025), or 60 degrees clockwise from 0.

The orthonormal knots on the side of the vesica aren’t necessary because 120 - 60 is only 60 degrees arc length, but they make calculating its width easier.

Not sure how to quickly summarize the relationship between the coordinates in a Bezier shape and their handles. It’s easier to learn if you start with a circle where 4 Bezier knots approximate a 90 degree arc each. Tutorials on how to make a Bezier circle will often refer to a magic number called kappa: (sqrt(2.0) - 1.0) * (4.0 / 3.0) or approximately 0.55228 .

1 Like

To get the arcs pixel-perfect, you could draw it with a shader:

PShader shdr;

void setup() {
  size( 800, 800, P2D );
  shdr = new PShader( this, vertSrc, fragSrc );
}

void draw() {
  filter( shdr );
}

String[] vertSrc = {"""#version 330
uniform mat4 transform;
in vec4 position;
void main() {
  gl_Position = transform * position; 
}
"""};

String[] fragSrc = {"""#version 330
uniform vec2 resolution;
out vec4 fragColor;
#define TAU (2.*3.14159265358979)
void main() {
  vec2 p = gl_FragCoord.xy / resolution.y * 16. - 8.;
  float a = atan(p.x,p.y)/TAU + 0.5;
  a = (fract(6.*a+0.5)-0.5)/6. * TAU;
  p = vec2( cos(a), abs(sin(a)) ) * length(p);

  vec3 c = vec3( .75 );
  float y = 6.;
  float d = length( p - vec2( 3., -y ) ) - sqrt(9.+y*y);
  if( d < 0. ) c = vec3(1.);
  c = mix( vec3(0.), c, smoothstep( 0.08, 0.1, abs(d) ) ); ;
  fragColor = vec4( c, 1. );
}
"""};
2 Likes

Hello @scudly,

It’s clear you have deep expertise in this area.

It’s inspiring to see what can be done with shaders.

I presented this code to my group of young programmers and I got a lot of exaggerated jawdrops.
It might be a little overwhelming for those still building foundational skills in Processing and graphics programming.

This is currently beyond my scope of understanding. Could you recommend how someone new to shaders might start learning to understand code like this?

Any tips or good resources would be appreciated!

Thanks!

:)

Hello @behreajj,

Any thoughts on how to get a miter where strokes join with bezierVertex()?

Reference:
strokeJoin() / Reference / Processing.org

I started with example here but was not successful:
bezierVertex() / Reference / Processing.org

:)

As far as I know, scale within pushMatrix and popMatrix can inflate the stroke weight, exaggerating errors. I would try PVector methods to transform a shape instead. Single precision (32 bit) floating point error might be a contributing factor as well, but there’s little to be done about it with Processing’s methods.

void setup() {
  size(400, 400, JAVA2D);
  // size(400, 400, P2D);

  strokeJoin(MITER);
  // strokeJoin(BEVEL);
  // strokeJoin(ROUND);
}

void draw() {
  PVector coRight = new PVector(1.0, 0.0);
  PVector fhRight = new PVector(0.690599, 0.178633);
  PVector rhRight = new PVector(0.690599, -0.178633);

  PVector coLeft = new PVector(0.0, 0.0);
  PVector fhLeft = new PVector(0.309401, -0.178633);
  PVector rhLeft = new PVector(0.309401, 0.178633);

  // Rotate
  float theta = frameCount * TAU / 360.0;
  coRight.rotate(theta);
  fhRight.rotate(theta);
  rhRight.rotate(theta);

  coLeft.rotate(theta);
  fhLeft.rotate(theta);
  rhLeft.rotate(theta);

  // Scale
  float scale = min(width, height) * 0.5;
  coRight.mult(scale);
  fhRight.mult(scale);
  rhRight.mult(scale);

  coLeft.mult(scale);
  fhLeft.mult(scale);
  rhLeft.mult(scale);

  // Translate
  PVector origin = new PVector(width / 2.0, height / 2.0);
  coRight.add(origin);
  fhRight.add(origin);
  rhRight.add(origin);

  coLeft.add(origin);
  fhLeft.add(origin);
  rhLeft.add(origin);

  background(#fff7d5);
  stroke(#202020);
  strokeWeight(5.0);

  fill(#0080ff);

  beginShape();
  vertex(coRight.x, coRight.y);
  bezierVertex(
    fhRight.x, fhRight.y,
    rhLeft.x, rhLeft.y,
    coLeft.x, coLeft.y);
  bezierVertex(
    fhLeft.x, fhLeft.y,
    rhRight.x, rhRight.y,
    coRight.x, coRight.y);
  endShape(CLOSE);
}

I got flickering with the P2D renderer on version 4.4.1 .

Other than that, the default AWT renderer passes the strokeJoin constant on to BasicStroke. For an angle that sharp, BEVEL is what’s left when MITER raises problems.

If you had access to the AWT renderer directly, you might be able to fiddle with the miterLimit of the BasicStroke:

Never tried that myself.

1 Like

Shaders, fragment shaders in particular, tend to be very math based and often are largely based around manipulating the coordinate system. When people talk about making art with shaders, they usually mean fragment shaders such as the thousands of examples you can find on https://www.shadertoy.com/, though you can also make things with vertex shaders such as those on https://www.vertexshaderart.com/.

As to learning how to learn them, https://thebookofshaders.com/ is a great place to start. Inigo Quilez, who created shadertoy, is one of the gods of raymarching fragment shaders and he has a ton of resources at https://iquilezles.org/articles/.
Or you can search youtube for “shader programming” and find tons of stuff.

Introduction to shaders: Learn the basics!,
An introduction to Shader Art Coding,
and the many videos by The Art of Code are all good starting spots for fragment shader programming.

2 Likes

Hello @behreajj,

I did some more experimenting and created a PShape in advance.
Once I found a suitable angle I rotated that and got consistent petals (all the same)

This is your code with some modifications:


PShape sh;

float theta;

void setup() {
  //size(400, 400, JAVA2D);
  size(400, 400, P2D);

  // Code removed
  
  theta = TAU/3; // Gives a nice miter!
  //theta = TAU/4;  // Malformed miter
  //theta = TAU/2;  // Malformed miter
  //theta = 0;  // Malformed miter
  
  coRight.rotate(theta);
  fhRight.rotate(theta);
  rhRight.rotate(theta);

  coLeft.rotate(theta);
  fhLeft.rotate(theta);
  rhLeft.rotate(theta);
  
// Code removed
  
  sh = createShape();
  sh.beginShape();
  sh.strokeWeight(10);
  sh.strokeJoin(MITER);
  sh.vertex(coRight.x, coRight.y);
  sh.bezierVertex(
    fhRight.x, fhRight.y,
    rhLeft.x, rhLeft.y,
    coLeft.x, coLeft.y);
  sh.bezierVertex(
    fhLeft.x, fhLeft.y,
    rhRight.x, rhRight.y,
    coRight.x, coRight.y);
  sh.endShape(CLOSE);
}

// Code removed

Some angles gave better miters than others!
The big question is why… but I will not be exploring that anytime soon.
It is a work around… the OP can decide what his project requirements are.

I removed the bulk of the code so the OP can have some fun exploring this!

TAU/3:

TAU/2:

And 6 shapes rotated:

:)

This can also be calculated as: theta = THIRD_PI * 2; :wink:

1 Like

Great, this did the trick in a simple way. I didn’t think of using PVector.

float rotateAngle = 0;

void setup() {
  size(800, 800);
  
  strokeWeight(10);
  drawFlower(width / 2, height / 2, 300);
}

void drawFlower(float x, float y, float radius) {
  pushMatrix();
  translate(x, y);
  rotate(rotateAngle);

  for (int i = 0; i < 6; i++) {
    pushMatrix();
    rotate(TWO_PI / 6 * i);
    vesicaPiscis(0, 0, radius);
    popMatrix();
  }
  popMatrix();
}

void vesicaPiscis(float x, float y, float scale) {

  // Points for the Bezier curves
  PVector coRight = new PVector(1.0, 0.0);
  PVector fhRight = new PVector(0.690599, 0.178633);
  PVector rhRight = new PVector(0.690599, -0.178633);

  PVector coLeft = new PVector(0.0, 0.0);
  PVector fhLeft = new PVector(0.309401, -0.178633);
  PVector rhLeft = new PVector(0.309401, 0.178633);

  // Scale points based on the 'scale' factor
  coRight.mult(scale);
  fhRight.mult(scale);
  rhRight.mult(scale);

  coLeft.mult(scale);
  fhLeft.mult(scale);
  rhLeft.mult(scale);

  // Translate to the center of the shape
  PVector origin = new PVector(x, y);
  coRight.add(origin);
  fhRight.add(origin);
  rhRight.add(origin);

  coLeft.add(origin);
  fhLeft.add(origin);
  rhLeft.add(origin);

  beginShape();
  vertex(coRight.x, coRight.y);
  bezierVertex(
    fhRight.x, fhRight.y,
    rhLeft.x, rhLeft.y,
    coLeft.x, coLeft.y);
  bezierVertex(
    fhLeft.x, fhLeft.y,
    rhRight.x, rhRight.y,
    coRight.x, coRight.y);
  endShape(CLOSE);
}

Compare:

PVector fhLeft = new PVector(0.309401, -0.032);
PVector rhLeft = new PVector(0.309401, 0.032);

And:

PVector fhLeft = new PVector(0.309401, -0.031);
PVector rhLeft = new PVector(0.309401, 0.031);

That’s where the miter limit is.