Created
October 23, 2024 22:17
-
-
Save wschutzer/654efce14a68099cc3e5a72372d373da to your computer and use it in GitHub Desktop.
Torus with texture
This file contains 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
// 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