~zjm/Moon3D

ff583b689903e98a9d6c03f5e52bed09a44f93ac — Zack Michener a month ago ee9bfd1
add comments; clean up a bit; remove debugging
7 files changed, 154 insertions(+), 95 deletions(-)

M Makefile
M src/game.c
M src/transform.c
M src/transform.h
M src/vector.c
M src/vector.h
M test/transform.test.c
M Makefile => Makefile +1 -1
@@ 12,7 12,7 @@ TESTS := $(shell find $(TEST_DIRS) -name *.test.c)
TEST_OBJS := $(TESTS:%=$(BUILD_DIR)/%.o) $(SRCS:%=$(BUILD_DIR)/%.test.o)

CC = gcc
CFLAGS = -I/usr/local/include/SDL2 -O2 -g -Wall -Wextra -Wno-unused-function
CFLAGS = -I/usr/local/include/SDL2 -O0 -g -Wall -Wextra -Wno-unused-function
LDFLAGS = -L/usr/local/lib -lSDL2 -lm
TESTFLAGS = -DTEST


M src/game.c => src/game.c +110 -57
@@ 42,10 42,11 @@ int main(void)
			Renderer *gameRenderer = NewRenderer(pixels, SCREEN_WIDTH, SCREEN_HEIGHT, scene->camera);

			// one-shot
			UpdateScene(scene, 1);
			ClearScreen(gameRenderer);
			RenderScene(scene, gameRenderer);
			// UpdateScene(scene, 1);
			// ClearScreen(gameRenderer);
			// RenderScene(scene, gameRenderer);

			/* variables for perf statistics */
			long frames = 0;
			long start = SDL_GetTicks();
			long dt = 0;


@@ 55,10 56,13 @@ int main(void)

				dt = SDL_GetTicks() - lastTime;
				lastTime = SDL_GetTicks();
				// UpdateScene(scene, dt);
				// ClearScreen(gameRenderer);
				// RenderScene(scene, gameRenderer);

				/* main game loop */
				UpdateScene(scene, dt);
				ClearScreen(gameRenderer);
				RenderScene(scene, gameRenderer);

				/* copy our framebuffer to SDL for display */
				SDL_UpdateTexture(texture, NULL, pixels, SCREEN_WIDTH*sizeof(uint32_t));
				SDL_RenderCopy(renderer, texture, NULL, NULL);
				SDL_RenderPresent(renderer);


@@ 68,12 72,14 @@ int main(void)
				//  done = SDL_TRUE;
				// }

				/* listen for a quit event */
				while (SDL_PollEvent(&event)) {
					if (event.type == SDL_QUIT) {
						done = SDL_TRUE;
					}
				}
			}
			/* display perf stats */
			long time = (SDL_GetTicks() - start)/1000;
			double fps = (double)frames/(double)time;
			printf("%f fps\n", fps);


@@ 88,22 94,11 @@ int main(void)
#endif

Scene *MoonWorld(void)
/* A wrapper for creating and setting up a moon scene */
{
	Scene *scene = NewScene();
	scene->camera->far = 50;
	scene->camera->near = 1;
	// scene->camera->physics->rotation = V(0, 0.1, 0);
	// PlaceModel(scene->camera->model, V(0, 0, 5));
	RotateModel(scene->camera->model, 0, -90, 0);
	SceneObject *obj = CreateSceneObject(scene, "assets/cube.obj", BLACK);
	PlaceModel(obj->model, V(0, 0, -10));
	// CommitTransform(RotationY(10), &obj->model->direction);
	// obj->physics->velocity = V(-0.001, 0, 0);
	// PlaceModel(obj->model, V(-1, 0, 0));
	// ScaleObject(obj, 10, 10, 10);
	// SpinObject(obj, 0, 1, 0);

	// DumpMesh(cube->renderable->mesh);

	InitParticleSystem(scene);
	for (int i = 0; i < 1000; i++) {


@@ 120,6 115,15 @@ Scene *MoonWorld(void)
}

Scene *NewScene(void)
/* Allocates and initializes a Scene.
A scene tracks all SceneObjects and all of their component parts. It owns global
memory pools (to which other objects may have a reference). Pools exist for
SceneObject, Renderable, Physics, Model, and Particle. The Particle pool is
initially NULL, and must be enabled separately.

Note that Scene does not track vertex or polygon pools. Those are owned
individually by each Mesh, as well as temporary rendering pools in Renderer.
*/
{
	Scene *scene = malloc(sizeof(Scene));
	scene->objects = NewPool(SceneObject, MAX_SCENE_OBJECTS);


@@ 134,6 138,7 @@ Scene *NewScene(void)
}

bool LoadObj(const char *filename, Mesh *mesh)
/* Loads an OBJ file into an empty Mesh. Returns success or failure. */
{
	FILE *fp;
	bool fileOk = true;


@@ 155,6 160,9 @@ bool LoadObj(const char *filename, Mesh *mesh)
}

bool ReadObjLine(FILE *fp, Mesh *mesh)
/* Reads the line type based on the first few characters and delegates parsing
to subroutines. Right now we only support OBJ_VERTEX and OBJ_FACE.
*/
{
	char itemType[8];
	if (fscanf(fp, "%8s", itemType) == 1) {


@@ 178,6 186,7 @@ bool ReadObjLine(FILE *fp, Mesh *mesh)
}

bool ReadObjVertex(FILE *fp, Mesh *mesh)
/* Reads and appends a vertex to the mesh's vertex pool */
{
	float x, y, z, w;
	bool match;


@@ 191,13 200,13 @@ bool ReadObjVertex(FILE *fp, Mesh *mesh)
	match = fscanf(fp, "%f", &w);
	if (!match) w = 1.0;

	/* mesh->vertices should be at the beginning of a pool */
	Vertex *vertex = NewVertex(mesh->vertices);
	*vertex = V4D(x, y, z, w);
	return true;
}

bool ReadObjFace(FILE *fp, Mesh *mesh)
/* Reads and appends a face to the mesh's polygon pool */
{
	int *polygon = NewPolygon(mesh->faces);
	int vIndex;


@@ 231,6 240,7 @@ ObjLineType ParseLineType(const char *type)
}

SceneObject *CreateSceneObject(Scene *scene, const char *meshFile, Color color)
/* Convenience function to load and create a SceneObject with an OBJ file */
{
	Model *model = NewModel(scene->model_pool);
	Renderable *renderable = NewRenderable(scene->renderable_pool, model, color);


@@ 241,6 251,7 @@ SceneObject *CreateSceneObject(Scene *scene, const char *meshFile, Color color)
}

Renderable *NewRenderable(Renderable *pool, Model *model, Color color)
/* Creates and initializes a Renderable in a memory pool */
{
	assert(PoolTotal(pool) < MAX_SCENE_OBJECTS);
	Renderable *renderable = PoolNext(pool);


@@ 251,6 262,7 @@ Renderable *NewRenderable(Renderable *pool, Model *model, Color color)
}

Physics *NewPhysics(Physics *pool, Model *model)
/* Creates and initializes a Physics in a memory pool */
{
	assert(PoolTotal(pool) < MAX_SCENE_OBJECTS);
	Physics *physics = PoolNext(pool);


@@ 262,6 274,16 @@ Physics *NewPhysics(Physics *pool, Model *model)
}

int *NewRawPool(int item_size, int max_items)
/* Allocates and initializes a memory pool. Used by the `NewPool` macro.
A memory pool is just a plain array of some item, except that immediately
preceding it in memory is an int tracking the number of current items. Access
is done by array subscripts, e.g. `Thing x = my_pool[12];`, but modifying the
pool should be done through pool macros.

`NewRawPool` allocates the memory for a pool and returns a pointer to the
counter int. `NewPool` uses this, but returns a pointer to the first element,
which is usually more useful.
*/
{
	int *raw_pool = malloc(item_size*(max_items) + sizeof(int));
	raw_pool[0] = 0;


@@ 269,6 291,7 @@ int *NewRawPool(int item_size, int max_items)
}

SceneObject *NewSceneObject(SceneObject *pool, Model *model, Renderable *renderable, Physics *physics)
/* Creates and initialized a new SceneObject */
{
	assert(PoolTotal(pool) < MAX_SCENE_OBJECTS);
	SceneObject *obj = PoolNext(pool);


@@ 279,11 302,15 @@ SceneObject *NewSceneObject(SceneObject *pool, Model *model, Renderable *rendera
}

void InitParticleSystem(Scene *scene)
/* Activates a Scene's particle pool. Presence of this pool will also enable
particle processing.
*/
{
	scene->particles = NewPool(Particle, MAX_PARTICLES);
}

Particle *AddParticle(Particle *pool, Vertex v)
/* Creates and initializes a Particle */
{
	assert(PoolTotal(pool) < MAX_PARTICLES);
	Particle *p = PoolNext(pool);


@@ 294,6 321,11 @@ Particle *AddParticle(Particle *pool, Vertex v)
}

Renderer *NewRenderer(Pixel *pixels, int width, int height, Camera *camera)
/* Creates and initializes a Renderer.
The camera is owned and initialized by Scene, but Scene doesn't know the aspect
ratio of the screen, so this will also update the camera with the screen's
aspect ratio.
*/
{
	Renderer *r = malloc(sizeof(Renderer));
	r->pixels = pixels;


@@ 325,6 357,7 @@ Renderer *NewRenderer(Pixel *pixels, int width, int height, Camera *camera)
}

void ClearScreen(Renderer *renderer)
/* Clears the frame buffer and depth buffer */
{
	for (int y = 0; y < renderer->height; y++) {
		for (int x = 0; x < renderer->width; x++) {


@@ 335,6 368,7 @@ void ClearScreen(Renderer *renderer)
}

DepthVal *NewDepthBuffer(int width, int height)
/* Allocates and initializes a depth buffer */
{
	DepthVal *values = malloc(sizeof(DepthVal)*width*height);
	for(int y = 0; y < height; y++) {


@@ 346,6 380,7 @@ DepthVal *NewDepthBuffer(int width, int height)
}

Camera *NewCamera(double fov, double aspect, double near, double far, Model *model, Physics *physics)
/* Allocates and initializes a Camera */
{
	Camera *camera = malloc(sizeof(Camera));
	camera->model = model;


@@ 360,16 395,20 @@ Camera *NewCamera(double fov, double aspect, double near, double far, Model *mod
}

void SetCamera(Camera *camera, double fov, double aspect, double near, double far)
/* Updates a Camera's properties */
{
	camera->fov = fov;
	camera->aspect = aspect;
	camera->x_cotan = 1/tan(Rad(fov)/2);
	camera->x_cotan = 1/tan(Rad(fov)/2); /* precalculated for efficiency */
	camera->y_cotan = aspect/tan(Rad(fov)/2);
	camera->near = near;
	camera->far = far;
}

Transform CameraTransform(Camera *camera)
/* Returns the transform to move world vertices into the camera's frame of
reference, and project them into image space
*/
{
	Transform camera_transform = Identity();



@@ 386,16 425,16 @@ Transform CameraTransform(Camera *camera)
	perspective_transform.m[3][3] = 0;
	perspective_transform.m[2][3] = (2*camera->far*camera->near) / (camera->far - camera->near);
	perspective_transform.m[3][2] = -1;
	// PrintMat(camera_transform);
	// printf("persp\n");
	// PrintMat(perspective_transform);

	AddTransform(perspective_transform, &camera_transform);
// PrintMat(camera_transform);
	return camera_transform;
}

// TODO: consider using positive Z-axis & make a special case for the camera
Transform AlignToAxis(Model *model)
/* Returns the transform to move vertices into the model's rotational frame of
reference — the model will be facing the negative Z-axis, and up will be the
positive Y-axis. Assumes the model is at the origin. */
{
	Vector Rx, Ry, Rz;
	Transform op = Identity();


@@ 416,7 455,9 @@ Transform AlignToAxis(Model *model)
	return op;
}

// TODO: test this, seems sus
Transform Orient(Model *model)
/* Same as AlignToAxis, but positive Z-axis? I think? */
{
	Vector Rx, Ry, Rz;
	Transform op = Identity();


@@ 438,13 479,16 @@ Transform Orient(Model *model)
}

void ZoomIn(Camera *camera, double amt)
/* Updates a Camera's params for a narrower field of view (zoom in) */
{
	camera->fov *= 1/(amt/100000 + 1);
	camera->x_cotan = 1/(camera->aspect * tan(camera->fov/2));
	camera->y_cotan = 1/tan(camera->fov/2);
}
// TODO: also make ZoomOut

Vertex *NewVertex(Vertex *pool)
/* Creates an uninitialized vertex in a vertex pool */
{
	assert(PoolTotal(pool) < MAX_RENDER_VERTICES);
	Vertex *v = PoolNext(pool);


@@ 452,6 496,7 @@ Vertex *NewVertex(Vertex *pool)
}

void UpdateScene(Scene *scene, int dt)
/* Game loop update for a scene—all logical updates for one frame in a scene */
{
	int i;



@@ 466,7 511,9 @@ void UpdateScene(Scene *scene, int dt)
	// }
}

// TODO: rethink how rotation is handled
void UpdatePhysics(Physics physics, int dt)
/* Applies Physics updates for a frame—follows Newton's laws */
{
	physics.model->position = VecAdd(physics.model->position, VecMul(physics.velocity, dt));
	physics.velocity = VecAdd(physics.velocity, VecMul(physics.acceleration, dt));


@@ 476,12 523,15 @@ void UpdatePhysics(Physics physics, int dt)
}

void UpdateParticle(Particle particle, int dt)
/* Applies physics updates to a particle. Particles don't use the Physics
component because they don't have rotation, etc */
{
	VecAdd(particle.position, VecMul(particle.velocity, dt));
	VecAdd(particle.velocity, VecMul(particle.acceleration, dt));
}

void RenderScene(Scene *scene, Renderer *renderer)
/* Top-level function to render all SceneObjects and Particles in a scene */
{
	for (int i = 0; i < PoolTotal(scene->renderable_pool); i++) {
		Render(scene->renderable_pool[i], renderer);


@@ 492,6 542,7 @@ void RenderScene(Scene *scene, Renderer *renderer)
}

void DrainRenderPools(Renderer *renderer)
/* Clears the temporary rendering pools in a Renderer */
{
	DrainPool(renderer->vertex_pool);
	DrainPool(renderer->polygon_pool);


@@ 499,6 550,7 @@ void DrainRenderPools(Renderer *renderer)
}

void Render(Renderable renderable, Renderer *renderer)
/* Main rendering pipeline for a mesh. Beware. */
{
	int i;
	int *polygon;


@@ 508,15 560,14 @@ void Render(Renderable renderable, Renderer *renderer)
	renderMesh.vertices = renderer->vertex_pool;
	renderMesh.faces = renderer->polygon_pool;

	// TODO: calculate camera transform once per-frame instead
	/* construct the image transform */
	Transform mesh_transform = ComposeTransform(
		CameraTransform(renderer->camera),
		ModelTransform(renderable.model)
	);
	// PrintMat(CameraTransform(renderer->camera));
	// PrintMat(ModelTransform(renderable.model));
	// PrintMat(mesh_transform);

	/* model and project all vertices */
	/* model and project all vertices into image space */
	for (i = 0; i < PoolTotal(renderable.mesh->vertices); i++) {
		Vertex *vertex = NewVertex(renderMesh.vertices);
		*vertex = renderable.mesh->vertices[i];


@@ 527,24 578,13 @@ void Render(Renderable renderable, Renderer *renderer)
	for (i = 0, polygon = renderable.mesh->faces;
		 i < PoolTotal(renderable.mesh->faces);
		 i += NumPolygonVertices(polygon)+1, NextPolygon(polygon)) {
		// printf("Original polygon:\n");
		// PrintPolygon(polygon, renderable.mesh->vertices);
		int *clipped = ClipPolygon(polygon, &renderMesh, renderer);
		if (NumPolygonVertices(clipped) > 0) {
			// printf("Polygon %d\n", i);
			// printf("Original\n");
			// PrintPolygon(polygon, renderable.mesh->vertices);
			// printf("Projected\n");
			// PrintPolygon(polygon, renderMesh.vertices);
			// printf("Clipped\n");
			// PrintPolygon(clipped, renderMesh.vertices);
		}
		ClipPolygon(polygon, &renderMesh, renderer);
	}

	/* viewmap all vertices */
	// TODO: skip clipped vertices
	for (i = 0; i < PoolTotal(renderMesh.vertices); i++) {
		Homogenize(renderMesh.vertices[i]);
		Homogenize(&renderMesh.vertices[i]);
		CommitTransform(renderer->deviceTransform, &renderMesh.vertices[i]);
	}



@@ 560,7 600,11 @@ void Render(Renderable renderable, Renderer *renderer)
}

Transform ModelTransform(Model *model)
/* Returns the transform to move vertices from a model's coordinate space into
world space
*/
{
	// TODO: figure out this rotation business
	/* rotate to align up and direction */
	// Transform t = Orient(model);
	Transform t = Identity();


@@ 569,6 613,7 @@ Transform ModelTransform(Model *model)
}

int *NewPolygon(int *pool)
/* Creates and initializes a 0-gon in a polygon pool */
{
	assert(PoolTotal(pool) < MAX_RENDER_POLYGONS);
	int *polygon = PoolNext(pool);


@@ 577,49 622,44 @@ int *NewPolygon(int *pool)
}

int *ClipPolygon(int *polygon, Mesh *mesh, Renderer *renderer)
/* Clips a polygon against a Renderer's clip planes. For each plane, the polygon
is copied (and clipped) back and forth between the renderer's two polygon pools.
The temporary polygon is erased each time after it's used to keep memory use low
*/
{
	int *polygonA = NewPolygon(renderer->polygon_pool);
	int *polygonB = NewPolygon(renderer->alt_polygon_pool);

	// ClipPolygonPlane(polygon, polygonA, mesh, renderer->clip_planes.w_pos, renderer->polygon_pool);
	// TODO: test which order of clipping these planes is fastest. (probably clip y-neg first, since most of the moon's verts will be below the screen)

// printf("Clipping this polygon:\n");
// PrintPolygon(polygon, mesh->vertices);
	// ClipPolygonPlane(polygon, polygonA, mesh, renderer->clip_planes.w_pos, renderer->polygon_pool);

// printf("Clipping on x_neg\n");
	ClipPolygonPlane(polygon,  polygonB, mesh, renderer->clip_planes.x_neg, renderer->alt_polygon_pool);
	// ClearPolygon(polygonA, renderer->polygon_pool);
// PrintPolygon(polygonB, mesh->vertices);
	// ClearPolygon(polygonA, renderer->polygon_pool); // only for w_pos clipping

// printf("Clipping on x_pos\n");
	ClipPolygonPlane(polygonB, polygonA, mesh, renderer->clip_planes.x_pos, renderer->polygon_pool);
	ClearPolygon(polygonB, renderer->alt_polygon_pool);
// PrintPolygon(polygonA, mesh->vertices);

// printf("Clipping on y_neg\n");
	ClipPolygonPlane(polygonA, polygonB, mesh, renderer->clip_planes.y_neg, renderer->alt_polygon_pool);
	ClearPolygon(polygonA, renderer->polygon_pool);
// PrintPolygon(polygonB, mesh->vertices);

// printf("Clipping on y_pos\n");
	ClipPolygonPlane(polygonB, polygonA, mesh, renderer->clip_planes.y_pos, renderer->polygon_pool);
	ClearPolygon(polygonB, renderer->alt_polygon_pool);
// PrintPolygon(polygonA, mesh->vertices);

// printf("Clipping on z_neg\n");
	ClipPolygonPlane(polygonA, polygonB, mesh, renderer->clip_planes.z_neg, renderer->alt_polygon_pool);
	ClearPolygon(polygonA, renderer->polygon_pool);
// PrintPolygon(polygonB, mesh->vertices);

// printf("Clipping on z_pos\n");
	ClipPolygonPlane(polygonB, polygonA, mesh, renderer->clip_planes.z_pos, renderer->polygon_pool);
	ClearPolygon(polygonB, renderer->alt_polygon_pool);
// PrintPolygon(polygonA, mesh->vertices);

	return polygonA;
}

void ClipPolygonPlane(int *srcPolygon, int *dstPolygon, Mesh *mesh, Plane clipBoundary, int *polygon_pool)
/* Sutherland-Hodgeman clipping of a polygon against a plane.
srcPolygon and dstPolygon both reference vertices in mesh->vertices. Vertices
are added to the mesh as needed, and v-indices are added to dstPolygon as needed
*/
{
	Vertex *intersection, *current, *previous;



@@ 648,13 688,16 @@ void ClipPolygonPlane(int *srcPolygon, int *dstPolygon, Mesh *mesh, Plane clipBo
	}
}

// TODO: profile & optimize
bool VertexInside(Vertex *v, Plane clipBoundary)
/* Tests whether a vertex is inside a plane. Planes have outward-facing normals */
{
	Vector n = clipBoundary.normal;
	return DotProd4D(n, *v) <= 0;
}

Vertex ClipIntersect(Vertex *s, Vertex *p, Plane clipBoundary)
/* Calculates the intersectino of a line segment with a plane */
{
	double d1 = DotProd4D(clipBoundary.normal, *s);
	double d2 = DotProd4D(clipBoundary.normal, *p);


@@ 670,7 713,9 @@ Vertex ClipIntersect(Vertex *s, Vertex *p, Plane clipBoundary)
// 	// TODO
// }

// TODO: find a more efficient way to wireframe a mesh without duplicating edges
void WireframePolygon(int *polygon, Vertex *vertices, Renderer *renderer)
/* Rasterizes lines between each edge of a polygon */
{
	Vertex *current, *previous;
	previous = LastPolygonVertex(polygon, vertices);


@@ 755,6 800,7 @@ void RasterLineSteep(Vertex a, Vertex b, Color color, Renderer *renderer)
}

void WritePixel(int x, int y, Color color, Renderer *renderer)
/* Writes a pixel into a renderer's frame buffer */
{
	Pixel p = ColorPixel(color);
	if (x >= 0 && x < renderer->width && y >= 0 && y < renderer->height) {


@@ 763,12 809,14 @@ void WritePixel(int x, int y, Color color, Renderer *renderer)
}

Color GetPixel(int x, int y, Renderer *renderer)
/* Reads a pixel from a renderer's frame buffer */
{
	Pixel p = renderer->pixels[(renderer->height - 1 - y)*renderer->width+x];
	return PixelColor(p);
}

void WriteDepth(int x, int y, DepthVal value, Renderer *renderer)
/* Writes a depth value into a renderer's depth buffer */
{
	if (x >= 0 && x < renderer->width && y >= 0 && y < renderer->height) {
		renderer->depthBuffer[(renderer->height - 1 - y)*renderer->width+x] = value;


@@ 776,6 824,8 @@ void WriteDepth(int x, int y, DepthVal value, Renderer *renderer)
}

bool UpdateDepthBuffer(int x, int y, double z, Renderer *renderer)
/* Returns whether z is in front of a depth value at an x, y position. Updates
the depth buffer with the z value if it is in front */
{
	if (z > renderer->depthBuffer[y*renderer->width+x]) {
		renderer->depthBuffer[y*renderer->width+x] = z;


@@ 791,6 841,7 @@ bool UpdateDepthBuffer(int x, int y, double z, Renderer *renderer)
// }

Model *NewModel(Model *pool)
/* Creates and initializes a Model */
{
	assert(PoolTotal(pool) < MAX_SCENE_OBJECTS);
	Model *model = PoolNext(pool);


@@ 802,6 853,8 @@ Model *NewModel(Model *pool)
}

Mesh *NewMesh(void)
/* Allocates and initializes a Mesh. A Mesh owns a private vertex pool and
polygon pool to track its vertices and polygons */
{
	Mesh *mesh = malloc(sizeof(Mesh));
	mesh->vertices = NewPool(Vertex, MAX_MESH_VERTICES);

M src/transform.c => src/transform.c +14 -6
@@ 3,6 3,7 @@
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <assert.h>

Transform Identity(void)
{


@@ 50,10 51,10 @@ Transform *NewTransform(void)
	return t;
}

Transform FromTransform(Transform transform)
Transform CloneTransform(Transform transform)
{
	int i, j;
	Transform newTransform = Identity();
	Transform newTransform;
	for (i = 0; i < 4; i++) {
		for (j = 0; j < 4; j++) {
			newTransform.m[i][j] = transform.m[i][j];


@@ 66,7 67,7 @@ void AddTransform(Transform A, Transform *subject)
/* combine two transforms that happen in subject, then A order */
{
	int i, j, s;
	Transform B = FromTransform(*subject);
	Transform B = CloneTransform(*subject);

	for (i = 0; i < 4; i++) {
		for (j = 0; j < 4; j++) {


@@ 118,7 119,6 @@ Vector ApplyTransform(Transform m, Vector v)
		m.m[3][2] * v.z +
		m.m[3][3];

	// printf("w: %f, %f, %f, %f\n", x, y, z, W);
	result.x = x;
	result.y = y;
	result.z = z;


@@ 132,6 132,7 @@ void CommitTransform(Transform t, Vector *v)
}

Transform RotationX(double angle)
/* angle in degrees */
{
	Transform t = Identity();
	t.m[1][1] = cos(Rad(angle));


@@ 142,6 143,7 @@ Transform RotationX(double angle)
}

Transform *RotateX(Transform *t, double angle)
/* angle in degrees */
{
	Transform rotation = RotationX(angle);
	AddTransform(rotation, t);


@@ 149,6 151,7 @@ Transform *RotateX(Transform *t, double angle)
}

Transform RotationY(double angle)
/* angle in degrees */
{
	Transform t = Identity();
	t.m[0][0] = cos(Rad(angle));


@@ 159,6 162,7 @@ Transform RotationY(double angle)
}

Transform *RotateY(Transform *t, double angle)
/* angle in degrees */
{
	Transform rotation = RotationY(angle);
	AddTransform(rotation, t);


@@ 166,6 170,7 @@ Transform *RotateY(Transform *t, double angle)
}

Transform RotationZ(double angle)
/* angle in degrees */
{
	Transform t = Identity();
	t.m[0][0] = cos(Rad(angle));


@@ 176,6 181,7 @@ Transform RotationZ(double angle)
}

Transform *RotateZ(Transform *t, double angle)
/* angle in degrees */
{
	Transform rotation = RotationZ(angle);
	AddTransform(rotation, t);


@@ 183,6 189,7 @@ Transform *RotateZ(Transform *t, double angle)
}

Transform Rotation(double rx, double ry, double rz)
/* angle in degrees */
{
	Transform t = RotationX(rx);
	AddTransform(RotationY(ry), &t);


@@ 191,6 198,7 @@ Transform Rotation(double rx, double ry, double rz)
}

Transform *Rotate(Transform *t, double rx, double ry, double rz)
/* angle in degrees */
{
	Transform rotation = Rotation(rx, ry, rz);
	AddTransform(rotation, t);


@@ 200,7 208,7 @@ Transform *Rotate(Transform *t, double rx, double ry, double rz)
Transform Translation(Vector dv)
{
	Transform T = Identity();
	dv = Homogenize(dv);
	assert(dv.w == 1);
	T.m[0][3] = dv.x;
	T.m[1][3] = dv.y;
	T.m[2][3] = dv.z;


@@ 237,7 245,7 @@ double Determinant(Transform mat)
	double result;

	n = 4;
	Transform M = FromTransform(mat);
	Transform M = CloneTransform(mat);

	for (i = 0; i < n-1; i++) {
		for (j = i + 1; j < n; j++)

M src/transform.h => src/transform.h +16 -4
@@ 4,7 4,8 @@
#include <stdbool.h>

typedef struct Transform {
	double m[4][4]; // [row][column], and we post-multiply column-vectors
	/* [row][column], and we post-multiply column-vectors */
	double m[4][4];
} Transform;

#define MapRange(v, a0, a1, b0, b1) (((v)-(a0))*((b1)-(b0))/((a1)-(a0))+(b0))


@@ 14,11 15,22 @@ Transform Identity(void);
void SetIdentity(Transform *m);
bool IsIdentity(Transform t);
Transform *NewTransform(void);
Transform FromTransform(Transform transform);

/* Creates a copy of a transform */
Transform CloneTransform(Transform transform);

/* Multiplies A into subject as (A · subject) */
void AddTransform(Transform A, Transform *subject);

/* Multiplies A and B without modifying them, as (A · B) */
Transform ComposeTransform(Transform A, Transform B);
Vector ApplyTransform(Transform t, Vector v);
void CommitTransform(Transform t, Vector *v);

/* Multiplies T to v as (T · v) without modifying */
Vector ApplyTransform(Transform T, Vector v);

/* Multiplies T into v as (T · v) */
void CommitTransform(Transform T, Vector *v);

Transform RotationX(double angle);
Transform *RotateX(Transform *t, double angle);
Transform RotationY(double angle);

M src/vector.c => src/vector.c +11 -25
@@ 31,15 31,12 @@ Vector V4D(double x, double y, double z, double w)
  return v;
}

Vector Homogenize(Vector v4d)
{
  Vector v = {
    v4d.x / v4d.w,
    v4d.y / v4d.w,
    v4d.z / v4d.w,
    1
  };
  return v;
void Homogenize(Vector *v)
{
	v->x /= v->w;
	v->y /= v->w;
	v->z /= v->w;
	v->w = 1;
}

Vector *FromVector(Vector v)


@@ 49,24 46,13 @@ Vector *FromVector(Vector v)

bool VectorEqual(Vector a, Vector b)
{
  return fabs(a.x - b.x) < EPSILON &&
      fabs(a.y - b.y) < EPSILON &&
      fabs(a.z - b.z) < EPSILON &&
      fabs(a.w - b.w) < EPSILON;
}

vIndex FindVertex(Vertex **vertices, Vertex *vertex)
{
	int i;
	for (i = 0; i < list_count(vertices); i++) {
		if (VectorEqual(*vertices[i], *vertex)) {
			return i;
		}
	}
	return VERTEX_NOT_FOUND;
  return
	fabs(a.x - b.x) < EPSILON &&
	fabs(a.y - b.y) < EPSILON &&
	fabs(a.z - b.z) < EPSILON &&
	fabs(a.w - b.w) < EPSILON;
}


Vector NegVec(Vector v)
{
	return V(-v.x, -v.y, -v.z);

M src/vector.h => src/vector.h +1 -1
@@ 10,7 10,7 @@ typedef struct Vertex {
	double x, y, z, w;
} Vector, Vertex;

Vector Homogenize(Vector v4d);
void Homogenize(Vector *v);
#define FromVertex(v) FromVector(v)
Vector *FromVector(Vector v);
bool VectorEqual(Vector a, Vector b);

M test/transform.test.c => test/transform.test.c +1 -1
@@ 19,7 19,7 @@ void TestFromTransform(void)
	t.m[2][3] = 3;
	t.m[0][3] = 9;
	t.m[2][1] = 7;
	Transform u = FromTransform(t);
	Transform u = CloneTransform(t);
	t.m[2][2] = 0;

	AssertEqual(u.m[2][3], 3);