PDA

View Full Version : Collision detection in 3D for obstacles, sword fighting...



Firlefanz
14-11-2006, 08:11 AM
Hello,

I need a collision detection for mainly 2 purposes:
- when the player is walking he should not walk 'through' obstacles
- when the player beats I want to know which monster(s) are hit

It is for a 3D kind of medieval RPG Action Game developed with DanJetX.

All my meshes are stored in a Engine based on a TList.

I have a very basic collision detection working a a circle's radius, taken and altered from a sample from Dan:



function TForm1.boundcollide(spriteself: TDJXEngineItem): boolean;
var i: integer;
colSpr: TDJXEngineItem;
ColPt: TD3DXVector2;
tmpVec: TD3DXVector2;
ndist: Double;
begin
//Collision with other sprites
//do not be alarmed by the complexity of the
//calculations, they are really simple
//once you take a closer look;)
result:=false;
for i:=0 to Engine.Count-1 do begin
ndist:=distance_dbl(spriteself.x,spriteself.z,0,En gine.Sprites[i].X,Engine.Sprites[i].Z,0);
if (Engine.Sprites[i]<>spriteself) and (ndist<Innomsprite(spriteself).nRadius+Innomsprite(Engine .Sprites[i]).nRadius+1)
then begin
colSpr:=Engine.Sprites[i];
//Player schl?§gt
if (spriteself.name='spieler') and (meshplayer(spriteself).status=schlag1) then begin
if (colspr.name='innom') and (meshinnom(colspr).status<>schmerz) then begin
meshinnom(colspr).nanimpos:=0;
meshinnom(colspr).status:=schmerz;
meshinnom(colspr).hit(meshplayer(spriteself).nhitp ower);
end;
end;
//Innom schl?§gt
if (spriteself.name='innom') and (meshinnom(spriteself).status<>schlag1) and (meshinnom(spriteself).status<>schmerz) then begin
if (colspr.name='bauer') then begin
meshinnom(spriteself).nanimpos:=0;
meshinnom(spriteself).status:=schlag1;
meshbauer(colspr).hit(meshinnom(spriteself).nhitpo wer);
end;
if (colspr.name='spieler') then begin
meshinnom(spriteself).nanimpos:=0;
meshinnom(spriteself).status:=schlag1;
meshplayer(colspr).hit(meshinnom(spriteself).nhitp ower);
end;
end;
if (Engine.Sprites[i]<>spriteself) and (ndist<Innomsprite(spriteself).nRadius+Innomsprite(Engine .Sprites[i]).nRadius)
then begin
result:=true;
colSpr:=Engine.Sprites[i];
ColPt:=D3DXVector2((spriteself.X+colSpr.X)/2,(spriteself.z+colSpr.z)/2);
tmpVec:=D3DXVector2(spriteself.X - colSpr.X,spriteself.z - colSpr.z);
D3DXVec2Normalize(tmpVec, tmpVec);
if innomsprite(spriteself).bolmovable then begin
if innomsprite(ColSpr).bolmovable then begin
spriteself.x:=ColPt.x + tmpVec.x * innomsprite(spriteself).nRadius;
spriteself.z:=ColPt.y + tmpVec.y * innomsprite(spriteself).nRadius;
end
else begin
spriteself.x := ColSpr.x + tmpVec.x * innomsprite(ColSpr).nRadius + tmpVec.x * innomsprite(spriteself).nRadius;
spriteself.z := ColSpr.z + tmpVec.y * innomsprite(ColSpr).nRadius + tmpVec.y * innomsprite(spriteself).nRadius;
end;
end;
end;
{ if innomsprite(ColSpr).bolmovable then begin
if innomsprite(spriteself).bolmovable then begin
ColSpr.x := ColPt.x - tmpVec.x * innomsprite(ColSpr).nRadius;
ColSpr.z := ColPt.y - tmpVec.y * innomsprite(ColSpr).nRadius;
end
else begin
ColSpr.x := spriteself.x - tmpVec.x * innomsprite(spriteself).nRadius - tmpVec.x * innomsprite(ColSpr).nRadius;
ColSpr.z := spriteself.z - tmpVec.y * innomsprite(spriteself).nRadius - tmpVec.y * innomsprite(ColSpr).nRadius;
end;
end; }
exit;
end;
end;
end;


One problem is it only works with circles, so this is bad when it comes to fences and walls etc. So I need another case for rectangle collisions?

The other problem is, 'spieler' means player, 'innom' is the monster, 'bauer' is a villagers and each of them has a nrotatey variable beween -pi and pi which direction the person faces.
Now when many monsters surround the player, one gets hits but not the one in the right direction, it may also be one standing behind the player...

I am not experienced with that, can somebody help me out, how do you guys do this?

Thanks,
Firle

grudzio
14-11-2006, 05:01 PM
Now when many monsters surround the player, one gets hits but not the one in the right direction, it may also be one standing behind the player...


Store the direction the player is facing as a vector. It can be made from facing angle



vDir &#58;= vector&#40;cos&#40;angle,0,sin&#40;angle&#41;&#41;;


To find out if player faces the monster first calculate vector v between player and monster positions. Then calculate the dot product of v and players direction. If it is greater than zero than monster is in front of a player otherwise monster is behind.

This method gives you information if the monster is behind or in front of the player. The monster can be in front of the player but outside players
viewing field (too far ont he right or left). To check this further tests are needed. An example



I am assuming that player has 90 degree field of view.
\ front /
\ /
\ /
left ---P --- right
/ \
/ \
/ back \

v &#58;= Monster.Pos - Player.Pos;
d &#58;= dotProduct&#40;Player.Dir,v&#41;;
if d > 0 then begin //the monster over the vertical line on the picture
d &#58;= ArcCos&#40;d&#41;;
if &#40;d < pi/4&#41; or &#40;d > 3*pi/4&#41; then //player is in the front area



As for collisons with obstacles you need bounding rectangles. You can do collision in two passes. First with static objects based on rectangles and second with monsters based on circles.

Hope you find it usefull.

Firlefanz
15-11-2006, 07:31 AM
Hello grudzio,

thanks a lot for this very useful info and explanations!

I'll try that, looks very good to me, just what I need I guess.

Thanks a lot! :D

Firlefanz

Firlefanz
15-11-2006, 01:20 PM
One question about this explanation:

vdir is : TD3DXVector3 right?

v and d are single or double, right?

What is Monster.Pos and Player.Pos?

I have x,y,z Position variable for each of them.

Is it also a TD3DXVector3 with X,Y,Z?

Thanks,
firle

Huehnerschaender
15-11-2006, 02:57 PM
Yes it is.

The Dotproduct of 2 Vectors (in your case TD3DXVector3) gives a float value.

The ArcCos of this value gives the angle (in radians), in which the 2 points are positioned to each other.

DotProduct =


function DotProduct(const a, b: TPoint3): Real;
begin
Result:= (a.x * b.x) + (a.y * b.y) + (a.z * b.z);
end;

grudzio
15-11-2006, 03:11 PM
Player.Pos and Monster.Pos are 3D vectors (TD3DXVector3) and describe world position of player and monster. They are composed of x,y,z variables you use currently.

Player.Dir is also TD3DXVector3 describing orientation of the player (or monster). It can be made from nrotatey variable you have

Player.Dir := MakeTD3DXVector3( cos(nrotatey), 0, sin(nrotatey));

If you store nrotatey in angles dont forget to change it to radians before you pass it to sin and cos functions.

vDir is also a TD3DXVector3 but in my example it is replaced by Player.Dir.

v is also a TD3DXVector3. So in the line
v := Monster.Pos - Player.Pos;
"-" means vector subtraction.

d is a scalar value (single or double).

I am sorry for the confusion. I hope it is more clear now.

I just noticed that it is already anwsered. I am so sloooow typer :( .

grudzio
15-11-2006, 03:21 PM
Just one more thing. Like Huehnerschaender already said, the dotProduct(v1,v2) returns an cosine of an angle between vectors v1 and v2. So it can be used to determine if player is facing monster. This can be usefull if you want the attacks to give more damage if made from side or behind.

Firlefanz
15-11-2006, 04:00 PM
Looks very good. Thank you both for those explantions, I will try that later and be back with more questions or let you know if it worked. :wink:

Firle

Firlefanz
16-11-2006, 07:30 AM
Hello,

yesterday I build a new routine for fighting collisions.
But something is wrong with it, when I am in the radius of the monster, in some position I hit it never, in another position I hit it always no matter how I rotate.

I guess I did something wrong here.



function TForm1.fightcollide&#40;spriteself&#58; TDJXEngineItem&#41;&#58; boolean;
var i&#58; integer;
colSpr&#58; TDJXEngineItem;
ColPt&#58; TD3DXVector2;
tmpVec&#58; TD3DXVector2;
ndist,nrot&#58; Double;
vdir,v&#58; TD3DXVector3;
d&#58; single;
begin
result&#58;=false;
nrot&#58;=degtorad&#40;spriteself.nrotatey&#41;;
vDir &#58;= D3DXVector3&#40;cos&#40;nrot&#41;,0,sin&#40;nrot&#41;&#41;;
for i&#58;=0 to Engine.Count-1 do begin
ndist&#58;=distance_dbl&#40;spriteself.x,spriteself.z,0,En gine.Sprites&#91;i&#93;.X,Engine.Sprites&#91;i&#93;.Z,0&#41;;
if &#40;Engine.Sprites&#91;i&#93;<>spriteself&#41;
and &#40;ndist<Innomsprite&#40;spriteself&#41;.nRadius*2+Innomsprite&#40;Engi ne.Sprites&#91;i&#93;&#41;.nRadius*2&#41;
then begin
colSpr&#58;=Engine.Sprites&#91;i&#93;;
//Player hits Monster
if &#40;spriteself.name='spieler'&#41; and &#40;meshplayer&#40;spriteself&#41;.status=schlag1&#41; then begin
if &#40;colspr.name='innom'&#41; and &#40;meshinnom&#40;colspr&#41;.status<>schmerz&#41; then begin
D3DXVec3Subtract&#40;v,D3DXVector3&#40;colspr.X,colspr.Y,c olspr.Z&#41;,D3DXVector3&#40;spriteself.X,spriteself.Y,spr iteself.Z&#41;&#41;;
d&#58;= &#40;v.x * vdir.x&#41; + &#40;v.z * vdir.z&#41;;
d &#58;= ArcCos&#40;d&#41;;
//if &#40;d <pi> 3*pi/4&#41; then begin //player is in the front area
if &#40;d <pi> 2*pi/3&#41; then begin //player is in the front area
meshinnom&#40;colspr&#41;.nanimpos&#58;=0;
meshinnom&#40;colspr&#41;.status&#58;=schmerz;
meshinnom&#40;colspr&#41;.hit&#40;meshplayer&#40;spriteself&#41;.nhitp ower&#41;;
end;
end;
end;
end;
end;
end;


Player is here spriteself, the monster is here the Engine item ColSpr.
I just changed the angle a bit for testing purposes but still the same, it depends on the position not on the rotation right now.

My rotation is a bit strange. When turning around I decrease it, I start at 0, at around -7 I am rotatet one time etc, at -14 two times.

When I render with -nrotatey-pi/2 for Y Rotation it is alright.
So much to my nrotateY. :D

Firle

Firlefanz
16-11-2006, 07:34 AM
Hey, the code format does strange things, the line is

...I cannot post it even with quote etc it always get changed

:evil:
Very angry! Cannot post the code!

When posting it it gets changed eached time, even with quote!



if &#40;d <pi> 2*pi/3&#41; then begin //player is in the front area


Firle

grudzio
16-11-2006, 07:45 AM
I have missed one important thing. The dotProduct(v1,v2) returns an cosine of an angle between vectors v1 and v2 when v1 and v2 are normalised. So before calculating dot product normalize both vectors.

Sorry about that.

About rotations. You have mixed somewhere radians and degrees.
7 is allmost equal to 2*pi = 6.28 - full 360 degree turn.

To get the < and > chars check the "Disable HTML in this post" option.

Firlefanz
16-11-2006, 09:58 AM
Hello grudzio,

thanks a lot again :)

I'll try that when I am home.

Firle

WILL
16-11-2006, 03:13 PM
Hey Eric, try posting with HTML disabled. sometimes the > and < characters are assumed to be tags.

Firlefanz
16-11-2006, 03:27 PM
Yes, grudzio also told me, thanks for the clue.

Just a funny picture, me in 3D, this will be one of our playable heroes:

http://www.ericbehme.de/phpBB2/files/pic_681.jpg

Firle

grudzio
16-11-2006, 06:13 PM
Nice picture :D

Huehnerschaender
16-11-2006, 07:00 PM
Nice underwear :) :)

grudzio, one question:

I also need a function which tells me the position of two points in height (The same as before but for higher and lower on terrain).

Can you help me with it?

Greetings,
Dirk

grudzio
16-11-2006, 09:53 PM
I am not sure if I understand you correctly. Do you want to check if the object A is behind or in front of object B and they are on different heights?

If so, just ignore the Y coordinate (height) or set it equal to zero.



v &#58;= ObjectB.Pos - ObjectA.Pos;
v.y &#58;= 0;
normalize&#40;v&#41;;
dir &#58;= ObjectA.Dir;
dir.y &#58;= 0;
normalize&#40;dir&#41;;
d &#58;= dotProduct&#40;v,dir&#41;;

Huehnerschaender
16-11-2006, 10:02 PM
This tells me if an object is left or right of my viewing direction.
I need to know if it is over or under my view direction. So I cannot ignore y, because y stores height and thats exactly what I need. The angle between my height and the height of object xy.

I use this functionality for a check if object is in my frustrum. Then I decide if I draw it or not. I know how to check if the object is left or right of me and if it is out of my angle of view I don't draw it.

To speed up things now, I also want to know if it is over or under the camera view. That's what I need the function for.

Greetings,
Dirk

grudzio
16-11-2006, 11:03 PM
Now I understand :D .

First check if the object is in front of the camera. (like in my post above).
If the test is passed then:
1. calculate vector between object and camera position:


v &#58;= Object.Pos - Camera.Pos;

2. Project v on the camera direction:


oz &#58;= dotProduct&#40;v,camera.Dir&#41;;

3. Test if object is over or under camera view:


//Pascal does not have tangent function
tan &#58;= sin&#40;CameraViewAngle*0.5&#41; / cos&#40;CameraViewAngle*0.5&#41;;

if &#40;v.y < oz*tan&#41; and &#40;v.y > -oz*tan&#41; then
// object is visible


In 3. I assume that camera is not rotated along z axis.

Just in case here is a link to a good general Frustum Culling tutorial
http://www.lighthouse3d.com/opengl/viewfrustum/index.php?intro.

Huehnerschaender
17-11-2006, 01:17 AM
Thank you very much. I will try it tomorrow....

Firlefanz
17-11-2006, 07:35 AM
Nice underwear lol :D

The guy gets a breast plate armor and trousers, both can be changed, the boots are directly on the mesh and stay. There will be 4 different chars with own textures, special moves and abilities (correct word?).

So one last Question (I hope):
This helped me a lot.
Now it seems to work if I have the monster exactly in front or, and this is the problem exactly in the back :roll: :wink:



function TForm1.fightcollide&#40;spriteself&#58; TDJXEngineItem&#41;&#58; boolean;
var i&#58; integer;
colSpr&#58; TDJXEngineItem;
ColPt&#58; TD3DXVector2;
tmpVec&#58; TD3DXVector2;
ndist,nrot&#58; Double;
vdir,v&#58; TD3DXVector3;
d&#58; single;
begin
result&#58;=false;
//nrot&#58;=degtorad&#40;spriteself.nrotatey&#41;;
nrot&#58;=spriteself.nrotatey;
vDir &#58;= D3DXVector3&#40;cos&#40;nrot&#41;,0,sin&#40;nrot&#41;&#41;;
for i&#58;=0 to Engine.Count-1 do begin
ndist&#58;=distance_dbl&#40;spriteself.x,spriteself.z,0,En gine.Sprites&#91;i&#93;.X,Engine.Sprites&#91;i&#93;.Z,0&#41;;
if &#40;Engine.Sprites&#91;i&#93;<>spriteself&#41;
and &#40;ndist<Innomsprite&#40;spriteself&#41;.nRadius*2+Innomsprite&#40;Engi ne.Sprites&#91;i&#93;&#41;.nRadius*2&#41;
then begin
colSpr&#58;=Engine.Sprites&#91;i&#93;;
//Player hits Monster
if &#40;spriteself.name='spieler'&#41; and &#40;meshplayer&#40;spriteself&#41;.status=schlag1&#41; then begin
if &#40;colspr.name='innom'&#41; and &#40;meshinnom&#40;colspr&#41;.status<>schmerz&#41; then begin
D3DXVec3Subtract&#40;v,D3DXVector3&#40;colspr.X,colspr.Y,c olspr.Z&#41;,D3DXVector3&#40;spriteself.X,spriteself.Y,spr iteself.Z&#41;&#41;;
D3DXVec3Normalize&#40;vdir, vdir &#41;;
D3DXVec3Normalize&#40;v,v&#41;;
d&#58;= &#40;v.x * vdir.x&#41; + &#40;v.z * vdir.z&#41;;
d &#58;= ArcCos&#40;d&#41;;
if &#40;d < pi/5&#41; or &#40;d > 5*pi/6&#41; then begin //player is in the front area
meshinnom&#40;colspr&#41;.nanimpos&#58;=0;
meshinnom&#40;colspr&#41;.status&#58;=schmerz;
meshinnom&#40;colspr&#41;.hit&#40;meshplayer&#40;spriteself&#41;.nhitp ower&#41;;
end;
end;
end;
end;
end;
end;


Disabling html....done :twisted:

Thanks a lot guys, this helps me a lot!

Firle

Huehnerschaender
17-11-2006, 08:23 AM
you "forgot" to check if d is greater than 0. only then the monster is in front of you. if d is negative then it is behind you. look at the first sample from grudizio.

greetings,
dirk

Firlefanz
17-11-2006, 08:33 AM
Now I got the idea.

This will be very cool for straight hit and 'swinging' hit lol.

Perfect, thanks! (need to write some comments now)

This helped me a lot, great stuff, thank you both :salute:

grudzio
17-11-2006, 11:51 PM
Huehnerschaender:

The third step - checking if object is below or above camera wiev should be changed to this



// CameraXAngle is the rotation angle along X axis
oy &#58;= v.y*cos&#40;CameraXAngle&#41;;
//Pascal does not have tangent function
tan &#58;= sin&#40;CameraViewAngle*0.5&#41; / cos&#40;CameraViewAngle*0.5&#41;;

if &#40;oy < oz*tan&#41; and &#40;oy > -oz*tan&#41; then
// object is visible

Now it works if camera looks up or down.

Huehnerschaender
18-11-2006, 07:43 AM
Thanks grudzio.

I will try it out as soon as possible.

Btw, my version of pascal (BDS 2005/2006) has a tan function included in math.pas.

Greetings,
Dirk