mathr / blog / #

Log-polar graph paper

warped Tetris

Code:

/*
gcc -std=c99 -Wall -pedantic -Wextra graph-paper.c -o graph-paper -lGLEW -lGL -lGLU -lglut
./graph-paper
for i in *.pgm
do
  pnmtopng -force -interlace -compression 9 -phys 11811 11811 1 <$i >${i%pgm}png
done
*/
#include <stdio.h>
#include <string.h>

#include <GL/glew.h>
#include <GL/glut.h>

#define GLSL(s) #s

static const int width = 2480;
static const int height = 3508;
static const float size = 4.0;

static int gcd(int x, int y) {
  if (y == 0) { return x; }
  else { return gcd(y, x % y); }
}

static const char *src = GLSL(

  uniform vec3 twist;

  vec2 clog(vec2 x) {
    return vec2(log(length(x)), atan2(x.y, x.x)) * 0.15915494309189535;
  }
  
  void main() {
    vec2 p = gl_TexCoord[0].xy;
    p *= mat2(0.0, -1.0, 1.0, 0.0);
    vec2 q = clog(p);
    float a = atan2(twist.y, twist.x);
    float h = length(vec2(twist.x, twist.y));
    q *= mat2(cos(a), sin(a), -sin(a), cos(a)) * h;
    float d = length(vec4(dFdx(q), dFdy(q)));
    float l = ceil(-log2(d));
    float f = pow(2.0, l + log2(d)) - 1.0;
    l -= 6.0;
    float o[2];
    for (int i = 0; i < 2; ++i) {
      l += 1.0;
      vec2 u = q * pow(2.0, l);
      u *= twist.z;
      u -= floor(u);
      float r = min
        ( min(length(u), length(u - vec2(1.0, 0.0)))
        , min(length(u - vec2(0.0, 1.0)), length(u - vec2(1.0, 1.0)))
        );
      float c = clamp(1.5 * r / (pow(2.0, l) * d), 0.0, 1.0);
      vec2 v = q * pow(2.0, l - 2.0);
      v *= twist.z;
      v -= floor(v);
      float s = min(min(v.x, v.y), min(1.0 - v.x, 1.0 - v.y));
      float k = clamp(0.75 + 0.25 * s / (pow(2.0, l - 2.0) * d), 0.0, 1.0);
      o[i] = c * k;
    }
    gl_FragColor = vec4(vec3(mix(o[1], o[0], f)), 1.0);
  }

);

int main(int argc, char **argv) {

  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
  glutCreateWindow("graphpaper");
  glewInit();

  GLint success;
  int prog = glCreateProgram();
  int frag = glCreateShader(GL_FRAGMENT_SHADER);
  glShaderSource(frag, 1, (const GLchar **) &src, 0);
  glCompileShader(frag);
  glAttachShader(prog, frag);
  glLinkProgram(prog);
  glGetProgramiv(prog, GL_LINK_STATUS, &success);
  if (!success) exit(1);
  glUseProgram(prog);
  GLuint utwist = glGetUniformLocation(prog, "twist");

  GLuint tex;
  glGenTextures(1, &tex);
  glBindTexture(GL_TEXTURE_2D, tex);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 4096, 4096, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
  glBindTexture(GL_TEXTURE_2D, 0);

  GLuint fbo;
  glGenFramebuffers(1, &fbo);
  glBindFramebuffer(GL_FRAMEBUFFER, fbo);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);

  glViewport(0, 0, width, height);
  glLoadIdentity();
  gluOrtho2D(0, 1, 1, 0);

  unsigned char *buffer = malloc(width * height);
  for (int x = 1; x <= 5; ++x) {
    for (int y = 0; y <= x; ++y) {
      if ((x > 1 && y == 0) || (y > 0 && gcd(x, y) != 1)) { continue; }
      for (int n = 5; n <= 8; ++n) {
        glUniform3f(utwist, x, y, n / 8.0);
        glBegin(GL_QUADS); {
          float u = size / 2.0;
          float v = size * (height - width / 2.0) / width;
          glTexCoord2f( u, -v); glVertex2f(1, 0);
          glTexCoord2f( u,  u); glVertex2f(1, 1);
          glTexCoord2f(-u,  u); glVertex2f(0, 1);
          glTexCoord2f(-u, -v); glVertex2f(0, 0);
        } glEnd();
        glReadPixels(0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, buffer);
        char fname[200];
        snprintf(fname, 100, "graphpaper-%d-%d-%d.pgm", x, y, n);
        FILE *f = fopen(fname, "wb");
        fputs("P5\n2480 3508\n255\n", f);
        fflush(f);
        fwrite(buffer, width * height, 1, f);
        fflush(f);
        fclose(f);
      }
    }
  }

  free(buffer);
  glDeleteFramebuffers(1, &fbo);
  glDeleteTextures(1, &tex);
  glDeleteShader(frag);
  glDeleteProgram(prog);
  glutReportErrors();
  return 0;
}

Output:

This is an excavation from my archives, around 2013 or so. Print out your favourites and enjoy doodling!