Raycasting precision & texture coords when raycasting

AdrianCG
Posts: 3
Joined: Mon Sep 05, 2011 4:17 pm

Raycasting precision & texture coords when raycasting

Post by AdrianCG »

I'm relatively new to bullet, but so far i find it quite easy to use. I started writing a toy raytracer and stumbled upon a few difficulties. Note that i'm not using bullet only for collision detection, i also included the dynamics part, to make little raytraced movies (in the future) using it.

That said, here's what happened:

I noticed than when creating sphere objects(not polygonal , but created by bullet, which i think uses an analytic representation) and raycasting them, i receive normals that don't seem to be precise. More precisely, while lighting the sphere i get banding artifacts. Here's a screenshot of the issue:

Image

At first, i turned the floating point precision to fp:precise and compiled bullet(2.78 version) with double precision. This did not really help and i thought i try this, after the raycast:

normalCollision = normalize(sphereHitCenter - hitPointReturnedByRaycast);

This did the trick and i got the results i was expecting:

Image

My first question is: why do i need to calculate the normal myself for the sphere? Are there any switches that calculate a better normal for the sphere being hit by the ray? Also, here's the code i'm using for the raycast:

Code: Select all

	collisionInfo rayTest(const Endeavour::Math::Vector3f rayFrom, const Endeavour::Math::Vector3f rayTo)
	{
		struct	AllRayResultCallback : public btCollisionWorld::RayResultCallback
		{
			AllRayResultCallback(const btVector3&	rayFromWorld,const btVector3&	rayToWorld)
			:m_rayFromWorld(rayFromWorld),
			m_rayToWorld(rayToWorld)
			{
			}

			btVector3	m_rayFromWorld;//used to calculate hitPointWorld from hitFraction
			btVector3	m_rayToWorld;

			btVector3	m_hitNormalWorld;
			btVector3	m_hitPointWorld;
			
			virtual	btScalar	addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace)
			{
				//caller already does the filter on the m_closestHitFraction
				btAssert(rayResult.m_hitFraction <= m_closestHitFraction);
			
				m_closestHitFraction = rayResult.m_hitFraction;

				m_collisionObject = rayResult.m_collisionObject;
				if (normalInWorldSpace)
				{
					m_hitNormalWorld = rayResult.m_hitNormalLocal;
				} else
				{
					///need to transform normal into worldspace
					m_hitNormalWorld = m_collisionObject->getWorldTransform().getBasis()*rayResult.m_hitNormalLocal;
				}
				m_hitPointWorld.setInterpolate3(m_rayFromWorld,m_rayToWorld,rayResult.m_hitFraction);
				return 1.f;
			}
		};
		
		collisionInfo cInfo;
		cInfo.collisionOccured = false;
		cInfo.userData = NULL;

		btVector3 btRayFrom = btVector3(rayFrom.x, rayFrom.y, rayFrom.z);
		btVector3 btRayTo = btVector3(rayTo.x, rayTo.y, rayTo.z);

		AllRayResultCallback	resultCallback(btRayFrom,btRayTo);

		dynamicsWorld->rayTest(btRayFrom,btRayTo,resultCallback);
		if (resultCallback.hasHit())
		{	
			btVector3 worldNormal = resultCallback.m_hitNormalWorld;
			btVector3 worldPosition = resultCallback.m_hitPointWorld;
			
			cInfo.collisionOccured = true;
			cInfo.position = Endeavour::Math::Vector3f(worldPosition.x(), worldPosition.y(), worldPosition.z());
			cInfo.normal = Endeavour::Math::Vector3f(worldNormal.x(), worldNormal.y(), worldNormal.z());
			cInfo.userData = resultCallback.m_collisionObject->getUserPointer();

			return cInfo;
		}

		return cInfo;
	}
Also, here's the way i setup bullet:

Code: Select all

	btDefaultCollisionConfiguration* collisionConfiguration;
	btCollisionDispatcher* dispatcher;
	btBroadphaseInterface* overlappingPairCache;
	btSequentialImpulseConstraintSolver* solver;
	btDynamicsWorld* dynamicsWorld;			
	
	void init()
	{
		collisionConfiguration = new btDefaultCollisionConfiguration();
		dispatcher = new	btCollisionDispatcher(collisionConfiguration);
		btVector3	worldAabbMin(-5000,-5000,-5000);
		btVector3	worldAabbMax(5000,5000,5000);

		overlappingPairCache = new bt32BitAxisSweep3(worldAabbMin,worldAabbMax);
					
		solver = new btSequentialImpulseConstraintSolver;
		dynamicsWorld = new btContinuousDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration);
		dynamicsWorld->setDebugDrawer(NULL);
		dynamicsWorld->setWorldUserInfo(NULL);
		dynamicsWorld->setInternalTickCallback(NULL);
	}
My second question is: how can i use bullet to interpolate per vertex attributes for the point returned by the raycast? Attributes like texture coordinates, normals, colors and whatever other information one can think of? Is this possible?

EDIT:

I corrected the bullet version that i'm using: it's 2.78.
User avatar
Erwin Coumans
Site Admin
Posts: 4221
Joined: Sun Jun 26, 2005 6:43 pm
Location: California, USA

Re: Raycasting precision & texture coords when raycasting

Post by Erwin Coumans »

The collision normal is an approximation, for many collision/physics purposes a very precise normal is not required.

If you need a more precise normal, can you try replacing the btSubsimplexConvexCast by btGjkConvexCast or btContinuousConvexCollision and see if that improves it?

It is in Bullet/src/BulletCollision/CollisionDispatch/btCollisionWorld.cpp, void btCollisionWorld::rayTestSingle
Change this code from

Code: Select all

#define USE_SUBSIMPLEX_CONVEX_CAST 1
#ifdef USE_SUBSIMPLEX_CONVEX_CAST
		btSubsimplexConvexCast convexCaster(castShape,convexShape,&simplexSolver);
#else
		//btGjkConvexCast	convexCaster(castShape,convexShape,&simplexSolver);
		//btContinuousConvexCollision convexCaster(castShape,convexShape,&simplexSolver,0);
#endif //#USE_SUBSIMPLEX_CONVEX_CAST
to

Code: Select all

		btContinuousConvexCollision convexCaster(castShape,convexShape,&simplexSolver,0);
or

Code: Select all

		btGjkConvexCast	convexCaster(castShape,convexShape,&simplexSolver);
My second question is: how can i use bullet to interpolate per vertex attributes for the point returned by the raycast?
Bullet uses a generic collision shape representation, and collision shapes don't have collision UV etc defined, so you will have to do some additional work to compute the barycentric coordinates.

Thanks,
Erwin
AdrianCG
Posts: 3
Joined: Mon Sep 05, 2011 4:17 pm

Re: Raycasting precision & texture coords when raycasting

Post by AdrianCG »

Thanks,Erwin!

I replaced the source code you pointed me at, with

Code: Select all

btGjkConvexCast   convexCaster(castShape,convexShape,&simplexSolver);
and the accuracy seems perfect now!

Regarding my second question's reply i have to say i was kind of expected that! Searching the forums i found some source code that helped me get a hold of the triangle vertices in world space.The thing is, now i can easily calculate barycentric coordinates and interpolate in this way whatever per-vertex attributes i need. But the problem that i see is how do "i tie" the returned face index from bullet with my own face array so i can access per-vertex attributes? How would one use the face index returned by bullet to access vertex attributes of his own?
AdrianCG
Posts: 3
Joined: Mon Sep 05, 2011 4:17 pm

Re: Raycasting precision & texture coords when raycasting

Post by AdrianCG »

Silly me! So the hit triangle index reported by bullet can be used to index in my face array and from there to the texture coordinates array. I actually wrote some code to test if the reported vertices were the same as the ones that were found by indexing into my own copy of the vertices. At first the code did not seem to work, but only because the output that should have matched(one from the vertices reported by bullet and another from indexing into my own vertices' copy) was scrambled, because of me using multiple threads to raytrace the scene. All in all, problem solved. I'm posting the routine that finds the intersected face and its vertices:

Code: Select all

collisionInfo rayTest(const Endeavour::Math::Vector3f rayFrom, const Endeavour::Math::Vector3f rayTo)
	{
		struct	AllRayResultCallback : public btCollisionWorld::RayResultCallback
		{	
			btCollisionShape * m_hitTriangleShape;
			int  m_hitTriangleIndex;
			int	 m_hitShapePart;

			AllRayResultCallback(const btVector3&	rayFromWorld,const btVector3&	rayToWorld)
			:m_rayFromWorld(rayFromWorld),
			m_rayToWorld(rayToWorld),
			m_hitTriangleShape(NULL),
			m_hitTriangleIndex(0),
			m_hitShapePart(0)
			{
			}

			btVector3	m_rayFromWorld;//used to calculate hitPointWorld from hitFraction
			btVector3	m_rayToWorld;

			btVector3	m_hitNormalWorld;
			btVector3	m_hitPointWorld;
						
			virtual	btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace)
			{
				//caller already does the filter on the m_closestHitFraction
				btAssert(rayResult.m_hitFraction <= m_closestHitFraction);
			
				m_closestHitFraction = rayResult.m_hitFraction;

				m_collisionObject = rayResult.m_collisionObject;
				if (normalInWorldSpace)
				{
					m_hitNormalWorld = rayResult.m_hitNormalLocal;
				} else
				{
					///need to transform normal into worldspace
					m_hitNormalWorld = m_collisionObject->getWorldTransform().getBasis()*rayResult.m_hitNormalLocal;
				}

				if (rayResult.m_localShapeInfo)
				{
					m_hitTriangleShape = rayResult.m_collisionObject->getCollisionShape();
					m_hitTriangleIndex = rayResult.m_localShapeInfo->m_triangleIndex;
					m_hitShapePart = rayResult.m_localShapeInfo->m_shapePart;
				} else 
				{
					m_hitTriangleShape = NULL;
					m_hitTriangleIndex = 0;
					m_hitShapePart = 0;
				}
				
				m_hitPointWorld.setInterpolate3(m_rayFromWorld,m_rayToWorld,rayResult.m_hitFraction);
				return 1.f;
			}
		};
		
		collisionInfo cInfo;
		cInfo.collisionOccured = false;
		cInfo.userData = NULL;

		btVector3 btRayFrom = btVector3(rayFrom.x, rayFrom.y, rayFrom.z);
		btVector3 btRayTo = btVector3(rayTo.x, rayTo.y, rayTo.z);

		AllRayResultCallback	resultCallback(btRayFrom,btRayTo);
				
		dynamicsWorld->rayTest(btRayFrom,btRayTo,resultCallback);
		btVector3 worldNormal;
		btVector3 worldPosition;

		if (resultCallback.hasHit())
		{	
			worldNormal = resultCallback.m_hitNormalWorld;
			worldPosition = resultCallback.m_hitPointWorld;

			btVector3 hit = resultCallback.m_hitPointWorld;
			btRigidBody* body = btRigidBody::upcast(resultCallback.m_collisionObject);
			if (body)
			{		
				if (resultCallback.m_hitTriangleShape)
				{
					float triangle[9]; 

					btStridingMeshInterface * meshInterface = NULL;
					btCollisionShape* shape = resultCallback.m_hitTriangleShape;
										
					RTBaseObject* rp = (RTBaseObject*)resultCallback.m_collisionObject->getUserPointer();
					BulletPhysicsObject* bp = (BulletPhysicsObject*)rp->getPhysicsObject().get();
			
					if (shape->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE && rp->getType() == "polygonal")
					{		
						btVector3 center(bp->getCenter().x, bp->getCenter().y, bp->getCenter().z);

						int hitTriangleIndex = resultCallback.m_hitTriangleIndex;
						meshInterface = ((btBvhTriangleMeshShape*)shape)->getMeshInterface();					
						
						unsigned char *vertexbase;
						int numverts;
						PHY_ScalarType type;
						int stride;
						unsigned char *indexbase;
						int indexstride;
						int numfaces;
						PHY_ScalarType indicestype;

						meshInterface->getLockedVertexIndexBase(
							&vertexbase,
							numverts,
							type,
							stride,
							&indexbase,
							indexstride,
							numfaces,
							indicestype,
							0);
						
						unsigned int * gfxbase = (unsigned int*)(indexbase+hitTriangleIndex*indexstride);
						const btVector3 & meshScaling = shape->getLocalScaling();
						btVector3 triangle_v[3];
						unsigned int indices[3];
							for (int j=2;j>=0;j--)
							{
								int graphicsindex = indicestype==PHY_SHORT?((unsigned short*)gfxbase)[j]:gfxbase[j];
								indices[j] = graphicsindex;
								btScalar * graphicsbase = (btScalar*)(vertexbase+graphicsindex*stride);

							triangle_v[j] = btVector3(graphicsbase[0]/**meshScaling.getX()*/,
													  graphicsbase[1]/**meshScaling.getY()*/,
													  graphicsbase[2]/**meshScaling.getZ()*/);	

							}

						meshInterface->unLockVertexBase(0);
						
						triangle[0] = triangle_v[0].getX();
						triangle[1] = triangle_v[0].getY();
						triangle[2] = triangle_v[0].getZ();
						triangle[3] = triangle_v[1].getX();
						triangle[4] = triangle_v[1].getY();
						triangle[5] = triangle_v[1].getZ();
						triangle[6] = triangle_v[2].getX();
						triangle[7] = triangle_v[2].getY();
						triangle[8] = triangle_v[2].getZ();		
						
						/*printf("<<<<<<<<<<<<<<<<<< %d %d %d\n", indices[0], indices[1], indices[2]);
						int tt;
						for (tt=0; tt<9; tt++)
							printf("%2.1f ", triangle[tt]);
						printf("\n**\n");

						for (tt=0; tt<3; tt++)
							printf("%2.1f %2.1f %2.1f ", bp->getFaceVertex(hitTriangleIndex, tt).x,
							bp->getFaceVertex(hitTriangleIndex, tt).y,
							bp->getFaceVertex(hitTriangleIndex, tt).z);
					
						printf("\n>>>>>>>>>>>>>>> %d %d %d\n");		*/	
					}
				}
			}
						
			cInfo.collisionOccured = true;
			cInfo.position = Endeavour::Math::Vector3f(worldPosition.x(), worldPosition.y(), worldPosition.z());
			cInfo.normal = Endeavour::Math::Vector3f(worldNormal.x(), worldNormal.y(), worldNormal.z());
			cInfo.userData = resultCallback.m_collisionObject->getUserPointer();

			return cInfo;
		}

		return cInfo;
	}

mi076
Posts: 144
Joined: Fri Aug 01, 2008 6:36 am
Location: Bonn, Germany

Re: Raycasting precision & texture coords when raycasting

Post by mi076 »

Code: Select all

meshInterface->getLockedVertexIndexBase(
                     &vertexbase,
                     numverts,
                     type,
                     stride,
                     &indexbase,
                     indexstride,
                     numfaces,
                     indicestype,
                     0);

Code: Select all

meshInterface->unLockVertexBase(0);
FYI, here is better to use part id (m_hitShapePart) instead of "0"... Otherwise it can cause crashes if there are submeshes ( addIndexedMesh() ), they are reported as part ids. I have updated the HitTriangleDemo, but an old link hangs around somewhere.