Fix for jerky camera orbitmode
by Joel Baxter · in Torque Game Engine · 04/01/2002 (10:56 am) · 22 replies
You've probably noticed that when you die and go into orbit mode around the corpse, the view is very jerky. This is because the position as well as the interpolation factors for the camera are being managed incorrectly, and the camera is setting its transform for every client-side interpolation rather than setting its render transform.
There turned out to be a lot of necessary interconnected changes to make, so in the end I just rewrote most of the relevant camera code. While I was at it, I also implemented the other two camera modes, although I haven't tested them. If anyone wants to examine this code, I've uploaded it to my website. Rather than try to describe all the changes here in the forum, I just commented the files, both with "normal" comments and also with "XXX" comments to mark unusual or dubious spots. Some of the more subtle problems with managing interpolation are also discussed in this thread: www.garagegames.com/index.php?sec=mg&mod=forums&page=result.thread&qt=3276 (that's talking about the Player code specifically, but the ideas are the same).
Now, when in orbit mode around a corpse, you may notice that the view hiccups slightly every now and then. It used to do this in the old code as well; it was just hard to see because of the violently shaking POV. :-) The reason for this hiccup AFAICT is not because the camera code is doing anything bad, but because the corpse object that it is attached to is hiccuping. This very well could be related to the stuff in that thread linked above; or, there's an outside chance it could be some problem with our specific player model. Either way, not a camera problem per se.
Also, before doing these changes I thought that the free-flying camera was not as smooth as it could be. Still feels the same way to me after the changes. Something to investigate later I guess.
There turned out to be a lot of necessary interconnected changes to make, so in the end I just rewrote most of the relevant camera code. While I was at it, I also implemented the other two camera modes, although I haven't tested them. If anyone wants to examine this code, I've uploaded it to my website. Rather than try to describe all the changes here in the forum, I just commented the files, both with "normal" comments and also with "XXX" comments to mark unusual or dubious spots. Some of the more subtle problems with managing interpolation are also discussed in this thread: www.garagegames.com/index.php?sec=mg&mod=forums&page=result.thread&qt=3276 (that's talking about the Player code specifically, but the ideas are the same).
Now, when in orbit mode around a corpse, you may notice that the view hiccups slightly every now and then. It used to do this in the old code as well; it was just hard to see because of the violently shaking POV. :-) The reason for this hiccup AFAICT is not because the camera code is doing anything bad, but because the corpse object that it is attached to is hiccuping. This very well could be related to the stuff in that thread linked above; or, there's an outside chance it could be some problem with our specific player model. Either way, not a camera problem per se.
Also, before doing these changes I thought that the free-flying camera was not as smooth as it could be. Still feels the same way to me after the changes. Something to investigate later I guess.
#2
04/26/2002 (3:06 pm)
Whoa. This post is to show support. Where is everybody? Sounds like you are making good headway. Keep it up...
#3
04/26/2002 (3:53 pm)
Thanks for the backing. :-) Back from my errand now, done some more looking... appears to be mLastCameraQuery.cameraMatrix in GuiTSCtrl that I should chase now.
#4
The camera is orbiting some focus object; the center of its orbit, its focus point, is determined by calculating the center of the focus object's bounding box.
In the example I'm testing, the camera is orbiting a player. On the server, the z value of the player's origin point (let's just look at one number to keep things simple) is 60.0. Because of compression, on the client the z value is 59.9947.
Our player models have their origin fairly close to the center of the bounding box, so when the bounding-box-center (focus point) z value is calculated on the server, the result is 59.5314. On the client it is 59.5262.
However when the server sends an update for the camera, it sends the camera's focus point, which is 59.5314, compressed and received at the client as 59.5347, which results in a noticeably different render than when the client camera is at 59.5262.
Basically the moral of this story is that when lossy compression is involved in the transmission, sending a number and then doing a calculation on the received number gets you a different answer than doing the calculation and then sending the calculation result.
I'm pretty sure the original camera code handled this part correctly and I boffed it up in the rewrite. Will fix this evening.
04/26/2002 (4:56 pm)
OK, here's the deal.The camera is orbiting some focus object; the center of its orbit, its focus point, is determined by calculating the center of the focus object's bounding box.
In the example I'm testing, the camera is orbiting a player. On the server, the z value of the player's origin point (let's just look at one number to keep things simple) is 60.0. Because of compression, on the client the z value is 59.9947.
Our player models have their origin fairly close to the center of the bounding box, so when the bounding-box-center (focus point) z value is calculated on the server, the result is 59.5314. On the client it is 59.5262.
However when the server sends an update for the camera, it sends the camera's focus point, which is 59.5314, compressed and received at the client as 59.5347, which results in a noticeably different render than when the client camera is at 59.5262.
Basically the moral of this story is that when lossy compression is involved in the transmission, sending a number and then doing a calculation on the received number gets you a different answer than doing the calculation and then sending the calculation result.
I'm pretty sure the original camera code handled this part correctly and I boffed it up in the rewrite. Will fix this evening.
#5
The camera code was setting the compression point using mPos. Generally mPos is the position of the camera, but when the camera is in orbitmode, mPos is the focus point (in my code) because that results in better interpolation.
However when the forced periodic control object update happens, the compression point is set by that code, using getPosition on the object. getPosition is a nonvirtual member function of SceneObject that will always return the current position of the object (according to its transform matrix), which is different than the camera's mPos when it is in orbitmode. So, when this happened it would momentarily swozzle the compression point.
That one was pretty easy to fix, I just changed my camera code to always use the position from the transform matrix as the compression point.
04/26/2002 (5:30 pm)
Might as well describe what I mentioned a few posts above as the problem with "maintaining a consistent compression point".The camera code was setting the compression point using mPos. Generally mPos is the position of the camera, but when the camera is in orbitmode, mPos is the focus point (in my code) because that results in better interpolation.
However when the forced periodic control object update happens, the compression point is set by that code, using getPosition on the object. getPosition is a nonvirtual member function of SceneObject that will always return the current position of the object (according to its transform matrix), which is different than the camera's mPos when it is in orbitmode. So, when this happened it would momentarily swozzle the compression point.
That one was pretty easy to fix, I just changed my camera code to always use the position from the transform matrix as the compression point.
#6
04/26/2002 (10:46 pm)
Download link in the first post has been updated with the new code.
#7
05/05/2002 (9:26 pm)
Well, funny thing. So I got this all working great, then this weekend I updated our codebase to include all the latest changes for TGE... still working great. Then out of curiosity I took a fresh TGE checkout and dumped this camera code in, and in that unmodified TGE build this new camera code made things worse. ?? Anyway, looks like some more fun investigation ahead there... probably going to take care of some other stuff for the immediate future tho, since our code is behaving fine. But for now I'm not sure that anyone other than our team will be interested in using this new camera code. :-) Ah well.
#8
I plan to use it with pathfollowing for simple ingame scene's .. even for scene impact.
this all ties in :)
05/06/2002 (2:12 am)
Joel the camera Work is great.I plan to use it with pathfollowing for simple ingame scene's .. even for scene impact.
this all ties in :)
#9
05/06/2002 (9:01 am)
joel, could you describe this new camera code a bit, since you said it works worse in head i didnt try it cause im working using head, but im interested in what's new in it? (besides the fixes, you mentioned u added some modes or something)
#10
05/06/2002 (10:30 pm)
The code is pretty heavily commented, so you could just download it and have a look. I don't remember now off the top of my head what I did with the modes, but basically, there was at least one mode that wasn't really implemented (stationary rotatable camera I think?) and so I put some code in for it, although I don't use it.
#11
So, the camera is tracking the player object, therefore it is set to be processed after the player object. However this "process after" request is only for the tick ordering, it doesn't apply to the ordering of messages from server to client.
When I send the control-object update for the camera, it just includes the identifier of its focus object, not the actual coordinates of the focus object. Then the camera finds out what the coordinates are on the client side. Trouble is, this update can be sent BEFORE the update for the player object, so the client-side camera code gets old data for the player coordinates.
La, la, la... OK. Shouldn't be too hard to fix, altho I'll have to be careful not to run afoul of the compression problems again. Fun stuff. :-)
05/08/2002 (10:25 pm)
waurgh... I think I know what the problem is, although I don't know why this wasn't biting me before. I suppose I was either lucky, or there was some change that I don't know about relevant to this behavior.So, the camera is tracking the player object, therefore it is set to be processed after the player object. However this "process after" request is only for the tick ordering, it doesn't apply to the ordering of messages from server to client.
When I send the control-object update for the camera, it just includes the identifier of its focus object, not the actual coordinates of the focus object. Then the camera finds out what the coordinates are on the client side. Trouble is, this update can be sent BEFORE the update for the player object, so the client-side camera code gets old data for the player coordinates.
La, la, la... OK. Shouldn't be too hard to fix, altho I'll have to be careful not to run afoul of the compression problems again. Fun stuff. :-)
#12
Glad to see you're making headway in so many areas. I've been tied up the last 3 weeks on the Tribes 2 update, but it looks like the end is in sight for that, so I'll be back on engine stuff shortly...
In news related to the camera stuff, when I migrated the control object checksumming fix from Torque to T2 I added a general purpose checksum function to ShapeBase that basically just checksums a bitstream writePacketData. I'll be moving that version back into Torque soon. The interpolation code on the camera basically is causing the checksum to be different on every packet, so it's great that you're looking at that stuff.
05/09/2002 (9:09 pm)
Joel, you rule! (fyi)Glad to see you're making headway in so many areas. I've been tied up the last 3 weeks on the Tribes 2 update, but it looks like the end is in sight for that, so I'll be back on engine stuff shortly...
In news related to the camera stuff, when I migrated the control object checksumming fix from Torque to T2 I added a general purpose checksum function to ShapeBase that basically just checksums a bitstream writePacketData. I'll be moving that version back into Torque soon. The interpolation code on the camera basically is causing the checksum to be different on every packet, so it's great that you're looking at that stuff.
#13
I had a big post typed up here but it vanished in the ether when I submitted it... doh! Not sure what happened there. Anyway bottom line is that I have a camera that works in my codebase now, wanna test it in unmodified TGE before I upload it again though.
05/12/2002 (11:25 pm)
That's interesting, because the main (well, only) guess I had about the differences between the old and new codebases was that it had something to do frequency of control-object update.I had a big post typed up here but it vanished in the ether when I submitted it... doh! Not sure what happened there. Anyway bottom line is that I have a camera that works in my codebase now, wanna test it in unmodified TGE before I upload it again though.
#14
Of course, the current camera in the TGE head looks pretty good as well, at least in the few tests I've done in the "fps" mod and Realm Wars. So there's probably not a burning need for this camera code unless you're seeing some camera problems in whatever game you are working on right now. As I mentioned above, I'm not sure why the camera orbitmode in TGE looks better now (since the camera code hasn't changed), but if the update frequency has dramatically increased that could explain things. If that happened, and if Mark F.'s upcoming changes reduce the update frequency back down, then maybe we'll see the shakiness resurface.
Anyway, I've updated the link in the first post in this thread for anyone who wants to try it out. This still isn't "perfect camera code", even if there are no more lurking bugs; in particular, the pack/unpackUpdate functions remain a little sketchy, and they might or might not do the job for some setup that needs to move around cameras autonomously. But for cameras as player control objects this seems to be working fine. See the comments throughout the file for various explanations and notes for where things could possibly be fixed or improved still.
So, FYI, here's what I changed since the previous version:
- When orbiting, don't need to update the position on receiving an update from the server, and in fact it is a BAD THING to update the position at that time since the focus object may not have been updated yet. Instead, basically do nothing... the right things will happen when it comes time to tick or interp position. One thing I've added tho is that it does note if the focus object has changed, to try to maintain the interp factors a little better for some odd corner cases... not real important.
- In getCameraTransform, we have to call getRenderEyeTransform rather than getEyeTransform. This sounds wrong, but the fact is that other clientside code calls getCameraTransform, in cases where one might instead expect something like getRenderCameraTransform. Easier to fix here rather than renaming stuff in a lot of other code.
- Made validateEyePosition get its start position from whatever matrix is passed in, rather than assuming it should use the rendermatrix. That used to be an OK assumption but no longer is.
- Instead of hardcoding the orbit distance, use the distance that was specified by the scripting. Also added and exported a function for changing the orbit distance.
- A few changes to processTick that might make the camera behave better if for some reason we wanted to have an orbiting camera that was not player-controlled... basically don't skip over the tick update if the camera is orbiting, even if there is no move input.
- Don't need to fix the render transform after getting an update from the server. The setTransform will indeed screw up the render transform, but there is guaranteed to be an interp before the next frame render, which will fix the render transform.
- Moved the setControlDirty calls to more appropriate spots, altho that's a moot point right now since the current TGE code doesn't use them.
To recap, the overall functional differences between this code and the original TGE camera code:
- Separated out the physics/collision transform from the render transform, to be in line with the way other objects work. Seems to generally be a good idea, and also handy if you intend to eventually make your camera be able to collide with things, as we plan to.
- Better management of interpolation, especially when receiving an update from the server.
- In orbitmode, the camera focus is interpolated, not the camera position, which can result in more circular interpolated motion.
- Implementation of the StationaryMode (fixed) and the FreeRotateMode (fixed but can rotate), although untested.
- Additional member function (exported to scripting) setOrbitModeIfNeeded, which does nothing if camera is already in orbit mode, so that the current camera orbit rotation is not disturbed.
- Uses the orbit distance that is passed as an argument to setOrbitMode/setOrbitModeIfNeeded.
- Additional member function (exported to scripting) setOrbitDist, which adjusts the orbit distance.
If anyone does have occasion to use this, let me know the good or the bad.
05/13/2002 (7:10 pm)
OK... I've tested it out in the TGE head and it still looks good.Of course, the current camera in the TGE head looks pretty good as well, at least in the few tests I've done in the "fps" mod and Realm Wars. So there's probably not a burning need for this camera code unless you're seeing some camera problems in whatever game you are working on right now. As I mentioned above, I'm not sure why the camera orbitmode in TGE looks better now (since the camera code hasn't changed), but if the update frequency has dramatically increased that could explain things. If that happened, and if Mark F.'s upcoming changes reduce the update frequency back down, then maybe we'll see the shakiness resurface.
Anyway, I've updated the link in the first post in this thread for anyone who wants to try it out. This still isn't "perfect camera code", even if there are no more lurking bugs; in particular, the pack/unpackUpdate functions remain a little sketchy, and they might or might not do the job for some setup that needs to move around cameras autonomously. But for cameras as player control objects this seems to be working fine. See the comments throughout the file for various explanations and notes for where things could possibly be fixed or improved still.
So, FYI, here's what I changed since the previous version:
- When orbiting, don't need to update the position on receiving an update from the server, and in fact it is a BAD THING to update the position at that time since the focus object may not have been updated yet. Instead, basically do nothing... the right things will happen when it comes time to tick or interp position. One thing I've added tho is that it does note if the focus object has changed, to try to maintain the interp factors a little better for some odd corner cases... not real important.
- In getCameraTransform, we have to call getRenderEyeTransform rather than getEyeTransform. This sounds wrong, but the fact is that other clientside code calls getCameraTransform, in cases where one might instead expect something like getRenderCameraTransform. Easier to fix here rather than renaming stuff in a lot of other code.
- Made validateEyePosition get its start position from whatever matrix is passed in, rather than assuming it should use the rendermatrix. That used to be an OK assumption but no longer is.
- Instead of hardcoding the orbit distance, use the distance that was specified by the scripting. Also added and exported a function for changing the orbit distance.
- A few changes to processTick that might make the camera behave better if for some reason we wanted to have an orbiting camera that was not player-controlled... basically don't skip over the tick update if the camera is orbiting, even if there is no move input.
- Don't need to fix the render transform after getting an update from the server. The setTransform will indeed screw up the render transform, but there is guaranteed to be an interp before the next frame render, which will fix the render transform.
- Moved the setControlDirty calls to more appropriate spots, altho that's a moot point right now since the current TGE code doesn't use them.
To recap, the overall functional differences between this code and the original TGE camera code:
- Separated out the physics/collision transform from the render transform, to be in line with the way other objects work. Seems to generally be a good idea, and also handy if you intend to eventually make your camera be able to collide with things, as we plan to.
- Better management of interpolation, especially when receiving an update from the server.
- In orbitmode, the camera focus is interpolated, not the camera position, which can result in more circular interpolated motion.
- Implementation of the StationaryMode (fixed) and the FreeRotateMode (fixed but can rotate), although untested.
- Additional member function (exported to scripting) setOrbitModeIfNeeded, which does nothing if camera is already in orbit mode, so that the current camera orbit rotation is not disturbed.
- Uses the orbit distance that is passed as an argument to setOrbitMode/setOrbitModeIfNeeded.
- Additional member function (exported to scripting) setOrbitDist, which adjusts the orbit distance.
If anyone does have occasion to use this, let me know the good or the bad.
#15
what happened to your site? it redirects me to some goofy domain registration site.
10/21/2002 (6:56 am)
Joel,what happened to your site? it redirects me to some goofy domain registration site.
#16
11/12/2002 (1:56 pm)
The clanplaid.net hosting had to move to a new site. The system is back up as of a few days ago, but I haven't yet gotten around to setting up the name resolution.
#17
08/24/2005 (1:23 pm)
Just want to add something. I was having a problem with jerky movements on my client machines. The problem was in Camera::mMovementSpeed (camera.cc) and $Camera::movementSpeed (camera.cs). It seems that these didn't match and it caused the server and client to update the cameras at different rates. To fix it you can either change match the values and recompile or ensure that the value is passed whenever it changes.
#18
08/24/2005 (4:13 pm)
Aight, noted! Thanks. #326.
#19
On a curious note, does the current version in the CVS of TGE fix that problem, or was the flickering/jerky action a feature? Thanks!
04/12/2006 (4:00 am)
I've just noticed this problem still existed with my current build. I've been using the clean installer of TGE1.4.0 and been modding the engine. I thought it must be something with changing part of the camera routine, but I've tested on the clean install of TGE 1.4.0 (installer not from the CVS) it seems this problem is still around. In the default starter.fps example in which when the player character dies (kamikaze or etc.), the orbitting camera seems to flicker as it rotates around the player. On a curious note, does the current version in the CVS of TGE fix that problem, or was the flickering/jerky action a feature? Thanks!
#20
-- Markus
05/02/2006 (1:12 pm)
The latest update I got a couple of days ago, still had this problem.-- Markus
Torque 3D Owner Joel Baxter
Not ready to do that quite yet because there's still a tiny bit of shaking in the view. The compression point and the camera object position (both serverside and clientside) are rock-solid, but there's a problem with the camera position value used to render the scene. Most of the time it is exactly the same as the clientside camera position, but every now and then it deviates by a few thousandths of a unit. You'd think that wouldn't be noticeable, but it really is.
The deviation is happening every 17th rendering of the scene, which is a frequency that makes me highly suspicious that the change is related to the periodic control object forced update (this is based on the 1.1.1 codebase... the newer codebase may not force those updates any more from what I hear).
Anyway I've got to go run some errands now, so I thought I'd cast this on the waters and see if there are any interesting answers posted before I return. :-) The deal basically is that in SceneGraph::renderScene, the line "dglGetModelview(&modelview);" returns those slightly-off values periodically. I'm an OpenGL ignoramus, so what I need to know is where TGE is setting the matrix or matrices that determine the matrix returned by that call, so I can set up shop with the debugger at that input point (rather than just helplessly watching the bad output). Any help appreciated!