//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "platform/platform.h" #include "T3D/camera.h" #include "math/mMath.h" #include "core/stream/bitStream.h" #include "T3D/fx/cameraFXMgr.h" #include "T3D/gameBase/gameConnection.h" #include "math/mathIO.h" #include "gui/worldEditor/editor.h" #include "console/engineAPI.h" #include "console/consoleTypes.h" #include "console/engineAPI.h" #include "math/mathUtils.h" #include "math/mTransform.h" #ifdef TORQUE_EXTENDED_MOVE #include "T3D/gameBase/extended/extendedMove.h" #endif S32 Camera::smExtendedMovePosRotIndex = 0; // The ExtendedMove position/rotation index used for camera movements #define MaxPitch 1.5706f #define CameraRadius 0.05f; ImplementEnumType( CameraMotionMode, "Movement behavior type for Camera.\n\n" "@ingroup BaseCamera" ) { Camera::StationaryMode, "Stationary", "Camera does not rotate or move." }, { Camera::FreeRotateMode, "FreeRotate", "Camera may rotate but does not move." }, { Camera::FlyMode, "Fly", "Camera may rotate and move freely." }, { Camera::OrbitObjectMode, "OrbitObject", "Camera orbits about a given object. Damage flash and white out is determined by the object being orbited. See Camera::setOrbitMode() to set the orbit object and other parameters." }, { Camera::OrbitPointMode, "OrbitPoint", "Camera orbits about a given point. See Camera::setOrbitMode() to set the orbit point and other parameters." }, { Camera::TrackObjectMode, "TrackObject", "Camera always faces a given object. See Camera::setTrackObject() to set the object to track and a distance to remain from the object." }, { Camera::OverheadMode, "Overhead", "Camera moves in the XY plane." }, { Camera::EditOrbitMode, "EditOrbit", "Used by the World Editor to orbit about a point. When first activated, the camera is rotated to face the orbit point rather than move to it." } EndImplementEnumType; //============================================================================= // CameraData. //============================================================================= // MARK: ---- CameraData ---- IMPLEMENT_CO_DATABLOCK_V1( CameraData ); ConsoleDocClass( CameraData, "@brief A datablock that describes a camera.\n\n" "@tsexample\n" "datablock CameraData(Observer)\n" "{\n" " mode = \"Observer\";\n" "};\n" "@endtsexample\n" "@see Camera\n\n" "@ref Datablock_Networking\n" "@ingroup BaseCamera\n" "@ingroup Datablocks\n" ); //----------------------------------------------------------------------------- void CameraData::initPersistFields() { docsURL; Parent::initPersistFields(); } //----------------------------------------------------------------------------- void CameraData::packData(BitStream* stream) { Parent::packData(stream); } //----------------------------------------------------------------------------- void CameraData::unpackData(BitStream* stream) { Parent::unpackData(stream); } //============================================================================= // Camera. //============================================================================= // MARK: ---- Camera ---- IMPLEMENT_CO_NETOBJECT_V1( Camera ); ConsoleDocClass( Camera, "@brief Represents a position, direction and field of view to render a scene from.\n\n" "A camera is typically manipulated by a GameConnection. When set as the connection's " "control object, the camera handles all movement actions ($mvForwardAction, $mvPitch, etc.) " "just like a Player.\n" "@tsexample\n" "// Set an already created camera as the GameConnection's control object\n" "%connection.setControlObject(%camera);\n" "@endtsexample\n\n" "

Methods of Operation

\n\n" "The camera has two general methods of operation. The first is the standard mode where " "the camera starts and stops its motion and rotation instantly. This is the default operation " "of the camera and is used by most games. It may be specifically set with Camera::setFlyMode() " "for 6 DoF motion. It is also typically the method used with Camera::setOrbitMode() or one of " "its helper methods to orbit about a specific object (such as the Player's dead body) or a " "specific point.\n\n" "The second method goes under the name of Newton as it follows Newton's 2nd law of " "motion: F=ma. This provides the camera with an ease-in and ease-out feel for both movement " "and rotation. To activate this method for movement, either use Camera::setNewtonFlyMode() or set " "the Camera::newtonMode field to true. To activate this method for rotation, set the Camera::newtonRotation " "to true. This method of operation is not typically used in games, and was developed to allow " "for a smooth fly through of a game level while recording a demo video. But with the right force " "and drag settings, it may give a more organic feel to the camera to games that use an overhead view, " "such as a RTS.\n\n" "There is a third, minor method of operation but it is not generally used for games. This is when the " "camera is used with Torque's World Editor in Edit Orbit Mode. When set, this allows the camera " "to rotate about a specific point in the world, and move towards and away from this point. See " "Camera::setEditOrbitMode() and Camera::setEditOrbitPoint(). While in this mode, Camera::autoFitRadius() " "may also be used.\n\n" "@tsexample\n" "// Create a camera in the level and set its position to a given spawn point.\n" "// Note: The camera starts in the standard fly mode.\n" "%cam = new Camera() {\n" " datablock = \"Observer\";\n" "};\n" "MissionCleanup.add( %cam );\n" "%cam.setTransform( %spawnPoint.getTransform() );\n" "@endtsexample\n\n" "@tsexample\n" "// Create a camera at the given spawn point for the specified\n" "// GameConnection i.e. the client. Uses the standard\n" "// Sim::spawnObject() function to create the camera using the\n" "// defined default settings.\n" "// Note: The camera starts in the standard fly mode.\n" "function GameConnection::spawnCamera(%this, %spawnPoint)\n" "{\n" " // Set the control object to the default camera\n" " if (!isObject(%this.camera))\n" " {\n" " if (isDefined(\"$Game::DefaultCameraClass\"))\n" " %this.camera = spawnObject($Game::DefaultCameraClass, $Game::DefaultCameraDataBlock);\n" " }\n" "\n" " // If we have a camera then set up some properties\n" " if (isObject(%this.camera))\n" " {\n" " // Make sure we're cleaned up when the mission ends\n" " MissionCleanup.add( %this.camera );\n" "\n" " // Make sure the camera is always in scope for the connection\n" " %this.camera.scopeToClient(%this);\n" "\n" " // Send all user input from the connection to the camera\n" " %this.setControlObject(%this.camera);\n" "\n" " if (isDefined(\"%spawnPoint\"))\n" " {\n" " // Attempt to treat %spawnPoint as an object, such as a\n" " // SpawnSphere class.\n" " if (getWordCount(%spawnPoint) == 1 && isObject(%spawnPoint))\n" " {\n" " %this.camera.setTransform(%spawnPoint.getTransform());\n" " }\n" " else\n" " {\n" " // Treat %spawnPoint as an AngleAxis transform\n" " %this.camera.setTransform(%spawnPoint);\n" " }\n" " }\n" " }\n" "}\n" "@endtsexample\n\n" "

Motion Modes

\n\n" "Beyond the different operation methods, the Camera may be set to one of a number " "of motion modes. These motion modes determine how the camera will respond to input " "and may be used to constrain how the Camera moves. The CameraMotionMode enumeration " "defines the possible set of modes and the Camera's current may be obtained by using " "getMode().\n\n" "Some of the motion modes may be set using specific script methods. These often provide " "additional parameters to set up the mode in one go. Otherwise, it is always possible to " "set a Camera's motion mode using the controlMode property. Just pass in the name of the " "mode enum. The following table lists the motion modes, how to set them up, and what they offer:\n\n" "" "" "" "" "" "" "" "" "" "" "" "
ModeSet From ScriptInput MoveInput RotateCan Use Newton Mode?
StationarycontrolMode propertyNoNoNo
FreeRotatecontrolMode propertyNoYesRotate Only
FlysetFlyMode()YesYesYes
OrbitObjectsetOrbitMode()Orbits objectPoints to objectMove only
OrbitPointsetOrbitPoint()Orbits pointPoints to locationMove only
TrackObjectsetTrackObject()NoPoints to objectYes
OverheadcontrolMode propertyYesNoYes
EditOrbit (object selected)setEditOrbitMode()Orbits objectPoints to objectMove only
EditOrbit (no object)setEditOrbitMode()YesYesYes
\n\n" "

%Trigger Input

\n\n" "Passing a move trigger ($mvTriggerCount0, $mvTriggerCount1, etc.) on to a Camera performs " "different actions depending on which mode the camera is in. While in Fly, Overhead or " "EditOrbit mode, either trigger0 or trigger1 will cause a camera to move twice its normal " "movement speed. You can see this in action within the World Editor, where holding down the " "left mouse button while in mouse look mode (right mouse button is also down) causes the Camera " "to move faster.\n\n" "Passing along trigger2 will put the camera into strafe mode. While in this mode a Fly, " "FreeRotate or Overhead Camera will not rotate from the move input. Instead the yaw motion " "will be applied to the Camera's x motion, and the pitch motion will be applied to the Camera's " "z motion. You can see this in action within the World Editor where holding down the middle mouse " "button allows the user to move the camera up, down and side-to-side.\n\n" "While the camera is operating in Newton Mode, trigger0 and trigger1 behave slightly differently. " "Here trigger0 activates a multiplier to the applied acceleration force as defined by speedMultiplier. " "This has the affect of making the camera move up to speed faster. trigger1 has the opposite affect " "by acting as a brake. When trigger1 is active a multiplier is added to the Camera's drag as " "defined by brakeMultiplier.\n\n" "@see CameraData\n" "@see CameraMotionMode\n" "@see Camera::movementSpeed\n\n" "@ingroup BaseCamera\n" ); F32 Camera::smMovementSpeed = 40.0f; //---------------------------------------------------------------------------- Camera::Camera() { mNetFlags.clear(Ghostable); mTypeMask |= CameraObjectType; mDataBlock = 0; mDelta.pos = Point3F(0.0f, 0.0f, 100.0f); mDelta.rot = Point3F(0.0f, 0.0f, 0.0f); mDelta.posVec = mDelta.rotVec = VectorF(0.0f, 0.0f, 0.0f); mObjToWorld.setColumn(3, mDelta.pos); mRot = mDelta.rot; mOffset.set(0.0f, 0.0f, 0.0f); mMinOrbitDist = 0.0f; mMaxOrbitDist = 0.0f; mCurOrbitDist = 0.0f; mOrbitObject = NULL; mPosition.set(0.0f, 0.0f, 0.0f); mObservingClientObject = false; mMode = FlyMode; mLastAbsoluteYaw = 0.0f; mLastAbsolutePitch = 0.0f; mLastAbsoluteRoll = 0.0f; // For NewtonFlyMode mNewtonRotation = false; mAngularVelocity.set(0.0f, 0.0f, 0.0f); mAngularForce = 100.0f; mAngularDrag = 2.0f; mVelocity.set(0.0f, 0.0f, 0.0f); mNewtonMode = false; mMass = 10.0f; mDrag = 2.0; mFlyForce = 500.0f; mSpeedMultiplier = 2.0f; mBrakeMultiplier = 2.0f; // For EditOrbitMode mValidEditOrbitPoint = false; mEditOrbitPoint.set(0.0f, 0.0f, 0.0f); mCurrentEditOrbitDist = 2.0; mLocked = false; } //---------------------------------------------------------------------------- Camera::~Camera() { } //---------------------------------------------------------------------------- bool Camera::onAdd() { if(!Parent::onAdd() || !mDataBlock) return false; mObjBox.maxExtents = mObjScale; mObjBox.minExtents = mObjScale; mObjBox.minExtents.neg(); resetWorldBox(); addToScene(); scriptOnAdd(); return true; } //---------------------------------------------------------------------------- void Camera::onRemove() { scriptOnRemove(); removeFromScene(); Parent::onRemove(); } //---------------------------------------------------------------------------- bool Camera::onNewDataBlock( GameBaseData *dptr, bool reload ) { mDataBlock = dynamic_cast(dptr); if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) ) return false; scriptOnNewDataBlock(); return true; } //---------------------------------------------------------------------------- void Camera::onEditorEnable() { mNetFlags.set(Ghostable); } //---------------------------------------------------------------------------- void Camera::onEditorDisable() { mNetFlags.clear(Ghostable); } //---------------------------------------------------------------------------- // check if the object needs to be observed through its own camera... void Camera::getCameraTransform(F32* pos, MatrixF* mat) { // The camera doesn't support a third person mode, // so we want to override the default ShapeBase behavior. ShapeBase * obj = dynamic_cast(static_cast(mOrbitObject)); if(obj && static_cast(obj->getDataBlock())->observeThroughObject) obj->getCameraTransform(pos, mat); else getRenderEyeTransform(mat); // Apply Camera FX. mat->mul( gCamFXMgr.getTrans() ); } void Camera::getEyeCameraTransform(IDisplayDevice *displayDevice, U32 eyeId, MatrixF *outMat) { // The camera doesn't support a third person mode, // so we want to override the default ShapeBase behavior. ShapeBase * obj = dynamic_cast(static_cast(mOrbitObject)); if(obj && static_cast(obj->getDataBlock())->observeThroughObject) obj->getEyeCameraTransform(displayDevice, eyeId, outMat); else { Parent::getEyeCameraTransform(displayDevice, eyeId, outMat); } } //---------------------------------------------------------------------------- F32 Camera::getCameraFov() { ShapeBase * obj = dynamic_cast(static_cast(mOrbitObject)); if(obj && static_cast(obj->getDataBlock())->observeThroughObject) return(obj->getCameraFov()); else return(Parent::getCameraFov()); } //---------------------------------------------------------------------------- F32 Camera::getDefaultCameraFov() { ShapeBase * obj = dynamic_cast(static_cast(mOrbitObject)); if(obj && static_cast(obj->getDataBlock())->observeThroughObject) return(obj->getDefaultCameraFov()); else return(Parent::getDefaultCameraFov()); } //---------------------------------------------------------------------------- bool Camera::isValidCameraFov(F32 fov) { ShapeBase * obj = dynamic_cast(static_cast(mOrbitObject)); if(obj && static_cast(obj->getDataBlock())->observeThroughObject) return(obj->isValidCameraFov(fov)); else return(Parent::isValidCameraFov(fov)); } //---------------------------------------------------------------------------- void Camera::setCameraFov(F32 fov) { ShapeBase * obj = dynamic_cast(static_cast(mOrbitObject)); if(obj && static_cast(obj->getDataBlock())->observeThroughObject) obj->setCameraFov(fov); else Parent::setCameraFov(fov); } //---------------------------------------------------------------------------- void clampPitchAngle(F32 &pitch) { // Clamp pitch to +/-MaxPitch, but allow pitch=PI as it is used by some editor // views (bottom, front, right) if ((pitch > MaxPitch) && !mIsEqual(pitch, M_PI_F, 0.001f)) pitch = MaxPitch; else if (pitch < -MaxPitch) pitch = -MaxPitch; } //---------------------------------------------------------------------------- void Camera::processTick(const Move* move) { Parent::processTick(move); if ( isMounted() ) { // Update SceneContainer. updateContainer(); return; } Point3F vec,pos; if (move) { bool strafeMode = move->trigger[2]; // If using editor then force camera into fly mode, unless using EditOrbitMode if(gEditingMission && mMode != FlyMode && mMode != EditOrbitMode) setFlyMode(); // Massage the mode if we're in EditOrbitMode CameraMotionMode virtualMode = mMode; if(mMode == EditOrbitMode) { if(!mValidEditOrbitPoint) { virtualMode = FlyMode; } else { // Reset any Newton camera velocities for when we switch // out of EditOrbitMode. mNewtonRotation = false; mVelocity.set(0.0f, 0.0f, 0.0f); mAngularVelocity.set(0.0f, 0.0f, 0.0f); } } // Update orientation mDelta.rotVec = mRot; VectorF rotVec(0, 0, 0); bool doStandardMove = true; #ifdef TORQUE_EXTENDED_MOVE GameConnection* con = getControllingClient(); // Work with an absolute rotation from the ExtendedMove class? if(con && con->getControlSchemeAbsoluteRotation()) { doStandardMove = false; const ExtendedMove* emove = dynamic_cast(move); U32 emoveIndex = smExtendedMovePosRotIndex; if(emoveIndex >= ExtendedMove::MaxPositionsRotations) emoveIndex = 0; if(emove->EulerBasedRotation[emoveIndex]) { if(virtualMode != StationaryMode && virtualMode != TrackObjectMode && (!mLocked || virtualMode != OrbitObjectMode && virtualMode != OrbitPointMode)) { // Pitch mRot.x += (emove->rotX[emoveIndex] - mLastAbsolutePitch); // Do we also include the relative pitch value? if(con->getControlSchemeAddPitchToAbsRot() && !strafeMode) { F32 x = move->pitch; if (x > M_PI_F) x -= M_2PI_F; mRot.x += x; } // Constrain the range of mRot.x while (mRot.x < -M_PI_F) mRot.x += M_2PI_F; while (mRot.x > M_PI_F) mRot.x -= M_2PI_F; // Yaw mRot.z += (emove->rotZ[emoveIndex] - mLastAbsoluteYaw); // Do we also include the relative yaw value? if(con->getControlSchemeAddYawToAbsRot() && !strafeMode) { F32 z = move->yaw; if (z > M_PI_F) z -= M_2PI_F; mRot.z += z; } // Constrain the range of mRot.z while (mRot.z < -M_PI_F) mRot.z += M_2PI_F; while (mRot.z > M_PI_F) mRot.z -= M_2PI_F; mLastAbsoluteYaw = emove->rotZ[emoveIndex]; mLastAbsolutePitch = emove->rotX[emoveIndex]; mLastAbsoluteRoll = emove->rotY[emoveIndex]; // Bank mRot.y = emove->rotY[emoveIndex]; // Constrain the range of mRot.y while (mRot.y > M_PI_F) mRot.y -= M_2PI_F; } } } #endif if(doStandardMove) { // process input/determine rotation vector if(virtualMode != StationaryMode && virtualMode != TrackObjectMode && (!mLocked || ((virtualMode != OrbitObjectMode) && (virtualMode != OrbitPointMode)))) { if(!strafeMode) { rotVec.x = move->pitch; rotVec.z = move->yaw; } } else if(virtualMode == TrackObjectMode && bool(mOrbitObject)) { // orient the camera to face the object Point3F objPos; // If this is a shapebase, use its render eye transform // to avoid jittering. ShapeBase *shape = dynamic_cast((GameBase*)mOrbitObject); if( shape != NULL ) { MatrixF ret; shape->getRenderEyeTransform( &ret ); objPos = ret.getPosition(); } else { mOrbitObject->getWorldBox().getCenter(&objPos); } mObjToWorld.getColumn(3,&pos); vec = objPos - pos; vec.normalizeSafe(); F32 pitch, yaw; MathUtils::getAnglesFromVector(vec, yaw, pitch); rotVec.x = -pitch - mRot.x; rotVec.z = yaw - mRot.z; if(rotVec.z > M_PI_F) rotVec.z -= M_2PI_F; else if(rotVec.z < -M_PI_F) rotVec.z += M_2PI_F; } // apply rotation vector according to physics rules if(mNewtonRotation) { const F32 force = mAngularForce; const F32 drag = mAngularDrag; VectorF acc(0.0f, 0.0f, 0.0f); rotVec.x *= 2.0f; // Assume that our -2PI to 2PI range was clamped to -PI to PI in script rotVec.z *= 2.0f; // Assume that our -2PI to 2PI range was clamped to -PI to PI in script F32 rotVecL = rotVec.len(); if(rotVecL > 0) { acc = (rotVec * force / mMass) * TickSec; } // Accelerate mAngularVelocity += acc; // Drag mAngularVelocity -= mAngularVelocity * drag * TickSec; // Rotate mRot += mAngularVelocity * TickSec; clampPitchAngle(mRot.x); } else { mRot.x += rotVec.x; mRot.z += rotVec.z; clampPitchAngle(mRot.x); } } // Update position VectorF posVec(0, 0, 0); bool mustValidateEyePoint = false; bool serverInterpolate = false; // process input/determine translation vector if(virtualMode == OrbitObjectMode || virtualMode == OrbitPointMode) { pos = mDelta.pos; if(virtualMode == OrbitObjectMode && bool(mOrbitObject)) { // If this is a shapebase, use its render eye transform // to avoid jittering. GameBase *castObj = mOrbitObject; ShapeBase* shape = dynamic_cast(castObj); if( shape != NULL ) { MatrixF ret; shape->getRenderEyeTransform( &ret ); mPosition = ret.getPosition(); } else { // Hopefully this is a static object that doesn't move, // because the worldbox doesn't get updated between ticks. mOrbitObject->getWorldBox().getCenter(&mPosition); } } posVec = (mPosition + mOffset) - pos; mustValidateEyePoint = true; serverInterpolate = mNewtonMode; } else if(virtualMode == EditOrbitMode && mValidEditOrbitPoint) { bool faster = move->trigger[0] || move->trigger[1]; F32 scale = smMovementSpeed * (faster + 1); mCurrentEditOrbitDist -= move->y * TickSec * scale; mCurrentEditOrbitDist -= move->roll * TickSec * scale; // roll will be -Pi to Pi and we'll attempt to scale it here to be in line with the move->y calculation above if(mCurrentEditOrbitDist < 0.0f) mCurrentEditOrbitDist = 0.0f; mPosition = mEditOrbitPoint; _setPosition(mPosition, mRot); _calcEditOrbitPoint(&mObjToWorld, mRot); pos = mPosition; } else if(virtualMode == FlyMode) { bool faster = move->trigger[0] || move->trigger[1]; F32 scale = smMovementSpeed * (faster + 1); mObjToWorld.getColumn(3,&pos); mObjToWorld.getColumn(0,&vec); posVec = vec * move->x * TickSec * scale + vec * (strafeMode ? move->yaw * 2.0f * TickSec * scale : 0.0f); mObjToWorld.getColumn(1,&vec); posVec += vec * move->y * TickSec * scale + vec * move->roll * TickSec * scale; mObjToWorld.getColumn(2,&vec); posVec += vec * move->z * TickSec * scale - vec * (strafeMode ? move->pitch * 2.0f * TickSec * scale : 0.0f); } else if(virtualMode == OverheadMode) { bool faster = move->trigger[0] || move->trigger[1]; F32 scale = smMovementSpeed * (faster + 1); mObjToWorld.getColumn(3,&pos); mObjToWorld.getColumn(0,&vec); vec.z = 0; vec.normalizeSafe(); vec = vec * move->x * TickSec * scale + (strafeMode ? vec * move->yaw * 2.0f * TickSec * scale : Point3F(0, 0, 0)); posVec = vec; mObjToWorld.getColumn(1,&vec); vec.z = 0; if (vec.isZero()) { mObjToWorld.getColumn(2,&vec); vec.z = 0; } vec.normalizeSafe(); vec = vec * move->y * TickSec * scale - (strafeMode ? vec * move->pitch * 2.0f * TickSec * scale : Point3F(0, 0, 0)); posVec += vec; posVec.z += move->z * TickSec * scale + move->roll * TickSec * scale; } else // ignore input { mObjToWorld.getColumn(3,&pos); } // apply translation vector according to physics rules mDelta.posVec = pos; if(mNewtonMode) { bool faster = move->trigger[0]; bool brake = move->trigger[1]; const F32 movementSpeedMultiplier = smMovementSpeed / 40.0f; // Using the starting value as the base const F32 force = faster ? mFlyForce * movementSpeedMultiplier * mSpeedMultiplier : mFlyForce * movementSpeedMultiplier; const F32 drag = brake ? mDrag * mBrakeMultiplier : mDrag; VectorF acc(0.0f, 0.0f, 0.0f); F32 posVecL = posVec.len(); if(posVecL > 0) { acc = (posVec * force / mMass) * TickSec; } // Accelerate mVelocity += acc; // Drag mVelocity -= mVelocity * drag * TickSec; // Move pos += mVelocity * TickSec; } else { pos += posVec; } _setPosition(pos,mRot); // If on the client, calc delta for backstepping if (serverInterpolate || isClientObject()) { mDelta.pos = pos; mDelta.rot = mRot; mDelta.posVec = mDelta.posVec - mDelta.pos; mDelta.rotVec = mDelta.rotVec - mDelta.rot; for(U32 i=0; i<3; ++i) { if (mDelta.rotVec[i] > M_PI_F) mDelta.rotVec[i] -= M_2PI_F; else if (mDelta.rotVec[i] < -M_PI_F) mDelta.rotVec[i] += M_2PI_F; } } if(mustValidateEyePoint) _validateEyePoint(1.0f, &mObjToWorld); setMaskBits(MoveMask); } // Need to calculate the orbit position for the editor so the camera position // doesn't change when switching between orbit and other camera modes if (isServerObject() && (mMode == EditOrbitMode && mValidEditOrbitPoint)) _calcEditOrbitPoint(&mObjToWorld, mRot); if(getControllingClient() && mContainer) updateContainer(); } //---------------------------------------------------------------------------- void Camera::onDeleteNotify( SimObject* obj ) { if( obj == mOrbitObject ) { mOrbitObject = NULL; if( mMode == OrbitObjectMode ) mMode = OrbitPointMode; } Parent::onDeleteNotify( obj ); } //---------------------------------------------------------------------------- void Camera::interpolateTick(F32 dt) { Parent::interpolateTick(dt); if ( isMounted() ) return; Point3F rot = mDelta.rot + mDelta.rotVec * dt; if((mMode == OrbitObjectMode || mMode == OrbitPointMode) && !mNewtonMode) { if(mMode == OrbitObjectMode && bool(mOrbitObject)) { // If this is a shapebase, use its render eye transform // to avoid jittering. GameBase *castObj = mOrbitObject; ShapeBase* shape = dynamic_cast(castObj); if( shape != NULL ) { MatrixF ret; shape->getRenderEyeTransform( &ret ); mPosition = ret.getPosition(); } else { // Hopefully this is a static object that doesn't move, // because the worldbox doesn't get updated between ticks. mOrbitObject->getWorldBox().getCenter(&mPosition); } } _setRenderPosition( mPosition + mOffset, rot ); _validateEyePoint( 1.0f, &mRenderObjToWorld ); } else if(mMode == EditOrbitMode && mValidEditOrbitPoint) { mPosition = mEditOrbitPoint; _setRenderPosition(mPosition, rot); _calcEditOrbitPoint(&mRenderObjToWorld, rot); } else if(mMode == TrackObjectMode && bool(mOrbitObject) && !mNewtonRotation) { // orient the camera to face the object Point3F objPos; // If this is a shapebase, use its render eye transform // to avoid jittering. ShapeBase *shape = dynamic_cast((GameBase*)mOrbitObject); if( shape != NULL ) { MatrixF ret; shape->getRenderEyeTransform( &ret ); objPos = ret.getPosition(); } else { mOrbitObject->getWorldBox().getCenter(&objPos); } Point3F pos = mDelta.pos + mDelta.posVec * dt; Point3F vec = objPos - pos; vec.normalizeSafe(); F32 pitch, yaw; MathUtils::getAnglesFromVector(vec, yaw, pitch); rot.x = -pitch; rot.z = yaw; _setRenderPosition(pos, rot); } else { Point3F pos = mDelta.pos + mDelta.posVec * dt; _setRenderPosition(pos,rot); if(mMode == OrbitObjectMode || mMode == OrbitPointMode) _validateEyePoint(1.0f, &mRenderObjToWorld); } } //---------------------------------------------------------------------------- void Camera::_setPosition(const Point3F& pos, const Point3F& rot) { MatrixF xRot, zRot; xRot.set(EulerF(rot.x, 0.0f, 0.0f)); zRot.set(EulerF(0.0f, 0.0f, rot.z)); MatrixF temp; if(mDataBlock && mDataBlock->cameraCanBank) { // Take rot.y into account to bank the camera MatrixF imat; imat.mul(zRot, xRot); MatrixF ymat; ymat.set(EulerF(0.0f, rot.y, 0.0f)); temp.mul(imat, ymat); } else { temp.mul(zRot, xRot); } temp.setColumn(3, pos); Parent::setTransform(temp); mRot = rot; } //---------------------------------------------------------------------------- void Camera::setRotation(const Point3F& rot) { MatrixF xRot, zRot; xRot.set(EulerF(rot.x, 0.0f, 0.0f)); zRot.set(EulerF(0.0f, 0.0f, rot.z)); MatrixF temp; if(mDataBlock && mDataBlock->cameraCanBank) { // Take rot.y into account to bank the camera MatrixF imat; imat.mul(zRot, xRot); MatrixF ymat; ymat.set(EulerF(0.0f, rot.y, 0.0f)); temp.mul(imat, ymat); } else { temp.mul(zRot, xRot); } temp.setColumn(3, getPosition()); Parent::setTransform(temp); mRot = rot; } //---------------------------------------------------------------------------- void Camera::_setRenderPosition(const Point3F& pos,const Point3F& rot) { MatrixF xRot, zRot; xRot.set(EulerF(rot.x, 0, 0)); zRot.set(EulerF(0, 0, rot.z)); MatrixF temp; // mDataBlock may not be defined yet as this method is called during // SceneObject::onAdd(). if(mDataBlock && mDataBlock->cameraCanBank) { // Take rot.y into account to bank the camera MatrixF imat; imat.mul(zRot, xRot); MatrixF ymat; ymat.set(EulerF(0.0f, rot.y, 0.0f)); temp.mul(imat, ymat); } else { temp.mul(zRot, xRot); } temp.setColumn(3, pos); Parent::setRenderTransform(temp); } //---------------------------------------------------------------------------- void Camera::writePacketData(GameConnection *connection, BitStream *bstream) { // Update client regardless of status flags. Parent::writePacketData(connection, bstream); Point3F pos; mObjToWorld.getColumn(3,&pos); bstream->setCompressionPoint(pos); mathWrite(*bstream, pos); bstream->write(mRot.x); if(mDataBlock && bstream->writeFlag(mDataBlock->cameraCanBank)) { // Include mRot.y to allow for camera banking bstream->write(mRot.y); } bstream->write(mRot.z); U32 writeMode = mMode; Point3F writePos = mPosition; S32 gIndex = -1; if(mMode == OrbitObjectMode) { gIndex = bool(mOrbitObject) ? connection->getGhostIndex(mOrbitObject): -1; if(gIndex == -1) { writeMode = OrbitPointMode; if(bool(mOrbitObject)) mOrbitObject->getWorldBox().getCenter(&writePos); } } else if(mMode == TrackObjectMode) { gIndex = bool(mOrbitObject) ? connection->getGhostIndex(mOrbitObject): -1; if(gIndex == -1) writeMode = StationaryMode; } bstream->writeRangedU32(writeMode, CameraFirstMode, CameraLastMode); if (writeMode == OrbitObjectMode || writeMode == OrbitPointMode) { bstream->write(mMinOrbitDist); bstream->write(mMaxOrbitDist); bstream->write(mCurOrbitDist); if(writeMode == OrbitObjectMode) { bstream->writeFlag(mObservingClientObject); bstream->writeInt(gIndex, NetConnection::GhostIdBitSize); } if (writeMode == OrbitPointMode) bstream->writeCompressedPoint(writePos); } else if(writeMode == TrackObjectMode) { bstream->writeInt(gIndex, NetConnection::GhostIdBitSize); } if(bstream->writeFlag(mNewtonMode)) { bstream->write(mVelocity.x); bstream->write(mVelocity.y); bstream->write(mVelocity.z); } if(bstream->writeFlag(mNewtonRotation)) { bstream->write(mAngularVelocity.x); bstream->write(mAngularVelocity.y); bstream->write(mAngularVelocity.z); } bstream->writeFlag(mValidEditOrbitPoint); if(writeMode == EditOrbitMode) { bstream->write(mEditOrbitPoint.x); bstream->write(mEditOrbitPoint.y); bstream->write(mEditOrbitPoint.z); bstream->write(mCurrentEditOrbitDist); } } //---------------------------------------------------------------------------- void Camera::readPacketData(GameConnection *connection, BitStream *bstream) { Parent::readPacketData(connection, bstream); Point3F pos,rot; mathRead(*bstream, &pos); bstream->setCompressionPoint(pos); bstream->read(&rot.x); if(bstream->readFlag()) { // Include rot.y to allow for camera banking bstream->read(&rot.y); } bstream->read(&rot.z); GameBase* obj = 0; mMode = (CameraMotionMode)bstream->readRangedU32(CameraFirstMode, CameraLastMode); mObservingClientObject = false; if (mMode == OrbitObjectMode || mMode == OrbitPointMode) { bstream->read(&mMinOrbitDist); bstream->read(&mMaxOrbitDist); bstream->read(&mCurOrbitDist); if(mMode == OrbitObjectMode) { mObservingClientObject = bstream->readFlag(); S32 gIndex = bstream->readInt(NetConnection::GhostIdBitSize); obj = static_cast(connection->resolveGhost(gIndex)); } if (mMode == OrbitPointMode) bstream->readCompressedPoint(&mPosition); } else if (mMode == TrackObjectMode) { S32 gIndex = bstream->readInt(NetConnection::GhostIdBitSize); obj = static_cast(connection->resolveGhost(gIndex)); } if (obj != (GameBase*)mOrbitObject) { if (mOrbitObject) { clearProcessAfter(); clearNotify(mOrbitObject); } mOrbitObject = obj; if (mOrbitObject) { processAfter(mOrbitObject); deleteNotify(mOrbitObject); } } mNewtonMode = bstream->readFlag(); if(mNewtonMode) { bstream->read(&mVelocity.x); bstream->read(&mVelocity.y); bstream->read(&mVelocity.z); } mNewtonRotation = bstream->readFlag(); if(mNewtonRotation) { bstream->read(&mAngularVelocity.x); bstream->read(&mAngularVelocity.y); bstream->read(&mAngularVelocity.z); } mValidEditOrbitPoint = bstream->readFlag(); if(mMode == EditOrbitMode) { bstream->read(&mEditOrbitPoint.x); bstream->read(&mEditOrbitPoint.y); bstream->read(&mEditOrbitPoint.z); bstream->read(&mCurrentEditOrbitDist); } _setPosition(pos,rot); // Movement in OrbitObjectMode is not input-based - don't reset interpolation if(mMode != OrbitObjectMode) { mDelta.pos = pos; mDelta.posVec.set(0.0f, 0.0f, 0.0f); mDelta.rot = rot; mDelta.rotVec.set(0.0f, 0.0f, 0.0f); } } //---------------------------------------------------------------------------- U32 Camera::packUpdate(NetConnection *con, U32 mask, BitStream *bstream) { Parent::packUpdate(con, mask, bstream); if (bstream->writeFlag(mask & UpdateMask)) { bstream->writeFlag(mLocked); mathWrite(*bstream, mOffset); } if(bstream->writeFlag(mask & NewtonCameraMask)) { bstream->write(mAngularForce); bstream->write(mAngularDrag); bstream->write(mMass); bstream->write(mDrag); bstream->write(mFlyForce); bstream->write(mSpeedMultiplier); bstream->write(mBrakeMultiplier); } if(bstream->writeFlag(mask & EditOrbitMask)) { bstream->write(mEditOrbitPoint.x); bstream->write(mEditOrbitPoint.y); bstream->write(mEditOrbitPoint.z); bstream->write(mCurrentEditOrbitDist); } // The rest of the data is part of the control object packet update. // If we're controlled by this client, we don't need to send it. if(bstream->writeFlag(getControllingClient() == con && !(mask & InitialUpdateMask))) return 0; if (bstream->writeFlag(mask & MoveMask)) { Point3F pos; mObjToWorld.getColumn(3,&pos); bstream->write(pos.x); bstream->write(pos.y); bstream->write(pos.z); bstream->write(mRot.x); bstream->write(mRot.z); // Only required if in NewtonFlyMode F32 len = mVelocity.len(); if(bstream->writeFlag(mNewtonMode && len > 0.02f)) { Point3F outVel = mVelocity; outVel *= 1.0f/len; bstream->writeNormalVector(outVel, 10); len *= 32.0f; // 5 bits of fraction if(len > 8191) len = 8191; bstream->writeInt((S32)len, 13); } // Rotation len = mAngularVelocity.len(); if(bstream->writeFlag(mNewtonRotation && len > 0.02f)) { Point3F outVel = mAngularVelocity; outVel *= 1.0f/len; bstream->writeNormalVector(outVel, 10); len *= 32.0f; // 5 bits of fraction if(len > 8191) len = 8191; bstream->writeInt((S32)len, 13); } } return 0; } //---------------------------------------------------------------------------- void Camera::unpackUpdate(NetConnection *con, BitStream *bstream) { Parent::unpackUpdate(con,bstream); if (bstream->readFlag()) { mLocked = bstream->readFlag(); mathRead(*bstream, &mOffset); } // NewtonCameraMask if(bstream->readFlag()) { bstream->read(&mAngularForce); bstream->read(&mAngularDrag); bstream->read(&mMass); bstream->read(&mDrag); bstream->read(&mFlyForce); bstream->read(&mSpeedMultiplier); bstream->read(&mBrakeMultiplier); } // EditOrbitMask if(bstream->readFlag()) { bstream->read(&mEditOrbitPoint.x); bstream->read(&mEditOrbitPoint.y); bstream->read(&mEditOrbitPoint.z); bstream->read(&mCurrentEditOrbitDist); } // controlled by the client? if(bstream->readFlag()) return; // MoveMask if (bstream->readFlag()) { Point3F pos,rot; bstream->read(&pos.x); bstream->read(&pos.y); bstream->read(&pos.z); bstream->read(&rot.x); bstream->read(&rot.z); rot.y = 0.0f; _setPosition(pos,rot); // NewtonMode if(bstream->readFlag()) { bstream->readNormalVector(&mVelocity, 10); mVelocity *= bstream->readInt(13) / 32.0f; } // NewtonRotation mNewtonRotation = bstream->readFlag(); if(mNewtonRotation) { bstream->readNormalVector(&mAngularVelocity, 10); mAngularVelocity *= bstream->readInt(13) / 32.0f; } if(mMode != OrbitObjectMode) { // New delta for client side interpolation mDelta.pos = pos; mDelta.rot = rot; mDelta.posVec = mDelta.rotVec = VectorF(0.0f, 0.0f, 0.0f); } } } //---------------------------------------------------------------------------- void Camera::initPersistFields() { docsURL; addGroup( "Camera" ); addProtectedField( "controlMode", TYPEID< CameraMotionMode >(), Offset( mMode, Camera ), &_setModeField, &defaultProtectedGetFn, "The current camera control mode." ); endGroup( "Camera" ); addGroup( "Camera: Newton Mode" ); addField( "newtonMode", TypeBool, Offset(mNewtonMode, Camera), "Apply smoothing (acceleration and damping) to camera movements." ); addField( "newtonRotation", TypeBool, Offset(mNewtonRotation, Camera), "Apply smoothing (acceleration and damping) to camera rotations." ); addProtectedField( "mass", TypeF32, Offset(mMass, Camera), &_setNewtonField, &defaultProtectedGetFn, "The camera's mass (Newton mode only). Default value is 10." ); addProtectedField( "drag", TypeF32, Offset(mDrag, Camera), &_setNewtonField, &defaultProtectedGetFn, "Drag on camera when moving (Newton mode only). Default value is 2." ); addProtectedField( "force", TypeF32, Offset(mFlyForce, Camera), &_setNewtonField, &defaultProtectedGetFn, "Force applied on camera when asked to move (Newton mode only). Default value is 500." ); addProtectedField( "angularDrag", TypeF32, Offset(mAngularDrag, Camera), &_setNewtonField, &defaultProtectedGetFn, "Drag on camera when rotating (Newton mode only). Default value is 2." ); addProtectedField( "angularForce", TypeF32, Offset(mAngularForce, Camera), &_setNewtonField, &defaultProtectedGetFn, "Force applied on camera when asked to rotate (Newton mode only). Default value is 100." ); addProtectedField( "speedMultiplier", TypeF32, Offset(mSpeedMultiplier, Camera), &_setNewtonField, &defaultProtectedGetFn, "Speed multiplier when triggering the accelerator (Newton mode only). Default value is 2." ); addProtectedField( "brakeMultiplier", TypeF32, Offset(mBrakeMultiplier, Camera), &_setNewtonField, &defaultProtectedGetFn, "Speed multiplier when triggering the brake (Newton mode only). Default value is 2." ); endGroup( "Camera: Newton Mode" ); Parent::initPersistFields(); } //----------------------------------------------------------------------------- void Camera::consoleInit() { Con::addVariable("$Camera::movementSpeed", TypeF32, &smMovementSpeed, "@brief Global camera movement speed in units/s (typically m/s), with a base value of 40.\n\n" "Used in the following camera modes:\n" "- Edit Orbit Mode\n" "- Fly Mode\n" "- Overhead Mode\n" "@ingroup BaseCamera\n"); // ExtendedMove support Con::addVariable("$camera::extendedMovePosRotIndex", TypeS32, &smExtendedMovePosRotIndex, "@brief The ExtendedMove position/rotation index used for camera movements.\n\n" "@ingroup BaseCamera\n"); } //----------------------------------------------------------------------------- bool Camera::_setNewtonField( void *object, const char *index, const char *data ) { static_cast(object)->setMaskBits(NewtonCameraMask); return true; // ok to set value } //----------------------------------------------------------------------------- bool Camera::_setModeField( void *object, const char *index, const char *data ) { Camera *cam = static_cast( object ); if( dStricmp(data, "Fly") == 0 ) { cam->setFlyMode(); return false; // already changed mode } else if( dStricmp(data, "EditOrbit" ) == 0 ) { cam->setEditOrbitMode(); return false; // already changed mode } else if( (dStricmp(data, "OrbitObject") == 0 && cam->mMode != OrbitObjectMode) || (dStricmp(data, "TrackObject") == 0 && cam->mMode != TrackObjectMode) || (dStricmp(data, "OrbitPoint") == 0 && cam->mMode != OrbitPointMode) ) { Con::warnf("Couldn't change Camera mode to %s: required information missing. Use camera.set%s().", data, data); return false; // don't change the mode - not valid } else if( dStricmp(data, "OrbitObject") != 0 && dStricmp(data, "TrackObject") != 0 && bool(cam->mOrbitObject) ) { cam->clearProcessAfter(); cam->clearNotify(cam->mOrbitObject); cam->mOrbitObject = NULL; } // make sure the requested mode is supported, and set it // NOTE: The _CameraMode namespace is generated by ImplementEnumType above const EngineEnumTable& enums = *( TYPE< CameraMotionMode >()->getEnumTable() ); const U32 numValues = enums.getNumValues(); for( S32 i = 0; i < numValues; ++i) { if( dStricmp(data, enums[i].mName) == 0 ) { cam->mMode = (CameraMotionMode) enums[i].mInt; return false; } } Con::warnf("Unsupported camera mode: %s", data); return false; } //----------------------------------------------------------------------------- Point3F Camera::getPosition() { static Point3F position; mObjToWorld.getColumn(3, &position); return position; } //----------------------------------------------------------------------------- void Camera::setFlyMode() { mMode = FlyMode; if (bool(mOrbitObject)) { clearProcessAfter(); clearNotify(mOrbitObject); } mOrbitObject = NULL; } //----------------------------------------------------------------------------- void Camera::setNewtonFlyMode() { mNewtonMode = true; setFlyMode(); } //----------------------------------------------------------------------------- void Camera::setOrbitMode(GameBase *obj, const Point3F &pos, const Point3F &rot, const Point3F& offset, F32 minDist, F32 maxDist, F32 curDist, bool ownClientObject, bool locked) { mObservingClientObject = ownClientObject; if (bool(mOrbitObject)) { clearProcessAfter(); clearNotify(mOrbitObject); } mOrbitObject = obj; if(bool(mOrbitObject)) { processAfter(mOrbitObject); deleteNotify(mOrbitObject); mOrbitObject->getWorldBox().getCenter(&mPosition); mMode = OrbitObjectMode; } else { mMode = OrbitPointMode; mPosition = pos; } _setPosition(mPosition, rot); mMinOrbitDist = minDist; mMaxOrbitDist = maxDist; mCurOrbitDist = curDist; if (locked != mLocked || mOffset != offset) { mLocked = locked; mOffset = offset; setMaskBits(UpdateMask); } } //----------------------------------------------------------------------------- void Camera::setTrackObject(GameBase *obj, const Point3F &offset) { if(bool(mOrbitObject)) { clearProcessAfter(); clearNotify(mOrbitObject); } mOrbitObject = obj; if(bool(mOrbitObject)) { processAfter(mOrbitObject); deleteNotify(mOrbitObject); } mOffset = offset; mMode = TrackObjectMode; setMaskBits( UpdateMask ); } //----------------------------------------------------------------------------- void Camera::_validateEyePoint(F32 pos, MatrixF *mat) { if (pos != 0) { // Use the eye transform to orient the camera Point3F dir; mat->getColumn(1, &dir); if (mMaxOrbitDist - mMinOrbitDist > 0.0f) pos *= mMaxOrbitDist - mMinOrbitDist; // Use the camera node's pos. Point3F startPos = getRenderPosition(); Point3F endPos; // Make sure we don't extend the camera into anything solid if(mOrbitObject) mOrbitObject->disableCollision(); disableCollision(); RayInfo collision; U32 mask = TerrainObjectType | WaterObjectType | StaticShapeObjectType | PlayerObjectType | ItemObjectType | VehicleObjectType; SceneContainer* pContainer = isServerObject() ? &gServerContainer : &gClientContainer; if (!pContainer->castRay(startPos, startPos - dir * 2.5 * pos, mask, &collision)) endPos = startPos - dir * pos; else { float dot = mDot(dir, collision.normal); if (dot > 0.01f) { F32 colDist = mDot(startPos - collision.point, dir) - (1 / dot) * CameraRadius; if (colDist > pos) colDist = pos; if (colDist < 0.0f) colDist = 0.0f; endPos = startPos - dir * colDist; } else endPos = startPos - dir * pos; } mat->setColumn(3, endPos); enableCollision(); if(mOrbitObject) mOrbitObject->enableCollision(); } } //----------------------------------------------------------------------------- void Camera::setTransform(const MatrixF& mat) { // This method should never be called on the client. // This currently converts all rotation in the mat into // rotations around the z and x axis. Point3F pos,vec; mat.getColumn(1, &vec); mat.getColumn(3, &pos); Point3F rot(-mAtan2(vec.z, mSqrt(vec.x * vec.x + vec.y * vec.y)), 0.0f, -mAtan2(-vec.x, vec.y)); _setPosition(pos,rot); } //----------------------------------------------------------------------------- void Camera::setRenderTransform(const MatrixF& mat) { // This method should never be called on the client. // This currently converts all rotation in the mat into // rotations around the z and x axis. Point3F pos,vec; mat.getColumn(1,&vec); mat.getColumn(3,&pos); Point3F rot(-mAtan2(vec.z, mSqrt(vec.x*vec.x + vec.y*vec.y)),0,-mAtan2(-vec.x,vec.y)); _setRenderPosition(pos,rot); } //----------------------------------------------------------------------------- F32 Camera::getDamageFlash() const { if (mMode == OrbitObjectMode && isServerObject() && bool(mOrbitObject)) { const GameBase *castObj = mOrbitObject; const ShapeBase* psb = dynamic_cast(castObj); if (psb) return psb->getDamageFlash(); } return mDamageFlash; } //----------------------------------------------------------------------------- F32 Camera::getWhiteOut() const { if (mMode == OrbitObjectMode && isServerObject() && bool(mOrbitObject)) { const GameBase *castObj = mOrbitObject; const ShapeBase* psb = dynamic_cast(castObj); if (psb) return psb->getWhiteOut(); } return mWhiteOut; } //----------------------------------------------------------------------------- void Camera::setVelocity(const VectorF& vel) { mVelocity = vel; setMaskBits(MoveMask); } //----------------------------------------------------------------------------- void Camera::setAngularVelocity(const VectorF& vel) { mAngularVelocity = vel; setMaskBits(MoveMask); } //----------------------------------------------------------------------------- void Camera::setEditOrbitMode() { mMode = EditOrbitMode; if (bool(mOrbitObject)) { clearProcessAfter(); clearNotify(mOrbitObject); } mOrbitObject = NULL; // If there is a valid orbit point, then point to it // rather than move the camera. if(mValidEditOrbitPoint) { Point3F currentPos; mObjToWorld.getColumn(3,¤tPos); Point3F dir = mEditOrbitPoint - currentPos; mCurrentEditOrbitDist = dir.len(); dir.normalize(); F32 yaw, pitch; MathUtils::getAnglesFromVector(dir, yaw, pitch); mRot.x = -pitch; mRot.z = yaw; } } //----------------------------------------------------------------------------- void Camera::_calcOrbitPoint( MatrixF* mat, const Point3F& rot ) { Point3F pos; pos.x = mCurOrbitDist * mSin( rot.x + mDegToRad( 90.0f ) ) * mCos( -1.0f * ( rot.z + mDegToRad( 90.0f ) ) ) + mPosition.x + mOffset.x; pos.y = mCurOrbitDist * mSin( rot.x + mDegToRad( 90.0f ) ) * mSin( -1.0f * ( rot.z + mDegToRad( 90.0f ) ) ) + mPosition.y + mOffset.y; pos.z = mCurOrbitDist * mSin( rot.x ) + mPosition.z + mOffset.z; mat->setColumn( 3, pos ); } //----------------------------------------------------------------------------- void Camera::_calcEditOrbitPoint( MatrixF *mat, const Point3F& rot ) { //Point3F dir; //mat->getColumn(1, &dir); //Point3F startPos = getRenderPosition(); //Point3F endPos = startPos - dir * mCurrentEditOrbitDist; Point3F pos; pos.x = mCurrentEditOrbitDist * mSin(rot.x + mDegToRad(90.0f)) * mCos(-1.0f * (rot.z + mDegToRad(90.0f))) + mEditOrbitPoint.x; pos.y = mCurrentEditOrbitDist * mSin(rot.x + mDegToRad(90.0f)) * mSin(-1.0f * (rot.z + mDegToRad(90.0f))) + mEditOrbitPoint.y; pos.z = mCurrentEditOrbitDist * mSin(rot.x) + mEditOrbitPoint.z; mat->setColumn(3, pos); } //----------------------------------------------------------------------------- void Camera::setValidEditOrbitPoint( bool state ) { mValidEditOrbitPoint = state; setMaskBits(EditOrbitMask); } //----------------------------------------------------------------------------- Point3F Camera::getEditOrbitPoint() const { return mEditOrbitPoint; } //----------------------------------------------------------------------------- void Camera::setEditOrbitPoint( const Point3F& pnt ) { // Change the point that we orbit in EditOrbitMode. // We'll also change the facing and distance of the // camera so that it doesn't jump around. Point3F currentPos; mObjToWorld.getColumn(3,¤tPos); Point3F dir = pnt - currentPos; mCurrentEditOrbitDist = dir.len(); if(mMode == EditOrbitMode) { dir.normalize(); F32 yaw, pitch; MathUtils::getAnglesFromVector(dir, yaw, pitch); mRot.x = -pitch; mRot.z = yaw; } mEditOrbitPoint = pnt; setMaskBits(EditOrbitMask); } //---------------------------------------------------------------------------- void Camera::lookAt( const Point3F& pos ) { Point3F vec; mObjToWorld.getColumn(3, &mPosition); vec = pos - mPosition; vec.normalizeSafe(); F32 pitch, yaw; MathUtils::getAnglesFromVector(vec, yaw, pitch); mRot.x = -pitch; mRot.z = yaw; _setPosition(mPosition, mRot); } //---------------------------------------------------------------------------- void Camera::autoFitRadius( F32 radius ) { F32 fov = mDegToRad( getCameraFov() ); F32 viewradius = (radius * 2.0f) / mTan(fov * 0.5f); // Be careful of infinite sized objects. Clip to 16km if(viewradius > 16000.0f) viewradius = 16000.0f; if(mMode == EditOrbitMode && mValidEditOrbitPoint) { mCurrentEditOrbitDist = viewradius; } else if(mValidEditOrbitPoint) { mCurrentEditOrbitDist = viewradius; Point3F currentPos; mObjToWorld.getColumn(3,¤tPos); Point3F dir = mEditOrbitPoint - currentPos; dir.normalize(); F32 yaw, pitch; MathUtils::getAnglesFromVector(dir, yaw, pitch); mRot.x = -pitch; mRot.z = yaw; mPosition = mEditOrbitPoint; _setPosition(mPosition, mRot); _calcEditOrbitPoint(&mObjToWorld, mRot); } } //============================================================================= // Console API. //============================================================================= // MARK: ---- Console API ---- //----------------------------------------------------------------------------- DefineEngineMethod( Camera, getMode, Camera::CameraMotionMode, (), , "Returns the current camera control mode.\n\n" "@see CameraMotionMode") { return object->getMode(); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, getPosition, Point3F, (), , "Get the camera's position in the world.\n\n" "@returns The position in the form of \"x y z\".") { return object->getPosition(); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, getRotation, Point3F, (), , "Get the camera's Euler rotation in radians.\n\n" "@returns The rotation in radians in the form of \"x y z\".") { return object->getRotation(); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setRotation, void, ( Point3F rot ), , "Set the camera's Euler rotation in radians.\n\n" "@param rot The rotation in radians in the form of \"x y z\"." "@note Rotation around the Y axis is ignored" ) { return object->setRotation( rot ); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, getOffset, Point3F, (), , "Get the camera's offset from its orbit or tracking point.\n\n" "The offset is added to the camera's position when set to CameraMode::OrbitObject.\n" "@returns The offset in the form of \"x y z\".") { return object->getOffset(); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setOffset, void, (Point3F offset), , "Set the camera's offset.\n\n" "The offset is added to the camera's position when set to CameraMode::OrbitObject.\n" "@param offset The distance to offset the camera by in the form of \"x y z\".") { object->setOffset(offset); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setOrbitMode, void, (GameBase* orbitObject, TransformF orbitPoint, F32 minDistance, F32 maxDistance, F32 initDistance, bool ownClientObj, Point3F offset, bool locked), (false, Point3F::Zero, false), "Set the camera to orbit around the given object, or if none is given, around the given point.\n\n" "@param orbitObject The object to orbit around. If no object is given (0 or blank string is passed in) use the orbitPoint instead\n" "@param orbitPoint The point to orbit around when no object is given. In the form of \"x y z ax ay az aa\" such as returned by SceneObject::getTransform().\n" "@param minDistance The minimum distance allowed to the orbit object or point.\n" "@param maxDistance The maximum distance allowed from the orbit object or point.\n" "@param initDistance The initial distance from the orbit object or point.\n" "@param ownClientObj [optional] Are we orbiting an object that is owned by us? Default is false.\n" "@param offset [optional] An offset added to the camera's position. Default is no offset.\n" "@param locked [optional] Indicates the camera does not receive input from the player. Default is false.\n" "@see Camera::setOrbitObject()\n" "@see Camera::setOrbitPoint()\n") { MatrixF mat; orbitPoint.mOrientation.setMatrix(&mat); object->setOrbitMode(orbitObject, orbitPoint.mPosition, mat.toEuler(), offset, minDistance, maxDistance, initDistance, ownClientObj, locked); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setOrbitObject, bool, (GameBase* orbitObject, VectorF rotation, F32 minDistance, F32 maxDistance, F32 initDistance, bool ownClientObject, Point3F offset, bool locked), (false, Point3F::Zero, false), "Set the camera to orbit around a given object.\n\n" "@param orbitObject The object to orbit around.\n" "@param rotation The initial camera rotation about the object in radians in the form of \"x y z\".\n" "@param minDistance The minimum distance allowed to the orbit object or point.\n" "@param maxDistance The maximum distance allowed from the orbit object or point.\n" "@param initDistance The initial distance from the orbit object or point.\n" "@param ownClientObject [optional] Are we orbiting an object that is owned by us? Default is false.\n" "@param offset [optional] An offset added to the camera's position. Default is no offset.\n" "@param locked [optional] Indicates the camera does not receive input from the player. Default is false.\n" "@returns false if the given object could not be found.\n" "@see Camera::setOrbitMode()\n") { if( !orbitObject ) { Con::errorf( "Camera::setOrbitObject - Invalid object"); return false; } object->setOrbitMode(orbitObject, Point3F(0.0f, 0.0f, 0.0f), rotation, offset, minDistance, maxDistance, initDistance, ownClientObject, locked); return true; } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setOrbitPoint, void, (TransformF orbitPoint, F32 minDistance, F32 maxDistance, F32 initDistance, Point3F offset, bool locked), (Point3F::Zero, false), "Set the camera to orbit around a given point.\n\n" "@param orbitPoint The point to orbit around. In the form of \"x y z ax ay az aa\" such as returned by SceneObject::getTransform().\n" "@param minDistance The minimum distance allowed to the orbit object or point.\n" "@param maxDistance The maximum distance allowed from the orbit object or point.\n" "@param initDistance The initial distance from the orbit object or point.\n" "@param offset [optional] An offset added to the camera's position. Default is no offset.\n" "@param locked [optional] Indicates the camera does not receive input from the player. Default is false.\n" "@see Camera::setOrbitMode()\n") { MatrixF mat; orbitPoint.mOrientation.setMatrix(&mat); object->setOrbitMode(NULL, orbitPoint.mPosition, mat.toEuler(), offset, minDistance, maxDistance, initDistance, false, locked); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setTrackObject, bool, (GameBase* trackObject, Point3F offset), (Point3F::Zero), "Set the camera to track a given object.\n\n" "@param trackObject The object to track.\n" "@param offset [optional] An offset added to the camera's position. Default is no offset.\n" "@returns false if the given object could not be found.\n") { if(!trackObject) { Con::warnf("Cannot track non-existing object."); return false; } object->setTrackObject(trackObject, offset); return true; } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setEditOrbitMode, void, (), , "Set the editor camera to orbit around a point set with Camera::setEditOrbitPoint().\n\n" "@note This method is generally used only within the World Editor and other tools. To " "orbit about an object or point within a game, see Camera::setOrbitMode() and its helper methods.\n") { object->setEditOrbitMode(); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setFlyMode, void, (), , "Set the camera to fly freely.\n\n" "Allows the camera to have 6 degrees of freedom. Provides for instantaneous motion " "and rotation unless one of the Newton fields has been set to true. See Camera::newtonMode " "and Camera::newtonRotation.\n") { object->setFlyMode(); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setNewtonFlyMode, void, (), , "Set the camera to fly freely, but with ease-in and ease-out.\n\n" "This method allows for the same 6 degrees of freedom as Camera::setFlyMode() but " "activates the ease-in and ease-out on the camera's movement. To also activate " "Newton mode for the camera's rotation, set Camera::newtonRotation to true.") { object->setNewtonFlyMode(); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, isRotationDamped, bool, (), , "Is this a Newton Fly mode camera with damped rotation?\n\n" "@returns true if the camera uses a damped rotation. i.e. Camera::newtonRotation is set to true.\n") { return object->isRotationDamped(); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, getAngularVelocity, VectorF, (), , "Get the angular velocity for a Newton mode camera.\n\n" "@returns The angular velocity in the form of \"x y z\".\n" "@note Only returns useful results when Camera::newtonRotation is set to true.") { return object->getAngularVelocity(); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setAngularVelocity, void, (VectorF velocity), , "Set the angular velocity for a Newton mode camera.\n\n" "@param velocity The angular velocity infor form of \"x y z\".\n" "@note Only takes affect when Camera::newtonRotation is set to true.") { object->setAngularVelocity(velocity); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setAngularForce, void, (F32 force), , "Set the angular force for a Newton mode camera.\n\n" "@param force The angular force applied when attempting to rotate the camera." "@note Only takes affect when Camera::newtonRotation is set to true.") { object->setAngularForce(force); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setAngularDrag, void, (F32 drag), , "Set the angular drag for a Newton mode camera.\n\n" "@param drag The angular drag applied while the camera is rotating." "@note Only takes affect when Camera::newtonRotation is set to true.") { object->setAngularDrag(drag); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setMass, void, (F32 mass), , "Set the mass for a Newton mode camera.\n\n" "@param mass The mass used during ease-in and ease-out calculations." "@note Only used when Camera is in Newton mode.") { object->setMass(mass); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, getVelocity, VectorF, (), , "Get the velocity for the camera.\n\n" "@returns The camera's velocity in the form of \"x y z\"." "@note Only useful when the Camera is in Newton mode.") { return object->getVelocity(); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setVelocity, void, (VectorF velocity), , "Set the velocity for the camera.\n\n" "@param velocity The camera's velocity in the form of \"x y z\"." "@note Only affects the Camera when in Newton mode.") { object->setVelocity(velocity); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setDrag, void, (F32 drag), , "Set the drag for a Newton mode camera.\n\n" "@param drag The drag applied to the camera while moving." "@note Only used when Camera is in Newton mode.") { object->setDrag(drag); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setFlyForce, void, (F32 force), , "Set the force applied to a Newton mode camera while moving.\n\n" "@param force The force applied to the camera while attempting to move." "@note Only used when Camera is in Newton mode.") { object->setFlyForce(force); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setSpeedMultiplier, void, (F32 multiplier), , "Set the Newton mode camera speed multiplier when trigger[0] is active.\n\n" "@param multiplier The speed multiplier to apply." "@note Only used when Camera is in Newton mode.") { object->setSpeedMultiplier(multiplier); } //----------------------------------------------------------------------------- DefineEngineMethod( Camera, setBrakeMultiplier, void, (F32 multiplier), , "Set the Newton mode camera brake multiplier when trigger[1] is active.\n\n" "@param multiplier The brake multiplier to apply." "@note Only used when Camera is in Newton mode.") { object->setBrakeMultiplier(multiplier); } //---------------------------------------------------------------------------- DefineEngineMethod( Camera, isEditOrbitMode, bool, (), , "Is the camera in edit orbit mode?\n\n" "@returns true if the camera is in edit orbit mode.") { return object->isEditOrbitMode(); } //---------------------------------------------------------------------------- DefineEngineMethod( Camera, setValidEditOrbitPoint, void, (bool validPoint), , "Set if there is a valid editor camera orbit point.\n" "When validPoint is set to false the Camera operates as if it is " "in Fly mode rather than an Orbit mode.\n\n" "@param validPoint Indicates the validity of the orbit point." "@note Only used when Camera is in Edit Orbit Mode.") { object->setValidEditOrbitPoint(validPoint); } //---------------------------------------------------------------------------- DefineEngineMethod( Camera, setEditOrbitPoint, void, (Point3F point), , "Set the editor camera's orbit point.\n\n" "@param point The point the camera will orbit in the form of \"x y z\".") { object->setEditOrbitPoint(point); } //---------------------------------------------------------------------------- DefineEngineMethod( Camera, autoFitRadius, void, (F32 radius), , "Move the camera to fully view the given radius.\n\n" "@note For this operation to take affect a valid edit orbit point must first be specified. See Camera::setEditOrbitPoint().\n" "@param radius The radius to view.") { object->autoFitRadius(radius); } //---------------------------------------------------------------------------- DefineEngineMethod( Camera, lookAt, void, (Point3F point), , "Point the camera at the specified position. Does not work in Orbit or Track modes.\n\n" "@param point The position to point the camera at.") { object->lookAt(point); }