//---------------------------------------------------------------------------- void Item::updateWorkingCollisionSet(const U32 mask, const F32 dt) { // It is assumed that we will never accelerate more than 10 m/s for gravity... // Point3F scaledVelocity = mVelocity * dt; F32 len = scaledVelocity.len(); F32 newLen = len + (10 * dt); // Check to see if it is actually necessary to construct the new working list, // or if we can use the cached version from the last query. We use the x // component of the min member of the mWorkingQueryBox, which is lame, but // it works ok. bool updateSet = false; Box3F convexBox = mConvex.getBoundingBox(getTransform(), getScale()); F32 l = (newLen * 1.1) + 0.1; // from Convex::updateWorkingList convexBox.minExtents -= Point3F(l, l, l); convexBox.maxExtents += Point3F(l, l, l); // Check containment { if (mWorkingQueryBox.minExtents.x != -1e9) { if (mWorkingQueryBox.isContained(convexBox) == false) { // Needed region is outside the cached region. Update it. updateSet = true; } else { // We can leave it alone, we're still inside the cached region } } else { // Must update updateSet = true; } } // Actually perform the query, if necessary if (updateSet == true) { mWorkingQueryBox = convexBox; mWorkingQueryBox.minExtents -= Point3F(2 * l, 2 * l, 2 * l); mWorkingQueryBox.maxExtents += Point3F(2 * l, 2 * l, 2 * l); disableCollision(); if (mCollisionObject) mCollisionObject->disableCollision(); mConvex.updateWorkingList(mWorkingQueryBox, mask); if (mCollisionObject) mCollisionObject->enableCollision(); enableCollision(); } }
void Etherform::updateWorkingCollisionSet() { // First, we need to adjust our velocity for possible acceleration. It is assumed // that we will never accelerate more than 20 m/s for gravity, plus 10 m/s for // jetting, and an equivalent 10 m/s for jumping. We also assume that the // working list is updated on a Tick basis, which means we only expand our // box by the possible movement in that tick. Point3F scaledVelocity = mVelocity * TickSec; F32 len = scaledVelocity.len(); F32 newLen = len + (10.0f * TickSec); // Check to see if it is actually necessary to construct the new working list, // or if we can use the cached version from the last query. We use the x // component of the min member of the mWorkingQueryBox, which is lame, but // it works ok. bool updateSet = false; Box3F convexBox = mConvex.getBoundingBox(getTransform(), getScale()); F32 l = (newLen * 1.1f) + 0.1f; // from Convex::updateWorkingList const Point3F lPoint( l, l, l ); convexBox.minExtents -= lPoint; convexBox.maxExtents += lPoint; // Check containment if (mWorkingQueryBox.minExtents.x != -1e9f) { if (mWorkingQueryBox.isContained(convexBox) == false) // Needed region is outside the cached region. Update it. updateSet = true; } else { // Must update updateSet = true; } // Actually perform the query, if necessary if (updateSet == true) { const Point3F twolPoint( 2.0f * l, 2.0f * l, 2.0f * l ); mWorkingQueryBox = convexBox; mWorkingQueryBox.minExtents -= twolPoint; mWorkingQueryBox.maxExtents += twolPoint; disableCollision(); mConvex.updateWorkingList(mWorkingQueryBox, isGhost() ? sClientCollisionContactMask : sServerCollisionContactMask); enableCollision(); } }
F32 FlyingVehicle::getHeight() { Point3F sp,ep; RayInfo collision; F32 height = (createHeightOn) ? mDataBlock->createHoverHeight : mDataBlock->hoverHeight; F32 r = 10 + height; getTransform().getColumn(3, &sp); ep.x = sp.x; ep.y = sp.y; ep.z = sp.z - r; disableCollision(); if( !mContainer->castRay(sp, ep, sClientCollisionMask, &collision) == true ) collision.t = 1; enableCollision(); return (r * collision.t - height) / 10; }
void AITurretShape::_trackTarget(F32 dt) { // Only on server if (isClientObject()) return; // We can only track a target if we have one if (!mTarget.isValid()) return; Point3F targetPos = mTarget.target->getBoxCenter(); // Can we see the target? MatrixF aimMat; getAimTransform(aimMat); Point3F start; aimMat.getColumn(3, &start); RayInfo ri; Point3F sightPoint; disableCollision(); bool los = _testTargetLineOfSight(start, mTarget.target, sightPoint); enableCollision(); if (!los) { // Target is blocked. Should we try to track from its last // known position and velocity? SimTime curTime = Sim::getCurrentTime(); if ( (curTime - mTarget.lastSightTime) > (mDataBlock->trackLostTargetTime * 1000.0f) ) { // Time's up. Stop tracking. _cleanupTargetAndTurret(); return; } // Use last known information to attempt to // continue to track target for a while. targetPos = mTarget.lastPos + mTarget.lastVel * F32(curTime - mTarget.lastSightTime) / 1000.0f; } else { // Target is visible // We only track targets that are alive if (mTarget.target->getDamageState() != Enabled) { // We can't track any more _cleanupTargetAndTurret(); return; } targetPos = sightPoint; // Store latest target info mTarget.lastPos = targetPos; mTarget.lastVel = mTarget.target->getVelocity(); mTarget.lastSightTime = Sim::getCurrentTime(); } // Calculate angles to face the target, specifically the part that we can see VectorF toTarget; MatrixF mat; S32 node = mDataBlock->aimNode; if (node != -1) { // Get the current position of our node MatrixF* nodeTrans = &mShapeInstance->mNodeTransforms[node]; Point3F currentPos; nodeTrans->getColumn(3, ¤tPos); // Turn this into a matrix we can use to put the target // position into our space. MatrixF nodeMat(true); nodeMat.setColumn(3, currentPos); mat.mul(mObjToWorld, nodeMat); mat.affineInverse(); } else { mat = mWorldToObj; } mat.mulP(targetPos, &toTarget); // lead the target F32 timeToTargetSquared = (mWeaponLeadVelocitySquared > 0) ? toTarget.lenSquared() / mWeaponLeadVelocitySquared : 0; if (timeToTargetSquared > 1.0) { targetPos = targetPos + (mTarget.lastVel * mSqrt(timeToTargetSquared)); mat.mulP(targetPos, &toTarget); } F32 yaw, pitch; MathUtils::getAnglesFromVector(toTarget, yaw, pitch); if (yaw > M_PI_F) yaw = yaw - M_2PI_F; //if (pitch > M_PI_F) // pitch = -(pitch - M_2PI_F); Point3F rot(-pitch, 0.0f, yaw); // If we have a rotation rate make sure we follow it if (mHeadingRate > 0) { F32 rate = mHeadingRate * dt; F32 rateCheck = mFabs(rot.z - mRot.z); if (rateCheck > rate) { // This will clamp the new value to the rate regardless if it // is increasing or decreasing. rot.z = mClampF(rot.z, mRot.z-rate, mRot.z+rate); } } if (mPitchRate > 0) { F32 rate = mPitchRate * dt; F32 rateCheck = mFabs(rot.x - mRot.x); if (rateCheck > rate) { // This will clamp the new value to the rate regardless if it // is increasing or decreasing. rot.x = mClampF(rot.x, mRot.x-rate, mRot.x+rate); } } // Test if the rotation to the target is outside of our limits if (_outsideLimits(rot)) { // We can't track any more _cleanupTargetAndTurret(); return; } // Test if the target is out of weapons range if (toTarget.lenSquared() > mWeaponRangeSquared) { // We can't track any more _cleanupTargetAndTurret(); return; } mRot = rot; _setRotation( mRot ); setMaskBits(TurretUpdateMask); }
void AITurretShape::_performScan() { // Only on server if (isClientObject()) return; // Are we ready for a scan? --mTicksToNextScan; if (mTicksToNextScan > 0) return; _cleanupPotentialTargets(); _setScanBox(); // Set up for the scan getScanTransform(mScanWorkspaceScanMat); mScanWorkspaceScanWorldMat = mScanWorkspaceScanMat; mScanWorkspaceScanWorldMat.affineInverse(); disableCollision(); for ( SimSetIterator iter(&mIgnoreObjects); *iter; ++iter ) { ShapeBase* obj = static_cast<ShapeBase*>( *iter ); obj->disableCollision(); } gServerContainer.findObjects( mTransformedScanBox, sScanTypeMask, _scanCallback, (void*)this ); for ( SimSetIterator iter(&mIgnoreObjects); *iter; ++iter ) { ShapeBase* obj = static_cast<ShapeBase*>( *iter ); obj->enableCollision(); } enableCollision(); if (mPotentialTargets.size() == 0) { // No targets in range. Clear out our current target, if necessary. _lostTarget(); } else { // Sort the targets comparePoint = getPosition(); dQsort(mPotentialTargets.address(),mPotentialTargets.size(),sizeof(SimObjectList::value_type),_sortCallback); // Go through the targets in order to find one that is not blocked from view Point3F start; mScanWorkspaceScanMat.getColumn(3, &start); S32 index = 0; bool los = false; disableCollision(); for (index=0; index < mPotentialTargets.size(); ++index) { ShapeBase* shape = (ShapeBase*)mPotentialTargets[index]; Point3F sightPoint; los = _testTargetLineOfSight(start, shape, sightPoint); // Check if we have a clear line of sight if (los) break; } enableCollision(); // If we found a valid, visible target (no hits between here and there), latch on to it if (los) { _gainedTarget((ShapeBase*)mPotentialTargets[index]); } } // Prepare for next scan period mTicksToNextScan = mScanTickFrequency; if (mScanTickFrequencyVariance > 0) { mTicksToNextScan += gRandGen.randI(0, mScanTickFrequencyVariance); } }
void Item::updatePos(const U32 /*mask*/, const F32 dt) { // Try and move Point3F pos; mObjToWorld.getColumn(3,&pos); delta.posVec = pos; bool contact = false; bool nonStatic = false; bool stickyNotify = false; CollisionList collisionList; F32 time = dt; static Polyhedron sBoxPolyhedron; static ExtrudedPolyList sExtrudedPolyList; static EarlyOutPolyList sEarlyOutPolyList; MatrixF collisionMatrix(true); Point3F end = pos + mVelocity * time; U32 mask = isServerObject() ? sServerCollisionMask : sClientCollisionMask; // Part of our speed problem here is that we don't track contact surfaces, like we do // with the player. In order to handle the most common and performance impacting // instance of this problem, we'll use a ray cast to detect any contact surfaces below // us. This won't be perfect, but it only needs to catch a few of these to make a // big difference. We'll cast from the top center of the bounding box at the tick's // beginning to the bottom center of the box at the end. Point3F startCast((mObjBox.minExtents.x + mObjBox.maxExtents.x) * 0.5, (mObjBox.minExtents.y + mObjBox.maxExtents.y) * 0.5, mObjBox.maxExtents.z); Point3F endCast((mObjBox.minExtents.x + mObjBox.maxExtents.x) * 0.5, (mObjBox.minExtents.y + mObjBox.maxExtents.y) * 0.5, mObjBox.minExtents.z); collisionMatrix.setColumn(3, pos); collisionMatrix.mulP(startCast); collisionMatrix.setColumn(3, end); collisionMatrix.mulP(endCast); RayInfo rinfo; bool doToughCollision = true; disableCollision(); if (mCollisionObject) mCollisionObject->disableCollision(); if (getContainer()->castRay(startCast, endCast, mask, &rinfo)) { F32 bd = -mDot(mVelocity, rinfo.normal); if (bd >= 0.0) { // Contact! if (mDataBlock->sticky && rinfo.object->getTypeMask() & (STATIC_COLLISION_TYPEMASK)) { mVelocity.set(0, 0, 0); mAtRest = true; mAtRestCounter = 0; stickyNotify = true; mStickyCollisionPos = rinfo.point; mStickyCollisionNormal = rinfo.normal; doToughCollision = false;; } else { // Subtract out velocity into surface and friction VectorF fv = mVelocity + rinfo.normal * bd; F32 fvl = fv.len(); if (fvl) { F32 ff = bd * mDataBlock->friction; if (ff < fvl) { fv *= ff / fvl; fvl = ff; } } bd *= 1 + mDataBlock->elasticity; VectorF dv = rinfo.normal * (bd + 0.002); mVelocity += dv; mVelocity -= fv; // Keep track of what we hit contact = true; U32 typeMask = rinfo.object->getTypeMask(); if (!(typeMask & StaticObjectType)) nonStatic = true; if (isServerObject() && (typeMask & ShapeBaseObjectType)) { ShapeBase* col = static_cast<ShapeBase*>(rinfo.object); queueCollision(col,mVelocity - col->getVelocity()); } } } } enableCollision(); if (mCollisionObject) mCollisionObject->enableCollision(); if (doToughCollision) { U32 count; for (count = 0; count < 3; count++) { // Build list from convex states here... end = pos + mVelocity * time; collisionMatrix.setColumn(3, end); Box3F wBox = getObjBox(); collisionMatrix.mul(wBox); Box3F testBox = wBox; Point3F oldMin = testBox.minExtents; Point3F oldMax = testBox.maxExtents; testBox.minExtents.setMin(oldMin + (mVelocity * time)); testBox.maxExtents.setMin(oldMax + (mVelocity * time)); sEarlyOutPolyList.clear(); sEarlyOutPolyList.mNormal.set(0,0,0); sEarlyOutPolyList.mPlaneList.setSize(6); sEarlyOutPolyList.mPlaneList[0].set(wBox.minExtents,VectorF(-1,0,0)); sEarlyOutPolyList.mPlaneList[1].set(wBox.maxExtents,VectorF(0,1,0)); sEarlyOutPolyList.mPlaneList[2].set(wBox.maxExtents,VectorF(1,0,0)); sEarlyOutPolyList.mPlaneList[3].set(wBox.minExtents,VectorF(0,-1,0)); sEarlyOutPolyList.mPlaneList[4].set(wBox.minExtents,VectorF(0,0,-1)); sEarlyOutPolyList.mPlaneList[5].set(wBox.maxExtents,VectorF(0,0,1)); CollisionWorkingList& eorList = mConvex.getWorkingList(); CollisionWorkingList* eopList = eorList.wLink.mNext; while (eopList != &eorList) { if ((eopList->mConvex->getObject()->getTypeMask() & mask) != 0) { Box3F convexBox = eopList->mConvex->getBoundingBox(); if (testBox.isOverlapped(convexBox)) { eopList->mConvex->getPolyList(&sEarlyOutPolyList); if (sEarlyOutPolyList.isEmpty() == false) break; } } eopList = eopList->wLink.mNext; } if (sEarlyOutPolyList.isEmpty()) { pos = end; break; } collisionMatrix.setColumn(3, pos); sBoxPolyhedron.buildBox(collisionMatrix, mObjBox, true); // Build extruded polyList... VectorF vector = end - pos; sExtrudedPolyList.extrude(sBoxPolyhedron, vector); sExtrudedPolyList.setVelocity(mVelocity); sExtrudedPolyList.setCollisionList(&collisionList); CollisionWorkingList& rList = mConvex.getWorkingList(); CollisionWorkingList* pList = rList.wLink.mNext; while (pList != &rList) { if ((pList->mConvex->getObject()->getTypeMask() & mask) != 0) { Box3F convexBox = pList->mConvex->getBoundingBox(); if (testBox.isOverlapped(convexBox)) { pList->mConvex->getPolyList(&sExtrudedPolyList); } } pList = pList->wLink.mNext; } if (collisionList.getTime() < 1.0) { // Set to collision point F32 dt = time * collisionList.getTime(); pos += mVelocity * dt; time -= dt; // Pick the most resistant surface F32 bd = 0; const Collision* collision = 0; for (int c = 0; c < collisionList.getCount(); c++) { const Collision &cp = collisionList[c]; F32 dot = -mDot(mVelocity,cp.normal); if (dot > bd) { bd = dot; collision = &cp; } } if (collision && mDataBlock->sticky && collision->object->getTypeMask() & (STATIC_COLLISION_TYPEMASK)) { mVelocity.set(0, 0, 0); mAtRest = true; mAtRestCounter = 0; stickyNotify = true; mStickyCollisionPos = collision->point; mStickyCollisionNormal = collision->normal; break; } else { // Subtract out velocity into surface and friction if (collision) { VectorF fv = mVelocity + collision->normal * bd; F32 fvl = fv.len(); if (fvl) { F32 ff = bd * mDataBlock->friction; if (ff < fvl) { fv *= ff / fvl; fvl = ff; } } bd *= 1 + mDataBlock->elasticity; VectorF dv = collision->normal * (bd + 0.002); mVelocity += dv; mVelocity -= fv; // Keep track of what we hit contact = true; U32 typeMask = collision->object->getTypeMask(); if (!(typeMask & StaticObjectType)) nonStatic = true; if (isServerObject() && (typeMask & ShapeBaseObjectType)) { ShapeBase* col = static_cast<ShapeBase*>(collision->object); queueCollision(col,mVelocity - col->getVelocity()); } } } } else { pos = end; break; } } if (count == 3) { // Couldn't move... mVelocity.set(0, 0, 0); } } // If on the client, calculate delta for backstepping if (isGhost()) { delta.pos = pos; delta.posVec -= pos; delta.dt = 1; } // Update transform MatrixF mat = mObjToWorld; mat.setColumn(3,pos); Parent::setTransform(mat); enableCollision(); if (mCollisionObject) mCollisionObject->enableCollision(); updateContainer(); if ( mPhysicsRep ) mPhysicsRep->setTransform( mat ); // if (contact) { // Check for rest condition if (!nonStatic && mVelocity.len() < sAtRestVelocity) { mVelocity.x = mVelocity.y = mVelocity.z = 0; mAtRest = true; mAtRestCounter = 0; } // Only update the client if we hit a non-static shape or // if this is our final rest pos. if (nonStatic || mAtRest) setMaskBits(PositionMask); } // Collision callbacks. These need to be processed whether we hit // anything or not. if (!isGhost()) { SimObjectPtr<Item> safePtr(this); if (stickyNotify) { notifyCollision(); if(bool(safePtr)) onStickyCollision_callback( getIdString() ); } else notifyCollision(); // water if(bool(safePtr)) { if(!mInLiquid && mWaterCoverage != 0.0f) { mInLiquid = true; if ( !isGhost() ) mDataBlock->onEnterLiquid_callback( this, mWaterCoverage, mLiquidType.c_str() ); } else if(mInLiquid && mWaterCoverage == 0.0f) { mInLiquid = false; if ( !isGhost() ) mDataBlock->onLeaveLiquid_callback( this, mLiquidType.c_str() ); } } } }
void Projectile::simulate( F32 dt ) { if ( isServerObject() && mCurrTick >= mDataBlock->lifetime ) { deleteObject(); return; } if ( mHasExploded ) return; // ... otherwise, we have to do some simulation work. RayInfo rInfo; Point3F oldPosition; Point3F newPosition; oldPosition = mCurrPosition; if ( mDataBlock->isBallistic ) mCurrVelocity.z -= 9.81 * mDataBlock->gravityMod * dt; newPosition = oldPosition + mCurrVelocity * dt; // disable the source objects collision reponse for a short time while we // determine if the projectile is capable of moving from the old position // to the new position, otherwise we'll hit ourself bool disableSourceObjCollision = (mSourceObject.isValid() && mCurrTick <= SourceIdTimeoutTicks); if ( disableSourceObjCollision ) mSourceObject->disableCollision(); disableCollision(); // Determine if the projectile is going to hit any object between the previous // position and the new position. This code is executed both on the server // and on the client (for prediction purposes). It is possible that the server // will have registered a collision while the client prediction has not. If this // happens the client will be corrected in the next packet update. // Raycast the abstract PhysicsWorld if a PhysicsPlugin exists. bool hit = false; if ( mPhysicsWorld ) hit = mPhysicsWorld->castRay( oldPosition, newPosition, &rInfo, Point3F( newPosition - oldPosition) * mDataBlock->impactForce ); else hit = getContainer()->castRay(oldPosition, newPosition, csmDynamicCollisionMask | csmStaticCollisionMask, &rInfo); if ( hit ) { // make sure the client knows to bounce if ( isServerObject() && ( rInfo.object->getTypeMask() & csmStaticCollisionMask ) == 0 ) setMaskBits( BounceMask ); MatrixF xform( true ); xform.setColumn( 3, rInfo.point ); setTransform( xform ); mCurrPosition = rInfo.point; // Get the object type before the onCollision call, in case // the object is destroyed. U32 objectType = rInfo.object->getTypeMask(); // re-enable the collision response on the source object since // we need to process the onCollision and explode calls if ( disableSourceObjCollision ) mSourceObject->enableCollision(); // Ok, here is how this works: // onCollision is called to notify the server scripts that a collision has occurred, then // a call to explode is made to start the explosion process. The call to explode is made // twice, once on the server and once on the client. // The server process is responsible for two things: // 1) setting the ExplosionMask network bit to guarantee that the client calls explode // 2) initiate the explosion process on the server scripts // The client process is responsible for only one thing: // 1) drawing the appropriate explosion // It is possible that during the processTick the server may have decided that a hit // has occurred while the client prediction has decided that a hit has not occurred. // In this particular scenario the client will have failed to call onCollision and // explode during the processTick. However, the explode function will be called // during the next packet update, due to the ExplosionMask network bit being set. // onCollision will remain uncalled on the client however, therefore no client // specific code should be placed inside the function! onCollision( rInfo.point, rInfo.normal, rInfo.object ); // Next order of business: do we explode on this hit? if ( mCurrTick > mDataBlock->armingDelay || mDataBlock->armingDelay == 0 ) { mCurrVelocity = Point3F::Zero; explode( rInfo.point, rInfo.normal, objectType ); } if ( mDataBlock->isBallistic ) { // Otherwise, this represents a bounce. First, reflect our velocity // around the normal... Point3F bounceVel = mCurrVelocity - rInfo.normal * (mDot( mCurrVelocity, rInfo.normal ) * 2.0); mCurrVelocity = bounceVel; // Add in surface friction... Point3F tangent = bounceVel - rInfo.normal * mDot(bounceVel, rInfo.normal); mCurrVelocity -= tangent * mDataBlock->bounceFriction; // Now, take elasticity into account for modulating the speed of the grenade mCurrVelocity *= mDataBlock->bounceElasticity; // Set the new position to the impact and the bounce // will apply on the next frame. //F32 timeLeft = 1.0f - rInfo.t; newPosition = oldPosition = rInfo.point + rInfo.normal * 0.05f; } else { mCurrVelocity = Point3F::Zero; } } // re-enable the collision response on the source object now // that we are done processing the ballistic movement if ( disableSourceObjCollision ) mSourceObject->enableCollision(); enableCollision(); if ( isClientObject() ) { emitParticles( mCurrPosition, newPosition, mCurrVelocity, U32( dt * 1000.0f ) ); updateSound(); } mCurrDeltaBase = newPosition; mCurrBackDelta = mCurrPosition - newPosition; mCurrPosition = newPosition; MatrixF xform( true ); xform.setColumn( 3, mCurrPosition ); setTransform( xform ); }
void TurretShape::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; 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; if (mContainer->castRay(sp, ep, ~(WaterObjectType | GameBaseObjectType | DefaultObjectType | sTriggerMask), &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 + (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() ); }