From e16cf0578f4baaf879e4ab9d3528a765bfd29be0 Mon Sep 17 00:00:00 2001 From: Charles McGarvey Date: Mon, 13 Feb 2012 02:40:37 -0700 Subject: [PATCH] add scene lighting constructs; real stdin support --- Makefile | 26 ++----- README | 4 +- animate.lua | 110 +++++++++++++++++++++++++++++ color.h | 39 ++++++++++- common.h | 7 ++ config.h | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++ light.h | 41 ++++++++--- list.h | 6 +- main.c | 79 +++++++++++++++------ raster.c | 56 ++++++++------- raster.h | 10 +++ rbtree.h | 2 +- rotate.lua | 95 ------------------------- scene.c | 165 +++++++++++++++++++++++++++++++------------- scene.h | 2 +- 15 files changed, 605 insertions(+), 231 deletions(-) create mode 100755 animate.lua create mode 100644 config.h delete mode 100755 rotate.lua diff --git a/Makefile b/Makefile index 024bb48..7d119e4 100644 --- a/Makefile +++ b/Makefile @@ -1,24 +1,6 @@ -PROJECT = project2 -IUSE = CLIPPING DEPTH_TEST SMOOTH_COLOR \ - EXTRA_INLINE NDEBUG RASTER_STATS RENDER_PROGRESS RENDER_TIMER - - -# BACKFACE_CULLING prevent drawing triangles that are not facing forward -# CLIPPING turn on clipping (you really always want this) -# DEPTH_TEST enable the z buffer for depth testing -# EXPORT_BMP save scene as bitmap (enabled by default) -# EXPORT_PPM save scene as a PPM file (also enabled by default) -# EXTRA_INLINE allow the compiler to inline even more aggressively -# LIGHTING turn the lights on (experimental) -# NDEBUG disable assertions and other nonessential checks -# PRE_NORMALS[=n] pre-compute mesh normals at scene-loading time; -# set to 1 for per-face normals, 2 for averaged normals, -# or 3 to also enable caching the computed normals -# RASTER_STATS print triangle count and optimization savings at the end -# RENDER_PROGRESS print progress while drawing the scene -# RENDER_TIMER add a timer to see how long the renderer takes -# SMOOTH_COLOR interpolate colors smoothly between triangle vertices +PROJECT = rasterize +IUSE = DEPTH_TEST EXTRA_INLINE SMOOTH_COLOR VIEWER = feh @@ -40,10 +22,10 @@ clean: rm -f $(PROJECT) $(OBJS) $(DEPS) distclean: clean - rm -f tags gmon.out scene.ppm scene.bmp + rm -f tags gmon.out dist: - git archive $(PROJECT) --prefix=$(PROJECT)/ --output=$(PROJECT).zip + git archive HEAD --prefix=$(PROJECT)/ --output=$(PROJECT).zip run: $(PROJECT) ./$< && $(VIEWER) scene.ppm diff --git a/README b/README index 01670d9..b5f29c8 100644 --- a/README +++ b/README @@ -6,11 +6,11 @@ mcgarvey@eng.utah.edu Project 2 Notes To build, just `make' it. You will need GNU make; it won't work well with any -other kind of make(1). Once built, the executable is called project2. The +other kind of make(1). Once built, the executable is called rasterize. The default options should meet the assignment specification. Known to run on: * Linux 3.2.1-gentoo - * NetBSD 5.1 + * NetBSD 5.1.2 * Darwin 10.8.0 (in the Mac lab) diff --git a/animate.lua b/animate.lua new file mode 100755 index 0000000..d04f5df --- /dev/null +++ b/animate.lua @@ -0,0 +1,110 @@ +#!/usr/bin/env lua + +-- +-- CS5600 University of Utah +-- Charles McGarvey +-- mcgarvey@eng.utah.edu +-- + +-- This program that draws a scene multiple times from different camera angles +-- and positions. The rasters are saved in the `frames' directory, and if +-- ffmpeg is installed, they will also be combined into a video file. The +-- camera will make a full rotation along the X and Z axes, around a specified +-- point (center), all the while looking at some other point (look). +-- This must be called from the same directory where the rasterize program is. + +local size = {w = 640, h = 480} -- width and height of the viewport +local frames = 512 -- number of animation frames + +local look = {x = 2, y = 1, z = 2} -- the point to look at +local center = {x = 0, y = 1, z = 0} -- center of rotation +local distance = 4 -- the distance from the center +local start = math.pi -- where is the first frame + +local jobs = 6 -- number of concurrent renderings + +-- the actual objects of the scene that will be drawn: +local scene = [[ +L 0 1000000 0 1 1 1 1 1 1 +L 0 0 1000000 1 1 1 1 1 1 +g triangle.raw +c 0.9 0.8 0.7 0.6 0.5 0.4 0.3 0.2 0.1 +t 2 0 -2 +s 1 1 1 +g dragon.raw +c 0.7 0.3 0.2 0.8 0.2 0.1 0.9 0.2 0.2 +t -2 -1 -2 +s 2 2 2 +g budda.raw +c 0.1 0.2 0.7 0.1 0.3 0.9 0.2 0.1 0.8 +t 2 -1.5 2 +s 10 10 10 +g bunny.raw +c 0.9 0.8 0.9 0.8 0.7 0.9 0.9 0.8 0.7 +t -2 -1 2 +s 10 10 10 +g teapot2.raw +c 0 1 0 0 1 0 0 1 0 +t 0 -1 0 +s 0.6 0.6 0.6 +]] + + +function vec_add(a, b) + return {x = a.x + b.x, y = a.y + b.y, z = a.z + b.z} +end + +function vec_scale(v, s) + return {x = v.x * s, y = v.y * s, z = v.z * s} +end + +local fmt = string.format +local write = function(...) io.write(fmt(...)) end + +function render(i, v) + while i and v do + local filename = fmt("frames/anim%04d.bmp", i) + local command = fmt("./rasterize -o %s >/dev/null", filename) + local out = io.popen(command, "w") + write("\27[80D\27[2Kframe\t %4d / %d", i + 1, frames) + out:write(fmt([[ +U3 +%d %d +%f %f %f +%f %f %f +0 1 0 +1.57 %f 0.1 1000 +%s +X +]], size.w, size.h, v.x, v.y, v.z, look.x, look.y, look.z, size.w/size.h, scene)) + i, v = coroutine.yield() + out:close() + end +end + + +print("Animating scene...") + +local threads = {} +for i = 1,jobs do + table.insert(threads, coroutine.wrap(render)) +end + +os.execute("rm -rf frames && mkdir frames >/dev/null 2>&1") +for i = 0,(frames-1) do + local t = start + 2 * math.pi * i / frames + local v = { + x = math.cos(t), + y = 0, + z = math.sin(t), + } + v = vec_add(vec_scale(v, distance), center) + threads[1 + (i % jobs)](i, v) +end +for _,thread in ipairs(threads) do thread(null) end +print() + +if os.execute("ffmpeg -i frames/anim%04d.bmp -b 1024k -y -an scene.avi") == 0 then + print("Animation written to scene.avi.") +end + diff --git a/color.h b/color.h index 26a6390..2c5bf80 100644 --- a/color.h +++ b/color.h @@ -80,7 +80,7 @@ void color_print(color_t c) /* - * Add two colors together. Color may need to be clamped afterward. + * Add two colors together. */ INLINE_MAYBE color_t color_add(color_t c1, color_t c2) @@ -89,7 +89,7 @@ color_t color_add(color_t c1, color_t c2) } /* - * Add three colors together. Color may need to be clamped afterward. + * Add three colors together. */ INLINE_MAYBE color_t color_add2(color_t c1, color_t c2, color_t c3) @@ -97,6 +97,41 @@ color_t color_add2(color_t c1, color_t c2, color_t c3) return color_add(color_add(c1, c2), c3); } +/* + * Multiply two colors together. + */ +INLINE_MAYBE +color_t color_mult(color_t c1, color_t c2) +{ + c1.r *= c2.r; + c1.g *= c2.g; + c1.b *= c2.b; + c1.a *= c2.a; + return c1; +} + +/* + * Scale a color by some scalar coefficient. + */ +INLINE_MAYBE +color_t color_scale(color_t c, scal_t k) +{ + c.r *= k; + c.g *= k; + c.b *= k; + c.a *= k; + return c; +} + +/* + * Scale a color by another color and some scalar coefficient. + */ +INLINE_MAYBE +color_t color_scale2(color_t c1, color_t c2, scal_t k) +{ + return color_scale(color_mult(c1, c2), k); +} + /* * Clamp a color's channels to the normal range of 0.0 to 1.0. */ diff --git a/common.h b/common.h index dd6f034..172b8af 100644 --- a/common.h +++ b/common.h @@ -15,6 +15,8 @@ #include #include +#include "config.h" + /* * Define a keyword for use while defining small and fast functions. @@ -132,11 +134,16 @@ int imax(int a, int b) * an error condition and an abort(3) occurs. * return. */ +#if VERBOSITY >= 1 #define TRY_DO(L, K, ARGS...) \ printf("* " L "... ", ##ARGS); \ fflush(stdout); \ if ((K) == 0) printf("done!\n"); \ else abort() +#else +#define TRY_DO(L, K, ARGS...) \ +if ((K) != 0) abort() +#endif /* diff --git a/config.h b/config.h new file mode 100644 index 0000000..9637dea --- /dev/null +++ b/config.h @@ -0,0 +1,194 @@ + +/* + * CS5600 University of Utah + * Charles McGarvey + * mcgarvey@eng.utah.edu + */ + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +/* + * BACKFACE_CULLING + * If enabled, triangles that are facing away from the viewer, according to + * the right-hand rule and counter-clockwise winding of the verticies, will + * not be drawn. This option can improve performance in some situations, but + * it may also cause visual problems for models that aren't entirely closed. + */ +#if BACKFACE_CULLING +#define IF_BACKFACE_CULLING(X) X +#else +#define IF_BACKFACE_CULLING(X) +#endif + +/* + * CLIPPING + * If enabled, triangles will be not be drawn if they are entirely outside of + * the viewing volume. The number of pixels tested while rasterizing a + * triangle can also be minimized, which leads to a huge performance boost. + * For that reason, this is on unless explicitly disabled, and you really + * always want to leave it enabled. Note that this does not have anything to + * do with viewport clipping, which is always performed. Also note that any + * triangle which is partially within the viewing volume will be entirely + * drawn, which may be somewhat unexpected. + */ +#ifndef CLIPPING +#define CLIPPING 1 +#endif +#if CLIPPING +#define IF_CLIPPING(X) X +#else +#define IF_CLIPPING(X) +#endif + +/* + * DEPTH_TEST + * If enabled, a z-buffer will be used to perform some sort of depth testing, + * resulting in the generally desirable situation where triangles that are + * further away will not appear in front of triangles that are closer. There + * is a performance penalty for this, so it must be enabled. + */ +#if DEPTH_TEST +#define IF_DEPTH_TEST(X) X +#else +#define IF_DEPTH_TEST(X) +#endif + +/* + * EXPORT_BMP + * If enabled, each scene rasterization will be saved as a BMP image file. + * This is on unless explicitly disabled. + */ +#ifndef EXPORT_BMP +#define EXPORT_BMP 1 +#endif +#if EXPORT_BMP +#define IF_EXPORT_BMP(X) X +#else +#define IF_EXPORT_BMP(X) +#endif + +/* + * EXPORT_PPM + * If enabled, each scene rasterization will be saved as a PPM image file. + * This is on unless explicitly disabled. + */ +#ifndef EXPORT_PPM +#define EXPORT_PPM 1 +#endif +#if EXPORT_PPM +#define IF_EXPORT_PPM(X) X +#else +#define IF_EXPORT_PPM(X) +#endif + +/* + * EXTRA_INLINE + * If enabled, functions that are defined in interface files will be marked as + * inline. The compiler will generally inline functions according to its own + * optimization heuristics, and this inline marking may persuade the compiler + * to inline a function that it otherwise would not. This option may bring a + * small performance boost, but it can also increase the size of the program + * executable. + */ +#if EXTRA_INLINE +#define IF_EXTRA_INLINE(X) X +#else +#define IF_EXTRA_INLINE(X) +#endif + +/* + * LIGHTING + * If enabled, local lighting will be used to increase realism of the scene. + * This option has a performance cost, but it can produce interesting visuals. + * The behavior of this option also depends on the SMOOTH_COLOR option and + * whether or not the model has unique vertex normals; if SMOOTH_COLOR is + * disabled, each triangle will be shaded a flat color. If it is enabled, + * Gouraud interpolation is used to smooth the lighting across the face of + * each triangle. See the PRE_NORMALS and SMOOTH_COLOR options. + */ +#if LIGHTING +#define IF_LIGHTING(X) X +#else +#define IF_LIGHTING(X) +#endif + +/* + * NDEBUG + * If enabled, assertions and other nonessential checks will not be compiled + * into the programs. + */ +#if NDEBUG +#define IF_NDEBUG(X) X +#else +#define IF_NDEBUG(X) +#endif + +/* + * PRE_NORMALS + * If enabled, normals are pre-computed while the triangles are loading. + * Otherwise, the normals are computed during rasterization. The behavior of + * this option is effected by its precise value: + * 1 Normals are computed per-face, according to the right-hand rule and + * counter-clockwise winding of the verticies. + * 2 Normals are computed per-vertex; the normal for a vertex that is shared + * between two or more faces will be the average of the normals of the + * faces. There is a performance penalty for this setting. + * 3 Same as 2, but the normals will be cached so that they will not need to + * be computed the next time the mesh is loaded. + */ +#if PRE_NORMALS +#define IF_PRE_NORMALS(X) X +#else +#define IF_PRE_NORMALS(X) +#endif + +/* + * SMOOTH_COLOR + * If enabled, color will be interpolated across the face of a triangle. + * Otherwise, the color will flat, and the average color of the colors + * associated with each vertex will be used. + */ +#if SMOOTH_COLOR +#define IF_SMOOTH_COLOR(X) X +#else +#define IF_SMOOTH_COLOR(X) +#endif + +/* + * VERBOSITY + * If enabled, a description of what is happening will be printed to stdout. + * Otherwise, nothing is printed. The behavior of this option is effected by + * its precise value: + * 1 Print just a few very general descriptions. + * 2 After rasterization, also print the triangle count and other + * information that may be interesting. + * 3 Also print the number of seconds it took to render the entire scene, + * according to wall time. + * 4 Also print the number of triangles as they are being rastered. This + * uses ANSI escape codes which may not be supported on all terminals. + * It also causes a lot to be printed, so it can actually decrease render + * performance, especially on a slow (or remote) terminal. + * The default setting for this option is 3. + */ +#ifndef VERBOSITY +#define VERBOSITY 3 +#endif +#if VERBOSITY >= 2 +#define IF_RASTER_STATS(X) X +#else +#define IF_RASTER_STATS(X) +#endif +#if VERBOSITY >= 3 +#define IF_RENDER_TIMER(X) X +#else +#define IF_RENDER_TIMER(X) +#endif +#if VERBOSITY >= 4 +#define IF_RENDER_PROGRESS(X) X +#else +#define IF_RENDER_PROGRESS(X) +#endif + +#endif // _CONFIG_H_ + diff --git a/light.h b/light.h index 99c0d7c..2518927 100644 --- a/light.h +++ b/light.h @@ -17,34 +17,55 @@ */ struct light { - color_t color; - vec_t position; + vec_t v; // position + color_t d; // diffuse + color_t s; // specular }; typedef struct light light_t; - /* - * Initialize a light with a color and position. + * Initialize a light with a position and color. */ INLINE_MAYBE -void light_init(light_t* l, color_t color, vec_t position) +void light_init(light_t* l, vec_t position, color_t diffuse, color_t specular) { - l->color = color; - l->position = position; + l->v = position; + l->d = diffuse; + l->s = specular; } /* - * Create a new light with a color and position. + * Create a new light with a position and color. */ INLINE_MAYBE -light_t light_new(color_t color, vec_t position) +light_t light_new(vec_t position, color_t diffuse, color_t specular) { light_t l; - light_init(&l, color, position); + light_init(&l, position, diffuse, specular); return l; } +/* + * Create a new light on the heap. + */ +INLINE_MAYBE +light_t* light_alloc(vec_t position, color_t diffuse, color_t specular) +{ + light_t* l = (light_t*)mem_alloc(sizeof(light_t)); + light_init(l, position, diffuse, specular); + return l; +} + +INLINE_MAYBE +light_t* light_copy(light_t l) +{ + light_t* n = (light_t*)mem_alloc(sizeof(light_t)); + memcpy(n, &l, sizeof(light_t)); + return n; +} + + #endif // _LIGHT_H_ diff --git a/list.h b/list.h index 5adf228..91f176d 100644 --- a/list.h +++ b/list.h @@ -94,10 +94,10 @@ void list_pop(list_t** l) * This is a O(n) operation. */ INLINE_MAYBE -void list_destroy(list_t* l) +void list_destroy(list_t** l) { - while (l) { - list_pop(&l); + while (*l) { + list_pop(l); } } diff --git a/main.c b/main.c index 83878bf..8f4ca4d 100644 --- a/main.c +++ b/main.c @@ -5,52 +5,60 @@ * mcgarvey@eng.utah.edu */ -#include +#define _POSIX_C_SOURCE 2 +#include +#include #include "raster.h" #include "scene.h" -#ifndef EXPORT_PPM -#define EXPORT_PPM 1 -#endif -#ifndef EXPORT_BMP -#define EXPORT_BMP 1 -#endif - - /* * Load a scene from a file. */ static int load(scene_t** scene, const char* filename) { - *scene = scene_alloc(filename); + FILE* file = fopen(filename, "r"); + if (file == NULL) { + fprintf(stderr, "Cannot read %s: %s\n", filename, strerror(errno)); + return -1; + } + + *scene = scene_alloc(file); if (*scene == NULL) { - return 1; + fclose(file); + return -1; } + + fclose(file); return 0; } /* - * Load a scene file, render it, and export it to PPM and BMP formats. + * Load a scene from standard input. */ -static void draw(const char* filename) +static int load_from_stdin(scene_t** scene) { - char* u3d; - if (strcmp(filename, "-") == 0) { - u3d = mem_strdup("stdin"); - } - else { - u3d = mem_strdup(filename); + *scene = scene_alloc(stdin); + if (*scene == NULL) { + return -1; } + return 0; +} +/* + * Load a scene file, render it, and export it. + */ +static void draw(const char* filename) +{ scene_t* scene; - TRY_DO("Loading %s", load(&scene, filename), u3d); + TRY_DO("Loading %s", load(&scene, filename), filename); raster_t* raster = scene_render(scene); scene_destroy(scene); raster_printstats(raster); + char* u3d = mem_strdup(filename); strcut(u3d, '.'); #if EXPORT_PPM char* ppm = mem_strcat(u3d, ".ppm"); @@ -67,16 +75,43 @@ static void draw(const char* filename) raster_destroy(raster); } +/* + * Render a scene that is read from standard input, and export to BMP. + */ +static void draw_from_stdin(const char* filename) +{ + scene_t* scene; + TRY_DO("Loading from stdin", load_from_stdin(&scene)); + + raster_t* raster = scene_render(scene); + scene_destroy(scene); + raster_printstats(raster); + + TRY_DO("Exporting to %s", raster_export_bmp(raster, filename), filename); + + raster_destroy(raster); +} + /* * Render one or more scenes from 3D scene files. */ int main(int argc, char* argv[]) { - if (argc <= 1) { + int out = 0; + int c; + while ((c = getopt(argc, argv, "o:")) != -1) { + switch (c) { + case 'o': + ++out; + draw_from_stdin(optarg); + } + } + + if (out == 0 && argc <= 1) { draw("scene.u3d"); } else { - for (int i = 1; i < argc; ++i) { + for (int i = optind; i < argc; ++i) { draw(argv[i]); } } diff --git a/raster.c b/raster.c index 59d3f46..aca2c5a 100644 --- a/raster.c +++ b/raster.c @@ -30,14 +30,13 @@ struct raster list_t* lights; color_t ambient; vec_t eye; + color_t specular; + scal_t shininess; #endif -#if RASTER_STATS +#if VERBOSITY >= 2 unsigned total; unsigned clipped; unsigned culled; -#define IF_RASTER_STATS(X) X -#else -#define IF_RASTER_STATS(X) #endif }; @@ -57,8 +56,10 @@ raster_t* raster_alloc(int width, int height, color_t fill) p->dirty = false; #if LIGHTING - p->ambient = color_new(S(0.05), S(0.05), S(0.05), S(1.0)); + p->ambient = color_new(S(0.2), S(0.2), S(0.2), S(1.0)); p->lights = NULL; + p->specular = COLOR_WHITE; + p->shininess = S(1.0); #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); @@ -73,13 +74,16 @@ void raster_destroy(raster_t* p) { mem_free(p->pixels); mem_free(p->zbuf); +#if LIGHTING + list_destroy(&p->lights); +#endif mem_free(p); } void raster_printstats(raster_t* p) { -#if RASTER_STATS +#if VERBOSITY >= 2 unsigned drawn = p->total - p->clipped - p->culled; float percent = 100.0f * (float)drawn / (float)p->total; printf("culled\t%u\n" @@ -96,7 +100,7 @@ void raster_clear(raster_t* p, color_t fill) for (int i = 0; i < size; ++i) { p->pixels[i] = fill; } -#if RASTER_STATS +#if VERBOSITY >= 2 p->total = 0; p->clipped = 0; p->culled = 0; @@ -139,15 +143,29 @@ void raster_eye(raster_t* p, vec_t eye) #endif } +void raster_ambient(raster_t* p, color_t ambient) +{ +#if LIGHTING + p->ambient = ambient; +#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)); + light_t* l = light_copy(light); list_push2(&p->lights, l, mem_free); #endif } +void raster_material(raster_t* p, color_t specular, scal_t shininess) +{ +#if LIGHTING + p->specular = specular; + p->shininess = shininess; +#endif +} + #define _CHECK_WRITE(X) if ((X) <= 0) goto fail @@ -293,7 +311,7 @@ color_t _get_vertex_color(raster_t* p, vert_t vert) 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 lpos = light.v; vec_t vpos = p->eye; vec_t n = vert.n; vec_t l = vec_normalize(vec_sub(lpos, mpos)); @@ -301,23 +319,13 @@ color_t _get_vertex_color(raster_t* p, vert_t vert) 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_t Id = color_scale2(light.d, vert.c, kd); + scal_t ks = scal_pow(scal_max(vec_dot(r, v), S(0.0)), p->shininess); + color_t Is = color_scale2(light.s, p->specular, ks); color = color_add2(color, Id, Is); } - color_t Ia = p->ambient; + color_t Ia = color_mult(p->ambient, vert.c); return color_clamp(color_add(color, Ia)); #else return vert.c; diff --git a/raster.h b/raster.h index ef358e7..1d43132 100644 --- a/raster.h +++ b/raster.h @@ -69,11 +69,21 @@ void raster_projection(raster_t* p, const mat_t* transform); */ void raster_eye(raster_t* p, vec_t eye); +/* + * Set the ambient light for the scene. + */ +void raster_ambient(raster_t* p, color_t ambient); + /* * Add a light to the scene. */ void raster_light(raster_t* p, light_t light); +/* + * Set the material properties for the scene. + */ +void raster_material(raster_t* p, color_t specular, scal_t shininess); + /* * Save the raster to a PPM file. diff --git a/rbtree.h b/rbtree.h index 4f72632..e23cd13 100644 --- a/rbtree.h +++ b/rbtree.h @@ -92,7 +92,7 @@ bool rbtree_empty(rbtree_t* t) } /* - * Clear the tree of all of it's nodes. + * Clear the tree of all of its nodes. */ void rbtree_clear(rbtree_t* t); diff --git a/rotate.lua b/rotate.lua deleted file mode 100755 index 4334dd8..0000000 --- a/rotate.lua +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env lua - --- --- CS5600 University of Utah --- Charles McGarvey --- mcgarvey@eng.utah.edu --- - --- Render a scene multiple times from different angles, like a camera --- rotating around the object of a scene. - -local size = {w = 640, h = 480} -local slices = 512 - -local center = {x = 0, y = 1, z = 0} -local look = {x = -2, y = -1, z = -2} -local distance = 4 -local start = math.pi - -local scene = [[ -g teapot2.raw -c 1.0 1.0 0.0 1.0 1.0 0.0 1.0 1.0 0.0 -t 0.0 0.0 0.0 -s 0.6 0.6 0.6 -g triangle.raw -c 0.9 0.8 0.7 0.6 0.5 0.4 0.3 0.2 0.1 -t 2.0 0.0 -2.0 -s 1.0 1.0 1.0 -g dragon.raw -c 0.7 0.3 0.2 0.8 0.2 0.1 0.9 0.2 0.2 -t -2.0 -1.0 -2.0 -s 2.0 2.0 2.0 -g budda.raw -c 0.1 0.2 0.7 0.1 0.3 0.9 0.2 0.1 0.8 -t 2.0 -1.5 2.0 -s 10.0 10.0 10.0 -g bunny.raw -c 0.9 0.8 0.9 0.8 0.7 0.9 0.9 0.8 0.7 -t -2.0 -1.0 2.0 -s 10.0 10.0 10.0 -]] - -function vec_add(a, b) - return {x = a.x + b.x, y = a.y + b.y, z = a.z + b.z} -end - -function vec_scale(v, s) - return {x = v.x * s, y = v.y * s, z = v.z * s} -end - -function render(i, v) - while true do - local out = io.popen("./project2 -", "w") - out:write(string.format([[ -U3 -%d %d -%f %f %f -%f %f %f -0.0 1.0 0.0 -1.57 %f 0.1 1000.0 -%s -]], size.w, size.h, v.x, v.y, v.z, look.x, look.y, look.z, size.w/size.h, scene)) - local a, b = coroutine.yield() - out:close() - os.rename("stdin.bmp", string.format("anim%04d.bmp", i)) - if a == null then - break - else - i = a - v = b - end - end -end - -local threads = {} -local jobs = 3 -for i = 1,jobs do - table.insert(threads, coroutine.wrap(render)) -end - -for i = 0,(slices-1) do - local t = start + 2 * i * math.pi / slices - local v = { - x = math.cos(t), - y = 0, - z = math.sin(t), - } - v = vec_add(vec_scale(v, distance), center) - threads[(i % jobs) + 1](i, v) -end - -for k,v in ipairs(threads) do - v(null) -- clear up any leftover work -end - diff --git a/scene.c b/scene.c index 3e0010f..be18511 100644 --- a/scene.c +++ b/scene.c @@ -16,12 +16,6 @@ #include "tri.h" -#if RENDER_PROGRESS -#define IF_RENDER_PROGRESS(X) X -#else -#define IF_RENDER_PROGRESS(X) -#endif - #if PRE_NORMALS >= 2 #include "map.h" @@ -91,6 +85,8 @@ struct _group { list_t* triangles; mat_t model; + color_t specular; + scal_t shininess; char* name; int count; }; @@ -107,7 +103,7 @@ static int _group_try_read_cache(const char* filename, list_t** l) char* cachename = mem_strcat(".", filename); FILE* file = fopen(cachename, "rb"); if (file == NULL) { - return 0; + goto fail; } int count = 0; @@ -129,6 +125,7 @@ static int _group_try_read_cache(const char* filename, list_t** l) vert_new2((scal_t)x2, (scal_t)y2, (scal_t)z2), vert_new2((scal_t)x3, (scal_t)y3, (scal_t)z3) ); + list_push2(l, t, mem_free); _CHECK_IO(fread(&x1, sizeof(float), 1, file)); _CHECK_IO(fread(&y1, sizeof(float), 1, file)); _CHECK_IO(fread(&z1, sizeof(float), 1, file)); @@ -141,13 +138,13 @@ static int _group_try_read_cache(const char* filename, list_t** l) t->a.n = vec_new((scal_t)x1, (scal_t)y1, (scal_t)z1); t->b.n = vec_new((scal_t)x2, (scal_t)y2, (scal_t)z2); t->c.n = vec_new((scal_t)x3, (scal_t)y3, (scal_t)z3); - - list_push2(l, t, mem_free); } fail: - fclose(file); mem_free(cachename); + if (file != NULL) { + fclose(file); + } return count; } @@ -161,7 +158,7 @@ static void _group_write_cache(const char* filename, int count, list_t* l) FILE* file = fopen(cachename, "wb"); if (file == NULL) { fprintf(stderr, "Cannot write %s: %s\n", cachename, strerror(errno)); - return; + goto fail; } _CHECK_IO(fwrite(&count, sizeof(count), 1, file)); @@ -189,11 +186,24 @@ static void _group_write_cache(const char* filename, int count, list_t* l) fail: mem_free(cachename); + if (file != NULL) { + fclose(file); + } } #undef _CHECK_IO +/* + * Destroy a group. + */ +static void _group_destroy(_group_t* g) +{ + mem_free(g->name); + list_destroy(&g->triangles); + mem_free(g); +} + /* * Allocate a group by reading raw triangle coordinates from a file. */ @@ -204,6 +214,8 @@ static _group_t* _group_alloc(const char* filename) g->model = MAT_IDENTITY; g->name = mem_strdup(filename); g->count = 0; + g->specular = COLOR_WHITE; + g->shininess = S(64.0); #if PRE_NORMALS == 3 int r = _group_try_read_cache(filename, &g->triangles); @@ -216,6 +228,7 @@ 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)); + _group_destroy(g); return NULL; } @@ -256,24 +269,14 @@ static _group_t* _group_alloc(const char* filename) fclose(file); if (g->triangles == NULL) { - fprintf(stderr, "No triangles coordinates read from %s\n", filename); - mem_free(g); + fprintf(stderr, "No triangles read from %s\n", filename); + _group_destroy(g); return NULL; } return g; } -/* - * Destroy a group. - */ -static void _group_destroy(_group_t* g) -{ - mem_free(g->name); - list_destroy(g->triangles); - mem_free(g); -} - /* * Set the colors of the triangles in the group as defined in a file. @@ -283,7 +286,7 @@ 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"); + fprintf(stderr, "Cannot read color values from scene.\n"); return -1; } @@ -303,7 +306,7 @@ static int _group_add_translate(_group_t* g, FILE* file) { double tx, ty, tz; if (fscanf(file, " %lf %lf %lf", &tx, &ty, &tz) != 3) { - fprintf(stderr, "Cannot read translate coordinates from scene file.\n"); + fprintf(stderr, "Cannot read translate coordinates from scene.\n"); return -1; } g->model = mat_mult(g->model, MAT_TRANSLATE((scal_t)tx, (scal_t)ty, (scal_t)tz)); @@ -317,7 +320,7 @@ static int _group_add_rotate(_group_t* g, FILE* file) { double theta, ax, ay, az; if (fscanf(file, " %lf %lf %lf %lf", &theta, &ax, &ay, &az) != 4) { - fprintf(stderr, "Cannot read rotation angle from scene file.\n"); + fprintf(stderr, "Cannot read rotation angle from scene.\n"); return -1; } g->model = mat_mult(g->model, MAT_ROTATE((scal_t)theta, (scal_t)ax, (scal_t)ay, (scal_t)az)); @@ -331,38 +334,75 @@ static int _group_add_scale(_group_t* g, FILE* file) { double sx, sy, sz; if (fscanf(file, " %lf %lf %lf", &sx, &sy, &sz) != 3) { - fprintf(stderr, "Cannot read scale factors from scene file.\n"); + fprintf(stderr, "Cannot read scale factors from scene.\n"); return -1; } g->model = mat_mult(g->model, MAT_SCALE((scal_t)sx, (scal_t)sy, (scal_t)sz)); return 0; } +/* + * Set the specular highlight and shininess factor for the group. + */ +static int _group_set_material(_group_t* g, FILE* file) +{ + double sr, sg, sb, shininess; + if (fscanf(file, " %lf %lf %lf %lf", &sr, &sg, &sb, &shininess) != 4) { + fprintf(stderr, "Cannot read material information from scene.\n"); + return -1; + } + g->specular = color_new((scal_t)sr, (scal_t)sg, (scal_t)sb, S(1.0)); + g->shininess = (scal_t)shininess; + return 0; +} + struct scene { list_t* groups; - list_t* lights; int w, h; mat_t view; mat_t projection; vec_t eye; + list_t* lights; + color_t ambient; }; -scene_t* scene_alloc(const char* filename) + +/* + * Set the ambient light properties of a scene. + */ +static int _scene_set_ambient(scene_t* s, FILE* file) { - FILE* file; - if (strcmp(filename, "-") == 0) { - file = stdin; - } - else { - file = fopen(filename, "r"); + double r, g, b; + if (fscanf(file, " %lf %lf %lf", &r, &g, &b) != 3) { + fprintf(stderr, "Cannot read ambient light from scene.\n"); + return -1; } - if (file == NULL) { - fprintf(stderr, "Cannot read %s: %s\n", filename, strerror(errno)); - return NULL; + s->ambient = color_new((scal_t)r, (scal_t)g, (scal_t)b, S(1.0)); + return 0; +} + +static int _scene_add_light(scene_t* s, FILE* file) +{ + double lx, ly, lz, dr, dg, db, sr, sg, sb; + if (fscanf(file, " %lf %lf %lf %lf %lf %lf %lf %lf %lf", + &lx, &ly, &lz, &dr, &dg, &db, &sr, &sg, &sb) != 9) { + fprintf(stderr, "Cannot read light values from scene.\n"); + return -1; } + light_t* l = light_alloc( + vec_new(lx, ly, lz), + color_new(dr, dg, db, S(1.0)), + color_new(sr, sg, sb, S(1.0)) + ); + list_push2(&s->lights, l, mem_free); + return 0; +} + +scene_t* scene_alloc(FILE* file) +{ int w, h; double eyeX, eyeY, eyeZ, spotX, spotY, spotZ, upX, upY, upZ; double fovy, aspect, near, far; @@ -370,7 +410,7 @@ scene_t* scene_alloc(const char* filename) &w, &h, &eyeX, &eyeY, &eyeZ, &spotX, &spotY, &spotZ, &upX, &upY, &upZ, &fovy, &aspect, &near, &far) != 15) { - fprintf(stderr, "Cannot read scene file header.\n"); + fprintf(stderr, "Cannot read scene header.\n"); return NULL; } @@ -383,6 +423,8 @@ scene_t* scene_alloc(const char* filename) vec_new( (scal_t)upX, (scal_t)upY, (scal_t)upZ)); s->eye = vec_new(eyeX, eyeY, eyeZ); s->projection = MAT_PERSPECTIVE((scal_t)fovy, (scal_t)aspect, (scal_t)near, (scal_t)far); + s->lights = NULL; + s->ambient = color_new(S(0.05), S(0.05), S(0.05), S(1.0)); char grp_filename[4096]; _group_t* g = NULL; @@ -435,14 +477,35 @@ if (g == NULL) { \ } break; + case 'A': + if (_scene_set_ambient(s, file) != 0) { + goto fail; + } + break; + + case 'L': + if (_scene_add_light(s, file) != 0) { + goto fail; + } + break; + + case 'M': + _ASSERT_G; + if (_group_set_material(g, file) != 0) { + goto fail; + } + break; + + case 'X': + goto done; + default: fprintf(stderr, "Unknown identifier: %c\n", type); } } - #undef _ASSERT_G - fclose(file); +done: return s; fail: @@ -452,14 +515,15 @@ fail: void scene_destroy(scene_t* s) { - list_destroy(s->groups); + list_destroy(&s->groups); + list_destroy(&s->lights); mem_free(s); } raster_t* scene_render(scene_t* s) { -#if RENDER_TIMER +#if VERBOSITY >= 3 timer_start(); #endif @@ -467,11 +531,13 @@ raster_t* scene_render(scene_t* s) raster_view(p, &s->view); raster_projection(p, &s->projection); raster_eye(p, s->eye); + raster_ambient(p, s->ambient); - raster_light(p, light_new(COLOR_WHITE, vec_new(S(5.0), S(3.0), S(6.0)))); - raster_light(p, light_new(COLOR_MAGENTA, vec_new(S(-2.0), S(2.0), S(-2.0)))); + for (list_t* i = s->lights; i; i = i->link) { + raster_light(p, *(light_t*)i->val); + } -#if RENDER_PROGRESS +#if VERBOSITY >= 4 #define PROGRESS_FMT "\033[80D\033[2K %s\t %9d / %d" int tri; printf("render scene:\n"); @@ -480,9 +546,10 @@ raster_t* scene_render(scene_t* s) for (list_t* gi = s->groups; gi; gi = gi->link) { _group_t* g = (_group_t*)gi->val; raster_model(p, &g->model); + raster_material(p, g->specular, g->shininess); IF_RENDER_PROGRESS(tri = 0); for (list_t* ti = g->triangles; ti; ti = ti->link) { -#if RENDER_PROGRESS +#if VERBOSITY >= 4 if (++tri % 100 == 0) { printf(PROGRESS_FMT, g->name, tri, g->count); fflush(stdout); @@ -490,13 +557,13 @@ raster_t* scene_render(scene_t* s) #endif raster_draw_tri(p, (tri_t*)ti->val); } -#if RENDER_PROGRESS +#if VERBOSITY >= 4 printf(PROGRESS_FMT"\n", g->name, tri, g->count); #endif } IF_RENDER_PROGRESS(printf("render complete!\n")); -#if RENDER_TIMER +#if VERBOSITY >= 3 long dt = timer_stop(); printf("time\t%.3fms\n", (float)dt / 1000.0f); #endif diff --git a/scene.h b/scene.h index 93f03e2..d198614 100644 --- a/scene.h +++ b/scene.h @@ -19,7 +19,7 @@ typedef struct scene scene_t; /* * Allocate a scene by reading in data from a file. */ -scene_t* scene_alloc(const char* filename); +scene_t* scene_alloc(FILE* file); /* * Destroy a scene. -- 2.43.0