Skip to content

Instantly share code, notes, and snippets.

@wschutzer
Created September 30, 2024 16:03
Show Gist options
  • Save wschutzer/7f89d66eaa2fc94fae08a3a61e6d6741 to your computer and use it in GitHub Desktop.
Save wschutzer/7f89d66eaa2fc94fae08a3a61e6d6741 to your computer and use it in GitHub Desktop.
Trefoil knot with digits
// Digits on a trefoil knot
// ------------------------
//
// Processing code by Waldeck Schützer (@infinitymathart)
// Motion blur template by @beesandbombs, explanation/article: https://bleuje.com/tutorial6/
// Idea and concept by @etinjcb
//
import peasy.*;
import processing.core.PMatrix3D;
PeasyCam cam;
boolean recording = true;
int frame_size = recording ? 2160 : 800;
float fac = frame_size/800.0;
int numSegments = 600; // Number of segments along the Möbius ring (higher means smoother)
int num_faces = 60;
float surfaceRadius = frame_size/10; // 200*fac; // Radius of the Möbius ring
float tf_radius = 0.5; // Model radius
int num_rows = 360; // For the digits
int num_cols = 30;
float gscl = 50.0/fac; // Proportional font scaling (digits)
int FPS = 60;
int numFrames = 60*FPS;
int samplesPerFrame = 5;
float shutterAngle = 2.1;
float bht = 2.0; // Brightness adjustment
color c_digit = color(255);
color c_digit_flip = color(200,180);
PFont courier;
PFont fdigits;
int[][] result;
float t, c;
int rows = 10;
int cols = 10;
thing[] things;
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;
}
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;
}
updatePixels();
saveFrame("/tmp/frame_####.png");
println(frameCount,"/",numFrames);
if (frameCount==numFrames)
exit();
}
}
// End of Template
// Begin of Trefoil Knot with Digits code
void settings()
{
size(frame_size,frame_size,P3D);
pixelDensity(1);
smooth(8);
}
void setup()
{
randomSeed(1337);
result = new int[width*height][3];
//cam = new PeasyCam(this, 0*fac, 0*fac, 0*fac, 600*fac);
cam = new PeasyCam(this, 3.4882555 *fac, -9.970151 *fac, -0.10424426 *fac, 600.0 *fac);
cam.setRotations( 0.0449344 , -0.09821731 , 0.33823663 );
if (recording)
cam.setActive(false);
courier = createFont("Courier New",24*fac,true);
fdigits = createFont("HelveticaNeue-Ultralight",36*fac,true);
// Things are random digits to be drawn on each of the 4 faces of the surface
// (actually there are only two faces)
things = new thing[num_rows*num_cols];
for(int j=0; j<num_rows*num_cols;j++)
things[j] = new thing();
}
void draw_()
{
background(0);
noStroke();//stroke(128); // Draw lines around the rectangles
fill(0); //fill(150*lvl, 200*lvl, 255*lvl);
lights();
directionalLight(255, 255, 255, 0, 0, 1);
pushMatrix();
drawSurface();
popMatrix();
// texts
pushMatrix();
float[] rot = cam.getRotations();
rotateX(rot[0]);
rotateY(rot[1]);
rotateZ(rot[2]);
translate(0,0,(float)cam.getDistance()-260*fac);
fill(255);stroke(255);
textAlign(CENTER, CENTER);
textFont(courier);
textSize(5*fac);
text("@infinitymathart • 2024",0.0*width,0.13*height);
popMatrix();
}
// Rendering the Möbius ring using the segment class for each segment
//
void drawSurface()
{
// Draw the surface
//
rotateY(TAU*t);
rotateX(2*TAU*t);
for (int i = 0; i < numSegments; i++)
{
float u1 = map(i, 0, numSegments, 0, TAU); // u parameter for current segment
float u2 = map(i + 1, 0, numSegments, 0, TAU); // u parameter for next segment
beginShape(QUAD_STRIP);
for(int j=0; j<num_faces; j++)
{
float v = map(j, 0, num_faces-1, 0, TAU);
PVector p1 = s(u1, v, tf_radius).mult(surfaceRadius);
PVector p2 = s(u2, v, tf_radius).mult(surfaceRadius);
vertex(p1.x, p1.y, p1.z);
vertex(p2.x, p2.y, p2.z);
}
endShape(CLOSE);
}
// Draw text on the surface
for(int i=0; i<num_rows-1; i++)
{
float u = map(i, 0, num_rows-1, 0, TAU)+TAU*t;
for(int j=0; j<num_cols-1; j++)
{
float v = map(j, 0, num_cols-1, 0, TAU)-6*TAU*t + ((i % 2 == 0) ? TAU/num_cols*0.5 : 0);
noStroke();
thing tg = things[j*num_cols+i];
if (tg.flipping()) fill(c_digit_flip); else fill(c_digit);
textFont(fdigits);
textSize(4*fac);
drawText(tg.get(), u, v);
}
}
}
// The parametric equations of the surface
//
PVector s(float u, float v, float r)
{
float q = 8*cos(3*u) + 17;
float p = 6*cos(3*u);
return new PVector(
sin(u) + 2*sin(2*u) - (4*sin(2*u)-sin(u))/sqrt(q)*r*cos(v) + p*(4*cos(2*u)+cos(u))/sqrt(q*p*p+q*q)*r*sin(v),
cos(u) - 2*cos(2*u) + (cos(u)+4*cos(2*u))/sqrt(q)*r*cos(v) - p*(sin(u)-4*sin(2*u))/sqrt(q*p*p+q*q)*r*sin(v),
q/sqrt(q*p*p+q*q)*r*sin(v)-2*sin(3*u)
);
}
// Numerical partial derivative with respect to u
//
PVector dsdu(float u, float v, float r)
{
float h = 0.01;
return (s(u+h, v, r).sub(s(u-h, v, r))).mult(0.5*h);
}
// Numerical partial derivative with respect to v
//
PVector dsdv(float u, float v, float r)
{
float h = 0.01;
return (s(u, v+h, r).sub(s(u, v-h, r))).mult(0.5*h);
}
// Normal vector at the point
//
PVector normal_vector(float u, float v, float r) {
PVector du = dsdu(u, v, r);
PVector dv = dsdv(u, v, r);
PVector n = dv.cross(du);
return n.normalize();
}
// System of coordinates at a point on the surface
//
Coords surfCoords(float u, float v, float r)
{
PVector du = dsdu(u,v,r).normalize();
PVector dv = dsdv(u,v,r).normalize();
PVector n = dv.cross(du).normalize();
return new Coords( s(u,v,r).mult(surfaceRadius), du, dv, n );
}
// Coords class to represent the coordinate system at a point on the surface
//
class Coords {
PVector p; // The point on the surface
PVector du; // Tangent vector along u
PVector dv; // Tangent vector along v
PVector n; // Normal vector
Coords(PVector p_, PVector du_, PVector dv_, PVector n_) {
p = p_.copy();
du = du_.copy();
dv = dv_.copy();
n = n_.copy();
}
PMatrix3D basisChange(Coords other) {
PMatrix3D inv = other.getBasisMatrix();
inv.invert(); // Get the inverse of the other system's basis
PMatrix3D result = getBasisMatrix();
result.apply(inv); // Apply inverse to get change of basis
return result;
}
PMatrix3D getBasisMatrix() {
return new PMatrix3D(
du.x, dv.x, n.x, 0,
du.y, dv.y, n.y, 0,
du.z, dv.z, n.z, 0,
0, 0, 0, 1
);
}
}
// General function to draw text on the surface using basis transformation
//
void drawTextOnSurface(PVector position, PMatrix3D basisChangeMatrix, String txt, float scl)
{
pushMatrix(); // Save the current transformation matrix
// Move to the position where the text should be placed
translate(position.x, position.y, position.z);
// Apply the basis change matrix to align the text correctly
applyMatrix(basisChangeMatrix);
// Draw the text
translate(0,0,0.05*fac); // move text up and away from the surface slightly
scale(scl);
text(txt, 0, 0); // Draw text at the origin
popMatrix(); // Restore the original transformation matrix
}
// Draw text on the surface
//
void drawText(String txt, float u, float v)
{
Coords globalCoords = new Coords(
new PVector(0, 0, 0),
new PVector(0, -1, 0),
new PVector(-1, 0, 0),
new PVector(0, 0, 1)
); // Global coordinate system
// Surface coordinate system
Coords localCoords = surfCoords(u, v, tf_radius);
// Compute the change of basis matrix
PMatrix3D basisChangeMatrix = localCoords.basisChange(globalCoords);
// Get the length of the tangent vector along the u-direction
float g = gscl*dsdu(u, v, tf_radius).mult(surfaceRadius).mag();
// Draw the text on the top face
drawTextOnSurface(localCoords.p, basisChangeMatrix, txt, g);
}
void mousePressed()
{
float[] rotations = cam.getRotations();
float[] look = cam.getLookAt();
double dist = cam.getDistance();
println("..:");
println(" cam = new PeasyCam(this, ", look[0], "*fac, ", look[1], "*fac, ", look[2], "*fac, ", dist,"*fac);" );
println(" cam.setRotations(", rotations[0], ", ", rotations[1], ", ", rotations[2],");\n\n");
}
class thing
{
char c; // First digit
char c1; // Second digit (flipped)
float offset; // Offset at which it begins to flip
boolean flips; // Does this thing flip or not?
thing()
{
c = "0123456789".charAt(int(random(0,10)));
do {
c1 = "0123456789".charAt(int(random(0,10)));
}
while (c1 == c);
flips = random(0,1) > 0.6;
offset = random(0, 1);
}
boolean flipping()
{
if (flips)
{
float tt = (t+offset)%1;
if ( (tt > 0.38 && tt < 0.40) || (tt > 0.68 && tt < 0.70 ) )
return true;
}
return false;
}
// Returns c, unless it flips and it's time to flip, in which case it returns c1
String get()
{
return flipping() ? ""+c1 : ""+c;
}
}
/* 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