Introducing randomness and asymmetry to shape

Good day :slight_smile:

I am trying to code a pattern of vulva shapes. So far I have managed to code the shape and make a pattern, but what I want to do is make each vulva different from the other ones, i.e., introduce some randomness I guess. Would also like to make them asymmetrical, but not sure how (I am quite new to Processing). Does any one have any suggestions on how I may do this?

Here what I have right now:

float c=0;
float d=0;
float z;
float q;

void setup() {
size(800, 800);
}

void draw() {
background(0);
//translate(width/2, height/2);
noFill();
stroke(255);
for (c=0; c < width - 100; c = c + 120) {
for (d=0; d < height - 200; d = d + 290) {
vagina(c + 100, d + 200);
}
}
}

void vagina(float c, float d) {

beginShape();
for (float a = 0; a < TWO_PI; a = a + 0.0001) {
float r = 50;
float x = c + rpow(cos(a), 3);
float y= d + r
sin(a)*2;
vertex(x, y);
}
endShape();

beginShape();
for (float a = 0; a < TWO_PI; a = a + 0.0001) {
float r = 90;
float x = c + rpow(cos(a), 5)/2;
float y= d + r
sin(a) + sin(a)*100/2;
vertex(x, y);
}
endShape();

beginShape();
for (float a = 0; a < TWO_PI; a = a + 0.0001) {
float r = 85;
float x = c + rpow(cos(a), 5)/4;
float y= d+ r
sin(a) + sin(a)*100/2;
vertex(x, y);
}
endShape();

beginShape();
for (float a = 0; a < TWO_PI; a = a + 0.0001) {
float r = 85;
float x = c + rpow(cos(a), 5)/14;
float y= d + r
sin(a) + sin(a)*100/2;
vertex(x, y);
}
endShape();

noLoop();
}

1 Like

No worries, it looks like this:

1 Like

None taken, but would love to get some suggestions!

1 Like

Hello @krissnutt,

Something you might want to investigate is the curveVertex() function.
The way I would go about it would be to hand drawn a base shape with some curve vertices.
Then you can loop through all your vertices and add a bit of offset using a random value. You could probably also add weight to the random value as the top part and bottom part should stay quite close to the base shape while the sides can be altered more.

Hope it helps =)

3 Likes

Wonderful, thank you, I will check it out =)

2 Likes

Hi @krissnutt,

Because your code is not formatted for posting, it is impossible to copy, paste, and then run it successfully without modification. For example, each occurrence of rsin and rpow in the posted code needs to be replaced by r * sin and r * pow, respectively, to achieve a successful run.

Please see the following for information on formatting code:

1 Like

All of that seems good. It might be best to only add the random values to the x coordinates, and not to the y. Perlin noise might be a good source of the randomness to make it smooth. Subtract 0.5 from the Perlin value to make it symmetrical around zero, and multiply the result by a varying factor to control the weight, subject to the suggestions in the above quote.

EDIT (February 10, 2022):

In the above, “symmetrical around zero” refers to mapping the range of the Perlin output to -0.5 to 0.5. Then multiply the result of that by a weight factor.

1 Like

Hi @javagar - thanks for letting me know and sorry about that. Pasting it in again.

float c=0;
float d=0;
float z;
float q;

void setup() {
  size(800, 800);
}

void draw() {
  background(0);
  noFill();
  stroke(255);
  for (c=0; c < width - 100; c = c + 120) {
    for (d=0; d < height - 200; d = d + 290) {
      vagina(c + 100, d + 200);
    }
  }
}

void vagina(float c, float d) {

  beginShape();
  for (float a = 0; a < TWO_PI; a = a + 0.0001) {
    float r = 50;
    float x = c + r*pow(cos(a), 3);
    float y= d + r*sin(a)*2;
    vertex(x, y);
  }
  endShape();

  beginShape();
  for (float a = 0; a < TWO_PI; a = a + 0.0001) {
    float r = 90;
    float x = c + r*pow(cos(a), 5)/2;
    float y= d + r*sin(a) + sin(a)*100/2;
    vertex(x, y);
  }
  endShape();

  beginShape();
  for (float a = 0; a < TWO_PI; a = a + 0.0001) {
    float r = 85;
    float x = c + r*pow(cos(a), 5)/4;
    float y= d+ r*sin(a) + sin(a)*100/2;
    vertex(x, y);
  }
  endShape();

  beginShape();
  for (float a = 0; a < TWO_PI; a = a + 0.0001) {
    float r = 85;
    float x = c + r*pow(cos(a), 5)/14;
    float y= d + r*sin(a) + sin(a)*100/2;
    vertex(x, y);
  }
  endShape();

  noLoop();
}
1 Like

@javagar thank you very much! I will see if I understand how to do this. Looks like I have a couple of things to learn on the way :wink:

2 Likes

Is it possible to know why so many posts have been flagged?

2 Likes

@krissnutt using the process I described above, this is the kind of results I managed to get:

The first shape is the base shape and the next 2 are 2 variations.

2 Likes

It does display asymmetry, with respect to both an imaginary vertical and horizontal axis.

here is my version with noise in one of the lines

// Vulva 1
float c=0;
float d=0;
float z;
float q;

void setup() {
  size(800, 980);
}

void draw() {
  background(0);
  //translate(width/2, height/2);
  noFill();
  stroke(255);
  for (c=0; c < width - 120; c = c + 120) {
    for (d=0; d < height - 200; d = d + 290) {
      vagina(c + 100, d + 200);
    }
  }
}

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

void vagina(float c, float d) {
  float add =0.001; // 0.0001

  noiseSeed(int(c));

  beginShape();
  for (float a = 0; a < TWO_PI; a = a + add) {

    float r = 50 ;
    float x = c + r*pow(cos(a), 3) ;
    float y = d + r*sin(a)*2 ;

    float perlinValue=noise(a*1);
    perlinValue-=0.5;
    x+= perlinValue*60; 
    vertex(x, y);
  }
  endShape();

  beginShape();
  for (float a = 0; a < TWO_PI; a = a + add) {
    float r = 90;
    float x = c + r*pow(cos(a), 5)/2;
    float y= d + r*sin(a) + sin(a)*100/2;
    vertex(x, y);
  }
  endShape();

  beginShape();
  for (float a = 0; a < TWO_PI; a = a + add) {
    float r = 85;
    float x = c + r*pow(cos(a), 5)/4;
    float y= d+ r*sin(a) + sin(a)*100/2;
    vertex(x, y);
  }
  endShape();

  beginShape();
  for (float a = 0; a < TWO_PI; a = a + add) {
    float r = 85;
    float x = c + r*pow(cos(a), 5)/14;
    float y= d + r*sin(a) + sin(a)*100/2;
    vertex(x, y);
  }
  endShape();

  noLoop();
}
//
2 Likes

Yes, it is remarkably similar to one of my attempts to introduce asymmetry. The same challenge occurred whereby there was a disconnect in the middle of the curve. A possible remedy might be to use the x, y coordinates of each vertex, prior to adding in the noise, to choose a noise value. The result would be that the middles of strands that are now disconnected would become more in sync with each other by virtue of their being randomized by a factor based on points that are close together in noise space.

2 Likes

Excited to how this looks when I’m back with my computer on Sunday :slight_smile:

2 Likes

Here is my second take using your approach rather than the one i described earlier.

Before going into the process of randomizing the shape, I started to clean a few things:

  1. your angle step (0.0001) is really small so first it takes way more time to produce the shape and second it can actually produce some artifacts since you are asking processing to draw lines between points that are on the same pixel. I lowered it to 0.01 and it should be more than enough.

  2. I changed the way you calculate the (x, y) coordinate of your shapes because it is quite confusing. It’s almost polar coordinates but it’s not. And because of that the meaning of the angle is not intuitive (I lost quite some times figuring this out actually).
    The way I did it was to first log all the (x, y) coordinates of one of the 4 shapes. Based on those values, i calculated the angle and the radius of the point in polar coordinates. The next part was plotting the (theta, r) coordinates and find a function that nicely approximate the shape. I did it in Excel and it looks like this for one of the shape:


    The blue is the original and the orange the fitting function.
    In the code, this is the purpose of the functions f and g

With this fresh start, the next thing to do was to produce a smooth noise with a period of 2 PI since the idea is to slightly change the r value for each angles. This is what the PRNG class is doing.

Finally, I don’t want to change the r value on the top part and the bottom part but only on the side. The way around this is to create a masking function that will be 0 for the angle corresponding to the bottom and top parts and 1 for the angle corresponding to the sides. The is the purpose of the scaleFactor function. The function itself look like this:
image

Here’s the result:

Code
PRNG prng = new PRNG(6, TWO_PI);

void setup() {
  size(800, 700, P2D);
  background(20);

  for (int c = 0; c < width - 100; c = c + 120) {
    for (int d = 0; d < 300; d = d + 290) {
      vagina(c + 100, d + 200);
    }
  }
}

float scaleFactor(float x, float a, float b) {
  x = ((x + HALF_PI) % PI) - HALF_PI;
  float c = 0;
  return 1 / (1 + pow(abs((x -c) / a), 2 * b));
}

float f(float x, float amp, float power, float offset) {
  return amp * (2 - pow(abs(cos(x)), power)) + offset;
}

float g(float x, float scale, float amp, float xOffset, float yOffset) {
  x = abs(((x + HALF_PI) % PI) - HALF_PI);
  return (amp / (log10(-scale * x +xOffset))) + yOffset;
}

float log10 (float x) {
  return (log(x) / log(10));
}

void vagina(float c, float d) {
  stroke(230);
  strokeWeight(1);
  noFill();

  prng.randomSeed(millis());
  beginShape();
  for (float a = 0; a < TWO_PI; a = a + 0.01) {    
    float r = f(a, 50, 0.4, 0);
    r += (prng.smoothNoise(a) - 0.5) * 8 * scaleFactor(a, HALF_PI / 2.0, 6);
    float x = c + r * cos(a);
    float y = d + r * sin(a);
    vertex(x, y);
  }
  endShape();

  prng.randomSeed(millis());
  beginShape();
  for (float a = 0; a < TWO_PI; a = a + 0.01) { 
    float r = f(a, 100, 0.28, -55);
    r += (prng.smoothNoise(a) - 0.5) * 10 * scaleFactor(a, HALF_PI / 2.0, 6);
    float x = c + r * cos(a);
    float y = d + r * sin(a);
    vertex(x, y);
  }
  endShape();

  prng.randomSeed(millis());
  beginShape();
  for (float a = 0; a < TWO_PI; a = a + 0.01) { 
    float r = f(a, 120, 0.25, -100);
    r += (prng.smoothNoise(a) - 0.5) * 4 * scaleFactor(a, HALF_PI / 2.0, 6);
    float x = c + r * cos(a);
    float y = d + r * sin(a);
    vertex(x, y);
  }
  endShape();

  prng.randomSeed(millis());
  beginShape();
  for (float a = 0; a < TWO_PI; a = a + 0.01) { 
    float r = g(a, 2.48, 6, 5, -3.5);
    r += (prng.smoothNoise(a) - 0.5) * 2 * scaleFactor(a, HALF_PI / 2.0, 6);
    float x = c + r * cos(a);
    float y = d + r * sin(a);
    vertex(x, y);
  }
  endShape();
}

class PRNG {
  private long seed;
  private long a;
  private long c;
  private long m32;
  private float[] samplePts;
  private int detail;
  private float period;

  PRNG(int detail, float period) {
    this(System.currentTimeMillis(), detail, period);
  }
  
  PRNG(long seed, int detail, float period) {
    this.a = 1664525;
    this.c = 1013904223;
    this.m32 = 0xFFFFFFFFL;
    this.detail = detail;
    this.period = period;
    this.randomSeed(seed);
  }

  void randomSeed(long newSeed) {
    this.seed = newSeed%Integer.MAX_VALUE;
    initSmoothNoise();
  }

  long nextLong() {
    this.seed = this.seed * a + c & m32;
    return this.seed;
  }

  int nextInt() {
    return (int)(this.nextLong()%Integer.MAX_VALUE);
  }

  float random() {
    return random(0, 1);
  }

  float random(float max) {
    return random(0, max);
  }

  float random(float min, float max) {
    return map(this.nextInt(), 0, Integer.MAX_VALUE, min, max);
  }

  private void initSmoothNoise() {
    samplePts = new float[detail + 1];

    for (int i = 0; i < samplePts.length; i++) {
      samplePts[i] = random();
    }
  }
  
  private float lerpValues(float a, float b, float t) {
    return a + fade(t) * (b - a);
  }
  
  private float fade(float t) {
    return t * t * t * ( t * ( t * 6 - 15 ) + 10 );
  }
  
  void changePeriod(float p) {
    period = p;
    initSmoothNoise();
  }
  
  void changeDetail(int d) {
    detail = d;
    initSmoothNoise();
  }
  
  float smoothNoise(float x) {
    float i = map((x % period), 0, period, 0, detail + 1);
    
    int prev = floor(i);
    int next = (int)((i + 1) % (detail + 1));
    
    float t = i - prev;
    
    return lerpValues(samplePts[prev], samplePts[next], t);
  }
}
5 Likes

Here is my try at introducing asymmetry, coded in p5.js mode:

The algorithm is based on the original one by @krissnutt, with a noise factor added to x for each vertex via this function:

function xDrift(x, y) {
  let mult = 100.0;
  let div = 100.0;
  return noise(x / div, y / div) * mult;
}

Continuity and smoothness within the shapes is facilitated within the function by basing the call to noise on the x and y values of the vertex.

Here’s the complete code:

let c=0;
let d=0;
let z;
let q;

function setup() {
  createCanvas(800, 1000);
}

function draw() {
  background(0);
  noFill();
  stroke(255);
  for (let c=0; c < width - 100; c = c + 120) {
    for (let d=0; d < height - 200; d = d + 290) {
      va(c + 100, d + 200);
    }
  }
}

function va(c, d) {
  beginShape();
  for (let a = 0; a < TWO_PI; a = a + 0.0001) {
    let r = 50;
    let x = c + r*pow(cos(a), 3);
    let y= d + r*sin(a)*2;
    vertex(x + xDrift(x, y), y); // drift the x value
  }
  endShape();

  beginShape();
  for (let a = 0; a < TWO_PI; a = a + 0.0001) {
    let r = 90;
    let x = c + r*pow(cos(a), 5)/2;
    let y= d + r*sin(a) + sin(a)*100/2;
    vertex(x + xDrift(x, y), y); // drift the x value
  }
  endShape();

  beginShape();
  for (let a = 0; a < TWO_PI; a = a + 0.0001) {
    let r = 85;
    let x = c + r*pow(cos(a), 5)/4;
    let y= d+ r*sin(a) + sin(a)*100/2;
    vertex(x + xDrift(x, y), y); // drift the x value
  }
  endShape();

  beginShape();
  for (let a = 0; a < TWO_PI; a = a + 0.0001) {
    let r = 85;
    let x = c + r*pow(cos(a), 5)/14;
    let y= d + r*sin(a) + sin(a)*100/2;
    vertex(x + xDrift(x, y), y); // drift the x value
  }
  endShape();

  noLoop();
}

function xDrift(x, y) {
  let mult = 100.0;
  let div = 100.0;
  return noise(x / div, y / div) * mult;
}

You can adjust the effect of the noise by changing the mult and div values within the function.

1 Like

Note, in the previous post, that the figures are not centered on the canvas. This is because Perlin noise is always positive. An improved xDrift function that centers the returned randomness factor around 0 would be:

function xDrift(x, y) {
  let mult = 100.0;
  let div = 100.0;
  let n = noise(x / div, y / div) * mult;
  n = n - n / 2;
  return n;
}
2 Likes

@jb4x thank you so much! This is super useful, and it is starting to look like something I was envisioning.

  1. Ok, makes sense. I used that angle step to avoid getting a “choppy” shape, but I do not think it helped really.
  2. To try to understand better - would you mind sharing what you did in excel?
1 Like

@javagar thank you very much! I have not used P5 a lot, but will check this out too

1 Like