Exponential grids / mobius transformations

Hi,

I want my sketch to draw this kind of things

But Im not sure where to start, what kind of maths are involved nor some seudocode examples. If you have some info, tips, suggestion, intuition, whatever it would be more than welcome

Thanks!

It’s tough to know the values that generated that exact image, but this is pretty close:

void setup(){
  size(600,600);
  strokeWeight(2);
}
void draw(){
  background(0);
  noFill();
  translate(20,300);
  
  stroke(255,0,0);
  line(0,-height,0,height);
  float rad = 100;
  for( int i = 0; i < 15; i++){
    ellipse(rad/2,0,rad,rad);
    rad*=1.3;
  }
  stroke(0,0,255);
  line(-20,0,width,0);
  rad = 200;
  for( int i = 0; i < 6; i++){
    ellipse(0,rad/2,rad,rad);
    ellipse(0,-rad/2,rad,rad);
    rad*=1.4;
  }
}

You can fiddle with the initial values for rad and the amount it increases by each time a loop happens.

Another example

text22

Well, that’s a little more involved. You’ll probably want to draw grid lines deformed via some equations. Start with a sketch that just draws normal grid lines:

int mapX( inX, inY){
  return inX;
}

int mapY( inX, inY){
  return inY;
}

for( each horizontal line ){
for( each pair of points along that line, call them (Ax,Ay) (Bx,By) ){
actually draw a line from (mapX(Ax,Ay), mapY(Ax,Ay)) to (mapX(Bx,By), mapB(Bx,By));
}
}
Do the same for vertical lines.

Once you have that working you can have mapX() and mapY() return values that result from mapping one input point to one output point.

Hi jc2046,

Maths are not my strong point, so take the following with caution; going by the formula at the top of the Wikipedia entry on Mobius transformation, the code might look like

Complex mobius(Complex a, Complex b, Complex c, Complex d, 
  Complex z) {
  return mobius(a, b, c, d, z, new Complex());
}

Complex mobius(Complex a, Complex b, Complex c, Complex d, 
  Complex z, Complex out) {
  return div(add(mult(a, z), b), add(mult(c, z), d), out);
}

There are all sorts of implementations for a complex number in Java; you can reference this Stack Overflow discussion for developed and tested implementations. For now, a basic class

class Complex {
  float real = 0.0;
  float imaginary = 0.0;

  Complex() {
  }

  Complex(float real, float imaginary) {
    set(real, imaginary);
  }

  String toString() {
    return String.format("(%.2f, %.2f)", real, imaginary);
  }

  Complex clone() {
    return new Complex(real, imaginary);
  }

  Complex set(float real, float imaginary) {
    this.real = real;
    this.imaginary = imaginary;
    return this;
  }
}

with some basic operations (+, -, *, /):

Complex add(Complex a, Complex b) {
  return add(a, b, new Complex());
}

Complex add(Complex a, Complex b, Complex out) {
  return out.set(a.real + b.real, 
    a.imaginary + b.imaginary);
}

Complex div(Complex a, Complex b) {
  return div(a, b, new Complex());
}

Complex div(Complex a, Complex b, Complex out) {
  return mult(a, reciprocal(b), out);
}

Complex mult(Complex a, Complex b) {
  return mult(a, b, new Complex());
}

Complex mult(Complex a, Complex b, Complex out) {
  return out.set(
    a.real * b.real - a.imaginary * b.imaginary, 
    a.real * b.imaginary + a.imaginary * b.real);
}

Complex reciprocal(Complex a) {
  return reciprocal(a, new Complex());
}

Complex reciprocal(Complex a, Complex out) {
  final float modulusSq = a.real * a.real + a.imaginary * a.imaginary;
  return out.set(a.real / modulusSq, -a.imaginary / modulusSq);
}

Complex sub(Complex a, Complex b) {
  return sub(a, b, new Complex());
}

Complex sub(Complex a, Complex b, Complex out) {
  return out.set(a.real - b.real, 
    a.imaginary - b.imaginary);
}

(static functions are not available to classes created within the Processing IDE, so I just placed the above under draw in the main sketch. You could also create instance methods for in-place operations a += b, a -= b, a *= b, a /= b .)

Messing around with the four inputs to the formula (a, b, c, d and e) gave me the following
complex

// Number of iterations of mobius transformation.
int transformations = 20;

// Smoothness of convex polygons approximating a circle.
int detail = 200;

// For display.
float x, y, scale, animIncr = 1 / 30.0;

// Four inputs to mobius function.
Complex a = new Complex(1.0, 0.0);
Complex b = new Complex(0.0, 0.0);
Complex c = new Complex(0.0, 0.0);
Complex d = new Complex(1.0, 0.0);

// Reference circle.
Complex[] circle = new Complex[detail];

// Transformations.
Complex[][] transformed = new Complex[transformations][detail];

void setup() {
  size(512, 256);
  smooth(8);
  noFill();
  strokeWeight(1.5);
  strokeCap(ROUND);
  strokeJoin(ROUND);

  // Translate and scale circles
  // to make them fit in the sketch's center.
  x = width * 0.5;
  y = height * 0.5;
  scale = min(width, height) * 0.25;

  // Calculate points on reference circle.
  // The y-axis represents imaginary numbers.
  float iToTheta = TWO_PI / float(detail);
  for (int i = 0; i < detail; ++i) {
    float theta = i * iToTheta;
    circle[i] = new Complex(cos(theta), sin(theta));
  }

  // Create transformations.
  float jToStep = 1.0 / float(transformations - 1);
  for (int j = 0; j < transformations; ++j) {
    for (int i = 0; i < detail; ++i) {
      float step = j * jToStep;
      c.real = lerp(-1.0, 1.0, step);
      d.imaginary = lerp(-1.0, 1.0, step);
      transformed[j][i] = mobius(a, b, c, d, circle[i]);
    }
  }
}

void draw() {
  surface.setTitle(String.format("%.2f", frameRate));
  background(0xff000000);

  // Manipulate inputs arbitrarily.
  a.real = map(mouseX, 0, width, -1.0, 1.0);
  b.real = map(mouseY, 0, height, -1.0, 1.0);

  float animStep = frameCount * animIncr;
  a.imaginary = cos(animStep);
  b.imaginary = sin(animStep);

  // Draw reference circle.
  stroke(0xff404040);
  beginShape(POLYGON);
  for (int i = 0; i < detail; ++i) {
    vertex(x + circle[i].real * scale, 
      y + circle[i].imaginary * scale);
  }
  endShape(CLOSE);

  // Draw transformations.
  float iToStep = 1.0 / float(detail - 1);
  float jToStep = 1.0 / float(transformations - 1);
  for (int j = 0; j < transformations; ++j) {

    // Manipulate inputs arbitrarily.
    float jStep = j * jToStep;
    c.imaginary = lerp(-1.0, 1.0, jStep);
    d.imaginary = lerp(-1.0, 1.0, jStep);

    stroke(lerpColor(0xffff0000, 0xff00ffff, jStep, HSB));
    beginShape(POLYGON);
    for (int i = 0; i < detail; ++i) {

      // Manipulate inputs arbitrarily.
      float iStep = i * iToStep;
      c.real = lerp(0.0, 2.0, iStep);
      d.real = lerp(-2.0, 0.0, iStep);

      // Mobius transformation, where z is reference circle.
      mobius(a, b, c, d, circle[i], transformed[j][i]);

      vertex(x + transformed[j][i].real * scale, 
        y + transformed[j][i].imaginary * scale);
    }
    
    // Depending on how inputs are manipulated,
    // the resultant shape may or may not be a
    // closed circle.
    // endShape(CLOSE);
    endShape();
  }
}

I’ve assumed that the y-axis represents the imaginary component of the complex number. I imagine you’ll have to move beyond the out-of-the-box primitives (ellipse, rect, etc.). You could use point, then draw points at a high enough density to create a continuous form; use beginShape, vertex and endShape as above; or dig into the pixels array. I find it helpful to keep function inputs and outputs in the range -1 to 1 (or the valid ranges specified by the given formula), then for display purposes translate, rotate and scale them in a separate part of the code.

Adam J. Murray has a gist exploring a Mobius tranformation in Processing as well (he uses a float array with two elements rather than define a Complex class).

Can’t speak to exponential grids or the second image posted, I’m afraid.

1 Like

Actually we can have static members inside nested classes as long as we declare those classes as static as well! :coffee:

For further info about that, go to this discussion thread: :raising_hand_man:

Wow. Thanks for the repplies, and for be so supportive, guys. Currently Im overflowing with a totally non related Processing project. In some time (maybe months) I will recover this thread and produce some output to show here.

That rotating rainbow sketch is delicious, by the way.

Thanks again and let the Mobius transfos be a party

By the way, The second example that looks so convoluted is really quite simple. You only have to plot cosines or simple functions. Been in this expo plane instead of tipical linear is what does the magic. Wolfram has a lot of this ideas to explore:

http://functions.wolfram.com/ElementaryFunctions/Cos/visualizations/7/ShowAll.html

I recently saw a related github repo of a processing sketch by @alexmiller for the Superformula:

People interested in similar geometric creations might want to check it out. It uses cp5 sliders.

1 Like