Created
December 21, 2024 22:32
-
-
Save wschutzer/5c259aa906656e6aba83b80a9859afdd to your computer and use it in GitHub Desktop.
Waves and digits on a torus - inside view, like a tunnel
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
/* Waves and digits on a torus (inside view) | |
* ----------------------------------------- | |
* Requires OpenSimplexNoise, available here: https://gist.github.com/wschutzer/4be8f14d12fc3541024f796ee7fb6fe2 | |
* Download OpenSimplexNoise.pde and place it in the same folder of this file. | |
* | |
* Copyright (C) 2024 Waldeck Schutzer (@infinitymathart) | |
* | |
* This program is free software: you can redistribute it and/or modify | |
* it under the terms of the GNU General Public License as published by | |
* the Free Software Foundation, either version 3 of the License, or | |
* (at your option) any later version. | |
* | |
* This program is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* GNU General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with this program. If not, see <https://www.gnu.org/licenses/>. | |
*/ | |
import peasy.*; | |
import processing.core.PMatrix3D; | |
PeasyCam cam; | |
boolean recording = false; | |
boolean monochromatic = true; | |
int shrinkby = 1; | |
float aspect = 1.0;// 1920.0/1080; | |
int frame_width = recording ? 1080 : 500; | |
int frame_height = (int)(frame_width*aspect); | |
float fac = frame_width/500.0; | |
int numRows = (int)(2*80*fac); // geometry | |
int numCols = (int)(2*24*fac); | |
float cellWidth = 20; | |
float cellHeight = 20; | |
int texWidth = (int)(600*fac); // digits | |
int texHeight = (int)(800*fac); | |
int textRows = 80; | |
int textCols = 80; | |
int numTexts = textRows*textCols; | |
float surfHeight = 40*fac; | |
float R = cellWidth*24*fac;//1000*fac; | |
int FPS = 60; | |
PFont courier; | |
int samplesPerFrame = 3; | |
int numFrames = 30*FPS/shrinkby; | |
float shutterAngle = 1.2; | |
float bht = 1.0; | |
boolean stamping = true; | |
color surfColor = hsbToRgb(200,0.1,0.0); | |
color bgColor = hsbToRgb(200,0.1,0.0); | |
color txtColor = hsbToRgb(0,0,1.0); | |
int[][] result; | |
float t, c; | |
PGraphics tex; | |
PShape surf; | |
OpenSimplexNoise noise; | |
// Parametric coordinates for the surface (here a wobbly torus, but could be virtually anything) | |
// | |
PVector s(float u, float v) | |
{ | |
final float nscl = 3; | |
final float tscl = 5; | |
final float a = R/8*map((float)noise.eval(nscl*sin(TAU*u),nscl*sin(TAU*v),tscl*cos(TAU*t),tscl*sin(TAU*t)),-1,1,1,1.5); | |
final float b = R; | |
float x = (a*cos(TAU*u) + b)*cos(TAU*v); | |
float y = (a*cos(TAU*u) + b)*sin(TAU*v); | |
float z = a*sin(TAU*u); | |
return new PVector(x, y, z); | |
} | |
// Function to change the alpha of a color | |
color setAlpha(color c, int newAlpha) | |
{ | |
return color(red(c), green(c), blue(c), newAlpha); | |
} | |
int hsbToRgb(float h, float s, float b) { | |
float r = 0, g = 0, bl = 0; // Initialize RGB values | |
// Ensure h is within [0, 360) range | |
h = h % 360; | |
if (h < 0) h += 360; | |
float c = b * s; // Chroma | |
float x = c * (1 - abs((h / 60) % 2 - 1)); | |
float m = b - c; | |
if (h < 60) { | |
r = c; g = x; bl = 0; | |
} else if (h < 120) { | |
r = x; g = c; bl = 0; | |
} else if (h < 180) { | |
r = 0; g = c; bl = x; | |
} else if (h < 240) { | |
r = 0; g = x; bl = c; | |
} else if (h < 300) { | |
r = x; g = 0; bl = c; | |
} else { | |
r = c; g = 0; bl = x; | |
} | |
// Convert to 0-255 range for RGB | |
int red = round((r + m) * 255); | |
int green = round((g + m) * 255); | |
int blue = round((bl + m) * 255); | |
// Return as an integer color using Processing's color function | |
return color(red, green, blue); | |
} | |
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(); | |
if (frameCount>1) | |
{ | |
saveFrame("/tmp/r/frame_####.png"); | |
if (stamping) println(frameCount,"/",numFrames); | |
if (frameCount>numFrames) | |
exit(); | |
} | |
} | |
} | |
void settings() | |
{ | |
size(frame_width,frame_height,P3D); | |
pixelDensity(1); | |
smooth(8); | |
} | |
void setup() | |
{ | |
randomSeed(73572); | |
noise = new OpenSimplexNoise(73573); | |
result = new int[width*height][3]; | |
tex = createGraphics(texWidth, texHeight); | |
cam = new PeasyCam(this, -470.4338 *fac, 8.478334 *fac, -16.255064 *fac, 1.0 *fac); | |
cam.setRotations( 1.5878239 , -0.16973245 , 2.208412 ); | |
if (recording) | |
cam.setActive(false); | |
courier = createFont("Courier New",24*fac,true); | |
things = new thing[numTexts]; | |
int k = 0; | |
for(int i=0; i<textCols; i++) | |
{ | |
float u = 1.0*i/textCols; | |
for(int j=0; j<textRows; j++) | |
{ | |
float v = 1.0*j/textRows; | |
things[k] = new thing(u, v, i, j); | |
k++; | |
} | |
} | |
} | |
void drawTexture() | |
{ | |
tex.beginDraw(); | |
tex.background(bgColor); | |
tex.noStroke(); | |
tex.fill(txtColor); | |
for(thing th: things) | |
{ | |
float d = (th.v+t) % 1; | |
float p = th.u;// + (th.j % 2 == 0 ? 2/numCols : 0); | |
tex.fill(th.colour()); | |
tex.textSize(12*fac); | |
tex.textAlign(LEFT, BOTTOM); | |
tex.text(th.get(), tex.width*p, tex.height*d); | |
} | |
tex.endDraw(); | |
/* if (frameCount == 2) | |
{ | |
tex.save("/tmp/r/texture.png"); | |
//exit(); | |
} */ | |
} | |
void draw_() | |
{ | |
// Texture | |
drawTexture(); | |
// Surface | |
pushMatrix(); | |
background(bgColor); | |
noStroke(); | |
fill(surfColor); | |
drawSurface(); | |
popMatrix(); | |
// Signature | |
cam.beginHUD(); // Start drawing in 2D screen space | |
fill(255); | |
textFont(courier); | |
textAlign(CENTER, BOTTOM); | |
textSize(12*fac); | |
text("@infinitymathart • 2024", width / 2.0, height - 10*fac); // Center horizontally, 10px from the bottom | |
cam.endHUD(); // End 2D drawing | |
} | |
// Rendering the (opaque) surface | |
// | |
void drawSurface() | |
{ | |
beginShape(TRIANGLES); | |
fill(255); | |
noStroke(); | |
textureMode(NORMAL); | |
texture(tex); | |
// Drawing only 1/4 of the whole surface, since the rest is not visible | |
for (int i = 0; i < (numCols-1)/4; i++) | |
{ | |
float v1 = map(i, 0, numCols-1, 0, 1)+0.25; | |
float v2 = map(i + 1, 0, numCols-1, 0, 1)+0.25; | |
for (int j=0; j<numRows-1; j++) | |
{ | |
float u1 = map(j, 0, numRows-1, 0, 1); // u parameter for current segment | |
float u2 = map(j + 1, 0, numRows-1, 0, 1); // u parameter for next segment | |
float r1 = u1;//(4*u1)%1; | |
float r2 = u2;//(4*u2)%1; | |
float s1 = (4*v1)%1; | |
float s2 = (4*v2)%1; | |
PVector pos1 = s(u1, v1); | |
PVector pos2 = s(u1, v2); | |
PVector pos3 = s(u2, v2); | |
PVector pos4 = s(u2, v1); | |
PVector n1 = normal_vector(u1, v1); | |
PVector n2 = normal_vector(u1, v2); | |
PVector n3 = normal_vector(u2, v2); | |
PVector n4 = normal_vector(u2, v1); | |
vertex(pos1.x, pos1.y, pos1.z, r1, s1); | |
normal(n1.x, n1.y, n1.z); | |
vertex(pos2.x, pos2.y, pos2.z, r1, s2); | |
normal(n2.x, n2.y, n2.z); | |
vertex(pos3.x, pos3.y, pos3.z, r2, s2); | |
normal(n3.x, n3.y, n3.z); | |
vertex(pos1.x, pos1.y, pos1.z, r1, s1); | |
normal(n1.x, n1.y, n1.z); | |
vertex(pos3.x, pos3.y, pos3.z, r2, s2); | |
normal(n3.x, n3.y, n3.z); | |
vertex(pos4.x, pos4.y, pos4.z, r2, s1); | |
normal(n4.x, n4.y, n4.z); | |
} | |
} | |
endShape(); | |
} | |
// Partial derivative (numerical) with respect to u | |
// | |
PVector dsdu(float u, float v) | |
{ | |
final float h = 0.001; | |
return PVector.sub(s(u+h, v), s(u-h, v)).div(2*h); | |
} | |
// Partial derivative (numerical) with respect to v | |
// | |
PVector dsdv(float u, float v) | |
{ | |
final float h = 0.001; | |
return PVector.sub(s(u, v+h), s(u, v-h)).div(2*h); | |
} | |
// Normal vector at the point | |
// | |
PVector normal_vector(float u, float v) { | |
PVector du = dsdu(u, v); | |
PVector dv = dsdv(u, v); | |
PVector n = dv.cross(du); | |
return n.normalize(); | |
} | |
void mousePressed() | |
{ | |
float[] rotations = cam.getRotations(); | |
float[] look = cam.getLookAt(); | |
double dist = cam.getDistance(); | |
stamping = false; | |
println("..:"); | |
println(" cam = new PeasyCam(this, ", look[0]/fac, "*fac, ", look[1]/fac, "*fac, ", look[2]/fac, "*fac, ", dist/fac,"*fac);" ); | |
println(" cam.setRotations(", rotations[0], ", ", rotations[1], ", ", rotations[2],");\n\n"); | |
} | |
// ---------------------------------------------------------------------------------- | |
class thing | |
{ | |
int i, j; | |
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? | |
color co1; // = hsbToRgb(random(0,360),0.7,1.0); | |
color co2; // = hsbToRgb(random(0,360),0.7,1.0); | |
thing(float u_, float v_, int i_, int j_) | |
{ | |
i = i_; | |
j = j_; | |
u = u_; | |
v = v_; | |
int p = (int)(random(0,10)); | |
int q = (int)(random(0,10)); | |
c = "0123456789".charAt(p); | |
c1 = "0123456789".charAt(q); | |
co1 = hsbToRgb(360*p/10,0.7,1.0); | |
co2 = hsbToRgb(360*q/10,0.7,1.0); | |
flips = random(0,1) > 0.4; | |
offset = random(0, 1); | |
} | |
boolean flipping() | |
{ | |
if (flips) | |
{ | |
float tt = (t+offset)%1; | |
if ( (tt > 0.20 && tt < 0.40) || (tt > 0.60 && tt < 0.80 ) ) | |
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; | |
} | |
color colour() | |
{ | |
return monochromatic ? txtColor : (flipping() ? co2 : co1); | |
} | |
} | |
thing[] things; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment