home *** CD-ROM | disk | FTP | other *** search
- /*
- * sphere.c -- standard sphere routines.
- *
- * (c) 1993, 1994 by Han-Wen Nienhuys <hanwen@stack.urc.tue.nl>
- *
- * This is based on work by George Kyriazis.
- *
- * This program 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;
- *
- * This program 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, write to the Free Software Foundation, Inc.,
- * 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
- #include "ray.h"
- #include "proto.h"
- #include "extern.h"
-
- extern struct methods my_methods;
-
-
- PRIVATE bool
- all_sphere_intersections(dqueue * q, object *o, struct ray *r,
- int flags, /* give all intersections */
- bool *isinside)
- { /* for use with CSG: isinside ==
- * inside_sphere() */
- dqueue q_ent;
- int no_int;
- double d[2];
-
-
- if (o->inv_trans != NULL) {
- struct ray localray;
-
- localray = *r;
- transform_ray(&localray, o);
- no_int = intersect_sphere(o->data.sphere->center, o->data.sphere->rsq, &localray, d);
- } else
- no_int = intersect_sphere(o->data.sphere->center, o->data.sphere->rsq, r, d);
-
- *isinside = (no_int % 2) ^ o->inverted;
-
- if (!no_int)
- return FALSE;
-
-
- q_ent.t = d[0];
- q_ent.obj = o;
- q_ent.entering = !(*isinside);
- add_to_queue(q, q_ent);
-
- if ((flags & CHKALL) && no_int == 2) {
- q_ent.t = d[1];
- q_ent.entering = (*isinside);
- add_to_queue(q, q_ent);
- }
- return TRUE;
- }
-
- /* Intersect ray with sphere */
- PUBLIC int
- intersect_sphere(vector center, double rsq, struct ray *r, double *hits)
- {
- vector center_direction;
- double b,
- c,
- d,
- dir_projection,
- dir_len,
- cd_lensq;
- double solmax,
- solmin;
-
-
- my_methods.test++;
-
- /*
- * solve: x = (pos+t*dir) and |x-center|^2 = radius^2
- */
- vsub(center_direction, r->pos, center); /* vector from center to
- * r.pos */
- dir_projection = vdot(r->dir, center_direction);
- cd_lensq = vdot(center_direction, center_direction); /* square of length of
- * center_direction */
-
- if (cd_lensq >= rsq && /* we're not inside the sphere */
- dir_projection > 0) { /* and we're not looking at the sphere */
- return 0;
- }
- b = 2 * dir_projection;
- c = cd_lensq - rsq;
-
- /* with the advent of extrusions, |r.dir| isn't always 1 */
- dir_len = vdot(r->dir, r->dir);
- d = b * b - 4 * dir_len * c;
-
- if (d < 0)
- return 0;
-
- dir_len *= 2;
- d = sqrt(d);
- solmax = (-b + d) / (dir_len);
- solmin = (-b - d) / (dir_len);
-
- if (solmax < tolerance)
- return 0; /* maximum is behind us. Shake it. */
-
- /* This is an intersection! */
- my_methods.hit++;
- if (solmin < tolerance) {
- *hits = solmax;
- return 1;
- } else {
- *hits++ = solmin;
- *hits = solmax;
- return 2;
- }
-
- }
-
- PRIVATE vector
- sphere_normal(struct intersect i, vector x)
- {
- vector n;
- object *o = i.q_ent.obj;
-
-
- if (o->inv_trans != NULL)
- x = mvproduct(*o->inv_trans, x);
-
-
- /* calculate the normal. It is just the direction of the radius */
- /* norm(i.x - cntr) */
- vsub(n, x, o->data.sphere->center);
-
- if (o->inv_trans != NULL)
- n = transform_normal(*o->inv_trans, n);
-
- norm(n, n);
-
- return n;
- }
-
- PRIVATE void
- precompute_sphere(object *o)
- {
- struct sphere_data *sph = o->data.sphere;
-
- sph->rsq = sqr(sph->radius);
- sph->radius = ABS(sph->radius);
-
- /* bounding volume calculation */
-
- /*
- * The bounding volume of a sphere is found. The algorithm performs
- * three passes, one for each dimension. In each pass the extrema of
- * the surface are found as values of the parameters u and v. These
- * values are then used to calculate the position of the two points in
- * the current dimension. Finally, these points are sorted to find the
- * minimum and maximum extents. The canonical sphere has a radius of
- * 1.0 and is centered at the origin.
- */
-
-
-
- {
- double min[3],
- max[3]; /* returned minimum and maximum of extent */
- matrix ctm,
- tempmatrix; /* cumulative transformation
- * matrix */
-
- int i;
- float u1,
- u2,
- v1,
- v2,
- tmp,
- denominator;
- vector scal;
-
- setvector(scal, sph->radius, sph->radius, sph->radius);
- scale_matrix(ctm, scal);
- translate_matrix(tempmatrix, sph->center);
- mmproduct(tempmatrix, tempmatrix, ctm);
-
- if (o->inv_trans) {
- invert_trans(tempmatrix, *o->inv_trans);
- mmproduct(tempmatrix, tempmatrix, ctm);
- }
- transpose_matrix(ctm, tempmatrix);
-
- for (i = 0; i < 3; i++) {
-
- /* calculate first extremum. */
- u1 = safe_arctangent(ctm[2][i], ctm[0][i]);
- denominator = ctm[0][i] * cos(u1) + ctm[2][i] * sin(u1);
- v1 = safe_arctangent(ctm[1][i], denominator);
-
- /* second extremum is +/- PI from u1, negative of v1 */
- if (u1 <= 0)
- u2 = u1 + M_PI;
- else
- u2 = u1 - M_PI;
- v2 = -v1;
-
- /* find and sort extrema locations in this dimension */
- min[i] =
- ctm[0][i] * cos(u1) * cos(v1) +
- ctm[1][i] * sin(v1) +
- ctm[2][i] * sin(u1) * cos(v1) +
- ctm[3][i];
- max[i] =
- ctm[0][i] * cos(u2) * cos(v2) +
- ctm[1][i] * sin(v2) +
- ctm[2][i] * sin(u2) * cos(v2) +
- ctm[3][i];
- if (min[i] > max[i]) {
- tmp = max[i];
- max[i] = min[i];
- min[i] = tmp;
- }
- }
- o->bmin.x = min[0];
- o->bmin.y = min[1];
- o->bmin.z = min[2];
- o->bmax.x = max[0];
- o->bmax.y = max[1];
- o->bmax.z = max[2];
- }
-
- my_methods.howmuch++;
- global_stats.prims++;
- }
-
- PRIVATE bool
- inside_sphere(object *o, vector x)
- {
- if (o->inv_trans)
- x = mvproduct(*o->inv_trans, x);
- vsub(x, x, o->data.sphere->center);
- if (vdot(x, x) < o->data.sphere->rsq)
- return !o->inverted;
- else
- return o->inverted;
- }
-
- PRIVATE void
- translate_sphere(object *o, vector t)
- {
- struct sphere_data *s;
-
- s = o->data.sphere;
- if (o->inv_trans)
- generic_translate_object(o, t);
- else
- vadd(s->center, s->center, t);
- }
-
- PRIVATE void
- scale_sphere(object *o, vector s)
- {
- struct sphere_data *sph;
- double factor;
-
- sph = o->data.sphere;
-
-
- if (ABS(s.x) != ABS(s.y) || ABS(s.y) != ABS(s.z)) {
- /* scale unevenly */
- generic_scale_object(o, s);
- } else {
- factor = ABS(s.x);
- sph->radius *= factor;
- vcproduct(sph->center, s, sph->center);
- }
- }
-
- PRIVATE void
- rotate_sphere(object *o, matrix m)
- {
- struct sphere_data *sph;
-
- sph = o->data.sphere;
- if (o->inv_trans != NULL)
- generic_rotate_object(o, m);
- else
- rotate_vector(&sph->center, m);
- }
-
- PRIVATE void
- init_sphere(struct sphere_data *s)
- {
- setvector(s->center, 0, 0, 0);
- s->radius = 1.0;
- }
-
- PRIVATE struct sphere_data *
- get_new_sphere(void)
- {
- struct sphere_data *s;
-
- s = ALLOC(struct sphere_data);
-
- CHECK_MEM(s, my_methods.name);
-
- init_sphere(s);
- return s;
- }
-
- PRIVATE void
- copy_sphere(object *dst, object *src)
- {
- assert(dst != NULL && src != NULL);
- if (dst->type != SPHERE)
- dst->data.sphere = get_new_sphere();
- *dst->data.sphere = *src->data.sphere;
- dst->type = src->type;
- }
-
- PRIVATE void
- free_sphere(object *s)
- {
- free((void *) s->data.sphere);
- s->type = NOSHAPE;
- }
-
- PRIVATE void
- print_sphere(object *o)
- {
- #ifdef DEBUG
- struct sphere_data *s = o->data.sphere;
-
- printf("radius %lf (%f) ", s->radius, s->rsq);
- print_v("cntr", s->center);
- #endif
- }
-
-
- PRIVATE struct methods my_methods =
- {
- all_sphere_intersections,
- sphere_normal,
- copy_sphere,
- inside_sphere,
- rotate_sphere,
- translate_sphere,
- scale_sphere,
- free_sphere,
- print_sphere,
- precompute_sphere,
- "sphere",
- };
-
-
- PUBLIC object *
- get_new_sphere_object(void)
- {
- object *o;
-
- o = get_new_object();
- o->data.sphere = get_new_sphere();
- o->methods = &my_methods;
- o->type = SPHERE;
-
- return o;
- }
-