Title: Do the Twist. Purpose: The default setup for Torque does not have the ability to do a torso twist. The ability to twist the torso and have the weapons follow is a central concept to many of the Mecha games. This engine modification will enable torso-twisting. Discussion: To enable torso twisting, I have extended the controls of the first-person view to include the capabilities of the third-person view. This is a combination process and it is absolute, i.e. both the first-person view and the third-person view function identically and all characters have this ability. The amount of twisting is determined by the look angle player datablock values. These values are in radians, so 180 degrees equals 3.1415. MinLookAngle and MaxLookAngle control the amount of vertical bending and maxFreelookAngle controls the amount of horizontal twisting. Comments: If you want a character to not have torso twist, simply set their maxFreelookAngle to 0. For animating your character, have the “headside” animation be the torso twist extremes, and the “look” animation be the up-down extremes. Note: the “look” animation expects an 80 degree angle up and an 80 degree angle down for the extremes, even if you don’t use the full range. Key: I use the same indication of modifications as a patch file. A line beginning with a “-“ is to be removed and a line beginning with a “+” is to be added. Be sure to remove the “+” when you actually put the line into your code. A few lines around the changes are shown to give context to the changes. The triple dot “…” is showing that information has been removed for brevity purposes. Development Environment: November 2004 Head 1.2.2 Win32 Single Player Implementation: EngineFile: moveManager.h struct Move { … S32 px, py, pz; - U32 pyaw, ppitch, proll; + U32 pyaw, ppitch, proll, ptwist; F32 x, y, z; // float -1 to 1 - F32 yaw, pitch, roll; // 0-2PI + F32 yaw, pitch, roll, twist; // 0-2PI … } class MoveManager { … static F32 mRoll; + static F32 mTwist; … static F32 mRollRightSpeed; + static F32 mTwistLeftSpeed; + static F32 mTwistRightSpeed; … }; EngineFile:gameConnectionMoves.cc … F32 MoveManager::mRoll = 0; +F32 MoveManager::mTwist = 0; … F32 MoveManager::mRollRightSpeed = 0; +F32 MoveManager::mTwistLeftSpeed = 0; +F32 MoveManager::mTwistRightSpeed = 0; … const Move NullMove = { 16,16,16, - 0,0,0, + 0,0,0,0, 0,0,0, // x,y,z - 0,0,0, // Yaw, pitch, roll + 0,0,0,0, // Yaw, pitch, roll, twist 0,0, false, false,false,false,false,false,false }; … void MoveManager::init() { … Con::addVariable("mvRoll", TypeF32, &mRoll); + Con::addVariable("mvTwist", TypeF32, &mTwist); Con::addVariable("mvPitchUpSpeed", TypeF32, &mPitchUpSpeed); … Con::addVariable("mvRollRightSpeed", TypeF32, &mRollRightSpeed); + Con::addVariable("mvTwistLeftSpeed", TypeF32, &mTwistLeftSpeed); + Con::addVariable("mvTwistRightSpeed", TypeF32, &mTwistRightSpeed); … } void Move::unclamp() { yaw = IANG2FANG(pyaw); pitch = IANG2FANG(ppitch); roll = IANG2FANG(proll); + twist = IANG2FANG(ptwist); x = (px - 16) / F32(16); y = (py - 16) / F32(16); z = (pz - 16) / F32(16); } void Move::clamp() { // angles are all 16 bit. pyaw = FANG2IANG(yaw); ppitch = FANG2IANG(pitch); proll = FANG2IANG(roll); + ptwist = FANG2IANG(twist); px = clampRangeClamp(x); py = clampRangeClamp(y); pz = clampRangeClamp(z); unclamp(); } void Move::pack(BitStream *stream) { if(stream->writeFlag(pyaw != 0)) stream->writeInt(pyaw, 16); if(stream->writeFlag(ppitch != 0)) stream->writeInt(ppitch, 16); if(stream->writeFlag(proll != 0)) stream->writeInt(proll, 16); + if(stream->writeFlag(ptwist != 0)) + stream->writeInt(ptwist, 16); stream->writeInt(px, 6); stream->writeInt(py, 6); stream->writeInt(pz, 6); stream->writeFlag(freeLook); for(U32 i = 0; i < MaxTriggerKeys; i++) stream->writeFlag(trigger[i]); } void Move::unpack(BitStream *stream) { if(stream->readFlag()) pyaw = stream->readInt(16); else pyaw = 0; if(stream->readFlag()) ppitch = stream->readInt(16); else ppitch = 0; if(stream->readFlag()) proll = stream->readInt(16); else proll = 0; + if(stream->readFlag()) + ptwist = stream->readInt(16); + else + ptwist = 0; px = stream->readInt(6); py = stream->readInt(6); pz = stream->readInt(6); unclamp(); freeLook = stream->readFlag(); for(U32 i = 0; i < MaxTriggerKeys; i++) trigger[i] = stream->readFlag(); } bool GameConnection::getNextMove(Move &curMove) { … F32 pitchAdd = MoveManager::mPitchUpSpeed - MoveManager::mPitchDownSpeed; F32 yawAdd = MoveManager::mYawLeftSpeed - MoveManager::mYawRightSpeed; F32 rollAdd = MoveManager::mRollRightSpeed - MoveManager::mRollLeftSpeed; + F32 twistAdd = MoveManager::mTwistLeftSpeed - MoveManager::mTwistRightSpeed; curMove.pitch = MoveManager::mPitch + pitchAdd; curMove.yaw = MoveManager::mYaw + yawAdd; curMove.roll = MoveManager::mRoll + rollAdd; + curMove.twist = MoveManager::mTwist + twistAdd; MoveManager::mPitch = 0; MoveManager::mYaw = 0; MoveManager::mRoll = 0; + MoveManager::mTwist = 0; … } EngineFile: player.cc void Player::updateMove(const Move* move) { … F32 p = move->pitch; if (p > M_PI) p -= M_2PI; mHead.x = mClampF(mHead.x + p,mDataBlock->minLookAngle, mDataBlock->maxLookAngle); + F32 t = move->twist; + if (t > M_PI) t -= M_2PI; + mhead.z = mClampF(mHead.z + t,-mDataBlock->maxFreelookAngle,mDataBlock->maxFreelookAngle); … } void Player::getMuzzleTransform(U32 imageSlot,MatrixF* mat) { … if (mActionAnimation.action < PlayerData::NumTableActionAnims) { - MatrixF xmat; + MatrixF pmat,xmat,zmat; xmat.set(EulerF(mHead.x, 0, 0)); + zmat.set(EulerF(0, 0, mHead.z)); + pmat.mul(zmat,xmat); + mat->mul(getTransform(), pmat); - mat->mul(getTransform(), xmat); F32 *sp = nmat,*dp = *mat; dp[3] = sp[3]; dp[7] = sp[7]; dp[11] = sp[11]; } … } void Player::getRenderMuzzleTransform(U32 imageSlot,MatrixF* mat) { ... if (mActionAnimation.action < PlayerData::NumTableActionAnims) { - MatrixF xmat; + MatrixF pmat,xmat,zmat; xmat.set(EulerF(mHead.x, 0, 0)); + zmat.set(EulerF(0, 0, mHead.z)); + pmat.mul(zmat,xmat); + mat->mul(getRenderTransform(),xmat); - mat->mul(getRenderTransform(),pmat); F32 *sp = nmat,*dp = *mat; dp[3] = sp[3]; dp[7] = sp[7]; dp[11] = sp[11]; } else *mat = nmat; } void Player::getMuzzlePointAI(U32 imageSlot, Point3F* point) { ... if (mActionAnimation.action < PlayerData::NumTableActionAnims) { - MatrixF xmat; + MatrixF pmat,xmat,zmat; xmat.set(EulerF(mHead.x, 0, 0)); + zmat.set(EulerF(0, 0, mHead.z)); + pmat.mul(zmat,xmat); MatrixF result; - result.mul(getTransform(), xmat); + result.mul(getTransform(), pmat); F32 *sp = nmat, *dp = result; dp[3] = sp[3]; dp[7] = sp[7]; dp[11] = sp[11]; result.getColumn(3, point); } else nmat.getColumn(3, point); } U32 Player::packUpdate(NetConnection *con, U32 mask, BitStream *stream) { ... stream->writeFloat(mRot.z / M_2PI, 7); stream->writeSignedFloat(mHead.x / mDataBlock->maxLookAngle, 6); + stream->writeSignedFloat(mHead.z / mDataBlock->maxFreelookAngle, 6); ... } void Player::unpackUpdate(NetConnection *con, BitStream *stream) { ... rot.z = stream->readFloat(7) * M_2PI; mHead.x = stream->readSignedFloat(6) * mDataBlock->maxLookAngle; + mHead.z = stream->readSignedFloat(6) * mDataBlock->maxFreelookAngle; ... } ScriptFile: /client/scripts/default.bind.cs function twist(%val) { $mvTwist += getMouseAdjustAmount(%val); } moveMap.bind( mouse, xaxis, twist ); function twistLeft( %val ) { $mvTwistRightSpeed = %val ? $Pref::Input::KeyboardTurnSpeed : 0; } moveMap.bind(keyboard, "comma", twistLeft); function twistRight( %val ) { $mvTwistLeftSpeed = %val ? $Pref::Input::KeyboardTurnSpeed : 0; } moveMap.bind(keyboard, "period", twistRight); ScriptFile: /client/config.cs moveMap.bind(mouse0, "xaxis", twist); moveMap.bind(keyboard, "comma", twistLeft); moveMap.bind(keyboard, "period", twistRight); Credits: Derk Adams - adamsderk@hotmail.com