-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
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
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)
--- /dev/null
+#!/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
+
/*
- * 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)
}
/*
- * 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)
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.
*/
#include <stdlib.h>
#include <string.h>
+#include "config.h"
+
/*
* Define a keyword for use while defining small and fast functions.
* 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
/*
--- /dev/null
+
+/*
+ * 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_
+
*/
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_
* 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);
}
}
* mcgarvey@eng.utah.edu
*/
-#include <stdio.h>
+#define _POSIX_C_SOURCE 2
+#include <errno.h>
+#include <unistd.h>
#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");
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]);
}
}
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
};
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);
{
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"
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;
#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
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));
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;
*/
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.
}
/*
- * Clear the tree of all of it's nodes.
+ * Clear the tree of all of its nodes.
*/
void rbtree_clear(rbtree_t* t);
+++ /dev/null
-#!/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
-
#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"
{
list_t* triangles;
mat_t model;
+ color_t specular;
+ scal_t shininess;
char* name;
int count;
};
char* cachename = mem_strcat(".", filename);
FILE* file = fopen(cachename, "rb");
if (file == NULL) {
- return 0;
+ goto fail;
}
int count = 0;
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));
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;
}
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));
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.
*/
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);
FILE* file = fopen(filename, "r");
if (file == NULL) {
fprintf(stderr, "Cannot read %s: %s\n", filename, strerror(errno));
+ _group_destroy(g);
return NULL;
}
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.
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;
}
{
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));
{
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));
{
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;
&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;
}
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;
}
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:
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
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");
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);
#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
/*
* 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.