Woo's method

Started by
26 comments, last by Endar 18 years, 5 months ago
Quote:Original post by haegarr
No, I'm not convinced that this is better than to use the infinite ray.


Not in a situation, where you usually have a very long segment anyway. For picking it might not be useful (unless the world is really huge compared to the view distance and absolutely no other method to limit the number of box tests is used). For collisions maybe, as the movement is usually very small per frame. But as I said, I would test it with a profiler and real world data sets to see if it hurts more than it helps.

Though against static objects I would think about calculating once when I would hit some object and only update when my direction or velocity changes. Else all the repeated small step tests are a bit like what I will just call the "are we there yet"-syndrome.

Well, everything at its own time. Back to quaternions and trying to build a "better" binary md5 bastard format.
f@dzhttp://festini.device-zero.de
Advertisement
Quote:Original post by haegarr
You may take into account to post the belonging code for inspection by another pair of eyes.

......

What components do you mean? Inverting the ray should not change anything, since the hit point will be the same.


1. Here's my code for inspection.
2. The components I was talking about were the components of the vector that represent the intersection point.

#define PLANE_EPSILON 0.0001f/** * A class to represent a plane. */class CPlane{public:	util::CVector3d m_normal;	///< The normal of the plane	float m_distance;		///< The plane-shift constant, the distance of the plane							///< from the origin. Is calculated by the dot product				///< between the normal and a point on the plane.										/**	 * Default constructor	 */	CPlane()		:	m_normal(0,0,0), m_distance(0)	{	}	/**	 * Constructor	 * \param n A const reference to a CVector3d object that is the normal of the plane	 * \param s The plane-shift constant (the dot product with the normal and a point on the plane	 */	CPlane( const util::CVector3d& n, float s )		:	m_normal(n), m_distance(s)	{		m_normal.normalize();	// make sure that the vector is normalized	}	/**	 * Constructor	 * \param n A const reference to a CVector3d object that is the normal of the plane	 * \param p A point on the plane	 */	CPlane( const util::CVector3d& n, const util::CVector3d& p )		:	m_normal(n)	{		m_normal.normalize();				// make sure that the vector is normalized		m_distance = n.dotProduct(p);	// calc the plane-shift constant	}	/**	 * Assignment operator	 * \param o A const reference to a CPlane object.	 * \return A reference to 'this' CPlane object.	 */	CPlane& operator=(const CPlane& p)	{		if( this != &p ){			m_normal = p.m_normal;			m_distance = p.m_distance;		}		return *this;	}	/**	 * Calculate if a point is on the plane.	 * \param p A const reference to a CVector3d object that represents a point	 * \return A boolean value that represents whether the passed point is on the plane	 */	bool pointOnPlane(const util::CVector3d& p) const	{		/*			Because the plane shift constant is the dot product between the normal of the plane,			to find out if the point is on the plane all we need to do is calc the dot product			of the normal and the passed point and see if that equals the shift constant we have		 */		float dot = m_normal.dotProduct(p);		return ( dot >= m_distance - PLANE_EPSILON  ||  dot <= m_distance + PLANE_EPSILON );	}	/**	 * Find if the passed ray intersects with this plane.	 * \param o The origin point of the vector.	 * \param d The direction of the vector (normalized)	 * \return 'true' if the plane and ray intersect, else 'false'	 */	bool intersect(const util::CVector3d& o, const util::CVector3d& d) const	{		util::CVector3d dir(d);				float len = dir.getLengthSquared();		// who wants to do an unecessary sqrt?		if( !(len >= 1.0f - PLANE_EPSILON  ||  len <= 1.0f + PLANE_EPSILON) )			dir.normalize();					// make sure the vector is normalized		// if direction of the ray is perpendicular to the plane normal, then the plane		// and the direction are parallel, so there won't even be an intersection,		// or, the ray is part of the plane (completely on it)		float dot = m_normal.dotProduct(dir);		if( !(dot >= 0.0f - PLANE_EPSILON  ||  dot <= 0.0f + PLANE_EPSILON) )			return false;		// no intersection		return true;		// there is an intersection	}	/**	 * Return the point of intersection between the ray and plane.	 * \param o The origin point of the vector.	 * \param d The direction of the vector (normalized)	 * \param intersection A reference to a vector in which the	 * intersection point, if there is one, will be stored.	 * \return 'true' if there is an intersection point, else 'false'	 */	bool intersectionPoint(const util::CVector3d& o, const util::CVector3d& d, util::CVector3d& intersection) const	{		float t;		/* calculate the intersection point */		util::CVector3d dir(d);				float len = dir.getLengthSquared();		// who wants to do an unecessary sqrt?		if( !(len >= 1.0f - PLANE_EPSILON  ||  len <= 1.0f + PLANE_EPSILON) )			dir.normalize();					// make sure the vector is normalized		// get the distance from the origin point of the ray along direction 'd' to 'this' plane		if( !distance(o,dir,t) )			return false;		// no intersection point		// calcuate the intersection point by starting with the origin of the ray and 		// scaling the ray's direction vector by the distance between the start point and		// the plane.		intersection = o + (dir * t);		return true;	}	/**	 * Returns 'true' if there is an intersection between the ray and 'this' plane.	 * \param o The origin point of the vector.	 * \param d The direction of the vector (normalized)	 * \param dist The distance from the origin point to 'this' plane	 * \return 'true' if the distance is calculated, else 'false	 */	bool distance(const util::CVector3d& o, const util::CVector3d& d, float& dist) const	{		// check to see if there is an intersection point		if( !intersect(o,d) )			return false;		/* calculate the intersection point */		util::CVector3d dir(d);				float len = dir.getLengthSquared();		// who wants to do an unecessary sqrt?		if( !(len >= 1.0f - PLANE_EPSILON  ||  len <= 1.0f + PLANE_EPSILON) )			dir.normalize();					// make sure the vector is normalized		// http://www.siggraph.org/education/materials/HyperGraph/raytrace/rayplane_intersection.htm		// this finds the distance between the origin point and the plane in the direction		// of 'd'		dist = (-(m_normal.dotProduct(o) + m_distance)) / m_normal.dotProduct(dir);		return true;	}};


/** * Default constructor * NOTE:: min is the bottom left corner toward the negative z-axis * NOTE:: max is the top right corner toward the positive z-axis */aabbox::aabbox():	min(util::CVector3d(-1,-1,-1)), max(util::CVector3d(1,1,1)){}/** * Constructor * NOTE:: min is the bottom left corner toward the negative z-axis * NOTE:: max is the top right corner toward the positive z-axis * \param nmin The new min value of the AABB * \param nmax The new max value of the AABB */aabbox::aabbox(const util::CVector3d& nmin, const util::CVector3d& nmax ):	min(nmin), max(nmax){}/** * Copy constructor * NOTE:: min is the bottom left corner toward the negative z-axis * NOTE:: max is the top right corner toward the positive z-axis */aabbox::aabbox(const aabbox&a):	min(a.min), max(a.max){}/** * Overloaded assignment operator. * \param a A const reference to an aabbox object * \return A reference to 'this' aabbox object */aabbox& aabbox::operator=(const aabbox& a){	if( &a != this ){		min = a.min;		max = a.max;	}	return *this;}/** * Add a point to the inside of the AABB and increase * the AABB max and min points if necessary to ensure that * point <= max && point >= min */void aabbox::addPoint(const util::CVector3d& p){	if( p.x > max.x )	max.x = p.x;	if( p.y > max.y )	max.y = p.y;	if( p.z > max.z )	max.z = p.z;	if( p.x < min.x )	min.x = p.x;	if( p.y < min.y )	min.y = p.y;	if( p.z < min.z )	min.z = p.z;}/** * Check if a point is within the bounds of the aabbox. * \param p A const reference to a CVector3d object * \return 'true' if the point is within the AABB, else 'false' */bool aabbox::checkPoint(const util::CVector3d& p) const{	// if point 'p' is within the AABB bounds	if( p.x >= min.x  &&  p.x <= max.x )		if( p.y >= min.y  &&  p.y <= max.y )			if( p.z >= min.z  &&  p.z <= max.z )				return true;	return false;}/** * Reset the bounding box. * \param initValue a reference to a CVector3d object that is the new min and max. */void aabbox::reset(const util::CVector3d& initValue){	min = initValue;	max = initValue;}/** * Return if the aabbox and the passed parameter aabbox intersect. * \param b Another aabbox to test for intersection with 'this' aabbox * \return 'true' if the two boxes intersect, else 'false' */bool aabbox::intersection(const aabbox& b) const{	if( (this->min.x > b.max.x) || (b.min.x > this->max.x) )		return false;	if( (this->min.y > b.max.y) || (b.min.y > this->max.y) )		return false;	if( (this->min.z > b.max.z) || (b.min.z > this->max.z) )		return false;	return true;	// the boxes intersect}/** * Return true if there is an intersection between the passed ray * and 'this' aabbox. * This creates 6 planes, one for each side of the aabb and then uses * Woo's method to find if there is an intersection with the ray. * http://gamealgorithms.tar.hu/ch22lev1sec2.html * * \param o A const reference to a CVector3d object that is the origin point of the ray. * \param d A const reference to a CVector3d object that is the direction of the ray (normalized vector) * \param intersection A reference to a CVector3d object in which the intersection point will be stored * if there is a valid point. * \return 'true' if there is an intersection, else 'false' */bool aabbox::intersection(const util::CVector3d& o, const util::CVector3d& d, util::CVector3d& intersection) const{	util::CPlane sides[6];	// 6 planes making up each side of the aabb	util::CPlane can[3];	// the 3 candidate planes	util::CVector3d ntemp;	// temp vector for holding the aabb normals	// top of the box	ntemp = util::CVector3d(0,1,0);	sides[0] = util::CPlane( ntemp, ntemp.dotProduct(max) );	// front of the box	ntemp = util::CVector3d(0,0,1);	sides[1] = util::CPlane( ntemp, ntemp.dotProduct(max) );	// right of the box	ntemp = util::CVector3d(1,0,0);	sides[2] = util::CPlane( ntemp, ntemp.dotProduct(max) );	// bottom of the box	ntemp = util::CVector3d(0,-1,0);	sides[3] = util::CPlane( ntemp, ntemp.dotProduct(min) );	// left of the box	ntemp = util::CVector3d(-1,0,0);	sides[4] = util::CPlane( ntemp, ntemp.dotProduct(min) );	// back of the box	ntemp = util::CVector3d(0,0,-1);	sides[5] = util::CPlane( ntemp, ntemp.dotProduct(min) );	// select the three different planes	int count=0;		// the number of planes to test against	for(int i=0, j=0; (i < 6) && (j < 3); i++){		// If the dot product is less than 0, this indicates that		// the vectors are going in roughly opposite directions, which		// is what we want, because with woo's method, the back-facing faces		// are discarded (faces with normals in roughly the same direction		// as the ray).		// This also ignores planes if they are perpendicular to the ray.		if( d.dotProduct(sides.m_normal) < 0 ){			can[j] = sides;			j++;			count++;		}	}	// By this point we have at most 3 planes to test against.		// Woo's method consists of choosing the 3 best planes 	// (or however many we can get, if less than 3),	// and we find the intersection distances between the ray and	// the planes. The largest of these distances corresponds to a	// potential hit. If a potential hit is found, the intersection	// point is computed. If it is within the AABB, then it is a	// real hit.	float largest = 0.0f;	float dist = 0.0f;	int hit = 0;	for(i=0; i < count; i++){		// get the distance to this plane from the ray origin along the ray		if( can.distance(o,d,dist) ){			if( dist > largest ){				largest = dist;		// new largest				hit = i;			}		}	}	// By this point we should have a single plane which is the best potential	// hit, according to Woo's method. Now, we calculate the intersection point,	// and if it is within the aabbox, then we have an actual hit, and the coords	// of the hit.	util::CVector3d hit_point;	// get the intersection point between the ray and the specified plane	if( can[hit].intersectionPoint( o, d, hit_point ) ){		// if the intersection point is within the AABB		if( checkPoint( hit_point ) ){			intersection = hit_point;	// set the intersection point (a real hit)			return true;		}	}	return false;}


int main(){		util::aabbox box;	box.addPoint(util::CVector3d(-1,-1,-1) );	box.addPoint(util::CVector3d(2,1,1) );	// intersection right side, with ray. Intersection should be (2,0,0)	util::CVector3d o(3,0,0);	util::CVector3d d(-1,0,0);	util::CVector3d p;	if( box.intersection(o,d,p) ){		printf("\n Right Intersection found: %.3f %.3f %.3f\n", p.x, p.y, p.z);	}	else		printf("\n Right Nothing found \n");	// intersection left side, with ray. Intersection should be (-1,0.5,0)	o = util::CVector3d(-2,0.5f,0);	d = util::CVector3d(1,0,0);	if( box.intersection(o,d,p) ){		printf("\n Left Intersection found: %.3f %.3f %.3f\n", p.x, p.y, p.z);	}	else		printf("\n Left Nothing found \n");return 0;}


It prints the results:
 Right Nothing found Left Intersection found: 1.000 0.500 0.000
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
Here comes my first inspection results: class CPlane

IMHO several of your boolean conditions are malformed. Those of normalization may possibly play no role (dependent on the usage) but there are also essentials.
Look at the lines prepended by /* haegarr */ and compare them with your previous code.
        bool pointOnPlane(const util::CVector3d& p) const        {                /*                        Because the plane shift constant is the dot product between the normal of the plane,                        to find out if the point is on the plane all we need to do is calc the dot product                        of the normal and the passed point and see if that equals the shift constant we have                 */                float dot = m_normal.dotProduct(p);/* haegarr */   return ( dot >= m_distance - PLANE_EPSILON  &&  dot <= m_distance + PLANE_EPSILON );        }        bool intersect(const util::CVector3d& o, const util::CVector3d& d) const        {                util::CVector3d dir(d);                                float len = dir.getLengthSquared();             // who wants to do an unecessary sqrt?/* haegarr */   if( len < 1.0f - PLANE_EPSILON  ||  len > 1.0f + PLANE_EPSILON )                        dir.normalize();                                        // make sure the vector is normalized                // if direction of the ray is perpendicular to the plane normal, then the plane                // and the direction are parallel, so there won't even be an intersection,                // or, the ray is part of the plane (completely on it)                float dot = m_normal.dotProduct(dir);/* haegarr */   if( dot >= 0.0f - PLANE_EPSILON  &&  dot <= 0.0f + PLANE_EPSILON )                        return false;           // no intersection                return true;            // there is an intersection/* haegarr */   // even better would be:/* haegarr */   // return dot < 0.0f - PLANE_EPSILON  ||  dot > 0.0f + PLANE_EPSILON;        }        bool intersectionPoint(const util::CVector3d& o, const util::CVector3d& d, util::CVector3d& intersection) const        {                float t;                /* calculate the intersection point */                util::CVector3d dir(d);                                float len = dir.getLengthSquared();             // who wants to do an unecessary sqrt?/* haegarr */   if( len < 1.0f - PLANE_EPSILON  ||  len > 1.0f + PLANE_EPSILON )                        dir.normalize();                                        // make sure the vector is normalized                // get the distance from the origin point of the ray along direction 'd' to 'this' plane                if( !distance(o,dir,t) )                        return false;           // no intersection point                // calcuate the intersection point by starting with the origin of the ray and                 // scaling the ray's direction vector by the distance between the start point and                // the plane.                intersection = o + (dir * t);                return true;        }        bool distance(const util::CVector3d& o, const util::CVector3d& d, float& dist) const        {                // check to see if there is an intersection point                if( !intersect(o,d) )                        return false;                /* calculate the intersection point */                util::CVector3d dir(d);                                float len = dir.getLengthSquared();             // who wants to do an unecessary sqrt?/* haegarr */   if( len < 1.0f - PLANE_EPSILON  ||  len > 1.0f + PLANE_EPSILON )                        dir.normalize();                                        // make sure the vector is normalized                // http://www.siggraph.org/education/materials/HyperGraph/raytrace/rayplane_intersection.htm                // this finds the distance between the origin point and the plane in the direction                // of 'd'                dist = (-(m_normal.dotProduct(o) + m_distance)) / m_normal.dotProduct(dir);                return true;        }


E.g. pointOnPlane: If dot-product minus distance nearly 0, then return true. Can be transformed into dot-product nearly distance, what means dot-product at least distance minus tolerance _and_ dot-product at most distance plus tolerance.

Exactly the inverse at the condition of normalization: if the vector's (squared) length is _not_ nearly 1, then ... I've applied the deMorgan rule here.

BTW: Notice that there is at least one comment that tolds that a vector is already normalized, but the routine also works on unnormalized vectors (due to self normalizing). You should hold comments and implementation synchrone.

I look furthur on the code, but I expect the corrections above already changing the overall result.
I'm still getting (1, 0.5, 0), but I can see where your changes are better than the code I had.
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
Here my 2nd thought (I could not do all at once, sorry):

IMHO the computation of the distance in CPlane isn't correct. Please check my explanation:

The plane is given by the Hess' Normal Form (don't know whether this is the correct term in english)
( r - p ) dot n = r dot n - p dot n = 0
where r is an arbitrary point, p is a point on the plane, and n is the plane's normal.
From this the distance D could be computed as
D := p dot n
so that
r dot n = D
is equivalent.
Now, the arbitrary point is set to the ray
r(t) := o + t * dir
so that
( o + t * dir) dot n = o dot n + t * dir dot n = D
t * dir dot n = D - o dot n
and finally
t = ( D - o dot n ) / ( dir dot n )
If you please compare this with your implementation
dist = (-(m_normal.dotProduct(o) + m_distance)) / m_normal.dotProduct(dir);
then you see a difference at the sign of D (your "m_distance")!

Looking at the site you've cited as the source of the formula, you'll see the plane equation:
A'x + B'y + C'z + D' = 0
Comparing to what I've written above, you see that the D' of this formula is the "inverse distance"
D' := - p dot n
(something that could often be found in literature, but one must be aware of this!

So, please replace your dist formula by
dist = ( m_distance - m_normal.dotProduct(o) ) / m_normal.dotProduct(dir);
Wow, thank you haegarr!

See, that's what happens when you only work on something like this when you are tired. Stupid mistakes happen.

Thanks haegarr, and all you other guys, you helped a lot!

It looks to be all good now, but I'm going to go through some more points on paper, and hopefully I won't have another problem.
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
Ok.

However, here you could see never to trust strange code w/o self checking it. It need not be wrong, of course, but also other start or side conditions may introduce differences.

And at least a tipp: The code you're using for AABB checking could be drastically optimized. I assume a runtime factor of 3 or more is possible. So, if you're doing a real-time app, you may take into account to remember this when coming to runtime optimizations...

Good luck furthur.
Quote:Original post by haegarr
And at least a tipp: The code you're using for AABB checking could be drastically optimized. I assume a runtime factor of 3 or more is possible. So, if you're doing a real-time app, you may take into account to remember this when coming to runtime optimizations...


Oh, definately, but I'm going to work with the code for a while longer before I start to optimize, otherwise I won't fully understand everything, and I'll just be back here, not having learned anything. [smile]
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper

This topic is closed to new replies.

Advertisement