Skip to content

Instantly share code, notes, and snippets.

@wschutzer
Created December 21, 2024 22:32
Show Gist options
  • Save wschutzer/5c259aa906656e6aba83b80a9859afdd to your computer and use it in GitHub Desktop.
Save wschutzer/5c259aa906656e6aba83b80a9859afdd to your computer and use it in GitHub Desktop.
Waves and digits on a torus - inside view, like a tunnel
/* 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