/************************************************************************* * Copyright (C) 2010 Tavian Barnes * * * * This file is part of Dimension. * * * * Dimension is free software; you can redistribute it and/or modify it * * under the terms of the GNU General Public License as published by the * * Free Software Foundation; either version 3 of the License, or (at * * your option) any later version. * * * * Dimension is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * *************************************************************************/ #include "realize.h" #include "parse.h" #include "utility.h" #include #include #include static double dmnsn_realize_float(dmnsn_astnode astnode) { switch (astnode.type) { case DMNSN_AST_FLOAT: return *(double *)astnode.ptr; case DMNSN_AST_INTEGER: return *(long *)astnode.ptr; default: dmnsn_assert(false, "Invalid float."); return 0; /* Silence compiler warning */ } } static dmnsn_vector dmnsn_realize_vector(dmnsn_astnode astnode) { dmnsn_assert(astnode.type == DMNSN_AST_VECTOR, "Expected a vector."); dmnsn_astnode xnode, ynode, znode; dmnsn_array_get(astnode.children, 0, &xnode); dmnsn_array_get(astnode.children, 1, &ynode); dmnsn_array_get(astnode.children, 2, &znode); double x = dmnsn_realize_float(xnode), y = dmnsn_realize_float(ynode), z = dmnsn_realize_float(znode); return dmnsn_new_vector(x, y, z); } static dmnsn_color dmnsn_realize_color(dmnsn_astnode astnode) { dmnsn_assert(astnode.type == DMNSN_AST_COLOR, "Expected a color."); dmnsn_astnode rnode, gnode, bnode, fnode, tnode; dmnsn_array_get(astnode.children, 0, &rnode); dmnsn_array_get(astnode.children, 1, &gnode); dmnsn_array_get(astnode.children, 2, &bnode); dmnsn_array_get(astnode.children, 3, &fnode); dmnsn_array_get(astnode.children, 4, &tnode); double r = dmnsn_realize_float(rnode), g = dmnsn_realize_float(gnode), b = dmnsn_realize_float(bnode), f = dmnsn_realize_float(fnode), t = dmnsn_realize_float(tnode); dmnsn_sRGB sRGB = { .R = r, .G = g, .B = b }; dmnsn_color color = dmnsn_color_from_sRGB(sRGB); color.filter = f; color.trans = t; return color; } static dmnsn_matrix dmnsn_realize_rotation(dmnsn_astnode astnode) { dmnsn_assert(astnode.type == DMNSN_AST_ROTATION, "Expected a rotation."); const double deg2rad = atan(1.0)/45.0; dmnsn_astnode angle_node; dmnsn_array_get(astnode.children, 0, &angle_node); dmnsn_vector angle = dmnsn_vector_mul( deg2rad, dmnsn_realize_vector(angle_node) ); dmnsn_matrix trans = dmnsn_rotation_matrix( dmnsn_new_vector(angle.x, 0.0, 0.0) ); trans = dmnsn_matrix_mul( dmnsn_rotation_matrix(dmnsn_new_vector(0.0, angle.y, 0.0)), trans ); trans = dmnsn_matrix_mul( dmnsn_rotation_matrix(dmnsn_new_vector(0.0, 0.0, angle.z)), trans ); return trans; } static dmnsn_matrix dmnsn_realize_scale(dmnsn_astnode astnode) { dmnsn_assert(astnode.type == DMNSN_AST_SCALE, "Expected a scale."); dmnsn_astnode scale_node; dmnsn_array_get(astnode.children, 0, &scale_node); dmnsn_vector scale = dmnsn_realize_vector(scale_node); return dmnsn_scale_matrix(scale); } static dmnsn_matrix dmnsn_realize_translation(dmnsn_astnode astnode) { dmnsn_assert(astnode.type == DMNSN_AST_TRANSLATION, "Expected a translation."); dmnsn_astnode trans_node; dmnsn_array_get(astnode.children, 0, &trans_node); dmnsn_vector trans = dmnsn_realize_vector(trans_node); return dmnsn_translation_matrix(trans); } static dmnsn_camera * dmnsn_realize_camera(dmnsn_astnode astnode) { dmnsn_assert(astnode.type == DMNSN_AST_CAMERA, "Expected a camera."); const double deg2rad = atan(1.0)/45.0; dmnsn_astnode_type camera_type = DMNSN_AST_PERSPECTIVE; dmnsn_vector location = dmnsn_new_vector(0.0, 0.0, 0.0); dmnsn_vector direction = dmnsn_new_vector(0.0, 0.0, 1.0); dmnsn_vector right = dmnsn_new_vector(4.0/3.0, 0.0, 0.0); dmnsn_vector up = dmnsn_new_vector(0.0, 1.0, 0.0); dmnsn_vector sky = dmnsn_new_vector(0.0, 1.0, 0.0); dmnsn_matrix trans = dmnsn_identity_matrix(); dmnsn_camera *camera = NULL; unsigned int i; for (i = 0; i < dmnsn_array_size(astnode.children); ++i) { dmnsn_astnode item; dmnsn_array_get(astnode.children, i, &item); switch (item.type) { /* Camera types */ case DMNSN_AST_PERSPECTIVE: camera_type = item.type; break; /* Camera vectors */ case DMNSN_AST_LOCATION: dmnsn_array_get(item.children, 0, &item); location = dmnsn_realize_vector(item); break; case DMNSN_AST_RIGHT: dmnsn_array_get(item.children, 0, &item); right = dmnsn_realize_vector(item); break; case DMNSN_AST_UP: dmnsn_array_get(item.children, 0, &item); right = dmnsn_realize_vector(item); break; case DMNSN_AST_SKY: dmnsn_array_get(item.children, 0, &item); sky = dmnsn_realize_vector(item); break; case DMNSN_AST_DIRECTION: dmnsn_array_get(item.children, 0, &item); direction = dmnsn_realize_vector(item); break; /* Camera modifiers */ case DMNSN_AST_LOOK_AT: { dmnsn_array_get(item.children, 0, &item); dmnsn_vector look_at = dmnsn_realize_vector(item); /* Line the camera up with the sky */ dmnsn_matrix sky1 = dmnsn_rotation_matrix( dmnsn_vector_mul( dmnsn_vector_axis_angle(up, sky, direction), dmnsn_vector_normalize(direction) ) ); up = dmnsn_matrix_vector_mul(sky1, up); right = dmnsn_matrix_vector_mul(sky1, right); dmnsn_matrix sky2 = dmnsn_rotation_matrix( dmnsn_vector_mul( dmnsn_vector_axis_angle(up, sky, right), dmnsn_vector_normalize(right) ) ); up = dmnsn_matrix_vector_mul(sky2, up); direction = dmnsn_matrix_vector_mul(sky2, direction); /* Line up the camera with the look_at */ look_at = dmnsn_vector_sub(look_at, location); dmnsn_matrix look_at1 = dmnsn_rotation_matrix( dmnsn_vector_mul( dmnsn_vector_axis_angle(direction, look_at, up), dmnsn_vector_normalize(up) ) ); right = dmnsn_matrix_vector_mul(look_at1, right); direction = dmnsn_matrix_vector_mul(look_at1, direction); dmnsn_matrix look_at2 = dmnsn_rotation_matrix( dmnsn_vector_mul( dmnsn_vector_axis_angle(direction, look_at, right), dmnsn_vector_normalize(right) ) ); up = dmnsn_matrix_vector_mul(look_at2, up); direction = dmnsn_matrix_vector_mul(look_at2, direction); break; } case DMNSN_AST_ANGLE: { dmnsn_array_get(item.children, 0, &item); double angle = deg2rad*dmnsn_realize_float(item); direction = dmnsn_vector_mul( 0.5*dmnsn_vector_norm(right)/tan(angle/2.0), dmnsn_vector_normalize(direction) ); break; } /* Transformations */ case DMNSN_AST_ROTATION: trans = dmnsn_matrix_mul(dmnsn_realize_rotation(item), trans); break; case DMNSN_AST_SCALE: trans = dmnsn_matrix_mul(dmnsn_realize_scale(item), trans); break; case DMNSN_AST_TRANSLATION: trans = dmnsn_matrix_mul(dmnsn_realize_translation(item), trans); break; default: dmnsn_assert(false, "Invalid camera item."); break; } } switch (camera_type) { case DMNSN_AST_PERSPECTIVE: { /* These multiplications are in right-to-left order so that user transformations happen after camera alignment */ trans = dmnsn_matrix_mul(trans, dmnsn_translation_matrix(location)); /* Align y with `up' */ dmnsn_matrix align = dmnsn_rotation_matrix( dmnsn_vector_mul( dmnsn_vector_axis_angle(dmnsn_y, up, dmnsn_z), dmnsn_z ) ); dmnsn_vector x = dmnsn_matrix_vector_mul(align, dmnsn_x); dmnsn_vector y = dmnsn_matrix_vector_mul(align, dmnsn_y); align = dmnsn_matrix_mul( dmnsn_rotation_matrix( dmnsn_vector_mul( dmnsn_vector_axis_angle(y, up, x), x ) ), align ); /* Align x with `right' */ align = dmnsn_matrix_mul( dmnsn_rotation_matrix( dmnsn_vector_mul( dmnsn_vector_axis_angle(x, right, up), dmnsn_vector_normalize(up) ) ), align ); trans = dmnsn_matrix_mul(trans, align); /* Scale the camera with `up', `right', and `direction' */ trans = dmnsn_matrix_mul( trans, dmnsn_scale_matrix( dmnsn_new_vector( dmnsn_vector_norm(right), dmnsn_vector_norm(up), dmnsn_vector_norm(direction) ) ) ); camera = dmnsn_new_perspective_camera(); dmnsn_set_perspective_camera_trans(camera, trans); break; } default: dmnsn_assert(false, "Unsupported camera type."); } return camera; } static dmnsn_pigment * dmnsn_realize_pigment(dmnsn_astnode astnode) { dmnsn_assert(astnode.type == DMNSN_AST_PIGMENT, "Expected a pigment."); dmnsn_pigment *pigment = NULL; dmnsn_astnode color_node; dmnsn_array_get(astnode.children, 0, &color_node); dmnsn_color color; switch (color_node.type) { case DMNSN_AST_NONE: break; case DMNSN_AST_COLOR: color = dmnsn_realize_color(color_node); pigment = dmnsn_new_solid_pigment(color); if (!pigment) { dmnsn_error(DMNSN_SEVERITY_HIGH, "Couldn't create pigment."); } break; default: dmnsn_assert(false, "Invalid pigment color."); } return pigment; } static dmnsn_finish * dmnsn_realize_reflection(dmnsn_astnode astnode) { dmnsn_assert(astnode.type == DMNSN_AST_REFLECTION, "Expected a reflection."); dmnsn_astnode min_node, max_node; dmnsn_array_get(astnode.children, 0, &min_node); dmnsn_array_get(astnode.children, 1, &max_node); dmnsn_color min = dmnsn_realize_color(min_node); dmnsn_color max = dmnsn_realize_color(max_node); double falloff = 1.0; dmnsn_astnode items; dmnsn_array_get(astnode.children, 2, &items); unsigned int i; for (i = 0; i < dmnsn_array_size(items.children); ++i) { dmnsn_astnode item, child; dmnsn_array_get(items.children, i, &item); switch (item.type) { case DMNSN_AST_FALLOFF: dmnsn_array_get(item.children, 0, &child); falloff = dmnsn_realize_float(child); break; default: dmnsn_assert(false, "Invalid reflection item."); } } dmnsn_finish *reflection = dmnsn_new_reflective_finish(min, max, falloff); if (!reflection) { dmnsn_error(DMNSN_SEVERITY_HIGH, "Couldn't allocate a reflection."); } return reflection; } static dmnsn_finish * dmnsn_realize_finish(dmnsn_astnode astnode) { dmnsn_assert(astnode.type == DMNSN_AST_FINISH, "Expected a finish."); dmnsn_finish *finish = dmnsn_new_finish(); dmnsn_color ambient = dmnsn_black; bool ambient_set = false; double diffuse = 0.0; bool diffuse_set = false; double phong = 0.0; double phong_size = 40.0; dmnsn_finish *reflection = NULL; unsigned int i; for (i = 0; i < dmnsn_array_size(astnode.children); ++i) { dmnsn_astnode item, child; dmnsn_array_get(astnode.children, i, &item); switch (item.type) { case DMNSN_AST_AMBIENT: dmnsn_array_get(item.children, 0, &child); ambient = dmnsn_realize_color(child); ambient_set = true; break; case DMNSN_AST_DIFFUSE: dmnsn_array_get(item.children, 0, &child); diffuse = dmnsn_realize_float(child); diffuse_set = true; break; case DMNSN_AST_PHONG: dmnsn_array_get(item.children, 0, &child); phong = dmnsn_realize_float(child); break; case DMNSN_AST_PHONG_SIZE: dmnsn_array_get(item.children, 0, &child); phong_size = dmnsn_realize_float(child); break; case DMNSN_AST_REFLECTION: dmnsn_delete_finish(reflection); reflection = dmnsn_realize_reflection(item); break; default: dmnsn_assert(false, "Invalid finish item."); } } if (ambient_set) { finish = dmnsn_new_finish_combination( dmnsn_new_ambient_finish(ambient), finish ); } if (diffuse_set) { finish = dmnsn_new_finish_combination( dmnsn_new_diffuse_finish(diffuse), finish ); } if (phong) { finish = dmnsn_new_finish_combination( dmnsn_new_phong_finish(phong, phong_size), finish ); } if (reflection) { finish = dmnsn_new_finish_combination(reflection, finish); } if (!finish) { dmnsn_error(DMNSN_SEVERITY_HIGH, "Couldn't allocate finish."); } return finish; } static dmnsn_texture * dmnsn_realize_texture(dmnsn_astnode astnode) { dmnsn_assert(astnode.type == DMNSN_AST_TEXTURE, "Expected a texture."); dmnsn_texture *texture = dmnsn_new_texture(); if (!texture) { dmnsn_error(DMNSN_SEVERITY_HIGH, "Couldn't allocate texture."); } unsigned int i; for (i = 0; i < dmnsn_array_size(astnode.children); ++i) { dmnsn_astnode modifier; dmnsn_array_get(astnode.children, i, &modifier); switch (modifier.type) { case DMNSN_AST_PIGMENT: dmnsn_delete_pigment(texture->pigment); texture->pigment = dmnsn_realize_pigment(modifier); break; case DMNSN_AST_FINISH: dmnsn_delete_finish(texture->finish); texture->finish = dmnsn_realize_finish(modifier); break; default: dmnsn_assert(false, "Invalid texture item."); } } return texture; } static void dmnsn_realize_object_modifiers(dmnsn_astnode astnode, dmnsn_object *object) { dmnsn_assert(astnode.type == DMNSN_AST_OBJECT_MODIFIERS, "Expected object modifiers."); unsigned int i; for (i = 0; i < dmnsn_array_size(astnode.children); ++i) { dmnsn_astnode modifier; dmnsn_array_get(astnode.children, i, &modifier); switch (modifier.type) { case DMNSN_AST_ROTATION: object->trans = dmnsn_matrix_mul( dmnsn_realize_rotation(modifier), object->trans ); break; case DMNSN_AST_SCALE: object->trans = dmnsn_matrix_mul( dmnsn_realize_scale(modifier), object->trans ); break; case DMNSN_AST_TRANSLATION: object->trans = dmnsn_matrix_mul( dmnsn_realize_translation(modifier), object->trans ); break; case DMNSN_AST_TEXTURE: dmnsn_delete_texture(object->texture); object->texture = dmnsn_realize_texture(modifier); break; default: dmnsn_assert(false, "Invalid object modifier."); } } } static dmnsn_object * dmnsn_realize_box(dmnsn_astnode astnode) { dmnsn_assert(astnode.type == DMNSN_AST_BOX, "Expected a box."); dmnsn_astnode corner1, corner2; dmnsn_array_get(astnode.children, 0, &corner1); dmnsn_array_get(astnode.children, 1, &corner2); dmnsn_vector x1 = dmnsn_realize_vector(corner1), x2 = dmnsn_realize_vector(corner2); dmnsn_object *box = dmnsn_new_cube(); if (!box) { dmnsn_error(DMNSN_SEVERITY_HIGH, "Couldn't allocate box."); } box->trans = dmnsn_scale_matrix( dmnsn_new_vector(fabs(x2.x - x1.x)/2.0, fabs(x2.y - x1.y)/2.0, fabs(x2.z - x1.z)/2.0) ); box->trans = dmnsn_matrix_mul( dmnsn_translation_matrix(dmnsn_new_vector((x2.x + x1.x)/2.0, (x2.y + x1.y)/2.0, (x2.z + x1.z)/2.0)), box->trans ); dmnsn_astnode modifiers; dmnsn_array_get(astnode.children, 2, &modifiers); dmnsn_realize_object_modifiers(modifiers, box); return box; } static dmnsn_light * dmnsn_realize_light_source(dmnsn_astnode astnode) { dmnsn_assert(astnode.type == DMNSN_AST_LIGHT_SOURCE, "Expected a light source."); dmnsn_astnode point, color_node; dmnsn_array_get(astnode.children, 0, &point); dmnsn_array_get(astnode.children, 1, &color_node); dmnsn_vector x0 = dmnsn_realize_vector(point); dmnsn_color color = dmnsn_realize_color(color_node); dmnsn_light *light = dmnsn_new_point_light(x0, color); if (!light) { dmnsn_error(DMNSN_SEVERITY_HIGH, "Couldn't allocate light."); } return light; } static dmnsn_object * dmnsn_realize_sphere(dmnsn_astnode astnode) { dmnsn_assert(astnode.type == DMNSN_AST_SPHERE, "Expected a sphere."); dmnsn_astnode center, radius; dmnsn_array_get(astnode.children, 0, ¢er); dmnsn_array_get(astnode.children, 1, &radius); dmnsn_vector x0 = dmnsn_realize_vector(center); double r = dmnsn_realize_float(radius); dmnsn_object *sphere = dmnsn_new_sphere(); if (!sphere) { dmnsn_error(DMNSN_SEVERITY_HIGH, "Couldn't allocate sphere."); } sphere->trans = dmnsn_scale_matrix(dmnsn_new_vector(r, r, r)); sphere->trans = dmnsn_matrix_mul(dmnsn_translation_matrix(x0), sphere->trans); dmnsn_astnode modifiers; dmnsn_array_get(astnode.children, 2, &modifiers); dmnsn_realize_object_modifiers(modifiers, sphere); return sphere; } static dmnsn_scene * dmnsn_realize_astree(const dmnsn_astree *astree) { dmnsn_scene *scene = dmnsn_new_scene(); if (!scene) { return NULL; } /* Default finish */ scene->default_texture->finish = dmnsn_new_finish_combination( dmnsn_new_ambient_finish( dmnsn_color_mul(0.1, dmnsn_white) ), dmnsn_new_diffuse_finish(0.6) ); if (!scene->default_texture->finish) { dmnsn_delete_scene(scene); return NULL; } /* Background color */ scene->background = dmnsn_black; /* Create the default perspective camera */ scene->camera = dmnsn_new_perspective_camera(); if (!scene->camera) { dmnsn_delete_scene(scene); return NULL; } /* * Now parse the abstract syntax tree */ dmnsn_astnode astnode; unsigned int i; for (i = 0; i < dmnsn_array_size(astree); ++i) { dmnsn_array_get(astree, i, &astnode); dmnsn_light *light; dmnsn_object *object; switch (astnode.type) { case DMNSN_AST_CAMERA: dmnsn_delete_camera(scene->camera); scene->camera = dmnsn_realize_camera(astnode); break; case DMNSN_AST_BACKGROUND: dmnsn_array_get(astnode.children, 0, &astnode); scene->background = dmnsn_realize_color(astnode); break; case DMNSN_AST_BOX: object = dmnsn_realize_box(astnode); dmnsn_array_push(scene->objects, &object); break; case DMNSN_AST_LIGHT_SOURCE: light = dmnsn_realize_light_source(astnode); dmnsn_array_push(scene->lights, &light); break; case DMNSN_AST_SPHERE: object = dmnsn_realize_sphere(astnode); dmnsn_array_push(scene->objects, &object); break; default: dmnsn_assert(false, "Unrecognised syntax element."); } } return scene; } dmnsn_scene * dmnsn_realize(FILE *file, dmnsn_symbol_table *symtable) { if (!symtable) { symtable = dmnsn_new_symbol_table(); } dmnsn_astree *astree = dmnsn_parse(file, symtable); if (!astree) { return NULL; } dmnsn_scene *scene = dmnsn_realize_astree(astree); dmnsn_delete_astree(astree); return scene; } dmnsn_scene * dmnsn_realize_string(const char *str, dmnsn_symbol_table *symtable) { if (!symtable) { symtable = dmnsn_new_symbol_table(); } dmnsn_astree *astree = dmnsn_parse_string(str, symtable); if (!astree) { return NULL; } dmnsn_scene *scene = dmnsn_realize_astree(astree); dmnsn_delete_astree(astree); return scene; }