Over-the-shoulder camera
by Kory Imaginism · 06/03/2013 (6:30 am) · 17 comments
player.h
after this code:
F32 minLookAngle; ///< Lowest angle (radians) the player can look
F32 maxLookAngle; ///< Highest angle (radians) the player can look
F32 maxFreelookAngle; ///< Max left/right angle the player can look
Add:
F32 cameraLag; ///< Amount of camera lag (lag += velocity * lag)
F32 cameraDecay;///< Rate at which camera returns to target pos.
Point2F cameraOffset;///< horizontal and vertical offset
After this code:
StateDelta delta;///< Used for interpolation on the client.
S32 mPredictionCount; ///< Number of ticks to predict
Add:
Point3F mCameraOffset; ///< 3rd person camera offset
After this code:
void setTransform(const MatrixF &mat);
void getEyeTransform(MatrixF* mat);
void getRenderEyeTransform(MatrixF* mat);
void getCameraParameters(F32 *min, F32 *max, Point3F *offset, MatrixF *rot);
Add:
void getCameraTransform(F32* pos,MatrixF* mat);
player.cpp
after this code:
renderFirstPerson = true;
pickupRadius = 0.0f;
minLookAngle = -1.4f;
maxLookAngle = 1.4f;
maxFreelookAngle = 3.0f;
maxTimeScale = 1.5f;
add:
cameraLag = 0;
cameraDecay = 0;
cameraOffset = Point2F( 0,0 );
after this code:
addField("renderFirstPerson", TypeBool, Offset(renderFirstPerson, PlayerData));
addField("minLookAngle", TypeF32, Offset(minLookAngle, PlayerData));
addField("maxLookAngle", TypeF32, Offset(maxLookAngle, PlayerData));
addField("maxFreelookAngle", TypeF32, Offset(maxFreelookAngle, PlayerData));
add:
addField("cameraLag", TypeF32, Offset(cameraLag, PlayerData));
addField("cameraDecay", TypeF32, Offset(cameraDecay, PlayerData));
addField("cameraOffset", TypePoint2F, Offset(cameraOffset, PlayerData));
after this code:
delta.pos = mAnchorPoint = Point3F(0,0,100);
delta.rot = delta.head = Point3F(0,0,0);
delta.rotOffset.set(0.0f,0.0f,0.0f);
delta.warpOffset.set(0.0f,0.0f,0.0f);
delta.posVec.set(0.0f,0.0f,0.0f);
delta.rotVec.set(0.0f,0.0f,0.0f);
delta.headVec.set(0.0f,0.0f,0.0f);
delta.warpTicks = 0;
delta.dt = 1.0f;
delta.move = NullMove;
add:
mCameraOffset.set(0,0,0);
after this code:
// update camera effects. Definitely need to find better place for this - bramage
if( isControlObject() )
{
if( mDamageState == Disabled || mDamageState == Destroyed )
{
// clear out all camera effects being applied to player if dead
gCamFXMgr.clear();
}
}
add:
mCameraOffset -= (mCameraOffset * mDataBlock->cameraDecay +
getVelocity() * mDataBlock->cameraLag) * dt;
after this code:
void Player::getCameraParameters(F32 *min,F32* max,Point3F* off,MatrixF* rot)
{
if (!mControlObject.isNull() && mControlObject == getObjectMount()) {
mControlObject->getCameraParameters(min,max,off,rot);
return;
}
const Point3F& scale = getScale();
*min = mDataBlock->cameraMinDist * scale.y;
*max = mDataBlock->cameraMaxDist * scale.y;
off->set(0.0f, 0.0f, 0.0f);
rot->identity();
}
add:
void Player::getCameraTransform(F32* pos,MatrixF* mat)
{
// Returns camera to world space transform
// Handles first person / third person camera position
if (isServerObject() && mShapeInstance)
mShapeInstance->animateNodeSubtrees(true);
if (*pos == 0) {
getRenderEyeTransform(mat);
return;
}
// Get the shape's camera parameters.
F32 min,max;
MatrixF rot;
Point3F offset;
getCameraParameters(&min,&max,&offset,&rot);
// Start with the current eye position
MatrixF eye;
getRenderEyeTransform(&eye);
// Build a transform that points along the eye axis
// but where the Z axis is always up.
MatrixF cam(1);
VectorF x,y,z(0,0,1);
eye.getColumn(1, &y);
mCross(y, z, &x);
x.normalize();
mCross(x, y, &z);
z.normalize();
cam.setColumn(0,x);
cam.setColumn(1,y);
cam.setColumn(2,z);
mat->mul(cam,rot);
// Camera is positioned straight back along the eye's -Y axis.
// A ray is cast to make sure the camera doesn't go through
// anything solid.
VectorF vp,vec;
vp.x = vp.z = 0;
vp.y = -(max - min) * *pos;
vp += VectorF( mDataBlock->cameraOffset.x, mDataBlock->cameraOffset.y, 0.0f ); //kenan
eye.mulV(vp,&vec);
// Use the camera node as the starting position if it exists.
Point3F osp,sp;
if (mDataBlock->cameraNode != -1)
{
mShapeInstance->mNodeTransforms[mDataBlock->cameraNode].getColumn(3,&osp);
getRenderTransform().mulP(osp,&sp);
}
else
eye.getColumn(3,&sp);
// Make sure we don't hit ourself...
disableCollision();
if (isMounted())
getObjectMount()->disableCollision();
// Cast the ray into the container database to see if we're going
// to hit anything.
RayInfo collision;
Point3F ep = sp + vec + offset + mCameraOffset;
if (mContainer->castRay(sp, ep,
~(WaterObjectType | GameBaseObjectType | DefaultObjectType),
&collision) == true) {
// Shift the collision point back a little to try and
// avoid clipping against the front camera plane.
F32 t = collision.t - (-mDot(vec, collision.normal) / vec.len()) * 0.1;
if (t > 0.0f)
ep = sp + offset + mCameraOffset + (vec * t);
else
eye.getColumn(3,&ep);
}
mat->setColumn(3,ep);
// Re-enable our collision.
if (isMounted())
getObjectMount()->enableCollision();
enableCollision();
// Apply Camera FX.
mat->mul( gCamFXMgr.getTrans() );
}
datablocks/player.cs
after this code:
datablock PlayerData(DefaultPlayerData)
{
renderFirstPerson = true;
className = Armor;
shapeFile = "art/shapes/actors/Gideon/gideon.dts";
cameraMaxDist = 1.5;//3;
computeCRC = true;
canObserve = true;
cmdCategory = "Clients";
cameraDefaultFov = 100.0;
cameraMinFov = 5.0;
cameraMaxFov = 120.0;
add:
cameraOffset = "0.4 0.4"; // offset from camera mount point
cameraLag = 0.16; // Velocity lag of camera
cameraDecay = 1.85; // Decay per sec. rate of velocity lag
And that�âïÿýïÿýs it.
cameraOffset modifies the horizontal and vertical offset.
cameraLag determines how fast it keeps up with the player based on the player�âïÿýïÿýs velocity, higher value is slower to catch up.
cameraDecay determines how fast the camera catches up to the player based on offset distance, higher is faster.
Those are some sample numbers above that appear to works pretty well. The cameraLag and cameraDecay were added to give the camera a more natural feel but if you want it very rigid then set those two values to 0.0
Lastly, cameraMaxDist, already in player datablock determines how far from the player the 3rd person camera is. I set it to 1.5 to get a really close over the shoulder camera when I was testing.
VIDEO:
after this code:
F32 minLookAngle; ///< Lowest angle (radians) the player can look
F32 maxLookAngle; ///< Highest angle (radians) the player can look
F32 maxFreelookAngle; ///< Max left/right angle the player can look
Add:
F32 cameraLag; ///< Amount of camera lag (lag += velocity * lag)
F32 cameraDecay;///< Rate at which camera returns to target pos.
Point2F cameraOffset;///< horizontal and vertical offset
After this code:
StateDelta delta;///< Used for interpolation on the client.
S32 mPredictionCount; ///< Number of ticks to predict
Add:
Point3F mCameraOffset; ///< 3rd person camera offset
After this code:
void setTransform(const MatrixF &mat);
void getEyeTransform(MatrixF* mat);
void getRenderEyeTransform(MatrixF* mat);
void getCameraParameters(F32 *min, F32 *max, Point3F *offset, MatrixF *rot);
Add:
void getCameraTransform(F32* pos,MatrixF* mat);
player.cpp
after this code:
renderFirstPerson = true;
pickupRadius = 0.0f;
minLookAngle = -1.4f;
maxLookAngle = 1.4f;
maxFreelookAngle = 3.0f;
maxTimeScale = 1.5f;
add:
cameraLag = 0;
cameraDecay = 0;
cameraOffset = Point2F( 0,0 );
after this code:
addField("renderFirstPerson", TypeBool, Offset(renderFirstPerson, PlayerData));
addField("minLookAngle", TypeF32, Offset(minLookAngle, PlayerData));
addField("maxLookAngle", TypeF32, Offset(maxLookAngle, PlayerData));
addField("maxFreelookAngle", TypeF32, Offset(maxFreelookAngle, PlayerData));
add:
addField("cameraLag", TypeF32, Offset(cameraLag, PlayerData));
addField("cameraDecay", TypeF32, Offset(cameraDecay, PlayerData));
addField("cameraOffset", TypePoint2F, Offset(cameraOffset, PlayerData));
after this code:
delta.pos = mAnchorPoint = Point3F(0,0,100);
delta.rot = delta.head = Point3F(0,0,0);
delta.rotOffset.set(0.0f,0.0f,0.0f);
delta.warpOffset.set(0.0f,0.0f,0.0f);
delta.posVec.set(0.0f,0.0f,0.0f);
delta.rotVec.set(0.0f,0.0f,0.0f);
delta.headVec.set(0.0f,0.0f,0.0f);
delta.warpTicks = 0;
delta.dt = 1.0f;
delta.move = NullMove;
add:
mCameraOffset.set(0,0,0);
after this code:
// update camera effects. Definitely need to find better place for this - bramage
if( isControlObject() )
{
if( mDamageState == Disabled || mDamageState == Destroyed )
{
// clear out all camera effects being applied to player if dead
gCamFXMgr.clear();
}
}
add:
mCameraOffset -= (mCameraOffset * mDataBlock->cameraDecay +
getVelocity() * mDataBlock->cameraLag) * dt;
after this code:
void Player::getCameraParameters(F32 *min,F32* max,Point3F* off,MatrixF* rot)
{
if (!mControlObject.isNull() && mControlObject == getObjectMount()) {
mControlObject->getCameraParameters(min,max,off,rot);
return;
}
const Point3F& scale = getScale();
*min = mDataBlock->cameraMinDist * scale.y;
*max = mDataBlock->cameraMaxDist * scale.y;
off->set(0.0f, 0.0f, 0.0f);
rot->identity();
}
add:
void Player::getCameraTransform(F32* pos,MatrixF* mat)
{
// Returns camera to world space transform
// Handles first person / third person camera position
if (isServerObject() && mShapeInstance)
mShapeInstance->animateNodeSubtrees(true);
if (*pos == 0) {
getRenderEyeTransform(mat);
return;
}
// Get the shape's camera parameters.
F32 min,max;
MatrixF rot;
Point3F offset;
getCameraParameters(&min,&max,&offset,&rot);
// Start with the current eye position
MatrixF eye;
getRenderEyeTransform(&eye);
// Build a transform that points along the eye axis
// but where the Z axis is always up.
MatrixF cam(1);
VectorF x,y,z(0,0,1);
eye.getColumn(1, &y);
mCross(y, z, &x);
x.normalize();
mCross(x, y, &z);
z.normalize();
cam.setColumn(0,x);
cam.setColumn(1,y);
cam.setColumn(2,z);
mat->mul(cam,rot);
// Camera is positioned straight back along the eye's -Y axis.
// A ray is cast to make sure the camera doesn't go through
// anything solid.
VectorF vp,vec;
vp.x = vp.z = 0;
vp.y = -(max - min) * *pos;
vp += VectorF( mDataBlock->cameraOffset.x, mDataBlock->cameraOffset.y, 0.0f ); //kenan
eye.mulV(vp,&vec);
// Use the camera node as the starting position if it exists.
Point3F osp,sp;
if (mDataBlock->cameraNode != -1)
{
mShapeInstance->mNodeTransforms[mDataBlock->cameraNode].getColumn(3,&osp);
getRenderTransform().mulP(osp,&sp);
}
else
eye.getColumn(3,&sp);
// Make sure we don't hit ourself...
disableCollision();
if (isMounted())
getObjectMount()->disableCollision();
// Cast the ray into the container database to see if we're going
// to hit anything.
RayInfo collision;
Point3F ep = sp + vec + offset + mCameraOffset;
if (mContainer->castRay(sp, ep,
~(WaterObjectType | GameBaseObjectType | DefaultObjectType),
&collision) == true) {
// Shift the collision point back a little to try and
// avoid clipping against the front camera plane.
F32 t = collision.t - (-mDot(vec, collision.normal) / vec.len()) * 0.1;
if (t > 0.0f)
ep = sp + offset + mCameraOffset + (vec * t);
else
eye.getColumn(3,&ep);
}
mat->setColumn(3,ep);
// Re-enable our collision.
if (isMounted())
getObjectMount()->enableCollision();
enableCollision();
// Apply Camera FX.
mat->mul( gCamFXMgr.getTrans() );
}
datablocks/player.cs
after this code:
datablock PlayerData(DefaultPlayerData)
{
renderFirstPerson = true;
className = Armor;
shapeFile = "art/shapes/actors/Gideon/gideon.dts";
cameraMaxDist = 1.5;//3;
computeCRC = true;
canObserve = true;
cmdCategory = "Clients";
cameraDefaultFov = 100.0;
cameraMinFov = 5.0;
cameraMaxFov = 120.0;
add:
cameraOffset = "0.4 0.4"; // offset from camera mount point
cameraLag = 0.16; // Velocity lag of camera
cameraDecay = 1.85; // Decay per sec. rate of velocity lag
And that�âïÿýïÿýs it.
cameraOffset modifies the horizontal and vertical offset.
cameraLag determines how fast it keeps up with the player based on the player�âïÿýïÿýs velocity, higher value is slower to catch up.
cameraDecay determines how fast the camera catches up to the player based on offset distance, higher is faster.
Those are some sample numbers above that appear to works pretty well. The cameraLag and cameraDecay were added to give the camera a more natural feel but if you want it very rigid then set those two values to 0.0
Lastly, cameraMaxDist, already in player datablock determines how far from the player the 3rd person camera is. I set it to 1.5 to get a really close over the shoulder camera when I was testing.
VIDEO:
About the author
#2
Thanks
06/03/2013 (7:16 am)
Your welcome glad I could help. BTW I haven't check it past 1.2, could you let me know if the zoom works when the fire button is pushed?Thanks
#3
06/03/2013 (7:26 am)
Zoom appears to be working fine in a clean 3.0 build.
#4
06/03/2013 (2:49 pm)
Where's the crosshair?
#5
06/03/2013 (3:52 pm)
You my have to find a way to add it, it was one of the issues I had as well. Just haven't had time to add it.
#6
if (!control || !(control->getTypeMask() & ObjectMask) || !conn->isFirstPerson())
return;
06/03/2013 (4:09 pm)
I got the crosshair visible in third person by commenting out this if statement in guiCrossHairHud.cppif (!control || !(control->getTypeMask() & ObjectMask) || !conn->isFirstPerson())
return;
#7
06/03/2013 (8:46 pm)
Thanks Kory. And thanks Paul, for the crosshair implementation and feedback re 3.0 :D
#8
06/09/2013 (7:16 pm)
haha this looks familiar, didn't I set this up for you?
#10
06/09/2013 (8:14 pm)
double post
#11
06/14/2013 (11:01 am)
Nice touch to adjust bullet trajectory so it still hits the crosshairs. Good work!
#12
06/17/2013 (9:14 pm)
Thanks for this, have it working in 2.0/AFX and 3.0...
#13
07/19/2013 (12:56 pm)
Does this work in multiplayer? I have added it and it works for the server but for the client the camera doesn't offset like the server.
#14
08/20/2013 (7:32 am)
I got this working on Multiplayer, I don't have the code available at this post, however. If you go through Player.cpp and look at the code around the new camera lines and search for those further down player.cpp you should see what code needs to be added (IE copy paste those stream codes and change them to the camera ones).
#15
Whatever changes you did to get it working with multiplayer please post them whenever you get a chance. I wouldn't mine getting it working with multiplayer myself.
08/22/2013 (8:25 pm)
GlennWhatever changes you did to get it working with multiplayer please post them whenever you get a chance. I wouldn't mine getting it working with multiplayer myself.
#16
void PlayerData::packData()
void PlayerData::unpackData()
08/24/2013 (6:35 am)
Add void PlayerData::packData()
stream->write(minLookAngle); stream->write(maxLookAngle); stream->write(maxFreelookAngle); stream->write(cameraLag); //ADDED stream->write(cameraDecay); //ADDEDAdd
void PlayerData::unpackData()
stream->read(&minLookAngle); stream->read(&maxLookAngle); stream->read(&maxFreelookAngle); stream->read(&cameraLag); //ADDED stream->read(&cameraDecay); //ADDEDI'm fairly sure that is it.
#17
03/19/2014 (9:19 pm)
great work..... t3d 3.5.1mit
#18
03/19/2014 (9:21 pm)
Did you test in multiplayer as well, Donnie? Thanks 
Torque Owner Paul Clarke