#include #include #include #include #include /* 5c moogle.c && 5l -o moogle moogle.5 */ typedef struct { double x; double y; } Point2d; typedef struct { double x; double y; double z; } Point3d; typedef struct { int a; int b; } Edge; typedef struct { Point3d position; Point3d vertices[128]; int verticeslen; Edge edges[128]; int edgeslen; } Mesh; typedef struct { Point3d origin; Mesh meshes[128]; int len; } Scene; typedef enum { ISOMETRIC, PERSPECTIVE } Projection; typedef struct { double pitch; double yaw; double roll; Projection projection; } Camera; Scene scn; Camera cam; Point center; Image *clrx, *clry, *clrz; /* helpers */ Point2d Pt2d(double x, double y) { return (Point2d){x, y}; } Point3d Pt3d(double x, double y, double z) { return (Point3d){x, y, z}; } Mesh Ms3d(double x, double y, double z) { Mesh m; m.position = Pt3d(x, y, z); m.verticeslen = 0; m.edgeslen = 0; return m; } Scene Sc3d(double x, double y, double z) { Scene s; s.origin = Pt3d(x, y, z); s.len = 0; return s; } Camera Cm3d(double pitch, double yaw, double roll) { return (Camera){pitch, yaw, roll, PERSPECTIVE}; } /* geometry */ double rad2deg(double rad) { return rad * (180 / PI); } double deg2rad(double deg) { return deg * (PI / 180); } double ptangledeg(Point2d a, Point2d b) { double theta = atan2(b.y - a.y, b.x - a.x); return rad2deg(theta); } double ptangle(Point2d a, Point2d b) { return atan2(b.y - a.y, b.x - a.x); } double ptdistance(Point2d a, Point2d b) { double x = a.x - b.x; double y = a.y - b.y; return sqrt(x * x + y * y); } Point2d rotpt(Point2d center, Point2d p0, double deg) { double rot = ptangle(center, p0) + deg2rad(deg); double r = ptdistance(center, p0); return Pt2d(center.x + r * cos(rot), center.y + r * sin(rot)); } /* scene */ void setvertex(Point3d* vertex, double x, double y, double z) { vertex->x = x; vertex->y = y; vertex->z = z; } void addvertex(Mesh* mesh, double x, double y, double z) { setvertex(&mesh->vertices[mesh->verticeslen], x, y, z); mesh->verticeslen++; } void setedge(Edge* edge, int v0, int v1) { edge->a = v0; edge->b = v1; } void addedge(Mesh* mesh, int v0, int v1) { setedge(&mesh->edges[mesh->edgeslen], v0, v1); mesh->edgeslen++; } void addmesh(Scene* scn, Mesh mesh) { scn->meshes[scn->len] = mesh; scn->len++; } void rotverx(Point3d* o, Point3d* v, double angle) { Point2d r = rotpt(Pt2d(o->y, o->z), Pt2d(v->y, v->z), angle); v->y = r.x; v->z = r.y; } void rotvery(Point3d* o, Point3d* v, double angle) { Point2d r = rotpt(Pt2d(o->x, o->z), Pt2d(v->x, v->z), angle); v->x = r.x; v->z = r.y; } void rotverz(Point3d* o, Point3d* v, double angle) { Point2d r = rotpt(Pt2d(o->x, o->y), Pt2d(v->x, v->y), angle); v->x = r.x; v->y = r.y; } void rotver(Point3d* o, Point3d* v, double pitch, double yaw, double roll) { rotverx(o, v, pitch); rotvery(o, v, yaw); rotverz(o, v, roll); } void rotatex(Mesh* mesh, double angle) { for(int i = 0; i < mesh->verticeslen; i++) { rotverx(&mesh->position, &mesh->vertices[i], angle); } } void rotatey(Mesh* mesh, double angle) { for(int i = 0; i < mesh->verticeslen; i++) { rotvery(&mesh->position, &mesh->vertices[i], angle); } } void rotatez(Mesh* mesh, double angle) { for(int i = 0; i < mesh->verticeslen; i++) { rotverz(&mesh->position, &mesh->vertices[i], angle); } } void rotate(Mesh* mesh, double x, double y, double z) { rotatex(mesh, x); rotatey(mesh, y); rotatez(mesh, z); } Point3d addpt3d(Point3d* a, Point3d* b) { return Pt3d(a->x + b->x, a->y + b->y, a->z + b->z); } /* Transforms */ void extrude(Mesh* dst, double depth) { int vl = dst->verticeslen; int el = dst->edgeslen; for(int i = 0; i < vl; i++) { addvertex(dst, dst->vertices[i].x, dst->vertices[i].y, dst->vertices[i].z + depth); addedge(dst, i, i + vl); } for(int i = 0; i < el; i++) { addedge(dst, dst->edges[i].a + vl, dst->edges[i].b + vl); } } void symmetry(Mesh* dst, double x, double y, double z) { int limit = dst->verticeslen; for(int i = 0; i < limit; i++) { addvertex(dst, dst->vertices[i].x * x, dst->vertices[i].y * y, dst->vertices[i].z * z); } int limitb = dst->edgeslen; for(int i = 0; i < limitb; i++) { addedge(dst, limitb + 1 + dst->edges[i].a, limitb + 1 + dst->edges[i].b); } } /* Primitives */ void addpoly(Mesh* dst, double x, double y, double z, double radius, int segments) { int offset = dst->verticeslen; for(int i = 0; i < segments; i++) { addvertex(dst, x + radius * cos(2 * PI * i / segments), y + radius * sin(2 * PI * i / segments), z); addedge(dst, offset + i, offset + (i + 1) % segments); } } void addpath(Scene* scn, double x, double y, double z, int verticeslen, Point3d vertices, ...) { va_list args; va_start(args, verticeslen); Mesh m = Ms3d(x, y, z); for(int i = 0; i < verticeslen; i++) { Point3d v = va_arg(args, Point3d); addvertex(&m, v.x, v.y, v.z); if(i < verticeslen - 1) addedge(&m, i, i + 1); } va_end(args); addmesh(scn, m); } void addpolygon(Scene* scn, double x, double y, double z, double radius, int segments) { Mesh dst = Ms3d(x, y, z); addpoly(&dst, x, y, z, radius, segments); addmesh(scn, dst); } void addpyramid(Scene* scene, double x, double y, double z, double radius, int segments, double depth) { Mesh dst = Ms3d(x, y, z); addpoly(&dst, x, y, z - depth / 2, radius, segments); addvertex(&dst, x, y, z + depth / 2); for(int i = 0; i < segments; i++) { addedge(&dst, i, segments); } addmesh(scene, dst); } void addfrustum(Scene* scene, double x, double y, double z, double radius, int segments, double depth, double mod) { Mesh dst = Ms3d(x, y, z); addpoly(&dst, x, y, z - depth / 2, radius, segments); addpoly(&dst, x, y, z + depth / 2, radius * mod, segments); for(int i = 0; i < segments; i++) { addedge(&dst, i, segments + i); } addmesh(scene, dst); } void addprism(Scene* scene, double x, double y, double z, double radius, int segments, double depth) { Mesh dst = Ms3d(x, y, z); addpoly(&dst, x, y, z - depth / 2, radius, segments); extrude(&dst, depth); addmesh(scene, dst); } /* Draw */ void lineb(Image* dst, Point p0, Point p1, Image* src, Point sp) { double dx = abs(p1.x - p0.x), sx = p0.x < p1.x ? 1 : -1; double dy = -abs(p1.y - p0.y), sy = p0.y < p1.y ? 1 : -1; double err = dx + dy, e2; for(;;) { draw(dst, Rect(p0.x, p0.y, p0.x + 1, p0.y + 1), src, nil, ZP); if(p0.x == p1.x && p0.y == p1.y) break; e2 = 2 * err; if(e2 >= dy) { err += dy; p0.x += sx; } if(e2 <= dx) { err += dx; p0.y += sy; } } } Point project(Camera* cam, Point3d v) { if(cam->projection == ISOMETRIC) { return Pt(center.x + 5 * v.x, center.y + 5 * v.y); } double r = 300 / (v.z + 50); return Pt(center.x + r * v.x, center.y + r * v.y); } void widget(Scene* scn, Camera* cam) { Point3d c = Pt3d(0, 0, 0); Point3d x = Pt3d(5, 0, 0); Point3d y = Pt3d(0, 5, 0); Point3d z = Pt3d(0, 0, 5); rotver(&scn->origin, &x, cam->pitch, cam->yaw, cam->roll); rotver(&scn->origin, &y, cam->pitch, cam->yaw, cam->roll); rotver(&scn->origin, &z, cam->pitch, cam->yaw, cam->roll); lineb(screen, project(cam, c), project(cam, x), clrx, screen->r.min); lineb(screen, project(cam, c), project(cam, y), clry, screen->r.min); lineb(screen, project(cam, c), project(cam, z), clrz, screen->r.min); string(screen, addpt(screen->r.min, Pt(10, 10)), display->black, ZP, display->defaultfont, cam->projection == ISOMETRIC ? "isometric" : "perspective"); char bufx[30], bufy[30], bufz[30]; snprint(bufx, strlen(bufx), "%d", (int)cam->pitch % 360); snprint(bufy, strlen(bufy), "%d", (int)cam->yaw % 360); snprint(bufz, strlen(bufz), "%d", (int)cam->roll % 360); string(screen, addpt(screen->r.min, Pt(10, 30)), clrx, ZP, display->defaultfont, bufx); string(screen, addpt(screen->r.min, Pt(50, 30)), clry, ZP, display->defaultfont, bufy); string(screen, addpt(screen->r.min, Pt(90, 30)), clrz, ZP, display->defaultfont, bufz); } void render(Scene* scn, Camera* cam) { draw(screen, screen->r, display->white, nil, ZP); for(int i = 0; i < scn->len; i++) { Mesh* mesh = &scn->meshes[i]; for(int j = 0; j < mesh->edgeslen; j++) { Edge* edge = &mesh->edges[j]; Point3d a = addpt3d(&mesh->vertices[edge->a], &mesh->position); Point3d b = addpt3d(&mesh->vertices[edge->b], &mesh->position); rotver(&scn->origin, &a, cam->pitch, cam->yaw, cam->roll); rotver(&scn->origin, &b, cam->pitch, cam->yaw, cam->roll); lineb(screen, project(cam, a), project(cam, b), display->black, screen->r.min); } } widget(scn, cam); flushimage(display, 1); } void orient(Scene* scn, Camera* cam, double pitch, double yaw, double roll) { cam->pitch = pitch; cam->yaw = yaw; cam->roll = roll; render(scn, cam); } void orbit(Scene* scn, Camera* cam, Point drag) { cam->pitch -= drag.y / 3.0; cam->yaw -= drag.x / 3.0; render(scn, cam); } void toggleprojection(Scene* scn, Camera* cam) { cam->projection = cam->projection == ISOMETRIC ? PERSPECTIVE : ISOMETRIC; render(scn, cam); } void eresized(int new) { if(new&& getwindow(display, Refnone) < 0) fprint(2, "can't reattach to window"); draw(screen, screen->r, display->white, nil, ZP); center.x = screen->r.min.x + (screen->r.max.x - screen->r.min.x) / 2; center.y = screen->r.min.y + (screen->r.max.y - screen->r.min.y) / 2; } void main(int argc, char** argv) { USED(argc, argv); Event ev; int e; Mouse m; Point drag; char* options[] = {"Projection", "Front", "Top", "Exit", 0}; Menu menu = {options}; initdraw(0, 0, "Moogle"); eresized(0); einit(Emouse); clrx = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0x72DEC2FF); clry = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xFF0000FF); clrz = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xAAAAAAFF); Scene scn = Sc3d(0, 0, 0); Camera cam = Cm3d(120, 20, 0); /* Requires primitives fns */ addpolygon(&scn, 0, 0, 0, 12, 24); render(&scn, &cam); for(;;) { e = event(&ev); if((e == Emouse) && (ev.mouse.buttons & 4)) { if(emenuhit(3, &ev.mouse, &menu) == 0) toggleprojection(&scn, &cam); if(emenuhit(3, &ev.mouse, &menu) == 1) orient(&scn, &cam, 180, 0, 0); if(emenuhit(3, &ev.mouse, &menu) == 2) orient(&scn, &cam, -90, 0, 0); if(emenuhit(3, &ev.mouse, &menu) == 3) exits(nil); } m = emouse(); if(m.buttons & 1) { drag = m.xy; while(m.buttons & 1) { m = emouse(); orbit(&scn, &cam, subpt(m.xy, drag)); } } } }