// 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 <psconfig.h>

#include <iengine/camera.h>
#include <cstool/csview.h>
#include <ivideo/graph2d.h>
#include <ivideo/graph3d.h>
#include <csutil/flags.h>
#include <iengine/mesh.h>
#include <csutil/xmltiny.h>
#include <imesh/object.h>
#include <imesh/spritecal3d.h>
#include <cstool/collider.h>
#include <ivaria/collider.h>
#include <csqsqrt.h>
#include <csgeom/plane3.h>
#include "util/psstring.h"
#include "globals.h"
#include "pscamera.h"
#include "pscelclient.h"
#include "pscharcontrol.h"
#include "gui/psmainwidget.h"


psCamera::psCamera()
{
    view = csPtr<iView> (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; i<CAMERA_MODES_COUNT; i++)
    {
        SetPosition(pos, i);
        SetTarget(pos, i);
        SetUp(csVector3(0, 1, 0), i);
        SetDistance(5.0f, i);
        SetPitch(0.0f, i);
        SetDefaultPitch(0.0f, i);
    }

    // the error has to be set to 0 to begin with
    SetPosition(csVector3(0,0,0), CAMERA_ERR);
    SetTarget(csVector3(0,0,0), CAMERA_ERR);
    SetUp(csVector3(0,0,0), CAMERA_ERR);

    // load settings from a file
    if (!LoadFromFile())
        Error1("Failed to load camera options");


    // grab race definition for camera offsets
    csString raceDef(psengine->GetCelClient()->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<iDocument> doc;
    csRef<iDocumentNode> root, camNode, settingNode;
    csRef<iDocumentNodeIterator> xmlbinds;
    csRef<iVFS> vfs;
    csRef<iDocumentSystem>  xml;
    const char* error;

    vfs = psengine->GetVFS();
    assert(vfs);
    csRef<iDataBuffer> 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; c<CAMERA_MODES_COUNT; c++)
    {
        switch (c)
        {
            case CAMERA_FIRST_PERSON:
                camNode = root->GetNode("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 += "<General>\n";
    xml += "    <camsetting name=\"StartingCameraMode\" value=\"";      xml += GetCameraMode();                             xml += "\" />\n";
    xml += "    <camsetting name=\"UseCollisionDetection\" value=\"";   xml += (CheckCameraCD() ? "on" : "off");            xml += "\" />\n";
    xml += "    <camsetting name=\"TransitionThreshold\" value=\"";     xml += GetTransitionThreshold();                    xml += "\" />\n";
    xml += "</General>\n";

    xml += "<CameraTransition>\n";
    xml += "    <camsetting name=\"SpringLength\" value=\"";            xml += GetSpringLength(CAMERA_TRANSITION);          xml += "\" />\n";
    xml += "    <camsetting name=\"SpringCoefficient\" value=\"";       xml += GetSpringCoef(CAMERA_TRANSITION);            xml += "\" />\n";
    xml += "    <camsetting name=\"DampeningCoefficient\" value=\"";    xml += GetDampeningCoef(CAMERA_TRANSITION);         xml += "\" />\n";
    xml += "</CameraTransition>\n";

    xml += "<CollisionBuffer>\n";
    xml += "    <camsetting name=\"SpringLength\" value=\"";            xml += GetSpringLength(CAMERA_COLLISION);          xml += "\" />\n";
    xml += "    <camsetting name=\"SpringCoefficient\" value=\"";       xml += GetSpringCoef(CAMERA_COLLISION);            xml += "\" />\n";
    xml += "    <camsetting name=\"DampeningCoefficient\" value=\"";    xml += GetDampeningCoef(CAMERA_COLLISION);         xml += "\" />\n";
    xml += "</CollisionBuffer>\n";
    
    xml += "<FirstPerson>\n";
    xml += "    <camsetting name=\"SpringLength\" value=\"";            xml += GetSpringLength(CAMERA_FIRST_PERSON);        xml += "\" />\n";
    xml += "    <camsetting name=\"SpringCoefficient\" value=\"";       xml += GetSpringCoef(CAMERA_FIRST_PERSON);          xml += "\" />\n";
    xml += "    <camsetting name=\"DampeningCoefficient\" value=\"";    xml += GetDampeningCoef(CAMERA_FIRST_PERSON);       xml += "\" />\n";
    xml += "    <camsetting name=\"StartingPitch\" value=\"";           xml += GetDefaultPitch(CAMERA_FIRST_PERSON);        xml += "\" />\n";
    xml += "</FirstPerson>\n";

    xml += "<ThirdPersonFollow>\n";
    xml += "    <camsetting name=\"SpringLength\" value=\"";            xml += GetSpringLength(CAMERA_THIRD_PERSON);        xml += "\" />\n";
    xml += "    <camsetting name=\"SpringCoefficient\" value=\"";       xml += GetSpringCoef(CAMERA_THIRD_PERSON);          xml += "\" />\n";
    xml += "    <camsetting name=\"DampeningCoefficient\" value=\"";    xml += GetDampeningCoef(CAMERA_THIRD_PERSON);       xml += "\" />\n";
    xml += "    <camsetting name=\"CameraDistance\" value=\"";          xml += GetDistance(CAMERA_THIRD_PERSON);            xml += "\" />\n";
    xml += "    <camsetting name=\"StartingPitch\" value=\"";           xml += GetDefaultPitch(CAMERA_THIRD_PERSON);        xml += "\" />\n";
    xml += "</ThirdPersonFollow>\n";

    xml += "<ThirdPersonM64>\n";
    xml += "    <camsetting name=\"SpringLength\" value=\"";            xml += GetSpringLength(CAMERA_M64_THIRD_PERSON);    xml += "\" />\n";
    xml += "    <camsetting name=\"SpringCoefficient\" value=\"";       xml += GetSpringCoef(CAMERA_M64_THIRD_PERSON);      xml += "\" />\n";
    xml += "    <camsetting name=\"DampeningCoefficient\" value=\"";    xml += GetDampeningCoef(CAMERA_M64_THIRD_PERSON);   xml += "\" />\n";
    xml += "    <camsetting name=\"MaxCameraDistance\" value=\"";       xml += GetMaxDistance(CAMERA_M64_THIRD_PERSON);     xml += "\" />\n";
    xml += "    <camsetting name=\"MinCameraDistance\" value=\"";       xml += GetMinDistance(CAMERA_M64_THIRD_PERSON);     xml += "\" />\n";
    xml += "    <camsetting name=\"StartingPitch\" value=\"";           xml += GetDefaultPitch(CAMERA_M64_THIRD_PERSON);    xml += "\" />\n";
    xml += "    <camsetting name=\"TurningSpeed\" value=\"";            xml += GetTurnSpeed(CAMERA_M64_THIRD_PERSON);       xml += "\" />\n";
    xml += "</ThirdPersonM64>\n";

    xml += "<ThirdPersonLara>\n";
    xml += "    <camsetting name=\"SpringLength\" value=\"";            xml += GetSpringLength(CAMERA_LARA_THIRD_PERSON);   xml += "\" />\n";
    xml += "    <camsetting name=\"SpringCoefficient\" value=\"";       xml += GetSpringCoef(CAMERA_LARA_THIRD_PERSON);     xml += "\" />\n";
    xml += "    <camsetting name=\"DampeningCoefficient\" value=\"";    xml += GetDampeningCoef(CAMERA_LARA_THIRD_PERSON);  xml += "\" />\n";
    xml += "    <camsetting name=\"MaxCameraDistance\" value=\"";       xml += GetMaxDistance(CAMERA_LARA_THIRD_PERSON);    xml += "\" />\n";
    xml += "    <camsetting name=\"MinCameraDistance\" value=\"";       xml += GetMinDistance(CAMERA_LARA_THIRD_PERSON);    xml += "\" />\n";
    xml += "    <camsetting name=\"StartingPitch\" value=\"";           xml += GetDefaultPitch(CAMERA_LARA_THIRD_PERSON);   xml += "\" />\n";
    xml += "    <camsetting name=\"TurningSpeed\" value=\"";            xml += GetTurnSpeed(CAMERA_LARA_THIRD_PERSON);      xml += "\" />\n";
    xml += "    <camsetting name=\"SwingCoefficient\" value=\"";        xml += GetSwingCoef(CAMERA_LARA_THIRD_PERSON);      xml += "\" />\n";
    xml += "</ThirdPersonLara>\n";

    xml += "<FreeRotation>\n";
    xml += "    <camsetting name=\"SpringLength\" value=\"";            xml += GetSpringLength(CAMERA_FREE);                xml += "\" />\n";
    xml += "    <camsetting name=\"SpringCoefficient\" value=\"";       xml += GetSpringCoef(CAMERA_FREE);                  xml += "\" />\n";
    xml += "    <camsetting name=\"DampeningCoefficient\" value=\"";    xml += GetDampeningCoef(CAMERA_FREE);               xml += "\" />\n";
    xml += "    <camsetting name=\"MaxCameraDistance\" value=\"";       xml += GetMaxDistance(CAMERA_FREE);                 xml += "\" />\n";
    xml += "    <camsetting name=\"MinCameraDistance\" value=\"";       xml += GetMinDistance(CAMERA_FREE);                 xml += "\" />\n";
    xml += "    <camsetting name=\"StartingPitch\" value=\"";           xml += GetDefaultPitch(CAMERA_FREE);                xml += "\" />\n";
    xml += "</FreeRotation>\n";

    xml += "<DistanceClipping>\n";
    xml += "    <camsetting name=\"Adaptive\" value=\"";                xml += distanceCfg.adaptive ? "on" : "off";         xml += "\" />\n";
    xml += "    <camsetting name=\"Dist\" value=\"";                    xml += distanceCfg.dist;                            xml += "\" />\n";
    xml += "    <camsetting name=\"MinFPS\" value=\"";                  xml += distanceCfg.minFPS;                          xml += "\" />\n";
    xml += "    <camsetting name=\"MaxFPS\" value=\"";                  xml += distanceCfg.maxFPS;                          xml += "\" />\n";
    xml += "    <camsetting name=\"MinDist\" value=\"";                 xml += distanceCfg.minDist;                         xml += "\" />\n";
    xml += "</DistanceClipping>\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 );
    GetICamera()->InvPerspective( perspective, 1, vc );
    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 );
    GetICamera()->InvPerspective( perspective, 1, vc );
    vw = GetICamera()->GetTransform().This2Other( vc );

    iSector* sector = GetICamera()->GetSector();

    if ( sector )
    {
        vo = GetICamera()->GetTransform().GetO2TTranslation();
        csVector3 isect;
        csVector3 end = vo + (vw-vo)*600;

        iMeshWrapper* sel = sector->HitBeamPortals(vo, end, isect, poly);
        if ( pos != NULL ) *pos = isect;
        return sel;
    }
    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<iSpriteCal3DState> 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;
}


