Last active
October 1, 2024 15:37
-
-
Save wschutzer/b26d5f979d0ee5453c4ea45ef94fe6c9 to your computer and use it in GitHub Desktop.
Mobius tunnel
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
// The tunnel is a mobius ring with digits seen from the inside. | |
// | |
// Mathematics and Processing code by Waldeck Schützer (@infinitymathart) | |
// Motion blur template by @davidbeesandbombs, explanation/article: https://bleuje.com/tutorial6/ | |
// modified by @infinitymathart to add the chromatic aberration effect. | |
// Idea and concept for flipping digits on a surface by @etinjcb | |
// First million digits of pi from: https://pi2e.ch/blog/2017/03/10/pi-digits-download/ file: pi_dec_1m.txt | |
// | |
// | |
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 = 200; // Number of segments along the Möbius ring (higher means smoother) | |
float ringRadius = 200*fac; // Radius of the Möbius ring | |
float ringWidth = 180*fac; // Width of the rectangular cross-section | |
float ringThickness = 180*fac; // Thickness of the rectangular cross-section | |
float maxOffset = 5; // Maximum amount of chromatic aberration at the edges | |
int n_chars = 35; | |
float lm = -0.94; | |
float csp = 1.96/n_chars; | |
float gscl = 0.24/fac; // Proportional font scaling | |
int FPS = 60; | |
int samplesPerFrame = 7; | |
int numFrames = 90*FPS; | |
float shutterAngle = 2.1; | |
float bht = 1.4; | |
color c_face1 = color(255); | |
color c_face1_flip = color(200); | |
color c_face2 = color(255); | |
color c_face2_flip = color(200); | |
PFont courier; | |
int[][] result; | |
float t, c; | |
int rows = 10; | |
int cols = 10; | |
BufferedReader piReader; | |
String piFilePath; | |
thing[] things_t; | |
thing[] things_b; | |
thing[] things_l; | |
thing[] things_r; | |
// ----------------------------------------------------------------------------------------- | |
// Modified @davebeesandbombs motion blur template to add lens chromatic aberration | |
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; | |
} | |
void push() { | |
pushMatrix(); | |
pushStyle(); | |
} | |
void pop() { | |
popStyle(); | |
popMatrix(); | |
} | |
void draw() { | |
if (!recording) { | |
t = 1 - 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 = 1-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++) | |
{ | |
// Lens chromatic aberration effect: offsets the pixel indices according to the | |
// normalized distance from the center of the image. | |
int x = i % width; | |
int y = i / width; | |
// Compute distance from center (normalized between 0 and 1) | |
float dx = (x - width / 2.0) / (width / 2.0); | |
float dy = (y - height / 2.0) / (height / 2.0); | |
float dist = sqrt(dx * dx + dy * dy); // Distance from center (0 to 1) | |
// Offset based on distance, stronger near the edges | |
int rx_ofs = (int)(maxOffset * dist); | |
int ry_ofs = -(int)(maxOffset * dist); | |
int gx_ofs = -(int)(maxOffset * dist); | |
int gy_ofs = (int)(maxOffset * dist); | |
int bx_ofs = (int)(maxOffset * dist / 2); | |
int by_ofs = -(int)(maxOffset * dist / 2); | |
// Calculate the offset index for each color channel | |
int ri = constrain(i + rx_ofs + ry_ofs * width, 0, pixels.length - 1); | |
int gi = constrain(i + gx_ofs + gy_ofs * width, 0, pixels.length - 1); | |
int bi = constrain(i + bx_ofs + by_ofs * width, 0, pixels.length - 1); | |
// Pixel averaging implementing the motion blur. Notice the pixel indices | |
// computed according to the chromatic aberration effect. | |
int r = int(constrain(1.0*result[ri][0]/samplesPerFrame*bht,0,255)); | |
int g = int(constrain(1.0*result[bi][1]/samplesPerFrame*bht,0,255)); | |
int b = int(constrain(1.0*result[gi][2]/samplesPerFrame*bht,0,255)); | |
pixels[i] = 0xff << 24 | | |
r << 16 | | |
g << 8 | | |
b; | |
} | |
updatePixels(); | |
saveFrame("/tmp/t/frame_####.png"); | |
println(frameCount,"/",numFrames); | |
if (frameCount==numFrames) | |
exit(); | |
} | |
} | |
char readNextChar() { | |
try { | |
// Read the next character from the file | |
int nextChar = piReader.read(); | |
if (nextChar == -1) { | |
// End of file reached, return null character | |
return '\0'; | |
} else { | |
return (char) nextChar; | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
return '\0'; | |
} | |
} | |
void settings() | |
{ | |
size(frame_size,frame_size,P3D); | |
pixelDensity(1); | |
smooth(8); | |
} | |
void setup() | |
{ | |
randomSeed(1337); | |
result = new int[width*height][3]; | |
// Tunnel view 1 | |
//cam = new PeasyCam(this, 181.10555 *fac, 27.147495 *fac, 1.1629903 *fac, 79.32707977294922 *fac); | |
//cam.setRotations( -1.4146475 , -0.10396168 , 0.03245295 ); | |
// Tunnel view 2 - viral on instagram | |
// cam = new PeasyCam(this, 219.31699 *fac, 30.588377 *fac, 6.93263 *fac, 79.32707977294922 *fac); | |
// cam.setRotations( -1.3788422 , 0.55727357 , -0.027554117 ); | |
// Tunnel inner wall perspective - cool | |
cam = new PeasyCam(this, 219.31699 *fac, 30.588377 *fac, 6.93263 *fac, 129.02213214848877 *fac); | |
cam.setRotations( -0.66477895 , 1.3625605 , -0.8804048 ); | |
if (recording) | |
cam.setActive(false); | |
courier = createFont("Courier New",24*fac,true); | |
piFilePath = dataPath("") + "/pi_dec_1m.txt"; | |
piReader = createReader(piFilePath); | |
// Things are random digits to be drawn on each of the 4 faces of the surface | |
// (actually there are only two faces) | |
things_t = new thing[n_chars*numSegments]; | |
things_b = new thing[n_chars*numSegments]; | |
things_l = new thing[n_chars*numSegments]; | |
things_r = new thing[n_chars*numSegments]; | |
// The digits of pi fill each individual face, begining with the top | |
for(int j=0; j<numSegments;j++) | |
{ | |
int k = n_chars*j; | |
for(int i=0; i<n_chars; i++) | |
things_t[k+i] = new thing(lm+i*csp, 0, readNextChar()); | |
} | |
for(int j=0; j<numSegments;j++) | |
{ | |
int k = n_chars*j; | |
for(int i=0; i<n_chars; i++) | |
things_b[k+i] = new thing(lm+i*csp, 0, readNextChar()); | |
} | |
for(int j=0; j<numSegments;j++) | |
{ | |
int k = n_chars*j; | |
for(int i=0; i<n_chars; i++) | |
things_l[k+i] = new thing(lm+i*csp, 0, readNextChar()); | |
} | |
for(int j=0; j<numSegments;j++) | |
{ | |
int k = n_chars*j; | |
for(int i=0; i<n_chars; i++) | |
things_r[k+i] = new thing(lm+i*csp, 0, readNextChar()); | |
} | |
} | |
void draw_() | |
{ | |
background(0); | |
pushMatrix(); | |
noStroke();//stroke(128); // Draw lines around the rectangles | |
fill(0); //fill(150*lvl, 200*lvl, 255*lvl); | |
lights(); | |
directionalLight(128, 128, 128, 0, 0, 1); | |
drawMobiusRing(); | |
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.18*height); | |
popMatrix(); | |
} | |
// Rendering the Möbius ring using the segment class for each segment | |
// | |
void drawMobiusRing() | |
{ | |
for (int i = 0; i < numSegments; i++) | |
{ | |
float u1 = map(i, 0, numSegments, 0, TWO_PI); // u parameter for current segment | |
float u2 = map(i + 1, 0, numSegments, 0, TWO_PI); // u parameter for next segment | |
// Create ring for u1 and u2 | |
ring f = new ring(u1, u2, 0); | |
f.set_index(i); | |
f.shift(-2*TAU*t); | |
f.draw(); | |
} | |
} | |
// Function to draw the debugging visuals | |
// | |
void drawFaceNormals(ring f) | |
{ | |
pushStyle(); | |
PVector[] centers = {f.tf.p, f.bf.p, f.rf.p, f.lf.p}; | |
PVector[] normals = {f.tf.n, f.bf.n, f.rf.n, f.lf.n}; | |
for (int i = 0; i < centers.length; i++) { | |
PVector center = centers[i]; | |
PVector normalEnd = PVector.add(center, normals[i].copy().mult(30)); // Scale normal for visibility | |
// Draw the normal vector (line) | |
stroke(0,0,255); // White line | |
strokeWeight(1); | |
line(center.x, center.y, center.z, normalEnd.x, normalEnd.y, normalEnd.z); | |
// Draw a green dot at the center of each face | |
stroke(0, 255, 0); | |
strokeWeight(6); | |
point(center.x, center.y, center.z); | |
// Draw a red dot at the end of each normal vector | |
stroke(255, 0, 0); | |
strokeWeight(6); | |
point(normalEnd.x, normalEnd.y, normalEnd.z); | |
} | |
popStyle(); | |
} | |
// Parametric surface of the Möbius strip | |
// | |
PVector s(float u, float v, float r) { | |
float x = r * (1 + v / 2 * cos(u / 2)) * cos(u); | |
float y = r * (1 + v / 2 * cos(u / 2)) * sin(u); | |
float z = r * v / 2 * sin(u / 2); | |
return new PVector(x, y, z); | |
} | |
// Partial derivative with respect to v | |
// | |
PVector dsdv(float u, float v, float r) | |
{ | |
float x = 1.0 / 2 * cos(u / 2) * cos(u); | |
float y = 1.0 / 2 * cos(u / 2) * sin(u); | |
float z = 1.0 / 2 * sin(u / 2); | |
return new PVector(x, y, z).mult(r); | |
} | |
// Partial derivative with respect to u | |
// | |
PVector dsdu(float u, float v, float r) | |
{ | |
float x = -v / 4 * sin(u / 2) * cos(u) - (1 + v / 2 * cos(u / 2)) * sin(u); | |
float y = -v / 4 * sin(u / 2) * sin(u) + (1 + v / 2 * cos(u / 2)) * cos(u); | |
float z = v / 4 * cos(u / 2); | |
return new PVector(x, y, z).mult(r); | |
} | |
// 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), 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 | |
); | |
} | |
} | |
// Surface segment. It consists of 4 lines (top,left,bottom,right) | |
// forming a quadrilateral with center at the point (u, v) | |
// (tipically, v = 0). | |
// | |
class segment { | |
float u, v; | |
Coords c; // Center and basis of the segment | |
PVector tl; // Top left vertex | |
PVector tr; // Top right vertex | |
PVector bl; // Bottom left vertex | |
PVector br; // Bottom right vertex | |
Coords tf; // Top line coordinate system | |
Coords bf; // Bottom line coordinate system | |
Coords rf; // Right line coordinate system | |
Coords lf; // Left line coordinate system | |
segment(float u, float v) | |
{ | |
this.u = u; | |
this.v = v; | |
// Central coordinate system | |
c = surfCoords(u, v, ringRadius); | |
// Compute the four vertices for the rectangular segment | |
tl = c.p.copy().add(c.dv.copy().mult(-ringWidth / 2)).add(c.n.copy().mult(ringThickness / 2)); | |
tr = c.p.copy().add(c.dv.copy().mult(ringWidth / 2)).add(c.n.copy().mult(ringThickness / 2)); | |
bl = c.p.copy().add(c.dv.copy().mult(-ringWidth / 2)).add(c.n.copy().mult(-ringThickness / 2)); | |
br = c.p.copy().add(c.dv.copy().mult(ringWidth / 2)).add(c.n.copy().mult(-ringThickness / 2)); | |
// Coordinate systems for each face: | |
tf = new Coords(tl.copy().add(tr).mult(0.5), c.du, c.dv, c.n); // Top face | |
bf = new Coords(bl.copy().add(br).mult(0.5), c.du, c.dv.copy().mult(-1), c.n.copy().mult(-1)); // Bottom face | |
rf = new Coords(tr.copy().add(br).mult(0.5), c.du, c.n.copy().mult(-1), c.dv); // Right face | |
lf = new Coords(tl.copy().add(bl).mult(0.5), c.du, c.n.copy(), c.dv.copy().mult(-1)); // Left face | |
} | |
void draw_tl() // Draws the vertex at tl | |
{ | |
vertex(tl.x, tl.y, tl.z); | |
} | |
void draw_tr() | |
{ | |
vertex(tr.x, tr.y, tr.z); | |
} | |
void draw_bl() | |
{ | |
vertex(bl.x, bl.y, bl.z); | |
} | |
void draw_br() | |
{ | |
vertex(br.x, br.y, br.z); | |
} | |
} | |
// Each pair of segments define 4 faces (top,left,bottom,right) which form | |
// a ring. | |
// | |
class ring | |
{ | |
float u1, u2, v; | |
float u_shift; // Shift to be applied to u1 and u2 during animation | |
segment s1; // First segment | |
segment s2; // Next segment | |
Coords tf; // System of coordinates for top face | |
Coords bf; // System of coordinates for bottom face | |
Coords rf; // System of coordinates for right face | |
Coords lf; // System of coordinates for left face | |
int index; | |
ring(float u1_, float u2_, float v_) | |
{ | |
u_shift = 0; | |
index = 0; | |
u1 = u1_; | |
u2 = u2_; | |
v = v_; | |
update_coords(); | |
} | |
void update_coords() | |
{ | |
// Create segment objects for u1 and u2 | |
s1 = new segment(u1+u_shift, v); | |
s2 = new segment(u2+u_shift, v); | |
tf = new Coords(s1.tf.p.copy().add(s2.tf.p).mult(0.5), s1.tf.du.copy().add(s2.tf.du).mult(0.5), s1.tf.dv.copy().add(s2.tf.dv).mult(0.5), s1.tf.n.copy().add(s2.tf.n).mult(0.5)); | |
bf = new Coords(s1.bf.p.copy().add(s2.bf.p).mult(0.5), s1.bf.du.copy().add(s2.bf.du).mult(0.5), s1.bf.dv.copy().add(s2.bf.dv).mult(0.5), s1.bf.n.copy().add(s2.bf.n).mult(0.5)); | |
lf = new Coords(s1.lf.p.copy().add(s2.lf.p).mult(0.5), s1.lf.du.copy().add(s2.lf.du).mult(0.5), s1.lf.dv.copy().add(s2.lf.dv).mult(0.5), s1.lf.n.copy().add(s2.lf.n).mult(0.5)); | |
rf = new Coords(s1.rf.p.copy().add(s2.rf.p).mult(0.5), s1.rf.du.copy().add(s2.rf.du).mult(0.5), s1.rf.dv.copy().add(s2.rf.dv).mult(0.5), s1.rf.n.copy().add(s2.rf.n).mult(0.5)); | |
} | |
void shift(float u_shift_) | |
{ | |
u_shift = u_shift_; | |
update_coords(); | |
} | |
void set_index(int i_) | |
{ | |
index = i_; | |
} | |
// Draws the faces of a ring and things on the faces | |
// | |
void draw() | |
{ | |
// Draw the top and bottom faces of the segment | |
beginShape(QUADS); | |
s1.draw_tl(); | |
s1.draw_bl(); | |
s2.draw_bl(); | |
s2.draw_tl(); | |
endShape(CLOSE); | |
beginShape(QUADS); | |
s1.draw_tr(); | |
s2.draw_tr(); | |
s2.draw_br(); | |
s1.draw_br(); | |
endShape(CLOSE); | |
// Draw the left face (connecting top-left and bottom-left) | |
beginShape(QUADS); | |
s1.draw_tl(); | |
s2.draw_tl(); | |
s2.draw_tr(); | |
s1.draw_tr(); | |
endShape(CLOSE); | |
// Draw the right face (connecting bottom-left and bottom-right) | |
beginShape(QUADS); | |
s1.draw_bl(); | |
s2.draw_bl(); | |
s2.draw_br(); | |
s1.draw_br(); | |
endShape(CLOSE); | |
// Text things on the faces of the ring | |
pushStyle(); | |
textAlign(CENTER, CENTER); // Center the text | |
textSize(10*fac); // Adjust text size as needed | |
fill(255); // Text color | |
stroke(255); | |
for(int i=0;i<n_chars;i++) | |
{ | |
thing tg = things_t[n_chars*index+i]; | |
if (tg.flipping()) fill(c_face1_flip); else fill(c_face1); | |
drawTextOnTopFace(this, tg.get(), tg.u, tg.v); | |
tg = things_b[n_chars*index+i]; | |
if (tg.flipping()) fill(c_face1_flip); else fill(c_face1); | |
drawTextOnBottomFace(this, tg.get(), tg.u, tg.v); | |
tg = things_l[n_chars*index+i]; | |
if (tg.flipping()) fill(c_face2_flip); else fill(c_face2); | |
drawTextOnLeftFace(this, tg.get(), tg.u, tg.v); | |
tg = things_r[n_chars*index+i]; | |
if (tg.flipping()) fill(c_face2_flip); else fill(c_face2); | |
drawTextOnRightFace(this, tg.get(), tg.u, tg.v); | |
} | |
popStyle(); | |
} | |
} | |
float dv_top(ring f, float u3, float v3) | |
{ | |
float h = 0.01; | |
return s_top_face(f, u3, v3+h).sub(s_top_face(f, u3, v3-h)).mult(0.5/h).mag(); | |
} | |
// Parametric function for the top face using bilinear interpolation | |
PVector s_top_face(ring f, float u3, float v3) | |
{ | |
// Interpolate between the four corner points (tl, tr, bl, br) | |
PVector tl = f.s1.tl; // Top-left | |
PVector tr = f.s1.tr; // Top-right | |
PVector bl = f.s2.tl; // Bottom-left | |
PVector br = f.s2.tr; // Bottom-right | |
// Adjust u3 and v3 to range from -1 to 1, making sure the center corresponds to u3 = 0, v3 = 0 | |
float uRatio = (u3 + 1) / 2.0; // Map u3 from [-1,1] to [0,1] | |
float vRatio = (v3 + 1) / 2.0; // Map v3 from [-1,1] to [0,1] | |
// Bilinear interpolation to calculate the point on the surface | |
PVector point = tl.copy().mult((1 - uRatio) * (1 - vRatio)) | |
.add(tr.copy().mult(uRatio * (1 - vRatio))) | |
.add(bl.copy().mult((1 - uRatio) * vRatio)) | |
.add(br.copy().mult(uRatio * vRatio)); | |
return point; | |
} | |
// Parametric function for the bottom face using bilinear interpolation | |
PVector s_bottom_face(ring f, float u3, float v3) { | |
PVector tl = f.s1.bl; // Bottom-left | |
PVector tr = f.s1.br; // Bottom-right | |
PVector bl = f.s2.bl; // Top-left | |
PVector br = f.s2.br; // Top-right | |
float uRatio = (u3 + 1) / 2.0; // Map u3 from [-1,1] to [0,1] | |
float vRatio = (v3 + 1) / 2.0; // Map v3 from [-1,1] to [0,1] | |
PVector point = tl.copy().mult((1 - uRatio) * (1 - vRatio)) | |
.add(tr.copy().mult(uRatio * (1 - vRatio))) | |
.add(bl.copy().mult((1 - uRatio) * vRatio)) | |
.add(br.copy().mult(uRatio * vRatio)); | |
return point; | |
} | |
float dv_bottom(ring f, float u3, float v3) | |
{ | |
float h = 0.01; | |
return s_bottom_face(f, u3, v3+h).sub(s_bottom_face(f, u3, v3-h)).mult(0.5/h).mag(); | |
} | |
// Parametric function for the left face using bilinear interpolation | |
PVector s_left_face(ring f, float u3, float v3) { | |
PVector tl = f.s1.tl; // Top-left | |
PVector tr = f.s1.bl; // Top-right | |
PVector bl = f.s2.tl; // Bottom-left | |
PVector br = f.s2.bl; // Bottom-right | |
float uRatio = (u3 + 1) / 2.0; // Map u3 from [-1,1] to [0,1] | |
float vRatio = (v3 + 1) / 2.0; // Map v3 from [-1,1] to [0,1] | |
PVector point = tl.copy().mult((1 - uRatio) * (1 - vRatio)) | |
.add(tr.copy().mult(uRatio * (1 - vRatio))) | |
.add(bl.copy().mult((1 - uRatio) * vRatio)) | |
.add(br.copy().mult(uRatio * vRatio)); | |
return point; | |
} | |
float dv_left(ring f, float u3, float v3) | |
{ | |
float h = 0.01; | |
return s_left_face(f, u3, v3+h).sub(s_left_face(f, u3, v3-h)).mult(0.5/h).mag(); | |
} | |
// Parametric function for the right face using bilinear interpolation | |
PVector s_right_face(ring f, float u3, float v3) { | |
PVector tl = f.s1.tr; // Top-left | |
PVector tr = f.s1.br; // Top-right | |
PVector bl = f.s2.tr; // Bottom-left | |
PVector br = f.s2.br; // Bottom-right | |
float uRatio = (u3 + 1) / 2.0; // Map u3 from [-1,1] to [0,1] | |
float vRatio = (v3 + 1) / 2.0; // Map v3 from [-1,1] to [0,1] | |
PVector point = tl.copy().mult((1 - uRatio) * (1 - vRatio)) | |
.add(tr.copy().mult(uRatio * (1 - vRatio))) | |
.add(bl.copy().mult((1 - uRatio) * vRatio)) | |
.add(br.copy().mult(uRatio * vRatio)); | |
return point; | |
} | |
float dv_right(ring f, float u3, float v3) | |
{ | |
float h = 0.01; | |
return s_right_face(f, u3, v3+h).sub(s_right_face(f, u3, v3-h)).mult(0.5/h).mag(); | |
} | |
// General function to draw text on a face using basis transformation | |
void drawTextOnFace(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,-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 | |
} | |
// Function to draw text on the top face | |
void drawTextOnTopFace(ring f, String label, 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 (identity matrix) | |
// Compute the change of basis matrix | |
PMatrix3D basisChangeMatrix = f.tf.basisChange(globalCoords); | |
// Get the point on the face where to draw | |
PVector center = s_top_face(f, u, -v); | |
float g = dv_top(f, u, -v); | |
// Draw the text on the top face | |
drawTextOnFace(center, basisChangeMatrix, label, g*gscl); | |
} | |
// Function to draw text on the bottom face | |
void drawTextOnBottomFace(ring f, String label, 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 for the bottom face | |
// Compute the change of basis matrix | |
PMatrix3D basisChangeMatrix = f.bf.basisChange(globalCoords); | |
// Get the center of the bottom face | |
PVector center = s_bottom_face(f, -u, -v); | |
float g = dv_bottom(f, -u, -v); | |
// Draw the text on the bottom face | |
drawTextOnFace(center, basisChangeMatrix, label, g*gscl); | |
} | |
// Function to draw text on the left face | |
void drawTextOnLeftFace(ring f, String label, float u, float v) | |
{ | |
Coords globalCoords = new Coords( | |
new PVector(0, 0, 0), | |
new PVector(0, 1, 0), // dv (horizontal) for the left face | |
new PVector(-1, 0, 0), // du (vertical) for the left face | |
new PVector(0, 0, 1) | |
); // Global coordinate system for the left face | |
// Compute the change of basis matrix | |
PMatrix3D basisChangeMatrix = f.lf.basisChange(globalCoords); | |
// Get the center of the left face | |
PVector center = s_left_face(f, -u, -v); | |
float g = dv_left(f, -u, -v); | |
// Draw the text on the left face | |
drawTextOnFace(center, basisChangeMatrix, label, g*gscl); | |
} | |
// Function to draw text on the right face | |
void drawTextOnRightFace(ring f, String label, float u, float v) | |
{ | |
Coords globalCoords = new Coords( | |
new PVector(0, 0, 0), | |
new PVector(0, 1, 0), // dv (horizontal) for the right face | |
new PVector(-1, 0, 0), // du (vertical) for the right face | |
new PVector(0, 0, 1) | |
); // Global coordinate system for the right face | |
// Compute the change of basis matrix | |
PMatrix3D basisChangeMatrix = f.rf.basisChange(globalCoords); | |
// Get the center of the right face | |
PVector center = s_right_face(f, u, -v); | |
float g = dv_right(f, u, -v); | |
// Draw the text on the right face | |
drawTextOnFace(center, basisChangeMatrix, label, g*gscl); | |
} | |
void mousePressed() | |
{ | |
float[] position = cam.getPosition(); | |
println("\n\nPosition (", position[0], ", ", position[1], ", ", position[2], ")\n"); | |
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 | |
{ | |
float u, v; // (u,v) coordinates relative to the surface face | |
char c; // First digit | |
char c1; // Second digit | |
float offset; // Offset at which it begins to flip | |
boolean flips; // Does this thing flip or not? | |
thing(float u_, float v_) | |
{ | |
u = u_; | |
v = v_; | |
c = "0123456789".charAt(int(random(0,9))); | |
c1 = "0123456789".charAt(int(random(0,9))); | |
flips = random(0,1) > 0.6; | |
offset = random(0, 1); | |
} | |
thing(float u_, float v_, char d) | |
{ | |
u = u_; | |
v = v_; | |
c = d; | |
// c1 = "×∞∅∂∮∿⋌⋉⪯⊕⊗⋍⟡∴⇒√𝛼±0123456789".charAt(int(random(0,28))); | |
c1 = "0123456789".charAt(int(random(0,9))); | |
flips = random(0,1) > 0.8; | |
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