Created
September 30, 2024 19:57
-
-
Save wschutzer/8f94d4ebaec1912ead8fa21fb2723d3f to your computer and use it in GitHub Desktop.
Hilbert's dream 2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Hilbert's dream | |
// --------------- | |
// | |
// Processing code by Waldeck Schützer (@infinitymathart) | |
// Motion blur template by @davidbeesandbombs, explanation/article: https://bleuje.com/tutorial6/ | |
// Idea and concept by @etinjcb. | |
// | |
PVector[][] paths; // Array to store paths for each level | |
PVector[] currentPath; | |
int currentLevel = 1; | |
int nextLevel = 2; | |
int maxLevel = 9; | |
float noiseScale = 0.1; // Scale for Perlin noise | |
float noiseStrength = 30; // Strength of the noise perturbation | |
PFont courier; | |
int duration = 15; | |
int fps = 60; | |
int numFrames = duration*fps; | |
boolean recording = true; | |
float fac = 1.0; | |
// ---- Begin Template --- | |
int samplesPerFrame = 5; | |
float shutterAngle = 1.5; | |
float bht = 1.0; // Brightness adjustment | |
float[][] result; | |
float t, c; | |
float ease(float p) { | |
return 3*p*p - 2*p*p*p; | |
} | |
float ease(float p, float g) { | |
if (p < 0.5) | |
return 0.5 * pow(2*p, g); | |
else | |
return 1 - 0.5 * pow(2*(1 - p), g); | |
} | |
float ease_wide(float p, float g) // p in range -1 to 1 instead of 0-1 | |
{ | |
return 2*ease((p+1)/2,g)-1; | |
} | |
float mn = .5*sqrt(3), ia = atan(sqrt(.5)); | |
void push() { | |
pushMatrix(); | |
pushStyle(); | |
} | |
void pop() { | |
popStyle(); | |
popMatrix(); | |
} | |
void draw() { | |
if (!recording) { | |
t = mouseX*1.0/width; | |
c = mouseY*1.0/height; | |
// if (mousePressed) | |
// println(c); | |
draw_(); | |
} else { | |
for (int i=0; i<width*height; i++) | |
for (int a=0; a<3; a++) | |
result[i][a] = 0; | |
c = 0; | |
for (int sa=0; sa<samplesPerFrame; sa++) { | |
t = map(frameCount-1 + sa*shutterAngle/samplesPerFrame, 0, numFrames, 0, 1); | |
push(); | |
draw_(); | |
loadPixels(); | |
for (int i=0; i<pixels.length; i++) { | |
/*result[i][0] += pixels[i] >> 16 & 0xff; | |
result[i][1] += pixels[i] >> 8 & 0xff; | |
result[i][2] += pixels[i] & 0xff;*/ | |
result[i][0] += sq(red(pixels[i])/255.0); | |
result[i][1] += sq(green(pixels[i])/255.0); | |
result[i][2] += sq(blue(pixels[i])/255.0); | |
} | |
pop(); | |
} | |
loadPixels(); | |
for (int i=0; i<pixels.length; i++) | |
{ | |
/* int r = int(constrain(1.0*result[i][0]/samplesPerFrame*bht,0,255)); | |
int g = int(constrain(1.0*result[i][1]/samplesPerFrame*bht,0,255)); | |
int b = int(constrain(1.0*result[i][2]/samplesPerFrame*bht,0,255)); | |
pixels[i] = 0xff << 24 | | |
r << 16 | | |
g << 8 | | |
b;*/ | |
int r = int(constrain(255.0*sqrt(result[i][0]/samplesPerFrame)*bht,0,255)); | |
int g = int(constrain(255.0*sqrt(result[i][1]/samplesPerFrame)*bht,0,255)); | |
int b = int(constrain(255.0*sqrt(result[i][2]/samplesPerFrame)*bht,0,255)); | |
pixels[i] = color(r, g, b); | |
} | |
updatePixels(); | |
saveFrame("/tmp/h/frame_####.png"); | |
println(frameCount,"/",numFrames); | |
if (frameCount==numFrames) | |
exit(); | |
} | |
} | |
// End of Template | |
float easeOutElastic(float x) | |
{ | |
float c4 = (2*PI)/3; | |
if(x<=0) return 0; | |
if(x>=1) return 1; | |
return pow(2, -10 * x) * sin((x * 10 - 0.75) * c4) + 1; | |
} | |
void settings() | |
{ | |
if (recording) | |
size(2160,2160); | |
else | |
size(800,800,P2D); | |
smooth(8); | |
fac = width/800.0; | |
} | |
void setup() | |
{ | |
result = new float[width*height][3]; | |
courier = createFont("Courier New",24*fac,true); | |
paths = new PVector[maxLevel+1][]; // Store paths for levels 1 to 8 | |
// Generate Hilbert curves for levels 1 to 8 | |
for (int i = 1; i <= maxLevel; i++) { | |
paths[i] = generateHilbertCurve(i); | |
} | |
// Initialize the current path with the correct number of points | |
currentPath = new PVector[paths[currentLevel].length]; | |
for (int i = 0; i < currentPath.length; i++) | |
currentPath[i] = new PVector(); | |
} | |
// go from v1 to v2 with rotation around their middle | |
PVector rotater(PVector v1,PVector v2,float p,boolean orientation) // p is the progress in this interpolation, in [0,1] | |
{ | |
PVector middle = v1.copy().add(v2).mult(0.5); | |
PVector middleToV1 = v1.copy().sub(middle); | |
float angle = atan2(middleToV1.y,middleToV1.x); | |
float o = (orientation?-1:1); | |
float r = middleToV1.mag(); | |
return new PVector(middle.x+r*cos(angle+o*PI*p),middle.y+r*sin(angle+o*PI*p)); | |
} | |
void draw_() | |
{ | |
background(0); | |
stroke(255); | |
strokeWeight( 2.2*fac ); | |
strokeCap(ROUND); | |
strokeJoin(ROUND); | |
noFill(); | |
pushMatrix(); | |
float ti = t % 1;; | |
float tt = (maxLevel*ti) % 1; | |
if (t<0.5) | |
{ | |
background(0); | |
stroke(255); | |
currentLevel = 1+int(maxLevel*ti*2); | |
tt = (maxLevel*ti*2) % 1; | |
nextLevel = currentLevel+1; | |
if (nextLevel > maxLevel) | |
{ | |
background(255); | |
stroke(0); | |
nextLevel = 1; | |
} | |
} | |
else | |
{ | |
background(255); | |
stroke(0); | |
currentLevel = 1+int(maxLevel*(ti-0.5)*2); | |
tt = (maxLevel*(ti-0.5)*2) % 1; | |
nextLevel = currentLevel+1; | |
if (nextLevel > maxLevel) | |
{ | |
background(0); | |
stroke(255); | |
nextLevel = 1; | |
} | |
} | |
// Ensure both paths have the same number of points | |
PVector[] interpolatedPath1 = paths[currentLevel]; | |
PVector[] interpolatedPath2 = paths[nextLevel]; | |
if (paths[currentLevel].length < paths[maxLevel].length) { | |
interpolatedPath1 = interpolatePath(paths[currentLevel], paths[maxLevel].length); | |
} | |
if (paths[nextLevel].length < paths[maxLevel].length) { | |
interpolatedPath2 = interpolatePath(paths[nextLevel], paths[maxLevel].length); | |
} | |
// Resize the currentPath array if needed | |
if (currentPath.length != interpolatedPath1.length) { | |
currentPath = new PVector[interpolatedPath1.length]; | |
for (int i = 0; i < currentPath.length; i++) { | |
currentPath[i] = new PVector(); | |
} | |
} | |
float ea = ease(tt, 2.2); | |
if (currentLevel == maxLevel) | |
ea = easeOutElastic(tt); | |
// Interpolate between the current and next level paths | |
for (int i = 0; i < currentPath.length; i++) { | |
currentPath[i] = rotater(interpolatedPath1[i],interpolatedPath2[i],ea,currentLevel%2==(t<0.5 ? 0 : 1)); | |
} | |
// Draw the current interpolated curve | |
beginShape(); | |
for (int i = 0; i < currentPath.length; i++) { | |
vertex(currentPath[i].x, currentPath[i].y); | |
} | |
endShape(); | |
popMatrix(); | |
// texts | |
pushStyle(); | |
blendMode(DIFFERENCE); | |
noStroke(); | |
fill(128); // if (t < 0.5) fill(255); else fill(0); | |
textAlign(CENTER, CENTER); | |
textFont(courier); | |
textSize(15*fac); | |
text("@infinitymathart • 2024",0.5*width,0.9*height); | |
popStyle(); | |
} | |
// Function to generate a Hilbert curve of a given level | |
PVector[] generateHilbertCurve(int level) { | |
int n = int(pow(2, level)); | |
int totalPoints = n * n; | |
PVector[] path = new PVector[totalPoints]; | |
for (int i = 0; i < totalPoints; i++) { | |
path[i] = hilbert(i, level); | |
path[i].mult(width / (float)n); // Scale the points to fit the screen | |
path[i].add(width / (float)(2 * n), height / (float)(2 * n)); // Center the path | |
} | |
return path; | |
} | |
// Function to calculate the i-th point in a Hilbert curve of a given level | |
PVector hilbert(int i, int level) { | |
int n = int(pow(2, level)); | |
int x = 0; | |
int y = 0; | |
for (int s = 1; s < n; s *= 2) { | |
int rx = 1 & (i / 2); | |
int ry = 1 & (i ^ rx); | |
if (ry == 0) { | |
if (rx == 1) { | |
x = s-1 - x; | |
y = s-1 - y; | |
} | |
// Swap x and y | |
int temp = x; | |
x = y; | |
y = temp; | |
} | |
x += s * rx; | |
y += s * ry; | |
i /= 4; | |
} | |
return new PVector(x, y); | |
} | |
// Function to interpolate a path to a desired number of points | |
PVector[] interpolatePath(PVector[] path, int newLength) { | |
PVector[] newPath = new PVector[newLength]; | |
for (int i = 0; i < newLength; i++) { | |
float t = map(i, 0, newLength - 1, 0, path.length - 1); | |
int index = int(t); | |
float remainder = t - index; | |
if (index < path.length - 1) { | |
newPath[i] = PVector.lerp(path[index], path[index + 1], remainder); | |
} else { | |
newPath[i] = path[index].copy(); | |
} | |
} | |
return newPath; | |
} | |
/* License: | |
* | |
* Copyright (c) 2024 Waldeck Schützer | |
* | |
* All rights reserved. | |
* | |
* This code after the template and the related animations are the property of the | |
* copyright holder. Any reproduction, distribution, or use of this material, | |
* in whole or in part, without the express written permission of the copyright | |
* holder is strictly prohibited. | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment