#include #include #include /* Copyright (c) 2020 Devine Lu Linvega Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE. */ #define HOR 0x40 #define VER 0x28 #define TITLE "out40x28.icn" #define SZ (HOR * VER * 8) #define VLIMIT 256 #define ELIMIT 512 #define MLIMIT 128 #define PI 3.14159265358979323846 #define VIEWFRONT Pt3d(180, 0, 0) #define VIEWTOP Pt3d(90, 0, 0) #define VIEWSIDE Pt3d(90, 90, 0) typedef struct { double x, y; } Point2d; typedef struct { double x, y, z; } Point3d; typedef struct { int color; Point3d *a, *b; } Edge; typedef struct { int verticeslen, edgeslen; Point3d position, vertices[VLIMIT]; Edge edges[ELIMIT]; } Mesh; typedef struct { int len; Point3d position, scale, rotation; Mesh meshes[MLIMIT]; } Scene; typedef enum { ISOMETRIC, PERSPECTIVE } Projection; typedef struct { double range; Point3d origin, rotation, torigin, trotation; Projection projection; } Camera; typedef struct { Uint8 down; double x, y; } Mouse; static int WIDTH = 8 * HOR; static int HEIGHT = 8 * VER; static int FPS = 30, ZOOM = 1; static Scene scn; static Camera cam; static Mouse mouse; /* interface */ static Uint32 theme[] = {0XFFFFFF, 0X000000, 0x55BBAA, 0xFF0000}; static SDL_Window *gWindow = NULL; static SDL_Renderer *gRenderer = NULL; static SDL_Texture *gTexture = NULL; static Uint32 *pixels; /* helpers */ double clamp(double val, double min, double max) { return (val >= min) ? (val <= max) ? val : max : min; } static Point2d Pt2d(double x, double y) { Point2d p; p.x = x; p.y = y; return p; } static Point3d * set3d(Point3d *v, double x, double y, double z) { v->x = x; v->y = y; v->z = z; return v; } static Point3d Pt3d(double x, double y, double z) { Point3d p; set3d(&p, x, y, z); return p; } static Scene Sc3d(void) { Scene s; s.len = 0; set3d(&s.position, 0, 0, 0); set3d(&s.scale, 1, 1, 1); set3d(&s.rotation, 0, 0, 0); return s; } static Camera Cm3d(double pitch, double yaw, double roll, double range) { Camera c; set3d(&c.rotation, pitch, yaw, roll); set3d(&c.trotation, pitch, yaw, roll); c.projection = PERSPECTIVE; c.range = range; return c; } /* geometry */ static double interpolate(double a, double b, double speed, double range) { if(a < b - range || a > b + range) a += (b - a) / speed; else a = b; return a; } static Point2d rot2d(Point2d a, Point2d b, double deg) { double radian = deg * (PI / 180); double angle = atan2(b.y - a.y, b.x - a.x) + radian; double r = sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); return Pt2d(a.x + r * cos(angle), a.y + r * sin(angle)); } static int equpt3d(Point3d p0, Point3d p1) { return p0.x == p1.x && p0.y == p1.y && p0.z == p1.z; } static Point3d * addpt3d(Point3d *p, double x, double y, double z) { return set3d(p, p->x + x, p->y + y, p->z + z); } static Point3d add3d(Point3d *a, Point3d *b) { return Pt3d(a->x + b->x, a->y + b->y, a->z + b->z); } static Point3d mul3d(Point3d *a, Point3d *b) { return Pt3d(a->x * b->x, a->y * b->y, a->z * b->z); } /* scene */ static Point3d * translate3d(Point3d *p, Point3d *t) { *p = add3d(p, t); return p; } static Point3d * scale3d(Point3d *p, Point3d *t) { *p = mul3d(p, t); return p; } static Point3d * rotate3d(Point3d *p, Point3d *o, Point3d *t) { if(t->x) { Point2d r = rot2d(Pt2d(o->y, o->z), Pt2d(p->y, p->z), t->x); p->y = r.x; p->z = r.y; } if(t->y) { Point2d r = rot2d(Pt2d(o->x, o->z), Pt2d(p->x, p->z), t->y); p->x = r.x; p->z = r.y; } if(t->z) { Point2d r = rot2d(Pt2d(o->x, o->y), Pt2d(p->x, p->y), t->z); p->x = r.x; p->y = r.y; } return p; } static Scene * moveto(Scene *s, double x, double y, double z) { set3d(&s->position, x, y, z); return s; } static Scene * scaleto(Scene *s, double x, double y, double z) { set3d(&s->scale, x, y, z); return s; } static Scene * rotateto(Scene *s, double x, double y, double z) { set3d(&s->rotation, x, y, z); return s; } static Scene * reset(Scene *s) { moveto(scaleto(rotateto(s, 0, 0, 0), 1, 1, 1), 0, 0, 0); return s; } static Point3d * addvertex(Mesh *m, double x, double y, double z) { int i; Point3d v = Pt3d(x, y, z); if(m->verticeslen == VLIMIT) { fprintf(stderr, "Warning: Reached vertex limit\n"); return NULL; } translate3d(&v, &scn.position); scale3d(&v, &scn.scale); rotate3d(&v, &scn.position, &scn.rotation); for(i = 0; i < m->verticeslen; ++i) if(equpt3d(m->vertices[i], v)) return &m->vertices[i]; return set3d(&m->vertices[m->verticeslen++], v.x, v.y, v.z); } static Edge * addedge(Mesh *m, Point3d *a, Point3d *b, int color) { if(m->edgeslen == ELIMIT) { fprintf(stderr, "Warning: Reached edge limit\n"); return NULL; } m->edges[m->edgeslen].a = a; m->edges[m->edgeslen].b = b; m->edges[m->edgeslen].color = color; return &m->edges[m->edgeslen++]; } /* Primitives */ static Mesh * addpoly(Mesh *m, int a, int b, int c, int color) { addedge(m, &m->vertices[a], &m->vertices[b], color); addedge(m, &m->vertices[b], &m->vertices[c], color); addedge(m, &m->vertices[c], &m->vertices[a], color); return m; } static Mesh * addline(Mesh *m, Point3d a, Point3d b, int color) { addedge(m, addvertex(m, a.x, a.y, a.z), addvertex(m, b.x, b.y, b.z), color); return m; } static Mesh * addarc(Mesh *m, double radius, double segs, double angle, int color) { int i; double arc = 2 * PI * angle / 360 / segs; Point3d b; for(i = 0; i < segs + 1; i++) { Point3d a = Pt3d(radius * cos(i * arc), radius * sin(i * arc), 0); if(i > 0) addline(m, a, b, color); b = a; } return m; } static Mesh * addshape(Mesh *m, double radius, int segs, int color) { return addarc(m, radius, segs, 360, color); } /* draw */ static void clear(Uint32 *dst) { int i; for(i = 0; i < WIDTH * HEIGHT; i++) dst[i] = theme[0]; } static void putpixel(Uint32 *dst, int x, int y, Uint32 color) { if(x > 0 && y > 0 && x < WIDTH && y < HEIGHT) dst[y * WIDTH + x] = color; } static Uint32 getpixel(Uint32 *src, int x, int y) { if(x < WIDTH && y < HEIGHT) return src[y * WIDTH + x]; return 0; } static void line(Uint32 *dst, Point2d p0, Point2d p1, Uint32 color) { int p0x = (int)p0.x, p0y = (int)p0.y; int p1x = (int)p1.x, p1y = (int)p1.y; int dx = abs(p1x - p0x), sx = p0x < p1x ? 1 : -1; int dy = -abs(p1y - p0y), sy = p0y < p1y ? 1 : -1; int err = dx + dy, e2; for(;;) { putpixel(dst, p0x, p0y, color); if(p0x == p1x && p0y == p1y) break; e2 = 2 * err; if(e2 >= dy) { err += dy; p0x += sx; } if(e2 <= dx) { err += dx; p0y += sy; } } } static Point2d project(Camera *c, Point3d v) { double r; if(c->projection == ISOMETRIC) return Pt2d((WIDTH / 2) + v.x * (10 - c->range / 10), (HEIGHT / 2) + v.y * (10 - c->range / 10)); r = 200 / (v.z + c->range); return Pt2d(WIDTH / 2 + r * v.x, HEIGHT / 2 + r * v.y); } static void redraw(Uint32 *dst) { int i, j; clear(dst); for(i = 0; i < scn.len; i++) { Mesh *m = &scn.meshes[i]; for(j = 0; j < m->edgeslen; j++) { Edge *edge = &m->edges[j]; Point3d a = add3d(edge->a, &m->position); Point3d b = add3d(edge->b, &m->position); rotate3d(&a, &cam.origin, &cam.rotation); rotate3d(&b, &cam.origin, &cam.rotation); line(dst, project(&cam, add3d(&cam.origin, &a)), project(&cam, add3d(&cam.origin, &b)), theme[edge->color]); } } SDL_UpdateTexture(gTexture, NULL, dst, WIDTH * sizeof(Uint32)); SDL_RenderClear(gRenderer); SDL_RenderCopy(gRenderer, gTexture, NULL, NULL); SDL_RenderPresent(gRenderer); } /* options */ static int error(char *msg, const char *err) { fprintf(stderr, "Error %s: %s\n", msg, err); return 0; } static void update(Camera *c, double speed) { if(!equpt3d(c->rotation, c->trotation) || !equpt3d(c->origin, c->torigin)) { set3d(&c->rotation, interpolate(c->rotation.x, c->trotation.x, speed, 1), interpolate(c->rotation.y, c->trotation.y, speed, 1), interpolate(c->rotation.z, c->trotation.z, speed, 1)); set3d(&c->origin, interpolate(c->origin.x, c->torigin.x, speed, 1), interpolate(c->origin.y, c->torigin.y, speed, 1), interpolate(c->origin.z, c->torigin.z, speed, 1)); redraw(pixels); } } static void setzoom(int val) { ZOOM = val; SDL_SetWindowSize(gWindow, WIDTH * ZOOM, HEIGHT * ZOOM); redraw(pixels); } static void toggleprojection(Camera *c) { c->projection = c->projection == ISOMETRIC ? PERSPECTIVE : ISOMETRIC; redraw(pixels); } static void modrange(int mod) { int res = cam.range + mod; if(res > 0 && res < 90) cam.range = res; redraw(pixels); } static void export_icn(Uint32 *src, int width, int height, char *filename) { int i, x, y; Uint8 icnbuf[SZ]; FILE *f = fopen(filename, "wb"); /* clean */ for(i = 0; i < SZ; ++i) icnbuf[i] = 0; /* write pixels */ for(y = 0; y < height; y++) { for(x = 0; x <= width; x++) { int color = getpixel(src, x, y); if(!color) { int col = x & 7, row = y & 7; int byte = (x & ~7) + (y & ~7) * width / 8 + row; if(byte < SZ) icnbuf[byte] |= 1 << (7 - col); } } } /* save */ if(!fwrite(icnbuf, sizeof(icnbuf), 1, f)) fprintf(stderr, "Failed: %s!\n", filename); fclose(f); fprintf(stderr, "Export: %s\n", filename); } static void quit(void) { free(pixels); SDL_DestroyTexture(gTexture); gTexture = NULL; SDL_DestroyRenderer(gRenderer); gRenderer = NULL; SDL_DestroyWindow(gWindow); gWindow = NULL; SDL_Quit(); exit(0); } static void handle_key(SDL_Event *event) { int shift = SDL_GetModState() & KMOD_LSHIFT || SDL_GetModState() & KMOD_RSHIFT; switch(event->key.keysym.sym) { case SDLK_F1: setzoom((ZOOM == 1) + 1); break; case SDLK_TAB: toggleprojection(&cam); break; case SDLK_1: set3d(&cam.trotation, VIEWFRONT.x, VIEWFRONT.y, VIEWFRONT.z); break; case SDLK_2: set3d(&cam.trotation, VIEWTOP.x, VIEWTOP.y, VIEWTOP.z); break; case SDLK_3: set3d(&cam.trotation, VIEWSIDE.x, VIEWSIDE.y, VIEWSIDE.z); break; case SDLK_e: export_icn(pixels, WIDTH, HEIGHT, TITLE); break; case SDLK_UP: case SDLK_w: if(shift) addpt3d(&cam.torigin, 0, 2.5, 0); else addpt3d(&cam.trotation, 5, 0, 0); break; case SDLK_LEFT: case SDLK_a: if(shift) addpt3d(&cam.torigin, 2.5, 0, 0); else addpt3d(&cam.trotation, 0, -5, 0); break; case SDLK_DOWN: case SDLK_s: if(shift) addpt3d(&cam.torigin, 0, -2.5, 0); else addpt3d(&cam.trotation, -5, 0, 0); break; case SDLK_RIGHT: case SDLK_d: if(shift) addpt3d(&cam.torigin, -2.5, 0, 0); else addpt3d(&cam.trotation, 0, 5, 0); break; case SDLK_f: set3d(&cam.torigin, 0, 0, 0); set3d(&cam.trotation, VIEWFRONT.x, VIEWFRONT.y, VIEWFRONT.z); break; case SDLK_z: if(shift) addpt3d(&cam.torigin, 0, 0, 2.5); else addpt3d(&cam.trotation, 0, 0, 5); break; case SDLK_x: if(shift) addpt3d(&cam.torigin, 0, 0, -2.5); else addpt3d(&cam.trotation, 0, 0, 5); break; } redraw(pixels); } static int init(void) { if(SDL_Init(SDL_INIT_VIDEO) < 0) return error("Init", SDL_GetError()); gWindow = SDL_CreateWindow("Moogle", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WIDTH * ZOOM, HEIGHT * ZOOM, SDL_WINDOW_SHOWN); if(gWindow == NULL) return error("Window", SDL_GetError()); gRenderer = SDL_CreateRenderer(gWindow, -1, 0); if(gRenderer == NULL) return error("Renderer", SDL_GetError()); gTexture = SDL_CreateTexture(gRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, WIDTH, HEIGHT); if(gTexture == NULL) return error("Texture", SDL_GetError()); pixels = (Uint32 *)malloc(WIDTH * HEIGHT * sizeof(Uint32)); if(pixels == NULL) return error("Pixels", "Failed to allocate memory"); clear(pixels); return 1; } /* transforms */ Mesh * translate(Mesh *m, double x, double y, double z) { int i; Point3d t = Pt3d(x, y, z); for(i = 0; i < m->verticeslen; i++) translate3d(&m->vertices[i], &t); return m; } Mesh * scale(Mesh *m, double x, double y, double z) { int i; Point3d t = Pt3d(x, y, z); for(i = 0; i < m->verticeslen; i++) scale3d(&m->vertices[i], &t); return m; } Mesh * rotate(Mesh *m, double pitch, double yaw, double roll) { int i; Point3d t = Pt3d(pitch, yaw, roll); for(i = 0; i < m->verticeslen; i++) rotate3d(&m->vertices[i], &m->position, &t); return m; } Mesh * extrude(Mesh *m, double x, double y, double z, int color) { int i, vl = m->verticeslen, el = m->edgeslen; for(i = 0; i < vl; i++) addedge(m, &m->vertices[i], addvertex(m, m->vertices[i].x + x, m->vertices[i].y + y, m->vertices[i].z + z), color); for(i = 0; i < el; i++) addedge(m, &m->vertices[vl + m->edges[i].a - &m->vertices[0]], &m->vertices[vl + m->edges[i].b - &m->vertices[0]], color); return m; } Mesh * symmetry(Mesh *m, double x, double y, double z, int color) { int i, el = m->edgeslen, vl = m->verticeslen; for(i = 0; i < vl; ++i) m->vertices[vl + i] = Pt3d(m->vertices[i].x * x, m->vertices[i].y * y, m->vertices[i].z * z); m->verticeslen += vl; for(i = 0; i < el; i++) addedge(m, &m->vertices[vl + m->edges[i].a - &m->vertices[0]], &m->vertices[vl + m->edges[i].b - &m->vertices[0]], color); return m; } /* Primitives */ Mesh * addmesh(Scene *s) { if(s->len == MLIMIT) { fprintf(stderr, "Warning: Reached mesh limit\n"); return NULL; } return &s->meshes[s->len++]; } Mesh * copymesh(Scene *s, Mesh *a) { int i; Mesh *b = addmesh(s); b->verticeslen = a->verticeslen; b->edgeslen = a->edgeslen; b->position = a->position; for(i = 0; i < a->verticeslen; ++i) b->vertices[i] = a->vertices[i]; for(i = 0; i < a->edgeslen; ++i) { b->edges[i].color = a->edges[i].color; b->edges[i].a = &b->vertices[a->edges[i].a - &a->vertices[0]]; b->edges[i].b = &b->vertices[a->edges[i].b - &a->vertices[0]]; } return b; } Mesh * createshape(Scene *s, double radius, double segs, int color) { return addarc(addmesh(s), radius, segs, 360, color); } Mesh * createfrustum(Scene *s, double radius, int segs, double depth, double cap, int color) { int i; Mesh *m = addmesh(s); addshape(m, cap, segs, color); translate(m, 0, 0, depth); addshape(m, radius, segs, color); for(i = 0; i < segs + 1; i++) addedge(m, &m->vertices[i], &m->vertices[segs + i + 1], color); return m; } Mesh * createpyramid(Scene *s, double radius, int segs, double depth, int color) { int i; Mesh *m = addmesh(s); Point3d *p = addvertex(m, 0, 0, depth); addshape(m, radius, segs, color); for(i = 0; i < segs + 1; i++) addedge(m, &m->vertices[i], p, color); return m; } Mesh * createprism(Scene *s, double radius, int segs, double depth, int color) { return createfrustum(s, radius, segs, depth, radius, color); } Mesh * createsphere(Scene *s, double radius, double segs, double ssegs, int color) { double i; Mesh *m = addmesh(s); for(i = 0; i < segs; i++) { double j = i - segs / 2; addshape(m, sqrt(radius * radius - j * j), ssegs, color); moveto(s, 0, 0, i + 1 - segs / 2); } reset(s); return m; } Mesh * createplane(Scene *s, double width, double height, double xsegs, double ysegs, int color) { int ix, iy; Mesh *m = addmesh(s); for(ix = 0; ix < xsegs + 1; ix++) addline(m, Pt3d(ix * (width / xsegs) - width / 2, height / 2, 0), Pt3d(ix * (width / xsegs) - width / 2, -height / 2, 0), color); for(iy = 0; iy < ysegs + 1; iy++) addline(m, Pt3d(width / 2, iy * (height / ysegs) - height / 2, 0), Pt3d(-width / 2, iy * (height / ysegs) - height / 2, 0), color); return m; } Mesh * createbox(Scene *s, double width, double height, double depth, int color) { return extrude(createplane(s, width, height, 1, 1, color), 0, 0, depth, color); } Mesh * createicosaedron(Scene *s, double radius, int color) { Mesh *m = addmesh(s); double t = (1.0 + sqrt(5.0)) / 2.0; addvertex(m, -1, t, 0); addvertex(m, 1, t, 0); addvertex(m, -1, -t, 0); addvertex(m, 1, -t, 0); addvertex(m, 0, -1, t); addvertex(m, 0, 1, t); addvertex(m, 0, -1, -t); addvertex(m, 0, 1, -t); addvertex(m, t, 0, -1); addvertex(m, t, 0, 1); addvertex(m, -t, 0, -1); addvertex(m, -t, 0, 1); addpoly(m, 0, 11, 5, color); /* - */ addpoly(m, 0, 5, 1, color); addpoly(m, 0, 1, 7, color); addpoly(m, 0, 7, 10, color); addpoly(m, 0, 10, 11, color); addpoly(m, 1, 5, 9, color); /* - */ addpoly(m, 5, 11, 4, color); addpoly(m, 11, 10, 2, color); addpoly(m, 10, 7, 6, color); addpoly(m, 7, 1, 8, color); addpoly(m, 3, 9, 4, color); /* - */ addpoly(m, 3, 4, 2, color); addpoly(m, 3, 2, 6, color); addpoly(m, 3, 6, 8, color); addpoly(m, 3, 8, 9, color); addpoly(m, 4, 9, 5, color); /* - */ addpoly(m, 2, 4, 11, color); addpoly(m, 6, 2, 10, color); addpoly(m, 8, 6, 7, color); addpoly(m, 9, 8, 1, color); scale(m, radius / 2, radius / 2, radius / 2); return m; } Mesh * createumbrella(Scene *s) { int i; Mesh *umbrella = addmesh(s); reset(s); for(i = 0; i < 5; ++i) { addarc(umbrella, 10, 8, 180, 2); rotateto(s, 0, 45 * i, 0); } rotateto(s, 90, 0, 0); addshape(umbrella, 10, 8, 1); translate(scale(umbrella, 1, 0.6, 1), 0, 2, 0); addline(umbrella, Pt3d(0, 0, -10), Pt3d(0, 0, 8), 1); rotateto(moveto(s, 0, -8, 2), 90, 90, 90); addarc(umbrella, 2, 8, 180, 2); reset(s); return umbrella; } int main(void) { int ticknext = 0; scn = Sc3d(); cam = Cm3d(180, 0, 0, 30); if(!init()) return error("Init", "Failure"); createumbrella(&scn); /* Begin */ redraw(pixels); while(1) { int tick = SDL_GetTicks(); SDL_Event event; if(tick < ticknext) SDL_Delay(ticknext - tick); ticknext = tick + (1000 / FPS); update(&cam, 5); while(SDL_PollEvent(&event) != 0) { switch(event.type) { case SDL_QUIT: quit(); break; case SDL_MOUSEWHEEL: modrange(event.wheel.y / -1); break; case SDL_MOUSEBUTTONUP: mouse.down = 0; break; case SDL_MOUSEBUTTONDOWN: mouse.down = 1; mouse.x = event.motion.x; mouse.y = event.motion.y; break; case SDL_MOUSEMOTION: if(mouse.down) addpt3d(&cam.trotation, (event.motion.y - mouse.y) / 4, (event.motion.x - mouse.x) / 4, 0); break; case SDL_KEYDOWN: handle_key(&event); break; case SDL_WINDOWEVENT: if(event.window.event == SDL_WINDOWEVENT_EXPOSED) redraw(pixels); break; } } } quit(); return 0; }