PDA

View Full Version : Direction between 2 Vectors

Colin
14-11-2012, 11:13 AM
OK so i have 2 3d vector's

the first is the camera and the second is an object i want to look at, my camera system using the mouse relative position so rot.x = rad offset from 0.

So i've been trying to come up with a function to calculate the Radians from vec1 to vec2 and I just can't seem to get anything to work, can anyone save me from this task as my head is about to explode.

I have the xz rotation working fine using: rV.x := ArcTan2( targ.z - posZ, targ.x - posX ); - altho sometimes it does a full rotation reversal

but i need the y axis also to be correct, looking up or down at the object once it is facing it

User137
15-11-2012, 12:49 AM
I had this function in my old codes:

function Angle3D(x1,y1,z1,x2,y2,z2: double): double;
var a: double;
begin
a:=(x1*x2+y1*y2+z1*z2) /
( sqrt(x1*x1+y1*y1+z1*z1) * sqrt(x2*x2+y2*y2+z2*z2) );
//if a>1 then a:=1 else if a<-1 then a:=-1;
result:=abs(arccos(a));
end;
It looks slightly unfinished, and i was surprised i didn't clean that up for nxPascal yet. Atleast what i understand, result should always be >= 0. Sqrt insides seem fine, they are always on positive side or 0 so don't need any temp variables either. Not sure what i was thinking with the if-clause.

LP
15-11-2012, 01:41 AM
I had this function in my old codes:...
Your code is perfectly fine. You don't need ABS in the last line though as ArcCos (http://docwiki.embarcadero.com/Libraries/XE3/en/System.Math.ArcCos) returns values from 0 to Pi. Values inside SQRT will never be negative as they are elevated to square. Detailed math is explained here (http://www.wikihow.com/Find-the-Angle-Between-Two-Vectors). As you can see, your code does exactly that - the first part of code calculates dot product and second part divides it by multiplication of two vector lengths. P.S. if a>1 check was probably due to vectors not being of unitary length, which you should normalize instead.

Dan
15-11-2012, 03:36 AM
I dont think the man is looking for an angle between two vectors. He is asking for the following: given two positions (origin and target) in 3d space he needs the Yaw and Pitch values which will orient the camera looking from the origin at the target. Colin, what I think you are looking for is simply: rV.y := ArcSin(Dir.y); where Dir is a normalized direction vector (Dir := normlize(targ.x - posX, targ.y - posY, targ.z - posZ))

Colin
15-11-2012, 08:04 AM
Dan, exactly the answer I needed, this is not the first time your post/replies have saved me from myself, I can't thank you enough but thank you very much :)

So my code so far (which works)

//
// sets the look at target of the camera
//
procedure TCameraController.setTarget(const targ: TD3DXMatrix);
var
pos, rV: TD3DXVector3;
diff: TD3DXVector3;
begin
pos := D3DXVector3(targ._41, targ._42, targ._43);

D3DXVec3Subtract(diff, pos, position);
D3DXVec3Normalize(diff, diff);

rV := D3DXVector3(
arctan2(diff.z, diff.x),
arcsin(diff.y),
diff.z
);

D3DXVec3Lerp(rotation, rV, rotation, 0.1);
end;

my only problem now is using the DX function MatrixLookAt for matView seems to have a rotation limit on the yaxis.

LP
15-11-2012, 01:29 PM
my only problem now is using the DX function MatrixLookAt for matView seems to have a rotation limit on the yaxis.
It could be due to gimbal lock (http://en.wikipedia.org/wiki/Gimbal_lock).

Colin
15-11-2012, 02:10 PM
I was reading about this a few hours ago and I think you may be right, so I think I will add in some quaternion routines and see if I can solve it. Thanks

User137
15-11-2012, 05:21 PM
Glad you got it solved, although not by my tip :D This is how the function ended up to:

function Angle(v1, v2: TVector): single;
begin
norm(v1.x, v1.y, v1.z);
norm(v2.x, v2.y, v2.z);
result:=arccos(dot(v1, v2));
end;
Didn't test yet, but i realized that normalized vectors, when their length is multiplied is 1. That leaves it with just dot product.

LP
15-11-2012, 07:51 PM
Didn't test yet, but i realized that normalized vectors, when their length is multiplied is 1. That leaves it with just dot product.
Actually, dot product of two 3D vectors is an indirect measure of angle between them. So if you need to test some specific cases, you may omit the arccos altogether. ;)

Dan
16-11-2012, 03:55 AM
rV := D3DXVector3(
arctan2(diff.z, diff.x),
arcsin(diff.y),
diff.z //why do you need this?
);

D3DXVec3Lerp(rotation, rV, rotation, 0.1); // interpolating angles is almost never a good idea, I would bet that whatever trouble you are having is probably caused by this. gimbal lock is a fairly rare phenomenon in 3d graphics and doesn't happen consistently, especially not in your case.

what I suggest you do if you want to achieve smooth camera tranitions is interpolate direction vectors. or even better - spherically interpolate rotation quaternions.

Colin
16-11-2012, 05:45 PM
That sounds good, but I seem to be having a lot of problems trying to incorporate this into my camera class, could you give some example? I appreciate the help. Thanks

I have made some changes and changed the lerp call to some linear interpolation which is working very smoothly and looks fantastic, i will post the code tomorrow when i'm able.

Dan
17-11-2012, 09:05 AM
well first of all remove the interpolation and make sure everything is working like this:
rotation := rV;

Colin
17-11-2012, 09:23 AM
Hello Dan, Yes it does work fine, expect still the limitation on the Y axis,

However after a bit testing i see the problem is on the xz rotation it takes 2*PI for full rotation, on the Y Axis, it would technically take 4*PI, so instead i will do a basic test for flipping the camera, and changing the right and up vectors so the view will then be upside down.

Colin
17-11-2012, 12:37 PM
OK i have my camera system working exactly how i want it except for 1 problem, I have uploaded test build, press N to spawn a ball, then if you press down and to the left so the ball rolls around the camera will at 1 point spin in the opposite direction back to the ball.

New code:

//
// sets the look at target of the camera
//
procedure TCameraController.setTarget(const targ: TD3DXMatrix);
const
tween = 0.01;
var
pos, rV: TD3DXVector3;
diff: TD3DXVector3;
begin
pos := D3DXVector3(targ._41, targ._42+5, targ._43); // + 5 look slightly above the object

D3DXVec3Subtract(diff, pos, position);
D3DXVec3Normalize(diff, diff);

rV := D3DXVector3(
arctan2(diff.z, diff.x),
arcsin(diff.y),
0
);

rotation.x := (rV.x * tween) + rotation.x * (1 - tween);
rotation.y := (rV.y * tween) + rotation.y * (1 - tween);
rotation.z := (rV.z * tween) + rotation.z * (1 - tween);
end;

//
// sets the position of the camera somewhere behind the object, either facing or by velocity
//
const
Dist = 20;
MaxDist = 50;
DefHeight = 15;
var
tweenSpeed: TD3DXVector3;
absdist: TD3DXVector3;

pos: TD3DXVector3;
gPoint: TD3DXVector3;
begin
pos := D3DXVector3(m._41, m._42, m._43);

gPoint.y := m._42 + DefHeight;
end else begin
gPoint.y := m._42 + DefHeight;
end;

absdist := D3DXVector3(abs(pos.x-position.x),
abs(pos.y-position.y),
abs(pos.z-position.z)
);

if absdist.x > MaxDist then
tweenSpeed.x := 0.005 * (absdist.x - (MaxDist-1))
else
tweenSpeed.x := 0.005;

tweenSpeed.y := 0.005;

if absdist.z > MaxDist then
tweenSpeed.z := 0.005 * (absdist.z - (MaxDist-1))
else
tweenSpeed.z := 0.005;

position.x := (gPoint.x * tweenSpeed.x) + position.x * (1 - tweenSpeed.x);
position.y := (gPoint.y * tweenSpeed.y) + position.y * (1 - tweenSpeed.y);
position.z := (gPoint.z * tweenSpeed.z) + position.z * (1 - tweenSpeed.z);
end;

else everything is nice, camera moves very smoothly for the most part, just this problem that is a pain... :)

Any ideas?

Thanks.

Dan
17-11-2012, 01:29 PM
rotation.x := (rV.x * tween) + rotation.x * (1 - tween);
rotation.y := (rV.y * tween) + rotation.y * (1 - tween);
rotation.z := (rV.z * tween) + rotation.z * (1 - tween);

you are again interpolating the angles, only doing it manually this time instead of using a d3dx function.
ok I suppose I have to explain why that is bad. as (I assume) you know arctan2 gives you an angle between
-Pi and Pi, and you store these angles in your rotation variable. every time you adjust your camera you get a
new angle and you interpolate between the old rotation variable and the new rV variable. the values that you
interpolate are angles (-Pi..Pi). so here's an example of the major issue with angles: what if the old angle is
-0.9 * Pi and the new angle is 0.9 * Pi? the actual angular difference here is only 0.2 * Pi but if you
numerically interpolate between these angles by a step size of 0.5 (for example) you will end up exactly at 0!
while you expect to be at Pi, so your camera will turn exactly in the opposite direction.

as I have said before, you should instead interpolate the direction vectors of the camera. the easiest way to
do that is to store the current direction vector and the new direction vector, interpolate between them and
extract your angles from the result. but since I see that you are trying to only deal with the angles you should
simply convert the rotation variable angles into a vector, interpolate it with the diff vector then extract the
angles from the result.

ok at this point if you need more help I can give you the code that will simply work. but that would be no fun would it ;)

Colin
17-11-2012, 01:50 PM
I understand the issue yes, this is the problem that i have been mainly trying to solve.

What do you mean by "convert the rotation variable angles into a vector" ? the rotation variable is already a vector ?

Dan
17-11-2012, 02:50 PM
the rotation variable is already a vector ?
no it's not=) you just store the angles it in a vector format that's it. what you need to do is convert these angles into a 3d direction vector.
ok, here's how you do it:

var
Dir: TD3DXVector3;
sh, ch, sv, cv: Single;
...
SinCos(rotation.x, sh, ch);
SinCos(rotation.y, sv, cv);
Dir.x := ch * cv; Dir.y := sv; Dir.z := sh * cv;

then you find the "diff" vector just as you do now.
interpolate between the Dir and diff vector (Dir is the old camera direction, diff is the new camera direction).
so you might want to do something like this:

Dir.x := Dir.x + (diff.x - Dir.x) * tween;
Dir.y := Dir.y + (diff.y - Dir.y) * tween;
Dir.z := Dir.z + (diff.z - Dir.z) * tween;
D3DXVec3Normalize(Dir, Dir);

and finally convert the Dir vector to rotation angles. I don't know why you are using a 3d vector for rotation
angles, but here it is:

rotation := D3DXVector3(
arctan2(Dir.z, Dir.x),
arcsin(Dir.y),
0
);

overall this approach should work fine. however, there is still a more consistent and reliable way of spherically interpolating quaternions instead of vectors,
but that is a tale for another time.

Colin
17-11-2012, 05:46 PM
Hello Dan,

Thanks for the code, and for the explanation, it has helped me to understand the idea behind the method. I have updated the procedure with your code and now it works exactly as it should.

//
// sets the look at target of the camera
//
procedure TCameraController.setTarget(const targ: TD3DXMatrix);
const
tween = 0.01;
var
pos, diff, Dir: TD3DXVector3;
sh, ch, sv, cv: Extended;
begin
SinCos(rotation.x, sh, ch);
SinCos(rotation.y, sv, cv);
Dir := D3DXVector3(ch * cv, sv, sh * cv);

pos := D3DXVector3(targ._41, targ._42+1, targ._43); // + 1 look slightly above the object

D3DXVec3Subtract(diff, pos, position);
D3DXVec3Normalize(diff, diff);

Dir := D3DXVector3(
Dir.x + (diff.x - Dir.x) * tween,
Dir.y + (diff.y - Dir.y) * tween,
Dir.z + (diff.z - Dir.z) * tween
);
D3DXVec3Normalize(Dir, Dir);

rotation := D3DXVector3(
arctan2(Dir.z, Dir.x),
arcsin(Dir.y),
0
);
end;

Once I finish adding an Actor class, then maybe i will come back to working on the camera class to look into the quaternion alternatives.

Thanks again