/* * Copyright (c) 1994-1995 Argonaut Technologies Limited. All rights reserved. * * $Id: pick.c 2.9 1997/06/18 12:19:11 JOHNG Exp $ * $Locker: $ * * Pick traversal and testing */ #include "v1db.h" #include "vecifns.h" #include "shortcut.h" #include "brassert.h" #include "math_ip.h" BR_RCS_ID("$Id: pick.c 2.9 1997/06/18 12:19:11 JOHNG Exp $") STATIC br_matrix34 pick_model_to_view; /* * Test a bounding box 'b' against the current pick ray '*rp - t . *rd' * * (NB, the direction of the ray is -ve as compared to normal usage) * * The active section of the ray is given with t_near and t_far * * If the active section of the ray intersects the box, then returns * TRUE, and stores the new t_near and t_far */ STATIC int PickBoundsTestRay(br_bounds *b, br_vector3 *rp, br_vector3 *rd, br_scalar t_near, br_scalar t_far, br_scalar *new_t_near, br_scalar *new_t_far) { int i; br_scalar s,t; /* * Find section of ray that intersects bounding box, if any, by * intersecting the half spaces defined by the bounding planes * with the ray. * * The planes tested against are - * * eqn d * +X 1, 0, 0, -max[0] * +Y 0, 1, 0, -max[1] * +Z 0, 0, 1, -max[2] * -X -1, 0, 0, min[0] * -Y 0,-1, 0, min[1] * -Z 0, 0,-1, min[2] * * The plane to line intersection is (for a general case) * * t = -(d + plane_eqn . position) * -------------------------- * plane_eqn . direction * * The actual calculations take into acount the special case of the * equations, and that the direction as passed in is -ve. * */ /* * For each axis, examine the slab made by the planes that are normal to it */ for(i=0; i<3 ; i++) { /* * See which way ray goes relative to axis */ if(rd->v[i] > (2 * BR_SCALAR_EPSILON)) { /* * Positive axis towards ray, negative axis away from ray */ s = BR_RCP(rd->v[i]); t = BR_MUL(rp->v[i]-b->max.v[i],s); if(t > t_near) t_near = t; t = BR_MUL(rp->v[i]-b->min.v[i],s); if(t < t_far) t_far = t; } else if(rd->v[i] < -(2 * BR_SCALAR_EPSILON)) { /* * Positive axis away from ray, negative axis towards ray */ s = BR_RCP(rd->v[i]); t = BR_MUL(rp->v[i]-b->max.v[i],s); if(t < t_far) t_far = t; t = BR_MUL(rp->v[i]-b->min.v[i],s); if(t > t_near) t_near = t; } else { /* * Ray is perp. to axis - check origin vs. both planes */ if(rp->v[i] > b->max.v[i] || rp->v[i] < b->min.v[i]) { /* * Ray is outside planes */ return 0; } } } /* * If there is any of the ray left, record the near point * and return true */ if(t_near <= t_far) { *new_t_near = t_near; *new_t_far = t_far; return 1; } else return 0; } /* * Traversal function for Picking, tests and actor, and recurse for * children */ STATIC int ActorPick2D(br_actor *ap, br_model *model, br_material *material, br_pick2d_cbfn *callback, void *arg) { br_actor *a; br_model *this_model; br_material *this_material; br_matrix34 m_to_v; br_matrix34 v_to_m; br_scalar t_near,t_far; int r = 0; ASSERT(callback != NULL); ASSERT(ap != NULL); this_model = ap->model?ap->model:model; this_material = ap->material?ap->material:material; /* * Concatenate transform of this actor onto current */ m_to_v = pick_model_to_view; BrMatrix34PreTransform(&pick_model_to_view,&ap->t); /* * Investigate this actor */ switch(ap->type) { case BR_ACTOR_MODEL: /* * Work out view to model transform and transform * the pick ray. * * The pick ray is (0,0,0) - t . (0,0,1), so * this coresponds to the fourth and third rows * of the v_to_m matrix */ BrMatrix34Inverse(&v_to_m,&pick_model_to_view); if(PickBoundsTestRay( &this_model->bounds, (br_vector3*)v_to_m.m[3], (br_vector3*)v_to_m.m[2], BR_SCALAR(0.0),BR_SCALAR_MAX, &t_near,&t_far)) { br_vector3 dir; dir.v[0] = -v_to_m.m[2][0]; dir.v[1] = -v_to_m.m[2][1]; dir.v[2] = -v_to_m.m[2][2]; /* * Invoke callback (with correct direction) */ r = callback(ap, this_model, this_material, (br_vector3*)v_to_m.m[3], &dir, t_near, t_far, arg); if(r) break; } if(r) break; /* FALL THROUGH */ default: BR_FOR_SIMPLELIST(&ap->children,a) { r = ActorPick2D(a,this_model,this_material,callback,arg); if(r) break; } break; case BR_ACTOR_BOUNDS: case BR_ACTOR_BOUNDS_CORRECT: /* * Only recurse to children if view ray intersects bounding box */ ASSERT(ap->type_data != NULL); BrMatrix34Inverse(&v_to_m,&pick_model_to_view); if(PickBoundsTestRay( ap->type_data, (br_vector3*)v_to_m.m[3], (br_vector3*)v_to_m.m[2], BR_SCALAR(0.0),BR_SCALAR_MAX, &t_near,&t_far)) BR_FOR_SIMPLELIST(&ap->children,a) { r = ActorPick2D(a,this_model,this_material,callback,arg); if(r) break; } break; } /* * Restore transform */ pick_model_to_view = m_to_v; return r; } /* * Pick objects from a given scene by casting a ray through the given * viewport pixel attached to the given camera. * * A callback will be invoked for each actor whose bounds intersect * this ray. * * The callback is given the ray point & direction in the actor's * local space, and the 't' values of the intersections of the ray * with the bounding box. * * If the callback returns a non-zero value, traversal stops, and the * value is returned from BrScenePick2D * * A general purpose 'void *' argument is passed down to the callback */ int BR_PUBLIC_ENTRY BrScenePick2D( br_actor *world, br_actor *camera, br_pixelmap *viewport, int pick_x, int pick_y, br_pick2d_cbfn *callback, void *arg) { br_matrix34 camera_tfm; br_scalar scale; br_camera *camera_data; br_int_32 t; UASSERT(world != NULL); UASSERT(camera != NULL); UASSERT(viewport != NULL); UASSERT(callback != NULL); UASSERT(camera->type == BR_ACTOR_CAMERA); UASSERT(camera->type_data != NULL); camera_data = camera->type_data; if ( camera_data->type == BR_CAMERA_PERSPECTIVE_OLD ) { pick_x = pick_x + viewport->origin_x - ( viewport->width / 2 ); pick_y = pick_y + viewport->origin_y - ( viewport->height / 2 ); } /* * Build view->world transform */ ActorToRootTyped(camera, world, &camera_tfm, &t); if(BrTransformTypeIsLP(t)) BrMatrix34LPInverse(&pick_model_to_view,&camera_tfm); else BrMatrix34Inverse(&pick_model_to_view,&camera_tfm); /* * Build view to screen transform s.t. ray is along -ve Z axis * and starts at the origin * */ switch(camera_data->type) { case BR_CAMERA_PERSPECTIVE_FOV: case BR_CAMERA_PERSPECTIVE_FOV_OLD: UASSERT_MESSAGE("BrScenePick2D divide by zero error", BR_SIN((br_angle)(camera_data->field_of_view/2)) != 0); scale = BR_DIV( BR_COS((br_angle)(camera_data->field_of_view/2)), BR_SIN((br_angle)(camera_data->field_of_view/2))); BrMatrix34PostScale(&pick_model_to_view,BR_DIV(scale,camera_data->aspect),scale,BR_SCALAR(1.0)); // BrMatrix34PostScale(&pick_model_to_view,BR_DIV(scale,camera_data->aspect),scale,BR_RCP(camera_data->yon_z)); UASSERT_MESSAGE("BrScenePick2D divide by zero error", viewport->width != 0 ); UASSERT_MESSAGE("BrScenePick2D divide by zero error", viewport->height != 0 ); BrMatrix34PostShearZ(&pick_model_to_view, BR_DIV(BrIntToScalar(2*pick_x),BrIntToScalar(viewport->width)), BR_DIV(BrIntToScalar(-2*pick_y),BrIntToScalar(viewport->height))); BrMatrix34PostScale(&pick_model_to_view,S1,S1,BR_RCP(camera_data->yon_z)); break; case BR_CAMERA_PARALLEL: case BR_CAMERA_PARALLEL_OLD: BrMatrix34PostTranslate(&pick_model_to_view,S0,S0,camera_data->hither_z); BrMatrix34PostScale(&pick_model_to_view, BR_RCP(camera_data->width), BR_RCP(camera_data->height), BR_RCP(camera_data->yon_z - camera_data->hither_z)); BrMatrix34PostTranslate(&pick_model_to_view, BR_DIV(BrIntToScalar(-2*pick_x),BrIntToScalar(viewport->width)), BR_DIV(BrIntToScalar(2*pick_y),BrIntToScalar(viewport->height)), S0); break; } /* * Traverse world */ return ActorPick2D(world,v1db.default_model,v1db.default_material,callback,arg); } /* * Work out whether two bounding boxes into different coordinate frames * interset */ #define MAKE_EQN(axis,sign,distance) {\ eqn.v[0] = sign p_to_m.m[0][axis];\ eqn.v[1] = sign p_to_m.m[1][axis];\ eqn.v[2] = sign p_to_m.m[2][axis];\ eqn.v[3] = sign p_to_m.m[3][axis] distance ;\ }\ #define TEST_NOT_IN\ (BR_MAC3(\ eqn.v[X],((eqn.v[X]>0)?(bounds->min.v[X]):(bounds->max.v[X])),\ eqn.v[Y],((eqn.v[Y]>0)?(bounds->min.v[Y]):(bounds->max.v[Y])),\ eqn.v[Z],((eqn.v[Z]>0)?(bounds->min.v[Z]):(bounds->max.v[Z]))) <\ eqn.v[W]) #define TEST_OUT\ (BR_MAC3(\ eqn.v[X],((eqn.v[X]>0)?(bounds->max.v[X]):(bounds->min.v[X])),\ eqn.v[Y],((eqn.v[Y]>0)?(bounds->max.v[Y]):(bounds->min.v[Y])),\ eqn.v[Z],((eqn.v[Z]>0)?(bounds->max.v[Z]):(bounds->min.v[Z]))) <\ eqn.v[W]) #pragma optimize("g",off) STATIC int PickBoundsTestBox(br_bounds *model_bounds,br_bounds *bounds, br_matrix34 *m_to_p) { br_matrix34 p_to_m; br_vector4 eqn; int i; BrMatrix34Inverse(&p_to_m,m_to_p); /* * For each plane of the model box, check to see if the pick box * is completely outside it */ for(i=0; i< 3; i++) { /* * Min X */ MAKE_EQN(i, - ,+ model_bounds->min.v[i]); if(TEST_OUT) return 0; /* * Max X */ MAKE_EQN(i, +, - model_bounds->max.v[i]); if(TEST_OUT) return 0; } return 1; } /* * Recursive function for 3D picking within a hierachy */ STATIC int ActorPick3D(br_actor *ap, br_model *model, br_material *material, br_bounds *bounds, br_pick3d_cbfn *callback, void *arg) { br_actor *a; br_model *this_model; br_material *this_material; br_matrix34 m_to_v; int r = 0; this_model = ap->model?ap->model:model; this_material = ap->material?ap->material:material; ASSERT(ap != NULL); ASSERT(model != NULL); ASSERT(material != NULL); ASSERT(bounds != NULL); ASSERT(callback != NULL); /* * Concatenate transform of this actor onto current */ m_to_v = pick_model_to_view; BrMatrix34PreTransform(&pick_model_to_view,&ap->t); switch(ap->type) { case BR_ACTOR_MODEL: if(PickBoundsTestBox(&this_model->bounds,bounds,&pick_model_to_view)) { /* * Invoke callback */ r = callback(ap, this_model, this_material, &m_to_v, bounds, arg); if(r) break; } /* FALL THROUGH */ default: BR_FOR_SIMPLELIST(&ap->children,a) { r = ActorPick3D(a,this_model,this_material,bounds,callback,arg); if(r) break; } break; case BR_ACTOR_BOUNDS: case BR_ACTOR_BOUNDS_CORRECT: /* * Only recurse into bounds if pick_bounds intersects */ ASSERT(ap->type_data != NULL); if(PickBoundsTestBox(ap->type_data,bounds,&pick_model_to_view)) { BR_FOR_SIMPLELIST(&ap->children,a) { r = ActorPick3D(a,this_model,this_material,bounds,callback,arg); if(r) break; } } break; } /* * Restore transform */ pick_model_to_view = m_to_v; return r; } /* * Find any actors whose bounding boxes intersect a 3D * pick box */ int BR_PUBLIC_ENTRY BrScenePick3D( br_actor *world, br_actor *actor, br_bounds *bounds, br_pick3d_cbfn *callback, void *arg ) { br_matrix34 mat; br_int_32 t; UASSERT(world != NULL); UASSERT(actor != NULL); UASSERT(bounds != NULL); UASSERT(callback != NULL); /* * Work out transform from world to base actor */ ActorToRootTyped(actor, world, &mat, &t); if(BrTransformTypeIsLP(t)) BrMatrix34LPInverse(&pick_model_to_view,&mat); else BrMatrix34Inverse(&pick_model_to_view,&mat); /* * Traverse the world */ return ActorPick3D(world, v1db.default_model, v1db.default_material, bounds, callback,arg); } /* * Intersect a models's faces with a ray segment * * XXX Possible options - * * All faces * Nearest face * Nearest vertex of hit face * U,V parameters of hit point * */ int BR_PUBLIC_ENTRY BrModelPick2D( br_model *model, br_material *material, br_vector3 *ray_pos, br_vector3 *ray_dir, br_scalar t_near, br_scalar t_far, br_modelpick2d_cbfn *callback, void *arg) { br_face *fp; int f,axis_m,axis_0,axis_1; br_scalar t,d,n; br_vector3 p; br_scalar u0,u1,u2,v0,v1,v2; br_scalar alpha,beta; br_vector2 map; int v,e,r; br_material *this_material; t_near -= BR_SCALAR(0.001); t_far += BR_SCALAR(0.001); UASSERT_MESSAGE("BrModelPick2D NULL pointer to model", model != NULL); UASSERT_MESSAGE("BrModelPick2D NULL pointer to model's default material", material != NULL); UASSERT_MESSAGE("BrModelPick2D NULL pointer to a 3D vector", ray_pos != NULL); UASSERT_MESSAGE("BrModelPick2D NULL pointer to a 3D vector", ray_dir != NULL); UASSERT_MESSAGE("BrModelPick2D NULL pointer to call-back function", callback != NULL); UASSERT(t_near < t_far); /* * For each face in model... */ for(f = 0, fp = model->faces; f < model->nfaces; f++, fp++) { /* * Work out face's material */ this_material = fp->material?fp->material:material; /* * Intersect ray with plane of polygon */ n = fp->d - BrFVector3Dot(&fp->n,ray_pos); d = BrFVector3Dot(&fp->n,ray_dir); #if 1 if(d > S0) { /* * If d is +ve, ray goes in same dirn as normal - ignore face if single sided */ if(!(this_material->flags & (BR_MATF_TWO_SIDED | BR_MATF_ALWAYS_VISIBLE))) continue; if(BR_ABS(n) < d) t = BR_DIV(n,d); else continue; } else if(d < S0) { if(BR_ABS(n) < -d) t = BR_DIV(n,d); else continue; } else /* d == 0.0 */ continue; #else if(BR_ABS(d) < 2*BR_SCALAR_EPSILON) continue; t = BR_DIV(n,d); #endif /* * Ignore face if intersection is outside ray segment */ if(t < t_near || t > t_far) continue; /* * Work out intersection point */ BrVector3Scale(&p,ray_dir,t); BrVector3Add(&p,&p,ray_pos); /* * Work out dominant axis of normal */ axis_m = 0; if(BR_ABS(fp->n.v[1]) > BR_ABS(fp->n.v[0])) axis_m = 1; if(BR_ABS(fp->n.v[2]) > BR_ABS(fp->n.v[axis_m])) axis_m = 2; switch(axis_m) { case 0: axis_0 = 1; axis_1 = 2; break; case 1: axis_0 = 0; axis_1 = 2; break; case 2: axis_0 = 0; axis_1 = 1; break; } /* * Work out point in polygon (Gems I, pg 392) */ u0 = model->vertices[fp->vertices[0]].p.v[axis_0]; v0 = model->vertices[fp->vertices[0]].p.v[axis_1]; u1 = model->vertices[fp->vertices[1]].p.v[axis_0] - u0; v1 = model->vertices[fp->vertices[1]].p.v[axis_1] - v0; u2 = model->vertices[fp->vertices[2]].p.v[axis_0] - u0; v2 = model->vertices[fp->vertices[2]].p.v[axis_1] - v0; u0 = p.v[axis_0] - u0; v0 = p.v[axis_1] - v0; if(BR_ABS(u1) <= (2*BR_SCALAR_EPSILON)) { if(BR_ABS(u0) > BR_ABS(u2)) continue; if(u2 == S0) continue; beta = BR_DIV(u0,u2); UASSERT_MESSAGE("BrModelPick2D divide by zero error", v1 != 0); if( beta >= S0 && beta <= S1) alpha = BR_DIV(BR_SUB(v0,BR_MUL(beta,v2)),v1); else continue; } else { n = BR_MAC2(v0,u1,-u0,v1); d = BR_MAC2(v2,u1,-u2,v1); if(BR_ABS(n) > BR_ABS(d)) continue; if(d == S0) continue; beta = BR_DIV(n,d); UASSERT_MESSAGE("BrModelPick2D divide by zero error", u1 != 0); if( beta >= S0 && beta <= S1) alpha = BR_DIV(BR_SUB(u0,BR_MUL(beta,u2)),u1); else continue; } if(alpha < S0 || (alpha+beta) > S1) continue; /* * Point is in polygon */ /* * Interpolate u,v */ BrVector2Scale(&map,&model->vertices[fp->vertices[1]].map,alpha); BrVector2AccumulateScale(&map,&model->vertices[fp->vertices[2]].map,beta); BrVector2AccumulateScale(&map,&model->vertices[fp->vertices[0]].map,(S1-(alpha+beta))); /* * Find `nearest' vertex and edge - partition triangle * along the bisectors of each corner */ v = 0; e = 1; if(alpha > beta) { /* * Edge 2 vs. Edge 1 */ if(alpha < (S1 - BR_CONST_MUL(beta,2))) e = 2; /* * Vertex 2 vs. Edge 0 */ if(alpha > (BR_SCALAR(0.5) - BR_CONST_DIV(beta,2))) v = 2; } else { /* * Edge 0 vs. Edge 1 */ if(alpha < (BR_SCALAR(0.5) - BR_CONST_DIV(beta,2))) e = 0; /* * Vertex 1 vs. Edge 0 */ if(alpha > (S1 - BR_CONST_MUL(beta,2))) v = 1; } /* * Invoke callback */ r = callback(model, this_material, ray_pos, ray_dir, t, f, e, v, &p, &map, arg); if(r) return r; } return 0; }