Camera behind a moving object (tomb raider style)

Blitz3D Forums/Blitz3D Programming/Camera behind a moving object (tomb raider style)

GC-Martijn(Posted 2003) [#1]


H!

I saw today that making a good camera isn't easy :S
I try some things with pivots but i don't see what i'm doing really.

I tryed to use/edit the mario64camera script but there is too much crap in it.

How can I make a good camera that is behind the player and stays there , and when the player goes left/right then the camera yaw (like the image) behind the player.

Please a super simple code that I can use.
Thank You very Much
GC-Martijn


(tu) sinu(Posted 2003) [#2]
i'd explain it to you but if you do a search for a ninja demo by jhocking in either user projects or user creations he has an example with source although it's not using pivots like you should it still is good for a basic example.


jhocking(Posted 2003) [#3]
It's in User Projects I think. Just scan down the list of threads for one titled "Ninja test etc." It's very basic 3rd person camera control (it doesn't avoid scenery, etc.) but it's a good starting point I should say.

I've been working on something using AlignToVector with a low transition value to smooth out the camera movement. It looks pretty good and it's something you should look into. Just generally, AlignToVector is a VERY useful command when dealing with 3rd person camera control; note that my demo mentioned above uses that command in a crucial spot for controlling the character.


sswift(Posted 2003) [#4]
If your camera's not moving using Quaternions it should.

And if it's not moving with a constant angular velocity, it should. After all, you don't want your game running at different speeds on different systems.

When you use stuff like aligntovector, you need to be careful, unless of course you're using render tweening to avoid having to worry about things moving at different speeds on different ssytems. But if you're not, then you'd have to calculate the difference in the angles and then use the time passed in a frame to move at a constant angular rate. Though you can smooth things out by adding a cosine to your angular rate so that things slow down as they approach the end values.

It's not an easy task, but it's not too hard either. I'd first go to the code archives, and find the Quaternion library that is there. Then stick that in your code, put in your camera's orientation, and the destination orienatation into the euler to quaternion function, and then interpolate to a point between the two quaternions using the quaternion SLERP function, and then convert back to euler rotations so you can adjust the camera.

You can easily make sure you move at a constant angular rate by doing a dot product between the two orientations, and then you'll know how much your two orientations point in different directions. Then when you know you're 180 degrees apart, and you want to move 90 degrees per second, you can calculate the time the frame took, multiply that by 90, and then divide 180 by that, which will give you a number which should be between 0 and 1 which is what you input into the SLERP function to tell it at what point you want the interpolation to be between the two orientations.

Btw, Tomb Raider uses Quaternions for it's camera movements. It uses them because you get the most realistic and smooth interpolation with them.


(tu) sinu(Posted 2003) [#5]
@Jhocking the aligntovector command is a bit problematic though with the way it slows down the closer it gets to it's destination. See the post in Advanced 3D.


sswift(Posted 2003) [#6]
You can correct that though as I said by figuring out the angle between the two objects, and then using that to calculate exactly where you want to put the interpolation value for aligntovector.

Actually, I think alignto vecotr uses quaternions.

So, what you would do is do a dot product to calculate the difference between the two orientations. This will be a value between -1 and 1. I forget what the values mean at the moment, but one of them means the two orientations are the same, and one means they are pointing 180 degrees away from eachother.

Anyhow once you know that, you know if the orienations are say, 10 degress off from one another. And when you know that, you can determine just what to put in aligntovector to either move it a little way towards the target orientation, a lot of the way, or all of the way.

Thus you won't have a problem with it slowing down. But you do want it to slow down as it reaches it's detination. But I think you want a cosine based slowdown, not something like an exponential falloff which you'll get with jhocking's method where the orientations will get closer and closer together but never match up.


(tu) sinu(Posted 2003) [#7]
this kind of stuff is over my head though, my maths sucks and trying to figure it out, like i've been doing for a while is so very hard.


sswift(Posted 2003) [#8]
Hey I failed math in high school, and never went to college. I don't even know calculus. :-) So if I can learn this stuff, you can too. You just need to find sources which lay the stuff out in a way that you can understand and then make use of it and you'll learn it over time. I didn't know what the hell a dot product was two years ago, but once I learned, they're really easy to understand, at least, well enough to make use of them in many situations like this. I don't really have an in depth understanding of how the math actually works, but I know what the end result is when you plug in two vectors, and have written the equation down.


This is a dot product:

AdotB# = ax#*bx# + ay#*by# + az#*bz#

You can do a 2D version of it by simply omitting the "+ az#*bz#" from the end.

A and B are vectors. A vector is like a line in space, with one end at 0,0,0, and the other at Vxyz.

If you imagine that your object has a vector that points down the Z axis, and you rotate your object, the end of the vector remains on a sphere as the object rotates. The length of the vector defines the size of that sphere.

Vectors with a length of 1 are called normals. You use normals a lot.

When you do a dot product, the vectors need to be normalized. They need to have a length of 1. Otherwise you won't get a result between -1 and 1.

Now that you know that, this is how to convert an object's orientation into a normal vector in the global coordinate system.

What we're doing here is specifying that we have a vector, ie, normal, ie, line, with a length of 1, pointing down positive Z. Basically just imagine a point at 0,0,1 in object space. That's the end of our normal.

This function converts that point from object space, to world space. In other words, this point is on the nose of our car, so it's always at 0,0,1 in object space, but when we rotate the car so it's nose points up, in WORLD/GLOBAL space, the point will be at 0,1,0, or pointing up the Y axis of the world.

Note that this function does not care where the object is POSITIONED in space, so you can have your obejct anywhere and get the correct result.

TFormNormal Entity, 0, 0, 1
Nx# = TFormedX()
Ny# = TFormedX()
Nz# = TFormedX()

Now we have a normal in world space that tells us which way pur object points.

Do this for both objects.

Then, do a dot product on the two vectors.

As I stated before this is the dot product:
AdotB# = ax#*bx# + ay#*by# + az#*bz#

So you just go:

Dp# = N1x#+N2x# + N1y#*N2y# + N1z#*N2z#


Now that you have Dp#, you know the angle between the two orienations.

If Dp# is 1, then the two vectors point in the same direction. Ie, the two objects point in the same direction. Ie, the angle between the vectors is 0.

If Dp# is 0, then the two vectors are perpendicular. Ie, the angle between the two vectors is 90 degrees. Just like between the X axis and the Y axis. or the X axis and the Z axis.

If Dp# is -1, them the two vectors point in opposite directions. 180 degrees apart. This is the most which two vectors can point away from eachother, whether we do this in 2D, or 3D.

Now that we know how much the two objects point away from eachother, we know how far apart in angle they are seperated.

And because we know that, we know how much aligntovector will rotate the object if we pass it a tween value of 1, or a tween value of 0.5.

Let's say that we want to rotate the objects at 90 degrees per second.

RSpeed# = 90.0

Let's say that the last frame took 200 miliseconds to render.

Time_Delta = 200

Now let's convert that to seconds:

Time_Delta_Sec# = Float(Time_Delta)/1000.0

Now we know that 0.2 seconds elapsed the last frame.
That btw is equivalent to 5 frames per second.

Now that we have the amount of time passed, we know how much we need to move out objects this frame, if we have specified all our speeds in X per second. Meters, degrees, etc.

And we have. We have said out speed is 90 degrees per second.

So, how much do we need to rotate our object this frame?

DeltaAngle# = RSpeed# * Time_Delta_Sec#

Okay, so we now know that we need to move our object 18 degrees this frame.

Now remember our dot product? Dp#?

LEt us say that our objects are seperated by 90 degrees. That means Dp# will be 0.

We know that we need to move 18 degrees this frame, because DeltaAngle# is 18.

And we know that if we pass a tween of 1.0 to AlignToVector, we will move the distance defined by Dp#. Ie, 90 degrees, becuase the objects will rotate to the same orientation instantly with a setting of 1. And we know 0 doesn't rotate them at all.

So, we need the value for Tween#.

Dp# is 0. That means 90 degrees. We can convert DP to degrees like so:

DpAngle# = (-(Dp# - 1.0) / 2.0) * 180.0

Ie, subtract one from Dp# which brings the max down to 0, and the min down to -2. That means now that when the objects point at eachother they're gonna be 0, and when pointing away they will be -2. But we want 1 when pointing away, and 0 when pointing the same. So we invert that to get 0 pointing in the same direction, and 2 pointing away from eachter. Then we divide by 2, so now we have 1.0 when pointing away from eachother, and 0 when pointing the same direction. And finally, we multiply this by 180, to get a value between 0 and 180. And there's our angle.

So now we have DpAngle#, which is 90. We're on the home stretch now!

Okay, so we now know that if we set tween on aligntovector to 1.0, then the objects will rotate 90 degrees.

Okay!

But we only want to move 18 degrees this frame. That's what DeltaAngle# was calculated to be!

So, all we have to do is divide 18 by 90, and we know where 18 is between 0 and 90. Ie, where between 0 and 1 we want to be!

So...

Tween# = DeltaAngle# / DpAngle#

And thus, we know that we need to set the tween on aligntovector to 0.2 this frame to rotate the entity by 18 degrees, which is a speed of 90 degrees per second, because this frame only took 1/20th of a second to run.

Phew!

Now I dare you tell me you still can't implement this! :-)


(tu) sinu(Posted 2003) [#9]
thanks mate, good read and just getting to understand it, will try and learn it more and then try it myself.

ps why didn't you post this in the post in advanced 3d :)
okay if i do a copy and paste there?


sswift(Posted 2003) [#10]
Go ahead!


Rottbott(Posted 2003) [#11]
Here's what I use, it's very simple and looks nice:

; Change -100 to whatever distance behind your character you want
; Change -30 to whatever distance above your character you want
; Change Player to the name of your player character
; Also change 'Cam' to the name of your Camera
TFormPoint 0, -30, -100, Player, 0
cx# = CurveValue(EntityX#(Cam), TFormedX#(), 15.0)
cy# = CurveValue(EntityY#(Cam), TFormedY#(), 15.0)
cz# = CurveValue(EntityZ#(Cam), TFormedZ#(), 15.0)
PositionEntity Cam, cx#, cy#, cz#
PointEntity Cam, Player

; Put this function at the bottom of your code
Function CurveValue(Current#, Destination#, Speed#)
  Return Current# + ((Destination# - Current#) / Speed#)
End Function


Here's all that in one general purpose function you can call to make any camera follow any object at any distance:

Function CameraFollow(Cam, EN, YD# = 0, ZD# = 0, Speed# = 15.0)
  TFormPoint 0, -YD#, -ZD#, EN, 0
  cx# = cx# + ((cx# - TFormedX#()) / Speed#)
  cy# = cy# + ((cy# - TFormedY#()) / Speed#)
  cz# = cz# + ((cz# - TFormedZ#()) / Speed#)
  PositionEntity Cam, cx#, cy#, cz#
  PointEntity Cam, EN
End Function


So to use it just stick that function at the bottom of your code and do something like:

CameraFollow(MyCam, MyPlayer, -30, -100)


Just set up sliding collision between the camera and the level so the camera doesn't go through walls.


jfk EO-11110(Posted 2003) [#12]
I would do something like this:
all floats
x=entityx(player)+sin(entityyaw(player)+180)*10
y=entityy(player)+4
z=entityz(player)+cos(entityyaw(player)+180)*10
xc=entityx(camera)
yc=entityy(camera)
zc=entityz(camera)
xc=(xc+(x-xc))/5
yc=(yc+(y-yc))/5
zc=(zc+(z-zc))/5

positionentity camera,xc,yc,zc
pointentity camera,player
while (camerapick(graphicswidth()/2,graphicsheight()/2)<> player) and (entitydistance(camera,player)>2)
 moveentity camera,0,0,.5
 positionentity camera,entityx(camera),entityy(player)+4,entityz(camera)
 pointentity camera,player
wend
...



GC-Martijn(Posted 2003) [#13]
wow lot of text , thanks that you people want to help me with this problem.

I think it must work now :)


Rottbott(Posted 2003) [#14]
jfk, that's more or less exactly what my code does, except for this curious bit:

while (camerapick(graphicswidth()/2,graphicsheight()/2)<> player) and (entitydistance(camera,player)>2)
moveentity camera,0,0,.5
positionentity camera,entityx(camera),entityy(player)+4,entityz(camera)
pointentity camera,player
wend

What is that meant to do? Is it to stop the camera being behind a wall? If so it'd only work with the EntityPickMode of the level set to something. But surely it's more slow than just using Sphere->Polygon collision, since you could end up doing hundreds of CameraPick()s in one frame. Or am I missing something?


sswift(Posted 2003) [#15]
"What is that meant to do? Is it to stop the camera being behind a wall? If so it'd only work with the EntityPickMode of the level set to something. But surely it's more slow than just using Sphere->Polygon collision, since you could end up doing hundreds of CameraPick()s in one frame. Or am I missing something?"


Hmm... if this is doing what I think it's doing, then this is pretty clever actually.

Just because your camera can collide with the level, doesn't mean that it won't get stuck behind something.

This appears to check if there is anything between the center of the camera view and the player, and if so, it keeps moving the camera forward, and then moves it up or down so that it is exactly 4 units above the player, and then points it at the player again.

Hm... Well, maybe it's not the best solution, but I guess it might move the camera out from behind SOME obstacles. For example, if there was a pillar between the player and the camera, this would push up against the pillar until it slides around it. But if the camera was caught behind a wall, it might not succeed in getting around the wall by moving forward, it might just end up wedging itself into a corner.

But the idea of using camerapick to dtermine if something is between the plauer and the camera is pretty clever.

I think this algorithm could be improved by sampling four points to the left right, top and bottom of the camera and seeing if any of those can see the player, and if so, the camera will move in that direction. However, you don't want the camera to avoid going behind trees completely. So if the camera is moving, then you would want to sample an additional time in the direction of motion to determine if the camera will only be blocked temporarily or not. Or maybe if you know where the destination of the camera will be you could check to see if that is blocked before you move there. Hm...

Making a good camera is a lot of hard work, I don't envy someone trying to implemnet one. :-)


Rottbott(Posted 2003) [#16]
Actually sswift, how about if the camera didn't have any collision set? Surely the camera would then go through the pillar, so nothing would ever get between the camera and the player providing you set a PickMode for it. It'd be a bit of a "sudden movement" though when the camera jumped in front of an obstacle. Either way, it's a nice idea but I can't help thinking it could be slow. Sampling four points would be worse still! Because it takes the samples multiple times per frame depending on how much there is between player and camera. Depends on your level complexity I suppose.

Perhaps there is a way to work out a non-blocked destination for the camera before moving it using a method something like this, and then work out a path to that destination that doesn't go through any objects, possibly with LinePick and/or EntityVisible. Then you just use linear interpolation to move the camera along that path a certain amount each frame. (Although the destination and therefore the path could change from frame to frame it'd still look ok since it wouldn't be moving the whole lot in one go).

Another good thing you can do I saw, I think in Neverwinter Nights or maybe Dungeon Siege or something, any objects that got between the camera and the player were set to alpha 0.5 so you could see the player through them without the camera having to move anywhere. Trouble is it only works on small, simply scenery objects such as trees, and not big complex ones like a whole level, for obvious reasons. Good for outdoors levels though.


sswift(Posted 2003) [#17]
It would look bad if the camera just clipped through stuff in the world, and one optimization of not clearing the backfuffer when you know everything will be drawn over every frame can make this effect especially ugly.


Rottbott(Posted 2003) [#18]
No because it would never actually end up inside something since the picking bit is looped until there's nothing in the way anymore. Tweening would screw it up badly though.


jfk EO-11110(Posted 2003) [#19]
Yes, it was meant with a camera without Collision. But of course, we cannot expect that a snippet like this is better than lara crofts camera. They also fade a mesh to transparency if the camera is inside it - but this would only make sense if culling is off. It depends on the kind of Geometry. If the camera is inside a Pillar then it's ok as long as you don't see the ground, especially when the Pillars bottom is looking like a hole in the Ground.


ChrML(Posted 2003) [#20]
This camera doesn't follow it. I tried it. It rotates perfectly, and rotates perfectly, but even if I set the distance at -1, it doesn't follow how matter how far I drive.


jfk EO-11110(Posted 2003) [#21]
Cmon!

Did you make everything Floating Point? ok...

; this will always be 10 units behind the player!
x=entityx(player)+sin(entityyaw(player)+180)*10
y=entityy(player)+4
z=entityz(player)+cos(entityyaw(player)+180)*10

;now we smoothly move the camera to that position
xc=entityx(camera)
yc=entityy(camera)
zc=entityz(camera)
xc=(xc+(x-xc))/5
yc=(yc+(y-yc))/5
zc=(zc+(z-zc))/5

well, ok, maybe I made a mistake and you have to swap the
xc=(xc+(x-xc))/5
yc=(yc+(y-yc))/5
zc=(zc+(z-zc))/5
to
xc=(xc+(xc-x))/5
yc=(yc+(yc-y))/5
zc=(zc+(zc-z))/5
or maybe
xc=(xc-(x-xc))/5
yc=(yc-(y-yc))/5
zc=(zc-(z-zc))/5

or something like that.

But all in all the camera will shurely follow the player, this isn't the problem. The Problem we tried to solve here was to prevent the camera get stuck behind objects and in corners. The examplke above was Pseudo Code. This means it only illustrates a solution, but will not run as it is. So when there is a bug then it's probably in your Code. PLease post your Code.


Rob Pearmain(Posted 2003) [#22]
I tryed to use/edit the mario64camera script but there is too much crap in it.



As the author of the mario64camera sample, sorry to spend so much time on it and get so little apprciation :(


jfk EO-11110(Posted 2003) [#23]
Cmon - the Demo is very beloved. It might be a bit hard to extract parts like the camera if one isn't very familar with types and so on. But the Demo is great, no question!


Rob Pearmain(Posted 2003) [#24]
:), it's ok, my pride has recovered now