XXIIVV
Pitch Yaw Roll picture
20P12 — Pitch Yaw Roll

Moogle is a 3D wireframe toolkit for Plan9.

Moogle is a minimal 3D wireframe tool designed to be used alongside its companion tool Noodle, on Plan9. It offers a handful of basic geometry drawing functions, it was written in Plan9 C, and was inspired by Graf3DScene.

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
#include <thread.h>

/* 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);
}

/* 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));
			}
		}
	}
}

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);
}
moogle.c 526 lines
Moogle Spheroid picture
20P01 — Moogle Spheroid

incoming(3): noodle graf3dscene defunct

Last update on 20R13, edited 4 times. +23/29fh-----+