//---------------------------------------------------------------------------- // EgalTech 2013-2014 //---------------------------------------------------------------------------- // File : SceneCamera.cpp Data : 10.02.14 Versione : 1.5b1 // Contenuto : Implementazione della gestione camera della classe scena. // // // // Modifiche : 10.02.14 DS Creazione modulo. // // //---------------------------------------------------------------------------- //--------------------------- Include ---------------------------------------- #include "stdafx.h" #include "Scene.h" #include "/EgtDev/Include/EGkFrame3d.h" #include "/EgtDev/Include/EGnStringUtils.h" #include "/EgtDev/Include/EgtNumUtils.h" #include "/EgtDev/Include/EgtILogger.h" using namespace std ; //--------------------------- Constants -------------------------------------- static const double MIN_DIST_CAMERA = 10 ; static const double STD_DIST_CAMERA = 1000 ; //---------------------------------------------------------------------------- bool Scene::SetCameraType( bool bOrthoOrPersp) { m_bOrthographic = bOrthoOrPersp ; // ricalcolo i parametri di vista m_bDistOk = false ; CalcDistCamera() ; CalcClippingPlanesFromExtView() ; return true ; } //---------------------------------------------------------------------------- bool Scene::SetZoomType( int nZoomMode) { if ( nZoomMode != ZT_STD && nZoomMode != ZT_DOLLY) return false ; m_nPerspZoomType = nZoomMode ; return true ; } //---------------------------------------------------------------------------- double Scene::CalcMinCameraDistance() { double dMinDist = MIN_DIST_CAMERA ; // nel caso di vista prospettica la distanza della camera deve garantire di non finire dentro gli oggetti if ( ! m_bOrthographic) { // calcolo il box di tutti gli oggetti visibili nel frame della camera Frame3d frCamera ; CalcCameraFrame( frCamera) ; BBox3d b3Tot ; if ( m_pGeomDB != nullptr) m_pGeomDB->GetGlobalBBox( GDB_ID_ROOT, b3Tot, BBF_ONLY_VISIBLE) ; b3Tot.ToLoc( frCamera) ; // la distanza minima della camera è quella che garantisce di trovarsi ad una distanza pari almeno a PERSPECTIVE_NEAR_PLANE // dalla faccia del box davanti alla camera Point3d ptMax = b3Tot.GetMax() ; ptMax.ToGlob( frCamera) ; dMinDist = ( ptMax - m_ptCenter) * m_vtDirCamera + PERSPECTIVE_NEAR_PLANE + 10 * EPS_SMALL ; } return dMinDist ; } //---------------------------------------------------------------------------- bool Scene::CalcDistCamera() { // verifico se il calcolo è necessario if ( m_bDistOk) return true ; if ( m_dDistCamera < EPS_SMALL) m_dDistCamera = STD_DIST_CAMERA ; else { double dMinDist = CalcMinCameraDistance() ; if ( m_dDistCamera < dMinDist) m_dDistCamera = dMinDist ; } m_bDistOk = true ; return true ; } //---------------------------------------------------------------------------- bool Scene::SetCenter( const Point3d& ptCenter) { // assegno il punto m_ptCenter = ptCenter ; // invalido ExtView m_bExtViewOk = false ; // calcolo nuovi dati di Z clipping CalcClippingPlanesFromExtView() ; return true ; } //---------------------------------------------------------------------------- bool Scene::SetCamera( double dAngVertDeg, double dAngOrizzDeg, double dDist) { // assegno la direzione ed eventuale distanza m_vtDirCamera = FromSpherical( 1, dAngVertDeg, dAngOrizzDeg) ; if ( dDist > EPS_SMALL) m_dDistCamera = dDist ; if ( ! VerifyCamera()) return false ; // se vista da sopra, impongo vtUp da angolo orizzontale if ( abs( dAngVertDeg) < EPS_ANG_SMALL) { m_vtUp = - FromPolar( 1, dAngOrizzDeg) ; m_bUpOk = true ; } // lo stesso anche per vista da sotto else if ( abs( dAngVertDeg - ANG_STRAIGHT) < EPS_ANG_SMALL) { m_vtUp = FromPolar( 1, dAngOrizzDeg) ; m_bUpOk = true ; } return true ; } //---------------------------------------------------------------------------- bool Scene::SetCamera( int nDir, double dDist) { switch ( nDir) { case CT_TOP : m_vtDirCamera.Set( 0, 0, 1) ; break ; case CT_FRONT : m_vtDirCamera.Set( 0, -1, 0) ; break ; case CT_RIGHT : m_vtDirCamera.Set( 1, 0, 0) ; break ; case CT_BACK : m_vtDirCamera.Set( 0, 1, 0) ; break ; case CT_LEFT : m_vtDirCamera.Set( -1, 0, 0) ; break ; case CT_BOTTOM : m_vtDirCamera.Set( 0, 0, -1) ; break ; case CT_ISO_SW : m_vtDirCamera.Set( -SQRT1_3, -SQRT1_3, SQRT1_3) ; break ; case CT_ISO_SE : m_vtDirCamera.Set( SQRT1_3, -SQRT1_3, SQRT1_3) ; break ; case CT_ISO_NE : m_vtDirCamera.Set( SQRT1_3, SQRT1_3, SQRT1_3) ; break ; case CT_ISO_NW : m_vtDirCamera.Set( -SQRT1_3, SQRT1_3, SQRT1_3) ; break ; case CT_CPLANE : m_vtDirCamera = GetGridFrame().VersZ() ; break ; default : return false ; break ; } if ( nDir != CT_CPLANE) m_vtDirCamera.Rotate( Z_AX, m_nOrizzOffsCamera * ANG_RIGHT) ; // assegno eventuale distanza if ( dDist > EPS_SMALL) m_dDistCamera = dDist ; return VerifyCamera( nDir != CT_CPLANE) ; } //---------------------------------------------------------------------------- bool Scene::VerifyCamera( bool bUseOrizzOffsCamera) { // verifico il versore if ( ! m_vtDirCamera.Normalize()) m_vtDirCamera.Set( 0, 0, 1) ; // ricalcolo direzione Up m_bUpOk = false ; if ( ! CalcDirUp( bUseOrizzOffsCamera)) return false ; // verifico la distanza m_bDistOk = false ; CalcDistCamera() ; // ricalcolo ExtView m_bExtViewOk = false ; if ( ! CalcExtView()) return false ; // calcolo nuovi dati di Z clipping return CalcClippingPlanesFromExtView() ; } //---------------------------------------------------------------------------- void Scene::GetCamera( double* pdAngVertDeg, double* pdAngOrizzDeg, double* pdDist) const { double dAngVertDeg ; double dAngOrizzDeg ; m_vtDirCamera.ToSpherical( nullptr, &dAngVertDeg, &dAngOrizzDeg) ; if ( abs( dAngVertDeg) < EPS_ANG_SMALL) { if ( m_bUpOk) (-m_vtUp).ToSpherical( nullptr, nullptr, &dAngOrizzDeg) ; else dAngOrizzDeg = -90 ; } else if ( abs( dAngVertDeg - ANG_STRAIGHT) < EPS_ANG_SMALL) { if ( m_bUpOk) (m_vtUp).ToSpherical( nullptr, nullptr, &dAngOrizzDeg) ; else dAngOrizzDeg = 90 ; } if ( pdAngVertDeg != nullptr) *pdAngVertDeg = dAngVertDeg ; if ( pdAngOrizzDeg != nullptr) *pdAngOrizzDeg = dAngOrizzDeg ; if ( pdDist != nullptr) *pdDist = m_dDistCamera ; } //---------------------------------------------------------------------------- int Scene::GetCameraDir( void) const { Vector3d vtDir = m_vtDirCamera ; vtDir.Rotate( Z_AX, - m_nOrizzOffsCamera * ANG_RIGHT) ; if ( vtDir.IsZplus()) return CT_TOP ; else if ( vtDir.IsYminus()) return CT_FRONT ; else if ( vtDir.IsXplus()) return CT_RIGHT ; else if ( vtDir.IsYplus()) return CT_BACK ; else if ( vtDir.IsXminus()) return CT_LEFT ; else if ( vtDir.IsZminus()) return CT_BOTTOM ; else if ( AreSameVectorApprox( vtDir, Vector3d( - SQRT1_3, - SQRT1_3, SQRT1_3))) return CT_ISO_SW ; else if ( AreSameVectorApprox( vtDir, Vector3d( SQRT1_3, - SQRT1_3, SQRT1_3))) return CT_ISO_SE ; else if ( AreSameVectorApprox( vtDir, Vector3d( SQRT1_3, SQRT1_3, SQRT1_3))) return CT_ISO_NE ; else if ( AreSameVectorApprox( vtDir, Vector3d( - SQRT1_3, SQRT1_3, SQRT1_3))) return CT_ISO_NW ; else return CT_NONE ; } //---------------------------------------------------------------------------- Point3d Scene::GetProjectedCenter( void) const { Point3d ptVCen ; if ( ! Project( m_ptCenter, ptVCen)) ptVCen.Set( 0, 0, 0) ; return ptVCen ; } //---------------------------------------------------------------------------- bool Scene::CalcDirUp( bool bUseOrizzOffsCamera) { // verifico se il calcolo è necessario if ( m_bUpOk) return true ; // direzione perpendicolare giacente nel piano XY // ( m_vtDirCamera è opposta alla direzione in cui si guarda) Vector3d vtPerpXY = m_vtDirCamera ^ Z_AX ; if ( ! vtPerpXY.Normalize()) { vtPerpXY = -X_AX ; if ( bUseOrizzOffsCamera) vtPerpXY.Rotate( Z_AX, m_nOrizzOffsCamera * ANG_RIGHT) ; } // direzione Up m_vtUp = vtPerpXY ^ m_vtDirCamera ; m_bUpOk = m_vtUp.Normalize() ; // invalido ExtView m_bExtViewOk = false ; return m_bUpOk ; } //---------------------------------------------------------------------------- bool Scene::CalcCameraFrame( Frame3d& frView) { // verifico direzione camera Up if ( ! CalcDirUp()) return false ; // eseguo il calcolo Vector3d vtZ = m_vtDirCamera ; Vector3d vtY = m_vtUp ; Vector3d vtX = vtY ^ vtZ ; if ( ! frView.Set( m_ptCenter + m_dDistCamera * vtZ, vtX, vtY, vtZ)) return false ; return true ; } //---------------------------------------------------------------------------- bool Scene::PanCamera( const Point3d& ptWinOld, const Point3d& ptWinNew) { // porto i due punti in coordinate mondo double dOldZCenter ; GetPixelZ( ptWinOld, dOldZCenter) ; Point3d ptViewOld( ptWinOld.x, ptWinOld.y, dOldZCenter) ; Point3d ptWorldOld ; if ( ! UnProject( ptViewOld, ptWorldOld)) return false ; double dNewZCenter ; GetPixelZ( ptWinNew, dNewZCenter) ; Point3d ptViewNew( ptWinNew.x, ptWinNew.y, dNewZCenter) ; Point3d ptWorldNew ; if ( ! UnProject( ptViewNew, ptWorldNew)) return false ; // calcolo lo spostamento tra i due punti Vector3d vtMove = ptWorldNew - ptWorldOld ; // applico questo spostamento al centro di vista SetCenter( GetCenter() - vtMove) ; // se dolly aggiusto la distanza per non entrare negli oggetti if ( ! m_bOrthographic && m_nPerspZoomType == ZT_DOLLY) CalcDistCamera() ; else m_bDistOk = true ; return true ; } //---------------------------------------------------------------------------- bool Scene::RotateCamera( const Point3d& ptWinOld, const Point3d& ptWinNew) { // angoli correnti della camera double dAngVertDeg ; double dAngOrizzDeg ; GetCamera( &dAngVertDeg, &dAngOrizzDeg) ; // incrementi double dDeltaAngVertDeg = ( ptWinNew.y - ptWinOld.y) / m_nViewportH * 360 ; double dDeltaAngOrizzDeg = ( ptWinNew.x - ptWinOld.x) / m_nViewportW * 360 ; // calcolo nuovi angoli e limitazione dell'angolo verticale const double MIN_ANG_VERT = 0.0 ; const double MAX_ANG_VERT = 180.0 ; dAngVertDeg -= dDeltaAngVertDeg ; if ( dAngVertDeg < MIN_ANG_VERT) dAngVertDeg = MIN_ANG_VERT ; else if ( dAngVertDeg > MAX_ANG_VERT) dAngVertDeg = MAX_ANG_VERT ; dAngOrizzDeg -= dDeltaAngOrizzDeg ; // imposto nuova direzione camera return SetCamera( dAngVertDeg, dAngOrizzDeg, 0) ; } //---------------------------------------------------------------------------- bool Scene::ZoomAll( void) { // aggiorno l'ingombro UpdateExtension() ; // traslo il centro di vista nel centro del box Point3d ptExtCent ; if ( ! m_b3ExtWorld.GetCenter( ptExtCent)) return false ; m_ptCenter = ptExtCent ; m_bExtViewOk = false ; // calcolo nuovi dati di ingombro CalcDimViewFromExtView() ; if ( ! m_bOrthographic) { if ( m_nPerspZoomType == ZT_DOLLY) { // adatto la distanza della camera in modo che il fov resti al valore standard m_dDistCamera = m_dHalfHeight / tan( PERSPECTIVE_STD_FOV * 0.5 * DEGTORAD) + ( m_b3ExtView.GetMax().z - m_b3ExtView.GetMin().z) * 0.5 ; } else { // aggiusto la distanza della camera m_b3ExtView.GetDiameter( m_dDistCamera) ; // il valore di m_dHalfHeight calcolato dal box non garantisce che l'oggetto sia interamente contenuto nel frustum // perchè è il valore in corrispondenza della faccia del box davanti alla camera. Per il calcolo del fov m_dHalfHeight // deve essere calcolato in corrispondenza di m_ptCenter // distanza della camera dalla faccia davanti del box double dDist = m_dDistCamera - ( m_b3ExtView.GetMax().z - m_b3ExtView.GetMin().z) * 0.5 ; // aggiorno le dimensioni double dAspect = m_dHalfWidth / m_dHalfHeight ; m_dHalfHeight = m_dHalfHeight / dDist * m_dDistCamera ; m_dHalfWidth = m_dHalfHeight * dAspect ; } } // la distanza della camera è calcolata correttamente m_bDistOk = true ; CalcClippingPlanesFromExtView() ; return true ; } //---------------------------------------------------------------------------- bool Scene::ZoomObject( int nId) { // Verifico puntatore a DB geometrico if ( m_pGeomDB == nullptr) return false ; // determino l'ingombro dell'oggetto/i in globale BBox3d b3Obj ; if ( nId == GDB_ID_SEL) { int nMyId = m_pGeomDB->GetFirstSelectedObj() ; while ( nMyId != GDB_ID_NULL) { BBox3d b3Curr ; if ( m_pGeomDB->GetGlobalBBox( nMyId, b3Curr, BBF_ONLY_VISIBLE)) b3Obj.Add( b3Curr) ; nMyId = m_pGeomDB->GetNextSelectedObj() ; } } else m_pGeomDB->GetGlobalBBox( nId, b3Obj, BBF_ONLY_VISIBLE) ; // recupero centro Point3d ptCent ; if ( ! b3Obj.GetCenter( ptCent)) return false ; // determino le dimensioni di vista nel riferimento di vista Frame3d frView ; if ( ! CalcCameraFrame( frView)) return false ; b3Obj.ToLoc( frView) ; const double COEFF = 1.05 ; double dHalfWidth = COEFF * ( b3Obj.GetMax().x - b3Obj.GetMin().x) / 2 ; double dHalfHeight = COEFF * ( b3Obj.GetMax().y - b3Obj.GetMin().y) / 2 ; // imposto il centro e le dimensioni di vista m_ptCenter = ptCent ; if ( ! AdjustDimView( dHalfWidth, dHalfHeight)) return false ; if ( ! m_bOrthographic) { // cfr conti analoghi in ZoomAll if ( m_nPerspZoomType == ZT_DOLLY) { m_dDistCamera = m_dHalfHeight / tan( PERSPECTIVE_STD_FOV * 0.5 * DEGTORAD) + ( b3Obj.GetMax().z - b3Obj.GetMin().z) * 0.5 ; } else { // verifico di non finire dentro l'oggetto double dDiam ; b3Obj.GetDiameter( dDiam) ; if ( m_dDistCamera < dDiam * 0.5) m_dDistCamera = dDiam * 0.5 ; // distanza della camera dalla faccia davanti del box double dDist = m_dDistCamera - ( b3Obj.GetMax().z - b3Obj.GetMin().z) * 0.5 ; // ricalcolo le dimensioni double dAspect = m_dHalfWidth / m_dHalfHeight ; m_dHalfHeight = m_dHalfHeight / dDist * m_dDistCamera ; m_dHalfWidth = m_dHalfHeight * dAspect ; } } // la distanza della camera è calcolata per non finire dentro gli oggetti m_bDistOk = true ; return true ; } //---------------------------------------------------------------------------- bool Scene::ZoomRadius( double dRadius) { // adatto le dimensioni a quelle della vista if ( ! AdjustDimView( dRadius, dRadius)) return false ; // per il dolly va modificata la distanza dalla camera if ( ! m_bOrthographic && m_nPerspZoomType == ZT_DOLLY) { m_dDistCamera = m_dHalfHeight / tan( PERSPECTIVE_STD_FOV * 0.5 * DEGTORAD) ; // verifico la distanza della camera CalcDistCamera() ; } else m_bDistOk = true ; return true ; } //---------------------------------------------------------------------------- bool Scene::ZoomChange( double dCoeff) { const double MIN_COEFF = 0.5 ; const double MAX_COEFF = 2 ; // cambio le dimensioni di zoom if ( ! m_bOrthographic && m_nPerspZoomType == ZT_DOLLY) { // per il dolly va modificata la distanza della camera m_dDistCamera *= dCoeff ; } else { // controllo i limiti dCoeff = clamp( dCoeff, MIN_COEFF, MAX_COEFF) ; m_dHalfWidth *= dCoeff ; m_dHalfHeight *= dCoeff ; } return true ; } //---------------------------------------------------------------------------- bool Scene::ZoomOnPoint( const Point3d& ptWin, double dCoeff) { // leggo Z del pixel double dWinZ ; GetPixelZ( ptWin, dWinZ) ; // porto il punto in coordinate mondo Point3d ptView( ptWin.x, ptWin.y, dWinZ) ; Point3d ptWorld ; if ( ! UnProject( ptView, ptWorld)) return false ; // modifico opportunamente il centro di vista Point3d ptOldCen = m_ptCenter ; Point3d ptNewCen = ptWorld + ( m_ptCenter - ptWorld) * dCoeff ; ptNewCen += ( ptWorld - ptNewCen) * m_vtDirCamera * m_vtDirCamera ; SetCenter( ptNewCen) ; m_bDistOk = true ; if ( ! m_bOrthographic) { if ( m_nPerspZoomType == ZT_DOLLY) { // nel caso di dolly verifico se il nuovo zoom fa entrare negli oggetti double dMinDist = CalcMinCameraDistance() ; if ( m_dDistCamera * dCoeff < dMinDist) { // adatto il coefficiente di zoom e ricalcolo il centro dCoeff = dMinDist / m_dDistCamera ; Point3d ptNewCenter = ptWorld + ( ptOldCen - ptWorld) * dCoeff ; SetCenter( ptNewCenter) ; } } else { // nel caso standard verifico se il nuovo fov supera il valore massimo if ( m_dHalfHeight * dCoeff / m_dDistCamera > PERSPECTIVE_TAN_MAX_FOV) { // adatto il coefficiente di zoom e ricalcolo il centro dCoeff = PERSPECTIVE_TAN_MAX_FOV * m_dDistCamera / m_dHalfHeight ; Point3d ptNewCenter = ptWorld + ( ptOldCen - ptWorld) * dCoeff ; SetCenter( ptNewCenter) ; } } } return ZoomChange( dCoeff) ; } //---------------------------------------------------------------------------- bool Scene::ZoomWin( const Point3d& ptWin1, const Point3d& ptWin2) { // leggo Z del pixel double dWinZ ; GetPixelZ( Media( ptWin1, ptWin2), dWinZ) ; // porto i punti in coordinate mondo Point3d ptWorld1 ; if ( ! UnProject( Point3d( ptWin1.x, ptWin1.y, dWinZ), ptWorld1)) return false ; Point3d ptWorld2 ; if ( ! UnProject( Point3d( ptWin2.x, ptWin2.y, dWinZ), ptWorld2)) return false ; // recupero il riferimento della camera (vista) Frame3d frView ; if ( ! CalcCameraFrame( frView)) return false ; // porto i punti in coordinate camera (vista) Point3d ptView1 = GetToLoc( ptWorld1, frView) ; Point3d ptView2 = GetToLoc( ptWorld2, frView) ; // imposto il nuovo centro di vista Point3d ptNewCen = 0.5 * ( ptWorld1 + ptWorld2) ; SetCenter( ptNewCen) ; // imposto il nuovo zoom double dHalfWidth = 0.5 * abs( ptView1.x - ptView2.x) ; double dHalfHeight = 0.5 * abs( ptView1.y - ptView2.y) ; // adatto le dimensioni a quelle della vista if ( ! AdjustDimView( dHalfWidth, dHalfHeight)) return false ; // per il dolly va modificata la distanza della camera if ( ! m_bOrthographic && m_nPerspZoomType == ZT_DOLLY) { m_dDistCamera = m_dHalfHeight / tan( PERSPECTIVE_STD_FOV * 0.5 * DEGTORAD) ; CalcDistCamera() ; } else m_bDistOk = true ; return true ; } //---------------------------------------------------------------------------- void Scene::GetOrthoCamParam( double* pdWidth, double* pdHeight, double* pdNear, double* pdFar) const { if ( pdWidth != nullptr) *pdWidth = m_dHalfWidth ; if ( pdHeight != nullptr) *pdHeight = m_dHalfHeight ; if ( pdNear != nullptr) *pdNear = m_dZNear ; if ( pdFar != nullptr) *pdFar = m_dZFar ; }