CLICK ON CENTER for random, or press keys for fine adjustment (see the comment in code)
xxxxxxxxxx
// // // // // // // // // // // // // // // // // // // // // //
//
// hi!!
// welcome to
//
// ,----..
// / / \ ,---,. .--.--. ,---,
// / . : ,' .' | / / '. ' .' \
// . / ;. \ ,---.' | | : /`. / / ; '.
// . ; / ` ; | | .' ; | |--` : : \
// ; | ; \ ; | : : |-, | : ;_ : | /\ \
// | : | ; | ' : | ;/| \ \ `. | : ' ;. :
// . | ' ' ' : | : .' `----. \ | | ;/ \ \
// ' ; \; / | | | |-, __ \ \ | ' : | \ \ ,'
// \ \ ', / ' : ;/| / /`--' / | | ' '--'
// ; : / | | \ '--'. / | : :
// \ \ .' | : .' `--'---' | | ,'
// `---` | | ,' `--''
// `----'
//
// by core~5~
// github.com/mj-una
//
//
// a completly original way (that i know of at least)
// to represent the collatz or 3n+1 conjecture in code.
// it's interactively! and run at very high speed!! and doesn't stop!!!
//
// here is a video explaining collatz conjecture (by verisatum)
// https://youtu.be/094y1Z2wpJg?si=P2ELHwF__k38FNlQ
//
// and here the best known way to adapt it to p5 (by daniel shiffman)
// https://youtu.be/EYLWxwo1Ed8?si=2Xc2qsXyziLyUNZO
//
// i created this code from scrath for a university practical project
// in "artes multimediales 1, catedra laccabanne, atam, una"
// https://github.com/mj-una/am1-tp1-collatz
//
// buenos aires
// 27-6-23
//
// an extra detail:
// also added a function that i wrote to make p5 responsive (on empty pages)
// it makes the canvas adapts correctly to any screen, regardless of the size
// that createCanvas recives. very easy tu use! ready to copy and paste!
//
// i have a tutorial in spanish explaining the function line by line. it also
// includes another version to fit a sketch within a responsive layout of
// an existing page. this is very useful for embedding the sketch into a page
// with other html elements without using iframes or too much javascript.
// https://github.com/mj-una/tutorial-p5-responsive/tree/main
//
// license:
// both the tutorial and this sketch are shared under cc0, meaning
// they are dedicated to the public domain. you can use them freely
// without needing to cite the authorship. i consider being
// reinterpreted or copied a great honor (i am a kopimist),
// so i would be glad to see it if someone uses my code
//
// for enthusiasts, there is a note at the end of the sketch
// thank you for reading :D
// bye!
//
// // // // // // // // // // // // // // // // // // // // // //
​
/*
CONTROLS:
- press "w" to restart with no changes
- click in the center to restart with random values
(it change length and angles)
- press "1" to increase length
- press "2" to decrease length
- press "3" to increase even angle
- press "4" to decrease even angle
- press "5" to increase odd angle
- press "6" to decrease odd angle
- press "7" to increase background opacity
- press "8" to decrease background opacity
- press "9" to return to transparent background
- press "a" to increase velocity
- press "s" to restore default velocity
- press "d" to decrease velocity
- press "p" to pause / resume
- press "o" to download image (with current number in file name)
- press "k" to restart, restoring default values (back to oesa)
recomendation: if you want to see how it works press "7" many times and
then "d" until make it slow. the drawing of the two cycles will be shown.
white travels from the center to the end, while black moves inversely.
you can change colors in code to make one cycle transparent and then
have a more typical way of collatz visualization
too much text!!!...
...where is the code???
*/
​
/****************************************************************
/** default values (oesa shape):
/**
/**/ let opacity = 0; // background opacity
/**/ let velocity = 12; // be carefull. if machine is slow try with less
/**
/**/ let evenAngle = 0.24; // added in each step for the next rotation...
/**/ let oddAngle = -0.47; // ...it is very caotic, but it is just visual
/**
/**/ let length = 2.0; // works like zoom in/out
/**
/**/ let currentNumber; // initialize in setup because needed in restart
/**
/***************************************************************/
​
let start, skip, cycle_A, cycle_B, returnTouch; // status variables
​
let hailstoneNumber_A, hailstoneNumber_B; // temporary result of the calculation
let accumulatedAngle_A, accumulatedAngle_B; // angle for rotation to draw line
let accumulatedPosition_x, accumulatedPosition_y; // translation in each step
​
​
//________________________________________________________________
​
function setup() {
let canvas = createCanvas(512, 512);
canvas.style("user-select", "none");
canvas.style("touch-action", "manipulation");
windowResized(); // make it responsive (*)
background(150);
​
currentNumber = 3; // first number. try biggers!
start = true;
skip = false;
returnTouch = false;
describe("A black and white spiral that grows according to the parity of the hailstone sequence of each number. It is a way to represent the Collatz Conjecture interactively and at high speed")
}
​
​
//________________________________________________________________
​
function draw() {
// background
resetMatrix();
fill(150, opacity);
noStroke();
rect(0, 0, width, height);
​
// velocity
for (let i = 0; i < 100 * velocity + 1; i++) {
// evaluate status
if (Number.MAX_VALUE < hailstoneNumber_A * 3 + 1) skip = true;
else skip = false;
if (hailstoneNumber_A > 1) cycle_A = true;
else cycle_A = false;
if (hailstoneNumber_B > 1) cycle_B = true;
else cycle_B = false;
// daniel shiffman's way is too heavy because it store the complete sequence
// in memory to be able to reverse the order of the drawing (from 1-origin
// to number-spiral, that is, the opposite of how it is calculated)
​
// what i did was go through the outward sequence and then return with
// another identical sequence but drawn in the reverse direction.
// each number is processed twice but speed is gained because the
// calculation is super simple. only the trace of what was drawn on
// the canvas remains (like scars). hailstone numbers are not stored,
// that's why it is lightweight and fully interactive at runtime
// starts in the center
if (start) {
hailstoneNumber_A = currentNumber;
hailstoneNumber_B = currentNumber;
accumulatedAngle_A = 0;
accumulatedAngle_B = 0;
accumulatedPosition_x = width * 0.5;
accumulatedPosition_y = height * 0.5;
start = false;
}
​
// CYCLE A -> white line from origin to the end of the spiral
else if (cycle_A && !skip) {
​
// even case
if (hailstoneNumber_A % 2 == 0) {
hailstoneNumber_A = hailstoneNumber_A * 0.5;
accumulatedAngle_A += evenAngle;
}
// odd case
else {
hailstoneNumber_A = 3 * hailstoneNumber_A + 1;
accumulatedAngle_A += oddAngle;
}
// render
resetMatrix();
translate(accumulatedPosition_x, accumulatedPosition_y);
stroke(255, 40 + (12 - velocity) * 5); // <- white color
line(0, 0, cos(accumulatedAngle_A) * length, -sin(accumulatedAngle_A) * length);
// next step
accumulatedPosition_x += cos(accumulatedAngle_A) * length;
accumulatedPosition_y -= sin(accumulatedAngle_A) * length;
}
​
// CYCLE B -> black line from the end of the spiral to origin
else if (cycle_B && !skip) {
​
// even case
if (hailstoneNumber_B % 2 == 0) {
hailstoneNumber_B = hailstoneNumber_B * 0.5;
accumulatedAngle_B -= evenAngle;
}
// odd case
else {
hailstoneNumber_B = 3 * hailstoneNumber_B + 1;
accumulatedAngle_B -= oddAngle;
}
// render
resetMatrix();
translate(accumulatedPosition_x, accumulatedPosition_y);
stroke(0, 50 + (12 - velocity) * 5); // <- black color
line(0, 0, -cos(accumulatedAngle_B) * length, -sin(accumulatedAngle_B) * length);
// next step
accumulatedPosition_x -= cos(accumulatedAngle_B) * length;
accumulatedPosition_y -= sin(accumulatedAngle_B) * length;
}
​
// when hailstones are both 1, get the next number and start collatzing again
else {
currentNumber++;
skip = false;
start = true;
}
}
​
// mouse
if (dist(mouseX, mouseY, width * 0.5, height * 0.5) < 45) cursor(HAND);
else cursor(CROSS);
}
​
​
//________________________________________________________________
​
function touchEnded() {
​
// prevent double action
if (returnTouch) return;
returnTouch = true;
setTimeout(function(){
returnTouch = false;
}, 100);
​
// click in the center
push();
resetMatrix();
if (dist(mouseX, mouseY, width * 0.5, height * 0.5) < 45) {
evenAngle = random(2 * PI);
oddAngle = random(2 * PI);
length = random(1.5, 50);
setup();
}
pop();
}
​
​
//________________________________________________________________
​
function keyPressed() {
​
// controls
if (key == "1" || keyCode == 49) length += 0.1;
if (key == "2" || keyCode == 50) length -= 0.1;
​
if (key == "3" || keyCode == 51) evenAngle += 0.02;
if (key == "4" || keyCode == 52) evenAngle -= 0.02;
​
if (key == "5" || keyCode == 53) oddAngle += 0.02;
if (key == "6" || keyCode == 54) oddAngle -= 0.02;
​
if (key == "7" || keyCode == 55) {
if ((opacity + 2) >= 255) opacity = 255;
else opacity += 2;
}
if (key == "8" || keyCode == 56) {
if ((opacity - 4) <= 0) opacity = 0;
else opacity -= 4;
}
if (key == "9" || keyCode == 57) opacity = 0;
​
if (key == "s" || key == "S") velocity = 15;
if (key == "a" || key == "A") velocity += velocity * 0.5;
if (key == "d" || key == "D") {
if (velocity <= 0) velocity = 0;
else velocity -= velocity * 0.5;
}
​
if (key == "w" || key == "W") setup();
if (key == "k" || key == "K") {
// back to oesa
opacity = 0;
velocity = 12;
evenAngle = 0.24;
oddAngle = -0.47;
length = 2.0;
setup();
}
​
if (key == "o" || key == "O") save("collatz_in_" + currentNumber + ".png");
if (key == "p" || key == "P") {
if (isLooping()) noLoop();
else loop();
}
}
​
​
//________________________________________________________________
​
// responsive sketch (very easy)
// you can copy and paste this function at the end of your own sketch and it will
// become adaptable to any screen. works when you want to show it on an empty page
// (*) remember to call windowResized once after createCanvas
​
function windowResized() {
let page = document.getElementsByTagName("body")[0];
let canvas = document.getElementById("defaultCanvas0");
// margin <== EDITABLE
let margin = 2; // percentage (between 0% and 50%)
// body color <== EDITABLE but it is better to do it from the css
// page.style.backgroundColor = "rgb(200,200,200)";
page.style.overflow = "hidden";
page.style.display = "flex";
page.style.justifyContent = "center";
page.style.alignItems = "center";
page.style.height = "100svh";
if (windowWidth * height > windowHeight * width) {
canvas.style.height = (100 - margin * 2) + "svh";
canvas.style.width = ((100 - margin * 2) / height) * width + "svh";
}
else {
canvas.style.width = (100 - margin * 2) + "vw";
canvas.style.height = ((100 - margin * 2) / width) * height + "vw";
}
}
​
​
//________________________________________________________________
​
/*
for my nerdy people,
​
if you find the way to make the spiral shaped like a tree, please tell me!
i know it is because of the order in which the black cycle moves around and
because of the angles. i'll keep trying new approaches in a forked sketch.
you can reach me on instagram @sepintangatos
​
disclaimer: i speak spanish and only a little bit of english.
all this text was translated with google, not sure if it's 100% correct
​
25O25
*/