#include <SDL2/SDL.h>
#include <math.h>
#include <stdio.h>
/*
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 0x3c
#define VER 0x22
#define TITLE "out3cx22.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
dokey(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:
dokey(&event);
break;
case SDL_WINDOWEVENT:
if(event.window.event == SDL_WINDOWEVENT_EXPOSED)
redraw(pixels);
break;
}
}
}
quit();
return 0;
}