1
$\begingroup$

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:

no rotation

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:

actual

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

expected

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:

actual with 0 rotation

What am I doing wrong here?

Note that my entire code base is available here.

$\endgroup$
3
  • 1
    $\begingroup$ 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... $\endgroup$
    – Claude
    Apr 4 at 18:31
  • $\begingroup$ Using that version of atan improved things in a certain sense. Thanks. I will add example images this evening after work. $\endgroup$ Apr 4 at 18:48
  • $\begingroup$ @Claude I've added some images. $\endgroup$ Apr 5 at 0:23

1 Answer 1

1
$\begingroup$
    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];
$\endgroup$
2
  • $\begingroup$ 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!)? $\endgroup$
    – Claude
    Apr 5 at 16:45
  • $\begingroup$ Yes, that's right. $\endgroup$ Apr 7 at 18:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.