Blob Blame Raw

#include <ctype.h>
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include <helianthus.h>


#define SPLITPOINTS 17
double pointsArc[SPLITPOINTS][3];
double pointsQua[SPLITPOINTS][3];
double pointsCub[SPLITPOINTS][4];

double ax =  0;
double ay = 10;
double mx, my;

int randomAngle = 1;
int rnd = 0;

typedef struct {
  double mat[9];
  double ox, oy;
  double curr[3];
} Painter;


const char *letters[][2] = {
  { "box", "L000-800-880-080-000-008-808-888-088-008 800-808 880-888 080-088"
           " 004-804-884-084z 040-840-848-048z 400-480-488-408z" },
  { "star", "L440-533-844-535-448-335-044-333-440-553-844-555-448-355-044-353z"
            " 333-404-535 335-404-533 353-484-555 355-484-553" },
  //{ "cube", "L000-800-880-080-000-008-808-888-088-008 800-808 880-888 080-088" },
  //{ "cub", "L000-800-880-080-000-008-808-888-088-008 800-808 880-888 080-088" },
  { "a", "L000-448-800 080-448-880 224-624-664-264z" },
  { "b", "L000-008-608-A808-806-804-604-L004-064-A084-086-088-068-L008 A806-886-086 608-668-068 604-664-064 600-660-060 802-882-082 604-804-802-800-600-L000-060-A080-082-084-064-L004" },
  { "c", "L800-400-A000-004-008-408-L808-848-A888-884-880-840-L800 A004-084-884 400-440-840 408-448-848" },
  { "d", "L008-000-400-A800-804-808-408-L008-048-A088-084-080-040-L000 A804-884-084 400-440-040 408-448-048" },
  { "e", "L000-800-880-080-000-008-808-888-088-008 004-804-884-084z" },
  { "f", "L000-008-808-888-088-008 004-804-884-084z" },
  { "g", "L800-804-404-A444-844-L804-A800-400-000-004-008-408-L808-848-A888-884-880-840-800-804 A004-084-884 400-440-840 408-448-848" },
  { "h", "L004-804-884-084z 000-008 800-808 880-888 080-088" },
  { "i", "L440-448 220-260-660-620z 228-268-668-628z" },
  { "j", "A848-888-488-088-048-008-408-808-848-L844-A840-440-040-044-004-404-804-844-884-484-084-044 404-400-440-480-484" },
  { "k", "L000-008 800-004-808 080-004-088 880-004-888" },
  { "l", "L008-000-800-880-080-000" },
  { "m", "L000-228-400-628-800 080-268-480-668-880 040-268-440-668-840-628-440-228z" },
  { "n", "L000-008-800-808 008-080-088 008-880-888" },
  { "o", "A044-040-440-840-844-848-448-048-044-004-404-804-844-884-484-084-044 404-400-440-480-484-488-448-408-404" },
  { "p", "L000-008-608-A808-806-804-604-L004-064-A084-086-088-068-L008 A806-886-086 608-668-068 604-664-064" },
  { "q", "A044-040-440-840-844-848-448-048-044-004-404-804-844-884-484-084-044 404-400-440-480-484-488-448-408-404 L444-840-A880-480z" },
  { "r", "L000-008-608-A808-806-804-604-L004-064-A084-086-088-068-L008 A806-886-086 608-668-068 604-664-064 604-804-802-L800-A880-080-L082-A084-064 802-882-082" },
  { "s", "A424-404-204-004-006-008-208-L808-868-A888-886-884-864-844-644 L208-248-A268-468-L868 006-046-A086-486-L886 A248-048-046-044-244 468-488-486-484-464"
         " 464-484-684-884-882-880-680-L080-020-A000-002-004-024-L044-244 680-640-A620-420-L020 882-842-A802-402-L002 A640-840-842-844-644 420-400-402-404-424"
         " 244-224-424-624-644-664-464-264-244" },
  { "t", "L008-808-888-088z 440-448" },
  { "u", "A848-888-488-088-048-008-408-808-848-L844-A840-440-040-044-L048 408-404-A400-440-480-484-L488 A044-004-404-804-844-884-484-084-044" },
  { "v", "L008-440-808 088-440-888" },
  { "w", "L008-220-408-620-808 088-260-488-660-888 048-260-448-660-848-620-448-220z" },
  { "x", "L000-888 800-088 880-008 080-808" },
  { "y", "L000-888 444-088 444-008 444-808" },
  { "z", "L000-800-880-080-000-888-088-008-808-888" },

  { "0", "A044-040-440-840-844-848-448-048-044-004-404-804-844-884-484-084-044 404-400-440-480-484-488-448-408-404 L040-848" },
  { "1", "L440-448-226 220-260-660-620z" },
  { "2", "L000-002-A004-204-404-604-804-806-808-608-L008-068-A088-086-084-064-044-024-004-002-L000-800-880-080-000"
         " 608-648-A668-468-L068 604-644-A664-464-L064 806-846-A886-486-L086 "
         " A648-848-846-844-644 468-488-486-484-464 " },
  { "3", "L008-608-A808-806-804-604-L004-064-A084-086-088-068-L008 A806-886-086 608-668-068 604-664-064 600-660-060 802-882-082 604-804-802-800-600-L000-060-A080-082-084-064-L004" },
  { "4", "L004-804-884-084z 004-008 800-808 884-888 084-088" },
  { "5", "L008-006-A004-204-404-604-804-802-800-600-L000-060-A080-082-084-064-044-024-004-006-L008-808-888-088-008"
         " 600-640-A660-460-L060 604-644-A664-464-L064 802-842-A882-482-L082 "
         " A640-840-842-844-644 460-480-482-484-464 " },
  { "6", "L042-044-C048-088-488-A888-848-808-408-C008-048-044-048-048-448-L848 408-488"
         " A042-040-240-L640-A840-842-844-644-L244-A044-042-002-402-802-842-882-482-082-042"
         "  402-400-420-L460-A480-482-484-464-L424-A404-402 244-224-424-624-644-664-464-264-244 240-220-420-620-640-660-460-260-240 "},
  { "7", "L000-888-088-008-808-888 224-264-664-624z" },
  { "8", "A244-044-042-040-240-L640-A840-842-844-644-L244-A044-046-048-248-L648-A848-846-844-644"
         " 424-404-402-400-420-L460-A480-482-484-464-L424-A404-406-408-428-L468-A488-486-484-464"
         " 042-002-402-802-842-882-482-082-042 046-006-406-806-846-886-486-086-046"
         " 240-220-420-620-640-660-460-260-240 244-224-424-624-644-664-464-264-244 248-228-428-628-648-668-468-268-248" },
  { "9", "L846-844-C840-880-480-A080-040-000-400-C800-840-844-840-840-440-L040 400-480"
         " A046-048-248-L648-A848-846-844-644-L244-A044-046-006-406-806-846-886-486-086-046"
         "  406-408-428-L468-A488-486-484-464-L424-A404-406 244-224-424-624-644-664-464-264-244 248-228-428-628-648-668-468-268-248 "},

  { ",", "P442 L442-220" },
  { "...", "P002 402 802 042 442 842 882 482 082" },
  { "..", "P222 622 662 262" },
  { ".", "L331-531-551-351-331-333-533-553-353-333 531-533 551-553 351-353 " },
  //{ ".", "P442" },
  { "?", "A442-444-424-404-604-804-806-808-608-L008-068-A088-086-084-064-044-244-444-442"
         " L608-648-A668-468-L068 806-846-A886-486-L086"
         " A648-848-846-844-644-444-442 468-488-486-484-464-444-442 424-624-644-664-464-264-244 P440" },
  { "!", "A442-244-246-248-448-648-646-644-442 A442-424-426-428-448-468-466-464-442 246-226-426-626-646-666-466-266-246 P440" },
  { ":", "P222 622 662 262 226 626 666 266" },
  { ";", "P222 622 662 262 226 626 666 266 L222-240 L622-420 L662-640 L262-460" },
  { "\"","P226 626 666 266 L226-244 L626-424 L666-644 L266-464" },
  { "\'","P446 L446-224" },
  { "`", "P446 L446-664" },
  { "<", "L800-044-880 808-044-888" },
  { ">", "L000-844-080 008-844-088" },
  { "=", "L222-262-662-622z 226-266-666-626z" },
  { "-", "L224-264-664-624z" },
  { "+", "L244-644 424-464 442-446" },
  { "*", "L244-644 424-464 442-446 222-666 622-266 662-226 262-626" },
  { "/", "L000-888" },
  { "\\","L800-088" },
  { "^", "L226-448-666 626-448-266" },
  { "(", "A640-680-684-688-648-608-604-600-640-240-244-248-648 604-204-244-284-684" },
  { ")", "A240-280-284-288-248-208-204-200-240-640-644-648-248 204-604-644-684-284" },
  { "[", "L640-680-684-688-648-608-604-600-640-240-244-248-648 604-204-244-284-684" },
  { "]", "L240-280-284-288-248-208-204-200-240-640-644-648-248 204-604-644-684-284" },
  { "{", "A640-440-442-444-244-444-446-448-648 604-404-424-444-244-444-464-484-684"
         " 464-466-446-426-424-422-442-462-464 L684-688-648-608-604-600-640-680-684 " },
  { "}", "A240-440-442-444-644-444-446-448-248 204-404-424-444-644-444-464-484-284"
         " 464-466-446-426-424-422-442-462-464 L284-288-248-208-204-200-240-280-284 " },
  { "_", "L000-800-880-080z" },
  { "||","L220-228 620-628 660-668 260-268" },
  { "|", "L440-448" },
  { "#", "L220-228 620-628 660-668 260-268 202-282 206-286 606-686 602-682 022-822 062-862 066-866 026-826" },
  { "@", "A244-242-442-642-644-646-446-246-244-224-424-624-644-664-464-264-244 424-422-442-462-464-466-446-426-424"
         " 440-480-484-084-044-040-440-L840-800-400-440"
         " C[8.00][4.00][0.00]-[8.00][6.31][0.00]-[4.52][8.33][1.78]-[4.00][8.00][4.00]"
                             "-[3.83][7.89][4.73]-[3.89][7.53][5.53]-[4.12][7.03][6.06]"
                             "-[4.36][6.53][6.59]-[4.78][5.83][6.91]-[5.18][5.18][6.89]"
                             "-[5.57][4.55][6.86]-[5.98][3.88][6.51]-[6.20][3.43][5.96]"
                             "-[6.41][3.00][5.44]-[6.47][2.71][4.68]-[6.31][2.67][4.00]"
                             "-[6.17][2.62][3.38]-[5.82][2.78][2.73]-[5.37][3.06][2.36]"
                             "-[4.98][3.30][2.04]-[4.45][3.68][1.88]-[4.00][4.00][2.00]"
                             "-[4.32][3.55][1.88]-[4.70][3.02][2.04]-[4.94][2.63][2.36]"
                             "-[5.22][2.18][2.73]-[5.38][1.83][3.38]-[5.33][1.69][4.00]"
                             "-[5.29][1.53][4.68]-[5.00][1.59][5.44]-[4.57][1.80][5.96]"
                             "-[4.12][2.02][6.51]-[3.45][2.43][6.86]-[2.82][2.82][6.89]"
                             "-[2.17][3.22][6.91]-[1.47][3.64][6.59]-[0.97][3.88][6.06]"
                             "-[0.47][4.11][5.53]-[0.11][4.17][4.73]-[0.00][4.00][4.00]"
                             "-[-0.33][3.48][1.78]-[1.69][0.00][0.00]-[4.00][0.00][0.00]"
          " [5.18][5.18][6.89]-[4.50][4.50][7.44]-[3.50][3.50][7.44]-[2.82][2.82][6.89]" },
  { "&", " A046-048-248-L648-A848-846-844-644-L244-A044-046-006-406-806-846-886-486-086-046"
          " 406-408-428-L468-A488-486-484-464-L424-A404-406 244-224-424-624-644-664-464-264-244 248-228-428-628-648-668-468-268-248"
         " A424-404-204-004-002-000-200-L800-860-A880-882-884-864-844-644"
         " L200-240-A260-460-L860 002-042-A082-482-L882 A240-040-042-044-244 460-480-482-484-464" },
  { "%", "A066-064-264-464-466-468-268-068-066-046-246-446-466-486-286-086-066 246-244-264-284-286-288-268-248-246"
         " 422-420-620-820-822-824-624-424-422-402-602-802-822-842-642-442-422 602-600-620-640-642-644-624-604-602"
         " L000-888" },
  { "~", "C244-254-255-355-A455-444-433-533-C633-634-644 A244-224-424-624-644-664-464-264-244" },
  { "$", "A424-404-204-004-006-008-208-L808-868-A888-886-884-864-844-644 L208-248-A268-468-L868 006-046-A086-486-L886 A248-048-046-044-244 468-488-486-484-464"
         " 464-484-684-884-882-880-680-L080-020-A000-002-004-024-L044-244 680-640-A620-420-L020 882-842-A802-402-L002 A640-840-842-844-644 420-400-402-404-424"
         " 244-224-424-624-644-664-464-264-244 L24[-2]-24[10] L64[-2]-64[10] L42[-2]-42[10] L46[-2]-46[10] " },
};
int lettersCount = sizeof(letters)/sizeof(letters[0]);

const char *textToDraw = NULL;
double boxStep = 64;
double boxSize = 0.5;
int renderToFiles = 0;


void vec3set(double *v, double x, double y, double z)
  { v[0] = x, v[1] = y, v[2] = z; }
void vec3cpy(double *v, const double *vv)
  { memcpy(v, vv, sizeof(*v)*3); }
void vec3add(double *v, const double *vv)
  { v[0] += vv[0]; v[1] += vv[1]; v[2] += vv[2]; }
void vec3trans(double *v, const double *m) {
  vec3set(v, v[0]*m[0] + v[1]*m[3] + v[2]*m[6],
             v[0]*m[1] + v[1]*m[4] + v[2]*m[7],
             v[0]*m[2] + v[1]*m[5] + v[2]*m[8] );
}

void mat3cpy(double *m, double *mm)
  { memcpy(m, mm, sizeof(*m)*9); }
void mat3one(double *m) {
  m[1] = m[2] = m[3] = m[5] = m[6] = m[7] = 0;
  m[0] = m[4] = m[8] = 1;
}
void mat3trans(double *m, const double *mm) {
  vec3trans(m+0, mm);
  vec3trans(m+3, mm);
  vec3trans(m+6, mm);
}
void mat3rotX(double *m, double a) {
  rotateVector(&m[1], &m[2], a);
  rotateVector(&m[4], &m[5], a);
  rotateVector(&m[7], &m[8], a);
}
void mat3rotY(double *m, double a) {
  rotateVector(&m[2], &m[0], a);
  rotateVector(&m[5], &m[3], a);
  rotateVector(&m[8], &m[6], a);
}
void mat3rotZ(double *m, double a) {
  rotateVector(&m[0], &m[1], a);
  rotateVector(&m[3], &m[4], a);
  rotateVector(&m[6], &m[7], a);
}


void painterInternalMoveTo(Painter *p, double x, double y, double z) {
  double xx = p->mat[0]*x + p->mat[3]*y + p->mat[6]*z + p->ox;
  double yy = p->mat[1]*x + p->mat[4]*y + p->mat[7]*z + p->oy;
  moveTo(xx, yy);
}

void painterInternalLineTo(Painter *p, double x, double y, double z) {
  double xx = p->mat[0]*x + p->mat[3]*y + p->mat[6]*z + p->ox;
  double yy = p->mat[1]*x + p->mat[4]*y + p->mat[7]*z + p->oy;
  lineTo(xx, yy);
}

void painterInternalPointTo(Painter *p, double x, double y, double z) {
  double xx = p->mat[0]*x + p->mat[3]*y + p->mat[6]*z + p->ox;
  double yy = p->mat[1]*x + p->mat[4]*y + p->mat[7]*z + p->oy;
  saveState();
  translate(xx, yy);
  zoom(3);
  point(0, 0);
  restoreState();
}

void painterFinish(Painter *p)
  { strokePath(); }

void painterMoveTo(Painter *p, double *pos) {
  painterFinish(p);
  painterInternalMoveTo(p, pos[0], pos[1], pos[2]);
  vec3cpy(p->curr, pos);
}

void painterLineTo(Painter *p, double *pos) {
  painterInternalLineTo(p, pos[0], pos[1], pos[2]);
  vec3cpy(p->curr, pos);
}

void painterPointTo(Painter *p, double *pos) {
  painterFinish(p);
  painterInternalPointTo(p, pos[0], pos[1], pos[2]);
  vec3cpy(p->curr, pos);
}

void painterArcTo(Painter *p, double *pos1, double *pos2) {
  double *pos0 = p->curr;
  for(int i = 1; i < SPLITPOINTS; ++i)
    painterInternalLineTo( p,
      pointsArc[i][0]*pos0[0] + pointsArc[i][1]*pos1[0] + pointsArc[i][2]*pos2[0],
      pointsArc[i][0]*pos0[1] + pointsArc[i][1]*pos1[1] + pointsArc[i][2]*pos2[1],
      pointsArc[i][0]*pos0[2] + pointsArc[i][1]*pos1[2] + pointsArc[i][2]*pos2[2] );
  vec3cpy(p->curr, pos2);
}

void painterQuadraticTo(Painter *p, double *pos1, double *pos2) {
  double *pos0 = p->curr;
  for(int i = 1; i < SPLITPOINTS; ++i)
    painterInternalLineTo( p,
      pointsQua[i][0]*pos0[0] + pointsQua[i][1]*pos1[0] + pointsQua[i][2]*pos2[0],
      pointsQua[i][0]*pos0[1] + pointsQua[i][1]*pos1[1] + pointsQua[i][2]*pos2[1],
      pointsQua[i][0]*pos0[2] + pointsQua[i][1]*pos1[2] + pointsQua[i][2]*pos2[2] );
  vec3cpy(p->curr, pos2);
}

void painterCubicTo(Painter *p, double *pos1, double *pos2, double *pos3) {
  double *pos0 = p->curr;
  for(int i = 1; i < SPLITPOINTS; ++i)
    painterInternalLineTo( p,
      pointsCub[i][0]*pos0[0] + pointsCub[i][1]*pos1[0] + pointsCub[i][2]*pos2[0] + pointsCub[i][3]*pos3[0],
      pointsCub[i][0]*pos0[1] + pointsCub[i][1]*pos1[1] + pointsCub[i][2]*pos2[1] + pointsCub[i][3]*pos3[1],
      pointsCub[i][0]*pos0[2] + pointsCub[i][1]*pos1[2] + pointsCub[i][2]*pos2[2] + pointsCub[i][3]*pos3[2] );
  vec3cpy(p->curr, pos3);
}

void painterClose(Painter *p)
  { closePath(); }


void drawLetter(Painter *p, const char *cmd) {
  saveState();
  noFill();
  double mat[11];
  mat3cpy(mat, p->mat);
  mat[9] = p->ox; mat[10] = p->oy;
  if (randomAngle) {
    double m[9], v[3];
    vec3set(v, -4, -4, -4);
    vec3trans(v, p->mat);

    mat3one(m);
    mat3rotZ(m, randomFloat()*360);
    mat3trans(m, p->mat);
    mat3cpy(p->mat, m);

    vec3set(m, -4, -4, -4);
    vec3trans(m, p->mat);
    p->ox += m[0] - v[0], p->oy += m[1] - v[1];
  }

  char mode = 'L';

  int begin = 1;
  int relative = 0;
  int cnt = 0;
  double coords[3][3] = {};

  for(const char *c = cmd; 1; ++c) {
    switch(*c) {
    case 'L':
    case 'A':
    case 'Q':
    case 'C':
    case 'P':
      mode = *c;
      relative = 0;
      cnt = 0;
      break;
    case 'l':
    case 'a':
    case 'q':
    case 'c':
    case 'p':
      mode = toupper(*c);
      relative = 1;
      cnt = 0;
      break;
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      coords[0][0] = coords[0][1];
      coords[0][1] = coords[0][2];
      coords[0][2] = *c - '0';
      break;
    case '[':
      coords[0][0] = coords[0][1];
      coords[0][1] = coords[0][2];
      coords[0][2] = atof(c+1);
      while(*c != ']' && c[1]) ++c;
      break;
    case '-':
    case ' ':
    case 'z':
    case 'Z':
    case 0:
      if (relative) vec3add(coords[0], coords[cnt+1]);

      if (mode == 'P') {
        painterPointTo(p, coords[0]);
        begin = 1; cnt = 0;
      } else
      if (begin) {
        painterMoveTo(p, coords[0]);
        begin = 0;
      } else
      if (mode == 'L') {
        painterLineTo(p, coords[0]);
      } else
      if (mode == 'A') {
        if (cnt) { painterArcTo(p, coords[1], coords[0]); cnt = 0; }
            else { vec3cpy(coords[1], coords[0]); ++cnt; }
      } else
      if (mode == 'Q') {
        if (cnt) { painterQuadraticTo(p, coords[1], coords[0]); cnt = 0; }
            else { vec3cpy(coords[1], coords[0]); ++cnt; }
      } else
      if (mode == 'C') {
        if (cnt > 1) { painterCubicTo(p, coords[2], coords[1], coords[0]); cnt = 0; }
                else { vec3cpy(coords[2], coords[1]); vec3cpy(coords[1], coords[0]); ++cnt; }
      }

      if (*c == ' ' || *c == 0) {
        painterFinish(p);
        begin = 1; cnt = 0;
      } else
      if (*c == 'z' || *c == 'Z') {
        painterClose(p);
        begin = 1; cnt = 0;
      }
      break;
    }
    if (!*c) break;
  }

  mat3cpy(p->mat, mat);
  p->ox = mat[9]; p->oy = mat[10];
  restoreState();
}


void drawStr(
  Painter *p, double dx, double dy,
  const char *str, char stopChar,
  int *rows, int *cols )
{
  if (randomAngle) srand(rnd);
  double ox, oy;
  if (p) { ox = p->ox; oy = p->oy; }

  int row = 0, col = 0, maxRow = -1, maxCol = -1;
  for(const char *c = str; *c && *c != stopChar; ++c) {
    if (*c == '\n')
      { ++row; col = 0; continue; }

    for(int i = 0; i < lettersCount; ++i) {
      int equals = 0;
      for(const char *ca = c, *cb = letters[i][0]; 1; ++ca, ++cb) {
        if (!*cb) { equals = 1; c += cb-letters[i][0]-1; break; }
        if (!*ca || *ca == stopChar || tolower(*ca) != tolower(*cb)) break;
      }
      if (equals) {
        if (maxCol < col) maxCol = col;
        if (maxRow < row) maxRow = row;
        if (p) {
          p->ox = ox + col*dx;
          p->oy = oy + row*dy;
          drawLetter(p, letters[i][1]);
        }
        break;
      }
    }
    ++col;
  }
  if (p) { p->ox = ox; p->oy = oy; }
  if (rows) *rows = maxRow + 1;
  if (cols) *cols = maxCol + 1;
}


void drawAllLetters(Painter *p) {
  if (randomAngle) srand(rnd);
  double ox = p->ox, oy = p->oy;
  int lcnt = (int)ceil(sqrt(lettersCount) - 1e-6);
  for(int i = 0; i < lettersCount; ++i) {
    int r = i/lcnt, c = i%lcnt;
    p->ox = (c - 0.5*(lcnt - 1))*boxStep + ox;
    p->oy = (r - 0.5*(lcnt - 1))*boxStep + oy;
    drawLetter(p, letters[i][1]);
  }
  p->ox = ox; p->oy = oy;
}


void initCurvePoints() {
  for(int i = 0; i < SPLITPOINTS; ++i) {
    double l = i/(SPLITPOINTS-1.0), k = 1-l;
    double a = l*PI/2, s = sin(a), c = cos(a);

    pointsQua[i][0] = k*k;
    pointsQua[i][1] = 2*k*l;
    pointsQua[i][2] = l*l;

    pointsCub[i][0] = k*k*k;
    pointsCub[i][1] = 3*k*k*l;
    pointsCub[i][2] = 3*l*l*k;
    pointsCub[i][3] = l*l*l;

    pointsArc[i][0] = 1-s;
    pointsArc[i][1] = s+c-1;
    pointsArc[i][2] = 1-c;
  }
}




void render(int framesCount) {
  saveState();

  int rows, cols;
  drawStr(NULL, 0, 0, textToDraw, 0, &rows, &cols);
  int w = (int)ceil(cols*boxStep - 1e-5);
  int h = (int)ceil(rows*boxStep - 1e-5);
  Framebuffer fb = createFramebuffer(w, h);
  target(fb);

  for(int i = 0; i < framesCount; ++i) {
    clear();
    double step = boxStep*boxSize/8.0;
    double mat[9] = {step, 0, 0, 0, 0, step, 0, -step, 0};
    mat3rotY(mat, 360.0*i/framesCount);
    mat3rotX(mat, 10);
    double vec[3] = {-4, -4, -4};
    vec3trans(vec, mat);

    Painter p = {};
    mat3cpy(p.mat, mat);
    p.ox = vec[0] + boxStep/2; p.oy = vec[1] + boxStep/2;
    drawStr(&p, boxStep, boxStep, textToDraw, 0, &rows, &cols);

    char buf[1024] = {};
    sprintf(buf, "data/output/message3d/msg%04d.png", i);
    printf("save file: %s\n", buf);
    viewportSave(buf);
  }

  noTarget();
  framebufferDestroy(fb);
  restoreState();
}


void init() {
  rnd = rand();
  if (renderToFiles)
      render(64);
}


void draw() {
  double pmx = mx, pmy = my;
  mx = mouseX(), my = mouseY();

  double w = windowGetWidth();
  double h = windowGetHeight();
  saveState();
  translate(w/2, h/2);

  int ml = mouseDown("left");
  int mr = mouseDown("right");

  ax += ml ? pmx - mx : windowGetFrameTime()*360/2;
  if (ml || mr) ay += my - pmy;
  ax = fmod(ax, 360);
  ay = fmod(ay, 360);

  double step = boxStep*boxSize/8.0;
  double mat[9] = {step, 0, 0, 0, 0, step, 0, -step, 0};
  mat3rotY(mat, ax);
  mat3rotX(mat, ay);
  double vec[3] = {-4, -4, -4};
  vec3trans(vec, mat);

  Painter p = {};
  mat3cpy(p.mat, mat);
  p.ox = vec[0]; p.oy = vec[1];

  if (textToDraw) {
    int rows, cols;
    drawStr(NULL, 0, 0, textToDraw, 0, &rows, &cols);
    p.ox -= (cols - 1)*boxStep/2;
    p.oy -= (rows - 1)*boxStep/2;
    drawStr(&p, boxStep, boxStep, textToDraw, 0, &rows, &cols);
  } else {
    drawAllLetters(&p);
  }

  restoreState();
}


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

  windowSetSize(768, 768);

  if (argc > 1) {
    textToDraw = argv[1];
    if (argc > 2 && strcmp(argv[2], "render") == 0) renderToFiles = 1;

    int rows, cols;
    drawStr(NULL, 0, 0, textToDraw, 0, &rows, &cols);
    double w = boxStep*cols;
    if (w > 1024) boxStep *= 1024/w;
    double h = boxStep*rows;
    if (h > 768) boxStep *= 768/h;
    windowSetSize( (int)ceil(boxStep*cols - 1e-5), (int)ceil(boxStep*rows - 1e-5) );
  } else {
    printf(
      "Usage: message3d <message> [render]\n"
      " - render path is hardcoded to data/output/message3d/msgXXXX.png\n" );
  }

  windowSetVariableFrameRate();
  windowSetResizable(TRUE);
  windowSetInit(&init);
  windowSetDraw(&draw);
  windowRun();
}