]> Dogcows Code - chaz/rasterize/blobdiff - raster.c
add support for 3d scenes, depth testing, lighting
[chaz/rasterize] / raster.c
diff --git a/raster.c b/raster.c
new file mode 100644 (file)
index 0000000..23b1d69
--- /dev/null
+++ b/raster.c
@@ -0,0 +1,396 @@
+
+/*
+ * CS5600 University of Utah
+ * Charles McGarvey
+ * mcgarvey@eng.utah.edu
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "list.h"
+#include "raster.h"
+
+
+struct raster
+{
+    color_t*    pixels;
+    scal_t*     zbuf;
+    int         w, h;
+    int         left, right, bottom, top;
+    mat_t       model;
+    mat_t       view;
+    mat_t       projection;
+    mat_t       modelviewprojection;
+    mat_t       viewport;
+    bool        dirty;
+#if LIGHTING
+    list_t*     lights;
+    color_t     ambient;
+    vec_t       eye;
+#endif
+#if RASTER_STATS
+    unsigned    total;
+    unsigned    clipped;
+    unsigned    culled;
+#define IF_RASTER_STATS(X) X
+#else
+#define IF_RASTER_STATS(X)
+#endif
+};
+
+
+raster_t* raster_alloc(int width, int height, color_t fill)
+{
+    assert(0 < width && 0 < height && "zero-dimension raster not allowed");
+    size_t size = width * height;
+
+    raster_t* p = (raster_t*)mem_alloc(sizeof(raster_t));
+    p->pixels = (color_t*)mem_alloc(sizeof(color_t) * size);
+    p->w = width;
+    p->h = height;
+    raster_clear(p, fill);
+    raster_viewport(p, 0, 0, width, height);
+    p->model = p->view = p->projection = MAT_IDENTITY;
+    p->dirty = false;
+
+#if LIGHTING
+    p->ambient = color_new(S(0.05), S(0.05), S(0.05), S(1.0));
+    p->lights = NULL;
+#endif
+ /*= light_new(COLOR_WHITE, vec_new(S(-2.0), S(4.0), S(0.0)));*/
+    p->zbuf = (scal_t*)mem_alloc(sizeof(scal_t) * size);
+    for (size_t i = 0; i < size; ++i) {
+        p->zbuf[i] = S(1.0);
+    }
+
+    return p;
+}
+
+void raster_destroy(raster_t* p)
+{
+    mem_free(p->pixels);
+    mem_free(p->zbuf);
+    mem_free(p);
+}
+
+
+void raster_printstats(raster_t* p)
+{
+#if RASTER_STATS
+    unsigned drawn = p->total - p->clipped - p->culled;
+    float percent = 100.0f * (float)drawn / (float)p->total;
+    printf("culled\t%u\n"
+           "clipped\t%u\n"
+           "drawn\t%u (%6.2f%%)\n"
+           "total\t%u\n", p->culled, p->clipped, drawn, percent, p->total);
+#endif
+}
+
+
+void raster_clear(raster_t* p, color_t fill)
+{
+    size_t size = p->w * p->h;
+    for (int i = 0; i < size; ++i) {
+        p->pixels[i] = fill;
+    }
+#if RASTER_STATS
+    p->total = 0;
+    p->clipped = 0;
+    p->culled = 0;
+#endif
+}
+
+
+void raster_viewport(raster_t* p, int x, int y, int width, int height)
+{
+    p->left     = x;
+    p->right    = x + width;
+    p->bottom   = y;
+    p->top      = y + height;
+    p->viewport = MAT_VIEWPORT(x, y, width, height);
+}
+
+void raster_model(raster_t* p, const mat_t* transform)
+{
+    p->model = *transform;
+    p->dirty = true;
+}
+
+void raster_view(raster_t* p, const mat_t* transform)
+{
+    p->view = *transform;
+    p->dirty = true;
+}
+
+void raster_projection(raster_t* p, const mat_t* transform)
+{
+    p->projection = *transform;
+    p->dirty = true;
+}
+
+
+void raster_eye(raster_t* p, vec_t eye)
+{
+#if LIGHTING
+    p->eye = eye;
+#endif
+}
+
+void raster_light(raster_t* p, light_t light)
+{
+#if LIGHTING
+    light_t* l = (light_t*)mem_alloc(sizeof(light_t));
+    memcpy(l, &light, sizeof(light_t));
+    list_push2(&p->lights, l, mem_free);
+#endif
+}
+
+
+#define _CHECK_WRITE(X) if ((X) <= 0) goto fail
+
+int raster_export_ppm(const raster_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 raster_export_bmp(const raster_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;
+}
+
+#undef _CHECK_WRITE
+
+
+/*
+ * See if the triangle is at all visible in the viewport.  Also, minimize the
+ * rectangle around the area that includes the triangle.
+ */
+INLINE_MAYBE
+bool _try_clip(tri_t t, int* left, int* right, int* bottom, int* top)
+{
+#if CLIPPING
+    aabb_t box = tri_aabb(t);
+    if (box.min.z < S(-1.0) || S(1.0) < box.max.z) {
+        return false;
+    }
+    *left = imax((int)scal_floor(box.min.x), *left);
+    *right = imin((int)scal_ceil(box.max.x), *right);
+    if (*right <= *left) {
+        return false;
+    }
+    *bottom = imax((int)scal_floor(box.min.y), *bottom);
+    *top = imin((int)scal_ceil(box.max.y), *top);
+    if (*top <= *bottom) {
+        return false;
+    }
+#endif // CLIPPING
+    return true;
+}
+
+/*
+ * See whether or not we need to draw based on the orientation of the
+ * triangle.
+ */
+INLINE_MAYBE
+bool _try_cull_backface(tri_t t)
+{
+#if BACKFACE_CULLING
+    vec_t n = tri_normal(t);
+    if (n.z < S(0.0)) {
+        return false;
+    }
+#endif
+    return true;
+}
+
+/*
+ * Determine what color is associated with the given vertex.
+ */
+INLINE_MAYBE
+color_t _get_vertex_color(raster_t* p, vert_t vert)
+{
+#if LIGHTING
+    color_t color = COLOR_BLACK;
+    for (list_t* i = p->lights; i; i = i->link) {
+        light_t light = *(light_t*)i->val;
+        vec_t   mpos = vert.v;
+        vec_t   lpos = light.position;
+        vec_t   vpos = p->eye;
+        vec_t   n = vert.n;
+        vec_t   l = vec_normalize(vec_sub(lpos, mpos));
+        vec_t   r = vec_sub(vec_scale(n, S(2.0) * vec_dot(n, l)), l);
+        vec_t   v = vec_normalize(vec_sub(vpos, mpos));
+
+        scal_t  kd = scal_max(vec_dot(l, n), S(0.0));
+        color_t Id = color_new(
+            light.color.r * vert.c.r * kd,
+            light.color.g * vert.c.g * kd,
+            light.color.b * vert.c.b * kd,
+            S(1.0)
+        );
+        scal_t  ks = scal_pow(scal_max(vec_dot(r, v), S(0.0)), S(64.0));
+        color_t Is = color_new(
+            light.color.r * COLOR_WHITE.r * ks,
+            light.color.g * COLOR_WHITE.g * ks,
+            light.color.b * COLOR_WHITE.b * ks,
+            S(1.0)
+        );
+
+        color = color_add2(color, Id, Is);
+    }
+    color_t Ia = p->ambient;
+    return color_clamp(color_add(color, Ia));
+#else
+    return vert.c;
+#endif // LIGHTING
+}
+
+void raster_draw_tri(raster_t* p, const tri_t* triangle)
+{
+    IF_RASTER_STATS(++p->total);
+    tri_t t = *triangle;
+
+    // need to recalculate the model-view-projection matrix if any one of its
+    // composing matrices have been changed
+    if (p->dirty) {
+        p->modelviewprojection = mat_mult(p->view, p->model);
+        p->modelviewprojection = mat_mult(p->projection, p->modelviewprojection);
+        p->dirty = false;
+    }
+    t = tri_transform(t, p->modelviewprojection);
+    t = tri_homodiv(t);
+
+    if (!_try_cull_backface(t)) {
+        IF_RASTER_STATS(++p->culled);
+        return;
+    }
+
+    t = tri_transform(t, p->viewport);
+
+    int left    = p->left;
+    int right   = p->right;
+    int bottom  = p->bottom;
+    int top     = p->top;
+
+    if (!_try_clip(t, &left, &right, &bottom, &top)) {
+        IF_RASTER_STATS(++p->clipped);
+        return;
+    }
+
+    tri_t   temp = tri_transform(*triangle, p->model);
+#if LIGHTING && (!FIND_NORMALS || (!SMOOTH_COLOR && FIND_NORMALS == 2))
+    temp.a.n = temp.b.n = temp.c.n = vec_normalize(tri_normal(temp));
+#endif
+#if !SMOOTH_COLOR
+    temp.a.c = tri_color(temp);
+#endif
+    color_t color1 = _get_vertex_color(p, temp.a);
+#if SMOOTH_COLOR
+    color_t color2 = _get_vertex_color(p, temp.b);
+    color_t color3 = _get_vertex_color(p, temp.c);
+#endif
+
+    for (int y = bottom; y < top; ++y) {
+        for (int x = left; x < right; ++x) {
+            vec_t   v = vec_new((scal_t)x, (scal_t)y, S(0.0));
+            scal_t  b[3];
+            if (tri_barycentric(t, b, v)) {
+#if DEPTH_TEST
+                v.z = tri_z(t, b);
+                scal_t* n = p->zbuf + y * p->w + x;
+                if (S(-1.0) < v.z && v.z < *n) {
+#endif
+                    color_t* c = p->pixels + y * p->w + x;
+#if SMOOTH_COLOR
+                    *c = color_interp2(color1, color2, color3, b);
+#else
+                    *c = color1;
+#endif
+#if DEPTH_TEST
+                    *n = v.z;
+                }
+#endif
+            }
+        }
+    }
+}
+
This page took 0.025049 seconds and 4 git commands to generate.