// compute a ray-sphere intersection using the geometric solution bool Sphere_intersect(const Sphere *e,const Vec3 *rayorig, const Vec3 *raydir, Real *t0, Real *t1) { Vec3 l; Real tca, d2, thc; Vec3_subs(&l,&e->center,rayorig); tca = Vec3_dot(&l,raydir); if (tca < 0) return false; d2 = Vec3_dot(&l,&l) - tca * tca; if (d2 > e->radius2) return false; thc = sqrt(e->radius2 - d2); if (t0 != NULL && t1 != NULL) { *t0 = tca - thc; *t1 = tca + thc; } return true; }
// Adapted from Jacco Bikker's raytracing tutorial. // TODO: come up with a reasonable explanation for this algorithm. float Sphere_intersect(Sphere* self, Vec3 origin, Vec3 dir) { Vec3 v = Vec3_sub(origin, self->center); float b = -Vec3_dot(v, dir); float det = b*b - Vec3_dot(v, v) + self->radius*self->radius; if (det > 0.0f) { det = sqrtf(det); float i1 = b - det; float i2 = b + det; if (i2 > 0.0f) { if (i1 < 0.0f) return -i2; // hit from the inside else return i1; // hit from the outside } } return 0.0f; // miss }
/* * *************************************************************************** * Routine: Aprx_partInert * * Purpose: Partition the domain using inertial bisection. * Partition sets of points in R^d (d=2 or d=3) by viewing them * as point masses of a rigid body, and by then employing the * classical mechanics ideas of inertia and Euler axes. * * Notes: We first locate the center of mass, then change the coordinate * system so that the center of mass is located at the origin. * We then form the (symmetric) dxd inertia tensor, and then find * the set of (real) eigenvalues and (orthogonal) eigenvectors. * The eigenvectors represent the principle inertial rotation axes, * and the eigenvalues represent the inertial strength in those * principle directions. The smallest inerial component along an * axis represents a direction along which the rigid body is most * "line-like" (assuming all the points have the same mass). * * For our purposes, it makes sense to using the axis (eigenvector) * corresponding to the smallest inertia (eigenvalue) as the line to * bisect with a line (d=2) or a plane (d=3). We know the center of * mass, and once we also have this particular eigenvector, we can * effectively bisect the point set into the two regions separated * by the line/plane simply by taking an inner-product of the * eigenvector with each point (or rather the 2- or 3-vector * representing the point). A positive inner-product represents one * side of the cutting line/plane, and a negative inner-product * represents the other side (a zero inner-product is right on the * cutting line/plane, so we arbitrarily assign it to one region or * the other). * * Author: Michael Holst * *************************************************************************** */ VPUBLIC int Aprx_partInert(Aprx *thee, int pcolor, int numC, double *evec, simHelper *simH) { int i, j, k, lambdaI; double rad, sca, lambda, normal, caxis[3]; Mat3 I, II, V, D; Vnm_print(0,"Aprx_partInert: WARNING: assuming single-chart manifold.\n"); Vnm_print(0,"Aprx_partInert: [pc=%d] partitioning:\n", pcolor); /* form the inertia tensors */ Mat3_eye(I); Mat3_init(II, 0.); for (i=0; i<numC; i++) { /* get vector length (squared!) */ rad = 0.; for (j=0; j<3; j++) { rad += ( simH[i].bc[j] * simH[i].bc[j] ); } /* add contribution to the inertia tensor */ for (j=0; j<3; j++) { for (k=0; k<3; k++) { II[j][k] += ( simH[i].mass * (I[j][k]*rad - simH[i].bc[j]*simH[i].bc[k]) ); } } } /* find the d-principle axes, and isolate the single axis we need */ /* (the principle axis we want is the one with SMALLEST moment) */ sca = Mat3_nrm8(II); Mat3_scal(II, 1./sca); (void)Mat3_qri(V, D, II); lambda = VLARGE; lambdaI = -1; for (i=0; i<3; i++) { if ( VABS(D[i][i]) < lambda ) { lambda = VABS(D[i][i]); lambdaI = i; } } VASSERT( lambda > 0. ); VASSERT( lambda != VLARGE ); VASSERT( lambdaI >= 0 ); for (i=0; i<3; i++) { caxis[i] = V[i][lambdaI]; } normal = Vec3_nrm2(caxis); VASSERT( normal > 0. ); Vec3_scal(caxis,1./normal); /* decompose points based on bisecting principle axis with a line or */ /* plane; we do this using an inner-product test with normal vec "caxis" */ normal = 0; for (i=0; i<numC; i++) { evec[i] = Vec3_dot( simH[i].bc, caxis ); normal += (evec[i]*evec[i]); } normal = VSQRT( normal ); /* normalize the final result */ for (i=0; i<numC; i++) { evec[i] = evec[i] / normal; } return 0; }
// This is the main trace function. It takes a ray as argument (defined by its origin // and direction). We test if this ray intersects any of the geometry in the scene. // If the ray intersects an object, we compute the intersection point, the normal // at the intersection point, and shade this point using this information. // Shading depends on the surface property (is it transparent, reflective, diffuse). // The function returns a color for the ray. If the ray intersects an object that // is the color of the object at the intersection point, otherwise it returns // the background color. void trace(Vec3 *v,const Vec3 *rayorig, const Vec3 *raydir, const unsigned size, const Sphere *spheres, const int depth) { //if (raydir.length() != 1) std::cerr << "Error " << raydir << std::endl; Real tnear = INFINITY; const Sphere *sphere = NULL; unsigned i, j; Vec3 surfaceColor, phit, nhit, aux; Real bias; bool inside; // find intersection of this ray with the sphere in the scene for (i = 0; i < size; ++i) { Real t0 = INFINITY, t1 = INFINITY; if (Sphere_intersect(&spheres[i], rayorig, raydir, &t0, &t1)) { if (t0 < 0) t0 = t1; if (t0 < tnear) { tnear = t0; sphere = &spheres[i]; } } } // if there's no intersection return black or background color if (!sphere) { Vec3_new1(v,2); return; } Vec3_new0(&surfaceColor); // color of the ray/surfaceof the object intersected by the ray // phit = rayorig + raydir * tnear; // point of intersection Vec3_axpy(&phit,tnear,raydir,rayorig); Vec3_subs(&nhit, &phit, &sphere->center); // normal at the intersection point Vec3_normalize(&nhit); // normalize normal direction // If the normal and the view direction are not opposite to each other // reverse the normal direction. That also means we are inside the sphere so set // the inside bool to true. Finally reverse the sign of IdotN which we want // positive. bias = 1e-4; // add some bias to the point from which we will be tracing inside = false; if (Vec3_dot(raydir,&nhit) > 0) Vec3_minus(&nhit), inside = true; if ((sphere->transparency > 0 || sphere->reflection > 0) && depth < MAX_RAY_DEPTH) { Real facingratio = -Vec3_dot(raydir,&nhit); // change the mix value to tweak the effect Real fresneleffect = mix(pow(1 - facingratio, 3), 1, 0.1); // compute reflection direction (not need to normalize because all vectors // are already normalized) Vec3 refldir, reflection, refraction; // refldir = raydir - nhit * 2 * raydir.dot(nhit); Vec3_axpy(&refldir, -2 * Vec3_dot(raydir, &nhit), &nhit, raydir); Vec3_normalize(&refldir); Vec3_axpy(&aux, bias, &nhit, &phit); trace( &reflection, &aux, &refldir, size, spheres, depth + 1); Vec3_new0(&refraction); // if the sphere is also transparent compute refraction ray (transmission) if (sphere->transparency) { Real ior = 1.1, eta = (inside) ? ior : 1 / ior; // are we inside or outside the surface? Real cosi = -Vec3_dot(&nhit,raydir); Real k = 1 - eta * eta * (1 - cosi * cosi); Vec3 refrdir; // refrdir = raydir * eta + nhit * (eta * cosi - sqrt(k)); refrdir = nhit; Vec3_scale(&refrdir, eta * cosi - sqrt(k)); Vec3_axpy(&refrdir, eta, raydir, &refrdir); Vec3_normalize(&refrdir); Vec3_axpy(&aux, -bias, &nhit, &phit); trace(&refraction, &aux, &refrdir, size, spheres, depth + 1); } // the result is a mix of reflection and refraction (if the sphere is transparent) //surfaceColor = (reflection * fresneleffect + // refraction * (1 - fresneleffect) * sphere->transparency) * sphere->surfaceColor; surfaceColor = refraction; Vec3_scale(&surfaceColor, (1 - fresneleffect) * sphere->transparency); Vec3_axpy(&surfaceColor, fresneleffect, &reflection, &surfaceColor); Vec3_prod(&surfaceColor, &surfaceColor, &sphere->surfaceColor); } else { // it's a diffuse object, no need to raytrace any further for (i = 0; i < size; ++i) { if (spheres[i].emissionColor.x > 0) { // this is a light Vec3 transmission, lightDirection; Vec3_new1(&transmission, 1); Vec3_subs(&lightDirection, &spheres[i].center, &phit); Vec3_normalize(&lightDirection); for (j = 0; j < size; ++j) { if (i != j) { Real t0, t1; Vec3_axpy(&aux, bias, &nhit, &phit); if (Sphere_intersect(&spheres[j], &aux, &lightDirection, &t0, &t1)) { // Truco para suavizar sombras if (spheres[j].transparency == 0.0) { Vec3_new0(&transmission); break; } else { Vec3_scale(&transmission, spheres[j].transparency); Vec3_prod(&transmission, &transmission, &spheres[j].surfaceColor); } } } } //surfaceColor += sphere->surfaceColor * transmission * // std::max(T(0), nhit.dot(lightDirection)) * spheres[i]->emissionColor; Vec3_prod(&aux, &sphere->surfaceColor, &transmission); Vec3_prod(&aux, &aux, &spheres[i].emissionColor); Vec3_axpy(&surfaceColor, max(0.0, Vec3_dot(&nhit, &lightDirection)), &aux, &surfaceColor); } } } Vec3_add(v, &surfaceColor, &sphere->emissionColor); }