Skip to content

Instantly share code, notes, and snippets.

@wschutzer
Created September 26, 2024 03:46
Show Gist options
  • Save wschutzer/eb8c1ae70c5c611e662027e8f026dc57 to your computer and use it in GitHub Desktop.
Save wschutzer/eb8c1ae70c5c611e662027e8f026dc57 to your computer and use it in GitHub Desktop.
sketch_240925b_mobiusring
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 = 50; // Number of segments along the Möbius ring (higher means smoother)
float ringRadius = 200*fac; // Radius of the Möbius ring
float ringWidth = 60*fac; // Width of the rectangular cross-section
float ringThickness = 60*fac; // Thickness of the rectangular cross-section
int FPS = 60;
int total_frames = 60*FPS;
float t = 0;
PFont courier;
int n_chars = 10;
void settings()
{
size(frame_size,frame_size,P3D);
smooth(8);
}
void setup()
{
randomSeed(1337);
cam = new PeasyCam(this, 500*fac); // Initialize PeasyCam with an initial distance
cam.setRotations(-0.66542256, -0.94496423, -3.0573382);
cam.pan(40*fac,-10*fac);
courier = createFont("Courier New",24*fac,true);
// 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[2*n_chars*numSegments];
things_b = new thing[2*n_chars*numSegments];
things_l = new thing[2*n_chars*numSegments];
things_r = new thing[2*n_chars*numSegments];
float lm = -0.95;
for(int j=0; j<numSegments;j++)
{
int k = 2*n_chars*j;
for(int i=0; i<n_chars; i++)
{
things_t[k+i] = new thing(lm+i*0.2, 1);
things_t[k+i+n_chars] = new thing(lm+i*0.2, 0);
things_l[k+i] = new thing(lm+i*0.2, 1);
things_l[k+i+n_chars] = new thing(lm+i*0.2, 0);
things_r[k+i] = new thing(lm+i*0.2, 1);
things_r[k+i+n_chars] = new thing(lm+i*0.2, 0);
things_b[k+i] = new thing(lm+i*0.2, 1);
things_b[k+i+n_chars] = new thing(lm+i*0.2, 0);
}
}
}
void draw()
{
t = (1.0*(frameCount-1)/total_frames) % 1;
background(0);
pushMatrix();
noStroke();//stroke(128); // Draw lines around the rectangles
fill(0); //fill(150*lvl, 200*lvl, 255*lvl);
lights();
drawMobiusRing();
popMatrix();
// texts
pushMatrix();
float[] rot = cam.getRotations();
rotateX(rot[0]);
rotateY(rot[1]);
rotateZ(rot[2]);
translate(19,0,0);
fill(255);stroke(255);
textAlign(CENTER, CENTER);
textFont(courier);
textSize(12*fac);
text("@infinitymathart • 2024",0.0*width,0.3*height);
popMatrix();
if (recording)
{
saveFrame("/Volumes/waldeck/frames/t/frame_####.png");
println(frameCount,"/",total_frames);
if (frameCount == total_frames)
{
stop();
exit();
}
}
}
// 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);
}
// 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);
}
// 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 {
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)
{
// 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);
// Debugging: Draw green dots at the center of each face and red dots at the end of normals
//drawFaceNormals(this);
/*pushStyle();
stroke(255);
strokeWeight(6);
PVector v = s_right_face(this, cos(TAU*t), sin(TAU*t));
point(v.x, v.y, v.z);
popStyle(); */
// Text things on the faces of the ring
pushStyle();
textAlign(LEFT, TOP); // Center the text
textSize(10*fac); // Adjust text size as needed
fill(255); // Text color
//drawTextOnTopFace(this, "TOP FACE", -1, 1);
/*String st = "3•14159257";
String st1 = "2•71828182";
float lm = -0.95;
for(int i=0;i<st.length();i++)
drawTextOnTopFace(this, ""+st.charAt(i), lm+i*0.2, 1);
for(int i=0;i<st.length();i++)
drawTextOnTopFace(this, ""+st.charAt(i), lm+i*0.2, 0);
for(int i=0;i<st.length();i++)
drawTextOnBottomFace(this, ""+st.charAt(i), lm+i*0.2, 1);
for(int i=0;i<st.length();i++)
drawTextOnBottomFace(this, ""+st.charAt(i), lm+i*0.2, 0);
for(int i=0;i<st.length();i++)
drawTextOnRightFace(this, ""+st1.charAt(i), lm+i*0.2, 1);
for(int i=0;i<st.length();i++)
drawTextOnRightFace(this, ""+st1.charAt(i), lm+i*0.2, 0);
for(int i=0;i<st.length();i++)
drawTextOnLeftFace(this, ""+st1.charAt(i), lm+i*0.2, 1);
for(int i=0;i<st.length();i++)
drawTextOnLeftFace(this, ""+st1.charAt(i), lm+i*0.2, 0); */
for(int i=0;i<2*n_chars;i++)
{
thing tg = things_t[2*9*index+i];
if (tg.flipping()) fill(200); else fill(255);
drawTextOnTopFace(this, tg.get(), tg.u, tg.v);
tg = things_b[2*n_chars*index+i];
if (tg.flipping()) fill(200); else fill(255);
drawTextOnBottomFace(this, tg.get(), tg.u, tg.v);
tg = things_l[2*n_chars*index+i];
if (tg.flipping()) fill(200); else fill(255);
drawTextOnLeftFace(this, tg.get(), tg.u, tg.v);
tg = things_r[2*n_chars*index+i];
if (tg.flipping()) fill(200); else fill(255);
drawTextOnRightFace(this, tg.get(), tg.u, tg.v);
}
popStyle();
}
}
// 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;
}
// 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;
}
// 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;
}
// 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);
// Draw the text on the top face
drawTextOnFace(center, basisChangeMatrix, label);
}
// General function to draw text on a face using basis transformation
void drawTextOnFace(PVector position, PMatrix3D basisChangeMatrix, String txt)
{
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
text(txt, 0, 0); // Draw text at the origin
popMatrix(); // Restore the original transformation matrix
}
// 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);
// Draw the text on the bottom face
drawTextOnFace(center, basisChangeMatrix, label);
}
// 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);
// Draw the text on the left face
drawTextOnFace(center, basisChangeMatrix, label);
}
// 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);
// Draw the text on the right face
drawTextOnFace(center, basisChangeMatrix, label);
}
void mousePressed()
{
float[] position = cam.getPosition();
println("Position (", position[0], ", ", position[1], ", ", position[2], ")");
float[] rotations = cam.getRotations();
println("Rotations: ", rotations[0], ", ", rotations[1], ", ", rotations[2]);
float[] look = cam.getLookAt();
println("Lootat: (", look[0], ", ", look[1], ", ", look[2], ")" );
}
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);
}
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;
}
}
thing[] things_t;
thing[] things_b;
thing[] things_l;
thing[] things_r;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment