Last active
August 21, 2022 23:17
-
-
Save nsbalbi/ab299269bb0dbfd9c1296dc3bdaad39b to your computer and use it in GitHub Desktop.
Astral Knot
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
// Processing Code By Nicholas Sbalbi (@nsbalbi) | |
// Twitter post: https://twitter.com/nsbalbi/status/1413940646063317001 | |
// Associated tutorial: https://nsbalbi.github.io/Blog%20Posts/blog_knot.html | |
// Github gist: https://gist.github.com/nsbalbi/ab299269bb0dbfd9c1296dc3bdaad39b | |
// 7-12-2021 Updated: 7-12-2022 | |
// Motion blur via @beesandbombs | |
float t; | |
int[][] result; | |
int nFrames = 160; | |
// Motion blur parameters | |
int samplesPerFrame = 8; // number of drawings used to render each final frame with motion blur | |
float shutterAngle = 0.4; // kind of the time interval used for each frame in the motion blur | |
// Knot parameters | |
float scale = 20; | |
float r = 8; | |
// Camera parameters | |
float cameraX = 175; float cameraY = 0; float cameraZ = 0; | |
// Particle parameters | |
int numParticles = 1000; | |
Particle[] particles = new Particle[numParticles]; | |
void setup() { | |
size(600,600,P3D); | |
result = new int[width*height][3]; | |
// initialize particles | |
for (int i = 0; i < numParticles; i++) { | |
particles[i] = new Particle(); | |
} | |
} | |
void draw() { | |
// Outer draw function (for adding screen effects) | |
// @beesandbombs Motion Blur Template | |
////////////////////////////////////////////////////////////////////////////// | |
for (int i=0; i<width*height; i++) | |
for (int a=0; a<3; a++) | |
result[i][a] = 0; | |
for (int sa=0; sa<samplesPerFrame; sa++) { | |
t = map(frameCount-1 + sa*shutterAngle/samplesPerFrame, 0, nFrames, 0, 1); | |
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; | |
} | |
} | |
loadPixels(); | |
for (int i=0; i<pixels.length; i++) | |
pixels[i] = 0xff << 24 | | |
int(result[i][0]*1.0/samplesPerFrame) << 16 | | |
int(result[i][1]*1.0/samplesPerFrame) << 8 | | |
int(result[i][2]*1.0/samplesPerFrame); | |
updatePixels(); | |
// Template End | |
////////////////////////////////////////////////////////////////////////////// | |
saveFrame("Output/fr###.png"); | |
println(frameCount,"/",nFrames); | |
if (frameCount==nFrames) | |
exit(); | |
} | |
void draw_() { | |
// Main draw function | |
background(0); | |
camera(cameraX,cameraY,cameraZ,0,0,0,0,-1,0); | |
pushMatrix(); | |
rotateY(2*PI*t); // rotate Y over time | |
rotateX(2*PI*t); // " X | |
// drawPath(); // would draw only knot path | |
drawSurface(); // draws knot surface | |
// draw particles | |
for (int i = 0; i < numParticles; i++) { | |
particles[i].drawPoint(); | |
} | |
popMatrix(); | |
} | |
PVector knotPath(float s) { | |
// Returns knot path parameterized by s, s loops every 2PI | |
// Trefoil Knot | |
float x = sin(s) + 2*sin(2*s); | |
float y = cos(s) - 2*cos(2*s); | |
float z = -sin(3*s); | |
return new PVector(scale*x,scale*y,scale*z); | |
} | |
PVector dKnotPath(float s) { | |
// Returns tangent to knot path parameterized by s, s loops every 2PI | |
// Trefoil Knot | |
float x = cos(s) + 4*cos(2*s); | |
float y = -sin(s) + 4*sin(2*s); | |
float z = -3*cos(3*s); | |
PVector out = new PVector(x,y,z); | |
return out.normalize(); | |
} | |
PVector pipe(float s, float theta, float r) { | |
// Returns location on knot given parameter s along path and theta around pipe surface with radius r | |
// s and theta loop every 2PI | |
// randomly generate vector to cross the tangent with to generate a normal vector | |
// must avoid the crossing vector from being ~ parallel to the tangent vector | |
PVector vect = new PVector(1*s,2*s,30*s); // fudge with crossing vector to prevent zeroing out | |
PVector gamma = knotPath(s); // knot path | |
PVector tangent = dKnotPath(s); // knot tangent | |
PVector normal = tangent.cross(vect).normalize(); // normal vector to knot | |
PVector binormal = normal.cross(tangent).normalize(); // binormal vector to knot | |
// generate circle around the knot path at the given position s, using the normal and binormal vectors | |
return gamma.add(normal.mult(r*cos(theta))).add(binormal.mult(r*sin(theta))); | |
} | |
void drawSurface() { | |
// Draws parameterized surface, adapted from @etiennejcb | |
int n1 = 160; // s subdivisions | |
int n2 = 15; // theta subdivisions | |
stroke(150); // white lines | |
fill(0); // black fill | |
noStroke(); // comment out for wireframe | |
for (int i = 0; i < n1; i++) { // for each s subdivision | |
beginShape(TRIANGLE_STRIP); // start shape | |
for (int j = 0; j < n2+1; j++) { // for each theta subdivision | |
float s1 = map(i,0,n1,0.01,2*PI+0.01); | |
// have to avoid exact 0 due to vector operations | |
float s2 = map(i+1,0,n1,0.01,2*PI+0.01); | |
float theta = map(j,0,n2,0,2*PI); | |
PVector v1 = pipe(s1,theta,r); // grab coordinates | |
PVector v2 = pipe(s2,theta,r); | |
vertex(v1.x,v1.y,v1.z); | |
vertex(v2.x,v2.y,v2.z); | |
} | |
endShape(); // end shape | |
} | |
} | |
void drawPath() { | |
// Draws knot path | |
int n = 100; // number of segments | |
PVector[] vectors = new PVector[n+1]; // one extra point to complete the loop | |
float ds = 2*PI/n; // segment spacing | |
for (int i = 0; i <= n; i++) { | |
// generate points along path | |
vectors[i] = knotPath(ds*i); | |
} | |
strokeWeight(2); | |
stroke(255); | |
// draw path | |
for (int i = 0; i < n; i++) { | |
// for each point, draw a line between it and the next | |
line(vectors[i].x,vectors[i].y,vectors[i].z,vectors[i+1].x,vectors[i+1].y,vectors[i+1].z); | |
} | |
} | |
// Particle Class | |
class Particle { | |
float s = random(0,2*PI); // position along knot | |
float theta = random(0,2*PI); // angle along pipe/tube | |
float offset = random(0,1); // random initial offset | |
float weight = random(3,4); // base stroke weight | |
void drawPoint() { | |
// Draws a point at the particle's location (with some time-based movement) | |
PVector v = pipe(s + 2*PI*t, theta, r+0.1); // get point on knot, move along s with t | |
stroke(255); | |
strokeWeight(weight + 2*sin(2*PI*(t+offset))); // slight shimmer effect | |
point(v.x,v.y,v.z); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment