--- /dev/null
+
+PROJECT = project1
+SRCS = main.c common.c list.c pixmap.c scene.c tri.c
+
+VIEWER = feh
+
+CC = gcc
+CFLAGS = -std=c99 -O0 -ggdb
+CPPFLAGS= -MMD -DDEBUG
+LDLIBS = -lm
+
+OBJS = $(SRCS:%.c=%.o)
+DEPS = $(OBJS:%.o=%.d)
+
+all: $(PROJECT)
+
+$(PROJECT): $(OBJS)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
+
+clean:
+ rm -f $(PROJECT) $(OBJS) $(DEPS)
+
+distclean: clean
+ rm -f scene.ppm scene.bmp
+
+run: $(PROJECT)
+ ./$< && $(VIEWER) scene.ppm
+
+debug: $(PROJECT)
+ gdb ./$<
+
+-include $(DEPS)
+
--- /dev/null
+
+/*
+ * CS5600 University of Utah
+ * Charles McGarvey
+ * mcgarvey@eng.utah.edu
+ */
+
+#ifndef __COLOR_H__
+#define __COLOR_H__
+
+#include "common.h"
+
+
+/*
+ * A color channel will be the same as a scalar.
+ */
+typedef scal_t colorchan_t;
+
+/*
+ * A color class.
+ * Colors are represented by RGBA values between 0.0 and 1.0.
+ */
+struct color
+{
+ colorchan_t r;
+ colorchan_t g;
+ colorchan_t b;
+ colorchan_t a;
+};
+typedef struct color color_t;
+
+/*
+ * Initialize a color.
+ */
+__fast__
+void color_init(color_t* c, colorchan_t r, colorchan_t g, colorchan_t b, colorchan_t a)
+{
+ c->r = r;
+ c->g = g;
+ c->b = b;
+ c->a = a;
+}
+
+
+/*
+ * Create a new color, copied by value.
+ */
+__fast__
+color_t color_new(colorchan_t r, colorchan_t g, colorchan_t b, colorchan_t a)
+{
+ color_t c;
+ color_init(&c, r, g, b, a);
+ return c;
+}
+
+#define COLOR_CLEAR color_new(S(0.0), S(0.0), S(0.0), S(0.0))
+#define COLOR_BLACK color_new(S(0.0), S(0.0), S(0.0), S(1.0))
+#define COLOR_RED color_new(S(1.0), S(0.0), S(0.0), S(1.0))
+#define COLOR_GREEN color_new(S(0.0), S(1.0), S(0.0), S(1.0))
+#define COLOR_BLUE color_new(S(0.0), S(0.0), S(1.0), S(1.0))
+#define COLOR_YELLOW color_new(S(1.0), S(1.0), S(0.0), S(1.0))
+#define COLOR_MAGENTA color_new(S(1.0), S(0.0), S(1.0), S(1.0))
+#define COLOR_CYAN color_new(S(0.0), S(1.0), S(1.0), S(1.0))
+#define COLOR_WHITE color_new(S(1.0), S(1.0), S(1.0), S(1.0))
+
+
+/*
+ * Define integer types for a 32-bit RGBA color representation.
+ */
+typedef uint32_t rgba_t;
+typedef uint8_t rgbachan_t;
+
+/*
+ * Create a new color from a 32-bit RGBA value.
+ */
+__fast__
+color_t color_from_rgba(rgba_t n)
+{
+ colorchan_t r = (colorchan_t)UNPACK(n, 3) / S(255.0);
+ colorchan_t g = (colorchan_t)UNPACK(n, 2) / S(255.0);
+ colorchan_t b = (colorchan_t)UNPACK(n, 1) / S(255.0);
+ colorchan_t a = (colorchan_t)UNPACK(n, 0) / S(255.0);
+ return color_new(r, g, b, a);
+}
+
+/*
+ * Split a color into 8-bit RGBA channels.
+ */
+__fast__
+void color_split(color_t c, rgbachan_t* r, rgbachan_t* g, rgbachan_t* b, rgbachan_t* a)
+{
+ if (r) *r = (rgbachan_t)(c.r * S(255.0));
+ if (g) *g = (rgbachan_t)(c.g * S(255.0));
+ if (b) *b = (rgbachan_t)(c.b * S(255.0));
+ if (a) *a = (rgbachan_t)(c.a * S(255.0));
+}
+
+/*
+ * Convert a color to a 32-bit RGBA value.
+ */
+__fast__
+rgba_t rgba_from_color(color_t c)
+{
+ rgbachan_t r, g, b, a;
+ color_split(c, &r, &g, &b, &a);
+ return ((rgba_t)r << 24) | ((rgba_t)g << 16) | ((rgba_t)b << 8) | (rgba_t)a;
+}
+
+
+#endif // __COLOR_H__
+
--- /dev/null
+
+/*
+ * CS5600 University of Utah
+ * Charles McGarvey
+ * mcgarvey@eng.utah.edu
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+
+
+static void* (*_mem_fn)(void*, size_t) = NULL;
+
+static int _mem_blocks = 0;
+
+
+/*
+ * Check the result of the allocation function and call the callback function
+ * if it failed.
+ */
+__fast__
+void* _mem_check(void* mem, void* old, size_t size)
+{
+ if (mem == NULL && size != 0) {
+ if (_mem_fn) {
+ mem = _mem_fn(old, size);
+ if (mem != NULL) {
+ goto done;
+ }
+ }
+ fprintf(stderr, "Memory allocation failed: %s\n", strerror(errno));
+ abort();
+ }
+
+done:
+ if (old == NULL) {
+ ++_mem_blocks;
+ }
+ return mem;
+}
+
+
+void* mem_alloc(size_t size)
+{
+ void* mem = _mem_check(malloc(size), NULL, size);
+#ifdef MEM_TRACE
+ fprintf(stderr, " ALLOC %6d %18p %10zd\n", _mem_blocks - 1, mem, size);
+#endif
+ return mem;
+}
+
+void* mem_realloc(void* mem, size_t size)
+{
+ void* old = mem;
+ mem = _mem_check(realloc(mem, size), mem, size);
+#ifdef MEM_TRACE
+ fprintf(stderr, " REALLOC %6d %18p %10zd (was %p)\n", _mem_blocks - 1, mem, size, old);
+#endif
+ return mem;
+}
+
+void mem_free(void* mem)
+{
+ --_mem_blocks;
+#ifdef MEM_TRACE
+ fprintf(stderr, " FREE %6d %18p\n", _mem_blocks, mem);
+#endif
+ free(mem);
+}
+
+void mem_set_fn(void* (*fn)(void*, size_t))
+{
+ _mem_fn = fn;
+}
+
+int mem_blocks()
+{
+ return _mem_blocks;
+}
+
+
+void rtrim(char *str)
+{
+ char *m;
+ for (m = str + strlen(str) - 1; str <= m && isspace((int)*m); --m);
+ m[1] = '\0';
+}
+
+void ltrim(char *str)
+{
+ char *m;
+ for (m = str; *m && isspace((int)*m); ++m);
+ memmove(str, m, strlen(m) + 1);
+}
+
--- /dev/null
+
+/*
+ * CS5600 University of Utah
+ * Charles McGarvey
+ * mcgarvey@eng.utah.edu
+ */
+
+#ifndef __COMMON_H__
+#define __COMMON_H__
+
+#include <math.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+/*
+ * Define a type for scalar values, either float or double.
+ */
+#ifdef USE_DOUBLE
+typedef double scal_t;
+#define S(K) K
+#define scal_sin sin
+#define scal_cos cos
+#else
+typedef float scal_t;
+#define S(K) K##f
+#define scal_sin sinf
+#define scal_cos cosf
+#endif
+
+#define S_ZERO S(0.0)
+
+
+/*
+ * Define a keyword for use while defining small and fast functions.
+ */
+#define __fast__ static inline
+
+
+/*
+ * Define some macros for packing and unpacking bytes to and from larger ints.
+ */
+#define PACK(W,N,B) (((B) << (8 * (N))) | ((W) & ~(0xff << (8 * (N)))))
+#define UNPACK(W,N) ((uint8_t)((W) >> (8 * (N))) & 0xff)
+
+
+typedef void (*dtor_t)(void*);
+#define DTOR(A) (dtor_t)(A)
+
+
+/*
+ * Allocate a block of memory of a certain size. This follows the semantics
+ * of malloc(3), except it will never return NULL and will abort(3) if the
+ * memory could not be allocated.
+ */
+void* mem_alloc(size_t size);
+
+/*
+ * Change the size of a block of memory. This follows the semantics of
+ * realloc(3), except it will never return NULL and will abort(3) if the
+ * memory could not be allocated.
+ */
+void* mem_realloc(void* mem, size_t size);
+
+/*
+ * Deallocate a block of memory previously allocated by mem_alloc or malloc(3)
+ * and friends. This is essentially just a call to free(3).
+ */
+void mem_free(void* mem);
+
+/*
+ * Set a function to call if either mem_alloc or mem_realloc fails, or NULL if
+ * no callback should be called. The callback takes the same arguments as
+ * realloc(3) and may try to fulfill the request. The return value of the
+ * callback function will be returned from the allocation function and must be
+ * a valid pointer to an allocated block of memory. The callback function
+ * should not call mem_alloc or mem_realloc and must not return if a block of
+ * memory could not be allocated.
+ */
+void mem_set_fn(void* (*fn)(void*, size_t));
+
+/*
+ * Get the number of blocks currently allocated with either mem_alloc or
+ * mem_realloc. This number should be zero at the end of a process running
+ * this program.
+ */
+int mem_blocks();
+
+
+/*
+ * Trim white space off of the right side of a string.
+ */
+void rtrim(char *str);
+
+/*
+ * Trim white space off of the left side of a string.
+ */
+void ltrim(char *str);
+
+/*
+ * Trim white space off of both sides of a string.
+ */
+__fast__
+void trim(char *str)
+{
+ rtrim(str);
+ ltrim(str);
+}
+
+
+#endif // __COMMON_H__
+
--- /dev/null
+
+/*
+ * CS5600 University of Utah
+ * Charles McGarvey
+ * mcgarvey@eng.utah.edu
+ */
+
+#include "list.h"
+
+void list_reverse(list_t** l)
+{
+ list_t* p = *l;
+ list_t* n = p->link;
+
+ p->link = NULL;
+
+ while (n) {
+ list_t* t = n->link;
+ n->link = p;
+ p = n;
+ n = t;
+ }
+
+ *l = p;
+}
+
--- /dev/null
+
+/*
+ * CS5600 University of Utah
+ * Charles McGarvey
+ * mcgarvey@eng.utah.edu
+ */
+
+#ifndef __LIST_H__
+#define __LIST_H__
+
+#include "common.h"
+
+
+/*
+ * A linked-list with a stack-like interface.
+ * The value of each node can be any pointer.
+ */
+struct list
+{
+ void* val;
+ struct list* link;
+ void (*dtor)(void*);
+};
+typedef struct list list_t;
+
+
+/*
+ * Add a value to the list. It will become the first item.
+ * This is a O(1) operation.
+ */
+__fast__
+void list_push2(list_t** l, void* value, void (*destructor)(void*))
+{
+ list_t* n = (list_t*)mem_alloc(sizeof(list_t));
+ n->val = value;
+ n->link = *l;
+ n->dtor = destructor;
+ *l = n;
+}
+
+/*
+ * Add a value to the list with no destructor set.
+ */
+__fast__
+void list_push(list_t** l, void* value)
+{
+ list_push2(l, value, 0);
+}
+
+
+/*
+ * Create a new list with a single value.
+ */
+__fast__
+list_t* list_new2(void* value, void (*destructor)(void*))
+{
+ list_t* l = NULL;
+ list_push2(&l, value, destructor);
+ return l;
+}
+
+/*
+ * Create a new list with a single value without a destructor set.
+ */
+__fast__
+list_t* list_new(void* value)
+{
+ list_t* l = NULL;
+ list_push2(&l, value, 0);
+ return l;
+}
+
+
+/*
+ * Remove a value from the front of the list. If the node has dtor set, it
+ * will be used to destroy the value.
+ * This is a O(1) operation.
+ */
+__fast__
+void list_pop(list_t** l)
+{
+ list_t* n = (*l)->link;
+
+ if ((*l)->dtor) {
+ (*l)->dtor((*l)->val);
+ }
+ mem_free(*l);
+
+ *l = n;
+}
+
+/*
+ * Destroy the list, freeing up all of its memory.
+ * This is a O(n) operation.
+ */
+__fast__
+void list_destroy(list_t* l)
+{
+ while (l) {
+ list_pop(&l);
+ }
+}
+
+
+/*
+ * Reverse the order of the items in the list.
+ * This is a O(n) operation.
+ */
+void list_reverse(list_t** l);
+
+
+#endif // __LIST_H__
+
--- /dev/null
+
+/*
+ * CS5600 University of Utah
+ * Charles McGarvey
+ * mcgarvey@eng.utah.edu
+ */
+
+#include <stdio.h>
+
+#include "pixmap.h"
+#include "scene.h"
+
+
+/*
+ * Read a scene file, construct the scene object, draw the scene to a pixmap,
+ * and export the pixmap in PPM and BMP formats.
+ */
+int main(int argc, char* argv[])
+{
+ scene_t* scene = scene_alloc("scene.u2d");
+ if (scene == NULL) {
+ fprintf(stderr, "An error prevented the scene from loading. Aborting!\n");
+ return 1;
+ }
+
+ pixmap_t* raster = scene_render(scene);
+ scene_destroy(scene);
+
+ pixmap_export_ppm(raster, "scene.ppm");
+ pixmap_export_bmp(raster, "scene.bmp");
+
+ pixmap_destroy(raster);
+
+#ifndef NDEBUG
+ int _blocks = mem_blocks();
+ if (_blocks != 0) {
+ fprintf(stderr, " *** Leaked %d blocks of memory! ***\n", _blocks);
+ return 1;
+ }
+#endif
+ return 0;
+}
+
--- /dev/null
+
+/*
+ * CS5600 University of Utah
+ * Charles McGarvey
+ * mcgarvey@eng.utah.edu
+ */
+
+#ifndef __MAT_H__
+#define __MAT_H__
+
+#include "common.h"
+#include "vec.h"
+
+
+/*
+ * A simple matrix class with column-major storage and notation.
+ */
+struct mat
+{
+ vec_t v[4];
+};
+typedef struct mat mat_t;
+
+/*
+ * Initialize a matrix with individual components, row by row.
+ */
+__fast__
+void mat_init(mat_t* m, scal_t m11, scal_t m12, scal_t m13, scal_t m14,
+ scal_t m21, scal_t m22, scal_t m23, scal_t m24,
+ scal_t m31, scal_t m32, scal_t m33, scal_t m34,
+ scal_t m41, scal_t m42, scal_t m43, scal_t m44)
+{
+ m->v[0] = vec_new2(m11, m21, m31, m41);
+ m->v[1] = vec_new2(m12, m22, m32, m42);
+ m->v[2] = vec_new2(m13, m23, m33, m43);
+ m->v[3] = vec_new2(m14, m24, m34, m44);
+}
+
+
+/*
+ * Create a new matrix with individual components, row by row.
+ */
+__fast__
+mat_t mat_new(scal_t m11, scal_t m12, scal_t m13, scal_t m14,
+ scal_t m21, scal_t m22, scal_t m23, scal_t m24,
+ scal_t m31, scal_t m32, scal_t m33, scal_t m34,
+ scal_t m41, scal_t m42, scal_t m43, scal_t m44)
+{
+ mat_t m;
+ mat_init(&m, m11, m12, m13, m14,
+ m21, m22, m23, m24,
+ m31, m32, m33, m34,
+ m41, m42, m43, m44);
+ return m;
+}
+
+/*
+ * Create a new matrix with four column vectors.
+ */
+__fast__
+mat_t mat_new2(vec_t a, vec_t b, vec_t c, vec_t d)
+{
+ mat_t m;
+ m.v[0] = a;
+ m.v[1] = b;
+ m.v[2] = c;
+ m.v[3] = d;
+ return m;
+}
+
+#define MAT_IDENTITY mat_new(S(1.0), S(0.0), S(0.0), S(0.0), \
+ S(0.0), S(1.0), S(0.0), S(0.0), \
+ S(0.0), S(0.0), S(1.0), S(0.0), \
+ S(0.0), S(0.0), S(0.0), S(1.0))
+
+
+/*
+ * Create a new translate matrix.
+ */
+__fast__
+mat_t MAT_TRANSLATE(scal_t x, scal_t y, scal_t z)
+{
+ return mat_new(S(1.0), S(0.0), S(0.0), x,
+ S(0.0), S(1.0), S(0.0), y,
+ S(0.0), S(0.0), S(1.0), z,
+ S(0.0), S(0.0), S(0.0), S(1.0));
+}
+
+/*
+ * Create a new scale matrix.
+ */
+__fast__
+mat_t MAT_SCALE(scal_t x, scal_t y, scal_t z)
+{
+ return mat_new( x, S(0.0), S(0.0), S(0.0),
+ S(0.0), y, S(0.0), S(0.0),
+ S(0.0), S(0.0), z, S(0.0),
+ S(0.0), S(0.0), S(0.0), S(1.0));
+}
+
+/*
+ * Create a rotation matrix (around the Z axis).
+ */
+__fast__
+mat_t MAT_ROTATE_Z(scal_t a)
+{
+ scal_t sin_a = scal_sin(a);
+ scal_t cos_a = scal_cos(a);
+ return mat_new( cos_a, -sin_a, S(0.0), S(0.0),
+ sin_a, cos_a, S(0.0), S(0.0),
+ S(0.0), S(0.0), S(1.0), S(0.0),
+ S(0.0), S(0.0), S(0.0), S(1.0));
+}
+
+/*
+ * Create a 2D orthogonal projection matrix.
+ */
+__fast__
+mat_t MAT_ORTHO(scal_t left, scal_t right, scal_t bottom, scal_t top)
+{
+ scal_t rml = right - left;
+ scal_t rpl = right + left;
+ scal_t tmb = top - bottom;
+ scal_t tpb = top + bottom;
+ return mat_new(S(2.0) / rml, S(0.0), S(0.0), -rpl / rml,
+ S(0.0), S(2.0) / tmb, S(0.0), -tpb / tmb,
+ S(0.0), S(0.0), S(-1.0), S(0.0),
+ S(0.0), S(0.0), S(0.0), S(1.0));
+}
+
+/*
+ * Create a viewport matrix.
+ */
+__fast__
+mat_t MAT_VIEWPORT(int x, int y, unsigned w, unsigned h)
+{
+ scal_t xs = (scal_t)x;
+ scal_t ys = (scal_t)y;
+ scal_t ws = (scal_t)w / S(2.0);
+ scal_t hs = (scal_t)h / S(2.0);
+ return mat_new( ws, S(0.0), S(0.0), ws + xs,
+ S(0.0), hs, S(0.0), hs + ys,
+ S(0.0), S(0.0), S(1.0), S(0.0),
+ S(0.0), S(0.0), S(0.0), S(1.0));
+}
+
+
+/*
+ * Get a column vector (can also access the vector array directly).
+ */
+__fast__
+vec_t mat_col(mat_t m, int i)
+{
+ return m.v[i];
+}
+
+/*
+ * Get a row vector.
+ */
+__fast__
+vec_t mat_row(mat_t m, int i)
+{
+ switch (i) {
+ case 0:
+ return vec_new2(m.v[0].x, m.v[1].x, m.v[2].x, m.v[3].x);
+ case 1:
+ return vec_new2(m.v[0].y, m.v[1].y, m.v[2].y, m.v[3].y);
+ case 2:
+ return vec_new2(m.v[0].z, m.v[1].z, m.v[2].z, m.v[3].z);
+ case 3:
+ return vec_new2(m.v[0].w, m.v[1].w, m.v[2].w, m.v[3].w);
+ }
+}
+
+
+/*
+ * Multiply two matrices together.
+ */
+__fast__
+mat_t mat_mult(mat_t a, mat_t b)
+{
+#define _DOT(I,J) vec_dot(mat_row(a,I), mat_col(b,J))
+ return mat_new(_DOT(0,0), _DOT(0,1), _DOT(0,2), _DOT(0,3),
+ _DOT(1,0), _DOT(1,1), _DOT(1,2), _DOT(1,3),
+ _DOT(2,0), _DOT(2,1), _DOT(2,2), _DOT(2,3),
+ _DOT(3,0), _DOT(3,1), _DOT(3,2), _DOT(3,3));
+#undef _DOT
+}
+
+/*
+ * Transform a vector using a matrix.
+ */
+__fast__
+vec_t mat_apply(mat_t m, vec_t v)
+{
+ return vec_new2(vec_dot(v,mat_row(m,0)),
+ vec_dot(v,mat_row(m,1)),
+ vec_dot(v,mat_row(m,2)),
+ vec_dot(v,mat_row(m,3)));
+}
+
+
+#endif // __MAT_H__
+
--- /dev/null
+
+/*
+ * CS5600 University of Utah
+ * Charles McGarvey
+ * mcgarvey@eng.utah.edu
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "pixmap.h"
+
+
+struct pixmap
+{
+ color_t* pixels;
+ int w;
+ int h;
+ int left, right, bottom, top;
+ mat_t modelview;
+ mat_t projection;
+ mat_t final;
+};
+
+
+pixmap_t* pixmap_alloc(int width, int height, color_t fill)
+{
+ assert(0 < width && 0 < height && "zero-dimension pixmap not allowed");
+ size_t size = width * height;
+
+ pixmap_t* p = (pixmap_t*)mem_alloc(sizeof(pixmap_t));
+ p->pixels = (color_t*)mem_alloc(sizeof(color_t) * size);
+ p->w = width;
+ p->h = height;
+ p->left = p->bottom = 0;
+ p->right = width;
+ p->top = height;
+ pixmap_clear(p, fill);
+ return p;
+}
+
+void pixmap_destroy(pixmap_t* p)
+{
+ mem_free(p->pixels);
+ mem_free(p);
+}
+
+
+void pixmap_clear(pixmap_t* p, color_t fill)
+{
+ size_t size = p->w * p->h;
+ for (int i = 0; i < size; ++i) {
+ p->pixels[i] = fill;
+ }
+}
+
+
+static void _pixmap_set_transformation(pixmap_t* p)
+{
+ /*
+ * Just including a viewport transformation in the final transformation.
+ * This could probably be faster by separating this out.
+ */
+ mat_t viewport = MAT_VIEWPORT(p->left, p->bottom, p->right - p->left, p->top - p->bottom);
+ p->final = mat_mult(p->projection, p->modelview);
+ p->final = mat_mult(viewport, p->final);
+}
+
+
+void pixmap_viewport(pixmap_t* p, int x, int y, int width, int height)
+{
+ p->left = x;
+ p->right = x + width;
+ p->bottom = y;
+ p->top = y + height;
+ _pixmap_set_transformation(p);
+}
+
+void pixmap_modelview(pixmap_t* p, const mat_t* transform)
+{
+ p->modelview = *transform;
+ _pixmap_set_transformation(p);
+}
+
+void pixmap_projection(pixmap_t* p, const mat_t* transform)
+{
+ p->projection = *transform;
+ _pixmap_set_transformation(p);
+}
+
+
+#define _CHECK_WRITE(X) if ((X) <= 0) goto fail
+
+int pixmap_export_ppm(const pixmap_t* p, const char* filename)
+{
+ FILE* file = fopen(filename, "w");
+ if (file == NULL) {
+fail: fprintf(stderr, "Cannot write to %s: %s\n", filename, strerror(errno));
+ return -1;
+ }
+
+ _CHECK_WRITE(fprintf(file, "P3\n%u %u\n255\n", p->w, p->h));
+ for (int y = (int)p->h - 1; y >= 0; --y) {
+ for (int x = 0; x < p->w; ++x) {
+ rgbachan_t r, g, b;
+ color_split(p->pixels[y * p->w + x], &r, &g, &b, NULL);
+ _CHECK_WRITE(fprintf(file, "%hhu %hhu %hhu\n", r, g, b));
+ }
+ }
+
+ fclose(file);
+ return 0;
+}
+
+int pixmap_export_bmp(const pixmap_t* p, const char* filename)
+{
+ /*
+ * This function was adapted from sample code provided with the assignment
+ * instructions.
+ */
+ FILE* file = fopen(filename, "wb");
+ if (file == NULL) {
+fail: fprintf(stderr, "Cannot write to %s: %s\n", filename, strerror(errno));
+ return -1;
+ }
+
+ uint16_t magicNumber = 0x4D42;
+ uint16_t reserved0 = 0;//0x4D41;
+ uint16_t reserved1 = 0;//0x5454;
+ uint32_t dataOffset = 54;
+ uint32_t infoHeaderSize = 40;
+ uint32_t width = p->w;
+ uint32_t height = p->h;
+ uint16_t colorPlanes = 1;
+ uint16_t bitsPerPixel = 32;
+ uint32_t compression = 0;
+ uint32_t dataSize = width * height * bitsPerPixel / 8;
+ uint32_t horizontalResolution = 2835;
+ uint32_t verticalResolution = 2835;
+ uint32_t paletteColorCount = 0;
+ uint32_t importantPaletteColorCount = 0;
+ uint32_t fileSize = 54 + dataSize;
+
+ /*
+ * Check the return values to avoid loud warnings.
+ */
+ _CHECK_WRITE(fwrite(&magicNumber, sizeof(magicNumber), 1, file));
+ _CHECK_WRITE(fwrite(&fileSize, sizeof(fileSize), 1, file));
+ _CHECK_WRITE(fwrite(&reserved0, sizeof(reserved0), 1, file));
+ _CHECK_WRITE(fwrite(&reserved1, sizeof(reserved1), 1, file));
+ _CHECK_WRITE(fwrite(&dataOffset, sizeof(dataOffset), 1, file));
+ _CHECK_WRITE(fwrite(&infoHeaderSize, sizeof(infoHeaderSize), 1, file));
+ _CHECK_WRITE(fwrite(&width, sizeof(width), 1, file));
+ _CHECK_WRITE(fwrite(&height, sizeof(height), 1, file));
+ _CHECK_WRITE(fwrite(&colorPlanes, sizeof(colorPlanes), 1, file));
+ _CHECK_WRITE(fwrite(&bitsPerPixel, sizeof(bitsPerPixel), 1, file));
+ _CHECK_WRITE(fwrite(&compression, sizeof(compression), 1, file));
+ _CHECK_WRITE(fwrite(&dataSize, sizeof(dataSize), 1, file));
+ _CHECK_WRITE(fwrite(&horizontalResolution, sizeof(horizontalResolution), 1, file));
+ _CHECK_WRITE(fwrite(&verticalResolution, sizeof(verticalResolution), 1, file));
+ _CHECK_WRITE(fwrite(&paletteColorCount, sizeof(paletteColorCount), 1, file));
+ _CHECK_WRITE(fwrite(&importantPaletteColorCount, sizeof(importantPaletteColorCount), 1, file));
+
+ size_t size = width * height;
+ for (int i = 0; i < size; ++i)
+ {
+ rgbachan_t a, r, g, b;
+ color_split(p->pixels[i], &r, &g, &b, &a);
+ uint32_t argb = PACK(argb, 3, a);
+ argb = PACK(argb, 2, r);
+ argb = PACK(argb, 1, g);
+ argb = PACK(argb, 0, b);
+ _CHECK_WRITE(fwrite(&argb, sizeof(argb), 1, file));
+ }
+
+ fclose(file);
+ return 0;
+}
+
+
+void pixmap_draw_tri(pixmap_t* p, const tri_t* triangle)
+{
+ tri_t t = tri_transform(*triangle, p->final);
+
+ for (int y = p->bottom; y < p->top; ++y) {
+ for (int x = p->left; x < p->right; ++x) {
+ vec_t pt = vec_new((scal_t)x, (scal_t)y, S(0.0));
+ vec_t b = tri_barycentric(&t, pt);
+ if (vec_is_barycentric(b)) {
+ color_t* c = p->pixels + y * p->w + x;
+ color_t color = color_new(
+ t.a.c.r * b.x + t.b.c.r * b.y + t.c.c.r * b.z,
+ t.a.c.g * b.x + t.b.c.g * b.y + t.c.c.g * b.z,
+ t.a.c.b * b.x + t.b.c.b * b.y + t.c.c.b * b.z,
+ S(1.0)
+ );
+ *c = color;
+ }
+ }
+ }
+}
+
--- /dev/null
+
+/*
+ * CS5600 University of Utah
+ * Charles McGarvey
+ * mcgarvey@eng.utah.edu
+ */
+
+#ifndef __PIXMAP_H__
+#define __PIXMAP_H__
+
+#include "color.h"
+#include "common.h"
+#include "tri.h"
+
+
+/*
+ * A pixel map for storing and manipulating a 2D grid of color values.
+ */
+typedef struct pixmap pixmap_t;
+
+
+/*
+ * Create a new pixmap on the heap.
+ */
+pixmap_t* pixmap_alloc(int width, int height, color_t fill);
+
+/*
+ * Free up the memory associated with the pixmap.
+ */
+void pixmap_destroy(pixmap_t* p);
+
+
+/*
+ * Fill the entire pixmap with a solid color.
+ */
+void pixmap_clear(pixmap_t* p, color_t fill);
+
+
+/*
+ * Set the viewport rectangle. This effectively sets up a clipping rectangle
+ * where nothing is drawn outside of the rectangle. The default viewport is
+ * [0, 0, width, height], or the entire pixmap area.
+ */
+void pixmap_viewport(pixmap_t* p, int x, int y, int width, int height);
+
+/*
+ * Set the modelview matrix. This positions the model or camera.
+ */
+void pixmap_modelview(pixmap_t* p, const mat_t* transform);
+
+/*
+ * Set the projection matrix. This provides the transformation matrix for
+ * converting to screen space.
+ */
+void pixmap_projection(pixmap_t* p, const mat_t* transform);
+
+
+/*
+ * Save the pixmap to a PPM file.
+ */
+int pixmap_export_ppm(const pixmap_t* p, const char* filename);
+
+/*
+ * Save the pixmap to a BMP file.
+ */
+int pixmap_export_bmp(const pixmap_t* p, const char* filename);
+
+
+/*
+ * Draw a smooth gradient triangle to the pixmap.
+ */
+void pixmap_draw_tri(pixmap_t* p, const tri_t* triangle);
+
+
+#endif // __PIXMAP_H__
+
--- /dev/null
+
+/*
+ * CS5600 University of Utah
+ * Charles McGarvey
+ * mcgarvey@eng.utah.edu
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "common.h"
+#include "mat.h"
+#include "list.h"
+#include "scene.h"
+#include "tri.h"
+
+
+/*
+ * A group of triangles and a transformation.
+ */
+struct _group
+{
+ list_t* triangles;
+ mat_t modelview;
+};
+typedef struct _group _group_t;
+
+/*
+ * Allocate a group by reading raw triangle coordinates from a file.
+ */
+static _group_t* _group_alloc(const char* filename)
+{
+ FILE* file = fopen(filename, "r");
+ if (file == NULL) {
+ fprintf(stderr, "Cannot read %s: %s\n", filename, strerror(errno));
+ return NULL;
+ }
+
+ _group_t* g = (_group_t*)mem_alloc(sizeof(_group_t));
+ g->triangles = NULL;
+ g->modelview = MAT_IDENTITY;
+
+ double x1, y1, z1, x2, y2, z2, x3, y3, z3;
+ while (fscanf(file, " %lf %lf %lf %lf %lf %lf %lf %lf %lf",
+ &x1, &y1, &z1, &x2, &y2, &z2, &x3, &y3, &z3) == 9) {
+ tri_t* t = tri_alloc(
+ vert_new2((scal_t)x1, (scal_t)y1, (scal_t)z1, COLOR_WHITE),
+ vert_new2((scal_t)x2, (scal_t)y2, (scal_t)z2, COLOR_WHITE),
+ vert_new2((scal_t)x3, (scal_t)y3, (scal_t)z3, COLOR_WHITE)
+ );
+ list_push2(&g->triangles, t, mem_free);
+ }
+ list_reverse(&g->triangles);
+
+ fclose(file);
+
+ if (g->triangles == NULL) {
+ fprintf(stderr, "No triangles coordinates read from %s\n", filename);
+ mem_free(g);
+ return NULL;
+ }
+
+ return g;
+}
+
+/*
+ * Destroy a group.
+ */
+static void _group_destroy(_group_t* g)
+{
+ list_destroy(g->triangles);
+ mem_free(g);
+}
+
+
+/*
+ * Set the colors of the triangles in the group as defined in a file.
+ */
+static int _group_set_colors(_group_t* g, FILE* file)
+{
+ double r1, g1, b1, r2, g2, b2, r3, g3, b3;
+ if (fscanf(file, " %lf %lf %lf %lf %lf %lf %lf %lf %lf",
+ &r1, &g1, &b1, &r2, &g2, &b2, &r3, &g3, &b3) != 9) {
+ fprintf(stderr, "Cannot read color values from scene file.\n");
+ return -1;
+ }
+
+ for (list_t* i = g->triangles; i; i = i->link) {
+ tri_t* t = (tri_t*)i->val;
+ t->a.c = color_new((colorchan_t)r1, (colorchan_t)g1, (colorchan_t)b1, S(1.0));
+ t->b.c = color_new((colorchan_t)r2, (colorchan_t)g2, (colorchan_t)b2, S(1.0));
+ t->c.c = color_new((colorchan_t)r3, (colorchan_t)g3, (colorchan_t)b3, S(1.0));
+ }
+ return 0;
+}
+
+/*
+ * Concat a translation matrix to the transformation as defined in a file.
+ */
+static int _group_add_translate(_group_t* g, FILE* file)
+{
+ double tx, ty;
+ if (fscanf(file, " %lf %lf", &tx, &ty) != 2) {
+ fprintf(stderr, "Cannot read translate coordinates from scene file.\n");
+ return -1;
+ }
+
+ g->modelview = mat_mult(g->modelview, MAT_TRANSLATE((scal_t)tx, (scal_t)ty, S(1.0)));
+ return 0;
+}
+
+/*
+ * Concat a rotation matrix to the transformation as defined in a file.
+ */
+static int _group_add_rotate(_group_t* g, FILE* file)
+{
+ double theta;
+ if (fscanf(file, " %lf", &theta) != 1) {
+ fprintf(stderr, "Cannot read rotation angle from scene file.\n");
+ return -1;
+ }
+
+ g->modelview = mat_mult(g->modelview, MAT_ROTATE_Z((scal_t)theta));
+ return 0;
+}
+
+/*
+ * Concat a scale matrix to the transformation as defined in a file.
+ */
+static int _group_add_scale(_group_t* g, FILE* file)
+{
+ double sx, sy;
+ if (fscanf(file, " %lf %lf", &sx, &sy) != 2) {
+ fprintf(stderr, "Cannot read scale factors from scene file.\n");
+ return -1;
+ }
+
+ g->modelview = mat_mult(g->modelview, MAT_SCALE((scal_t)sx, (scal_t)sy, S(1.0)));
+ return 0;
+}
+
+
+struct scene
+{
+ list_t* groups;
+ int w;
+ int h;
+ mat_t projection;
+};
+
+scene_t* scene_alloc(const char* filename)
+{
+ scene_t* s = (scene_t*)mem_alloc(sizeof(scene_t));
+ s->groups = NULL;
+
+ FILE* file = fopen(filename, "r");
+
+ int w, h;
+ double minX, minY, maxX, maxY;
+ if (fscanf(file, "U2 %d %d %lf %lf %lf %lf",
+ &w, &h, &minX, &minY, &maxX, &maxY) != 6) {
+ fprintf(stderr, "Cannot read scene file header.\n");
+ return NULL;
+ }
+
+ s->w = w;
+ s->h = h;
+ s->projection = MAT_ORTHO((scal_t)minX, (scal_t)maxX, (scal_t)minY, (scal_t)maxY);
+
+ char grp_filename[4096];
+ _group_t* g = NULL;
+
+#define _ASSERT_G \
+if (g == NULL) { \
+ fprintf(stderr, "Unexpected line before group is loaded.\n"); \
+ goto fail; \
+}
+
+ char type;
+ while (fscanf(file, " %c", &type) == 1) {
+ switch (type) {
+ case 'g':
+ if (fgets(grp_filename, 4096, file) == NULL) {
+ fprintf(stderr, "Cannot read raw triangle filename.\n");
+ }
+ trim(grp_filename);
+ g = _group_alloc(grp_filename);
+ if (g == NULL) {
+ goto fail;
+ }
+ list_push2(&s->groups, g, DTOR(_group_destroy));
+ break;
+
+ case 'c':
+ _ASSERT_G;
+ if (_group_set_colors(g, file) != 0) {
+ goto fail;
+ }
+ break;
+
+ case 't':
+ _ASSERT_G;
+ if (_group_add_translate(g, file) != 0) {
+ goto fail;
+ }
+ break;
+
+ case 'r':
+ if (_group_add_rotate(g, file) != 0) {
+ goto fail;
+ }
+ break;
+
+ case 's':
+ _ASSERT_G;
+ if (_group_add_scale(g, file) != 0) {
+ goto fail;
+ }
+ break;
+
+ default:
+ fprintf(stderr, "Unknown identifier: %c\n", type);
+ }
+ }
+ list_reverse(&s->groups);
+
+#undef _ASSERT_G
+
+ fclose(file);
+ return s;
+
+fail:
+ scene_destroy(s);
+ return NULL;
+}
+
+void scene_destroy(scene_t* s)
+{
+ list_destroy(s->groups);
+ mem_free(s);
+}
+
+
+pixmap_t* scene_render(scene_t* s)
+{
+ pixmap_t* pix = pixmap_alloc(s->w, s->h, COLOR_BLACK);
+ pixmap_projection(pix, &s->projection);
+
+ for (list_t* gi = s->groups; gi; gi = gi->link) {
+ _group_t* g = (_group_t*)gi->val;
+ pixmap_modelview(pix, &g->modelview);
+ for (list_t* ti = g->triangles; ti; ti = ti->link) {
+ pixmap_draw_tri(pix, (tri_t*)ti->val);
+ }
+ }
+
+ return pix;
+}
+
--- /dev/null
+
+/*
+ * CS5600 University of Utah
+ * Charles McGarvey
+ * mcgarvey@eng.utah.edu
+ */
+
+#ifndef __SCENE_H__
+#define __SCENE_H__
+
+#include "pixmap.h"
+
+
+/*
+ * A scene.
+ */
+typedef struct scene scene_t;
+
+/*
+ * Allocate a scene by reading in data from a file.
+ */
+scene_t* scene_alloc(const char* filename);
+
+/*
+ * Destroy a scene.
+ */
+void scene_destroy(scene_t* s);
+
+
+/*
+ * Render a scene to an in-memory pixmap. The caller takes ownership of the
+ * returned object and must destroy it when it is no longer needed.
+ */
+pixmap_t* scene_render(scene_t* s);
+
+
+#endif // __SCENE_H__
+
--- /dev/null
+U2
+500 500
+-1.0 -1.0 1.0 1.0
+g triangle.raw
+c 0.9 0.8 0.7 0.6 0.5 0.4 0.3 0.2 0.1
+s 0.5 1.0
+t 0.3 -0.2
+
+
--- /dev/null
+
+/*
+ * CS5600 University of Utah
+ * Charles McGarvey
+ * mcgarvey@eng.utah.edu
+ */
+
+#include "tri.h"
+
+vec_t tri_barycentric(const tri_t* t, vec_t v)
+{
+ vec_t c = VEC_ZERO;
+ scal_t denom = (t->b.v.y - t->c.v.y) * (t->a.v.x - t->c.v.x) + (t->c.v.x - t->b.v.x) * (t->a.v.y - t->c.v.y);
+ c.x = ((t->b.v.y - t->c.v.y) * (v.x - t->c.v.x) + (t->c.v.x - t->b.v.x) * (v.y - t->c.v.y)) / denom;
+ c.y = ((t->c.v.y - t->a.v.y) * (v.x - t->c.v.x) + (t->a.v.x - t->c.v.x) * (v.y - t->c.v.y)) / denom;
+ c.z = S(1.0) - c.x - c.y;
+ return c;
+}
+
--- /dev/null
+
+/*
+ * CS5600 University of Utah
+ * Charles McGarvey
+ * mcgarvey@eng.utah.edu
+ */
+
+#ifndef __TRI_H__
+#define __TRI_H__
+
+#include "mat.h"
+#include "vert.h"
+
+
+/*
+ * A triangle is a polygon of three vertices.
+ */
+struct tri
+{
+ vert_t a;
+ vert_t b;
+ vert_t c;
+};
+typedef struct tri tri_t;
+
+/*
+ * Initialize a triangle.
+ */
+__fast__
+void tri_init(tri_t* t, vert_t a, vert_t b, vert_t c)
+{
+ t->a = a;
+ t->b = b;
+ t->c = c;
+}
+
+
+/*
+ * Create a new triangle.
+ */
+__fast__
+tri_t tri_new(vert_t a, vert_t b, vert_t c)
+{
+ tri_t t;
+ tri_init(&t, a, b, c);
+ return t;
+}
+
+#define TRI_ZERO tri_new(VERT_ZERO, VERT_ZERO, VERT_ZERO)
+
+
+/*
+ * Create a new triangle on the heap.
+ */
+__fast__
+tri_t* tri_alloc(vert_t a, vert_t b, vert_t c)
+{
+ tri_t* t = (tri_t*)mem_alloc(sizeof(tri_t));
+ tri_init(t, a, b, c);
+ return t;
+}
+
+
+/*
+ * Apply a transformation matrix to alter the triangle geometry.
+ */
+__fast__
+tri_t tri_transform(tri_t t, mat_t m)
+{
+ t.a.v = mat_apply(m, t.a.v);
+ t.b.v = mat_apply(m, t.b.v);
+ t.c.v = mat_apply(m, t.c.v);
+ return t;
+}
+
+
+/*
+ * Get the barycentric coordinates of a vector against a triangle. The
+ * returned coordinates will be a linear combination, but they may not
+ * actually be barycentric coordinates. Use the function vec_is_barycentric
+ * to check if they really are barycentric coordinates, meaning the point
+ * vector v is inside the triangle, ignoring the Z components.
+ */
+vec_t tri_barycentric(const tri_t* t, vec_t v);
+
+
+#endif // __TRI_H__
+
--- /dev/null
+-0.5 -0.5 0.0
+ 0.5 -0.5 0.0
+ 0.0 0.5 0.0
--- /dev/null
+
+/*
+ * CS5600 University of Utah
+ * Charles McGarvey
+ * mcgarvey@eng.utah.edu
+ */
+
+#ifndef __VEC_H__
+#define __VEC_H__
+
+#include "common.h"
+
+
+/*
+ * A simple vector class.
+ */
+struct vec
+{
+ scal_t x;
+ scal_t y;
+ scal_t z;
+ scal_t w;
+};
+typedef struct vec vec_t;
+
+/*
+ * Initialize a vector with four components.
+ */
+__fast__
+void vec_init(vec_t* v, scal_t x, scal_t y, scal_t z, scal_t w)
+{
+ v->x = x;
+ v->y = y;
+ v->z = z;
+ v->w = w;
+}
+
+
+/*
+ * Create a new vector with four components.
+ */
+__fast__
+vec_t vec_new2(scal_t x, scal_t y, scal_t z, scal_t w)
+{
+ vec_t v;
+ vec_init(&v, x, y, z, w);
+ return v;
+}
+
+/*
+ * Create a new vector with three components. The fourth component is
+ * initialized to one.
+ */
+__fast__
+vec_t vec_new(scal_t x, scal_t y, scal_t z)
+{
+ return vec_new2(x, y, z, S(1.0));
+}
+
+#define VEC_ZERO vec_new(S(0.0), S(0.0), S(0.0))
+#define VEC_ORTHO_X vec_new(S(1.0), S(0.0), S(0.0))
+#define VEC_ORTHO_Y vec_new(S(0.0), S(1.0), S(0.0))
+#define VEC_ORTHO_Z vec_new(S(0.0), S(0.0), S(1.0))
+
+
+/*
+ * Scale the vector with a scalar value.
+ */
+__fast__
+vec_t vec_scale(vec_t v, scal_t s)
+{
+ v.x *= s;
+ v.y *= s;
+ v.z *= s;
+ return v;
+}
+
+/*
+ * Add two vectors together.
+ */
+__fast__
+vec_t vec_add(vec_t a, vec_t b)
+{
+ a.x += b.x;
+ a.y += b.y;
+ a.z += b.z;
+ return a;
+}
+
+/*
+ * Subtract a vector from another vector.
+ */
+__fast__
+vec_t vec_sub(vec_t a, vec_t b)
+{
+ a.x -= b.x;
+ a.y -= b.y;
+ a.z -= b.z;
+ return a;
+}
+
+
+/*
+ * Get the dot product of two vectors.
+ */
+__fast__
+scal_t vec_dot(vec_t a, vec_t b)
+{
+ return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
+}
+
+/*
+ * Get the dot product of two vectors, ignoring the last component.
+ */
+__fast__
+scal_t vec_dot3(vec_t a, vec_t b)
+{
+ return a.x * b.x + a.y * b.y + a.z * b.z;
+}
+
+
+/*
+ * Check whether the values of the first three components could actually be
+ * barycentric coordinates. Note: The necessary condition of each component
+ * adding up to one is assumed and not checked.
+ */
+__fast__
+bool vec_is_barycentric(vec_t v)
+{
+ /*
+ * XXX: I'm fudging the bounds a little because horizontal edges (relative
+ * to the screen) are otherwise sometimes really jagged. This probably
+ * isn't the best solution.
+ */
+ if (S(-0.000001) <= v.x && v.x <= S(1.000001) &&
+ S(-0.000001) <= v.y && v.y <= S(1.000001) &&
+ S(-0.000001) <= v.z && v.z <= S(1.000001)) {
+ return true;
+ }
+ return false;
+}
+
+#endif // __VEC_H__
+
--- /dev/null
+
+/*
+ * CS5600 University of Utah
+ * Charles McGarvey
+ * mcgarvey@eng.utah.edu
+ */
+
+#ifndef __VERT_H__
+#define __VERT_H__
+
+#include "color.h"
+#include "vec.h"
+
+
+/*
+ * A vertex is a point and its associated color.
+ */
+struct vert
+{
+ vec_t v;
+ color_t c;
+};
+typedef struct vert vert_t;
+
+
+/*
+ * Initialize a vertex with a point vector and a color.
+ */
+__fast__
+void vert_init(vert_t* r, vec_t v, color_t c)
+{
+ r->v = v;
+ r->c = c;
+}
+
+
+/*
+ * Create a new vertex with a point vector and a color.
+ */
+__fast__
+vert_t vert_new(vec_t v, color_t c)
+{
+ vert_t r;
+ vert_init(&r, v, c);
+ return r;
+}
+
+/*
+ * Create a new vertex from vector components and a color.
+ */
+__fast__
+vert_t vert_new2(scal_t x, scal_t y, scal_t z, color_t c)
+{
+ vec_t v = vec_new(x, y, z);
+ return vert_new(v, c);
+}
+
+#define VERT_ZERO vert_new(VEC_ZERO, COLOR_CLEAR)
+
+
+#endif // __VERT_H__
+