Reputation: 115
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
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