Sillas Senna
Sillas Senna

Reputation: 115

How to divide a circle into equal sizes and rotate it?

I found this code on the internet (font: Visualize It) and it already works close to what I need. However, to ensure that it works 100%, I need it to be divided into 3 parts or more and that half of one of these dividers is a different color.

let wheel_angle, camera_angle;
let wheel_rpm, angular_speed;
let wheel_radius;
let frames_skip, cooldown, sampling_rate, frame_no;
let is_paused, direction;

function update() {
    wheel_angle += direction * angular_speed;
    if (wheel_angle > 360) {
        wheel_angle -= 360;
    }
    else if (wheel_angle < 0) {
        wheel_angle += 360;
    }

    if (frame_no == 0) {
        camera_angle = wheel_angle;
        frame_no = frames_skip;
    }
    else {
        frame_no -= 1;
    }
}

function render() {
    context.fillStyle = "#000000";
    context.fillRect(0, 0, canvas_width, canvas_height);

    context.strokeStyle = "#ffffff";
    context.beginPath();
    context.moveTo(canvas_width / 2, 0);
    context.lineTo(canvas_width / 2, canvas_height);
    context.stroke();

    context.strokeStyle = "#ff51ff";
    context.beginPath();
    context.moveTo(canvas_width / 4 + wheel_radius * Math.cos(toRadian(wheel_angle)), canvas_height / 2 - wheel_radius * Math.sin(toRadian(wheel_angle)) + y_offset);
    if (wheel_angle >= 180) {
        context.lineTo(canvas_width / 4 + wheel_radius * Math.cos(toRadian(wheel_angle - 180)), canvas_height / 2 - wheel_radius * Math.sin(toRadian(wheel_angle - 180)) + y_offset);
    }
    else {
        context.lineTo(canvas_width / 4 + wheel_radius * Math.cos(toRadian(wheel_angle + 180)), canvas_height / 2 - wheel_radius * Math.sin(toRadian(wheel_angle + 180)) + y_offset);
    }
    context.stroke();

    context.beginPath();
    context.arc(canvas_width / 4, canvas_height / 2 + y_offset, wheel_radius, 0, 2 * Math.PI);
    context.stroke();

    context.strokeStyle = "#00ff00";
    context.beginPath();
    context.moveTo(3 * canvas_width / 4 + wheel_radius * Math.cos(toRadian(camera_angle)), canvas_height / 2 - wheel_radius * Math.sin(toRadian(camera_angle)) + y_offset);
    if (wheel_angle >= 180) {
        context.lineTo(3 * canvas_width / 4 + wheel_radius * Math.cos(toRadian(camera_angle - 180)), canvas_height / 2 - wheel_radius * Math.sin(toRadian(camera_angle - 180)) + y_offset);
    }
    else {
        context.lineTo(3 * canvas_width / 4 + wheel_radius * Math.cos(toRadian(camera_angle + 180)), canvas_height / 2 - wheel_radius * Math.sin(toRadian(camera_angle + 180)) + y_offset);
    }
    context.stroke();

    context.beginPath();
    context.arc(3 * canvas_width / 4, canvas_height / 2 + y_offset, wheel_radius, 0, 2 * Math.PI);
    context.stroke();

    if (mobile) {
        context.font = "15px Arial";
    }
    else {
        context.font = "30px Arial";
    }
    context.textAlign = "center";
    context.fillStyle = "#ffffff";
    context.fillText("Base", canvas_width / 4, 30);
    context.fillText("Teste Aliasing", 3 * canvas_width / 4, 30);
}

function initParams() {
    wheel_angle = Math.random() * 360;
    wheel_rpm = 60;
    rpm_slider.value = wheel_rpm;
    calcSpeed();
    //rpm_display.innerHTML = `Wheel speed: ${wheel_rpm} RPM or ${(angular_speed * fps / 360).toFixed(2)} revolution(s) per second`;
    rpm_display.innerHTML = `Frequência do cículo base: ${(angular_speed * fps / 360).toFixed(2)} Hz (Hertz)`;

    frames_skip = 26;
    calcCooldown();
    fps_slider.value = 60 - frames_skip;
    //fps_display.innerHTML = `Sampling rate: ${sampling_rate.toFixed(2)} Hz; Sampling Time: ${cooldown.toFixed(2)} seconds`;
    fps_display.innerHTML = `Frequência de Amostragem: ${sampling_rate.toFixed(2)} Hz;`;

    wheel_radius = (canvas_width / 4) - 20;
    frame_no = 0;
    paused = false;
    direction = -1;
}

function updateParams(variable) {
    if (variable == 'rpm') {
        wheel_rpm = rpm_slider.value;
        calcSpeed();
        //rpm_display.innerHTML = `Wheel speed: ${wheel_rpm} RPM or ${(angular_speed * fps / 360).toFixed(2)} revolution(s) per second`;
        rpm_display.innerHTML = `Frequência do cículo base: ${(angular_speed * fps / 360).toFixed(2)} Hz (Hertz)`;
    }
    else if (variable == 'fps') {
        frames_skip = 60 - fps_slider.value;
        calcCooldown();
        //fps_display.innerHTML = `Sampling rate: ${sampling_rate.toFixed(2)} Hz; Sampling Time: ${cooldown.toFixed(2)} seconds`;
        fps_display.innerHTML = `Frequência de Amostragem: ${sampling_rate.toFixed(2)} Hz;`;
    }
    else if (variable == 'pause') {
        if (is_paused) {
            is_paused = false;
            pause_button.innerHTML = "Parar";
        }
        else {
            is_paused = true;
            pause_button.innerHTML = "Continuar";
        }
    }
    else if (variable == 'dir') {
        direction *= (-1);
    }
}

function simulate(number) {
    if (number == 1) {
        rpm_slider.value = 80;
        fps_slider.value = 59;
    }
    else if (number == 2) {
        rpm_slider.value = 120;
        fps_slider.value = 48;
    }
    else if (number == 3) {
        rpm_slider.value = 120;
        fps_slider.value = 46;
    }
    else if (number == 4) {
        rpm_slider.value = 100;
        fps_slider.value = 55;
    }
    else if (number == 5) {
        rpm_slider.value = 100;
        fps_slider.value = 41;
    }
    else if (number == 6) {
        rpm_slider.value = 60;
        fps_slider.value = 1;
    }
    updateParams("rpm");
    updateParams("fps");
    window.scrollTo(0, 40);
}

function step() {
    if (!is_paused) {
        update();
    }
    render();
    animate(step);
}

function calcSpeed() {
    angular_speed = (wheel_rpm * 360) / (fps * 60);
}

function calcCooldown() {
    cooldown = (frames_skip + 1) / fps;
    sampling_rate = 1 / cooldown;
}

function toRadian(degree) {
    return (Math.PI * degree / 180);
}

let screen_width = window.innerWidth, screen_height = window.innerHeight;
let y_offset = 30;
let fps = 60;

let canvas = document.getElementById("canvas");
let context = canvas.getContext("2d");

let pause_button = document.getElementById("pause-button");

let rpm_display = document.getElementById("rpm-display");
let rpm_slider = document.getElementById("rpm-slider");

let fps_slider = document.getElementById("fps-slider");
let fps_display = document.getElementById("fps-display");

if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
    mobile = true;
} else {
    mobile = false;
}

if (mobile) {
    canvas_width = 0.9 * screen_width;
}
else {
    canvas_width = 0.5 * screen_width;
}
canvas_height = canvas_width / 2 + y_offset;

canvas.width = canvas_width;
canvas.height = canvas_height;

let animate = window.requestAnimationFrame
    || window.webkitRequestAnimationFrame
    || window.mozRequestAnimationFrame
    || function (callback) {
        window.setTimeout(callback, 1000 / (fps));
    };

window.onload = function() {
    initParams();
    animate(step);
}
<!DOCTYPE html>
<html lang="en-US">

<head>
  <title>Stroboscopic Effect | Visualize It</title>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <!-- Materialize -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css" />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>

  <!-- Jquery -->
  <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>

  <!-- CSS -->
  <link rel="stylesheet" href="../style.css" />
  <script src="basic.js" defer></script>
  <script src="stroboscopic_effect.js" defer></script>
</head>

<script async src="https://www.googletagmanager.com/gtag/js?id=G-M95CKRP8HB"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag() { window.dataLayer.push(arguments); }
  gtag('js', new Date());

  gtag('config', 'G-M95CKRP8HB');
</script>

<body>

  <div class="text">
    <br>
    <div class="container" style="width:90%">
      <div class="row">
        <div class="col s12 l8">
          <canvas id="canvas"></canvas>
        </div>
        <div class="col s12 l4">
          <center>
            <p id="rpm-display"></p>
            <input type="range" min="0" max="200" value="30" id="rpm-slider" oninput="updateParams('rpm')"
              onchange="updateParams('rpm')" class="slider">
            <p id="fps-display"></p>
            <input type="range" min="0" max="59" value="30" id="fps-slider" oninput="updateParams('fps')"
              onchange="updateParams('fps')" class="slider">
          </center>
        </div>
        <div class="col s12 l4">
          <button type="button" class="btn btn-danger" onclick="simulate(3);">Estacionáro</button>
          <button type="button" class="btn btn-success" onclick="simulate(1);">Normal</button>
          <button type="button" class="btn btn-success" onclick="simulate(5);">Normal lento</button>
          <button type="button" class="btn btn-success" onclick="simulate(6);">Alternado 180</button>
          <button type="button" class="btn btn-success" onclick="simulate(2);">Contrário Lento</button>
        </div>
      </div>
    </div>
  </div>
</body>

</html>

The purpose of this code is to show the stroboscopic effect, however, with this code I cannot show the effect when the input frequency is the same sampling frequency, in this condition the sampling would vary by 180°.

Please help me, I spent a whole day trying to figure out how to solve it and I couldn't.

Upvotes: 0

Views: 453

Answers (1)

Sebastian
Sebastian

Reputation: 527

This is a communiti wiki answer, and edits by others (also by the OP) are encouraged to further improve it.

This answer so far has divided both the rotating circle into three parts. The corresponding change in the code is exclusively in the function render() at the place where it is marked.

The original code moved first to the boundary of the circle an then drew a line from there directly to the opposite side. If one divides the circle into three parts (angle 120) or even more, one has to always go back to the center. That's done in the new code.

let wheel_angle, camera_angle;
let wheel_rpm, angular_speed;
let wheel_radius;
let frames_skip, cooldown, sampling_rate, frame_no;
let is_paused, direction;
/* original code from  */
/* https://visualize-it.github.io/stroboscopic_effect/simulation.html */

function update() {
    wheel_angle += direction * angular_speed;
    if (wheel_angle > 360) {
        wheel_angle -= 360;
    }
    else if (wheel_angle < 0) {
        wheel_angle += 360;
    }

    if (frame_no == 0) {
        camera_angle = wheel_angle;
        frame_no = frames_skip;
    }
    else {
        frame_no -= 1;
    }
}

function render() {
    context.fillStyle = "#000000";
    context.fillRect(0, 0, canvas_width, canvas_height);

    context.strokeStyle = "#ffffff";
    context.beginPath();
    context.moveTo(canvas_width / 2, 0);
    context.lineTo(canvas_width / 2, canvas_height);
    context.stroke();

    context.strokeStyle = "#ff51ff";
    context.beginPath();

    /* Edit begins here */
    /* left circle */
    const x1 = canvas_width / 4, y1 = canvas_height / 2 + y_offset; //center of left circle
    context.moveTo(x1, y1); // move to the circle-center
    context.lineTo(x1 + wheel_radius * Math.cos(toRadian(wheel_angle)), y1 - wheel_radius * Math.sin(toRadian(wheel_angle))); // draw line from the center to point at circle at current wheel_angle
context.moveTo(x1, y1); // move back to the center
    context.lineTo(x1 + wheel_radius * Math.cos(toRadian(wheel_angle + 120)), y1 - wheel_radius * Math.sin(toRadian(wheel_angle + 120))); // draw line from the center to point at circle 120 degrees from current wheel_angle  
    context.moveTo(x1, y1); // move back to the center
    context.lineTo(x1 + wheel_radius * Math.cos(toRadian(wheel_angle + 240)), y1 - wheel_radius * Math.sin(toRadian(wheel_angle + 240))); // draw line from the center to point at circle 240 degrees from current wheel_angle 

    context.stroke();

    context.beginPath();
    context.arc(canvas_width / 4, canvas_height / 2 + y_offset, wheel_radius, 0, 2 * Math.PI);
    context.stroke();

    /* right circle */
    context.strokeStyle = "#00ff00";
    context.beginPath();
    const x2 = 3 * canvas_width / 4, y2 = y1; //center of right circle
    context.moveTo(x2, y2); // move to the circle-center
    context.lineTo(x2 + wheel_radius * Math.cos(toRadian(camera_angle)), y1 - wheel_radius * Math.sin(toRadian(camera_angle)));
    context.moveTo(x2, y2); // move back to the circle-center
    context.lineTo(x2 + wheel_radius * Math.cos(toRadian(camera_angle + 120)), y2 - wheel_radius * Math.sin(toRadian(camera_angle + 120)));
    context.moveTo(x2, y2); // move back to the circle-center
    context.lineTo(x2 + wheel_radius * Math.cos(toRadian(camera_angle + 240)), y2 - wheel_radius * Math.sin(toRadian(camera_angle + 240)));
    /* Edit ends here*/

    context.stroke();

    context.beginPath();
    context.arc(3 * canvas_width / 4, canvas_height / 2 + y_offset, wheel_radius, 0, 2 * Math.PI);
    context.stroke();

    if (mobile) {
        context.font = "15px Arial";
    }
    else {
        context.font = "30px Arial";
    }
    context.textAlign = "center";
    context.fillStyle = "#ffffff";
    context.fillText("Base", canvas_width / 4, 30);
    context.fillText("Teste Aliasing", 3 * canvas_width / 4, 30);
}

function initParams() {
    wheel_angle = Math.random() * 360;
    wheel_rpm = 60;
    rpm_slider.value = wheel_rpm;
    calcSpeed();
    //rpm_display.innerHTML = `Wheel speed: ${wheel_rpm} RPM or ${(angular_speed * fps / 360).toFixed(2)} revolution(s) per second`;
    rpm_display.innerHTML = `Frequência do cículo base: ${(angular_speed * fps / 360).toFixed(2)} Hz (Hertz)`;

    frames_skip = 26;
    calcCooldown();
    fps_slider.value = 60 - frames_skip;
    //fps_display.innerHTML = `Sampling rate: ${sampling_rate.toFixed(2)} Hz; Sampling Time: ${cooldown.toFixed(2)} seconds`;
    fps_display.innerHTML = `Frequência de Amostragem: ${sampling_rate.toFixed(2)} Hz;`;

    wheel_radius = (canvas_width / 4) - 20;
    frame_no = 0;
    paused = false;
    direction = -1;
}

function updateParams(variable) {
    if (variable == 'rpm') {
        wheel_rpm = rpm_slider.value;
        calcSpeed();
        //rpm_display.innerHTML = `Wheel speed: ${wheel_rpm} RPM or ${(angular_speed * fps / 360).toFixed(2)} revolution(s) per second`;
        rpm_display.innerHTML = `Frequência do cículo base: ${(angular_speed * fps / 360).toFixed(2)} Hz (Hertz)`;
    }
    else if (variable == 'fps') {
        frames_skip = 60 - fps_slider.value;
        calcCooldown();
        //fps_display.innerHTML = `Sampling rate: ${sampling_rate.toFixed(2)} Hz; Sampling Time: ${cooldown.toFixed(2)} seconds`;
        fps_display.innerHTML = `Frequência de Amostragem: ${sampling_rate.toFixed(2)} Hz;`;
    }
    else if (variable == 'pause') {
        if (is_paused) {
            is_paused = false;
            pause_button.innerHTML = "Parar";
        }
        else {
            is_paused = true;
            pause_button.innerHTML = "Continuar";
        }
    }
    else if (variable == 'dir') {
        direction *= (-1);
    }
}

function simulate(number) {
    if (number == 1) {
        rpm_slider.value = 80;
        fps_slider.value = 59;
    }
    else if (number == 2) {
        rpm_slider.value = 120;
        fps_slider.value = 48;
    }
    else if (number == 3) {
        rpm_slider.value = 120;
        fps_slider.value = 46;
    }
    else if (number == 4) {
        rpm_slider.value = 100;
        fps_slider.value = 55;
    }
    else if (number == 5) {
        rpm_slider.value = 100;
        fps_slider.value = 41;
    }
    else if (number == 6) {
        rpm_slider.value = 60;
        fps_slider.value = 1;
    }
    updateParams("rpm");
    updateParams("fps");
    window.scrollTo(0, 40);
}

function step() {
    if (!is_paused) {
        update();
    }
    render();
    animate(step);
}

function calcSpeed() {
    angular_speed = (wheel_rpm * 360) / (fps * 60);
}

function calcCooldown() {
    cooldown = (frames_skip + 1) / fps;
    sampling_rate = 1 / cooldown;
}

function toRadian(degree) {
    return (Math.PI * degree / 180);
}

let screen_width = window.innerWidth, screen_height = window.innerHeight;
let y_offset = 30;
let fps = 60;

let canvas = document.getElementById("canvas");
let context = canvas.getContext("2d");

let pause_button = document.getElementById("pause-button");

let rpm_display = document.getElementById("rpm-display");
let rpm_slider = document.getElementById("rpm-slider");

let fps_slider = document.getElementById("fps-slider");
let fps_display = document.getElementById("fps-display");

if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
    mobile = true;
} else {
    mobile = false;
}

if (mobile) {
    canvas_width = 0.9 * screen_width;
}
else {
    canvas_width = 0.5 * screen_width;
}
canvas_height = canvas_width / 2 + y_offset;

canvas.width = canvas_width;
canvas.height = canvas_height;

let animate = window.requestAnimationFrame
    || window.webkitRequestAnimationFrame
    || window.mozRequestAnimationFrame
    || function (callback) {
        window.setTimeout(callback, 1000 / (fps));
    };

window.onload = function() {
    initParams();
    animate(step);
}
<!DOCTYPE html>
<html lang="en-US">

<head>
  <title>Stroboscopic Effect | Visualize It</title>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <!-- Materialize -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css" />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>

  <!-- Jquery -->
  <script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>

  <!-- CSS -->
  <link rel="stylesheet" href="../style.css" />
  <script src="basic.js" defer></script>
  <script src="stroboscopic_effect.js" defer></script>
</head>

<script async src="https://www.googletagmanager.com/gtag/js?id=G-M95CKRP8HB"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag() { window.dataLayer.push(arguments); }
  gtag('js', new Date());

  gtag('config', 'G-M95CKRP8HB');
</script>

<body>

  <div class="text">
    <br>
    <div class="container" style="width:90%">
      <div class="row">
        <div class="col s12 l8">
          <canvas id="canvas"></canvas>
        </div>
        <div class="col s12 l4">
          <center>
            <p id="rpm-display"></p>
            <input type="range" min="0" max="200" value="30" id="rpm-slider" oninput="updateParams('rpm')"
              onchange="updateParams('rpm')" class="slider">
            <p id="fps-display"></p>
            <input type="range" min="0" max="59" value="30" id="fps-slider" oninput="updateParams('fps')"
              onchange="updateParams('fps')" class="slider">
          </center>
        </div>
        <div class="col s12 l4">
          <button type="button" class="btn btn-danger" onclick="simulate(3);">Estacionáro</button>
          <button type="button" class="btn btn-success" onclick="simulate(1);">Normal</button>
          <button type="button" class="btn btn-success" onclick="simulate(5);">Normal lento</button>
          <button type="button" class="btn btn-success" onclick="simulate(6);">Alternado 180</button>
          <button type="button" class="btn btn-success" onclick="simulate(2);">Contrário Lento</button>
        </div>
      </div>
    </div>
  </div>
</body>

</html>

Upvotes: 1

Related Questions