/* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "GameView.h" #include "graphics/Camera.h" #include "graphics/CinemaTrack.h" #include "graphics/ColladaManager.h" #include "graphics/HFTracer.h" #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/ObjectManager.h" #include "graphics/Patch.h" #include "graphics/SkeletonAnimManager.h" #include "graphics/Terrain.h" #include "graphics/TextureManager.h" #include "graphics/Unit.h" #include "graphics/UnitManager.h" #include "lib/input.h" #include "lib/timer.h" #include "maths/Bound.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" #include "maths/Quaternion.h" #include "ps/ConfigDB.h" #include "ps/Game.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Interact.h" #include "ps/Loader.h" #include "ps/LoaderThunks.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "renderer/Renderer.h" #include "renderer/SkyManager.h" #include "renderer/WaterManager.h" #include "scripting/ScriptableObject.h" #include "simulation/Entity.h" #include "simulation/EntityOrders.h" #include "simulation/LOSManager.h" #include "simulation/Projectile.h" #include "simulation2/Simulation2.h" float g_MaxZoomHeight=350.0f; //note: Max terrain height is this minus YMinOffset float g_YMinOffset=15.0f; extern int g_xres, g_yres; static CVector3D cameraBookmarks[10]; static bool bookmarkInUse[10] = { false, false, false, false, false, false, false, false, false, false }; static i8 currentBookmark = -1; const float CGameView::defaultFOV = DEGTORAD(20.f); const float CGameView::defaultNear = 4.f; const float CGameView::defaultFar = 4096.f; class CGameViewImpl : public CJSObject { NONCOPYABLE(CGameViewImpl); public: CGameViewImpl(CGame* game) : Game(game), ColladaManager(), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager), ObjectManager(MeshManager, SkeletonAnimManager), ViewCamera(), CullCamera(), LockCullCamera(false), Culling(true), ViewScrollSpeed(60), ViewRotateSensitivity(0.002f), ViewRotateSensitivityKeyboard(1.0f), ViewRotateAboutTargetSensitivity(0.010f), ViewRotateAboutTargetSensitivityKeyboard(2.0f), ViewDragSensitivity(0.5f), ViewZoomSensitivityWheel(16.0f), ViewZoomSensitivity(256.0f), ViewZoomSmoothness(0.02f), ViewSnapSmoothness(0.02f), CameraDelta(), CameraPivot(), ZoomDelta(0) { } CGame* Game; CColladaManager ColladaManager; CMeshManager MeshManager; CSkeletonAnimManager SkeletonAnimManager; CObjectManager ObjectManager; /** * this camera controls the eye position when rendering */ CCamera ViewCamera; /** * this camera controls the frustum that is used for culling * and shadow calculations * * Note that all code that works with camera movements should only change * m_ViewCamera. The render functions automatically sync the cull camera to * the view camera depending on the value of m_LockCullCamera. */ CCamera CullCamera; /** * When @c true, the cull camera is locked in place. * When @c false, the cull camera follows the view camera. * * Exposed to JS as gameView.lockCullCamera */ bool LockCullCamera; /** * When @c true, culling is enabled so that only models that have a chance of * being visible are sent to the renderer. * Otherwise, the entire world is sent to the renderer. * * Exposed to JS as gameView.culling */ bool Culling; /** * Cache global lighting environment. This is used to check whether the * environment has changed during the last frame, so that vertex data can be updated etc. */ CLightEnv CachedLightEnv; CCinemaManager TrackManager; //////////////////////////////////////// // Settings float ViewScrollSpeed; float ViewRotateSensitivity; float ViewRotateSensitivityKeyboard; float ViewRotateAboutTargetSensitivity; float ViewRotateAboutTargetSensitivityKeyboard; float ViewDragSensitivity; float ViewZoomSensitivityWheel; float ViewZoomSensitivity; float ViewZoomSmoothness; // 0.0 = instantaneous zooming, 1.0 = so slow it never moves float ViewSnapSmoothness; // Just the same. //////////////////////////////////////// // Camera Controls State CVector3D CameraDelta; CVector3D CameraPivot; CEntity* UnitView; CModel* UnitViewProp; CEntity* UnitAttach; //float m_CameraZoom; std::vector CameraTargets; // Accumulate zooming changes across frames for smoothness float ZoomDelta; // JS Interface bool JSI_StartCustomSelection(JSContext *cx, uintN argc, jsval *argv); bool JSI_EndCustomSelection(JSContext *cx, uintN argc, jsval *argv); static void ScriptingInit(); }; CGameView::CGameView(CGame *pGame): m(new CGameViewImpl(pGame)) { SViewPort vp; vp.m_X=0; vp.m_Y=0; vp.m_Width=g_xres; vp.m_Height=g_yres; m->ViewCamera.SetViewPort(&vp); m->ViewCamera.SetProjection (defaultNear, defaultFar, defaultFOV); m->ViewCamera.m_Orientation.SetXRotation(DEGTORAD(30)); m->ViewCamera.m_Orientation.RotateY(DEGTORAD(0)); m->ViewCamera.m_Orientation.Translate (100, 150, -100); m->CullCamera = m->ViewCamera; g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera); m->UnitView=NULL; m->UnitAttach=NULL; } CGameView::~CGameView() { g_Selection.ClearSelection(); g_Mouseover.Clear(); g_BuildingPlacer.Deactivate(); UnloadResources(); delete m; } CObjectManager& CGameView::GetObjectManager() const { return m->ObjectManager; } JSObject* CGameView::GetScript() { return m->GetScript(); } /*static*/ void CGameView::ScriptingInit() { return CGameViewImpl::ScriptingInit(); } CCamera* CGameView::GetCamera() { return &m->ViewCamera; } CCinemaManager* CGameView::GetCinema() { return &m->TrackManager; }; void CGameView::AttachToUnit(CEntity* target) { m->UnitAttach = target; } bool CGameView::IsAttached() { return (m->UnitAttach != NULL); } bool CGameView::IsUnitView() { return (m->UnitView != NULL); } void CGameViewImpl::ScriptingInit() { AddMethod("startCustomSelection", 0); AddMethod("endCustomSelection", 0); AddProperty(L"culling", &CGameViewImpl::Culling); AddProperty(L"lockCullCamera", &CGameViewImpl::LockCullCamera); CJSObject::ScriptingInit("GameView"); } int CGameView::Initialize(CGameAttributes* UNUSED(pAttribs)) { CFG_GET_SYS_VAL( "view.scroll.speed", Float, m->ViewScrollSpeed ); CFG_GET_SYS_VAL( "view.rotate.speed", Float, m->ViewRotateSensitivity ); CFG_GET_SYS_VAL( "view.rotate.keyboard.speed", Float, m->ViewRotateSensitivityKeyboard ); CFG_GET_SYS_VAL( "view.rotate.abouttarget.speed", Float, m->ViewRotateAboutTargetSensitivity ); CFG_GET_SYS_VAL( "view.rotate.keyboard.abouttarget.speed", Float, m->ViewRotateAboutTargetSensitivityKeyboard ); CFG_GET_SYS_VAL( "view.drag.speed", Float, m->ViewDragSensitivity ); CFG_GET_SYS_VAL( "view.zoom.speed", Float, m->ViewZoomSensitivity ); CFG_GET_SYS_VAL( "view.zoom.wheel.speed", Float, m->ViewZoomSensitivityWheel ); CFG_GET_SYS_VAL( "view.zoom.smoothness", Float, m->ViewZoomSmoothness ); CFG_GET_SYS_VAL( "view.snap.smoothness", Float, m->ViewSnapSmoothness ); if( ( m->ViewZoomSmoothness < 0.0f ) || ( m->ViewZoomSmoothness > 1.0f ) ) m->ViewZoomSmoothness = 0.02f; if( ( m->ViewSnapSmoothness < 0.0f ) || ( m->ViewSnapSmoothness > 1.0f ) ) m->ViewSnapSmoothness = 0.02f; return 0; } void CGameView::RegisterInit(CGameAttributes *pAttribs) { // CGameView init RegMemFun1(this, &CGameView::Initialize, pAttribs, L"CGameView init", 1); // previously done by CGameView::InitResources RegMemFun(g_TexMan.GetSingletonPtr(), &CTextureManager::LoadTerrainTextures, L"LoadTerrainTextures", 60); RegMemFun(g_Renderer.GetSingletonPtr(), &CRenderer::LoadAlphaMaps, L"LoadAlphaMaps", 5); RegMemFun(g_Renderer.GetSingletonPtr()->GetWaterManager(), &WaterManager::LoadWaterTextures, L"LoadWaterTextures", 80); RegMemFun(g_Renderer.GetSingletonPtr()->GetSkyManager(), &SkyManager::LoadSkyTextures, L"LoadSkyTextures", 15); } void CGameView::Render() { if (m->LockCullCamera == false) { // Set up cull camera m->CullCamera = m->ViewCamera; // This can be uncommented to try getting a bigger frustum.. // but then it makes shadow maps too low-detail. //m_CullCamera.SetProjection(1.0f, 10000.0f, DEGTORAD(30)); //m_CullCamera.UpdateFrustum(); } g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera); CheckLightEnv(); g_Renderer.RenderScene(this); } /////////////////////////////////////////////////////////// // This callback is part of the Scene interface // Submit all objects visible in the given frustum void CGameView::EnumerateObjects(const CFrustum& frustum, SceneCollector* c) { PROFILE_START( "submit terrain" ); CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); const ssize_t patchesPerSide = pTerrain->GetPatchesPerSide(); // find out which patches will be drawn for (ssize_t j=0; jGetPatch(i,j); // can't fail // If the patch is underwater, calculate a bounding box that also contains the water plane CBound bounds = patch->GetBounds(); float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight + 0.001f; if(bounds[1].Y < waterHeight) { bounds[1].Y = waterHeight; } if (!m->Culling || frustum.IsBoxVisible (CVector3D(0,0,0), bounds)) { //c->Submit(patch); // set the renderstate for this patch patch->setDrawState(true); // set the renderstate for the neighbors CPatch *nPatch; nPatch = pTerrain->GetPatch(i-1,j-1); if(nPatch) nPatch->setDrawState(true); nPatch = pTerrain->GetPatch(i,j-1); if(nPatch) nPatch->setDrawState(true); nPatch = pTerrain->GetPatch(i+1,j-1); if(nPatch) nPatch->setDrawState(true); nPatch = pTerrain->GetPatch(i-1,j); if(nPatch) nPatch->setDrawState(true); nPatch = pTerrain->GetPatch(i+1,j); if(nPatch) nPatch->setDrawState(true); nPatch = pTerrain->GetPatch(i-1,j+1); if(nPatch) nPatch->setDrawState(true); nPatch = pTerrain->GetPatch(i,j+1); if(nPatch) nPatch->setDrawState(true); nPatch = pTerrain->GetPatch(i+1,j+1); if(nPatch) nPatch->setDrawState(true); } } } // draw the patches for (ssize_t j=0; jGetPatch(i,j); // can't fail if(patch->getDrawState() == true) { c->Submit(patch); patch->setDrawState(false); } } } PROFILE_END( "submit terrain" ); if (g_UseSimulation2) { PROFILE_START( "submit sim components" ); m->Game->GetSimulation2()->RenderSubmit(*c, frustum, m->Culling); PROFILE_END( "submit sim components" ); return; } // Old simulation: PROFILE_START( "submit models" ); CWorld* world = m->Game->GetWorld(); CUnitManager& unitMan = world->GetUnitManager(); CProjectileManager& pProjectileMan = world->GetProjectileManager(); CLOSManager* losMgr = world->GetLOSManager(); const std::vector& units = unitMan.GetUnits(); for (size_t i=0;iGetEntity(); if( ent && !ent->m_visible ) continue; int status = losMgr->GetUnitStatus(units[i], g_Game->GetLocalPlayer()); CModel* model = units[i]->GetModel(); model->ValidatePosition(); if (status != UNIT_HIDDEN && (!m->Culling || frustum.IsBoxVisible(CVector3D(0,0,0), model->GetBounds()))) { if(units[i] != g_BuildingPlacer.m_actor) { CColor color; if(status == UNIT_VISIBLE) { color = CColor(1.0f, 1.0f, 1.0f, 1.0f); } else // status == UNIT_REMEMBERED { color = CColor(0.7f, 0.7f, 0.7f, 1.0f); } model->SetShadingColor(color); } PROFILE( "submit models" ); c->SubmitRecursive(model); } } const std::list& projectiles = pProjectileMan.GetProjectiles(); std::list::const_iterator it = projectiles.begin(); for (; it != projectiles.end(); ++it) { CModel* model = (*it)->GetModel(); model->ValidatePosition(); const CBound& bound = model->GetBounds(); CVector3D centre; bound.GetCentre(centre); if ((!m->Culling || frustum.IsBoxVisible(CVector3D(0,0,0), bound)) && losMgr->GetStatus(centre.X, centre.Z, g_Game->GetLocalPlayer()) & LOS_VISIBLE) { PROFILE( "submit projectiles" ); c->SubmitRecursive((*it)->GetModel()); } } PROFILE_END( "submit models" ); } //locks the camera in place void CGameView::CameraLock(const CVector3D& Trans, bool smooth) { CameraLock(Trans.X, Trans.Y, Trans.Z, smooth); } void CGameView::CameraLock(float x, float y, float z, bool smooth) { CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); float height = pTerrain->GetExactGroundLevel( m->ViewCamera.m_Orientation._14 + x, m->ViewCamera.m_Orientation._34 + z) + g_YMinOffset; //is requested position within limits? if (m->ViewCamera.m_Orientation._24 + y <= g_MaxZoomHeight) { if( m->ViewCamera.m_Orientation._24 + y >= height) { m->ViewCamera.m_Orientation.Translate(x, y, z); } else if (m->ViewCamera.m_Orientation._24 + y < height && smooth == true) { m->ViewCamera.m_Orientation.Translate(x, y, z); m->ViewCamera.m_Orientation._24=height; } } } static void MarkUpdateColorRecursive(CModel* model) { model->SetDirty(RENDERDATA_UPDATE_COLOR); const std::vector& props = model->GetProps(); for(size_t i = 0; i < props.size(); ++i) { MarkUpdateColorRecursive(props[i].m_Model); } } void CGameView::CheckLightEnv() { if (m->CachedLightEnv == g_LightEnv) return; m->CachedLightEnv = g_LightEnv; CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); if (!pTerrain) return; PROFILE("update light env"); pTerrain->MakeDirty(RENDERDATA_UPDATE_COLOR); const std::vector& units = m->Game->GetWorld()->GetUnitManager().GetUnits(); for(size_t i = 0; i < units.size(); ++i) { MarkUpdateColorRecursive(units[i]->GetModel()); } } void CGameView::UnloadResources() { g_TexMan.UnloadTerrainTextures(); g_Renderer.UnloadAlphaMaps(); g_Renderer.GetWaterManager()->UnloadWaterTextures(); } void CGameView::ResetCamera() { // quick hack to return camera home, for screenshots (after alt+tabbing) m->ViewCamera.SetProjection (defaultNear, defaultFar, defaultFOV); m->ViewCamera.m_Orientation.SetXRotation(DEGTORAD(30)); m->ViewCamera.m_Orientation.RotateY(DEGTORAD(-45)); m->ViewCamera.m_Orientation.Translate (100, 150, -100); } void CGameView::ResetCameraOrientation() { CVector3D origin = m->ViewCamera.m_Orientation.GetTranslation(); CVector3D dir = m->ViewCamera.m_Orientation.GetIn(); CVector3D target = origin + dir * ( ( 50.0f - origin.Y ) / dir.Y ); target -= CVector3D( -22.474480f, 50.0f, 22.474480f ); m->ViewCamera.SetProjection (defaultNear, defaultFar, defaultFOV); m->ViewCamera.m_Orientation.SetXRotation(DEGTORAD(30)); m->ViewCamera.m_Orientation.RotateY(DEGTORAD(-45)); target += CVector3D( 100.0f, 150.0f, -100.0f ); m->ViewCamera.m_Orientation.Translate( target ); } void CGameView::RotateAboutTarget() { m->CameraPivot = m->ViewCamera.GetWorldCoordinates(true); } void CGameView::Update(float DeltaTime) { if (!g_app_has_focus) return; // TODO: this is probably not an ideal place for this, it should probably go // in a CCmpWaterManager or some such thing (once such a thing exists) g_Renderer.GetWaterManager()->m_WaterTexTimer += DeltaTime; if (m->UnitView) { m->ViewCamera.m_Orientation.SetYRotation(m->UnitView->m_orientation.Y); m->ViewCamera.m_Orientation.Translate(m->UnitViewProp->GetTransform().GetTranslation()); m->ViewCamera.UpdateFrustum(); return; } if (m->UnitAttach) { CVector3D ToMove = m->UnitAttach->m_position - m->ViewCamera.GetFocus(); m->ViewCamera.m_Orientation._14 += ToMove.X; m->ViewCamera.m_Orientation._34 += ToMove.Z; m->ViewCamera.UpdateFrustum(); return; } if (m->TrackManager.IsActive() && m->TrackManager.IsPlaying()) { if (! m->TrackManager.Update(DeltaTime)) ResetCamera(); return; } float delta = powf( m->ViewSnapSmoothness, DeltaTime ); m->ViewCamera.m_Orientation.Translate( m->CameraDelta * ( 1.0f - delta ) ); m->CameraDelta *= delta; // This could be rewritten much more reliably, so it doesn't e.g. accidentally tilt // the camera, assuming we know exactly what limits the camera should have. // Calculate mouse movement static int mouse_last_x = 0; static int mouse_last_y = 0; int mouse_dx = g_mouse_x - mouse_last_x; int mouse_dy = g_mouse_y - mouse_last_y; mouse_last_x = g_mouse_x; mouse_last_y = g_mouse_y; // Miscellaneous vectors CVector3D forwards = m->ViewCamera.m_Orientation.GetIn(); CVector3D rightwards = m->ViewCamera.m_Orientation.GetLeft() * -1.0f; // upwards.Cross(forwards); CVector3D upwards( 0.0f, 1.0f, 0.0f ); // rightwards.Normalize(); CVector3D forwards_horizontal = forwards; forwards_horizontal.Y = 0.0f; forwards_horizontal.Normalize(); if( hotkeys[HOTKEY_CAMERA_ROTATE] || hotkeys[HOTKEY_CAMERA_ROTATE_KEYBOARD] ) { // Ctrl + middle-drag or left-and-right-drag to rotate view // Untranslate the camera, so it rotates around the correct point CVector3D position = m->ViewCamera.m_Orientation.GetTranslation(); m->ViewCamera.m_Orientation.Translate(position*-1); // Sideways rotation float rightways = 0.0f; if( hotkeys[HOTKEY_CAMERA_ROTATE] ) rightways = (float)mouse_dx * m->ViewRotateSensitivity; if( hotkeys[HOTKEY_CAMERA_ROTATE_KEYBOARD] ) { if( hotkeys[HOTKEY_CAMERA_LEFT] ) rightways -= m->ViewRotateSensitivityKeyboard * DeltaTime; if( hotkeys[HOTKEY_CAMERA_RIGHT] ) rightways += m->ViewRotateSensitivityKeyboard * DeltaTime; } m->ViewCamera.m_Orientation.RotateY( rightways ); // Up/down rotation float upways = 0.0f; if( hotkeys[HOTKEY_CAMERA_ROTATE] ) upways = (float)mouse_dy * m->ViewRotateSensitivity; if( hotkeys[HOTKEY_CAMERA_ROTATE_KEYBOARD] ) { if( hotkeys[HOTKEY_CAMERA_UP] ) upways -= m->ViewRotateSensitivityKeyboard * DeltaTime; if( hotkeys[HOTKEY_CAMERA_DOWN] ) upways += m->ViewRotateSensitivityKeyboard * DeltaTime; } CQuaternion temp; temp.FromAxisAngle(rightwards, upways); m->ViewCamera.m_Orientation.Rotate(temp); // Retranslate back to the right position m->ViewCamera.m_Orientation.Translate(position); } else if( hotkeys[HOTKEY_CAMERA_ROTATE_ABOUT_TARGET] ) { CVector3D origin = m->ViewCamera.m_Orientation.GetTranslation(); CVector3D delta = origin - m->CameraPivot; CQuaternion rotateH, rotateV; CMatrix3D rotateM; // Sideways rotation float rightways = (float)mouse_dx * m->ViewRotateAboutTargetSensitivity; rotateH.FromAxisAngle( upwards, rightways ); // Up/down rotation float upways = (float)mouse_dy * m->ViewRotateAboutTargetSensitivity; rotateV.FromAxisAngle( rightwards, upways ); rotateH *= rotateV; rotateH.ToMatrix( rotateM ); delta = rotateM.Rotate( delta ); // Lock the inclination to a rather arbitrary values (for the sake of graphical decency) float scan = sqrt( delta.X * delta.X + delta.Z * delta.Z ) / delta.Y; if( ( scan >= 0.5f ) ) { // Move the camera to the origin (in preparation for rotation ) m->ViewCamera.m_Orientation.Translate( origin * -1.0f ); m->ViewCamera.m_Orientation.Rotate( rotateH ); // Move the camera back to where it belongs m->ViewCamera.m_Orientation.Translate( m->CameraPivot + delta ); } } else if( hotkeys[HOTKEY_CAMERA_ROTATE_ABOUT_TARGET_KEYBOARD] ) { // Split up because the keyboard controls use the centre of the screen, not the mouse position. CVector3D origin = m->ViewCamera.m_Orientation.GetTranslation(); CVector3D pivot = m->ViewCamera.GetFocus(); CVector3D delta = origin - pivot; CQuaternion rotateH, rotateV; CMatrix3D rotateM; // Sideways rotation float rightways = 0.0f; if( hotkeys[HOTKEY_CAMERA_LEFT] ) rightways -= m->ViewRotateAboutTargetSensitivityKeyboard * DeltaTime; if( hotkeys[HOTKEY_CAMERA_RIGHT] ) rightways += m->ViewRotateAboutTargetSensitivityKeyboard * DeltaTime; rotateH.FromAxisAngle( upwards, rightways ); // Up/down rotation float upways = 0.0f; if( hotkeys[HOTKEY_CAMERA_UP] ) upways -= m->ViewRotateAboutTargetSensitivityKeyboard * DeltaTime; if( hotkeys[HOTKEY_CAMERA_DOWN] ) upways += m->ViewRotateAboutTargetSensitivityKeyboard * DeltaTime; rotateV.FromAxisAngle( rightwards, upways ); rotateH *= rotateV; rotateH.ToMatrix( rotateM ); delta = rotateM.Rotate( delta ); // Lock the inclination to a rather arbitrary values (for the sake of graphical decency) float scan = sqrt( delta.X * delta.X + delta.Z * delta.Z ) / delta.Y; if( ( scan >= 0.5f ) ) { // Move the camera to the origin (in preparation for rotation ) m->ViewCamera.m_Orientation.Translate( origin * -1.0f ); m->ViewCamera.m_Orientation.Rotate( rotateH ); // Move the camera back to where it belongs m->ViewCamera.m_Orientation.Translate( pivot + delta ); } } else if( hotkeys[HOTKEY_CAMERA_PAN] ) { // Middle-drag to pan //keep camera in bounds CameraLock(rightwards * (m->ViewDragSensitivity * mouse_dx)); CameraLock(forwards_horizontal * (-m->ViewDragSensitivity * mouse_dy)); } // Mouse movement if( g_mouse_active && !hotkeys[HOTKEY_CAMERA_ROTATE] && !hotkeys[HOTKEY_CAMERA_ROTATE_ABOUT_TARGET] ) { if (g_mouse_x >= g_xres-2 && g_mouse_x < g_xres) CameraLock(rightwards * (m->ViewScrollSpeed * DeltaTime)); else if (g_mouse_x <= 3 && g_mouse_x >= 0) CameraLock(-rightwards * (m->ViewScrollSpeed * DeltaTime)); if (g_mouse_y >= g_yres-2 && g_mouse_y < g_yres) CameraLock(-forwards_horizontal * (m->ViewScrollSpeed * DeltaTime)); else if (g_mouse_y <= 3 && g_mouse_y >= 0) CameraLock(forwards_horizontal * (m->ViewScrollSpeed * DeltaTime)); } // Keyboard movement (added to mouse movement, so you can go faster if you want) if( hotkeys[HOTKEY_CAMERA_PAN_KEYBOARD] ) { if( hotkeys[HOTKEY_CAMERA_RIGHT] ) CameraLock(rightwards * (m->ViewScrollSpeed * DeltaTime)); if( hotkeys[HOTKEY_CAMERA_LEFT] ) CameraLock(-rightwards * (m->ViewScrollSpeed * DeltaTime)); if( hotkeys[HOTKEY_CAMERA_DOWN] ) CameraLock(-forwards_horizontal * (m->ViewScrollSpeed * DeltaTime)); if( hotkeys[HOTKEY_CAMERA_UP] ) CameraLock(forwards_horizontal * (m->ViewScrollSpeed * DeltaTime)); } // Smoothed zooming (move a certain percentage towards the desired zoom distance every frame) // Note that scroll wheel zooming is event-based and handled in game_view_handler if( hotkeys[HOTKEY_CAMERA_ZOOM_IN] ) m->ZoomDelta += m->ViewZoomSensitivity*DeltaTime; else if( hotkeys[HOTKEY_CAMERA_ZOOM_OUT] ) m->ZoomDelta -= m->ViewZoomSensitivity*DeltaTime; if (fabsf(m->ZoomDelta) > 0.1f) // use a fairly high limit to avoid nasty flickering when zooming { float zoom_proportion = powf(m->ViewZoomSmoothness, DeltaTime); CameraLock(forwards * (m->ZoomDelta * (1.0f-zoom_proportion)), false); m->ZoomDelta *= zoom_proportion; } m->ViewCamera.UpdateFrustum (); } void CGameView::ToUnitView(CEntity* target, CModel* prop) { if( !target ) { //prevent previous zooming m->ZoomDelta = 0.0f; ResetCamera(); SetCameraTarget( m->UnitView->m_position ); } m->UnitView = target; m->UnitViewProp = prop; } void CGameView::PushCameraTarget( const CVector3D& target ) { // Save the current position m->CameraTargets.push_back( m->ViewCamera.m_Orientation.GetTranslation() ); // And set the camera SetCameraTarget( target ); } void CGameView::SetCameraTarget( const CVector3D& target ) { // Maintain the same orientation and level of zoom, if we can // (do this by working out the point the camera is looking at, saving // the difference between that position and the camera point, and restoring // that difference to our new target) CVector3D CurrentTarget = m->ViewCamera.GetFocus(); m->CameraDelta = target - CurrentTarget; } void CGameView::PopCameraTarget() { m->CameraDelta = m->CameraTargets.back() - m->ViewCamera.m_Orientation.GetTranslation(); m->CameraTargets.pop_back(); } InReaction game_view_handler(const SDL_Event_* ev) { // put any events that must be processed even if inactive here if(!g_app_has_focus || !g_Game) return IN_PASS; CGameView *pView=g_Game->GetView(); return pView->HandleEvent(ev); } InReaction CGameView::HandleEvent(const SDL_Event_* ev) { switch(ev->ev.type) { case SDL_HOTKEYDOWN: switch(ev->ev.user.code) { case HOTKEY_WIREFRAME: if (g_Renderer.GetModelRenderMode() == SOLID) { g_Renderer.SetTerrainRenderMode(EDGED_FACES); g_Renderer.SetModelRenderMode(EDGED_FACES); } else if (g_Renderer.GetModelRenderMode() == EDGED_FACES) { g_Renderer.SetTerrainRenderMode(WIREFRAME); g_Renderer.SetModelRenderMode(WIREFRAME); } else { g_Renderer.SetTerrainRenderMode(SOLID); g_Renderer.SetModelRenderMode(SOLID); } return( IN_HANDLED ); case HOTKEY_CAMERA_RESET_ORIGIN: ResetCamera(); return( IN_HANDLED ); case HOTKEY_CAMERA_RESET: ResetCameraOrientation(); return( IN_HANDLED ); case HOTKEY_CAMERA_ROTATE_ABOUT_TARGET: RotateAboutTarget(); return( IN_HANDLED ); // Mouse wheel must be treated using events instead of polling, // because SDL auto-generates a sequence of mousedown/mouseup events // and we never get to see the "down" state inside Update(). case HOTKEY_CAMERA_ZOOM_WHEEL_IN: m->ZoomDelta += m->ViewZoomSensitivityWheel; return( IN_HANDLED ); case HOTKEY_CAMERA_ZOOM_WHEEL_OUT: m->ZoomDelta -= m->ViewZoomSensitivityWheel; return( IN_HANDLED ); default: if( ( ev->ev.user.code >= HOTKEY_CAMERA_BOOKMARK_0 ) && ( ev->ev.user.code <= HOTKEY_CAMERA_BOOKMARK_9 ) ) { // The above test limits it to 10 bookmarks, so don't worry about overflowing i8 id = (i8)( ev->ev.user.code - HOTKEY_CAMERA_BOOKMARK_0 ); if( hotkeys[HOTKEY_CAMERA_BOOKMARK_SAVE] ) { // Attempt to track the ground we're looking at cameraBookmarks[id] = GetCamera()->GetFocus(); bookmarkInUse[id] = true; } else if( hotkeys[HOTKEY_CAMERA_BOOKMARK_SNAP] ) { if( bookmarkInUse[id] && ( currentBookmark == -1 ) ) { PushCameraTarget( cameraBookmarks[id] ); currentBookmark = id; } } else { if( bookmarkInUse[id] ) SetCameraTarget( cameraBookmarks[id] ); } return( IN_HANDLED ); } } case SDL_HOTKEYUP: switch( ev->ev.user.code ) { case HOTKEY_CAMERA_BOOKMARK_SNAP: if( currentBookmark != -1 ) PopCameraTarget(); currentBookmark = -1; break; default: return( IN_PASS ); } return( IN_HANDLED ); } return IN_PASS; } bool CGameViewImpl::JSI_StartCustomSelection( JSContext* UNUSED(context), uintN UNUSED(argc), jsval* UNUSED(argv)) { StartCustomSelection(); return true; } bool CGameViewImpl::JSI_EndCustomSelection( JSContext* UNUSED(context), uintN UNUSED(argc), jsval* UNUSED(argv)) { ResetInteraction(); return true; }