Skip to content

Instantly share code, notes, and snippets.

@wschutzer
Created October 23, 2024 22:17
Show Gist options
  • Save wschutzer/654efce14a68099cc3e5a72372d373da to your computer and use it in GitHub Desktop.
Save wschutzer/654efce14a68099cc3e5a72372d373da to your computer and use it in GitHub Desktop.
Torus with texture
// Torus with texture
//
// Processing code and Mathematics by Waldeck Schutzer (@infinitymathart)
// Based on code by Etienne Jacob (@etinjcb)
// Motion blur template by @davebeesandbombs, explanation/article: https://bleuje.com/tutorial6/
// See the license information at the end of this file.
final boolean recording = false;
final boolean reverse = true;
final boolean draw_digits = false; // Digits or texture?
PShape surf;
PGraphics tex;
PFont mono;
float fac = 1;
float R = 240;
int tube_radius = 34;
final int u_divs = 300;
final int v_divs = 1200;
final color fg_color = color(255,255,255);
final color bg_color = color(0,0,0);
final color surf_color = color(0,0,0);
float tscl = 1.0; // Text scale
final int num_stripes = 10;
final float d_hover = 1.0; // text goes slightly above the surface (not needed if surface mesh very thin)
final int num_digits_v = 19; // Quantity of digits along the v direction
final Coordinates global_coords = new Coordinates( // System coordinates (global)
new PVector(0, 0, 0),
new PVector(0, -1, 0),
new PVector(1, 0, 0),
new PVector(0, 0, 1)
);
//////////////////////////////////////////////////////////////////////////////
// Start of template
int[][] result; // pixel colors buffer for motion blur
float t; // time global variable in [0,1[
float c; // other global variable for testing things, controlled by mouse
//-----------------------------------
// some generally useful functions...
float c01(float x)
{
return constrain(x, 0, 1);
}
// ease in and out, [0,1] -> [0,1], with a parameter g:
// https://patakk.tumblr.com/post/88602945835/heres-a-simple-function-you-can-use-for-easing
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);
}
// defines a map function variant to constrain or not in target interval (exists in openFrameworks)
float map(float x, float a, float b, float c, float d, boolean constr)
{
return constr ? constrain(map(x, a, b, c, d), min(c, d), max(c, d)) : map(x, a, b, c, d);
}
// short one to map an x from [a,b] to [0,1] and constrain
float mp01(float x, float a, float b)
{
return map(x, a, b, 0, 1, true);
}
// reversed pow that does some kind of ease out, [0,1] -> [0,1], with a parameter g
float pow_(float p, float g)
{
return 1-pow(1-p, g);
}
// hyperbolic tangent, maps ]-infinity,+infinity[ to ]-1,1[
float tanh(float x)
{
return (float)Math.tanh(x);
}
//-----------------------------------
void draw()
{
if (!recording) // test mode...
{
t = (mouseX*1.3/width)%1;
t += 0.007;
t%=1;
c = mouseY*1.0/height;
if (mousePressed)
println(c);
draw_();
} else // render mode...
{
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)%1;
// t %= 1;
draw_();
loadPixels();
for (int i=0; i<pixels.length; i++) {
result[i][0] += constrain(red(pixels[i])*bht,0,255);
result[i][1] += constrain(green(pixels[i])*bht,0,255);
result[i][2] += constrain(blue(pixels[i])*bht,0,255);
}
}
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();
// Due to a weird flicker that has to do with the first frame not being
// rendered correctly, skip the first frame, and render past 1 frame at end.
// Does not occur if the default font is used.
if (frameCount>1 && frameCount<=numFrames) {
saveFrame("/tmp/r/fr###.png");
println(frameCount, "/", numFrames);
}
if (frameCount==numFrames+1)
exit();
}
}
// End of template
//////////////////////////////////////////////////////////////////////////////
final int samplesPerFrame = 1;
final int numFrames = 120;
final float shutterAngle = 0.6;
final float bht = 1.1; // Brightness adjustment
class Coordinates
{
PVector position;
PVector u1, u2, u3;
Coordinates(PVector pos, PVector u1_, PVector u2_, PVector u3_)
{
position = pos;
u1 = u1_.copy().normalize();
u2 = u2_.copy().normalize();
u3 = u3_.copy().normalize();
}
// Matrix whose columns are the basis vectors
PMatrix3D getBasisMatrix() {
return new PMatrix3D(
u1.x, u2.x, u3.x, 0,
u1.y, u2.y, u3.y, 0,
u1.z, u2.z, u3.z, 0,
0, 0, 0, 1
);
}
// Computes the change of basis matrix from this basis to the other basis
PMatrix3D basisChange(Coordinates other) {
PMatrix3D inv = other.getBasisMatrix();
inv.invert(); // Computes the inverse of other matrix
PMatrix3D result = getBasisMatrix();
result.apply(inv); // Multiplies the current matrix by the inverse of other
return result;
}
}
// This defines a (closed) space curve
PVector spaceCurve(float u)
{
float theta = TAU*u; // u in the interval [0, 1]
float x = cos(theta);
float y = sin(theta);
float z = 0;
return new PVector(x, y, z).mult(R);
}
// Computes a (non-unit) tangent vector to the curve - numerical approximation
PVector tangent_vector(float u)
{
final float h = 0.001;
return PVector.sub(spaceCurve(u+h),spaceCurve(u-h)).div(2*h);
}
// Computes a (non-unit) normal vector to the curve - numerical approximation
PVector normal_vector(float u)
{
final float h = 0.001;
return tangent_vector(u+h).cross(tangent_vector(u-h));
}
// This computes the Frenet-Serret frame
Coordinates FrenetBasis(float u)
{
PVector pos = spaceCurve(u);
PVector u1 = tangent_vector(u); // T vector (tangent)
PVector u2 = normal_vector(u); // N vector (normal)
PVector u3 = u2.copy().cross(u1); // B vector (binormal)
return new Coordinates(pos, u1, u2, u3);
}
// This creates a circular tube enclosing the curve
PVector s(float u, float v, float r)
{
float d1 = r*cos(TAU*v);
float d2 = r*sin(TAU*v);
Coordinates coords = FrenetBasis(u);
// The cross-section of the surface is a circle in the (N, B)-plane:
PVector crossSection = PVector.add(coords.u2.copy().mult(d1),coords.u3.copy().mult(d2));
return PVector.add(coords.position, crossSection);
}
// Tangent vector to the surface along the u direction
PVector dsdu(float u, float v, float r)
{
final float h = 0.001;
return PVector.sub( s(u+h,v,r), s(u-h,v,r) ).div(2*h);
}
// Tangent vector to the surface along the v direction
PVector dsdv(float u, float v, float r)
{
final float h = 0.001;
return PVector.sub( s(u,v+h,r), s(u,v-h,r) ).div(2*h);
}
// Normal vector to the surface at a point
PVector normal_s(float u, float v, float r)
{
return dsdu(u,v,r).cross( dsdv(u,v,r) );
}
// Compute the surface geometry
PShape createSurface()
{
noFill();
noStroke();
PShape s = createShape();
s.beginShape(TRIANGLE_STRIP);
if (draw_digits)
s.fill(reverse ? fg_color : surf_color);
else
{
s.fill(255);
s.textureMode(NORMAL);
s.texture(tex);
}
for (int i=0; i<v_divs; i++)
{
float u1 = map(i, 0, v_divs, 0, 1);
float u2 = map(i+1, 0, v_divs, 0, 1);
for (int j=0; j<=u_divs; j++)
{
float v = map(j, 0, u_divs, 0, 1);
PVector v1 = s(u1, v, tube_radius);
PVector n1 = normal_s(u1, v, tube_radius);
PVector v2 = s(u2, v, tube_radius);
PVector n2 = normal_s(u2, v, tube_radius);
s.normal(n1.x,n1.y,n1.z);
s.vertex(v1.x, v1.y, v1.z, u1, v);
s.normal(n2.x,n2.y,n2.z);
s.vertex(v2.x, v2.y, v2.z, u2, v);
}
}
s.endShape();
return s;
}
// Draw the stored surface
void drawSurface()
{
if (draw_digits)
surf.setFill(reverse ? fg_color : surf_color);
else
{
surf.setTextureMode(NORMAL);
surf.setFill(color(255));
surf.setTexture(tex);
}
shape(surf); // This is a lot faster than the immediate mode
}
void drawDigit(int nv)
{
final int kSpeed = 6;
final int K = kSpeed*60;
for (int i=0; i<K; i++)
{
float u = (i+kSpeed*t)/K;
float v = map(nv, 0, num_digits_v, 0, 1) + 5*u;
PVector pos = s(u, v, tube_radius + d_hover);
PVector u2 = dsdu(u, v, tube_radius);
PVector u3 = dsdv(u, v, tube_radius);
PVector u1 = u2.copy().cross(u3); // Normal vector to the surface
Coordinates local_coords = new Coordinates(pos, u2, u3, u1);
push();
translate(pos.x, pos.y, pos.z);
applyMatrix( local_coords.basisChange(global_coords) );
noStroke();
textSize(13);
scale(tscl*0.00025*u2.mag()); // Surface metric controls text size
fill(reverse ? bg_color : fg_color);
textAlign(CENTER,CENTER);
text((nv + 3*(i%kSpeed))%10, 0, 0);
pop();
}
}
void drawDigits()
{
for (int i=0; i<num_digits_v; i++)
{
drawDigit(i);
}
}
void settings()
{
if (recording)
size(2160,2160,P3D);
else
size(600, 600, P3D);
fac = width/600.0;
R *= fac;
tube_radius *= fac;
smooth(8);
}
void setup()
{
tex = createGraphics(2*width, 2*height);
if (!draw_digits) drawTexture();
surf = createSurface();
result = new int[width*height][3];
// Some fonts tested.
// mono = createFont("AkayaKanadaka", 144);
// mono = createFont("American Typewriter", 144);
// mono = createFont("Andale Mono", 144); // Very Nice
// mono = createFont("Annai MN", 144); tscl = 0.7;// Interesting
// mono = createFont("Arial Rounded MT Bold", 144);
// mono = createFont("Bai Jamjuree", 144); tscl = 0.9;
// textFont(mono);
}
void draw_()
{
if (!draw_digits)
drawTexture();
background(reverse ? fg_color : bg_color);
lights();
camera(width/2.0, height/2.0, (height/2.0) / tan(PI*23.0 / 180.0), width/2.0, height/2.0, 0, 0, 1, 0);
push();
translate(width/2, height/2-fac*20);
scale(1.2);
//translate(0,15,0);
rotateZ(-0.13*HALF_PI);
drawSurface();
if (draw_digits)
drawDigits();
pop();
drawSignature();
}
void drawTexture()
{
int s_len = int(tex.width/(2*num_stripes));
tex.beginDraw();
tex.background(reverse ? 255 : 20);
tex.noStroke();
tex.fill(reverse? 20 : 255);
for(int i=-2*s_len-int(2*s_len*t); i<tex.width+2*s_len; i+= 2*s_len)
{
tex.rect(0, i, tex.width, s_len);
}
tex.endDraw();
//if (frameCount==1)
// tex.save("/tmp/frame.png");
}
void drawSignature()
{
fill(reverse ? bg_color : fg_color);
push();
translate(width/2,height/2,0);
translate(0,height*0.63,0);
scale(0.14*fac);
textAlign(CENTER);
text("@infinitymathart | 2024", 0, 0, 0);
pop();
}
/* License:
*
* Copyright (c) 2024 Waldeck Schutzer
*
* 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