# Implementation of rotation in chaos games

I am trying to implement rotation in chaos games. I have the following implementation for determining the next point:

function w(x, attractor) {
let compression_ratio = attractor.compression_ratio;

if ('rotation' in attractor) {
let unrotated_new_x =
compression_ratio * x[0] +
(1.0 - compression_ratio) * attractor['point'][0];
let unrotated_new_y =
compression_ratio * x[1] +
(1.0 - compression_ratio) * attractor['point'][1];
let dx = attractor['point'][0] - unrotated_new_x;
let dy = attractor['point'][1] - unrotated_new_y;
let d = Math.sqrt(dx**2 + dy**2);
let theta_1 = Math.atan2(dy, dx);
let theta_2 = attractor['rotation'] * Math.PI / 180.0;
let theta = theta_1 + theta_2;
let new_dx = d * Math.cos(theta);
let new_dy = d * Math.sin(theta);

return [attractor['point'][0] + new_dx, attractor['point'][1] + new_dy];
} else {
return [compression_ratio * x[0] +
(1.0 - compression_ratio) * attractor['point'][0],
compression_ratio * x[1] +
(1.0 - compression_ratio) * attractor['point'][1]];
}
}


If I use these attractors (without rotation):

function get_sierpinski_triangle_attractors() {
v_1 = [-1.0, 0.0];
v_2 = [1.0, 0.0];
v_3 = [0.0, Math.sqrt(3.0)];
vs = [v_1, v_2, v_3];

let attractors =
[{"point": vs[0], "compression_ratio": 0.5,
"probability": 1.0 / 3.0, "color": "red"},
{"point": vs[1], "compression_ratio": 0.5,
"probability": 1.0 / 3.0, "color": "yellow"},
{"point": vs[2], "compression_ratio": 0.5,
"probability": 1.0 / 3.0, "color": "blue"}];

return attractors;
}


it works and I get this:

However, if I set a rotation on the blue vertex:

  let attractors =
[{"point": vs[0], "compression_ratio": 0.5,
"probability": 1.0 / 3.0, "color": "red"},
{"point": vs[1], "compression_ratio": 0.5,
"probability": 1.0 / 3.0, "color": "yellow"},
{"point": vs[2], "compression_ratio": 0.5,
"probability": 1.0 / 3.0, "color": "blue", "rotation": 90}];


I get this:

but I'm expecting something similar to this (except flipped vertically):

Note that this is from "Chaos Rules!" by Robert L. Devaney of Boston University.

Moreover, if I set the rotation to 0:

  let attractors =
[{"point": vs[0], "compression_ratio": 0.5,
"probability": 1.0 / 3.0, "color": "red"},
{"point": vs[1], "compression_ratio": 0.5,
"probability": 1.0 / 3.0, "color": "yellow"},
{"point": vs[2], "compression_ratio": 0.5,
"probability": 1.0 / 3.0, "color": "blue", "rotation": 0}];


I expect to get the usual Sierpinski triangle. However, I get this:

What am I doing wrong here?

Note that my entire code base is available here.

• Math.atan2(dy, dx) is like atan(dy/dx) but respecting quadrants. But in general if you change one transformation in an iterated function system the whole attractor will change. Example images would help diagnose your problem, I'm just guessing... Apr 4 at 18:31
• Using that version of atan improved things in a certain sense. Thanks. I will add example images this evening after work. Apr 4 at 18:48
• @Claude I've added some images. Apr 5 at 0:23

    let unrotated_new_x =
compression_ratio * x[0] +
(1.0 - compression_ratio) * attractor['point'][0];
let unrotated_new_y =
compression_ratio * x[1] +
(1.0 - compression_ratio) * attractor['point'][1];
let dx = unrotated_new_x - attractor['point'][0];
let dy = unrotated_new_y - attractor['point'][1];
let d = Math.sqrt(dx**2 + dy**2);
let theta_1 = Math.atan2(dy, dx);
let theta_2 = attractor['rotation'] * Math.PI / 180.0;
let theta = theta_1 + theta_2;
let new_dx = d * Math.cos(theta);
let new_dy = d * Math.sin(theta);

return [attractor['point'][0] + new_dx, attractor['point'][1] + new_dy];

• so you used atan2 instead of atan, and reversed the order of the subtraction to get dx, dy - any other changes? I presume it's working now (another image would be nice to see!)? Apr 5 at 16:45
• Yes, that's right. Apr 7 at 18:14