Copy link to clipboard
Copied
Hi Everyone,
I have attached two files. Is there a way to recreate these patterns in illustrator?
I tried poking around to find some answers, but I wonder if I'm having trouble finding the right direction because I don't know what to call it.
Any help with this would be greatly appreciated.
Thank you so much
Matt
1 Correct answer
Here's a script that draws a Sierpinski triangle that fits the open document. The number of recursions can be changed by changing the number of "levels" in line 2, although it is advised not to go too much above 10, otherwise Illustrator may become unresponsive. Adapted from: https://www.youtube.com/watch?v=e3Ll0_oxChU
function main() {
var level = 5;
var doc = app.activeDocument;
function drawTriangle(p1, p2, p3) {
var path1 = doc.pathItems.add();
path1.setEnti
...
Explore related tutorials & articles
Copy link to clipboard
Copied
they are called Fractals
the first is called Apollonian Gasket
https://www.wikihow.com/Create-an-Apollonian-Gasket
the second is called Sierpinski Triangle
https://www.youtube.com/watch?v=j6WYSdHuWgY
Copy link to clipboard
Copied
Carlos, thank you for the informative links.
Math is not my thing!!!
What I do know is that a hexagon divided into 6 equal segments= 6 equilateral triangles.
So to recreate this Sierpinski triangle, I created a seamless hexagon pattern.
Next Live Paint. I random recolor as I work.
Expand Live Paint. Pathfinder>Unite each unique color.
Recolor as you like.
Here is what it looks like with simple black and white.
K
Copy link to clipboard
Copied
I think your method is right one. Or we need to draw all possible lines via simple objects and use shape builder tool. The main concept of fractals is that one small element raised in size (for each iteration) and create a whole structure. But we can't do it in Illustrator as it repeats each unit and keep its original size when we trying to create a pattern.
Copy link to clipboard
Copied
Thank you so much for the clarity around WHAT they are. That is so helpful.
Creating the Sierpinski Triangle was pretty easy once the math was understood.
The Apollonian Gasket is proving to be a little more challenging with exact placement to reproduce the same results.
Copy link to clipboard
Copied
usually these images are generated programmatically to place items precisely
Copy link to clipboard
Copied
I use some Fractals Application for creating fractals that I use as patterns in my work. Also this G'MIC - GREYC's Magic for Image Computing: A Full-Featured Open-Source Framework for Image Processin... includs such a filter. Just my 2 cents.
Copy link to clipboard
Copied
Here's a script that draws a Sierpinski triangle that fits the open document. The number of recursions can be changed by changing the number of "levels" in line 2, although it is advised not to go too much above 10, otherwise Illustrator may become unresponsive. Adapted from: https://www.youtube.com/watch?v=e3Ll0_oxChU
function main() {
var level = 5;
var doc = app.activeDocument;
function drawTriangle(p1, p2, p3) {
var path1 = doc.pathItems.add();
path1.setEntirePath([p1, p2, p3, p1]);
path1.closed = true;
}
function getMidpoint(p1, p2) {
return [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2];
}
var Triangle = function(p1, p2, p3) {
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
this.draw = function() {
drawTriangle(this.p1, this.p2, this.p3);
}
}
function drawSierpinskiTriangle(tri, depth) {
if (depth == level) return;
var m1 = getMidpoint(tri.p1, tri.p2);
var m2 = getMidpoint(tri.p2, tri.p3);
var m3 = getMidpoint(tri.p3, tri.p1);
var t0 = new Triangle(m1, m2, m3);
t0.draw();
var t1 = new Triangle(tri.p1, m1, m3);
var t2 = new Triangle(tri.p2, m1, m2);
var t3 = new Triangle(tri.p3, m2, m3);
drawSierpinskiTriangle(t1, depth + 1);
drawSierpinskiTriangle(t2, depth + 1);
drawSierpinskiTriangle(t3, depth + 1);
}
var size = doc.width < doc.height ? doc.width : doc.height;
var t = new Triangle(
[size / 2, 0],
[0, -size],
[size, -size]
);
t.draw();
drawSierpinskiTriangle(t, 0);
}
main();
Copy link to clipboard
Copied
How cool is that. Being able to write code to achieve the look!
Copy link to clipboard
Copied
That's a very good approach, Femke.
I once created an action and a graphic style to create the triangle construction. Of course, it was insanely complicated.
Your script is way better and pretty fast.
Copy link to clipboard
Copied
Yes, I love it, just one remark, it creates open paths (easy to solve with a forth and back to Live Paint).
Copy link to clipboard
Copied
@Ton Frederiks I've added a line that makes the paths closed.
Copy link to clipboard
Copied
Thanks @femkeblanco I found that the new code indeed closed the paths, but created triangles with 4 points.
I am not a scripter but tried to change
path1.setEntirePath([p1, p2, p3, p1]);
into
path1.setEntirePath([p1, p2, p3]);
and to my surprise that worked.
Thanks again!
Copy link to clipboard
Copied
And here's a script that draws three types of Apollonian gaskets. This is adapted from a browser script that is dependent on the size of the canvas. I've kept it at the same size (400 pt) so as to show the same results; they can of course be manually scaled.
(NB. All credit to the original creator; I just rewrote it for Illustrator.)
// part 1: Complex class (manipulates complex numbers)
// abridged from Complex.js by Robert Eisele
// https://rawgit.com/infusion/Complex.js/master/complex.js
var parse = function (a, b) {
var z = {
're': 0,
'im': 0
};
if (a == undefined || a == null) {
z['re'] = z['im'] = 0;
} else if (b != undefined) {
z['re'] = a;
z['im'] = b;
} else
if (typeof a == 'object') {
z['re'] = a['re'];
z['im'] = a['im'];
} else if (typeof a == 'number') {
z['im'] = 0;
z['re'] = a;
}
return z;
};
var Complex = function (a, b) {
if (!(this instanceof Complex)) {
return new Complex(a, b);
}
var z = parse(a, b);
this['re'] = z['re'];
this['im'] = z['im'];
};
Complex.prototype = {
're': 0,
'im': 0,
'add': function(a, b) {
var z = new Complex(a, b);
return new Complex(
this['re'] + z['re'],
this['im'] + z['im']);
},
'sub': function(a, b) {
var z = new Complex(a, b);
return new Complex(
this['re'] - z['re'],
this['im'] - z['im']);
},
'mul': function(a, b) {
var z = new Complex(a, b);
if (z['im'] == 0 && this['im'] == 0) {
return new Complex(this['re'] * z['re'], 0);
}
return new Complex(
this['re'] * z['re'] - this['im'] * z['im'],
this['re'] * z['im'] + this['im'] * z['re']);
},
'div': function(a, b) {
var z = new Complex(a, b);
a = this['re'];
b = this['im'];
var c = z['re'];
var d = z['im'];
var t, x;
if (0 == d) {
return new Complex(a / c, b / c);
}
if (Math.abs(c) < Math.abs(d)) {
x = c / d;
t = c * x + d;
return new Complex(
(a * x + b) / t,
(b * x - a) / t);
} else {
x = d / c;
t = d * x + c;
return new Complex(
(a + b * x) / t,
(b - a * x) / t);
}
},
'sqrt': function() {
var a = this['re'];
var b = this['im'];
var r = this['abs']();
var re, im;
if (a >= 0) {
if (b == 0) {
return new Complex(Math.sqrt(a), 0);
}
re = 0.5 * Math.sqrt(2.0 * (r + a));
} else {
re = Math.abs(b) / Math.sqrt(2 * (r - a));
}
if (a <= 0) {
im = 0.5 * Math.sqrt(2.0 * (r - a));
} else {
im = Math.abs(b) / Math.sqrt(2 * (r + a));
}
return new Complex(re, b < 0 ? -im : im);
},
'abs': function() {
return Math.sqrt(this['re'] * this['re'] + this['im'] * this['im']);
}
};
// part 2: main
// adapted from Apollonian gasket by pimskie
// https://codepen.io/pimskie
var doc = app.activeDocument;
var DIM = 400;
var MID = DIM * 0.5;
var MIN_R = 2;
var Circle = function(r, center) {
this.r = r;
this.b = 1 / this.r;
this.center = center;
this.bc = this.center.mul(this.b);
};
var solveEquation = function(k1, k2, k3) {
var s = k1.add(k2).add(k3);
var k12 = k1.mul(k2);
var k13 = k1.mul(k3);
var k23 = k2.mul(k3);
var ksum = k12.add(k13).add(k23);
return ksum.sqrt().mul(2).add(s);
};
var getAdjacent = function(c1, c2, c3) {
var b1 = new Complex(c1.b);
var b2 = new Complex(c2.b);
var b3 = new Complex(c3.b);
var b4 = solveEquation(b1, b2, b3);
var r4 = Math.abs(1 / b4.re);
var pos4 = solveEquation(c1.bc, c2.bc, c3.bc).div(b4);
return new Circle(r4, pos4);
};
var flip = function(c4, c1, c2, c3) {
var bend = 2 * (c1.b + c2.b + c3.b) - c4.b;
var center = c1.bc.add(c2.bc).add(c3.bc).mul(2).sub(c4.bc).div(bend);
return new Circle(1 / bend, center);
};
var addCircle = function (circle) {
circles.push(circle);
};
var recurse = function(c1, c2, c3, c4, depth) {
depth = depth || 0;
var cn2 = flip(c2, c1, c3, c4);
var cn3 = flip(c3, c1, c2, c4);
var cn4 = flip(c4, c1, c2, c3);
if (cn2.r > MIN_R) {
addCircle(cn2);
recurse(cn2, c1, c3, c4, depth + 1);
}
if (cn3.r > MIN_R) {
addCircle(cn3);
recurse(cn3, c1, c2, c4, depth + 1);
}
if (cn4.r > MIN_R) {
addCircle(cn4);
recurse(cn4, c1, c2, c3, depth + 1);
}
};
var drawGasket = function(c1, c2, c3) {
var c4 = getAdjacent(c1, c2, c3);
var c5 = flip(c1, c2, c3, c4)
addCircle(c1);
addCircle(c2);
addCircle(c3);
addCircle(c4);
addCircle(c5);
recurse(c1, c2, c3, c4);
recurse(c5, c2, c3, c4);
};
var symmetricSet = function() {
var c1r = -MID;
var c1center = new Complex(MID, MID);
var c1 = new Circle(c1r, c1center);
var c2r = 100;
var c2center = new Complex(c2r, MID);
var c2 = new Circle(c2r, c2center);
var c3r = Math.abs(c1.r) - c2.r;
var c3x = c2.center.re + c2.r + c3r;
var c3y = c2.center.im;
var c3center = new Complex(c3x, c3y);
var c3 = new Circle(c3r, c3center);
return [
[c1, c2, c3]
];
};
var aSymmetricSet = function() {
var c1r = -MID;
var c1center = new Complex(MID, MID);
var c1 = new Circle(c1r, c1center);
var c2r = 160;
var c2center = new Complex(c2r, MID);
var c2 = new Circle(c2r, c2center);
var c3r = Math.abs(c1.r) - c2.r;
var c3x = c2.center.re + c2.r + c3r;
var c3y = c2.center.im;
var c3center = new Complex(c3x, c3y);
var c3 = new Circle(c3r, c3center);
return [
[c1, c2, c3]
];
};
var nestedSet = function() {
var c1r = -MID;
var c1center = new Complex(MID, MID);
var c1 = new Circle(c1r, c1center);
var c2r = 160;
var c2center = new Complex(MID, c2r);
var c2 = new Circle(c2r, c2center);
var c3r = MID - 160;
var c3center = new Complex(MID, DIM - c3r);
var c3 = new Circle(c3r, c3center);
var ci1r = -c2r;
var ci1center = new Complex(MID, Math.abs(ci1r));
var ci1 = new Circle(ci1r, ci1center);
var ci2r = Math.abs(ci1r) / 2;
var ci2center = new Complex(MID, ci2r);
var ci2 = new Circle(ci2r, ci2center);
var ci3r = Math.abs(ci1r) - ci2.r;
var ci3x = ci2.center.re;
var ci3y = ci2r + ci2r + ci3r;
var ci3center = new Complex(ci3x, ci3y);
var ci3 = new Circle(ci3r, ci3center);
return [
[c1, c2, c3],
[ci1, ci2, ci3]
];
};
var sets = {
'Symmetric': symmetricSet(),
'Asymmetric': aSymmetricSet(),
'Nested': nestedSet()
};
var drawCircle = function (c) {
var absR = Math.abs(c.r);
var d = absR * 2;
var dx = (doc.width - DIM) / 2 - d / 2;
var dy = (doc.height - DIM) / 2 - d / 2;
return doc.pathItems.ellipse(
-c.center.im - dy, c.center.re + dx,
d, d);
};
var circles;
var group;
var draw = function(selectedSet) {
circles = [];
group = doc.groupItems.add();
group.name = selectedSet;
var set = sets[selectedSet];
for (var i = 0; i < set.length; i++) {
var gasket = set[i];
drawGasket(gasket[0], gasket[1], gasket[2]);
}
for (var i = 0; i < circles.length; i++) {
drawCircle(circles[i]).moveToBeginning(group);
}
};
draw('Symmetric');
app.redraw();
// part 3: ScriptUI
var w = new Window("dialog");
var g = w.add("group");
var b1 = g.add("button", undefined, "Symmetric");
b1.preferredSize.width = 100;
var b2 = g.add("button", undefined, "Asymmetric");
b2.preferredSize.width = 100;
var b3 = g.add("button", undefined, "Nested");
b3.preferredSize.width = 100;
var b4 = w.add("button", undefined, "OK");
b4.preferredSize.width = 100;
var f1 = function (p1) {
doc.groupItems.removeAll();
draw(p1);
app.redraw();
};
b1.onClick = function () {
f1(b1.text);
};
b2.onClick = function () {
f1(b2.text);
};
b3.onClick = function () {
f1(b3.text);
};
w.show();
Copy link to clipboard
Copied
Perfectly wonderful, Femke.
Thanks for sharing.
Copy link to clipboard
Copied
@femkeblanco holy cow! That is impressive! Still always amazed at the genius of others.
Thank you so much for passing that along!
Copy link to clipboard
Copied
Thanks Femke, I am impressed again.

