Skip to main content
GameDev.net gamedev.net
🔒 Locked

Collision Detection (Sphere-Triangle)

Started by Arangol Jan 3, 2007 at 7:33 AM 11 replies 2.9k views
Original Post
Arangol
Arangol
Hello I have a small 3d game demo that loads in Obj files and Md2 files and you can move around. I would like to know how to test for collision detection between the obj files and the md2 files. I think that it should be so that the md2 file(the player) would be treated as a sphere and the Obj files(objects) would have triangles(because they are triangulated), so I need to know how to check for sphere-triangle collision. This is how my Obj loading code looks like ( I don't think the Md2 loading code is important beacause I just need the players positions) ObjLoader.cpp

#ifdef WIN32
#include <windows.h>
#endif
#include "Main.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <GL/gl.h>
#include <GL/glu.h>

#include "ObjLoader.h"

extern int NumObjects;
/* Ignored until Version6 */
void ConvertMeshTo(ObjFile id,unsigned char What)
{
}
void SetLightPosition(float lx,float ly,float lz)
{
}
void SetTextures(ObjFile id,const char BumpTex[],const char BaseTex[])
{
}

/*===================================================================================================
**
**										Internal Data Structures
**
**=================================================================================================*/



typedef struct
{
	float x,			/*	The x component of the vertex position	*/
		  y,			/*	The y component of the vertex position	*/
		  z;			/*	The z component of the vertex position	*/
} ObjVertex;



typedef struct
{
	float x,			/*	The x component of the normal vector	*/
		  y,			/*	The y component of the normal vector	*/
		  z;			/*	The z component of the normal vector	*/
} ObjNormal;


typedef struct
{
	float u,			/*	The u parametric texturing co-ordinate	*/
		  v;			/*	The v parametric texturing co-ordinate	*/
} ObjTexCoord;



typedef struct
{
	unsigned int  m_aVertexIndices[3],		/*	array of indicies that reference the vertex array in the mesh	*/
				  m_aNormalIndices[3],		/*	array of indicies that reference the normal array in the mesh	*/
				  m_aTexCoordIndicies[3];	/*	array of indicies that reference the uv coordinate array in the mesh	*/

} ObjFace;


typedef struct _ObjMesh
{
	ObjVertex		*m_aVertexArray;		/*	Array of vertices that make up this mesh	*/
	ObjNormal		*m_aNormalArray;		/*	Array of normals that make up this mesh		*/
	ObjTexCoord		*m_aTexCoordArray;		/*	Array of texturing co-ordinates that make up this mesh */
	ObjFace			*m_aFaces;				/*	Array of faces that make up this mesh */

	unsigned int	 m_iNumberOfVertices,	/*	The number of vertices in the m_aVertexArray array	*/
					 m_iNumberOfNormals,	/*	The number of normals in the m_aNormalArray array	*/
					 m_iNumberOfTexCoords,	/*	The number of uv's in the m_aTexCoordArray array	*/
					 m_iNumberOfFaces;		/*	The number of faces in the m_aFaces array			*/

	struct _ObjMesh *m_pNext;			/*	Each mesh will be stored internally as a node on a linked list */
	ObjFile			 m_iMeshID;				/*	the ID of the mesh	*/

} ObjMesh;


unsigned int ResizeTriangleArray(ObjMesh *pMesh,unsigned int iExtraTriangleCount)
{
	/*
	**	Create a temporary pointer to the old face array in the mesh
	*/
	ObjFace *pTempFaces = pMesh->m_aFaces;

	/*
	**	Keep track of the old face count
	*/
	unsigned int iTemp  = pMesh->m_iNumberOfFaces;

	/*
	**	Increase the number of faces that will be stored in the mesh
	*/
	pMesh->m_iNumberOfFaces += iExtraTriangleCount;

	/*
	**	Allocate the number of faces now required by this mesh. Note, you could use
	**	realloc for this, but it's just my personal preference to do it this way.
	*/
	pMesh->m_aFaces = (ObjFace*)malloc(pMesh->m_iNumberOfFaces*sizeof(ObjFace));

	/*
	**	If we assert here then the memory allocation for the meshes failed
	*/
	assert(pMesh->m_aFaces);

	/*
	**	Copy across the data that was stored in the old array
	*/
	memcpy(pMesh->m_aFaces,pTempFaces,iTemp*sizeof(ObjFace));

	/*
	**	Delete the data held in the previous array
	*/
	free(pTempFaces);

	memset(&pMesh->m_aFaces[ iTemp ],0,iExtraTriangleCount*sizeof(ObjFace) );

	/*
	**	return the old size of the array (thats the same as the index of the first new triangle).
	*/
	return iTemp;

}


//										Global Variables


ObjMesh *g_LinkedListHead = NULL;

/* 
**	This is used to generate a unique ID for each Obj File
*/
unsigned int g_ObjIdGenerator=0;

/*
**	This function is only called from within the *.c file and is used to create an ObjMesh structure and
**	initialise its values (adds the mesh to the linked list also).
*/
ObjMesh *MakeOBJ( void )
{

	/*
	**	The pointer we will create the mesh at the end of
	*/
	ObjMesh *pMesh = NULL;


	pMesh = (ObjMesh*) malloc (sizeof(ObjMesh));

	/*	If the program asserts here, then there was a memory allocation failure	*/
	assert(pMesh);

	/*
	**	Initialise all pointers to NULL
	*/
	pMesh->m_aFaces				= NULL;
	pMesh->m_aNormalArray		= NULL;
	pMesh->m_aTexCoordArray		= NULL;
	pMesh->m_aVertexArray		= NULL;
	pMesh->m_iNumberOfFaces		= 0;
	pMesh->m_iNumberOfNormals	= 0;
	pMesh->m_iNumberOfTexCoords = 0;
	pMesh->m_iNumberOfVertices	= 0;
	pMesh->m_iMeshID			= ++g_ObjIdGenerator;


	/*
	**	Insert the mesh at the beginning of the linked list
	*/
	pMesh->m_pNext				= g_LinkedListHead;
	g_LinkedListHead			= pMesh;

	return pMesh;
}

void GetIndices(char *pString, unsigned int *pVert, unsigned int *pNorm, unsigned int *pUV)
{
	char *pV = pString,
		 *pN = NULL,
		 *pT = NULL;

	
	while( *pString != '\0' )
	{
		if(*pString == '/')
		{
			*pString = '\0';
			if(!pT)
			{
				pT = pString+1;
			}
			else
			{
				pN = pString+1;
			}
		}
		pString++;
	}

	/*
	**	Read the vertex index
	*/
	sscanf(pV,"%d",pVert);


	if( pN == NULL || *pN == '\0' )
	{
		*pNorm = 1;
	}
	else
	{
		sscanf(pN,"%d",pNorm);
	}


	if( pT == NULL || *pT == '\0' )
	{
		*pUV = 1;
	}
	else
	{
		sscanf(pT,"%d",pUV);
	}
}

ObjFile LoadOBJ(const char *filename)
{
	ObjMesh *pMesh=NULL;
	unsigned int vc=0,nc=0,tc=0,fc=0;
	char buffer[256];
	FILE *fp = NULL;

	/*
	**	Open the file for reading
	*/
	fp = fopen(filename,"r");

	/*
	**	If the program asserted here, then the file could not be found or opened
	*/
	assert(fp);

	/*
	**	Create the mesh structure and add it to the linked list
	*/
	pMesh = MakeOBJ();



	while(!feof(fp))
	{
		memset(buffer,0,200);

		/*	Grab a line at a time	*/
		fgets(buffer,256,fp);

		/*	look for the 'vn' - vertex normal - flag	*/
		if( strncmp("vn ",buffer,3) == 0 )
		{
			++pMesh->m_iNumberOfNormals;
		}
		else

		/*	look for the 'vt' - texturing co-ordinate - flag  */
		if( strncmp("vt ",buffer,3) == 0 )
		{
			++pMesh->m_iNumberOfTexCoords;
		}
		else

		/*	look for the 'v ' - vertex co-ordinate - flag  */
		if( strncmp("v ",buffer,2) == 0 )
		{
			++pMesh->m_iNumberOfVertices;
		}
		else

		/*	look for the 'f ' - face - flag  */
		if( strncmp("f ",buffer,2) == 0 )
		{
			++pMesh->m_iNumberOfFaces;
		}
	}

	/*
	**	close the file for now....
	*/
	fclose(fp);

	/*
	**	Allocate the memory for the data arrays and check that it allocated ok
	*/
	pMesh->m_aVertexArray	= (ObjVertex*  )malloc( pMesh->m_iNumberOfVertices	* sizeof(ObjVertex)	  );
	assert(pMesh->m_aVertexArray);

	/*	there are occasionally times when the obj does not have any normals in it */
	if( pMesh->m_iNumberOfNormals > 0 )
	{
		pMesh->m_aNormalArray	= (ObjNormal*  )malloc( pMesh->m_iNumberOfNormals	* sizeof(ObjNormal)	  );
		assert(pMesh->m_aNormalArray);
	}

	/*	there are occasionally times when the obj does not have any tex coords in it */
	if( pMesh->m_iNumberOfTexCoords > 0 )
	{
		pMesh->m_aTexCoordArray = (ObjTexCoord*)malloc( pMesh->m_iNumberOfTexCoords	* sizeof(ObjTexCoord) );
		assert(pMesh->m_aTexCoordArray);
	}

	pMesh->m_aFaces			= (ObjFace*    )malloc( pMesh->m_iNumberOfFaces		* sizeof(ObjFace)	  );
	assert(pMesh->m_aFaces);



	fp = fopen(filename,"r");

	while(!feof(fp))
	{
		memset(buffer,0,255);

		/*	Grab a line at a time	*/
		fgets(buffer,256,fp);

		/*	look for the 'vn' - vertex normal - flag	*/
		if( strncmp("vn ",buffer,3) == 0 )
		{
			sscanf((buffer+2),"%f%f%f",
							&pMesh->m_aNormalArray[ nc ].x,
							&pMesh->m_aNormalArray[ nc ].y,
							&pMesh->m_aNormalArray[ nc ].z);
			++nc;
		}
		else

		/*	look for the 'vt' - texturing co-ordinate - flag  */
		if( strncmp("vt ",buffer,3) == 0 )
		{
			sscanf((buffer+2),"%f%f",
							&pMesh->m_aTexCoordArray[ tc ].u,
							&pMesh->m_aTexCoordArray[ tc ].v);
			++tc;
		}
		else

		/*	look for the 'v ' - vertex co-ordinate - flag  */
		if( strncmp("v ",buffer,2) == 0 )
		{
			sscanf((buffer+1),"%f%f%f",
							&pMesh->m_aVertexArray[ vc ].x,
							&pMesh->m_aVertexArray[ vc ].y,
							&pMesh->m_aVertexArray[ vc ].z);
			++vc;
		}
		else

		/*	look for the 'f ' - face - flag  */
		if( strncmp("f ",buffer,2) == 0 )
		{
			/*
			**	some data for later....
			*/
			char *pSplitString = NULL;
			unsigned int i,ii = 0;
			unsigned int iNewTrianglesIndex = 0,
						 iNumberOfExtraTriangles = 0;
			unsigned int aFirstIndices[3],
						 aSecondIndices[3];

			ObjFace *pf = NULL;

			
			
			for(i=0;i<strlen(buffer);i++)
			{
				if(buffer == '/')
					ii++;
			}
			ii/=2;

			
			
			if( (ii-2) > 1 )
			{
				iNumberOfExtraTriangles = ii-3;
				iNewTrianglesIndex = ResizeTriangleArray(pMesh,iNumberOfExtraTriangles);
			}


			pf = &pMesh->m_aFaces[ fc ];


		
		
			pSplitString = strtok((buffer+2)," \t\n");
			i=0;

			/*
			**	Get the vertex structures that we know WILL exists (there has to be a minimum of three)
			*/
			for(i=0;i<3;i++)
			{
				GetIndices( pSplitString,
							&pf->m_aVertexIndices,
							&pf->m_aNormalIndices,
							&pf->m_aTexCoordIndicies);

				/* need to reduce the indices by 1 because array indices start at 0, obj starts at 1  */
				--pf->m_aTexCoordIndicies;
				--pf->m_aNormalIndices;
				--pf->m_aVertexIndices;

			
			
			
				switch(i)
				{
				case 0:
					aFirstIndices[0] = pf->m_aVertexIndices;
					aFirstIndices[1] = pf->m_aNormalIndices;
					aFirstIndices[2] = pf->m_aTexCoordIndicies;
					break;

				case 2:
					aSecondIndices[0] = pf->m_aVertexIndices;
					aSecondIndices[1] = pf->m_aNormalIndices;
					aSecondIndices[2] = pf->m_aTexCoordIndicies;
					break;
				default:
					break;
				}

				pSplitString = strtok(NULL," \t\n");
			}
			++fc;

			/*
			**	Get the additional triangles that may be there
			*/
			for(i=0;i<iNumberOfExtraTriangles;i++)
			{
				pf = &pMesh->m_aFaces[ iNewTrianglesIndex + i ];

				GetIndices( pSplitString,
							&pf->m_aVertexIndices[2],
							&pf->m_aNormalIndices[2],
							&pf->m_aTexCoordIndicies[2]);

				/* need to reduce the indices by 1 because array indices start at 0, obj starts at 1  */
				--pf->m_aTexCoordIndicies[2];
				--pf->m_aNormalIndices[2];
				--pf->m_aVertexIndices[2];

				/*
				**	Now assign the other indices for the triangle
				*/
				pf->m_aVertexIndices[0]		= aFirstIndices[0];
				pf->m_aNormalIndices[0]		= aFirstIndices[1];
				pf->m_aTexCoordIndicies[0]	= aFirstIndices[2];

				pf->m_aVertexIndices[1]		= aSecondIndices[0];
				pf->m_aNormalIndices[1]		= aSecondIndices[1];
				pf->m_aTexCoordIndicies[1]	= aSecondIndices[2];

				/*
				**	Copy over the indices for the next triangle.
				*/
				aSecondIndices[0] = pf->m_aVertexIndices[2];
				aSecondIndices[1] = pf->m_aNormalIndices[2];
				aSecondIndices[2] = pf->m_aTexCoordIndicies[2];

				pSplitString = strtok(NULL," \t\n");
			}
		}
	}

	fclose(fp);

	return pMesh->m_iMeshID;
}




void DrawOBJ(ObjFile id)
{
	
	
	ObjMesh *pMesh = g_LinkedListHead;

	while(pMesh && pMesh->m_iMeshID != id)
	{
		pMesh = pMesh->m_pNext;
	}

	/*
	**	Check to see if the mesh ID is valid.
	*/
	if(pMesh != NULL)
	{
	
	
	
	
		if( pMesh->m_aNormalArray   != NULL &&
			pMesh->m_aTexCoordArray != NULL )
		{
			unsigned int i;
			
			glBegin(GL_TRIANGLES);
			for(i=0;i<pMesh->m_iNumberOfFaces;i++)
			{
				unsigned int j;
				ObjFace *pf = &pMesh->m_aFaces;

				/*
				**	Draw the polygons with normals & uv co-ordinates
				*/
				for(j=0;j<3;j++)
				{
					glTexCoord2f( pMesh->m_aTexCoordArray[ pf->m_aTexCoordIndicies[j] ].u,
								  pMesh->m_aTexCoordArray[ pf->m_aTexCoordIndicies[j] ].v);
					glNormal3f( pMesh->m_aNormalArray[ pf->m_aNormalIndices[j] ].x,
								pMesh->m_aNormalArray[ pf->m_aNormalIndices[j] ].y,
								pMesh->m_aNormalArray[ pf->m_aNormalIndices[j] ].z);
					glVertex3f( pMesh->m_aVertexArray[ pf->m_aVertexIndices[j] ].x,
								pMesh->m_aVertexArray[ pf->m_aVertexIndices[j] ].y,
								pMesh->m_aVertexArray[ pf->m_aVertexIndices[j] ].z);
				}
			}
			glEnd();
		}
		else
		if( pMesh->m_aNormalArray   != NULL )
		{
			unsigned int i;
			glBegin(GL_TRIANGLES);
			
			for(i=0;i<pMesh->m_iNumberOfFaces;i++)
			{
				unsigned int j;
				ObjFace *pf = &pMesh->m_aFaces;

				/*
				**	Draw the polygons with Normals only
				*/
				for(j=0;j<3;j++)
				{
					glNormal3f( pMesh->m_aNormalArray[ pf->m_aNormalIndices[j] ].x,
								pMesh->m_aNormalArray[ pf->m_aNormalIndices[j] ].y,
								pMesh->m_aNormalArray[ pf->m_aNormalIndices[j] ].z);
					glVertex3f( pMesh->m_aVertexArray[ pf->m_aVertexIndices[j] ].x,
								pMesh->m_aVertexArray[ pf->m_aVertexIndices[j] ].y,
								pMesh->m_aVertexArray[ pf->m_aVertexIndices[j] ].z);
				}
			}
			glEnd();
		}
		else
		if( pMesh->m_aTexCoordArray != NULL )
		{
			unsigned int i;
			glBegin(GL_TRIANGLES);
			for(i=0;i<pMesh->m_iNumberOfFaces;i++)
			{
				unsigned int j;
				ObjFace *pf = &pMesh->m_aFaces;

				/*
				**	Draw the polygons with Texturing co-ordinates only
				*/
				for(j=0;j<3;j++)
				{
					glTexCoord2f( pMesh->m_aTexCoordArray[ pf->m_aTexCoordIndicies[j] ].u,
								  pMesh->m_aTexCoordArray[ pf->m_aTexCoordIndicies[j] ].v);
					glVertex3f( pMesh->m_aVertexArray[ pf->m_aVertexIndices[j] ].x,
								pMesh->m_aVertexArray[ pf->m_aVertexIndices[j] ].y,
								pMesh->m_aVertexArray[ pf->m_aVertexIndices[j] ].z);
				}
			}
			glEnd();
		}
		else
		{
			unsigned int i;
			glBegin(GL_TRIANGLES);
			for(i=0;i<pMesh->m_iNumberOfFaces;i++)
			{
				unsigned int j;
				ObjFace *pf = &pMesh->m_aFaces;

				/*
				**	Draw the polygons
				*/
				for(j=0;j<3;j++)
				{
					glVertex3f( pMesh->m_aVertexArray[ pf->m_aVertexIndices[j] ].x,
								pMesh->m_aVertexArray[ pf->m_aVertexIndices[j] ].y,
								pMesh->m_aVertexArray[ pf->m_aVertexIndices[j] ].z);
				}
			}
			glEnd();
		}
	}
}



void DeleteMesh(ObjMesh* pMesh)
{
	/*
	**	If the pointer is valid
	*/
	if(pMesh)
	{
		/*	delete the face array */
		if(pMesh->m_aFaces)
		{
			free(pMesh->m_aFaces);
			pMesh->m_aFaces = NULL;
		}

		/*	delete the vertex array */
		if(pMesh->m_aVertexArray)
		{
			free(pMesh->m_aVertexArray);
			pMesh->m_aVertexArray = NULL;
		}

		/*	delete the normal array */
		if(pMesh->m_aNormalArray)
		{
			free(pMesh->m_aNormalArray);
			pMesh->m_aNormalArray = NULL;
		}

		/*	delete the texturing co-ordinate array */
		if(pMesh->m_aTexCoordArray)
		{
			free(pMesh->m_aTexCoordArray);
			pMesh->m_aTexCoordArray = NULL;
		}

		/*	free the mesh */
		free( pMesh );
	}
}

void DeleteOBJ(ObjFile id)
{
	/*
	**	Create two pointers to walk through the linked list
	*/
	ObjMesh *pCurr,
			*pPrev = NULL;

	/*
	**	Start traversing the list from the start
	*/
	pCurr = g_LinkedListHead;



	while(pCurr != NULL && pCurr->m_iMeshID != id)
	{
		pPrev = pCurr;
		pCurr = pCurr->m_pNext;
	}

	/*
	**	If we found the node that needs to be deleted
	*/
	if(pCurr != NULL)
	{
		/*
		**	If the pointer before it is NULL, then we need to
		**	remove the first node in the list
		*/
		if(pPrev == NULL)
		{
			g_LinkedListHead = pCurr->m_pNext;
		}

		/*
		**	Otherwise we are removing a node from somewhere else
		*/
		else
		{
			pPrev->m_pNext = pCurr->m_pNext;
		}

		/*
		**	Free the memory allocated for this mesh
		*/
		DeleteMesh(pCurr);
	}
}

/*
**	Delete all of the meshes starting from the front.
*/
void CleanUpOBJ(void)
{
	ObjMesh *pCurr;
	while(g_LinkedListHead != NULL)
	{
		pCurr = g_LinkedListHead;
		g_LinkedListHead = g_LinkedListHead->m_pNext;
		DeleteMesh(pCurr);
	}
}



Thanks in advance Edit: I hope this was in the right forum.
oliii
oliii
One simple example of collision detection and response between spheres and triangles:

1) For each triangle, find closest point of the triangle from the sphere centre.
2) if the distance between the sphere centre and the point is greater than the sphere radius, then there is no collision.
3) Else, push the sphere away from that point, so the point is at distance = radius from the sphere centre.

void CheckInterference(Vector& SpherePos, const float SphereCentre, const CTriangle* Triangles, const int NumTriangles, const int NumIterations/*=3*/, const float RelaxationCoefficient/*=0.66f*/){    for(int j = 0; j < NumIterations; j ++)    {         for(int i = 0; i < NumTriangles; i ++)         {             // find closest point on the triangle to the sphere centre             Vector ClosestPoint = Triangles.ClosestPoint(SphereCentre);             // calculate tye distance between the two points.             Vector Dist = SphereCentre - ClosestPoint;             float DistanceSquared = Dist.DotProduct(Dist);                 // triangle too far away to collide             if(DistanceSquared > Sphere.Radius * Sphere.Radius)                 return false;             float  Distance = sqrt(DistanceSquared);             float  Depth    = Sphere.Radius - Distance; // depth of intersection.             Vector Push     = Dist * (Depth / Distance); // calculate the interference vector             SpherePos      += Push * RelaxationCoefficient; // push sphere pos away (with some damping).         }    }}


the NumIteration runs the algo several times, because pushing away from one triangle might push you towards another triangle, so you need to do teh check several time, until it 'settles'. The relaxation coefficient add some damping in the algo, so the pushes are not too brutal. It will mean that the sphere will be intersecting triangles slightly, but it's a small price to pay.

as the sphere move, that function will make sure the sphere does not go through the triangles (to a degree). As for the ClosestPoint() function, for a later post.
Everything is better with Metal.
Arangol
Arangol
Thanks, but that only clarifies a bit, I still don't know how to make my obj loader and your code to work together properly. I'm a beginner with 3d and model loading. I know how to get simple things like cubes to have collision detection, but I'm not sure how to do it with an obj loader.
Gagyi
Gagyi
Well, once you loaded your object, you should have an array of vertex positions. You can use that for collision detection.
AFAIK it has nothing to do about how you loaded the mesh.

BTW, sphere-point distance is a bit difficult, because you have to:

Project point on triangles plane
In the triangle? return distance

Project point on the three edges
Is it on at least one of them? Return closest

Else return the distance from the closest corner
-----------------------------------"After you finish the first 90% of a project, you have to finish the other 90%." - Michael Abrashstickman.hu <=my game (please tell me your opinion about it)
oliii
oliii
I can give a quick function I ripped from Mr Eberley ages ago. That gives you teh point-triangle distance (and closest point).

//-------------------------------------------------------------------// Calculate the squared distance of a point to the triangle//// Nicked code from David Eberly's most excelent physics site// Don;t ask me how it works. It just does :)//// (s, t) => ClosestPoint = V[0] + s * (V[1] - V[0]) + t * (V[2] - V[0]).// // (http://www.magic-software.com)//-------------------------------------------------------------------float CTriangle::DistanceFromPoint(const Vector& P, float* pfSParam, float* pfTParam) const{	if (!gpxVerts)		return 10000000.0f;		//-------------------------------------------------------------------	// 2 edges of the triangle	//-------------------------------------------------------------------	Vector E0 = (gpxVerts[i[1]]) - (gpxVerts[i[0]]);	Vector E1 = (gpxVerts[i[2]]) - (gpxVerts[i[0]]);    Vector kDiff = (gpxVerts[i[0]]) - P;    float fA00 = E0 * E0;    float fA01 = E0 * E1;    float fA11 = E1 * E1;    float fB0  = kDiff * E0;	float fB1  = kDiff * E1;	float fC   = kDiff * kDiff;    float fDet = (float) fabs(fA00*fA11-fA01*fA01);    float fS   = fA01*fB1-fA11*fB0;    float fT   = fA01*fB0-fA00*fB1;    float fSqrDist;	//-------------------------------------------------------------------	// degenerate triangle	//-------------------------------------------------------------------	if (fabs(fDet) < 0.000000001f)		return 100000000.0f;    if ( fS + fT <= fDet )    {        if ( fS < (float)0.0 )        {            if ( fT < (float)0.0 )  // region 4            {                if ( fB0 < (float)0.0 )                {                    fT = (float)0.0;                    if ( -fB0 >= fA00 )                    {                        fS = (float)1.0;                        fSqrDist = fA00+((float)2.0)*fB0+fC;                    }                    else                    {                        fS = -fB0/fA00;                        fSqrDist = fB0*fS+fC;                    }                }                else                {                    fS = (float)0.0;                    if ( fB1 >= (float)0.0 )                    {                        fT = (float)0.0;                        fSqrDist = fC;                    }                    else if ( -fB1 >= fA11 )                    {                        fT = (float)1.0;                        fSqrDist = fA11+((float)2.0)*fB1+fC;                    }                    else                    {                        fT = -fB1/fA11;                        fSqrDist = fB1*fT+fC;                    }                }            }            else  // region 3            {                fS = (float)0.0;                if ( fB1 >= (float)0.0 )                {                    fT = (float)0.0;                    fSqrDist = fC;                }                else if ( -fB1 >= fA11 )                {                    fT = (float)1.0;                    fSqrDist = fA11+((float)2.0)*fB1+fC;                }                else                {                    fT = -fB1/fA11;                    fSqrDist = fB1*fT+fC;                }            }        }        else if ( fT < (float)0.0 )  // region 5        {            fT = (float)0.0;            if ( fB0 >= (float)0.0 )            {                fS = (float)0.0;                fSqrDist = fC;            }            else if ( -fB0 >= fA00 )            {                fS = (float)1.0;                fSqrDist = fA00+((float)2.0)*fB0+fC;            }            else            {                fS = -fB0/fA00;                fSqrDist = fB0*fS+fC;            }        }        else  // region 0        {            // minimum at interior point            float fInvDet = ((float)1.0)/fDet;            fS *= fInvDet;            fT *= fInvDet;            fSqrDist = fS*(fA00*fS+fA01*fT+((float)2.0)*fB0) +                fT*(fA01*fS+fA11*fT+((float)2.0)*fB1)+fC;        }    }    else    {        float fTmp0, fTmp1, fNumer, fDenom;        if ( fS < (float)0.0 )  // region 2        {            fTmp0 = fA01 + fB0;            fTmp1 = fA11 + fB1;            if ( fTmp1 > fTmp0 )            {                fNumer = fTmp1 - fTmp0;                fDenom = fA00-2.0f*fA01+fA11;                if ( fNumer >= fDenom )                {                    fS = (float)1.0;                    fT = (float)0.0;                    fSqrDist = fA00+((float)2.0)*fB0+fC;                }                else                {                    fS = fNumer/fDenom;                    fT = (float)1.0 - fS;                    fSqrDist = fS*(fA00*fS+fA01*fT+2.0f*fB0) +                        fT*(fA01*fS+fA11*fT+((float)2.0)*fB1)+fC;                }            }            else            {                fS = (float)0.0;                if ( fTmp1 <= (float)0.0 )                {                    fT = (float)1.0;                    fSqrDist = fA11+((float)2.0)*fB1+fC;                }                else if ( fB1 >= (float)0.0 )                {                    fT = (float)0.0;                    fSqrDist = fC;                }                else                {                    fT = -fB1/fA11;                    fSqrDist = fB1*fT+fC;                }            }        }        else if ( fT < (float)0.0 )  // region 6        {            fTmp0 = fA01 + fB1;            fTmp1 = fA00 + fB0;            if ( fTmp1 > fTmp0 )            {                fNumer = fTmp1 - fTmp0;                fDenom = fA00-((float)2.0)*fA01+fA11;                if ( fNumer >= fDenom )                {                    fT = (float)1.0;                    fS = (float)0.0;                    fSqrDist = fA11+((float)2.0)*fB1+fC;                }                else                {                    fT = fNumer/fDenom;                    fS = (float)1.0 - fT;                    fSqrDist = fS*(fA00*fS+fA01*fT+((float)2.0)*fB0) +                        fT*(fA01*fS+fA11*fT+((float)2.0)*fB1)+fC;                }            }            else            {                fT = (float)0.0;                if ( fTmp1 <= (float)0.0 )                {                    fS = (float)1.0;                    fSqrDist = fA00+((float)2.0)*fB0+fC;                }                else if ( fB0 >= (float)0.0 )                {                    fS = (float)0.0;                    fSqrDist = fC;                }                else                {                    fS = -fB0/fA00;                    fSqrDist = fB0*fS+fC;                }            }        }        else  // region 1        {            fNumer = fA11 + fB1 - fA01 - fB0;            if ( fNumer <= (float)0.0 )            {                fS = (float)0.0;                fT = (float)1.0;                fSqrDist = fA11+((float)2.0)*fB1+fC;            }            else            {                fDenom = fA00-2.0f*fA01+fA11;                if ( fNumer >= fDenom )                {                    fS = (float)1.0;                    fT = (float)0.0;                    fSqrDist = fA00+((float)2.0)*fB0+fC;                }                else                {                    fS = fNumer/fDenom;                    fT = (float)1.0 - fS;                    fSqrDist = fS*(fA00*fS+fA01*fT+((float)2.0)*fB0) +                        fT*(fA01*fS+fA11*fT+((float)2.0)*fB1)+fC;                }            }        }    }    if ( pfSParam )        *pfSParam = fS;    if ( pfTParam )        *pfTParam = fT;    return (float) fabs(fSqrDist);}
Everything is better with Metal.
oliii
oliii
As for writing the whole collision system, I won't do that for you. Collision detection has nothing to do with model loading, and also, you'll have rendering on top. All I can give you are pointers.

As I said, For each triangle, find closest point to the sphere centre, if point isnide sphere, move sphere centre away from the point, that's all there is. You will also need to cancel out the velocity probably, if you use a velocity / position system.
Everything is better with Metal.
Arangol
Arangol
Ok, I will use sphere-point collision detecion. Could someone show me how to do sphere-point collision, I didn't really get what you showed me, Gagyi. Pseudo code is fine (but c++ would be nice too :)).

EDIT: Olii, we posted at the same time.

[Edited by - Arangol on January 3, 2007 12:56:45 PM]
Bob Janova
Bob Janova
Uh, I think I must not be getting you. Sphere-point collision detection says, to me, 'is a point in a sphere?' which is trivially
collision = (point-sphere.centre).Magnitude < r;
or, optimising out the square root in Magnitude:
collision = (point-sphere.centre).MagSquared < r²
oliii
oliii
If you don't know vector maths, You should learn about it, at least the basics. There is a very good and concise article in the article section about basic vector maths.

So yeah, to detect if a point

is in a sphere [C, r].

Vector D = (C - P); // the vector between C and P
float d2 = D.DotProduct(D); // squared distance of C from P, also squared length of vector D
float r2 = r * r; // squared radius.

if (d2 > r2) return false; // squared distance > squared radius. point not in sphere.

if the point is inside the sphere, you need to move the sphere away from the point. Meaning, you have to put P so that the distance between C and P is equal to 'r'.

For that, you push the sphere centre 'C' along the vector D, to a position where |CP| = r. for that you can use that simple formula.

float d = sqrt(d2); // length of vector D, which is square root of d2
C += D * (r - d) / d;

If you use explicit velocities (like, a euler integrator), don't forget to cancel the velocity component along the normal of collision, like so:

float vd = V.DotProduct(D);
V -= D * (vd / d2);

and the point C will be pushed away from P, and will be at distance 'r' from P.

Using the 'ClosestPoint' function, you can find your point 'P', and apply the algo above.

I'll re-write the ClosestPoint function for you.

//-------------------------------------------------------------------// Calculate the squared distance of a point to the triangle//// Nicked code from David Eberly's most excelent physics site// Don't ask me how it works. It just does :)// (http://www.magic-software.com)//// Parameters.// -----------//  : Point in question.// [V0, V1, V2] : triangle vertices.//// return values.// --------------// [s, t] : closest point, in Parametric form (for tex coord reconstruction).// [ClosestPoint] : The point on the triangle the closest, as a vector.// Returns : The squared distance of the point to the triangle.//-------------------------------------------------------------------float PointTriangleDistanceSquared(const Vector& P, const Vector& V0, const Vector& V1, const Vector& V2, float* s, float* t, Vector* Closest){    //-------------------------------------------------------------------    // 2 edges of the triangle    //-------------------------------------------------------------------    Vector E0    = (V1 - V0);    Vector E1    = (V2 - V0);    Vector kDiff = (V0 -  P);    float fA00 = E0.DotProduct(E0);    float fA01 = E0.DotProduct(E1);    float fA11 = E1.DotProduct(E1);    float fB0  = kDiff.DotProduct(E0);    float fB1  = kDiff.DotProduct(E1);    float fC   = kDiff.DotProduct(kDiff);    float fDet = (float) fabs(fA00*fA11-fA01*fA01);    float fS   = fA01*fB1-fA11*fB0;    float fT   = fA01*fB0-fA00*fB1;    float fSqrDist;	//-------------------------------------------------------------------	// degenerate triangle	//-------------------------------------------------------------------	if (fabs(fDet) < 0.000000001f)		return 100000000.0f;    if ( fS + fT <= fDet )    {        if ( fS < (float)0.0 )        {            if ( fT < (float)0.0 )  // region 4            {                if ( fB0 < (float)0.0 )                {                    fT = (float)0.0;                    if ( -fB0 >= fA00 )                    {                        fS = (float)1.0;                        fSqrDist = fA00+((float)2.0)*fB0+fC;                    }                    else                    {                        fS = -fB0/fA00;                        fSqrDist = fB0*fS+fC;                    }                }                else                {                    fS = (float)0.0;                    if ( fB1 >= (float)0.0 )                    {                        fT = (float)0.0;                        fSqrDist = fC;                    }                    else if ( -fB1 >= fA11 )                    {                        fT = (float)1.0;                        fSqrDist = fA11+((float)2.0)*fB1+fC;                    }                    else                    {                        fT = -fB1/fA11;                        fSqrDist = fB1*fT+fC;                    }                }            }            else  // region 3            {                fS = (float)0.0;                if ( fB1 >= (float)0.0 )                {                    fT = (float)0.0;                    fSqrDist = fC;                }                else if ( -fB1 >= fA11 )                {                    fT = (float)1.0;                    fSqrDist = fA11+((float)2.0)*fB1+fC;                }                else                {                    fT = -fB1/fA11;                    fSqrDist = fB1*fT+fC;                }            }        }        else if ( fT < (float)0.0 )  // region 5        {            fT = (float)0.0;            if ( fB0 >= (float)0.0 )            {                fS = (float)0.0;                fSqrDist = fC;            }            else if ( -fB0 >= fA00 )            {                fS = (float)1.0;                fSqrDist = fA00+((float)2.0)*fB0+fC;            }            else            {                fS = -fB0/fA00;                fSqrDist = fB0*fS+fC;            }        }        else  // region 0        {            // minimum at interior point            float fInvDet = ((float)1.0)/fDet;            fS *= fInvDet;            fT *= fInvDet;            fSqrDist = fS*(fA00*fS+fA01*fT+((float)2.0)*fB0) +                fT*(fA01*fS+fA11*fT+((float)2.0)*fB1)+fC;        }    }    else    {        float fTmp0, fTmp1, fNumer, fDenom;        if ( fS < (float)0.0 )  // region 2        {            fTmp0 = fA01 + fB0;            fTmp1 = fA11 + fB1;            if ( fTmp1 > fTmp0 )            {                fNumer = fTmp1 - fTmp0;                fDenom = fA00-2.0f*fA01+fA11;                if ( fNumer >= fDenom )                {                    fS = (float)1.0;                    fT = (float)0.0;                    fSqrDist = fA00+((float)2.0)*fB0+fC;                }                else                {                    fS = fNumer/fDenom;                    fT = (float)1.0 - fS;                    fSqrDist = fS*(fA00*fS+fA01*fT+2.0f*fB0) +                        fT*(fA01*fS+fA11*fT+((float)2.0)*fB1)+fC;                }            }            else            {                fS = (float)0.0;                if ( fTmp1 <= (float)0.0 )                {                    fT = (float)1.0;                    fSqrDist = fA11+((float)2.0)*fB1+fC;                }                else if ( fB1 >= (float)0.0 )                {                    fT = (float)0.0;                    fSqrDist = fC;                }                else                {                    fT = -fB1/fA11;                    fSqrDist = fB1*fT+fC;                }            }        }        else if ( fT < (float)0.0 )  // region 6        {            fTmp0 = fA01 + fB1;            fTmp1 = fA00 + fB0;            if ( fTmp1 > fTmp0 )            {                fNumer = fTmp1 - fTmp0;                fDenom = fA00-((float)2.0)*fA01+fA11;                if ( fNumer >= fDenom )                {                    fT = (float)1.0;                    fS = (float)0.0;                    fSqrDist = fA11+((float)2.0)*fB1+fC;                }                else                {                    fT = fNumer/fDenom;                    fS = (float)1.0 - fT;                    fSqrDist = fS*(fA00*fS+fA01*fT+((float)2.0)*fB0) +                        fT*(fA01*fS+fA11*fT+((float)2.0)*fB1)+fC;                }            }            else            {                fT = (float)0.0;                if ( fTmp1 <= (float)0.0 )                {                    fS = (float)1.0;                    fSqrDist = fA00+((float)2.0)*fB0+fC;                }                else if ( fB0 >= (float)0.0 )                {                    fS = (float)0.0;                    fSqrDist = fC;                }                else                {                    fS = -fB0/fA00;                    fSqrDist = fB0*fS+fC;                }            }        }        else  // region 1        {            fNumer = fA11 + fB1 - fA01 - fB0;            if ( fNumer <= (float)0.0 )            {                fS = (float)0.0;                fT = (float)1.0;                fSqrDist = fA11+((float)2.0)*fB1+fC;            }            else            {                fDenom = fA00-2.0f*fA01+fA11;                if ( fNumer >= fDenom )                {                    fS = (float)1.0;                    fT = (float)0.0;                    fSqrDist = fA00+((float)2.0)*fB0+fC;                }                else                {                    fS = fNumer/fDenom;                    fT = (float)1.0 - fS;                    fSqrDist = fS*(fA00*fS+fA01*fT+((float)2.0)*fB0) +                        fT*(fA01*fS+fA11*fT+((float)2.0)*fB1)+fC;                }            }        }    }    if ( s )        *s = fS;    if ( t )        *t = fT;    if ( ClosestPoint )        *ClosestPoint = V0 + (fS * E0) + (fT * E1);     return (float) fabs(fSqrDist);}


That should work.

Everything is better with Metal.
daftasbrush
daftasbrush
Seems to me, that whilst both methods above will work, neither of them is particularly "optimal" for "collision detection", though Mr Eberley's method (pb Oliii) does show promise.

The point is.. for the vast majority of "space" (positions where the centre of the sphere could be), and ordinarily "most of the time".. the sphere and triangle do not / cannot collide. So really you want to be able to detect / determine that "as fast as possible".
Personally I'd convert the function to return a boolean (they collide / they don't), pass in the sphere radius (or its square) so I could perform the radius tests directly, rather than returning a value then testing that.

For most of "the time", or "the space", you don't need to calculate the actual distance to the closest point of the triangle, (or multiple distances and pick the lowest [rolleyes]).
You can perform some relatively fast rejection tests early on, and determine that the sphere and triangle cannot possibly collide.

The "biggy" here is point-plane distance.
You calculate the distance from the centre of the sphere to the plane of the triangle, pretty much right at the start.

If that distance is greater than the sphere's radius, you're done. (they don't collide)
If not... then you need to move on, and actually consider the triangle "proper", probably using Oliii's code..

It looks something like this... just a couple of extra lines..
 Vector E0    = (V1 - V0); Vector E1    = (V2 - V0); Vector kDiff = (V0 -  P); Vector norm  = E0.CrossProduct(E1); float fD = norm.DotProduct(kdiff); // Test the distance (fD) against the radius // I'd do the test this way to avoid the sqrt and the division if ((fD * fD) > (Radius * Radius * norm.DotProduct(norm)))	return 1000000.0f;  // if they can collide.. continue on and test the triangle  float fA00 = E0.DotProduct(E0);  float fA01 = E0.DotProduct(E1);
You then save quite a bit of time for the "vast majority" of the calls you'll ever make to the function.

There is one further "spatial" optimization... (provided you've added the above)
If it turns out that the centre of the sphere projects to be inside the triangle (region 0 in the code)..
Then you don't need to calculate the distance to determine collision, they must / do collide.

[Edited by - daftasbrush on January 4, 2007 9:21:40 AM]
oliii
oliii
Yeah, that code is grossly unoptimised, It's just to get you started. but you can run the function on a 10,000 triangle mesh model, and it will run at 15 fps easy. With proper optimisation, you can expect a 100x increase in performance easily (space partitioning, and fast rejects).

Also, the code ain't great for fast moving spheres. As usual, this is just one of the simplest possible way to do collision detection, there are much better methods but more complicated.

I have a crappy demo actually, doing that, in a very simple way, but with a AABBtree to speed up the code (still, the code is very slow).

http://uk.geocities.com/olivier_rebellion/simple_collision.zip

It's rubbish, mind you... but you'll get the idea.
Everything is better with Metal.
Arangol
Arangol
Thanks for all the help. I think I can figure out how to do collision detection.
I haven't read about vectors so I probably should.
oliii
oliii
here's the example, cleaned up

http://members.gamedev.net/oliii/very_basic_collision.rar
Everything is better with Metal.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.