// TODO: change name of offsets to correspond to correct stuff /* * Author: Andrew Robberts * * Copyright (C) 2003 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation (version 2 of the License) * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util/psstring.h" #include "globals.h" #include "pscamera.h" #include "pscelclient.h" #include "pscharcontrol.h" #include "gui/psmainwidget.h" psCamera::psCamera() { view = csPtr (new csView(psengine->GetEngine(), psengine->GetG3D())); actor = NULL; vc = CS_QUERY_REGISTRY (psengine->GetObjectRegistry(), iVirtualClock); cdsys = CS_QUERY_REGISTRY (psengine->GetObjectRegistry (), iCollideSystem); // most of these are default values that will be overwritten firstPersonPositionOffset = csVector3(0, 1, 0); thirdPersonPositionOffset = csVector3(0, 1, 3); camData[CAMERA_FIRST_PERSON].springCoef = 10.0f; camData[CAMERA_FIRST_PERSON].InertialDampeningCoef = 0.1f; camData[CAMERA_FIRST_PERSON].springLength = 0.01f; camData[CAMERA_FIRST_PERSON].maxDistance = 1.0f; camData[CAMERA_FIRST_PERSON].minDistance = 1.0f; camData[CAMERA_FIRST_PERSON].turnSpeed = 0.0f; camData[CAMERA_FIRST_PERSON].swingCoef = 0.0f; camData[CAMERA_THIRD_PERSON].springCoef = 3.5f; camData[CAMERA_THIRD_PERSON].InertialDampeningCoef = 0.25f; camData[CAMERA_THIRD_PERSON].springLength = 0.01f; camData[CAMERA_THIRD_PERSON].maxDistance = 15.0f; camData[CAMERA_THIRD_PERSON].minDistance = 1.0f; camData[CAMERA_THIRD_PERSON].turnSpeed = 0.0f; camData[CAMERA_THIRD_PERSON].swingCoef = 0.0f; camData[CAMERA_M64_THIRD_PERSON].springCoef = 3.5f; camData[CAMERA_M64_THIRD_PERSON].InertialDampeningCoef = 0.25f; camData[CAMERA_M64_THIRD_PERSON].springLength = 0.01f; camData[CAMERA_M64_THIRD_PERSON].minDistance = 2.0f; camData[CAMERA_M64_THIRD_PERSON].maxDistance = 6.0f; camData[CAMERA_M64_THIRD_PERSON].turnSpeed = 1.0f; camData[CAMERA_M64_THIRD_PERSON].swingCoef = 0.0f; camData[CAMERA_LARA_THIRD_PERSON].springCoef = 3.5f; camData[CAMERA_LARA_THIRD_PERSON].InertialDampeningCoef = 0.25f; camData[CAMERA_LARA_THIRD_PERSON].springLength = 0.01f; camData[CAMERA_LARA_THIRD_PERSON].minDistance = 2.0f; camData[CAMERA_LARA_THIRD_PERSON].maxDistance = 6.0f; camData[CAMERA_LARA_THIRD_PERSON].turnSpeed = 1.0f; camData[CAMERA_LARA_THIRD_PERSON].swingCoef = 0.7f; camData[CAMERA_FREE].springCoef = 3.5f; camData[CAMERA_FREE].InertialDampeningCoef = 0.25f; camData[CAMERA_FREE].springLength = 0.01f; camData[CAMERA_FREE].minDistance = 2.0f; camData[CAMERA_FREE].maxDistance = 16.0f; camData[CAMERA_FREE].turnSpeed = 0.0f; camData[CAMERA_FREE].swingCoef = 0.0f; camData[CAMERA_ACTUAL_DATA].springCoef = 0.0f; camData[CAMERA_ACTUAL_DATA].InertialDampeningCoef = 0.0; camData[CAMERA_ACTUAL_DATA].springLength = 0.01f; camData[CAMERA_ACTUAL_DATA].minDistance = 0.0f; camData[CAMERA_ACTUAL_DATA].maxDistance = 0.0f; camData[CAMERA_ACTUAL_DATA].turnSpeed = 0.0f; camData[CAMERA_ACTUAL_DATA].swingCoef = 0.0f; camData[CAMERA_LAST_ACTUAL].springCoef = 0.0f; camData[CAMERA_LAST_ACTUAL].InertialDampeningCoef = 0.0; camData[CAMERA_LAST_ACTUAL].springLength = 0.01f; camData[CAMERA_LAST_ACTUAL].minDistance = 0.0f; camData[CAMERA_LAST_ACTUAL].maxDistance = 0.0f; camData[CAMERA_LAST_ACTUAL].turnSpeed = 0.0f; camData[CAMERA_LAST_ACTUAL].swingCoef = 0.0f; camData[CAMERA_TRANSITION].springCoef = 3.5f; camData[CAMERA_TRANSITION].InertialDampeningCoef = 0.25f; camData[CAMERA_TRANSITION].springLength = 0.01f; camData[CAMERA_TRANSITION].minDistance = 1.0f; camData[CAMERA_TRANSITION].maxDistance = 16.0f; camData[CAMERA_TRANSITION].turnSpeed = 0.0f; camData[CAMERA_TRANSITION].swingCoef = 0.0f; camData[CAMERA_COLLISION].springCoef = 1.0f; camData[CAMERA_COLLISION].InertialDampeningCoef = 0.25f; camData[CAMERA_COLLISION].springLength = 0.01f; camData[CAMERA_COLLISION].minDistance = 2.0f; camData[CAMERA_COLLISION].maxDistance = 16.0f; camData[CAMERA_COLLISION].turnSpeed = 0.0f; camData[CAMERA_COLLISION].swingCoef = 0.0f; camData[CAMERA_ERR].springCoef = 0.0f; camData[CAMERA_ERR].InertialDampeningCoef = 0.0; camData[CAMERA_ERR].springLength = 0.01f; camData[CAMERA_ERR].minDistance = 0.0f; camData[CAMERA_ERR].maxDistance = 0.0f; camData[CAMERA_ERR].turnSpeed = 0.0f; camData[CAMERA_ERR].swingCoef = 0.0f; transitionThresholdSquared = 1.0f; cameraHasBeenPositioned = false; hasCollision = false; distanceCfg.adaptive = false; distanceCfg.dist = 200; distanceCfg.minFPS = 20; distanceCfg.maxFPS = 30; distanceCfg.minDist = 30; useCameraCD = false; lastActorSector = 0; cameraInitialized = false; } psCamera::~psCamera() { // save all the camera setting to the config file just once SaveToFile(); } bool psCamera::InitializeView(GEMClientActor* entity) { actor = entity; lastCameraMode = CAMERA_FIRST_PERSON; SetCameraMode(CAMERA_FIRST_PERSON); SetPitch(0.0f); SetPitchVelocity(0.0f); SetYaw(0.0f); SetYawVelocity(0.0f); csVector3 pos; float yRot; iSector* sector; actor->linmove->GetLastPosition(pos, yRot, sector); view->GetCamera()->SetSector(sector); view->GetCamera()->GetTransform().SetOrigin(pos); int width = psengine->GetG2D()->GetWidth(); int height = psengine->GetG2D()->GetHeight(); view->SetRectangle(0, 0, width, height); view->GetCamera()->SetPerspectiveCenter(width >> 1, height >> 1); view->SetContext(psengine->GetG3D()); view->GetCamera()->SetFOV( view->GetCamera()->GetFOV(), width ); for (int i=0; iGetCelClient()->GetMainPlayer()->race); race = psengine->GetCharManager()->GetCreation()->GetRace(raceDef.GetData()); if (race) { // calculate new offsets based on the race data // note that the x coordinate is ignored for speed reasons, so the offset // can only be either directly behind or in front of the actor. firstPersonPositionOffset = race->FirstPos; thirdPersonPositionOffset = race->FollowPos; } return true; } bool psCamera::LoadFromFile(bool useDefault) { csString fileName = "/this/data/options/camera.xml"; if (!psengine->GetVFS()->Exists(fileName) || useDefault) { fileName = "/this/data/options/camera_def.xml"; } csRef doc; csRef root, camNode, settingNode; csRef xmlbinds; csRef vfs; csRef xml; const char* error; vfs = psengine->GetVFS(); assert(vfs); csRef buff = vfs->ReadFile(fileName); if (buff == NULL) { Error2("Could not find file: %s", fileName.GetData()); return false; } xml = psengine->GetXMLParser (); doc = xml->CreateDocument(); assert(doc); error = doc->Parse( buff ); if ( error ) { Error3("Parse error in %s: %s", fileName.GetData(), error); return false; } if (doc == NULL) return false; root = doc->GetRoot(); if (root == NULL) { Error1("No root in XML"); return false; } // general settings camNode = root->GetNode("General"); if (camNode != NULL) { xmlbinds = camNode->GetNodes("camsetting"); while (xmlbinds->HasNext()) { settingNode = xmlbinds->Next(); csString settingName = settingNode->GetAttributeValue("name"); csString settingValue = settingNode->GetAttributeValue("value"); if (settingName == "UseCollisionDetection") useCameraCD = (settingValue.Upcase() == "ON"); else if (settingName == "TransitionThreshold") { transitionThresholdSquared = atof(settingValue.GetData()); transitionThresholdSquared *= transitionThresholdSquared; } else if (settingName == "StartingCameraMode" && !cameraInitialized) { int mode = atoi(settingValue.GetDataSafe()); if (mode < 0 || mode >= CAMERA_MODES_COUNT) mode = CAMERA_THIRD_PERSON; SetCameraMode(mode); } } } camNode = root->GetNode("DistanceClipping"); if (camNode != NULL) { xmlbinds = camNode->GetNodes("camsetting"); while (xmlbinds->HasNext()) { settingNode = xmlbinds->Next(); csString settingName = settingNode->GetAttributeValue("name"); csString settingValue = settingNode->GetAttributeValue("value"); if (settingName == "Adaptive") distanceCfg.adaptive = (settingValue.Upcase() == "ON"); else if (settingName == "Dist") distanceCfg.dist = atoi(settingValue.GetData()); else if (settingName == "MinFPS") distanceCfg.minFPS = atoi(settingValue.GetData()); else if (settingName == "MaxFPS") distanceCfg.maxFPS = atoi(settingValue.GetData()); else if (settingName == "MinDist") distanceCfg.minDist = atoi(settingValue.GetData()); } } // camera mode specific settings for (int c=0; cGetNode("FirstPerson"); break; case CAMERA_THIRD_PERSON: camNode = root->GetNode("ThirdPersonFollow"); break; case CAMERA_M64_THIRD_PERSON: camNode = root->GetNode("ThirdPersonM64"); break; case CAMERA_LARA_THIRD_PERSON: camNode = root->GetNode("ThirdPersonLara"); break; case CAMERA_FREE: camNode = root->GetNode("FreeRotation"); break; case CAMERA_ACTUAL_DATA: camNode = root->GetNode("CameraStart"); break; case CAMERA_TRANSITION: camNode = root->GetNode("CameraTransition"); break; case CAMERA_COLLISION: camNode = root->GetNode("CollisionBuffer"); } if (camNode != NULL) { xmlbinds = camNode->GetNodes("camsetting"); while (xmlbinds->HasNext()) { settingNode = xmlbinds->Next(); csString settingName = settingNode->GetAttributeValue("name"); csString settingValue = settingNode->GetAttributeValue("value"); if (settingName == "StartingPitch") { camData[c].pitch = atof(settingValue.GetData()); camData[c].defaultPitch = camData[c].pitch; } else if (settingName == "StartingYaw") { camData[c].yaw = atof(settingValue.GetData()); camData[c].defaultYaw = camData[c].yaw; } else if (settingName == "StartingRoll") { camData[c].roll = atof(settingValue.GetData()); camData[c].defaultRoll = camData[c].roll; } else if (settingName == "CameraDistance") camData[c].distance = atof(settingValue.GetData()); else if (settingName == "MinCameraDistance") camData[c].minDistance = atof(settingValue.GetData()); else if (settingName == "MaxCameraDistance") camData[c].maxDistance = atof(settingValue.GetData()); else if (settingName == "TurningSpeed") camData[c].turnSpeed = atof(settingValue.GetData()); else if (settingName == "SpringCoefficient") camData[c].springCoef = atof(settingValue.GetData()); else if (settingName == "DampeningCoefficient") camData[c].InertialDampeningCoef = atof(settingValue.GetData()); else if (settingName == "SpringLength") camData[c].springLength = atof(settingValue.GetData()); else if (settingName == "SwingCoefficient") camData[c].swingCoef = atof(settingValue.GetData()); } } } cameraInitialized = true; return true; } bool psCamera::SaveToFile() { if (!cameraInitialized) return false; // We don't have any settings to save yet! csString xml; xml += "\n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += "\n"; xml += "\n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += "\n"; xml += "\n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += "\n"; xml += "\n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += "\n"; xml += "\n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += "\n"; xml += "\n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += "\n"; xml += "\n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += "\n"; xml += "\n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += "\n"; xml += "\n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += " \n"; xml += "\n"; return psengine->GetVFS()->WriteFile("/this/data/options/camera.xml", xml.GetData(), xml.Length()); } bool psCamera::Draw() { if (!actor) // Not ready yet return false; AdaptDistanceClipping(); // calculate the elapsed time between this frame and the last one csTicks elapsedTicks = vc->GetElapsedTicks(); float elapsedSeconds = elapsedTicks / 1000.0f; // perform the velocity calculations MovePitch(pitchVelocity * elapsedSeconds); MoveYaw(yawVelocity * elapsedSeconds); // get actor info csVector3 actorPos; float actorYRot; iSector* actorSector; actor->linmove->GetLastPosition(actorPos, actorYRot, actorSector); actorYRot = SaturateAngle(actorYRot); // decide whether the current camera is elastic or not bool isElastic = true; // store the previous frame's ideal camera data (it will be compared against curr frame later to become the delta part) CameraData deltaIdeal; if (isElastic) { deltaIdeal.worldPos = GetPosition(); deltaIdeal.worldTar = GetTarget(); deltaIdeal.worldUp = GetUp(); } // calculate the eye position of the actor according to his eye offset (defined by the race definition) csVector3 actorEye = actorPos + csVector3(sin(actorYRot)*firstPersonPositionOffset.z, firstPersonPositionOffset.y, cos(actorYRot)*firstPersonPositionOffset.z); // calculate ideal camera data (won't affect the actual camera data yet) DoCameraIdealCalcs(elapsedTicks, actorPos, actorEye, actorYRot); if (!cameraHasBeenPositioned) { // this will only get called once to ensure that the camera starts off in an appropriate place cameraHasBeenPositioned = true; ResetActualCameraData(); } // transition phase calculations DoCameraTransition(); // this makes the deltaIdeal data true to it's delta wording by subtracting the current ideal data if (isElastic) { deltaIdeal.worldPos -= GetPosition(); deltaIdeal.worldTar -= GetTarget(); deltaIdeal.worldUp -= GetUp(); } // interpolate between ideal and actual camera data DoElasticPhysics(isElastic, elapsedTicks, deltaIdeal, actorSector); EnsureActorVisibility(); // tell CS to render the scene if (!psengine->GetG3D()->BeginDraw( psengine->GetEngine()->GetBeginDrawFlags() | CSDRAW_3DGRAPHICS)) return false; // if the actor changed sectors, then ensure that the position is correct (in case it's a warping portal) if (actorSector != lastActorSector && lastActorSector != 0) { // calculate the new actual data using relative error from last frame SetPosition(GetPosition() + GetPosition(CAMERA_ERR), CAMERA_ACTUAL_DATA); SetTarget(GetTarget() + GetTarget(CAMERA_ERR), CAMERA_ACTUAL_DATA); SetUp(GetUp() + GetUp(CAMERA_ERR), CAMERA_ACTUAL_DATA); } // assume the normal camera movement is good, and move the camera view->GetCamera()->SetSector(actorSector); view->GetCamera()->GetTransform().SetOrigin(actorPos + csVector3(0,1,0)); view->GetCamera()->OnlyPortals(true); view->GetCamera()->GetTransform().LookAt(GetTarget(CAMERA_ACTUAL_DATA) - GetPosition(CAMERA_ACTUAL_DATA), GetUp(CAMERA_ACTUAL_DATA)); view->GetCamera()->MoveWorld(GetPosition(CAMERA_ACTUAL_DATA) - view->GetCamera()->GetTransform().GetOrigin()); view->Draw(); // calculate the error of the camera SetPosition(GetPosition(CAMERA_ACTUAL_DATA) - GetPosition(), CAMERA_ERR); SetTarget(GetTarget(CAMERA_ACTUAL_DATA) - GetTarget(), CAMERA_ERR); SetUp(GetUp(CAMERA_ACTUAL_DATA) - GetUp(), CAMERA_ERR); lastActorSector = actorSector; // not needed at this point //CloneCameraModeData(CAMERA_ACTUAL_DATA, CAMERA_LAST_ACTUAL); return true; } void psCamera::SetPosition(const csVector3& pos, int mode) { if (mode < 0) mode = currCameraMode; camData[mode].worldPos = pos; } csVector3 psCamera::GetPosition(int mode) const { if (mode < 0) mode = currCameraMode; return camData[mode].worldPos; } void psCamera::SetTarget(const csVector3& tar, int mode) { if (mode < 0) mode = currCameraMode; camData[mode].worldTar = tar; } csVector3 psCamera::GetTarget(int mode) const { if (mode < 0) mode = currCameraMode; return camData[mode].worldTar; } void psCamera::SetUp(const csVector3& up, int mode) { if (mode < 0) mode = currCameraMode; camData[mode].worldUp = up; camData[mode].worldUp.Normalize(); } csVector3 psCamera::GetUp(int mode) const { if (mode < 0) mode = currCameraMode; return camData[mode].worldUp; } void psCamera::NextCameraMode() { if (GetCameraMode()+1 == CAMERA_ACTUAL_DATA) SetCameraMode(0); else SetCameraMode(GetCameraMode()+1); // display new mode onscreen psengine->GetMainWidget()->ClearFadingText(); psSystemMessage mode(0,MSG_ACK,PawsManager::GetSingleton().Translate(GetCameraModeVerbose())); mode.FireEvent(); } void psCamera::SetCameraMode(int mode) { lastCameraMode = currCameraMode; currCameraMode = mode; // reset camera velocities SetYawVelocity(0.0f); SetPitchVelocity(0.0f); // get actor info csVector3 actorPos; float actorYRot; iSector* actorSector; actor->linmove->GetLastPosition(actorPos, actorYRot, actorSector); csVector3 actorEye = actorPos + csVector3(sin(actorYRot)*firstPersonPositionOffset.z, firstPersonPositionOffset.y, cos(actorYRot)*firstPersonPositionOffset.z); PawsManager::GetSingleton().GetMouse()->WantCrosshair(false); switch (mode) { case CAMERA_FIRST_PERSON: if (psengine->GetCharControl() && psengine->GetCharControl()->GetMovementManager()->MouseLook()) PawsManager::GetSingleton().GetMouse()->WantCrosshair(); break; case CAMERA_THIRD_PERSON: SetPosition(actorPos + csVector3(sin(actorYRot)*thirdPersonPositionOffset.z, thirdPersonPositionOffset.y, cos(actorYRot)*thirdPersonPositionOffset.z)); SetYaw(actorYRot); break; case CAMERA_M64_THIRD_PERSON: //SetYaw(actorYRot); SetYaw(GetYaw(lastCameraMode)); SetDistance(GetMaxDistance()); EnsureCameraDistance(); CalculatePositionFromYawPitchRoll(); break; case CAMERA_LARA_THIRD_PERSON: //SetYaw(actorYRot); SetYaw(GetYaw(lastCameraMode)); SetDistance(GetMaxDistance()); EnsureCameraDistance(); CalculatePositionFromYawPitchRoll(); break; case CAMERA_FREE: //SetYaw(actorYRot); SetYaw(GetYaw(lastCameraMode)); break; } // enable transition phase inTransitionPhase = true; actor->pcmesh->GetMesh()->SetFlagsRecursive(CS_ENTITY_INVISIBLE, 0); } int psCamera::GetCameraMode() const { return currCameraMode; } csString psCamera::GetCameraModeVerbose() const { switch (currCameraMode) { case CAMERA_FIRST_PERSON: return csString("First Person"); case CAMERA_THIRD_PERSON: return csString("Third Person Follow"); case CAMERA_M64_THIRD_PERSON: return csString("Free Movement"); case CAMERA_LARA_THIRD_PERSON: return csString("Dynamic Follow"); case CAMERA_FREE: return csString("Free Rotation"); } return csString("Unknown"); } iCamera *psCamera::GetICamera() { return view->GetCamera(); } iView *psCamera::GetView() { return view; } iMeshWrapper *psCamera::Get3DPointFrom2D(int x, int y, csVector3 * worldCoord, csVector3 * untransfCoord) { if (!GetICamera()) return NULL; csVector3 vc, vo, vw; csVector2 perspective( x, GetICamera()->GetShiftY() * 2 - y ); vc = GetICamera()->InvPerspective( perspective, 1 ); vw = GetICamera()->GetTransform().This2Other( vc ); iSector* sector = GetICamera()->GetSector(); if ( sector ) { vo = GetICamera()->GetTransform().GetO2TTranslation(); csVector3 isect; csVector3 end = vo + (vw-vo)*600; csIntersectingTriangle closest_tri; iMeshWrapper* sel = 0; float dist = csColliderHelper::TraceBeam (cdsys, sector, vo, end, true, closest_tri, isect, &sel); if (worldCoord != 0) *worldCoord = isect; if (untransfCoord) *untransfCoord = vo + (vw-vo).Unit()*csQsqrt(dist); return sel; } return 0; /* csVector3 v; csVector2 p(x, psengine->GetG2D()->GetHeight()-y); GetICamera()->InvPerspective(p, 30, v); csVector3 vw = GetICamera()->GetTransform().This2Other(v); iSector* sector = GetICamera()->GetSector (); csVector3 origin = GetICamera()->GetTransform().GetO2TTranslation (); csVector3 isect; int sel; iMeshWrapper* mesh = sector->HitBeamPortals(origin, origin + (vw-origin) * 20, isect, &sel); if (mesh) { worldCoord = isect; return mesh; } csVector3 currPos; float yRot; iSector* sectorPos; actor->pcmove->GetLastPosition(currPos, yRot, sectorPos); worldCoord = currPos; return NULL; */ } iMeshWrapper* psCamera::FindMeshUnder2D(int x, int y, csVector3 *pos, int *poly) { if (!GetICamera()) return NULL; csVector3 vc, vo, vw; csVector2 perspective( x, GetICamera()->GetShiftY() * 2 - y ); vc = GetICamera()->InvPerspective( perspective, 1 ); vw = GetICamera()->GetTransform().This2Other( vc ); iSector* sector = GetICamera()->GetSector(); if ( sector ) { vo = GetICamera()->GetTransform().GetO2TTranslation(); csVector3 end = vo + (vw-vo)*600; csSectorHitBeamResult result; result = sector->HitBeamPortals( vo, end ); //iMeshWrapper* sel = sector->HitBeamPortals(vo, end, isect, poly); if ( pos != NULL ) *pos = result.isect; return result.mesh; } return NULL; } void psCamera::SetPitch(float pitch, int mode) { if (mode < 0) mode = currCameraMode; if (pitch > 3.1415f/2.1f) pitch = 3.1415f/2.1f; if (pitch < -3.1415f/2.1f) pitch = -3.1415f/2.1f; camData[mode].pitch = pitch; } void psCamera::MovePitch(float deltaPitch, int mode) { if (mode < 0) mode = currCameraMode; SetPitch(GetPitch(mode) + deltaPitch, mode); } float psCamera::GetPitch(int mode) const { if (mode < 0) mode = currCameraMode; return camData[mode].pitch; } void psCamera::SetPitchVelocity(float pitchVel) { pitchVelocity = pitchVel; } float psCamera::GetPitchVelocity() const { return pitchVelocity; } void psCamera::SetYaw(float yaw, int mode) { if (mode < 0) mode = currCameraMode; camData[mode].yaw = SaturateAngle(yaw); } void psCamera::MoveYaw(float deltaYaw, int mode) { if (mode < 0) mode = currCameraMode; SetYaw(GetYaw(mode) + deltaYaw, mode); } float psCamera::GetYaw(int mode) const { if (mode < 0) mode = currCameraMode; return camData[mode].yaw; } void psCamera::SetYawVelocity(float yawVel) { yawVelocity = yawVel; } float psCamera::GetYawVelocity() const { return yawVelocity; } void psCamera::SetDistance(float distance, int mode) { if (mode < 0) mode = currCameraMode; if (distance > camData[mode].maxDistance) distance = camData[mode].maxDistance; else if (distance < camData[mode].minDistance) distance = camData[mode].minDistance; camData[mode].distance = distance; } void psCamera::MoveDistance(float deltaDistance, int mode) { SetDistance(GetDistance(mode) + deltaDistance, mode); } float psCamera::GetDistance(int mode) const { if (mode < 0) mode = currCameraMode; return camData[mode].distance; } void psCamera::ResetActualCameraData() { SetPosition(GetPosition(), CAMERA_ACTUAL_DATA); SetTarget(GetTarget(), CAMERA_ACTUAL_DATA); SetUp(GetUp(), CAMERA_ACTUAL_DATA); } void psCamera::ResetCameraPositioning() { cameraHasBeenPositioned = false; } bool psCamera::RotateCameraWithPlayer() const { // it's easier to specify by what modes don't move with the camera return !(GetCameraMode() == CAMERA_M64_THIRD_PERSON || GetCameraMode() == CAMERA_LARA_THIRD_PERSON || GetCameraMode() == CAMERA_FREE); } csVector3 psCamera::GetForwardVector(int mode) const { if (mode < 0) mode = currCameraMode; csVector3 dir = GetTarget(mode) - GetPosition(mode); dir.Normalize(); return dir; } csVector3 psCamera::GetRightVector(int mode) const { if (mode < 0) mode = currCameraMode; csVector3 dir = GetTarget(mode) - GetPosition(mode); csVector3 right = GetUp(mode) % dir; right.Normalize(); return right; } float psCamera::GetMinDistance(int mode) const { if (mode < 0) mode = currCameraMode; return camData[mode].minDistance; } void psCamera::SetMinDistance(float dist, int mode) { if (mode < 0) mode = currCameraMode; camData[mode].minDistance = dist; } float psCamera::GetMaxDistance(int mode) const { if (mode < 0) mode = currCameraMode; return camData[mode].maxDistance; } void psCamera::SetMaxDistance(float dist, int mode) { if (mode < 0) mode = currCameraMode; camData[mode].maxDistance = dist; } float psCamera::GetTurnSpeed(int mode) const { if (mode < 0) mode = currCameraMode; return camData[mode].turnSpeed; } void psCamera::SetTurnSpeed(float speed, int mode) { if (mode < 0) mode = currCameraMode; camData[mode].turnSpeed = speed; } float psCamera::GetSpringCoef(int mode) const { if (mode < 0) mode = currCameraMode; return camData[mode].springCoef; } void psCamera::SetSpringCoef(float coef, int mode) { if (mode < 0) mode = currCameraMode; camData[mode].springCoef = coef; } float psCamera::GetDampeningCoef(int mode) const { if (mode < 0) mode = currCameraMode; return camData[mode].InertialDampeningCoef; } void psCamera::SetDampeningCoef(float coef, int mode) { if (mode < 0) mode = currCameraMode; camData[mode].InertialDampeningCoef = coef; } float psCamera::GetSpringLength(int mode) const { if (mode < 0) mode = currCameraMode; return camData[mode].springLength; } void psCamera::SetSpringLength(float length, int mode) { if (mode < 0) mode = currCameraMode; camData[mode].springLength = length; } bool psCamera::CheckCameraCD() const { return useCameraCD; } void psCamera::SetCameraCD(bool useCD) { useCameraCD = useCD; } float psCamera::GetTransitionThreshold() const { return sqrt(transitionThresholdSquared); } void psCamera::SetTransitionThreshold(float threshold) { transitionThresholdSquared = threshold*threshold; } float psCamera::GetDefaultPitch(int mode) const { if (mode < 0) mode = currCameraMode; return camData[mode].defaultPitch; } void psCamera::SetDefaultPitch(float pitch, int mode) { if (mode < 0) mode = currCameraMode; camData[mode].defaultPitch = SaturateAngle(pitch); } float psCamera::GetDefaultYaw(int mode) const { if (mode < 0) mode = currCameraMode; return camData[mode].defaultYaw; } void psCamera::SetDefaultYaw(float yaw, int mode) { if (mode < 0) mode = currCameraMode; camData[mode].defaultYaw = SaturateAngle(yaw); } float psCamera::GetSwingCoef(int mode) const { if (mode < 0) mode = currCameraMode; return camData[mode].swingCoef; } void psCamera::SetSwingCoef(float swingCoef, int mode) { if (mode < 0) mode = currCameraMode; camData[mode].swingCoef = swingCoef; } void psCamera::UseFixedDistanceClipping(float dist) { distanceCfg.adaptive = false; distanceCfg.dist = (int)dist; SetDistanceClipping(dist); } void psCamera::UseAdaptiveDistanceClipping(int minFPS, int maxFPS, int minDist) { distanceCfg.adaptive = true; distanceCfg.minFPS = minFPS; distanceCfg.maxFPS = maxFPS; distanceCfg.minDist = minDist; if (GetDistanceClipping() < minDist) SetDistanceClipping(minDist); } psCamera::DistanceCfg psCamera::GetDistanceCfg() { return distanceCfg; } void psCamera::DoCameraIdealCalcs(const csTicks elapsedTicks, const csVector3& actorPos, const csVector3& actorEye, const float actorYRot) { csVector3 velocity; actor->linmove->GetVelocity(velocity); float velNormSquared = velocity.SquaredNorm(); switch (GetCameraMode()) { case CAMERA_FIRST_PERSON: SetDistance(1); SetPosition(actorEye); SetYaw(actorYRot); CalculateFromYawPitchRoll(); SetPosition(GetPosition() + (GetPosition()-GetTarget())*0.1f); break; case CAMERA_THIRD_PERSON: SetTarget(actorEye); SetYaw(actorYRot); CalculatePositionFromYawPitchRoll(); break; case CAMERA_M64_THIRD_PERSON: SetTarget(actorEye); SetDistance((GetTarget()-GetPosition()).Norm()); EnsureCameraDistance(); CalculatePositionFromYawPitchRoll(); break; case CAMERA_LARA_THIRD_PERSON: SetTarget(actorEye); if (velNormSquared > 0.01f) { // when the player is running (only), a new position is interpolated // so with this camera mode, two springs are modelled, but the second // spring is ignored unless the actor is moving // calculate where the camera would be if there weren't a swing effect csVector3 newIdealPos = actorPos + csVector3(sin(actorYRot)*thirdPersonPositionOffset.z, thirdPersonPositionOffset.y, cos(actorYRot)*thirdPersonPositionOffset.z); // interpolate to the new calculated position SetPosition(CalcElasticPos(GetPosition(), newIdealPos, 0, (float)elapsedTicks/1000.0f, GetSwingCoef(), 0.0f, GetSpringLength())); SetYaw(CalculateNewYaw(GetTarget()-GetPosition())); } // ensure valid distance SetDistance((GetTarget()-GetPosition()).Norm()); EnsureCameraDistance(); // this allows pitch to work // note that this doesn't really use the yaw calculation, // because whenever the position is modified (above), a new yaw that // represents the (position - target) vector is calculated. This ensures // that this function won't change the yaw at all and only handle // the pitch. CalculatePositionFromYawPitchRoll(); break; case CAMERA_FREE: SetTarget(actorEye); EnsureCameraDistance(); CalculatePositionFromYawPitchRoll(); break; } } csVector3 psCamera::CalcCollisionPos(const csVector3& pseudoTarget, const csVector3& pseudoPosition, iSector* sector) { hasCollision = false; if (!useCameraCD) return pseudoPosition; // no collision detection actor->pcmesh->GetMesh()->GetFlags().Set(CS_ENTITY_NOHITBEAM); switch (GetCameraMode()) { case CAMERA_THIRD_PERSON: case CAMERA_M64_THIRD_PERSON: case CAMERA_LARA_THIRD_PERSON: case CAMERA_FREE: csVector3 isect; csIntersectingTriangle closest_tri; csVector3 modifiedTarget = pseudoTarget; //iMeshWrapper* mesh = sector->HitBeamPortals(modifiedTarget, pseudoPosition, isect, &sel); // iMeshWrapper * mesh; csColliderHelper::TraceBeam(cdsys, sector, modifiedTarget, pseudoPosition, true, closest_tri, isect, &mesh); if (mesh) { actor->pcmesh->GetMesh()->GetFlags().Reset(CS_ENTITY_NOHITBEAM); hasCollision = true; return isect + (modifiedTarget-isect)*0.1f; } // csRef spstate = SCF_QUERY_INTERFACE(mesh->GetMeshObject(), iSpriteCal3DState); break; } actor->pcmesh->GetMesh()->GetFlags().Reset(CS_ENTITY_NOHITBEAM); return pseudoPosition; } void psCamera::DoCameraTransition() { if (inTransitionPhase) { if ((GetPosition() - GetPosition(CAMERA_ACTUAL_DATA)).SquaredNorm() < transitionThresholdSquared) { inTransitionPhase = false; } } } void psCamera::DoElasticPhysics(bool isElastic, const csTicks elapsedTicks, const CameraData& deltaIdeal, iSector* sector) { // if the camera mode is elastic then progress gradually to the ideal pos if (isElastic) { csVector3 newPseudoPos = CalcCollisionPos(GetTarget(), GetPosition(), sector); float cameraSpringCoef, cameraInertialDampeningCoef, cameraSpringLength; if (hasCollision) { cameraSpringCoef = GetSpringCoef(CAMERA_COLLISION); cameraInertialDampeningCoef = GetDampeningCoef(CAMERA_COLLISION); cameraSpringLength = GetSpringLength(CAMERA_COLLISION); } else if (!inTransitionPhase) { cameraSpringCoef = GetSpringCoef(); cameraInertialDampeningCoef = GetDampeningCoef(); cameraSpringLength = GetSpringLength(); } else { cameraSpringCoef = GetSpringCoef(CAMERA_TRANSITION); cameraInertialDampeningCoef = GetDampeningCoef(CAMERA_TRANSITION); cameraSpringLength = GetSpringLength(CAMERA_TRANSITION); } csVector3 velIdealPos, velIdealTar, velIdealUp, newPos, newTar, newUp; newPos = CalcElasticPos(GetPosition(CAMERA_ACTUAL_DATA), newPseudoPos, deltaIdeal.worldPos, (float)elapsedTicks/1000.0f, cameraSpringCoef, cameraInertialDampeningCoef, cameraSpringLength); SetPosition(newPos, CAMERA_ACTUAL_DATA); newTar = CalcElasticPos(GetTarget(CAMERA_ACTUAL_DATA), GetTarget(), deltaIdeal.worldTar, (float)elapsedTicks/1000.0f, cameraSpringCoef, cameraInertialDampeningCoef, cameraSpringLength); SetTarget(newTar, CAMERA_ACTUAL_DATA); newUp = CalcElasticPos(GetUp(CAMERA_ACTUAL_DATA), GetUp(), deltaIdeal.worldUp, (float)elapsedTicks/1000.0f, cameraSpringCoef, cameraInertialDampeningCoef, cameraSpringLength); SetUp(newUp, CAMERA_ACTUAL_DATA); } else { // camera isn't elastic, so no interpolation is done between ideal and actual SetPosition(CalcCollisionPos(GetTarget(), GetPosition(), sector), CAMERA_ACTUAL_DATA); SetTarget(GetTarget(), CAMERA_ACTUAL_DATA); SetUp(GetUp(), CAMERA_ACTUAL_DATA); } } void psCamera::EnsureActorVisibility() { actor->pcmesh->GetMesh()->SetFlagsRecursive(CS_ENTITY_INVISIBLE, CS_ENTITY_INVISIBLE); // make the actor visible if the camera mode calls for it, and // the camera isn't too close to the player if (IsActorVisible() || inTransitionPhase) { if ((GetPosition(CAMERA_ACTUAL_DATA)-GetTarget(CAMERA_ACTUAL_DATA)).SquaredNorm() > 0.3f) actor->pcmesh->GetMesh()->SetFlagsRecursive(CS_ENTITY_INVISIBLE, 0); } } csVector3 psCamera::CalcElasticPos(csVector3 currPos, csVector3 idealPos, csVector3 deltaIdealPos, float deltaTime, float springCoef, float dampCoef, float springLength) const { csVector3 deltaPos; csVector3 vel; float force; deltaPos = currPos - idealPos; if (deltaPos.SquaredNorm() == 0) return currPos; vel = deltaIdealPos * deltaTime; force = springCoef * (springLength - deltaPos.Norm()) + dampCoef * (deltaPos * vel) / deltaPos.Norm(); float dist = deltaPos.Norm(); //printf("DIST: %7.3f, force*deltaTime: %7.3f \n", dist, force*deltaTime); if (-dist < force * deltaTime) deltaPos *= force * deltaTime / dist; else deltaPos *= -1; return currPos + deltaPos; } float psCamera::CalcElasticFloat(float curr, float ideal, float deltaIdeal, float deltaTime, float springCoef, float dampCoef, float springLength) const { float delta; float vel; float force; delta = curr - ideal; if (delta == 0) return curr; vel = deltaIdeal * deltaTime; force = springCoef * (springLength - delta) + dampCoef * (delta * vel) / delta; if (-delta < force * deltaTime) delta = force * deltaTime; else delta *= -1; return curr + delta; } void psCamera::CalculateFromYawPitchRoll(int mode) { float cosYaw, sinYaw; float cosPit, sinPit; float cosRol, sinRol; cosYaw = (float)cos(GetYaw(mode)); sinYaw = (float)sin(GetYaw(mode)); cosPit = (float)cos(GetPitch(mode)); sinPit = (float)sin(GetPitch(mode)); cosRol = 1.0f; sinRol = 0.0f; // at this point, our camera doesn't support Roll if (cosPit == 0.0f) cosPit = 0.001f; SetTarget(GetPosition(mode) + (GetDistance(mode) * csVector3(-sinYaw * cosPit, sinPit, cosPit * -cosYaw))); } void psCamera::CalculateNewYawPitchRoll(int mode) { // arcsin((dir / dist).y) SetPitch((float)asin((((GetTarget(mode).y - GetPosition(mode).y) / GetDistance(mode)))), mode); // arccos((dir / dist).z / -cos(pitch)) SetYaw((float)acos(((GetTarget(mode).z - GetPosition(mode).z) / GetDistance(mode)) / -(float)cos(GetPitch(mode))), mode); // but have to check the z value for possible vertical flip if (GetTarget(mode).x > GetPosition(mode).x) SetYaw(2*3.14159f - GetYaw(mode), mode); } void psCamera::CalculatePositionFromYawPitchRoll(int mode) { float cosYaw, sinYaw; float cosPit, sinPit; float cosRol, sinRol; cosYaw = (float)cos(GetYaw(mode)); sinYaw = (float)sin(GetYaw(mode)); cosPit = (float)cos(GetPitch(mode)); sinPit = (float)sin(GetPitch(mode)); cosRol = 1.0f; sinRol = 0.0f; // at this point, our camera doesn't support Roll if (cosPit == 0.0f) cosPit = 0.001f; SetPosition(GetTarget(mode) - (GetDistance(mode) * csVector3(-sinYaw * cosPit, sinPit, // we have to reverse the vertical thing cosPit * -cosYaw))); } float psCamera::CalculateNewYaw(csVector3 dir) { if (dir.x == 0.0f) dir.x = 0.00001f; return atan2(-dir.x, -dir.z); } void psCamera::EnsureCameraDistance(int mode) { if (GetDistance(mode) > GetMaxDistance(mode)) SetDistance(GetMaxDistance(mode), mode); else if (GetDistance(mode) < GetMinDistance(mode)) SetDistance(GetMinDistance(mode), mode); } bool psCamera::IsActorVisible(int mode) const { if (mode < 0 || mode == CAMERA_ACTUAL_DATA) mode = currCameraMode; return (mode != CAMERA_FIRST_PERSON); } float psCamera::SaturateAngle(float angle) const { while (angle >= 3.14159f) angle -= 2*3.14159f; while (angle < -3.14169f) angle += 2*3.14159f; return angle; } bool psCamera::CloneCameraModeData(int fromMode, int toMode) { SetPosition(GetPosition(fromMode), toMode); SetTarget(GetTarget(fromMode), toMode); SetUp(GetUp(fromMode), toMode); SetPitch(GetPitch(fromMode), toMode); SetYaw(GetYaw(fromMode), toMode); //SetRoll(GetRoll(fromMode), toMode); SetDefaultPitch(GetDefaultPitch(fromMode), toMode); SetDefaultYaw(GetDefaultYaw(fromMode), toMode); //SetDefaultRoll(GetDefaultRoll(fromMode), toMode); SetDistance(GetDistance(fromMode), toMode); SetMinDistance(GetMinDistance(fromMode), toMode); SetMaxDistance(GetMaxDistance(fromMode), toMode); SetTurnSpeed(GetTurnSpeed(fromMode), toMode); SetSpringCoef(GetSpringCoef(fromMode), toMode); SetDampeningCoef(GetDampeningCoef(fromMode), toMode); SetSpringLength(GetSpringLength(fromMode), toMode); SetSwingCoef(GetSwingCoef(fromMode), toMode); return true; } void psCamera::SetDistanceClipping(float dist) { csVector3 v1(0, 0, dist), v2(0, 1, dist), v3(1, 0, dist); csPlane3 p(v1, v2, v3); view->GetCamera()->SetFarPlane(&p); } float psCamera::GetDistanceClipping() { csPlane3 *p; p = view->GetCamera()->GetFarPlane(); if (p != NULL) return p->DD / sqrt(p->norm.x*p->norm.x + p->norm.y*p->norm.y + p->norm.z*p->norm.z); else return -1; } void psCamera::AdaptDistanceClipping() { const float MAX_DIST = 500; // maximum visibile distance that is never crossed const float INITIAL_DISTANCE = 200; // we begin from this value static bool calledFirstTime = true; static csTicks lastTime; // when was this method last called ? csTicks currTime; // the current time static float lastChangeTime; // when did we change the distance last time ? float currFPS; // FPS calculated from the last frame static float smoothFPS = 30; // smoothed FPS - less influeced by chaotic FPS movements float currDist; // current distance limit csString debug; // when we are called for the first time, we just initialize some variables and exit if (calledFirstTime) { calledFirstTime = false; lastTime = csGetTicks(); lastChangeTime = csGetTicks(); return; } currTime = csGetTicks(); if (currTime == lastTime) currFPS = 1000; // ha, ha .... this is just a theoretial check ;-) else currFPS = 1000 / (currTime - lastTime); smoothFPS = 0.5*currFPS + 0.5*smoothFPS; //debug.AppendFmt("fps=%.0f avg=%.0f ", currFPS, smoothFPS); if (distanceCfg.adaptive && currTime-lastChangeTime>1000*2) { float change; currDist = GetDistanceClipping(); if (currDist == -1) currDist = INITIAL_DISTANCE; if (smoothFPS < distanceCfg.minFPS) { change = - (distanceCfg.minFPS-smoothFPS); currDist += change; if (currDist >= distanceCfg.minDist) SetDistanceClipping(currDist); //debug.AppendFmt("%.5f-=%.5f \n",currDist, change); } else if (smoothFPS > distanceCfg.maxFPS) { change = (smoothFPS-distanceCfg.maxFPS); currDist += change; if (currDist <= MAX_DIST) SetDistanceClipping(currDist); //debug.AppendFmt("%.5f+=%.5f \n",currDist, change); } lastChangeTime = currTime; } //Error2("%s", debug.GetData()); lastTime = currTime; }