/* * CS5600 University of Utah * Charles McGarvey * mcgarvey@eng.utah.edu */ #include #include #include #include "common.h" #include "mat.h" #include "list.h" #include "scene.h" #include "tri.h" #if FIND_NORMALS == 2 #include "map.h" DECLARE_AND_DEFINE_MAP_TYPE3(vec_t, list_t*, vnorm, vec_compare(*a, *b)); /* * Associate a triangle with one of its vertices. */ static void _find_normals_add_vertex(map_t* m, vec_t v, tri_t* t) { list_t** l = map_vnorm_search(m, v); if (l == NULL) { map_vnorm_data_t* d = map_vnorm_insert(m, v, NULL); l = &d->val; } list_push(l, t); } /* * Associate a triangle with all of its vertices. */ static void _find_normals_add_triangle(map_t* m, tri_t* t) { _find_normals_add_vertex(m, t->a.v, t); _find_normals_add_vertex(m, t->b.v, t); _find_normals_add_vertex(m, t->c.v, t); } /* * Calculate an averaged normal from a list of triangles that share a common * vertex. */ static void _find_normals_average(const vec_t* v, list_t** l) { // first, compute the average normal vec_t n = VEC_ZERO; for (list_t* i = *l; i; i = i->link) { tri_t* t = (tri_t*)i->val; n = vec_add(n, tri_normal(*t)); } n = vec_normalize(n); // set the normal on each triangle's vertex that is shared while (*l) { tri_t* t = (tri_t*)(*l)->val; if (vec_isequal(*v, t->a.v)) { t->a.n = n; } else if (vec_isequal(*v, t->b.v)) { t->b.n = n; } else if (vec_isequal(*v, t->c.v)) { t->c.n = n; } list_pop(l); } } #endif // FIND_NORMALS /* * A group of triangles and a transformation. */ struct _group { list_t* triangles; mat_t model; #if RENDER_PROGRESS char* name; int count; #define IF_RENDER_PROGRESS(X) X #else #define IF_RENDER_PROGRESS(X) #endif }; 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->model = MAT_IDENTITY; #if RENDER_PROGRESS g->name = mem_strdup(filename); g->count = 0; #endif #if FIND_NORMALS == 2 map_t* m = map_vnorm_alloc(); #endif 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), vert_new2((scal_t)x2, (scal_t)y2, (scal_t)z2), vert_new2((scal_t)x3, (scal_t)y3, (scal_t)z3) ); list_push2(&g->triangles, t, mem_free); IF_RENDER_PROGRESS(++g->count); #if FIND_NORMALS == 1 vec_t n = vec_normalize(tri_normal(*t)); t->a.n = n; t->b.n = n; t->c.n = n; #elif FIND_NORMALS == 2 _find_normals_add_triangle(m, t); #endif } #if FIND_NORMALS == 2 map_vnorm_call(m, _find_normals_average); rbtree_destroy(m); #endif 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) { IF_RENDER_PROGRESS(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. */ 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, tz; if (fscanf(file, " %lf %lf %lf", &tx, &ty, &tz) != 3) { fprintf(stderr, "Cannot read translate coordinates from scene file.\n"); return -1; } g->model = mat_mult(g->model, MAT_TRANSLATE((scal_t)tx, (scal_t)ty, (scal_t)tz)); 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, 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"); return -1; } g->model = mat_mult(g->model, MAT_ROTATE((scal_t)theta, (scal_t)ax, (scal_t)ay, (scal_t)az)); 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, sz; if (fscanf(file, " %lf %lf %lf", &sx, &sy, &sz) != 3) { fprintf(stderr, "Cannot read scale factors from scene file.\n"); return -1; } g->model = mat_mult(g->model, MAT_SCALE((scal_t)sx, (scal_t)sy, (scal_t)sz)); return 0; } struct scene { list_t* groups; list_t* lights; int w, h; mat_t view; mat_t projection; vec_t eye; }; scene_t* scene_alloc(const char* filename) { FILE* file = fopen(filename, "r"); if (file == NULL) { fprintf(stderr, "Cannot read %s: %s\n", filename, strerror(errno)); return NULL; } int w, h; double eyeX, eyeY, eyeZ, spotX, spotY, spotZ, upX, upY, upZ; double fovy, aspect, near, far; if (fscanf(file, "U3 %d %d %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf", &w, &h, &eyeX, &eyeY, &eyeZ, &spotX, &spotY, &spotZ, &upX, &upY, &upZ, &fovy, &aspect, &near, &far) != 15) { fprintf(stderr, "Cannot read scene file header.\n"); return NULL; } scene_t* s = (scene_t*)mem_alloc(sizeof(scene_t)); s->groups = NULL; s->w = w; s->h = h; s->view = MAT_LOOKAT(vec_new( (scal_t)eyeX, (scal_t)eyeY, (scal_t)eyeZ), vec_new((scal_t)spotX, (scal_t)spotY, (scal_t)spotZ), 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); char grp_filename[4096]; _group_t* g = NULL; #define _ASSERT_G \ if (g == NULL) { \ fprintf(stderr, "Unexpected line before group is specified.\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); } } #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); } raster_t* scene_render(scene_t* s) { #if RENDER_TIMER timer_start(); #endif raster_t* p = raster_alloc(s->w, s->h, COLOR_BLACK); raster_view(p, &s->view); raster_projection(p, &s->projection); raster_eye(p, s->eye); 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)))); #if RENDER_PROGRESS #define PROGRESS_FMT "\033[80D\033[2K %s\t %9d / %d" int tri; printf("render scene:\n"); #endif for (list_t* gi = s->groups; gi; gi = gi->link) { _group_t* g = (_group_t*)gi->val; raster_model(p, &g->model); IF_RENDER_PROGRESS(tri = 0); for (list_t* ti = g->triangles; ti; ti = ti->link) { #if RENDER_PROGRESS 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 printf(PROGRESS_FMT"\n", g->name, tri, g->count); #endif } IF_RENDER_PROGRESS(printf("render complete!\n")); #if RENDER_TIMER long dt = timer_stop(); printf("time\t%.3fms\n", (float)dt / 1000.0f); #endif return p; }