Last active
October 24, 2024 14:19
-
-
Save wschutzer/74e5f09d48026eaf9767407669ba9600 to your computer and use it in GitHub Desktop.
Canyon 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
// Canyon 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/ | |
// OpenSimplexNoise.pde can be found here: https://gist.github.com/wschutzer/4be8f14d12fc3541024f796ee7fb6fe2 | |
// See the license information at the end of this file. | |
final boolean recording = false; | |
final boolean reverse = false; | |
PShape surf; | |
PGraphics tex; | |
PFont mono; | |
float fac = 1; | |
float R = 1000; | |
int tube_radius = 200; | |
final int v_divs = 120; | |
final int u_divs = 600; | |
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 = 40; | |
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) | |
); | |
OpenSimplexNoise noise; | |
////////////////////////////////////////////////////////////////////////////// | |
// 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 = 4; | |
final int numFrames = 4*120; | |
final float shutterAngle = 1.2; | |
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) | |
{ | |
final float nsu = 10.0; | |
final float nsv = 4.0; | |
final float w = 2*TAU; | |
final float b = 3*PI/4; | |
float rs = r*(1+0.3*(float)noise.eval(nsu*cos(w*u), nsu*sin(w*u), nsv*cos(b*v), nsv*sin(b*v))); | |
float d1 = rs*cos(b*v+PI/8); | |
float d2 = rs*sin(b*v+PI/8); | |
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 dsdv(u,v,r).cross( dsdu(u,v,r) ); | |
} | |
// Compute the surface geometry | |
PShape createSurface() | |
{ | |
noFill(); | |
noStroke(); | |
PShape s = createShape(); | |
s.beginShape(TRIANGLES); | |
s.fill(255); | |
s.textureMode(NORMAL); | |
s.texture(tex); | |
for (int i=0; i<=u_divs; i++) | |
{ | |
float u1 = map(i, 0, u_divs, 0, 1); | |
float u2 = map(i+1, 0, u_divs, 0, 1); | |
for (int j=0; j<v_divs; j++) | |
{ | |
float v = map(j, 0, v_divs, 0, 1); | |
float vn = map(j+1, 0, v_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); | |
PVector v3 = s(u1, vn, tube_radius); | |
PVector n3 = normal_s(u1, vn, tube_radius); | |
PVector v4 = s(u2, vn, tube_radius); | |
PVector n4 = normal_s(u2, vn, 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.normal(n4.x,n4.y,n4.z); | |
s.vertex(v4.x,v4.y,v4.z, u2, vn); | |
s.normal(n1.x,n1.y,n1.z); | |
s.vertex(v1.x, v1.y, v1.z, u1, v); | |
s.normal(n4.x,n4.y,n4.z); | |
s.vertex(v4.x,v4.y,v4.z, u2, vn); | |
s.normal(n3.x,n3.y,n3.z); | |
s.vertex(v3.x,v3.y,v3.z, u1, vn); | |
} | |
} | |
s.endShape(); | |
return s; | |
} | |
// Draw the stored surface | |
void drawSurface() | |
{ | |
surf.setTextureMode(NORMAL); | |
surf.setFill(color(255)); | |
surf.resetMatrix(); | |
surf.setTexture(tex); | |
pushMatrix(); | |
rotateZ(-PI*t); | |
shape(surf); // This is a lot faster than the immediate mode | |
popMatrix(); | |
} | |
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); | |
noise = new OpenSimplexNoise(1337); | |
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_() | |
{ | |
drawTexture(); | |
background(reverse ? fg_color : bg_color); | |
lights(); | |
//lightFalloff(2.0, 0.005, 0.001); | |
directionalLight(200, 200, 200, 0, -1, -1); | |
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); | |
rotateZ(HALF_PI); | |
rotateX(-HALF_PI); | |
translate(-fac*20-R-0.4*tube_radius, -0.4*R, 0); | |
drawSurface(); | |
pop(); | |
drawSignature(); | |
} | |
void drawTexture() | |
{ | |
int s_len = int(tex.width/(3*num_stripes)); | |
tex.beginDraw(); | |
tex.background(reverse ? 255 : 0); | |
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+= 3*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