220
%{
#include "StdH.h"
%}

uses "Entities/Player";
uses "Entities/CameraMarker";

class CCamera : CMovableModelEntity
{
name      "Camera";
thumbnail "Thumbnails\\Camera.tbn";
features  "HasName", "IsTargetable", "IsImportant";


properties:

  1 FLOAT m_tmTime             "Time" 'E' = 5.0f,       // how long to show the scene
  2 FLOAT m_fFOV               "FOV" 'F' = 90.0f,       // camera fov
  5 FLOAT m_fLastFOV = 90.0f,
  3 CEntityPointer m_penTarget "Target" 'T' COLOR(C_lBLUE|0xFF),
  4 CTString m_strName         "Name" 'N' = "Camera",
  6 CEntityPointer m_penOnBreak "OnBreak" 'B' COLOR(C_lRED|0xFF),
  7 BOOL m_bWideScreen "WideScreen" 'W' = TRUE,

 10 FLOAT m_tmAtMarker = 0.0f, // time when current marker was reached
 11 FLOAT m_tmDelta = 0.0f, // time to reach next marker
 13 FLOAT3D m_vPNp0 = FLOAT3D(0,0,0),
 14 FLOAT3D m_vPNp1 = FLOAT3D(0,0,0),
 15 FLOAT3D m_vTNp0 = FLOAT3D(0,0,0),
 16 FLOAT3D m_vTNp1 = FLOAT3D(0,0,0),
 17 FLOAT m_fFOVp0 = 0.0f,
 18 FLOAT m_fFOVp1 = 0.0f,
 19 FLOAT m_fTFOVp0 = 0.0f,
 20 FLOAT m_fTFOVp1 = 0.0f,
 31 FLOATquat3D m_qPNp0 = FLOATquat3D(0,0,0,0),
 32 FLOATquat3D m_qPNp1 = FLOATquat3D(0,0,0,0),
 33 FLOATquat3D m_qANp0 = FLOATquat3D(0,0,0,0),
 34 FLOATquat3D m_qANp1 = FLOATquat3D(0,0,0,0),
 
 40 CEntityPointer m_penLast,    // previous marker
 41 CEntityPointer m_penPlayer,  // player viewing this camera
 42 CTString m_strDescription = "",
 43 BOOL m_bStopMoving = FALSE,       // stop moving camera on next target

 50 COLOR m_colFade0 = 0,     // camera fading color
 51 COLOR m_colFade1 = 0,
 52 BOOL m_bMoving = FALSE,   // set while moving

components:

  1 model   MODEL_CAMERA     "Models\\Editor\\Camera.mdl",
  2 texture TEXTURE_CAMERA   "Models\\Editor\\Camera.tex"


functions:

  // render particles
  void RenderParticles(void)
  {
    if (Particle_GetViewer()==this) {
      Particles_ViewerLocal(this);
    }
  }

  // Check if entity is moved on a route set up by its targets
  BOOL MovesByTargetedRoute( CTString &strTargetProperty) const
  {
    strTargetProperty = "Target";
    return TRUE;
  }

  // Check if entity can drop marker for making linked route
  BOOL DropsMarker( CTFileName &fnmMarkerClass, CTString &strTargetProperty) const
  {
    fnmMarkerClass = CTFILENAME( "Classes\\CameraMarker.ecl");
    strTargetProperty = "Target";
    return TRUE;
  }

  // returns camera description
  const CTString &GetDescription(void) const
  {
    if (m_penTarget!=NULL) {
      ((CTString&)m_strDescription).PrintF("->%s", m_penTarget->GetName());
    } else {
      ((CTString&)m_strDescription).PrintF("-><none>");
    }
    return m_strDescription;
  }


  CPlacement3D GetLerpedPlacement(void) const
  {
    FLOAT fLerpFactor;
    if (IsPredictor()) {
      fLerpFactor = _pTimer->GetLerpFactor();
    } else {
      fLerpFactor = _pTimer->GetLerpFactor2();
    }
    return LerpPlacementsPrecise(en_plLastPlacement, en_plPlacement, fLerpFactor);
    //return CMovableEntity::GetLerpedPlacement();
  }

  void PreMoving()
  {
    // remember old placement for lerping
    en_plLastPlacement = en_plPlacement;  
  }


  void DoMoving()  
  {
    if (!m_bMoving) {
      return;
    }
    // read current tick
    FLOAT tmCurrent = _pTimer->CurrentTick();
    // lerping is initially enabled
    BOOL bLerping = TRUE;

    // if we hit a marker
    if( tmCurrent > (m_tmAtMarker+m_tmDelta - _pTimer->TickQuantum*3/2)) 
    {
      // get markers
      CCameraMarker *pcmNm1 = &(CCameraMarker&)*m_penLast;
      CCameraMarker *pcmNp0 = &(CCameraMarker&)*m_penTarget;
      CCameraMarker *pcmNp1 = &(CCameraMarker&)*pcmNp0->m_penTarget;
      CCameraMarker *pcmNp2 = &(CCameraMarker&)*pcmNp1->m_penTarget;

      // repeat
      FOREVER {
        // if there is a trigger at the hit marker
        if (pcmNp0->m_penTrigger!=NULL) {
          // trigger it
          SendToTarget(pcmNp0->m_penTrigger, EET_TRIGGER, m_penPlayer);
        }
        
        // if the marker should not be skipped
        if (!pcmNp0->m_bSkipToNext) {
          // stop skipping
          break;
        }

        // go to next marker immediately
        pcmNm1 = pcmNp0;
        pcmNp0 = pcmNp1;
        pcmNp1 = pcmNp2;
        pcmNp2 = (CCameraMarker*)&*pcmNp2->m_penTarget;
        // disable lerping
        bLerping = FALSE;
      }

      // update markers for next interval
      m_penTarget = pcmNp1;
      m_penLast   = pcmNp0;

      // get markers
      CCameraMarker &cmNm1 = *pcmNm1;
      CCameraMarker &cmNp0 = *pcmNp0;
      CCameraMarker &cmNp1 = *pcmNp1;
      CCameraMarker &cmNp2 = *pcmNp2;

      // get positions from four markers
      const FLOAT3D &vPNm1 = cmNm1.GetPlacement().pl_PositionVector;
      const FLOAT3D &vPNp0 = cmNp0.GetPlacement().pl_PositionVector;
      const FLOAT3D &vPNp1 = cmNp1.GetPlacement().pl_PositionVector;
      const FLOAT3D &vPNp2 = cmNp2.GetPlacement().pl_PositionVector;
      ANGLE3D aPNm1 = cmNm1.GetPlacement().pl_OrientationAngle;
      ANGLE3D aPNp0 = cmNp0.GetPlacement().pl_OrientationAngle;
      ANGLE3D aPNp1 = cmNp1.GetPlacement().pl_OrientationAngle;
      ANGLE3D aPNp2 = cmNp2.GetPlacement().pl_OrientationAngle;
      FLOAT fFOVm1 = cmNm1.m_fFOV;
      FLOAT fFOVp0 = cmNp0.m_fFOV;
      FLOAT fFOVp1 = cmNp1.m_fFOV;
      FLOAT fFOVp2 = cmNp2.m_fFOV;
      m_colFade0 = cmNp0.m_colFade;
      m_colFade1 = cmNp1.m_colFade;

      // find quaternions for rotations
      FLOATquat3D qPNm1; qPNm1.FromEuler(aPNm1);
      FLOATquat3D qPNp0; qPNp0.FromEuler(aPNp0);
      FLOATquat3D qPNp1; qPNp1.FromEuler(aPNp1);
      FLOATquat3D qPNp2; qPNp2.FromEuler(aPNp2);

      // make all angles between quaternion pairs acute
      if( qPNm1%qPNp0<0 ) {
        qPNp0 = -qPNp0;
      }
      if( qPNp0%qPNp1<0 ) {
        qPNp1 = -qPNp1;
      }
      if( qPNp1%qPNp2<0 ) {
        qPNp2 = -qPNp2;
      }

      // update time and position
      m_tmAtMarker = m_tmAtMarker+m_tmDelta;
      m_tmDelta    = cmNp0.m_fDeltaTime;
      m_vPNp0 = vPNp0;
      m_vPNp1 = vPNp1;
      m_fFOVp0 = fFOVp0;
      m_fFOVp1 = fFOVp1;
      m_qPNp0 = qPNp0;
      m_qPNp1 = qPNp1;

      // determine delta time multipliers
      FLOAT tmDNm1 = cmNm1.m_fDeltaTime;
      FLOAT tmDNp0 = cmNp0.m_fDeltaTime;
      FLOAT tmDNp1 = cmNp1.m_fDeltaTime;
      FLOAT fD0 = 2*tmDNp0 / (tmDNm1+tmDNp0);
      FLOAT fD1 = 2*tmDNp0 / (tmDNp0+tmDNp1);

      // determine biases, tensions and continuities
      FLOAT fBNp0 = cmNp0.m_fBias;
      FLOAT fTNp0 = cmNp0.m_fTension;
      FLOAT fCNp0 = cmNp0.m_fContinuity;
      FLOAT fBNp1 = cmNp1.m_fBias;
      FLOAT fTNp1 = cmNp1.m_fTension;
      FLOAT fCNp1 = cmNp1.m_fContinuity;

      FLOAT fF00 = (1-fTNp0)*(1-fCNp0)*(1-fBNp0) / 2;
      FLOAT fF01 = (1-fTNp0)*(1+fCNp0)*(1+fBNp0) / 2;
      FLOAT fF10 = (1-fTNp1)*(1+fCNp1)*(1-fBNp1) / 2;
      FLOAT fF11 = (1-fTNp1)*(1-fCNp1)*(1+fBNp1) / 2;

      // find tangents for translation
      m_vTNp0 = ( (vPNp1-vPNp0) * fF00 + (vPNp0-vPNm1) * fF01) * fD0;
      m_vTNp1 = ( (vPNp2-vPNp1) * fF10 + (vPNp1-vPNp0) * fF11) * fD1;

      // find tangents for FOV
      m_fTFOVp0 = ( (fFOVp1-fFOVp0) * fF00 + (fFOVp0-fFOVm1) * fF01) * fD0;
      m_fTFOVp1 = ( (fFOVp2-fFOVp1) * fF10 + (fFOVp1-fFOVp0) * fF11) * fD1;

      // find tangents for rotation
      FLOATquat3D qTNp0, qTNp1;
      qTNp0 = ( Log(qPNp0.Inv()*qPNp1) * fF00 + Log(qPNm1.Inv()*qPNp0) * fF01) * fD0;
      qTNp1 = ( Log(qPNp1.Inv()*qPNp2) * fF10 + Log(qPNp0.Inv()*qPNp1) * fF11) * fD1;

      // find squad parameters
      m_qANp0 = qPNp0*Exp( (qTNp0 - Log(qPNp0.Inv()*qPNp1))/2 );
      m_qANp1 = qPNp1*Exp( (Log(qPNp0.Inv()*qPNp1) - qTNp1)/2 );

      // check for stop moving
      if( cmNp0.m_bStopMoving) {
        m_bStopMoving = TRUE;
      }
    }

    // calculate the parameter value and hermit basis
    FLOAT fT  = (tmCurrent - m_tmAtMarker) / m_tmDelta;
    FLOAT fH0 =  2*fT*fT*fT - 3*fT*fT + 1;
    FLOAT fH1 = -2*fT*fT*fT + 3*fT*fT;
    FLOAT fH2 =    fT*fT*fT - 2*fT*fT + fT;
    FLOAT fH3 =    fT*fT*fT -   fT*fT;

    // interpolate position, rotation and fov
    FLOAT3D vPos = m_vPNp0*fH0 + m_vPNp1*fH1 + m_vTNp0*fH2 + m_vTNp1*fH3;
    FLOAT fFOV = m_fFOVp0*fH0 + m_fFOVp1*fH1 + m_fTFOVp0*fH2 + m_fTFOVp1*fH3;
    FLOATquat3D qRot = Squad(fT, m_qPNp0, m_qPNp1, m_qANp0, m_qANp1);
    FLOATmatrix3D mRot;
    qRot.ToMatrix(mRot);

    // just cache near polygons for various engine needs
    en_vNextPosition = vPos;
    en_mNextRotation = mRot;
    CacheNearPolygons();

    // set new placement
    CPlacement3D plNew;
    plNew.pl_PositionVector = vPos;
    DecomposeRotationMatrixNoSnap(plNew.pl_OrientationAngle, mRot);
    SetPlacement_internal(plNew, mRot, TRUE);
    // if lerping is disabled
    if (!bLerping) {
      // make last placement same as this one
      en_plLastPlacement = en_plPlacement;  
    }
    // set new fov
    m_fLastFOV = m_fFOV;
    m_fFOV = fFOV;
  }


  void PostMoving()  
  {
    if (!m_bMoving) {
      return;
    }
    //
    if( m_bStopMoving) {
      m_bMoving = FALSE;
      // mark for removing from list of movers
      en_ulFlags |= ENF_INRENDERING;
      SendEvent( EStop());
    }
  }


procedures:

  // routine for playing static camera
  PlayStaticCamera()
  {
    m_bMoving = FALSE;
    ECameraStart eStart;
    eStart.penCamera = this;
    m_penPlayer->SendEvent(eStart);
    autowait(m_tmTime);
    ECameraStop eStop;
    eStop.penCamera=this;
    m_penPlayer->SendEvent(eStop);
    return;
  }


  // routine for playing movable camera
  PlayMovingCamera()
  {
    // init camera
    ECameraStart eStart;
    eStart.penCamera = this;
    m_penPlayer->SendEvent(eStart);

    // check all markers for correct type and numbers
    INDEX ctMarkers=1;
    INDEX ctNonSkipped=0;
    CCameraMarker *pcm0 = (CCameraMarker*)&*m_penTarget;
    CCameraMarker *pcm  = (CCameraMarker*)&*pcm0->m_penTarget;
    // loop thru markers
    while( pcm!=NULL && pcm->m_penTarget!=pcm0)
    {
      pcm = (CCameraMarker*)&*pcm->m_penTarget;
      if (pcm==NULL) {
        WarningMessage( "Movable camera - broken link!");
        return;
      }
      if (!pcm->m_bSkipToNext) {
        ctNonSkipped++;
      }
      ctMarkers++;
      if (ctMarkers>500) {
        WarningMessage( "Movable camera - invalid marker loop!");
        return;
      }
    }
    // check if we have enough markers to do smooth interpolation
    if( ctMarkers<2) {
      WarningMessage( "Movable camera requires at least 2 markers in order to work!");
      return;
    }
    // check if we have enough markers to do smooth interpolation
    if( ctNonSkipped<1) {
      WarningMessage( "Movable camera requires at least 1 non-skipped marker!");
      return;
    }

    // prepare internal variables
    FLOAT tmCurrent = _pTimer->CurrentTick();
    m_tmAtMarker = tmCurrent;
    m_tmDelta = 0.0f;
    m_bStopMoving = FALSE;
    m_penLast = pcm; // keep last marker
    ASSERT( pcm->m_penTarget == m_penTarget);
    pcm  = (CCameraMarker*)&*m_penTarget;
    m_colFade0 = m_colFade1 = pcm->m_colFade;

    // register camera as movable entity
    AddToMovers();
    m_bMoving = TRUE;

    // roll, baby, roll ...
    wait() {
      on( EStop) : {
        ECameraStop eStop;
        eStop.penCamera=this;
        m_penPlayer->SendEvent(eStop);
        return;
      }
      otherwise() : {
        resume;
      }
    }

    // all done for now
    return;
  }


  // determine camera type and jump to corresponding routine
  PlayCamera()
  {
    // eventually add to movers list
    CCameraMarker &cm = (CCameraMarker&)*m_penTarget;
    // if we have at least one marker
    if( &cm!=NULL) {
      // treat camera as movable
      jump PlayMovingCamera();
    // if there isn't any markers
    } else {
      // treat camera as fixed
      jump PlayStaticCamera();
    }
  }


  Main()
  {
    // init as model
    InitAsEditorModel();
    SetPhysicsFlags(EPF_MOVABLE);
    SetCollisionFlags(ECF_CAMERA);
    // set appearance
    FLOAT fSize = 5.0f;
    GetModelObject()->mo_Stretch = FLOAT3D(fSize, fSize, fSize);
    SetModel(MODEL_CAMERA);
    SetModelMainTexture(TEXTURE_CAMERA);
    m_fLastFOV = m_fFOV;

    if( m_penTarget!=NULL && !IsOfClass( m_penTarget, "Camera Marker")) {
      WarningMessage( "Entity '%s' is not of Camera Marker class!", m_penTarget);
      m_penTarget = NULL;
    }

    while(TRUE)
    {
      wait() {
        on (ETrigger eTrigger) : {
          if( IsDerivedFromClass(eTrigger.penCaused, "Player")) {
            m_penPlayer = eTrigger.penCaused;
            call PlayCamera();
          }
        }
      }
    };

    // cease to exist
    Destroy();
    return;
  };
};

