float sphere_intersect(vec3 center, float radius, const ray& ray, const range &r) { if(keep_stats) sphere_intersections++; vec3 diff = vec3_subtract(ray.o, center); float b = 2 * vec3_dot(ray.d, diff); float radicand = b * b - 4 * (vec3_dot(diff, diff) - radius * radius); if(radicand < 0) return NO_t; float t1 = (-b + sqrtf(radicand)) / 2; if(t1 < r.t0) return NO_t; float t0 = (-b - sqrtf(radicand)) / 2; if(t0 > r.t1) return NO_t; if(t0 < r.t0) return t1; return t0; }
/** * Component of the support function for a cylinder collider. **/ static void object_support_component_cylinder(object_t *object, float direction[3], float out[3]) { float top[3] = { 0, object->h / 2, 0 }; float bottom[3] = { 0, -object->h / 2, 0 }; float axis[3]; float dir_perp[3]; float trans[16]; float dot_top; object_get_transform_mat(object, trans); /* Top and bottom are set to points in the middle of the top and the * bottom faces of the cylinder respectively. We transform them to * their worldspace positions. */ matrix_vec3_mul(trans, top, 1.0, top); matrix_vec3_mul(trans, bottom, 1.0, bottom); /* We get an axis vector that runs down the middle of the cylinder. */ vec3_subtract(top, bottom, axis); /* Part of another process, but useful now for a reason... */ vec3_cross(axis, direction, dir_perp); /* If the cross product is zero, our direction is aligned with the * cylinder and top and bottom are our two final candidates. */ if (vec3_magnitude(dir_perp) > 0) { /* This completes what we started with the last cross product. * dir_perp is now a vector perpendicular to the cylinder's * axis, but as close to our selected direction as possible. */ vec3_cross(dir_perp, axis, dir_perp); /* Scale dir_perp to be the radius of our cylinder. */ vec3_normalize(dir_perp, dir_perp); vec3_scale(dir_perp, dir_perp, object->r); /* Finally, move top and bottom to the edges of our cylinder in * the appropriate direction. We now have our two final * candidates. */ vec3_add(dir_perp, top, top); vec3_add(dir_perp, bottom, bottom); } /* We now have two candidates, top and bottom. We can just use largest * dot product to determine which is furthest. */ dot_top = vec3_dot(top, direction); if (dot_top > vec3_dot(bottom, direction)) memcpy(out, top, 3 * sizeof(float)); else memcpy(out, bottom, 3 * sizeof(float)); }
static void find_nearest_block(struct group *group, struct ray const *ray, struct nearest *nearest) { struct groupdata *groupdata = group->data; struct ray groupspaceray; vec3_subtract(&groupspaceray.origin, &ray->origin, &group->position); groupspaceray.direction = ray->direction; buffer *buffer = create_buffer(); ptrarray *blocks = groupdata->blocks; size_t blockcount = ptrarray_count(blocks); for (size_t i = 0; i < blockcount; ++i) { block *block = get_ptrarray(blocks, i); get_block_triangles(block, buffer); triangle *tris = buffer_data(buffer); size_t tricount = buffer_count(buffer, sizeof(struct triangle)); for (size_t j = 0; j < tricount; ++j) { float temp; if (ray_intersect_triangle(&temp, &groupspaceray, &tris[j])) { /* * Use <= for distance comparsion here so that when there are * multiple candidates for selection the last block added will * be selected. It is useful after pasting that the new blocks * can be dragged to a new location. */ if (temp <= nearest->distance) { nearest->distance = temp; nearest->group = group; nearest->block = block; nearest->triangle = tris[j]; } } } clear_buffer(buffer); } destroy_buffer(buffer); struct ptrarray *groups = groupdata->groups; size_t groupcount = ptrarray_count(groups); for (size_t i = 0; i < groupcount; ++i) { struct group *childgroup = get_ptrarray(groups, i); find_nearest_block(childgroup, ray, nearest); } }
/** * GJK collision support function. **/ static void object_support(object_t *a, object_t *b, float direction[3], float out[3]) { float pa[3]; float pb[3]; object_support_component(a, direction, pa); vec3_scale(direction, direction, -1); object_support_component(b, direction, pb); vec3_subtract(pa, pb, out); }
void move_group_origin(struct group *group, vec3 const *position) { vec3 translation; vec3_subtract(&translation, &group->position, position); foreach_block(group->data, translate_block_, &translation); foreach_group(group->data, translate_group_, &translation); group->position = *position; update_group_vertexarray(group); }
void keyboard(unsigned char key, int x, int y) { float direction[3]; float rotation[16]; mat4_identity(rotation); switch (key) { case 'q': vec3_subtract(center, eye, direction); vec3_normalize(direction, direction); vec3_add(eye, direction, eye); break; case 'e': vec3_subtract(center, eye, direction); vec3_normalize(direction, direction); vec3_subtract(eye, direction, eye); break; case 'a': mat4_rotateY(rotation, 0.1f, rotation); mat4_multiply(rotation, model, model); break; case 'd': mat4_rotateY(rotation, -0.1f, rotation); mat4_multiply(rotation, model, model); break; case 'w': mat4_rotateX(rotation, 0.1f, rotation); mat4_multiply(rotation, model, model); break; case 's': mat4_rotateX(rotation, -0.1f, rotation); mat4_multiply(rotation, model, model); break; case 27: exit(0); default: break; } }
bool sphere_possible(vec3 center, float radius, const ray& ray) { if(keep_stats) sphere_tests++; vec3 diff = vec3_subtract(ray.o, center); float b = 2 * vec3_dot(ray.d, diff); float radicand = b * b - 4 * (vec3_dot(diff, diff) - radius * radius); if(radicand < 0) return false; return true; }
/* Manually apply a look-at matrix. Will reset MODELVIEW automatically. */ void cm_apply_look_at(VEC3 target, VEC3 position) { VEC3 forward = vec3_normalize(vec3_subtract(target, position)); VEC3 right = vec3_cross(forward, (VEC3){ 0, 1, 0 }); VEC3 up = vec3_normalize(vec3_cross(forward, right)); double ang = atan(NOT_DUMB_ABS(forward.y) / sqrt(forward.x * forward.x + forward.z * forward.z)); if (ang >= PI_O4) { up = vec3_negate(up); } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(position.x, position.y, position.z, target.x, target.y, target.z, up.x, up.y, up.z); }
range sphere_intersect(vec3 center, float radius, const ray& ray) { if(keep_stats) sphere_intersections++; vec3 diff = vec3_subtract(ray.o, center); float b = 2 * vec3_dot(ray.d, diff); float radicand = b * b - 4 * (vec3_dot(diff, diff) - radius * radius); if(radicand < 0) return range(std::numeric_limits<float>::max(), -std::numeric_limits<float>::max()); float t0 = (-b - sqrtf(radicand)) / 2; float t1 = (-b + sqrtf(radicand)) / 2; return range(t0, t1); }
bool sphere::intersect(const ray& ray, const range& r, surface_hit* hit) { float t = sphere_intersect(center, radius, ray, r); if(t == NO_t) return false; if(t > hit->t) return false; if(keep_stats) sphere_shadings++; vec3 point = vec3_add(ray.o, vec3_scale(ray.d, t)); // snap to sphere surface vec3 to_surface = vec3_subtract(point, center); float distance = sqrtf(vec3_dot(to_surface, to_surface)); hit->point = vec3_add(center, vec3_scale(to_surface, radius / distance)); hit->normal = vec3_divide(to_surface, radius); hit->color = color; hit->t = t; return true; }
void matrix_4x4_lookat(math_matrix_4x4 *out, vec3_t eye, vec3_t center, vec3_t up) { vec3_t zaxis; /* the "forward" vector */ vec3_t xaxis; /* the "right" vector */ vec3_t yaxis; /* the "up" vector */ vec3_copy(&zaxis[0], center); vec3_subtract(&zaxis[0], eye); vec3_normalize(&zaxis[0]); vec3_cross(&xaxis[0], &zaxis[0], up); vec3_normalize(&xaxis[0]); vec3_cross(&yaxis[0], &xaxis[0], zaxis); MAT_ELEM_4X4(*out, 0, 0) = xaxis[0]; MAT_ELEM_4X4(*out, 0, 1) = yaxis[0]; MAT_ELEM_4X4(*out, 0, 2) = -zaxis[0]; MAT_ELEM_4X4(*out, 0, 3) = 0.0; MAT_ELEM_4X4(*out, 1, 0) = xaxis[1]; MAT_ELEM_4X4(*out, 1, 1) = yaxis[1]; MAT_ELEM_4X4(*out, 1, 2) = -zaxis[1]; MAT_ELEM_4X4(*out, 1, 3) = 0.0f; MAT_ELEM_4X4(*out, 2, 0) = xaxis[2]; MAT_ELEM_4X4(*out, 2, 1) = yaxis[2]; MAT_ELEM_4X4(*out, 2, 2) = -zaxis[2]; MAT_ELEM_4X4(*out, 2, 3) = 0.0f; MAT_ELEM_4X4(*out, 3, 0) = -(xaxis[0] * eye[0] + xaxis[1] * eye[1] + xaxis[2] * eye[2]); MAT_ELEM_4X4(*out, 3, 1) = -(yaxis[0] * eye[0] + yaxis[1] * eye[1] + yaxis[2] * eye[2]); MAT_ELEM_4X4(*out, 3, 2) = -(zaxis[0] * eye[0] + zaxis[1] * eye[1] + zaxis[2] * eye[2]); MAT_ELEM_4X4(*out, 3, 3) = 1.f; }
/* TODO/FIXME - finish */ void matrix_4x4_lookat(math_matrix_4x4 *out, vec3_t eye, vec3_t center, vec3_t up) { vec3_t s, t, f; vec3_copy(&f[0], center); vec3_subtract(&f[0], eye); vec3_normalize(&f[0]); vec3_cross(&s[0], &f[0], up); vec3_normalize(&s[0]); vec3_cross(&t[0], &s[0], f); memset(out, 0, sizeof(*out)); MAT_ELEM_4X4(*out, 0, 0) = s[0]; MAT_ELEM_4X4(*out, 0, 1) = t[0]; MAT_ELEM_4X4(*out, 0, 2) = -f[0]; MAT_ELEM_4X4(*out, 1, 0) = s[1]; MAT_ELEM_4X4(*out, 1, 1) = t[1]; MAT_ELEM_4X4(*out, 1, 2) = -f[1]; MAT_ELEM_4X4(*out, 2, 0) = s[2]; MAT_ELEM_4X4(*out, 2, 1) = t[2]; MAT_ELEM_4X4(*out, 2, 2) = -f[2]; MAT_ELEM_4X4(*out, 3, 3) = 1.f; #if 0 mat4x4_translate_in_place(m, -eye[0], -eye[1], -eye[2]); #endif }
/** * Check for collision between objects. **/ int object_check_collision(object_t *a, object_t *b) { float direction[3] = { 0, 1, 0 }; float simplex[4][3]; size_t simplex_pos = 5; size_t last; float middle[3]; float to_last[3]; if (! (object_is_collider(a) && object_is_collider(b))) return 0; object_support(a, b, direction, simplex[0]); direction[2] = -1; object_support(a, b, direction, simplex[1]); if (vec3_dot(simplex[1], direction) <= 0) return 0; vec3_subtract(simplex[1], simplex[0], to_last); vec3_cross(simplex[1], to_last, direction); vec3_cross(direction, to_last, direction); object_support(a, b, direction, simplex[2]); if (vec3_dot(simplex[2], direction) <= 0) return 0; while (vec3_dot(simplex[simplex_pos], direction)) { if (simplex_pos == 5) { simplex_pos = 3; last = 2; } else { last = simplex_pos; simplex_pos = object_simplex_detect_region(simplex, simplex_pos); } if (simplex_pos == 4) return 1; middle[0] = middle[1] = middle[2] = 0; if (simplex_pos != 3) vec3_add(simplex[3], middle, middle); if (simplex_pos != 2) vec3_add(simplex[2], middle, middle); if (simplex_pos != 1) vec3_add(simplex[1], middle, middle); if (simplex_pos != 0) vec3_add(simplex[0], middle, middle); vec3_scale(middle, middle, 1.0/3.0); vec3_subtract(middle, simplex[last], to_last); vec3_cross(middle, to_last, direction); vec3_cross(direction, to_last, direction); object_support(a, b, direction, simplex[simplex_pos]); } return 0; }
world *load_world(char *fname) // Get world and return pointer. { char *inpstr; int wkd; std::auto_ptr<world> w(new world); time_t prev = time(NULL); scoped_FILE fp(fopen(fname, "r")); if(fp == NULL) { fprintf(stderr, "Cannot open file %s for input.\nE#%d\n", fname, errno); return NULL; } if((inpstr = getstr(fp)) == NULL) { fprintf(stderr, "Cannot read title.\n"); return NULL; } if(strcmp(inpstr, ".") != 0) { w->Title = NULL; } else { w->Title = new char[strlen(inpstr) + 1]; strcpy(w->Title, inpstr); } // lace now ignored int lace; if(!getint(fp, &lace)) { fprintf(stderr, "*!LACE\n"); return NULL; } int wdth, lnth; // now ignored if(!getint(fp, &wdth)) { fprintf(stderr, "*!WDTH\n"); return NULL; } if(!getint(fp, &lnth)) { fprintf(stderr, "*!LNTH\n"); return NULL; } int xsub, ysub; // now ignored if(!getint(fp, &xsub)) { fprintf(stderr, "*!xsub\n"); return NULL; } if(!getint(fp, &ysub)) { fprintf(stderr, "*!ysub\n"); return NULL; } w->xsub = 1; w->ysub = 1; int r, g, b, brt; if(!getint(fp, &r)) { fprintf(stderr, "*!backgroundr\n"); return NULL; } if(!getint(fp, &g)) { fprintf(stderr, "*!backgroundg\n"); return NULL; } if(!getint(fp, &b)) { fprintf(stderr, "*!backgroundb\n"); return NULL; } if(!getint(fp, &brt)) { fprintf(stderr, "*!backgroundb\n"); return NULL; } w->background.x = r / 16.0 * brt / 100.0; w->background.y = g / 16.0 * brt / 100.0; w->background.z = b / 16.0 * brt / 100.0; int df; if(!getint(fp, &df)) { fprintf(stderr, "*!DIFFUSION\n"); return NULL; } if(!getint(fp, &w->sphere_count)) { fprintf(stderr, "*!#Sphere\n"); return NULL; } prev = time(NULL); w->spheres = new sphere[w->sphere_count]; for(int i = 0; i < w->sphere_count; i++) { if(time(NULL) > prev) { prev = time(NULL); fprintf(stderr, "loaded %u spheres\n", i); } float x, y, z, radius; wkd = fltget(fp, &x) && fltget(fp, &y); wkd = (wkd && fltget(fp, &z) && fltget(fp, &radius)); int r, g, b, brt; wkd = (wkd && getint(fp, &r) && getint(fp, &g)); wkd = (wkd && getint(fp, &b) && getint(fp, &brt)); if(!wkd) { fprintf(stderr, "*!Sphere #%d\n", i); return NULL; } vec3 color(r / 16.0 * brt / 100.0, g / 16.0 * brt / 100.0, b / 16.0 * brt / 100.0); w->spheres[i] = sphere(vec3(x, y, z), radius, color); } w->scene_center = w->spheres[0].center; for(int i = 1; i < w->sphere_count; i++) { w->scene_center = vec3_add(w->spheres[i].center, w->scene_center); } w->scene_center = vec3_divide(w->scene_center, w->sphere_count); w->scene_extent = 0; for(int i = 0; i < w->sphere_count; i++) { vec3 to_center = vec3_subtract(w->scene_center, w->spheres[i].center); float distance = sqrtf(vec3_dot(to_center, to_center)) + w->spheres[i].radius; w->scene_extent = std::max(w->scene_extent, distance); } w->scene_extent *= 2; w->root = make_tree(w->spheres, 0, w->sphere_count); print_tree_stats(); wkd = fltget(fp, &w->cam.eye.x) && fltget(fp, &w->cam.eye.y); wkd = (wkd && fltget(fp, &w->cam.eye.z) && fltget(fp, &w->cam.yaw)); wkd = (wkd && fltget(fp, &w->cam.pitch) && fltget(fp, &w->cam.roll)); wkd = (wkd && fltget(fp, &w->cam.fov)); if(!wkd) { fprintf(stderr, "*!Viewpoint\n"); return NULL; } w->cam.pitch = to_radians(w->cam.pitch); w->cam.yaw = to_radians(w->cam.yaw); w->cam.roll = to_radians(w->cam.roll); w->cam.fov = to_radians(w->cam.fov); return w.release(); }
group* make_tree(sphere* spheres, int start, unsigned int count, int level = 0) { if(level == 0) { previous = time(NULL); if(getenv("DEBUG_BVH") != NULL) { bvh_spheres_debug_output = fopen("bvh.r9", "w"); } } if(time(NULL) > previous) { fprintf(stderr, "total treed = %d\n", total_treed); previous = time(NULL); } if((level >= bvh_max_depth) || count <= bvh_leaf_max) { total_treed += count; group* g = new group(spheres, start, count); if(bvh_spheres_debug_output != NULL) { // fprintf(bvh_spheres_debug_output, "sphere 7 %f %f %f %f 1 1 1 # %d\n", g->radius, g->center.x, g->center.y, g->center.z, level ); if(level == 0) { fclose(bvh_spheres_debug_output); bvh_spheres_debug_output = NULL; } } bvh_leaf_size_counts[std::min(63U, count)]++; bvh_leaf_count++; bvh_level_counts[level]++; bvh_node_count++; return g; } vec3 split_pivot; vec3 split_plane_normal; // find bounding box vec3 boxmin, boxmax; box_init(boxmin, boxmax); for(unsigned int i = 0; i < count; i++) { sphere &s = spheres[start + i]; vec3 spheremin = vec3_subtract(s.center, vec3(s.radius)); vec3 spheremax = vec3_add(s.center, vec3(s.radius)); box_extend(boxmin, boxmax, spheremin, spheremax); } split_pivot = vec3_scale(vec3_add(boxmin, boxmax), .5); vec3 boxdim = vec3_subtract(boxmax, boxmin); if(boxdim.x > boxdim.y && boxdim.x > boxdim.z) { split_plane_normal = vec3(1, 0, 0); } else if(boxdim.y > boxdim.z) { split_plane_normal = vec3(0, 1, 0); } else { split_plane_normal = vec3(0, 0, 1); } // XXX output split plane to BVH debug file int startA; int countA; int startB; int countB; if(!bvh_split_median) { int s1 = start - 1; int s2 = start + count; do { // from start to s1, not including s1, is negative // from s2 to start + count - 1 is positive do { s1 += 1; } while((s1 < s2) && vec3_dot(vec3_subtract(spheres[s1].center, split_pivot), split_plane_normal) < 0); // If there wasn't a positive sphere before s2, done. if(s1 >= s2) break; // s1 is now location of lowest positive sphere do { s2 -= 1; } while((s1 < s2) && vec3_dot(vec3_subtract(spheres[s2].center, split_pivot), split_plane_normal) >= 0); // If there wasn't a negative sphere between s1 and s2, done if(s1 >= s2) break; // s2 is now location of highest negative sphere std::swap(spheres[s1], spheres[s2]); } while(true); // s1 is the first of the positive spheres startA = start; countA = s1 - startA; startB = s1; countB = start + count - s1; } else { sphere_sorter sorter(split_plane_normal); std::sort(spheres + start, spheres + start + count - 1, sorter); startA = start; countA = count / 2; startB = startA + countA; countB = count - countA; } group *g; if(countA > 0 && countB > 0) { // get a tighter bound around children than hierarchical bounds vec3 boxmin, boxmax; box_init(boxmin, boxmax); for(unsigned int i = 0; i < count; i++) { add_sphere(&boxmin, &boxmax, spheres[start + i].center, spheres[start + i].radius); } // construct children group *g1 = make_tree(spheres, startA, countA, level + 1); g = new group(spheres, g1, NULL, split_plane_normal, boxmin, boxmax); group *g2 = make_tree(spheres, startB, countB, level + 1); g->positive = g2; bvh_level_counts[level]++; bvh_node_count++; } else { total_treed += count; fprintf(stderr, "Leaf node at %d, %u spheres, total %d\n", level, count, total_treed); g = new group(spheres, start, count); bvh_leaf_size_counts[std::min(63U, count)]++; bvh_leaf_count++; bvh_level_counts[level]++; bvh_node_count++; } if(bvh_spheres_debug_output != NULL) { // fprintf(bvh_spheres_debug_output, "sphere 7 %f %f %f %f 1 1 1 # %d\n", g->radius, g->center.x, g->center.y, g->center.z, level ); if(level == 0) { fclose(bvh_spheres_debug_output); bvh_spheres_debug_output = NULL; } } return g; }
bool operator() (const sphere& s1, const sphere& s2) { vec3 diff = vec3_subtract(s2.center, s1.center); return vec3_dot(diff, split_plane_normal) > 0; }