Skip to content

Instantly share code, notes, and snippets.

@wschutzer
Created September 30, 2024 19:57
Show Gist options
  • Save wschutzer/8f94d4ebaec1912ead8fa21fb2723d3f to your computer and use it in GitHub Desktop.
Save wschutzer/8f94d4ebaec1912ead8fa21fb2723d3f to your computer and use it in GitHub Desktop.
Hilbert's dream 2
// 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