Text archives Help
- From: boulos@sci.utah.edu
- To: manta@sci.utah.edu
- Subject: [MANTA] r1235 - trunk/Model/Groups
- Date: Sun, 12 Nov 2006 05:45:12 -0700 (MST)
Author: boulos
Date: Sun Nov 12 05:45:11 2006
New Revision: 1235
Modified:
trunk/Model/Groups/DynBVH.cc
Log:
Rewriting firstIntersect in MANTA_SSE begin/end
style. Ensuring that box test has cmple instead
of cmplt
Modified: trunk/Model/Groups/DynBVH.cc
==============================================================================
--- trunk/Model/Groups/DynBVH.cc (original)
+++ trunk/Model/Groups/DynBVH.cc Sun Nov 12 05:45:11 2006
@@ -15,7 +15,6 @@
rays.computeInverseDirections();
rays.computeSigns();
-
// compute IntervalArithmetic Data
IAData ia_data;
for (int axis = 0; axis < 3; axis++ )
@@ -36,17 +35,7 @@
const Real new_rcp = rays.getInverseDirection(ray, axis);
const Real new_org = rays.getOrigin(ray,axis);
const Real new_org_rcp = new_org * new_rcp;
-#define USE_STDMINMAX 0
-#if USE_STDMINMAX
- ia_data.min_rcp[axis] = std::min(ia_data.min_rcp[axis], new_rcp);
- ia_data.max_rcp[axis] = std::max(ia_data.max_rcp[axis], new_rcp);
-
- ia_data.min_org[axis] = std::min(ia_data.min_org[axis], new_org);
- ia_data.max_org[axis] = std::max(ia_data.max_org[axis], new_org);
- ia_data.min_org_rcp[axis] = std::min(ia_data.min_org_rcp[axis],
new_org_rcp);
- ia_data.max_org_rcp[axis] = std::max(ia_data.max_org_rcp[axis],
new_org_rcp);
-#else
ia_data.min_rcp[axis] = (ia_data.min_rcp[axis] < new_rcp) ?
ia_data.min_rcp[axis] : new_rcp;
ia_data.max_rcp[axis] = (ia_data.max_rcp[axis] < new_rcp) ?
new_rcp : ia_data.max_rcp[axis];
@@ -57,7 +46,7 @@
ia_data.min_org_rcp[axis] : new_org_rcp;
ia_data.max_org_rcp[axis] = (ia_data.max_org_rcp[axis] <
new_org_rcp) ?
new_org_rcp : ia_data.max_org_rcp[axis];
-#endif
+
}
}
@@ -158,24 +147,10 @@
RayPacket subpacket(rays, firstActive, rays.end());
// recurse
-#if 0 // for erw6 this actually hurts slightly
- int sign_bit = subpacket.getSign(subpacket.begin(),
static_cast<int>(node.axis));
- // in manta sign_bit is 1 if direction is < 0
- // if signbit is < 0, want left child first
- intersectNode(node.child+(1-sign_bit), context, subpacket,
ia_data);
- intersectNode(node.child+sign_bit, context, subpacket, ia_data);
-#else
-#if 1
int front_son = subpacket.getDirection(subpacket.begin(),
static_cast<int>(node.axis)) > 0 ? 0 : 1;
intersectNode(node.child+front_son, context, subpacket, ia_data);
intersectNode(node.child+1-front_son, context, subpacket,
ia_data);
-#else
- intersectNode(node.child+0, context, subpacket, ia_data);
- intersectNode(node.child+1, context, subpacket, ia_data);
-#endif
-#endif
-
}
}
}
@@ -183,250 +158,213 @@
// return the first index (between [rays.begin(),rays.end()]) which hits the
box
int DynBVH::firstIntersects(const BBox& box, const RayPacket& rays, const
IAData& ia_data, float* out_tmin) const
{
-#define DYNBVH_NEW_SSE MANTA_SSE
-
-#if DYNBVH_NEW_SSE
- int sse_begin = (rays.begin() + 3)&(~3);
- int sse_end = rays.end() & (~3);
- const RayPacketData* data = rays.data;
-#endif
- // we always want to do the first ray and IA in C not SIMD
- for (int ray = rays.begin(); ray < rays.end(); ray++ )
- {
+#ifdef MANTA_SSE
+ int b = (rays.rayBegin + 3) & (~3);
+ int e = rays.rayEnd & (~3);
+ if (b==e) {
+ for (int i = rays.begin(); i < rays.end(); i++) {
float tmin = 1e-5f;
- float tmax = rays.getMinT(ray);
+ float tmax = rays.getMinT(i);
for (int c = 0; c < 3; c++) {
- float t0 = (box[0][c] - rays.getOrigin(ray,c)) *
rays.getInverseDirection(ray,c);
- float t1 = (box[1][c] - rays.getOrigin(ray,c)) *
rays.getInverseDirection(ray,c);
+ float t0 = (box[0][c] - rays.getOrigin(i,c)) *
rays.getInverseDirection(i,c);
+ float t1 = (box[1][c] - rays.getOrigin(i,c)) *
rays.getInverseDirection(i,c);
float near = (t0 < t1) ? t0 : t1;
float far = (t0 < t1) ? t1 : t0;
- //tmin = (tmin < near) ? near : tmin; // max of tmin, near
- //tmax = (far < tmax) ? far : tmax; // min of tmax, far
- tmin = (near < tmin) ? tmin : near; // non nan-safe max of tmin,
near
- tmax = (tmax < far) ? tmax : far; // non nan-safe min of tmax,
far
+ tmin = (tmin < near) ? near : tmin; // max of tmin, near
+ tmax = (far < tmax) ? far : tmax; // min of tmax, far
}
if (tmin <= tmax) { // valid intersection
*out_tmin = tmin;
- return ray;
+ return i;
}
+ }
+ } else {
+ int i = rays.rayBegin;
+ for(;i<b;i++) {
+ float tmin = 1e-5f;
+ float tmax = rays.getMinT(i);
-#if 1 // enable/disable frustum test (goes from 15 fps to 21 fps)
- if (ray == rays.begin())
- {
- // try a frustum miss
- float tmin_frustum = 1e-5;
- float tmax_frustum = FLT_MAX;
-
- for (int axis = 0; axis < 3; axis++)
- {
-#if 1 // (box - orgIA) * rcpIA
- // the subtraction is really (boxIA + -orgIA)
- // or boxIA + [-max_org, -min_org]
- // [box_min, box_max] + [-max_org, -min_org]
- float a = box[0][axis]-ia_data.max_org[axis];
- float b = box[1][axis]-ia_data.min_org[axis];
-
- // now for multiplication
- float ar0 = a * ia_data.min_rcp[axis];
- float ar1 = a * ia_data.max_rcp[axis];
- float br0 = b * ia_data.min_rcp[axis];
- float br1 = b * ia_data.max_rcp[axis];
-
- // [a,b] is valid intersection interval so a is min
- // and b is max t-value
-
- //a = std::min(ar0, std::min(ar1, std::min(br0, br1)));
- a = (br0 < br1) ? br0 : br1;
- a = (a < ar1) ? a : ar1;
- a = (a < ar0) ? a : ar0;
-
- //b = std::max(ar0, std::max(ar1, std::max(br0, br1)));
- b = (br0 < br1) ? br1 : br0;
- b = (b < ar1) ? ar1 : b;
- b = (b < ar0) ? ar0 : b;
-#else //box * rcpIA - org_rcpIA
- float a = box[0][axis]*ia_data.min_rcp[axis];
- float b = box[0][axis]*ia_data.max_rcp[axis];
- float first_min = (a < b) ? a : b;
- float first_max = (a < b) ? b : a;
-
- float c = box[1][axis]*ia_data.min_rcp[axis];
- float d = box[1][axis]*ia_data.max_rcp[axis];
-
- float second_min = (c < d) ? c : d;
- float second_max = (c < d) ? d : c;
-
- a = (first_min < second_min) ? first_min : second_min;
- b = (first_max > second_max) ? first_max : second_max;
-
- a -= ia_data.max_org_rcp[axis];
- b -= ia_data.min_org_rcp[axis];
-#endif
- tmin_frustum = (tmin_frustum < a) ? a : tmin_frustum;
- tmax_frustum = (tmax_frustum > b) ? b : tmax_frustum;
- }
+ for (int c = 0; c < 3; c++) {
+ float t0 = (box[0][c] - rays.getOrigin(i,c)) *
rays.getInverseDirection(i,c);
+ float t1 = (box[1][c] - rays.getOrigin(i,c)) *
rays.getInverseDirection(i,c);
- if (tmin_frustum > tmax_frustum) // frustum exit
- {
- return rays.end();
- }
+ float near = (t0 < t1) ? t0 : t1;
+ float far = (t0 < t1) ? t1 : t0;
+ tmin = (tmin < near) ? near : tmin; // max of tmin, near
+ tmax = (far < tmax) ? far : tmax; // min of tmax, far
+ }
+ if (tmin <= tmax) { // valid intersection
+ *out_tmin = tmin;
+ return i;
}
-#endif // do frustum test
-#if (DYNBVH_NEW_SSE && 1)
- // if we can use simd now, jump out (redoes some work for SIMD
aligned rays)
- if ( ray == sse_begin || ray == sse_begin - 1 )
- break;
-#endif
- }
-
-#if DYNBVH_NEW_SSE
- // process simds now
-
- // TODO(boulos): replace operator overloads with direct access
-#if 1
- int pack_begin = sse_begin >> 2;
- int pack_end = sse_end >> 2;
- __m128 box_x0 = _mm_set1_ps(box[0][0]);
- __m128 box_x1 = _mm_set1_ps(box[1][0]);
-
- __m128 box_y0 = _mm_set1_ps(box[0][1]);
- __m128 box_y1 = _mm_set1_ps(box[1][1]);
- __m128 box_z0 = _mm_set1_ps(box[0][2]);
- __m128 box_z1 = _mm_set1_ps(box[1][2]);
+ if (i == rays.rayBegin) {
+ // try a frustum miss
+ float tmin_frustum = 1e-5;
+ float tmax_frustum = FLT_MAX;
+
+ for (int axis = 0; axis < 3; axis++) {
+ // the subtraction is really (boxIA + -orgIA)
+ // or boxIA + [-max_org, -min_org]
+ // [box_min, box_max] + [-max_org, -min_org]
+ float a = box[0][axis]-ia_data.max_org[axis];
+ float b = box[1][axis]-ia_data.min_org[axis];
+
+ // now for multiplication
+ float ar0 = a * ia_data.min_rcp[axis];
+ float ar1 = a * ia_data.max_rcp[axis];
+ float br0 = b * ia_data.min_rcp[axis];
+ float br1 = b * ia_data.max_rcp[axis];
+
+ // [a,b] is valid intersection interval so a is min
+ // and b is max t-value
+
+ //a = std::min(ar0, std::min(ar1, std::min(br0, br1)));
+ a = (br0 < br1) ? br0 : br1;
+ a = (a < ar1) ? a : ar1;
+ a = (a < ar0) ? a : ar0;
+
+ //b = std::max(ar0, std::max(ar1, std::max(br0, br1)));
+ b = (br0 < br1) ? br1 : br0;
+ b = (b < ar1) ? ar1 : b;
+ b = (b < ar0) ? ar0 : b;
+
+ tmin_frustum = (tmin_frustum < a) ? a : tmin_frustum;
+ tmax_frustum = (tmax_frustum > b) ? b : tmax_frustum;
+ }
+
+ // frustum exit
+ if (tmin_frustum > tmax_frustum) {
+ return rays.end();
+ }
+ }
+ }
- for (int pack = pack_begin, ray = sse_begin; pack < pack_end; ++pack,
ray += 4)
- {
- __m128 x0 = _mm_mul_ps(_mm_sub_ps(box_x0,
_mm_load_ps(&data->origin[0][ray])),
- _mm_load_ps(&data->inverseDirection[0][ray]));
- __m128 x1 = _mm_mul_ps(_mm_sub_ps(box_x1,
_mm_load_ps(&data->origin[0][ray])),
- _mm_load_ps(&data->inverseDirection[0][ray]));
+ RayPacketData* data = rays.data;
+ __m128 box_x0 = _mm_set1_ps(box[0][0]);
+ __m128 box_x1 = _mm_set1_ps(box[1][0]);
+
+ __m128 box_y0 = _mm_set1_ps(box[0][1]);
+ __m128 box_y1 = _mm_set1_ps(box[1][1]);
+
+ __m128 box_z0 = _mm_set1_ps(box[0][2]);
+ __m128 box_z1 = _mm_set1_ps(box[1][2]);
+
+ for(;i<e;i+=4){
+ __m128 x0 = _mm_mul_ps(_mm_sub_ps(box_x0,
_mm_load_ps(&data->origin[0][i])),
+ _mm_load_ps(&data->inverseDirection[0][i]));
+ __m128 x1 = _mm_mul_ps(_mm_sub_ps(box_x1,
_mm_load_ps(&data->origin[0][i])),
+ _mm_load_ps(&data->inverseDirection[0][i]));
__m128 xmin = _mm_min_ps(x0,x1);
__m128 xmax = _mm_max_ps(x0,x1);
- __m128 y0 = _mm_mul_ps(_mm_sub_ps(box_y0,
_mm_load_ps(&data->origin[1][ray])),
- _mm_load_ps(&data->inverseDirection[1][ray]));
- __m128 y1 = _mm_mul_ps(_mm_sub_ps(box_y1,
_mm_load_ps(&data->origin[1][ray])),
- _mm_load_ps(&data->inverseDirection[1][ray]));
+ __m128 y0 = _mm_mul_ps(_mm_sub_ps(box_y0,
_mm_load_ps(&data->origin[1][i])),
+ _mm_load_ps(&data->inverseDirection[1][i]));
+ __m128 y1 = _mm_mul_ps(_mm_sub_ps(box_y1,
_mm_load_ps(&data->origin[1][i])),
+ _mm_load_ps(&data->inverseDirection[1][i]));
__m128 ymin = _mm_min_ps(y0,y1);
__m128 ymax = _mm_max_ps(y0,y1);
- __m128 z0 = _mm_mul_ps(_mm_sub_ps(box_z0,
_mm_load_ps(&data->origin[2][ray])),
- _mm_load_ps(&data->inverseDirection[2][ray]));
- __m128 z1 = _mm_mul_ps(_mm_sub_ps(box_z1,
_mm_load_ps(&data->origin[2][ray])),
- _mm_load_ps(&data->inverseDirection[2][ray]));
+ __m128 z0 = _mm_mul_ps(_mm_sub_ps(box_z0,
_mm_load_ps(&data->origin[2][i])),
+ _mm_load_ps(&data->inverseDirection[2][i]));
+ __m128 z1 = _mm_mul_ps(_mm_sub_ps(box_z1,
_mm_load_ps(&data->origin[2][i])),
+ _mm_load_ps(&data->inverseDirection[2][i]));
__m128 zmin = _mm_min_ps(z0,z1);
__m128 zmax = _mm_max_ps(z0,z1);
__m128 maximum_minimum =
_mm_max_ps(xmin,_mm_max_ps(ymin,_mm_max_ps(zmin, _mm_set1_ps(1e-5f))));
- __m128 minimum_maximum =
_mm_min_ps(xmax,_mm_min_ps(ymax,_mm_min_ps(zmax,_mm_load_ps(&data->minT[ray]))));
- __m128 valid_intersect =
_mm_cmplt_ps(maximum_minimum,minimum_maximum);
+ __m128 minimum_maximum =
_mm_min_ps(xmax,_mm_min_ps(ymax,_mm_min_ps(zmax,_mm_load_ps(&data->minT[i]))));
+ __m128 valid_intersect =
_mm_cmple_ps(maximum_minimum,minimum_maximum);
if (_mm_movemask_ps(valid_intersect) != 0x0)
- return ray;
- }
-#else // different more register efficient version
- // NOTE(boulos): This code had a severe bug that may have made it look
more efficient than it is...
- // two regs for box
- __m128 box0 = _mm_set_ps(0.f, box[0][2], box[0][1], box[0][0]);
- __m128 box1 = _mm_set_ps(0.f, box[1][2], box[1][1], box[1][0]);
- for (int ray = sse_begin; ray < sse_end; ray += 4) {
- // two regs for interval tracking
- __m128 tmin = _mm_set1_ps(1e-5f);
- __m128 tmax = _mm_load_ps(&data->minT[ray]);
- // current is 4
- // 1 for inv dir, 1 for ray org, 1 for shuffle output
-
- __m128 t0 = _mm_mul_ps( _mm_sub_ps( _mm_shuffle_ps(box0, box0,
_MM_SHUFFLE(0,0,0,0)),
-
_mm_load_ps(&data->origin[0][ray])),
- _mm_load_ps(&data->inverseDirection[0][ray])
);
- __m128 t1 = _mm_mul_ps( _mm_sub_ps( _mm_shuffle_ps(box1, box1,
_MM_SHUFFLE(0,0,0,0)),
-
_mm_load_ps(&data->origin[0][ray])),
- _mm_load_ps(&data->inverseDirection[0][ray])
);
- tmin = _mm_max_ps(tmin, _mm_min_ps(t0, t1));
- tmax = _mm_min_ps(tmax, _mm_max_ps(t0, t1));
-
- t0 = _mm_mul_ps( _mm_sub_ps( _mm_shuffle_ps(box0, box0,
_MM_SHUFFLE(1,1,1,1)),
- _mm_load_ps(&data->origin[1][ray])),
- _mm_load_ps(&data->inverseDirection[1][ray]) );
- t1 = _mm_mul_ps( _mm_sub_ps( _mm_shuffle_ps(box1, box1,
_MM_SHUFFLE(1,1,1,1)),
- _mm_load_ps(&data->origin[1][ray])),
- _mm_load_ps(&data->inverseDirection[1][ray]) );
-
- tmin = _mm_max_ps(tmin, _mm_min_ps(t0, t1));
- tmax = _mm_min_ps(tmax, _mm_max_ps(t0, t1));
-
- t0 = _mm_mul_ps( _mm_sub_ps( _mm_shuffle_ps(box0, box0,
_MM_SHUFFLE(2,2,2,2)),
- _mm_load_ps(&data->origin[2][ray])),
- _mm_load_ps(&data->inverseDirection[2][ray]) );
- t1 = _mm_mul_ps( _mm_sub_ps( _mm_shuffle_ps(box1, box1,
_MM_SHUFFLE(2,2,2,2)),
- _mm_load_ps(&data->origin[2][ray])),
- _mm_load_ps(&data->inverseDirection[2][ray]) );
-
- tmin = _mm_max_ps(tmin, _mm_min_ps(t0, t1));
- tmax = _mm_min_ps(tmax, _mm_max_ps(t0, t1));
-
- __m128 valid_intersect = _mm_cmple_ps(tmin, tmax);
- if (_mm_movemask_ps(valid_intersect) != 0x0) {
- sse_t min_with_inf = _mm_or_ps(_mm_and_ps(valid_intersect, tmin),
- _mm_andnot_ps(valid_intersect,
_mm_set1_ps(1e150f)));
- // a = (t0, t0, t1, t1)
- sse_t a = _mm_unpacklo_ps(min_with_inf,min_with_inf);
- // b = (t2, t2, t3, t3)
- sse_t b = _mm_unpackhi_ps(min_with_inf,min_with_inf);
- // c = (min(t0,t2), min(t0, t2), min(t1, t3), min(t1, t3))
- sse_t c = _mm_min_ps(a, b);
- // The movehl will move the high 2 values to the low 2 values.
- // This will allow us to compare min(t0,t2) with min(t1, t3).
- sse_t result = _mm_min_ss(c, _mm_movehl_ps(c, c));
- // Return the first value.
- *out_tmin = *((float*)&result);
- return ray;
+ return i;
+ }
+
+ for(;i<rays.rayEnd;i++) {
+ float tmin = 1e-5f;
+ float tmax = rays.getMinT(i);
+
+ for (int c = 0; c < 3; c++) {
+ float t0 = (box[0][c] - rays.getOrigin(i,c)) *
rays.getInverseDirection(i,c);
+ float t1 = (box[1][c] - rays.getOrigin(i,c)) *
rays.getInverseDirection(i,c);
+
+ float near = (t0 < t1) ? t0 : t1;
+ float far = (t0 < t1) ? t1 : t0;
+ tmin = (tmin < near) ? near : tmin; // max of tmin, near
+ tmax = (far < tmax) ? far : tmax; // min of tmax, far
+ }
+ if (tmin <= tmax) { // valid intersection
+ *out_tmin = tmin;
+ return i;
}
+ }
}
-#endif
- // get remaining rays
- for (int ray = sse_begin+1; ray < rays.end(); ++ray) {
- float maximum_minimum = 1e-5;
- float minimum_maximum = rays.getMinT(ray);
-
- float x_minimum = (box[rays.getSign(ray,0)][0] -
rays.getOrigin(ray,0)) * rays.getInverseDirection(ray,0);
- float x_maximum = (box[1-rays.getSign(ray,0)][0] -
rays.getOrigin(ray,0)) * rays.getInverseDirection(ray,0);
-
- float y_minimum = (box[rays.getSign(ray,1)][1] -
rays.getOrigin(ray,1)) * rays.getInverseDirection(ray,1);
- float y_maximum = (box[1-rays.getSign(ray,1)][1] -
rays.getOrigin(ray,1)) * rays.getInverseDirection(ray,1);
-
- float z_minimum = (box[rays.getSign(ray,2)][2] -
rays.getOrigin(ray,2)) * rays.getInverseDirection(ray,2);
- float z_maximum = (box[1-rays.getSign(ray,2)][2] -
rays.getOrigin(ray,2)) * rays.getInverseDirection(ray,2);
-
- if ( minimum_maximum < x_minimum ||
- maximum_minimum > x_maximum )
- continue;
- if ( minimum_maximum > x_maximum )
- minimum_maximum = x_maximum;
- if ( maximum_minimum < x_minimum )
- maximum_minimum = x_minimum;
- if ( minimum_maximum < y_minimum ||
- maximum_minimum > y_maximum )
- continue;
- if ( minimum_maximum > y_maximum )
- minimum_maximum = y_maximum;
- if ( maximum_minimum < y_minimum )
- maximum_minimum = y_minimum;
-
- if ( minimum_maximum >= z_minimum &&
- maximum_minimum <= z_maximum )
- {
- *out_tmin = (maximum_minimum < z_minimum) ? maximum_minimum :
z_minimum;
- return ray; // found a hit
+ return rays.end();
+#else
+ for (int i = rays.begin(); i < rays.end(); i++) {
+ float tmin = 1e-5f;
+ float tmax = rays.getMinT(ray);
+
+ for (int c = 0; c < 3; c++) {
+ float t0 = (box[0][c] - rays.getOrigin(i,c)) *
rays.getInverseDirection(i,c);
+ float t1 = (box[1][c] - rays.getOrigin(i,c)) *
rays.getInverseDirection(i,c);
+
+ float near = (t0 < t1) ? t0 : t1;
+ float far = (t0 < t1) ? t1 : t0;
+ tmin = (tmin < near) ? near : tmin; // max of tmin, near
+ tmax = (far < tmax) ? far : tmax; // min of tmax, far
+ }
+ if (tmin <= tmax) { // valid intersection
+ *out_tmin = tmin;
+ return ray;
+ }
+
+ if (i == rays.begin()) {
+ // try a frustum miss
+ float tmin_frustum = 1e-5;
+ float tmax_frustum = FLT_MAX;
+
+ for (int axis = 0; axis < 3; axis++) {
+ // the subtraction is really (boxIA + -orgIA)
+ // or boxIA + [-max_org, -min_org]
+ // [box_min, box_max] + [-max_org, -min_org]
+ float a = box[0][axis]-ia_data.max_org[axis];
+ float b = box[1][axis]-ia_data.min_org[axis];
+
+ // now for multiplication
+ float ar0 = a * ia_data.min_rcp[axis];
+ float ar1 = a * ia_data.max_rcp[axis];
+ float br0 = b * ia_data.min_rcp[axis];
+ float br1 = b * ia_data.max_rcp[axis];
+
+ // [a,b] is valid intersection interval so a is min
+ // and b is max t-value
+
+ //a = std::min(ar0, std::min(ar1, std::min(br0, br1)));
+ a = (br0 < br1) ? br0 : br1;
+ a = (a < ar1) ? a : ar1;
+ a = (a < ar0) ? a : ar0;
+
+ //b = std::max(ar0, std::max(ar1, std::max(br0, br1)));
+ b = (br0 < br1) ? br1 : br0;
+ b = (b < ar1) ? ar1 : b;
+ b = (b < ar0) ? ar0 : b;
+
+ tmin_frustum = (tmin_frustum < a) ? a : tmin_frustum;
+ tmax_frustum = (tmax_frustum > b) ? b : tmax_frustum;
+ }
+
+ // frustum exit
+ if (tmin_frustum > tmax_frustum) {
+ return rays.end();
}
+ }
}
#endif
- return rays.end();
}
// return the last index which hits the box
- [MANTA] r1235 - trunk/Model/Groups, boulos, 11/12/2006
Archive powered by MHonArc 2.6.16.