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
// 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.