Index: ps/trunk/source/graphics/CinemaManager.cpp =================================================================== --- ps/trunk/source/graphics/CinemaManager.cpp (revision 19374) +++ ps/trunk/source/graphics/CinemaManager.cpp (revision 19375) @@ -1,298 +1,212 @@ -/* Copyright (C) 2016 Wildfire Games. +/* Copyright (C) 2017 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 #include -#include "graphics/Camera.h" #include "graphics/CinemaManager.h" + +#include "graphics/Camera.h" #include "graphics/GameView.h" #include "gui/CGUI.h" +#include "gui/GUIutil.h" #include "gui/GUIManager.h" #include "gui/IGUIObject.h" #include "lib/ogl.h" #include "maths/MathUtil.h" #include "maths/Quaternion.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" #include "ps/CLogger.h" +#include "ps/ConfigDB.h" #include "ps/CStr.h" #include "ps/Game.h" #include "ps/GameSetup/Config.h" #include "ps/Hotkey.h" +#include "simulation2/components/ICmpCinemaManager.h" #include "simulation2/components/ICmpOverlayRenderer.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpSelectable.h" #include "simulation2/components/ICmpTerritoryManager.h" #include "simulation2/MessageTypes.h" #include "simulation2/system/ComponentManager.h" #include "simulation2/Simulation2.h" #include "renderer/Renderer.h" CCinemaManager::CCinemaManager() : m_DrawPaths(false) { } -void CCinemaManager::AddPath(const CStrW& name, const CCinemaPath& path) +void CCinemaManager::Update(const float deltaRealTime) const { - if (m_CinematicSimulationData.m_Paths.find(name) != m_CinematicSimulationData.m_Paths.end()) - { - LOGWARNING("Path with name '%s' already exists", name.ToUTF8()); + CmpPtr cmpCinemaManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity()); + if (!cmpCinemaManager) return; - } - m_CinematicSimulationData.m_Paths[name] = path; -} -void CCinemaManager::AddPathToQueue(const CStrW& name) -{ - if (!HasPath(name)) - { - LOGWARNING("Path with name '%s' doesn't exist", name.ToUTF8()); - return; - } - m_CinematicSimulationData.m_PathQueue.push_back(m_CinematicSimulationData.m_Paths[name]); -} + UpdateSessionVisibility(); + UpdateSilhouettesVisibility(); -void CCinemaManager::ClearQueue() -{ - m_CinematicSimulationData.m_PathQueue.clear(); + if (IsPlaying()) + cmpCinemaManager->PlayQueue(deltaRealTime, g_Game->GetView()->GetCamera()); } -void CCinemaManager::SetAllPaths(const std::map& paths) +void CCinemaManager::Render() const { - m_CinematicSimulationData.m_Paths = paths; -} + if (IsEnabled()) + DrawBars(); -bool CCinemaManager::HasPath(const CStrW& name) const -{ - return m_CinematicSimulationData.m_Paths.find(name) != m_CinematicSimulationData.m_Paths.end(); + if (m_DrawPaths) + DrawPaths(); } -void CCinemaManager::SetEnabled(bool enabled) +void CCinemaManager::UpdateSessionVisibility() const { - // TODO: maybe assert? - if (m_CinematicSimulationData.m_PathQueue.empty() && enabled) - { - enabled = false; - m_CinematicSimulationData.m_Paused = true; - } - - if (m_CinematicSimulationData.m_Enabled == enabled) - return; - // TODO: Enabling/Disabling does not work if the session GUI page is not the top page. - // This can happen in various situations, for example when the player wins/looses the game + // This can happen in various situations, for example when the player wins/loses the game // while the cinematic is running (a message box is the top page in this case). - // It might be better to disable the whole GUI during the cinematic instead of a specific - // GUI object. - // sn - session gui object IGUIObject *sn = g_GUI->FindObjectByName("sn"); - CmpPtr cmpRangeManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity()); - CmpPtr cmpTerritoryManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity()); - - // GUI visibility - if (sn) - { - if (enabled) - sn->SetSetting("hidden", L"true"); - else - sn->SetSetting("hidden", L"false"); - } - - // Overlay visibility - g_Renderer.SetOptionBool(CRenderer::Option::OPT_SILHOUETTES, !enabled); - if (cmpRangeManager) - { - if (enabled) - m_CinematicSimulationData.m_MapRevealed = cmpRangeManager->GetLosRevealAll(-1); - // TODO: improve m_MapRevealed state and without fade in - cmpRangeManager->SetLosRevealAll(-1, enabled); - } - if (cmpTerritoryManager) - cmpTerritoryManager->SetVisibility(!enabled); - ICmpSelectable::SetOverrideVisibility(!enabled); - ICmpOverlayRenderer::SetOverrideVisibility(!enabled); + if (!sn) + return; - m_CinematicSimulationData.m_Enabled = enabled; -} + bool hidden = false; + GUI::GetSetting(sn, "hidden", hidden); -void CCinemaManager::Play() -{ - m_CinematicSimulationData.m_Paused = false; + if (hidden != IsPlaying()) + sn->SetSetting("hidden", IsPlaying() ? L"true" : L"false"); } -void CCinemaManager::Stop() +void CCinemaManager::UpdateSilhouettesVisibility() const { - m_CinematicSimulationData.m_PathQueue.clear(); -} - -void CCinemaManager::Update(const float deltaRealTime) -{ - if (g_Game->m_Paused != m_CinematicSimulationData.m_Paused) - { - m_CinematicSimulationData.m_Paused = g_Game->m_Paused; - - // sn - session gui object - IGUIObject *sn = g_GUI->FindObjectByName("sn"); - - // GUI visibility - if (sn) - { - if (m_CinematicSimulationData.m_Paused) - sn->SetSetting("hidden", L"false"); - else - sn->SetSetting("hidden", L"true"); - } - } - - if (m_CinematicSimulationData.m_PathQueue.empty() || !m_CinematicSimulationData.m_Enabled || m_CinematicSimulationData.m_Paused) + if (!CRenderer::IsInitialised()) return; - if (HotkeyIsPressed("leave")) - { - // TODO: implement skip - } - - CCamera *camera = g_Game->GetView()->GetCamera(); - m_CinematicSimulationData.m_PathQueue.front().Play(deltaRealTime, camera); + bool silhouettes = false; + CFG_GET_VAL("silhouettes", silhouettes); + g_Renderer.SetOptionBool(CRenderer::Option::OPT_SILHOUETTES, !IsEnabled() && silhouettes); } -void CCinemaManager::Render() +void CCinemaManager::DrawPaths() const { - if (GetEnabled()) - { - DrawBars(); - return; - } - - if (!m_DrawPaths) + CmpPtr cmpCinemaManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity()); + if (!cmpCinemaManager) return; - // draw all paths - for (const std::pair& p : m_CinematicSimulationData.m_Paths) + for (const std::pair& p : cmpCinemaManager->GetPaths()) p.second.Draw(); } void CCinemaManager::DrawBars() const { int height = (float)g_xres / 2.39f; int shift = (g_yres - height) / 2; if (shift <= 0) return; #if CONFIG2_GLES #warning TODO : implement bars for GLES #else // Set up transform for GL bars glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); CMatrix3D transform; transform.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); glLoadMatrixf(&transform._11); glColor4f(0.0f, 0.0f, 0.0f, 1.0f); glEnable(GL_BLEND); glDisable(GL_DEPTH_TEST); glBegin(GL_QUADS); glVertex2i(0, 0); glVertex2i(g_xres, 0); glVertex2i(g_xres, shift); glVertex2i(0, shift); glEnd(); glBegin(GL_QUADS); glVertex2i(0, g_yres - shift); glVertex2i(g_xres, g_yres - shift); glVertex2i(g_xres, g_yres); glVertex2i(0, g_yres); glEnd(); glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); // Restore transform glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); #endif } InReaction cinema_manager_handler(const SDL_Event_* ev) { // put any events that must be processed even if inactive here if (!g_Game || !g_Game->IsGameStarted()) return IN_PASS; CCinemaManager* pCinemaManager = g_Game->GetView()->GetCinema(); return pCinemaManager->HandleEvent(ev); } -InReaction CCinemaManager::HandleEvent(const SDL_Event_* ev) +InReaction CCinemaManager::HandleEvent(const SDL_Event_* ev) const { switch (ev->ev.type) { case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: - if (GetEnabled() && !m_CinematicSimulationData.m_Paused) + // Prevent selection of units while the path is playing + if (IsPlaying()) return IN_HANDLED; default: return IN_PASS; } } -bool CCinemaManager::GetEnabled() const +bool CCinemaManager::IsEnabled() const { - return m_CinematicSimulationData.m_Enabled; + CmpPtr cmpCinemaManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity()); + return cmpCinemaManager && cmpCinemaManager->IsEnabled(); } bool CCinemaManager::IsPlaying() const { - return !m_CinematicSimulationData.m_Paused; -} - -const std::map& CCinemaManager::GetAllPaths() -{ - return m_CinematicSimulationData.m_Paths; -} - -CinematicSimulationData* CCinemaManager::GetCinematicSimulationData() -{ - return &m_CinematicSimulationData; + return IsEnabled() && g_Game && !g_Game->m_Paused; } bool CCinemaManager::GetPathsDrawing() const { return m_DrawPaths; } void CCinemaManager::SetPathsDrawing(const bool drawPath) { m_DrawPaths = drawPath; } Index: ps/trunk/source/graphics/CinemaManager.h =================================================================== --- ps/trunk/source/graphics/CinemaManager.h (revision 19374) +++ ps/trunk/source/graphics/CinemaManager.h (revision 19375) @@ -1,142 +1,67 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2017 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 . */ #ifndef INCLUDED_CINEMAMANAGER #define INCLUDED_CINEMAMANAGER #include #include #include "lib/input.h" // InReaction - can't forward-declare enum #include "graphics/CinemaPath.h" #include "ps/CStr.h" -/* - desc: contains various functions used for cinematic camera paths - See also: CinemaHandler.cpp, CinemaPath.cpp -*/ - -// Cinematic structure for data accessable from the simulation -struct CinematicSimulationData -{ - bool m_Enabled; - bool m_Paused; - std::map m_Paths; - std::list m_PathQueue; - - // States before playing - bool m_MapRevealed; - - fixed m_ElapsedTime, m_TotalTime, m_CurrentPathElapsedTime; - - CinematicSimulationData() - : m_Enabled(false), - m_Paused(true), - m_MapRevealed(false), - m_ElapsedTime(fixed::Zero()), - m_TotalTime(fixed::Zero()), - m_CurrentPathElapsedTime(fixed::Zero()) - {} -}; - /** * Class for in game playing of cinematics. Should only be instantiated in CGameView. */ - class CCinemaManager { public: CCinemaManager(); ~CCinemaManager() {} /** - * Adds the path to the path list - * @param name path name - * @param CCinemaPath path data - */ - void AddPath(const CStrW& name, const CCinemaPath& path); - - /** - * Adds the path to the playlist - * @param name path name - */ - void AddPathToQueue(const CStrW& name); - - /** - * Clears the playlist - */ - void ClearQueue(); - - /** - * Checks the path name in the path list - * @param name path name - * @return true if path with that name exists, else false - */ - bool HasPath(const CStrW& name) const; - - const std::map& GetAllPaths(); - void SetAllPaths( const std::map& tracks); - - /** - * Starts play paths - */ - void Play(); - void Stop(); - bool IsPlaying() const; - - /** * Renders black bars and paths (if enabled) */ - void Render(); + void Render() const; void DrawBars() const; + void DrawPaths() const; + void UpdateSessionVisibility() const; + void UpdateSilhouettesVisibility() const; - /** - * Get current enable state of the cinema manager - */ - bool GetEnabled() const; - - /** - * Sets enable state of the cinema manager (shows/hide gui, show/hide rings, etc) - * @enable new state - */ - void SetEnabled(bool enabled); + bool IsPlaying() const; + bool IsEnabled() const; /** * Updates CCinemManager and current path * @param deltaRealTime Elapsed real time since the last frame. */ - void Update(const float deltaRealTime); - - InReaction HandleEvent(const SDL_Event_* ev); - - CinematicSimulationData* GetCinematicSimulationData(); + void Update(const float deltaRealTime) const; + InReaction HandleEvent(const SDL_Event_* ev) const; bool GetPathsDrawing() const; void SetPathsDrawing(const bool drawPath); private: bool m_DrawPaths; - - // Cinematic data is accessed from the simulation - CinematicSimulationData m_CinematicSimulationData; }; extern InReaction cinema_manager_handler(const SDL_Event_* ev); #endif Index: ps/trunk/source/graphics/CinemaPath.cpp =================================================================== --- ps/trunk/source/graphics/CinemaPath.cpp (revision 19374) +++ ps/trunk/source/graphics/CinemaPath.cpp (revision 19375) @@ -1,371 +1,369 @@ -/* Copyright (C) 2016 Wildfire Games. +/* Copyright (C) 2017 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 "CinemaPath.h" #include #include #include "Camera.h" #include "CinemaManager.h" #include "GameView.h" #include "gui/CGUI.h" #include "gui/GUIManager.h" #include "gui/IGUIObject.h" #include "lib/ogl.h" #include "maths/MathUtil.h" #include "maths/Quaternion.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Game.h" #include "renderer/Renderer.h" CCinemaPath::CCinemaPath(const CCinemaData& data, const TNSpline& spline, const TNSpline& targetSpline) : CCinemaData(data), TNSpline(spline), m_TargetSpline(targetSpline), m_TimeElapsed(0.f) { - m_TimeElapsed = 0; - // Calculate curves by nodes BuildSpline(); m_TargetSpline.BuildSpline(); if (m_Orientation == L"target") { m_LookAtTarget = true; ENSURE(!m_TargetSpline.GetAllNodes().empty()); } // Set distortion mode and style if (data.m_Mode == L"ease_in") DistModePtr = &CCinemaPath::EaseIn; else if (data.m_Mode == L"ease_out") DistModePtr = &CCinemaPath::EaseOut; else if (data.m_Mode == L"ease_inout") DistModePtr = &CCinemaPath::EaseInOut; else if (data.m_Mode == L"ease_outin") DistModePtr = &CCinemaPath::EaseOutIn; else { LOGWARNING("Cinematic mode not found for '%s'", data.m_Mode.ToUTF8().c_str()); DistModePtr = &CCinemaPath::EaseInOut; } if (data.m_Style == L"default") DistStylePtr = &CCinemaPath::EaseDefault; else if (data.m_Style == L"growth") DistStylePtr = &CCinemaPath::EaseGrowth; else if (data.m_Style == L"expo") DistStylePtr = &CCinemaPath::EaseExpo; else if (data.m_Style == L"circle") DistStylePtr = &CCinemaPath::EaseCircle; else if (data.m_Style == L"sine") DistStylePtr = &CCinemaPath::EaseSine; else { LOGWARNING("Cinematic style not found for '%s'", data.m_Style.ToUTF8().c_str()); DistStylePtr = &CCinemaPath::EaseDefault; } } void CCinemaPath::Draw() const { DrawSpline(*this, CVector4D(0.2f, 0.2f, 1.f, 0.5f), 100, true); DrawNodes(*this, CVector4D(0.5f, 1.0f, 0.f, 0.5f)); if (!m_LookAtTarget) return; DrawSpline(m_TargetSpline, CVector4D(1.0f, 0.2f, 0.2f, 0.5f), 100, true); DrawNodes(m_TargetSpline, CVector4D(1.0f, 0.5f, 0.f, 0.5f)); } void CCinemaPath::DrawSpline(const RNSpline& spline, const CVector4D& RGBA, int smoothness, bool lines) const { if (spline.NodeCount < 2 || DistModePtr == NULL) return; if (spline.NodeCount == 2 && lines) smoothness = 2; float start = spline.MaxDistance.ToFloat() / smoothness; float time = 0; #if CONFIG2_GLES #warning TODO: do something about CCinemaPath on GLES #else glColor4f(RGBA.X, RGBA.Y, RGBA.Z, RGBA.W); if (lines) { glLineWidth(1.8f); glEnable(GL_LINE_SMOOTH); glBegin(GL_LINE_STRIP); for (int i = 0; i <= smoothness; ++i) { // Find distorted time time = start*i / spline.MaxDistance.ToFloat(); CVector3D tmp = spline.GetPosition(time); glVertex3f(tmp.X, tmp.Y, tmp.Z); } glEnd(); glDisable(GL_LINE_SMOOTH); glLineWidth(1.0f); } else { smoothness /= 2; start = spline.MaxDistance.ToFloat() / smoothness; glEnable(GL_POINT_SMOOTH); glPointSize(3.0f); glBegin(GL_POINTS); for (int i = 0; i <= smoothness; ++i) { // Find distorted time time = (this->*DistModePtr)(start*i / spline.MaxDistance.ToFloat()); CVector3D tmp = spline.GetPosition(time); glVertex3f(tmp.X, tmp.Y, tmp.Z); } glColor3f(1.0f, 1.0f, 0.0f); // yellow for (size_t i = 0; i < spline.GetAllNodes().size(); ++i) glVertex3f( spline.GetAllNodes()[i].Position.X.ToFloat(), spline.GetAllNodes()[i].Position.Y.ToFloat(), spline.GetAllNodes()[i].Position.Z.ToFloat() ); glEnd(); glPointSize(1.0f); glDisable(GL_POINT_SMOOTH); } #endif } void CCinemaPath::DrawNodes(const RNSpline& spline, const CVector4D& RGBA) const { #if CONFIG2_GLES #warning TODO : do something about CCinemaPath on GLES #else glEnable(GL_POINT_SMOOTH); glPointSize(5.0f); glColor4f(RGBA.X, RGBA.Y, RGBA.Z, RGBA.W); glBegin(GL_POINTS); const std::vector& nodes = spline.GetAllNodes(); for (size_t i = 0; i < nodes.size(); ++i) glVertex3f(nodes[i].Position.X.ToFloat(), nodes[i].Position.Y.ToFloat(), nodes[i].Position.Z.ToFloat()); glEnd(); glPointSize(1.0f); glDisable(GL_POINT_SMOOTH); #endif } CVector3D CCinemaPath::GetNodePosition(const int index) const { return Node[index].Position; } fixed CCinemaPath::GetNodeDuration(const int index) const { return Node[index].Distance; } fixed CCinemaPath::GetDuration() const { return MaxDistance; } float CCinemaPath::GetNodeFraction() const { return (m_TimeElapsed - m_PreviousNodeTime) / Node[m_CurrentNode].Distance.ToFloat(); } float CCinemaPath::GetElapsedTime() const { return m_TimeElapsed; } CStrW CCinemaPath::GetName() const { return m_Name; } void CCinemaPath::SetTimescale(fixed scale) { m_Timescale = scale; } -void CCinemaPath::MoveToPointAt(float t, float nodet, const CVector3D& startRotation, CCamera* camera) +void CCinemaPath::MoveToPointAt(float t, float nodet, const CVector3D& startRotation, CCamera* camera) const { t = (this->*DistModePtr)(t); CVector3D pos = GetPosition(t); if (m_LookAtTarget) { if (m_TimeElapsed <= m_TargetSpline.MaxDistance.ToFloat()) camera->LookAt(pos, m_TargetSpline.GetPosition(m_TimeElapsed / m_TargetSpline.MaxDistance.ToFloat()), CVector3D(0, 1, 0)); else camera->LookAt(pos, m_TargetSpline.GetAllNodes().back().Position, CVector3D(0, 1, 0)); } else { CVector3D nodeRotation = Node[m_CurrentNode + 1].Rotation; CQuaternion start, end; start.FromEulerAngles(DEGTORAD(startRotation.X), DEGTORAD(startRotation.Y), DEGTORAD(startRotation.Z)); end.FromEulerAngles(DEGTORAD(nodeRotation.X), DEGTORAD(nodeRotation.Y), DEGTORAD(nodeRotation.Z)); start.Slerp(start, end, nodet); camera->m_Orientation.SetIdentity(); camera->m_Orientation.Rotate(start); camera->m_Orientation.Translate(pos); } camera->UpdateFrustum(); } // Distortion mode functions float CCinemaPath::EaseIn(float t) const { return (this->*DistStylePtr)(t); } float CCinemaPath::EaseOut(float t) const { return 1.0f - EaseIn(1.0f-t); } float CCinemaPath::EaseInOut(float t) const { if (t < m_Switch) return EaseIn(1.0f/m_Switch * t) * m_Switch; return EaseOut(1.0f/m_Switch * (t-m_Switch)) * m_Switch + m_Switch; } float CCinemaPath::EaseOutIn(float t) const { if (t < m_Switch) return EaseOut(1.0f/m_Switch * t) * m_Switch; return EaseIn(1.0f/m_Switch * (t-m_Switch)) * m_Switch + m_Switch; } // Distortion style functions float CCinemaPath::EaseDefault(float t) const { return t; } float CCinemaPath::EaseGrowth(float t) const { return pow(t, m_Growth); } float CCinemaPath::EaseExpo(float t) const { if (t == 0) return t; return powf(m_Growth, 10*(t-1.0f)); } float CCinemaPath::EaseCircle(float t) const { t = -(sqrt(1.0f - t*t) - 1.0f); if (m_GrowthCount > 1.0f) { --m_GrowthCount; return (this->*DistStylePtr)(t); } return t; } float CCinemaPath::EaseSine(float t) const { t = 1.0f - cos(t * (float)M_PI/2); if (m_GrowthCount > 1.0f) { --m_GrowthCount; return (this->*DistStylePtr)(t); } return t; } const CCinemaData* CCinemaPath::GetData() const { return CCinemaData::GetData(); } bool CCinemaPath::Validate() { if (m_TimeElapsed > GetDuration().ToFloat() || m_TimeElapsed < 0.0f) return false; // Find current node and past "node time" float previousTime = 0.0f, cumulation = 0.0f; // Ignore the last node, since it is a blank (node time values are shifted down one from interface) for (size_t i = 0; i < Node.size() - 1; ++i) { cumulation += Node[i].Distance.ToFloat(); if (m_TimeElapsed <= cumulation) { m_PreviousNodeTime = previousTime; m_PreviousRotation = Node[i].Rotation; m_CurrentNode = i; // We're moving toward this next node, so use its rotation return true; } previousTime += Node[i].Distance.ToFloat(); } debug_warn("validation of cinema path is wrong\n"); return false; } bool CCinemaPath::Play(const float deltaRealTime, CCamera* camera) { m_TimeElapsed += m_Timescale.ToFloat() * deltaRealTime; if (!Validate()) return false; MoveToPointAt(m_TimeElapsed / GetDuration().ToFloat(), GetNodeFraction(), m_PreviousRotation, camera); return true; } bool CCinemaPath::Empty() const { return Node.empty(); } void CCinemaPath::Reset() { m_TimeElapsed = 0.0f; } fixed CCinemaPath::GetTimescale() const { return m_Timescale; } const TNSpline& CCinemaPath::GetTargetSpline() const { return m_TargetSpline; } Index: ps/trunk/source/graphics/CinemaPath.h =================================================================== --- ps/trunk/source/graphics/CinemaPath.h (revision 19374) +++ ps/trunk/source/graphics/CinemaPath.h (revision 19375) @@ -1,141 +1,141 @@ -/* Copyright (C) 2016 Wildfire Games. +/* Copyright (C) 2017 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 . */ #ifndef INCLUDED_CINEMAPATH #define INCLUDED_CINEMAPATH #include "maths/NUSpline.h" #include "ps/CStr.h" class CVector3D; class CVector4D; class CCamera; // For loading data class CCinemaData { public: CCinemaData() : m_LookAtTarget(false), m_GrowthCount(0), m_Growth(1), m_Switch(1), m_Timescale(fixed::FromInt(1)) {} virtual ~CCinemaData() {} const CCinemaData* GetData() const { return this; } CStrW m_Name; CStrW m_Orientation; CStrW m_Mode; CStrW m_Style; bool m_LookAtTarget; fixed m_Timescale; // a negative timescale results in backwards play // Distortion variables mutable float m_GrowthCount; float m_Growth; float m_Switch; }; // Once the data is part of the path, it shouldn't be changeable, so use private inheritance. // This class encompasses the spline and the information which determines how the path will operate // and also provides the functionality for doing so class CCinemaPath : private CCinemaData, public TNSpline { public: CCinemaPath() : m_TimeElapsed(0), m_PreviousNodeTime(0) {} CCinemaPath(const CCinemaData& data, const TNSpline& spline, const TNSpline& targetSpline); // Sets camera position to calculated point on spline - void MoveToPointAt(float t, float nodet, const CVector3D& startRotation, CCamera* camera); + void MoveToPointAt(float t, float nodet, const CVector3D& startRotation, CCamera* camera) const; // Distortion mode functions-change how ratio is passed to distortion style functions float EaseIn(float t) const; float EaseOut(float t) const; float EaseInOut(float t) const; float EaseOutIn(float t) const; // Distortion style functions float EaseDefault(float t) const; float EaseGrowth(float t) const; float EaseExpo(float t) const; float EaseCircle(float t) const; float EaseSine(float t) const; float (CCinemaPath::*DistStylePtr)(float ratio) const; float (CCinemaPath::*DistModePtr)(float ratio) const; const CCinemaData* GetData() const; public: void Draw() const; void DrawSpline(const RNSpline& spline, const CVector4D& RGBA, int smoothness, bool lines) const; void DrawNodes(const RNSpline& spline, const CVector4D& RGBA) const; CVector3D GetNodePosition(const int index) const; fixed GetNodeDuration(const int index) const; fixed GetDuration() const; float GetNodeFraction() const; float GetElapsedTime() const; CStrW GetName() const; void SetTimescale(fixed scale); float m_TimeElapsed; float m_PreviousNodeTime; // How much time has passed before the current node size_t m_CurrentNode; CVector3D m_PreviousRotation; public: /** * Returns false if finished. * @param deltaRealTime Elapsed real time since the last frame. * @param camera An affected camera */ bool Play(const float deltaRealTime, CCamera* camera); /** * Validate the path * @return true if the path is valid */ bool Validate(); /** * Returns true if path doesn't contain nodes */ bool Empty() const; /** * Resets the path state */ void Reset(); fixed GetTimescale() const; const TNSpline& GetTargetSpline() const; private: TNSpline m_TargetSpline; }; #endif // INCLUDED_CINEMAPATH Index: ps/trunk/source/graphics/GameView.cpp =================================================================== --- ps/trunk/source/graphics/GameView.cpp (revision 19374) +++ ps/trunk/source/graphics/GameView.cpp (revision 19375) @@ -1,1156 +1,1154 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2017 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/CinemaManager.h" #include "graphics/ColladaManager.h" #include "graphics/HFTracer.h" #include "graphics/LOSTexture.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/TerrainTextureManager.h" #include "graphics/TerritoryTexture.h" #include "graphics/Unit.h" #include "graphics/UnitManager.h" #include "graphics/scripting/JSInterface_GameView.h" #include "lib/input.h" #include "lib/timer.h" #include "lobby/IXmppClient.h" #include "maths/BoundingBoxAligned.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" #include "maths/Quaternion.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Joystick.h" #include "ps/Loader.h" #include "ps/LoaderThunks.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "ps/TouchInput.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/WaterManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpRangeManager.h" extern int g_xres, g_yres; // Maximum distance outside the edge of the map that the camera's // focus point can be moved static const float CAMERA_EDGE_MARGIN = 2.0f*TERRAIN_TILE_SIZE; /** * A value with exponential decay towards the target value. */ class CSmoothedValue { public: CSmoothedValue(float value, float smoothness, float minDelta) : m_Target(value), m_Current(value), m_Smoothness(smoothness), m_MinDelta(minDelta) { } float GetSmoothedValue() { return m_Current; } void SetValueSmoothly(float value) { m_Target = value; } void AddSmoothly(float value) { m_Target += value; } void Add(float value) { m_Target += value; m_Current += value; } float GetValue() { return m_Target; } void SetValue(float value) { m_Target = value; m_Current = value; } float Update(float time) { if (fabs(m_Target - m_Current) < m_MinDelta) return 0.0f; double p = pow((double)m_Smoothness, 10.0 * (double)time); // (add the factor of 10 so that smoothnesses don't have to be tiny numbers) double delta = (m_Target - m_Current) * (1.0 - p); m_Current += delta; return (float)delta; } void ClampSmoothly(float min, float max) { m_Target = Clamp(m_Target, (double)min, (double)max); } // Wrap so 'target' is in the range [min, max] void Wrap(float min, float max) { double t = fmod(m_Target - min, (double)(max - min)); if (t < 0) t += max - min; t += min; m_Current += t - m_Target; m_Target = t; } private: double m_Target; // the value which m_Current is tending towards double m_Current; // (We use double because the extra precision is worthwhile here) float m_MinDelta; // cutoff where we stop moving (to avoid ugly shimmering effects) public: float m_Smoothness; }; class CGameViewImpl { NONCOPYABLE(CGameViewImpl); public: CGameViewImpl(CGame* game) : Game(game), ColladaManager(g_VFS), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager), ObjectManager(MeshManager, SkeletonAnimManager, *game->GetSimulation2()), LOSTexture(*game->GetSimulation2()), TerritoryTexture(*game->GetSimulation2()), ViewCamera(), CullCamera(), LockCullCamera(false), ConstrainCamera(true), Culling(true), FollowEntity(INVALID_ENTITY), FollowFirstPerson(false), // Dummy values (these will be filled in by the config file) ViewScrollSpeed(0), ViewScrollSpeedModifier(1), ViewRotateXSpeed(0), ViewRotateXMin(0), ViewRotateXMax(0), ViewRotateXDefault(0), ViewRotateYSpeed(0), ViewRotateYSpeedWheel(0), ViewRotateYDefault(0), ViewRotateSpeedModifier(1), ViewDragSpeed(0), ViewZoomSpeed(0), ViewZoomSpeedWheel(0), ViewZoomMin(0), ViewZoomMax(0), ViewZoomDefault(0), ViewZoomSpeedModifier(1), ViewFOV(DEGTORAD(45.f)), ViewNear(2.f), ViewFar(4096.f), JoystickPanX(-1), JoystickPanY(-1), JoystickRotateX(-1), JoystickRotateY(-1), JoystickZoomIn(-1), JoystickZoomOut(-1), HeightSmoothness(0.5f), HeightMin(16.f), PosX(0, 0, 0.01f), PosY(0, 0, 0.01f), PosZ(0, 0, 0.01f), Zoom(0, 0, 0.1f), RotateX(0, 0, 0.001f), RotateY(0, 0, 0.001f) { } CGame* Game; CColladaManager ColladaManager; CMeshManager MeshManager; CSkeletonAnimManager SkeletonAnimManager; CObjectManager ObjectManager; CLOSTexture LOSTexture; CTerritoryTexture TerritoryTexture; /** * 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; /** * Whether the camera movement should be constrained by min/max limits * and terrain avoidance. */ bool ConstrainCamera; /** * 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 CinemaManager; /** * Entity for the camera to follow, or INVALID_ENTITY if none. */ entity_id_t FollowEntity; /** * Whether to follow FollowEntity in first-person mode. */ bool FollowFirstPerson; //////////////////////////////////////// // Settings float ViewScrollSpeed; float ViewScrollSpeedModifier; float ViewRotateXSpeed; float ViewRotateXMin; float ViewRotateXMax; float ViewRotateXDefault; float ViewRotateYSpeed; float ViewRotateYSpeedWheel; float ViewRotateYDefault; float ViewRotateSpeedModifier; float ViewDragSpeed; float ViewZoomSpeed; float ViewZoomSpeedWheel; float ViewZoomMin; float ViewZoomMax; float ViewZoomDefault; float ViewZoomSpeedModifier; float ViewFOV; float ViewNear; float ViewFar; int JoystickPanX; int JoystickPanY; int JoystickRotateX; int JoystickRotateY; int JoystickZoomIn; int JoystickZoomOut; float HeightSmoothness; float HeightMin; //////////////////////////////////////// // Camera Controls State CSmoothedValue PosX; CSmoothedValue PosY; CSmoothedValue PosZ; CSmoothedValue Zoom; CSmoothedValue RotateX; // inclination around x axis (relative to camera) CSmoothedValue RotateY; // rotation around y (vertical) axis }; #define IMPLEMENT_BOOLEAN_SETTING(NAME) \ bool CGameView::Get##NAME##Enabled() \ { \ return m->NAME; \ } \ \ void CGameView::Set##NAME##Enabled(bool Enabled) \ { \ m->NAME = Enabled; \ } IMPLEMENT_BOOLEAN_SETTING(Culling); IMPLEMENT_BOOLEAN_SETTING(LockCullCamera); IMPLEMENT_BOOLEAN_SETTING(ConstrainCamera); #undef IMPLEMENT_BOOLEAN_SETTING static void SetupCameraMatrixSmooth(CGameViewImpl* m, CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m->RotateX.GetSmoothedValue()); orientation->RotateY(m->RotateY.GetSmoothedValue()); orientation->Translate(m->PosX.GetSmoothedValue(), m->PosY.GetSmoothedValue(), m->PosZ.GetSmoothedValue()); } static void SetupCameraMatrixSmoothRot(CGameViewImpl* m, CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m->RotateX.GetSmoothedValue()); orientation->RotateY(m->RotateY.GetSmoothedValue()); orientation->Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue()); } static void SetupCameraMatrixNonSmooth(CGameViewImpl* m, CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m->RotateX.GetValue()); orientation->RotateY(m->RotateY.GetValue()); orientation->Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue()); } 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(m->ViewNear, m->ViewFar, m->ViewFOV); SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); m->CullCamera = m->ViewCamera; g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera); } CGameView::~CGameView() { UnloadResources(); delete m; } void CGameView::SetViewport(const SViewPort& vp) { m->ViewCamera.SetViewPort(vp); m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); } CObjectManager& CGameView::GetObjectManager() const { return m->ObjectManager; } CCamera* CGameView::GetCamera() { return &m->ViewCamera; } CCinemaManager* CGameView::GetCinema() { return &m->CinemaManager; }; CLOSTexture& CGameView::GetLOSTexture() { return m->LOSTexture; } CTerritoryTexture& CGameView::GetTerritoryTexture() { return m->TerritoryTexture; } int CGameView::Initialize() { CFG_GET_VAL("view.scroll.speed", m->ViewScrollSpeed); CFG_GET_VAL("view.scroll.speed.modifier", m->ViewScrollSpeedModifier); CFG_GET_VAL("view.rotate.x.speed", m->ViewRotateXSpeed); CFG_GET_VAL("view.rotate.x.min", m->ViewRotateXMin); CFG_GET_VAL("view.rotate.x.max", m->ViewRotateXMax); CFG_GET_VAL("view.rotate.x.default", m->ViewRotateXDefault); CFG_GET_VAL("view.rotate.y.speed", m->ViewRotateYSpeed); CFG_GET_VAL("view.rotate.y.speed.wheel", m->ViewRotateYSpeedWheel); CFG_GET_VAL("view.rotate.y.default", m->ViewRotateYDefault); CFG_GET_VAL("view.rotate.speed.modifier", m->ViewRotateSpeedModifier); CFG_GET_VAL("view.drag.speed", m->ViewDragSpeed); CFG_GET_VAL("view.zoom.speed", m->ViewZoomSpeed); CFG_GET_VAL("view.zoom.speed.wheel", m->ViewZoomSpeedWheel); CFG_GET_VAL("view.zoom.min", m->ViewZoomMin); CFG_GET_VAL("view.zoom.max", m->ViewZoomMax); CFG_GET_VAL("view.zoom.default", m->ViewZoomDefault); CFG_GET_VAL("view.zoom.speed.modifier", m->ViewZoomSpeedModifier); CFG_GET_VAL("joystick.camera.pan.x", m->JoystickPanX); CFG_GET_VAL("joystick.camera.pan.y", m->JoystickPanY); CFG_GET_VAL("joystick.camera.rotate.x", m->JoystickRotateX); CFG_GET_VAL("joystick.camera.rotate.y", m->JoystickRotateY); CFG_GET_VAL("joystick.camera.zoom.in", m->JoystickZoomIn); CFG_GET_VAL("joystick.camera.zoom.out", m->JoystickZoomOut); CFG_GET_VAL("view.height.smoothness", m->HeightSmoothness); CFG_GET_VAL("view.height.min", m->HeightMin); CFG_GET_VAL("view.pos.smoothness", m->PosX.m_Smoothness); CFG_GET_VAL("view.pos.smoothness", m->PosY.m_Smoothness); CFG_GET_VAL("view.pos.smoothness", m->PosZ.m_Smoothness); CFG_GET_VAL("view.zoom.smoothness", m->Zoom.m_Smoothness); CFG_GET_VAL("view.rotate.x.smoothness", m->RotateX.m_Smoothness); CFG_GET_VAL("view.rotate.y.smoothness", m->RotateY.m_Smoothness); CFG_GET_VAL("view.near", m->ViewNear); CFG_GET_VAL("view.far", m->ViewFar); CFG_GET_VAL("view.fov", m->ViewFOV); // Convert to radians m->RotateX.SetValue(DEGTORAD(m->ViewRotateXDefault)); m->RotateY.SetValue(DEGTORAD(m->ViewRotateYDefault)); m->ViewFOV = DEGTORAD(m->ViewFOV); return 0; } void CGameView::RegisterInit() { // CGameView init RegMemFun(this, &CGameView::Initialize, L"CGameView init", 1); // previously done by CGameView::InitResources RegMemFun(g_TexMan.GetSingletonPtr(), &CTerrainTextureManager::LoadTerrainTextures, L"LoadTerrainTextures", 60); RegMemFun(g_Renderer.GetSingletonPtr(), &CRenderer::LoadAlphaMaps, L"LoadAlphaMaps", 5); } void CGameView::BeginFrame() { if (m->LockCullCamera == false) { // Set up cull camera m->CullCamera = m->ViewCamera; } g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera); CheckLightEnv(); m->Game->CachePlayerColors(); } void CGameView::Render() { 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) { { PROFILE3("submit terrain"); CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight + 0.001f; 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 CBoundingBoxAligned bounds = patch->GetWorldBounds(); if(bounds[1].Y < waterHeight) bounds[1].Y = waterHeight; if (!m->Culling || frustum.IsBoxVisible(bounds)) c->Submit(patch); } } } m->Game->GetSimulation2()->RenderSubmit(*c, frustum, m->Culling); } void CGameView::CheckLightEnv() { if (m->CachedLightEnv == g_LightEnv) return; if (m->CachedLightEnv.GetLightingModel() != g_LightEnv.GetLightingModel()) g_Renderer.MakeShadersDirty(); 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) units[i]->GetModel().SetDirtyRec(RENDERDATA_UPDATE_COLOR); } void CGameView::UnloadResources() { g_TexMan.UnloadTerrainTextures(); g_Renderer.UnloadAlphaMaps(); g_Renderer.GetWaterManager()->UnloadWaterTextures(); } static void FocusHeight(CGameViewImpl* m, bool smooth) { /* The camera pivot height is moved towards ground level. To prevent excessive zoom when looking over a cliff, the target ground level is the maximum of the ground level at the camera's near and pivot points. The ground levels are filtered to achieve smooth camera movement. The filter radius is proportional to the zoom level. The camera height is clamped to prevent map penetration. */ if (!m->ConstrainCamera) return; CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation); const CVector3D position = targetCam.m_Orientation.GetTranslation(); const CVector3D forwards = targetCam.m_Orientation.GetIn(); // horizontal view radius const float radius = sqrtf(forwards.X * forwards.X + forwards.Z * forwards.Z) * m->Zoom.GetSmoothedValue(); const float near_radius = radius * m->HeightSmoothness; const float pivot_radius = radius * m->HeightSmoothness; const CVector3D nearPoint = position + forwards * m->ViewNear; const CVector3D pivotPoint = position + forwards * m->Zoom.GetSmoothedValue(); const float ground = m->Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z); // filter ground levels for smooth camera movement const float filtered_near_ground = m->Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(nearPoint.X, nearPoint.Z, near_radius); const float filtered_pivot_ground = m->Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(pivotPoint.X, pivotPoint.Z, pivot_radius); // filtered maximum visible ground level in view const float filtered_ground = std::max(filtered_near_ground, filtered_pivot_ground); // target camera height above pivot point const float pivot_height = -forwards.Y * (m->Zoom.GetSmoothedValue() - m->ViewNear); // minimum camera height above filtered ground level const float min_height = (m->HeightMin + ground - filtered_ground); const float target_height = std::max(pivot_height, min_height); const float height = (nearPoint.Y - filtered_ground); const float diff = target_height - height; if (fabsf(diff) < 0.0001f) return; if (smooth) { m->PosY.AddSmoothly(diff); } else { m->PosY.Add(diff); } } CVector3D CGameView::GetSmoothPivot(CCamera& camera) const { return camera.m_Orientation.GetTranslation() + camera.m_Orientation.GetIn() * m->Zoom.GetSmoothedValue(); } void CGameView::Update(const float deltaRealTime) { // If camera movement is being handled by the touch-input system, // then we should stop to avoid conflicting with it if (g_TouchInput.IsEnabled()) return; if (!g_app_has_focus) return; - if (m->CinemaManager.GetEnabled()) - { - m->CinemaManager.Update(deltaRealTime); + m->CinemaManager.Update(deltaRealTime); + if (m->CinemaManager.IsEnabled()) return; - } // 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; if (HotkeyIsPressed("camera.rotate.cw")) m->RotateY.AddSmoothly(m->ViewRotateYSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.ccw")) m->RotateY.AddSmoothly(-m->ViewRotateYSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.up")) m->RotateX.AddSmoothly(-m->ViewRotateXSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.down")) m->RotateX.AddSmoothly(m->ViewRotateXSpeed * deltaRealTime); float moveRightward = 0.f; float moveForward = 0.f; if (HotkeyIsPressed("camera.pan")) { moveRightward += m->ViewDragSpeed * mouse_dx; moveForward += m->ViewDragSpeed * -mouse_dy; } if (g_mouse_active) { if (g_mouse_x >= g_xres - 2 && g_mouse_x < g_xres) moveRightward += m->ViewScrollSpeed * deltaRealTime; else if (g_mouse_x <= 3 && g_mouse_x >= 0) moveRightward -= m->ViewScrollSpeed * deltaRealTime; if (g_mouse_y >= g_yres - 2 && g_mouse_y < g_yres) moveForward -= m->ViewScrollSpeed * deltaRealTime; else if (g_mouse_y <= 3 && g_mouse_y >= 0) moveForward += m->ViewScrollSpeed * deltaRealTime; } if (HotkeyIsPressed("camera.right")) moveRightward += m->ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.left")) moveRightward -= m->ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.up")) moveForward += m->ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.down")) moveForward -= m->ViewScrollSpeed * deltaRealTime; if (g_Joystick.IsEnabled()) { // This could all be improved with extra speed and sensitivity settings // (maybe use pow to allow finer control?), and inversion settings moveRightward += g_Joystick.GetAxisValue(m->JoystickPanX) * m->ViewScrollSpeed * deltaRealTime; moveForward -= g_Joystick.GetAxisValue(m->JoystickPanY) * m->ViewScrollSpeed * deltaRealTime; m->RotateX.AddSmoothly(g_Joystick.GetAxisValue(m->JoystickRotateX) * m->ViewRotateXSpeed * deltaRealTime); m->RotateY.AddSmoothly(-g_Joystick.GetAxisValue(m->JoystickRotateY) * m->ViewRotateYSpeed * deltaRealTime); // Use a +1 bias for zoom because I want this to work with trigger buttons that default to -1 m->Zoom.AddSmoothly((g_Joystick.GetAxisValue(m->JoystickZoomIn) + 1.0f) / 2.0f * m->ViewZoomSpeed * deltaRealTime); m->Zoom.AddSmoothly(-(g_Joystick.GetAxisValue(m->JoystickZoomOut) + 1.0f) / 2.0f * m->ViewZoomSpeed * deltaRealTime); } if (moveRightward || moveForward) { // Break out of following mode when the user starts scrolling m->FollowEntity = INVALID_ENTITY; float s = sin(m->RotateY.GetSmoothedValue()); float c = cos(m->RotateY.GetSmoothedValue()); m->PosX.AddSmoothly(c * moveRightward); m->PosZ.AddSmoothly(-s * moveRightward); m->PosX.AddSmoothly(s * moveForward); m->PosZ.AddSmoothly(c * moveForward); } if (m->FollowEntity) { CmpPtr cmpPosition(*(m->Game->GetSimulation2()), m->FollowEntity); CmpPtr cmpRangeManager(*(m->Game->GetSimulation2()), SYSTEM_ENTITY); if (cmpPosition && cmpPosition->IsInWorld() && cmpRangeManager && cmpRangeManager->GetLosVisibility(m->FollowEntity, m->Game->GetViewedPlayerID()) == ICmpRangeManager::VIS_VISIBLE) { // Get the most recent interpolated position float frameOffset = m->Game->GetSimulation2()->GetLastFrameOffset(); CMatrix3D transform = cmpPosition->GetInterpolatedTransform(frameOffset); CVector3D pos = transform.GetTranslation(); if (m->FollowFirstPerson) { float x, z, angle; cmpPosition->GetInterpolatedPosition2D(frameOffset, x, z, angle); float height = 4.f; m->ViewCamera.m_Orientation.SetIdentity(); m->ViewCamera.m_Orientation.RotateX((float)M_PI/24.f); m->ViewCamera.m_Orientation.RotateY(angle); m->ViewCamera.m_Orientation.Translate(pos.X, pos.Y + height, pos.Z); m->ViewCamera.UpdateFrustum(); return; } else { // Move the camera to match the unit CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = pos - pivot; m->PosX.AddSmoothly(delta.X); m->PosY.AddSmoothly(delta.Y); m->PosZ.AddSmoothly(delta.Z); } } else { // The unit disappeared (died or garrisoned etc), so stop following it m->FollowEntity = INVALID_ENTITY; } } if (HotkeyIsPressed("camera.zoom.in")) m->Zoom.AddSmoothly(-m->ViewZoomSpeed * deltaRealTime); if (HotkeyIsPressed("camera.zoom.out")) m->Zoom.AddSmoothly(m->ViewZoomSpeed * deltaRealTime); if (m->ConstrainCamera) m->Zoom.ClampSmoothly(m->ViewZoomMin, m->ViewZoomMax); float zoomDelta = -m->Zoom.Update(deltaRealTime); if (zoomDelta) { CVector3D forwards = m->ViewCamera.m_Orientation.GetIn(); m->PosX.AddSmoothly(forwards.X * zoomDelta); m->PosY.AddSmoothly(forwards.Y * zoomDelta); m->PosZ.AddSmoothly(forwards.Z * zoomDelta); } if (m->ConstrainCamera) m->RotateX.ClampSmoothly(DEGTORAD(m->ViewRotateXMin), DEGTORAD(m->ViewRotateXMax)); FocusHeight(m, true); // Ensure the ViewCamera focus is inside the map with the chosen margins // if not so - apply margins to the camera if (m->ConstrainCamera) { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation); CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CVector3D desiredPivot = pivot; CmpPtr cmpRangeManager(*(m->Game->GetSimulation2()), SYSTEM_ENTITY); if (cmpRangeManager && cmpRangeManager->GetLosCircular()) { // Clamp to a circular region around the center of the map float r = pTerrain->GetMaxX() / 2; CVector3D center(r, desiredPivot.Y, r); float dist = (desiredPivot - center).Length(); if (dist > r - CAMERA_EDGE_MARGIN) desiredPivot = center + (desiredPivot - center).Normalized() * (r - CAMERA_EDGE_MARGIN); } else { // Clamp to the square edges of the map desiredPivot.X = Clamp(desiredPivot.X, pTerrain->GetMinX() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxX() - CAMERA_EDGE_MARGIN); desiredPivot.Z = Clamp(desiredPivot.Z, pTerrain->GetMinZ() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxZ() - CAMERA_EDGE_MARGIN); } // Update the position so that pivot is within the margin m->PosX.SetValueSmoothly(desiredPivot.X + delta.X); m->PosZ.SetValueSmoothly(desiredPivot.Z + delta.Z); } m->PosX.Update(deltaRealTime); m->PosY.Update(deltaRealTime); m->PosZ.Update(deltaRealTime); // Handle rotation around the Y (vertical) axis { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmooth(m, &targetCam.m_Orientation); float rotateYDelta = m->RotateY.Update(deltaRealTime); if (rotateYDelta) { // We've updated RotateY, and need to adjust Pos so that it's still // facing towards the original focus point (the terrain in the center // of the screen). CVector3D upwards(0.0f, 1.0f, 0.0f); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CQuaternion q; q.FromAxisAngle(upwards, rotateYDelta); CVector3D d = q.Rotate(delta) - delta; m->PosX.Add(d.X); m->PosY.Add(d.Y); m->PosZ.Add(d.Z); } } // Handle rotation around the X (sideways, relative to camera) axis { CCamera targetCam = m->ViewCamera; SetupCameraMatrixSmooth(m, &targetCam.m_Orientation); float rotateXDelta = m->RotateX.Update(deltaRealTime); if (rotateXDelta) { CVector3D rightwards = targetCam.m_Orientation.GetLeft() * -1.0f; CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot; CQuaternion q; q.FromAxisAngle(rightwards, rotateXDelta); CVector3D d = q.Rotate(delta) - delta; m->PosX.Add(d.X); m->PosY.Add(d.Y); m->PosZ.Add(d.Z); } } /* This is disabled since it doesn't seem necessary: // Ensure the camera's near point is never inside the terrain if (m->ConstrainCamera) { CMatrix3D target; target.SetIdentity(); target.RotateX(m->RotateX.GetValue()); target.RotateY(m->RotateY.GetValue()); target.Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue()); CVector3D nearPoint = target.GetTranslation() + target.GetIn() * defaultNear; float ground = m->Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z); float limit = ground + 16.f; if (nearPoint.Y < limit) m->PosY.AddSmoothly(limit - nearPoint.Y); } */ m->RotateY.Wrap(-(float)M_PI, (float)M_PI); // Update the camera matrix m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); } float CGameView::GetCameraX() { CCamera targetCam = m->ViewCamera; CVector3D pivot = GetSmoothPivot(targetCam); return pivot.X; } float CGameView::GetCameraZ() { CCamera targetCam = m->ViewCamera; CVector3D pivot = GetSmoothPivot(targetCam); return pivot.Z; } float CGameView::GetCameraPosX() { return m->PosX.GetValue(); } float CGameView::GetCameraPosY() { return m->PosY.GetValue(); } float CGameView::GetCameraPosZ() { return m->PosZ.GetValue(); } float CGameView::GetCameraRotX() { return m->RotateX.GetValue(); } float CGameView::GetCameraRotY() { return m->RotateY.GetValue(); } float CGameView::GetCameraZoom() { return m->Zoom.GetValue(); } void CGameView::SetCamera(CVector3D Pos, float RotX, float RotY, float zoom) { m->PosX.SetValue(Pos.X); m->PosY.SetValue(Pos.Y); m->PosZ.SetValue(Pos.Z); m->RotateX.SetValue(RotX); m->RotateY.SetValue(RotY); m->Zoom.SetValue(zoom); FocusHeight(m, false); SetupCameraMatrixNonSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); // Break out of following mode so the camera really moves to the target m->FollowEntity = INVALID_ENTITY; } void CGameView::MoveCameraTarget(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) CCamera targetCam = m->ViewCamera; SetupCameraMatrixNonSmooth(m, &targetCam.m_Orientation); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = target - pivot; m->PosX.SetValueSmoothly(delta.X + m->PosX.GetValue()); m->PosZ.SetValueSmoothly(delta.Z + m->PosZ.GetValue()); FocusHeight(m, false); // Break out of following mode so the camera really moves to the target m->FollowEntity = INVALID_ENTITY; } void CGameView::ResetCameraTarget(const CVector3D& target) { CMatrix3D orientation; orientation.SetIdentity(); orientation.RotateX(DEGTORAD(m->ViewRotateXDefault)); orientation.RotateY(DEGTORAD(m->ViewRotateYDefault)); CVector3D delta = orientation.GetIn() * m->ViewZoomDefault; m->PosX.SetValue(target.X - delta.X); m->PosY.SetValue(target.Y - delta.Y); m->PosZ.SetValue(target.Z - delta.Z); m->RotateX.SetValue(DEGTORAD(m->ViewRotateXDefault)); m->RotateY.SetValue(DEGTORAD(m->ViewRotateYDefault)); m->Zoom.SetValue(m->ViewZoomDefault); FocusHeight(m, false); SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation); m->ViewCamera.UpdateFrustum(); // Break out of following mode so the camera really moves to the target m->FollowEntity = INVALID_ENTITY; } void CGameView::ResetCameraAngleZoom() { CCamera targetCam = m->ViewCamera; SetupCameraMatrixNonSmooth(m, &targetCam.m_Orientation); // Compute the zoom adjustment to get us back to the default CVector3D forwards = targetCam.m_Orientation.GetIn(); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = pivot - targetCam.m_Orientation.GetTranslation(); float dist = delta.Dot(forwards); m->Zoom.AddSmoothly(m->ViewZoomDefault - dist); // Reset orientations to default m->RotateX.SetValueSmoothly(DEGTORAD(m->ViewRotateXDefault)); m->RotateY.SetValueSmoothly(DEGTORAD(m->ViewRotateYDefault)); } void CGameView::CameraFollow(entity_id_t entity, bool firstPerson) { m->FollowEntity = entity; m->FollowFirstPerson = firstPerson; } entity_id_t CGameView::GetFollowedEntity() { return m->FollowEntity; } float CGameView::GetNear() const { return m->ViewNear; } float CGameView::GetFar() const { return m->ViewFar; } float CGameView::GetFOV() const { return m->ViewFOV; } void CGameView::SetCameraProjection() { m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); } 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 || !g_Game->IsGameStarted()) 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: std::string hotkey = static_cast(ev->ev.user.data1); if (hotkey == "wireframe") { if (g_XmppClient && g_rankedGame == true) break; else 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; } // 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(). else if (hotkey == "camera.zoom.wheel.in") { m->Zoom.AddSmoothly(-m->ViewZoomSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.zoom.wheel.out") { m->Zoom.AddSmoothly(m->ViewZoomSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.rotate.wheel.cw") { m->RotateY.AddSmoothly(m->ViewRotateYSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.rotate.wheel.ccw") { m->RotateY.AddSmoothly(-m->ViewRotateYSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.scroll.speed.increase") { m->ViewScrollSpeed *= m->ViewScrollSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.scroll.speed.decrease") { m->ViewScrollSpeed /= m->ViewScrollSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.rotate.speed.increase") { m->ViewRotateXSpeed *= m->ViewRotateSpeedModifier; m->ViewRotateYSpeed *= m->ViewRotateSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.rotate.speed.decrease") { m->ViewRotateXSpeed /= m->ViewRotateSpeedModifier; m->ViewRotateYSpeed /= m->ViewRotateSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.zoom.speed.increase") { m->ViewZoomSpeed *= m->ViewZoomSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.zoom.speed.decrease") { m->ViewZoomSpeed /= m->ViewZoomSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.reset") { ResetCameraAngleZoom(); return IN_HANDLED; } } return IN_PASS; } Index: ps/trunk/source/graphics/MapReader.cpp =================================================================== --- ps/trunk/source/graphics/MapReader.cpp (revision 19374) +++ ps/trunk/source/graphics/MapReader.cpp (revision 19375) @@ -1,1581 +1,1585 @@ /* Copyright (C) 2017 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 "MapReader.h" #include "graphics/Camera.h" #include "graphics/CinemaManager.h" #include "graphics/Entity.h" #include "graphics/GameView.h" #include "graphics/MapGenerator.h" #include "graphics/Patch.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "lib/timer.h" #include "lib/external_libraries/libsdl.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/Loader.h" #include "ps/LoaderThunks.h" #include "ps/World.h" #include "ps/XML/Xeromyces.h" #include "renderer/PostprocManager.h" #include "renderer/SkyManager.h" #include "renderer/WaterManager.h" #include "simulation2/Simulation2.h" +#include "simulation2/components/ICmpCinemaManager.h" #include "simulation2/components/ICmpObstruction.h" #include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpPlayer.h" #include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpTerrain.h" #include "simulation2/components/ICmpVisual.h" #include "simulation2/components/ICmpWaterManager.h" #include CMapReader::CMapReader() : xml_reader(0), m_PatchesPerSide(0), m_MapGen(0) { cur_terrain_tex = 0; // important - resets generator state // Maps that don't override the default probably want the old lighting model //m_LightEnv.SetLightingModel("old"); //pPostproc->SetPostEffect(L"default"); } // LoadMap: try to load the map from given file; reinitialise the scene to new data if successful void CMapReader::LoadMap(const VfsPath& pathname, JSRuntime* rt, JS::HandleValue settings, CTerrain *pTerrain_, WaterManager* pWaterMan_, SkyManager* pSkyMan_, CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_, CSimulation2 *pSimulation2_, const CSimContext* pSimContext_, int playerID_, bool skipEntities) { // latch parameters (held until DelayedLoadFinished) pTerrain = pTerrain_; pLightEnv = pLightEnv_; pGameView = pGameView_; pWaterMan = pWaterMan_; pSkyMan = pSkyMan_; pCinema = pCinema_; pTrigMan = pTrigMan_; pPostproc = pPostproc_; pSimulation2 = pSimulation2_; pSimContext = pSimContext_; m_PlayerID = playerID_; m_SkipEntities = skipEntities; m_StartingCameraTarget = INVALID_ENTITY; m_ScriptSettings.init(rt, settings); filename_xml = pathname.ChangeExtension(L".xml"); // In some cases (particularly tests) we don't want to bother storing a large // mostly-empty .pmp file, so we let the XML file specify basic terrain instead. // If there's an .xml file and no .pmp, then we're probably in this XML-only mode only_xml = false; if (!VfsFileExists(pathname) && VfsFileExists(filename_xml)) { only_xml = true; } file_format_version = CMapIO::FILE_VERSION; // default if there's no .pmp if (!only_xml) { // [25ms] unpacker.Read(pathname, "PSMP"); file_format_version = unpacker.GetVersion(); } // check oldest supported version if (file_format_version < FILE_READ_VERSION) throw PSERROR_File_InvalidVersion(); // delete all existing entities if (pSimulation2) pSimulation2->ResetState(); // reset post effects if (pPostproc) pPostproc->SetPostEffect(L"default"); // load map or script settings script if (settings.isUndefined()) RegMemFun(this, &CMapReader::LoadScriptSettings, L"CMapReader::LoadScriptSettings", 50); else RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50); // load player settings script (must be done before reading map) RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50); // unpack the data if (!only_xml) RegMemFun(this, &CMapReader::UnpackMap, L"CMapReader::UnpackMap", 1200); // read the corresponding XML file RegMemFun(this, &CMapReader::ReadXML, L"CMapReader::ReadXML", 50); // apply terrain data to the world RegMemFun(this, &CMapReader::ApplyTerrainData, L"CMapReader::ApplyTerrainData", 5); // read entities RegMemFun(this, &CMapReader::ReadXMLEntities, L"CMapReader::ReadXMLEntities", 5800); // apply misc data to the world RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5); // load map settings script (must be done after reading map) RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5); RegMemFun(this, &CMapReader::DelayLoadFinished, L"CMapReader::DelayLoadFinished", 5); } // LoadRandomMap: try to load the map data; reinitialise the scene to new data if successful void CMapReader::LoadRandomMap(const CStrW& scriptFile, JSRuntime* rt, JS::HandleValue settings, CTerrain *pTerrain_, WaterManager* pWaterMan_, SkyManager* pSkyMan_, CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_, CSimulation2 *pSimulation2_, int playerID_) { // latch parameters (held until DelayedLoadFinished) m_ScriptFile = scriptFile; pSimulation2 = pSimulation2_; pSimContext = pSimulation2 ? &pSimulation2->GetSimContext() : NULL; m_ScriptSettings.init(rt, settings); pTerrain = pTerrain_; pLightEnv = pLightEnv_; pGameView = pGameView_; pWaterMan = pWaterMan_; pSkyMan = pSkyMan_; pCinema = pCinema_; pTrigMan = pTrigMan_; pPostproc = pPostproc_; m_PlayerID = playerID_; m_SkipEntities = false; m_StartingCameraTarget = INVALID_ENTITY; // delete all existing entities if (pSimulation2) pSimulation2->ResetState(); only_xml = false; // copy random map settings (before entity creation) RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50); // load player settings script (must be done before reading map) RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50); // load map generator with random map script RegMemFun(this, &CMapReader::GenerateMap, L"CMapReader::GenerateMap", 5000); // parse RMS results into terrain structure RegMemFun(this, &CMapReader::ParseTerrain, L"CMapReader::ParseTerrain", 500); // parse RMS results into environment settings RegMemFun(this, &CMapReader::ParseEnvironment, L"CMapReader::ParseEnvironment", 5); // parse RMS results into camera settings RegMemFun(this, &CMapReader::ParseCamera, L"CMapReader::ParseCamera", 5); // apply terrain data to the world RegMemFun(this, &CMapReader::ApplyTerrainData, L"CMapReader::ApplyTerrainData", 5); // parse RMS results into entities RegMemFun(this, &CMapReader::ParseEntities, L"CMapReader::ParseEntities", 1000); // apply misc data to the world RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5); // load map settings script (must be done after reading map) RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5); RegMemFun(this, &CMapReader::DelayLoadFinished, L"CMapReader::DelayLoadFinished", 5); } // UnpackMap: unpack the given data from the raw data stream into local variables int CMapReader::UnpackMap() { // now unpack everything into local data int ret = UnpackTerrain(); if (ret != 0) // failed or timed out { return ret; } return 0; } // UnpackTerrain: unpack the terrain from the end of the input data stream // - data: map size, heightmap, list of textures used by map, texture tile assignments int CMapReader::UnpackTerrain() { // yield after this time is reached. balances increased progress bar // smoothness vs. slowing down loading. const double end_time = timer_Time() + 200e-3; // first call to generator (this is skipped after first call, // i.e. when the loop below was interrupted) if (cur_terrain_tex == 0) { m_PatchesPerSide = (ssize_t)unpacker.UnpackSize(); // unpack heightmap [600us] size_t verticesPerSide = m_PatchesPerSide*PATCH_SIZE+1; m_Heightmap.resize(SQR(verticesPerSide)); unpacker.UnpackRaw(&m_Heightmap[0], SQR(verticesPerSide)*sizeof(u16)); // unpack # textures num_terrain_tex = unpacker.UnpackSize(); m_TerrainTextures.reserve(num_terrain_tex); } // unpack texture names; find handle for each texture. // interruptible. while (cur_terrain_tex < num_terrain_tex) { CStr texturename; unpacker.UnpackString(texturename); ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled) CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texturename); m_TerrainTextures.push_back(texentry); cur_terrain_tex++; LDR_CHECK_TIMEOUT(cur_terrain_tex, num_terrain_tex); } // unpack tile data [3ms] ssize_t tilesPerSide = m_PatchesPerSide*PATCH_SIZE; m_Tiles.resize(size_t(SQR(tilesPerSide))); unpacker.UnpackRaw(&m_Tiles[0], sizeof(STileDesc)*m_Tiles.size()); // reset generator state. cur_terrain_tex = 0; return 0; } int CMapReader::ApplyTerrainData() { if (m_PatchesPerSide == 0) { // we'll probably crash when trying to use this map later throw PSERROR_Game_World_MapLoadFailed("Error loading map: no terrain data.\nCheck application log for details."); } if (!only_xml) { // initialise the terrain pTerrain->Initialize(m_PatchesPerSide, &m_Heightmap[0]); // setup the textures on the minipatches STileDesc* tileptr = &m_Tiles[0]; for (ssize_t j=0; jGetPatch(i,j)->m_MiniPatches[m][k]; // can't fail mp.Tex = m_TerrainTextures[tileptr->m_Tex1Index]; mp.Priority = tileptr->m_Priority; tileptr++; } } } } } CmpPtr cmpTerrain(*pSimContext, SYSTEM_ENTITY); if (cmpTerrain) cmpTerrain->ReloadTerrain(); return 0; } // ApplyData: take all the input data, and rebuild the scene from it int CMapReader::ApplyData() { // copy over the lighting parameters if (pLightEnv) *pLightEnv = m_LightEnv; CmpPtr cmpPlayerManager(*pSimContext, SYSTEM_ENTITY); if (pGameView && cmpPlayerManager) { // Default to global camera (with constraints) pGameView->ResetCameraTarget(pGameView->GetCamera()->GetFocus()); // TODO: Starting rotation? CmpPtr cmpPlayer(*pSimContext, cmpPlayerManager->GetPlayerByID(m_PlayerID)); if (cmpPlayer && cmpPlayer->HasStartingCamera()) { // Use player starting camera CFixedVector3D pos = cmpPlayer->GetStartingCameraPos(); pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat())); } else if (m_StartingCameraTarget != INVALID_ENTITY) { // Point camera at entity CmpPtr cmpPosition(*pSimContext, m_StartingCameraTarget); if (cmpPosition) { CFixedVector3D pos = cmpPosition->GetPosition(); pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat())); } } } return 0; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// PSRETURN CMapSummaryReader::LoadMap(const VfsPath& pathname) { VfsPath filename_xml = pathname.ChangeExtension(L".xml"); CXeromyces xmb_file; if (xmb_file.Load(g_VFS, filename_xml, "scenario") != PSRETURN_OK) return PSRETURN_File_ReadFailed; // Define all the relevant elements used in the XML file #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(scenario); EL(scriptsettings); #undef AT #undef EL XMBElement root = xmb_file.GetRoot(); ENSURE(root.GetNodeName() == el_scenario); XERO_ITER_EL(root, child) { int child_name = child.GetNodeName(); if (child_name == el_scriptsettings) { m_ScriptSettings = child.GetText(); } } return PSRETURN_OK; } void CMapSummaryReader::GetMapSettings(ScriptInterface& scriptInterface, JS::MutableHandleValue ret) { JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); scriptInterface.Eval("({})", ret); if (m_ScriptSettings.empty()) return; JS::RootedValue scriptSettingsVal(cx); scriptInterface.ParseJSON(m_ScriptSettings, &scriptSettingsVal); scriptInterface.SetProperty(ret, "settings", scriptSettingsVal, false); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Holds various state data while reading maps, so that loading can be // interrupted (e.g. to update the progress display) then later resumed. class CXMLReader { NONCOPYABLE(CXMLReader); public: CXMLReader(const VfsPath& xml_filename, CMapReader& mapReader) : m_MapReader(mapReader), nodes(NULL, 0, NULL) { Init(xml_filename); } CStr ReadScriptSettings(); // read everything except for entities void ReadXML(); // return semantics: see Loader.cpp!LoadFunc. int ProgressiveReadEntities(); private: CXeromyces xmb_file; CMapReader& m_MapReader; int el_entity; int el_tracks; int el_template, el_player; int el_position, el_orientation, el_obstruction; int el_actor; int at_x, at_y, at_z; int at_group, at_group2; int at_angle; int at_uid; int at_seed; XMBElementList nodes; // children of root // loop counters size_t node_idx; size_t entity_idx; // # entities+nonentities processed and total (for progress calc) int completed_jobs, total_jobs; // maximum used entity ID, so we can safely allocate new ones entity_id_t max_uid; void Init(const VfsPath& xml_filename); void ReadTerrain(XMBElement parent); void ReadEnvironment(XMBElement parent); void ReadCamera(XMBElement parent); void ReadPaths(XMBElement parent); void ReadTriggers(XMBElement parent); int ReadEntities(XMBElement parent, double end_time); }; void CXMLReader::Init(const VfsPath& xml_filename) { // must only assign once, so do it here node_idx = entity_idx = 0; if (xmb_file.Load(g_VFS, xml_filename, "scenario") != PSRETURN_OK) throw PSERROR_File_ReadFailed(); // define the elements and attributes that are frequently used in the XML file, // so we don't need to do lots of string construction and comparison when // reading the data. // (Needs to be synchronised with the list in CXMLReader - ugh) #define EL(x) el_##x = xmb_file.GetElementID(#x) #define AT(x) at_##x = xmb_file.GetAttributeID(#x) EL(entity); EL(tracks); EL(template); EL(player); EL(position); EL(orientation); EL(obstruction); EL(actor); AT(x); AT(y); AT(z); AT(group); AT(group2); AT(angle); AT(uid); AT(seed); #undef AT #undef EL XMBElement root = xmb_file.GetRoot(); ENSURE(xmb_file.GetElementString(root.GetNodeName()) == "Scenario"); nodes = root.GetChildNodes(); // find out total number of entities+nonentities // (used when calculating progress) completed_jobs = 0; total_jobs = 0; for (XMBElement node : nodes) total_jobs += node.GetChildNodes().size(); // Find the maximum entity ID, so we can safely allocate new IDs without conflicts max_uid = SYSTEM_ENTITY; XMBElement ents = nodes.GetFirstNamedItem(xmb_file.GetElementID("Entities")); XERO_ITER_EL(ents, ent) { CStr uid = ent.GetAttributes().GetNamedItem(at_uid); max_uid = std::max(max_uid, (entity_id_t)uid.ToUInt()); } } CStr CXMLReader::ReadScriptSettings() { XMBElement root = xmb_file.GetRoot(); ENSURE(xmb_file.GetElementString(root.GetNodeName()) == "Scenario"); nodes = root.GetChildNodes(); XMBElement settings = nodes.GetFirstNamedItem(xmb_file.GetElementID("ScriptSettings")); return settings.GetText(); } void CXMLReader::ReadTerrain(XMBElement parent) { #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) AT(patches); AT(texture); AT(priority); AT(height); #undef AT ssize_t patches = 9; CStr texture = "grass1_spring"; int priority = 0; u16 height = 16384; XERO_ITER_ATTR(parent, attr) { if (attr.Name == at_patches) patches = attr.Value.ToInt(); else if (attr.Name == at_texture) texture = attr.Value; else if (attr.Name == at_priority) priority = attr.Value.ToInt(); else if (attr.Name == at_height) height = (u16)attr.Value.ToInt(); } m_MapReader.m_PatchesPerSide = patches; // Load the texture ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled) CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texture); m_MapReader.pTerrain->Initialize(patches, NULL); // Fill the heightmap u16* heightmap = m_MapReader.pTerrain->GetHeightMap(); ssize_t verticesPerSide = m_MapReader.pTerrain->GetVerticesPerSide(); for (ssize_t i = 0; i < SQR(verticesPerSide); ++i) heightmap[i] = height; // Fill the texture map for (ssize_t pz = 0; pz < patches; ++pz) { for (ssize_t px = 0; px < patches; ++px) { CPatch* patch = m_MapReader.pTerrain->GetPatch(px, pz); // can't fail for (ssize_t z = 0; z < PATCH_SIZE; ++z) { for (ssize_t x = 0; x < PATCH_SIZE; ++x) { patch->m_MiniPatches[z][x].Tex = texentry; patch->m_MiniPatches[z][x].Priority = priority; } } } } } void CXMLReader::ReadEnvironment(XMBElement parent) { #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(posteffect); EL(skyset); EL(suncolor); EL(sunelevation); EL(sunrotation); EL(terrainambientcolor); EL(unitsambientcolor); EL(water); EL(waterbody); EL(type); EL(color); EL(tint); EL(height); EL(waviness); EL(murkiness); EL(windangle); EL(fog); EL(fogcolor); EL(fogfactor); EL(fogthickness); EL(postproc); EL(brightness); EL(contrast); EL(saturation); EL(bloom); AT(r); AT(g); AT(b); #undef AT #undef EL XERO_ITER_EL(parent, element) { int element_name = element.GetNodeName(); XMBAttributeList attrs = element.GetAttributes(); if (element_name == el_skyset) { if (m_MapReader.pSkyMan) m_MapReader.pSkyMan->SetSkySet(element.GetText().FromUTF8()); } else if (element_name == el_suncolor) { m_MapReader.m_LightEnv.m_SunColor = RGBColor( attrs.GetNamedItem(at_r).ToFloat(), attrs.GetNamedItem(at_g).ToFloat(), attrs.GetNamedItem(at_b).ToFloat()); } else if (element_name == el_sunelevation) { m_MapReader.m_LightEnv.m_Elevation = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_sunrotation) { m_MapReader.m_LightEnv.m_Rotation = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_terrainambientcolor) { m_MapReader.m_LightEnv.m_TerrainAmbientColor = RGBColor( attrs.GetNamedItem(at_r).ToFloat(), attrs.GetNamedItem(at_g).ToFloat(), attrs.GetNamedItem(at_b).ToFloat()); } else if (element_name == el_unitsambientcolor) { m_MapReader.m_LightEnv.m_UnitsAmbientColor = RGBColor( attrs.GetNamedItem(at_r).ToFloat(), attrs.GetNamedItem(at_g).ToFloat(), attrs.GetNamedItem(at_b).ToFloat()); } else if (element_name == el_fog) { XERO_ITER_EL(element, fog) { int element_name = fog.GetNodeName(); if (element_name == el_fogcolor) { XMBAttributeList attrs = fog.GetAttributes(); m_MapReader.m_LightEnv.m_FogColor = RGBColor( attrs.GetNamedItem(at_r).ToFloat(), attrs.GetNamedItem(at_g).ToFloat(), attrs.GetNamedItem(at_b).ToFloat()); } else if (element_name == el_fogfactor) { m_MapReader.m_LightEnv.m_FogFactor = fog.GetText().ToFloat(); } else if (element_name == el_fogthickness) { m_MapReader.m_LightEnv.m_FogMax = fog.GetText().ToFloat(); } } } else if (element_name == el_postproc) { XERO_ITER_EL(element, postproc) { int element_name = postproc.GetNodeName(); if (element_name == el_brightness) { m_MapReader.m_LightEnv.m_Brightness = postproc.GetText().ToFloat(); } else if (element_name == el_contrast) { m_MapReader.m_LightEnv.m_Contrast = postproc.GetText().ToFloat(); } else if (element_name == el_saturation) { m_MapReader.m_LightEnv.m_Saturation = postproc.GetText().ToFloat(); } else if (element_name == el_bloom) { m_MapReader.m_LightEnv.m_Bloom = postproc.GetText().ToFloat(); } else if (element_name == el_posteffect) { if (m_MapReader.pPostproc) m_MapReader.pPostproc->SetPostEffect(postproc.GetText().FromUTF8()); } } } else if (element_name == el_water) { XERO_ITER_EL(element, waterbody) { ENSURE(waterbody.GetNodeName() == el_waterbody); XERO_ITER_EL(waterbody, waterelement) { int element_name = waterelement.GetNodeName(); if (element_name == el_height) { CmpPtr cmpWaterManager(*m_MapReader.pSimContext, SYSTEM_ENTITY); ENSURE(cmpWaterManager); cmpWaterManager->SetWaterLevel(entity_pos_t::FromString(waterelement.GetText())); continue; } // The rest are purely graphical effects, and should be ignored if // graphics are disabled if (!m_MapReader.pWaterMan) continue; if (element_name == el_type) { if (waterelement.GetText() == "default") m_MapReader.pWaterMan->m_WaterType = L"ocean"; else m_MapReader.pWaterMan->m_WaterType = waterelement.GetText().FromUTF8(); } #define READ_COLOR(el, out) \ else if (element_name == el) \ { \ XMBAttributeList attrs = waterelement.GetAttributes(); \ out = CColor( \ attrs.GetNamedItem(at_r).ToFloat(), \ attrs.GetNamedItem(at_g).ToFloat(), \ attrs.GetNamedItem(at_b).ToFloat(), \ 1.f); \ } #define READ_FLOAT(el, out) \ else if (element_name == el) \ { \ out = waterelement.GetText().ToFloat(); \ } \ READ_COLOR(el_color, m_MapReader.pWaterMan->m_WaterColor) READ_COLOR(el_tint, m_MapReader.pWaterMan->m_WaterTint) READ_FLOAT(el_waviness, m_MapReader.pWaterMan->m_Waviness) READ_FLOAT(el_murkiness, m_MapReader.pWaterMan->m_Murkiness) READ_FLOAT(el_windangle, m_MapReader.pWaterMan->m_WindAngle) #undef READ_FLOAT #undef READ_COLOR else debug_warn(L"Invalid map XML data"); } } } else debug_warn(L"Invalid map XML data"); } m_MapReader.m_LightEnv.CalculateSunDirection(); } void CXMLReader::ReadCamera(XMBElement parent) { // defaults if we don't find player starting camera #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(declination); EL(rotation); EL(position); AT(angle); AT(x); AT(y); AT(z); #undef AT #undef EL float declination = DEGTORAD(30.f), rotation = DEGTORAD(-45.f); CVector3D translation = CVector3D(100, 150, -100); XERO_ITER_EL(parent, element) { int element_name = element.GetNodeName(); XMBAttributeList attrs = element.GetAttributes(); if (element_name == el_declination) { declination = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_rotation) { rotation = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_position) { translation = CVector3D( attrs.GetNamedItem(at_x).ToFloat(), attrs.GetNamedItem(at_y).ToFloat(), attrs.GetNamedItem(at_z).ToFloat()); } else debug_warn(L"Invalid map XML data"); } if (m_MapReader.pGameView) { m_MapReader.pGameView->GetCamera()->m_Orientation.SetXRotation(declination); m_MapReader.pGameView->GetCamera()->m_Orientation.RotateY(rotation); m_MapReader.pGameView->GetCamera()->m_Orientation.Translate(translation); m_MapReader.pGameView->GetCamera()->UpdateFrustum(); } } void CXMLReader::ReadPaths(XMBElement parent) { #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(path); EL(rotation); EL(node); EL(position); EL(target); AT(name); AT(timescale); AT(orientation); AT(mode); AT(style); AT(x); AT(y); AT(z); AT(deltatime); #undef EL #undef AT + CmpPtr cmpCinemaManager(*m_MapReader.pSimContext, SYSTEM_ENTITY); XERO_ITER_EL(parent, element) { int elementName = element.GetNodeName(); if (elementName == el_path) { CCinemaData pathData; XMBAttributeList attrs = element.GetAttributes(); CStrW pathName(attrs.GetNamedItem(at_name).FromUTF8()); pathData.m_Name = pathName; pathData.m_Timescale = fixed::FromString(attrs.GetNamedItem(at_timescale)); pathData.m_Orientation = attrs.GetNamedItem(at_orientation).FromUTF8(); pathData.m_Mode = attrs.GetNamedItem(at_mode).FromUTF8(); pathData.m_Style = attrs.GetNamedItem(at_style).FromUTF8(); TNSpline positionSpline, targetSpline; fixed lastPositionTime = fixed::Zero(); fixed lastTargetTime = fixed::Zero(); XERO_ITER_EL(element, pathChild) { elementName = pathChild.GetNodeName(); attrs = pathChild.GetAttributes(); // Load node data used for spline if (elementName == el_node) { lastPositionTime += fixed::FromString(attrs.GetNamedItem(at_deltatime)); lastTargetTime += fixed::FromString(attrs.GetNamedItem(at_deltatime)); XERO_ITER_EL(pathChild, nodeChild) { elementName = nodeChild.GetNodeName(); attrs = nodeChild.GetAttributes(); if (elementName == el_position) { CFixedVector3D position(fixed::FromString(attrs.GetNamedItem(at_x)), fixed::FromString(attrs.GetNamedItem(at_y)), fixed::FromString(attrs.GetNamedItem(at_z))); positionSpline.AddNode(position, CFixedVector3D(), lastPositionTime); lastPositionTime = fixed::Zero(); } else if (elementName == el_rotation) { // TODO: Implement rotation slerp/spline as another object } else if (elementName == el_target) { CFixedVector3D targetPosition(fixed::FromString(attrs.GetNamedItem(at_x)), fixed::FromString(attrs.GetNamedItem(at_y)), fixed::FromString(attrs.GetNamedItem(at_z))); targetSpline.AddNode(targetPosition, CFixedVector3D(), lastTargetTime); lastTargetTime = fixed::Zero(); } else LOGWARNING("Invalid cinematic element for node child"); } } else LOGWARNING("Invalid cinematic element for path child"); } // Construct cinema path with data gathered CCinemaPath path(pathData, positionSpline, targetSpline); if (path.Empty()) { LOGWARNING("Path with name '%s' is empty", pathName.ToUTF8()); return; } - if (!m_MapReader.pCinema->HasPath(pathName)) - m_MapReader.pCinema->AddPath(pathName, path); + if (!cmpCinemaManager) + continue; + if (!cmpCinemaManager->HasPath(pathName)) + cmpCinemaManager->AddPath(pathName, path); else LOGWARNING("Path with name '%s' already exists", pathName.ToUTF8()); } else LOGWARNING("Invalid path child with name '%s'", element.GetText()); } } void CXMLReader::ReadTriggers(XMBElement UNUSED(parent)) { } int CXMLReader::ReadEntities(XMBElement parent, double end_time) { XMBElementList entities = parent.GetChildNodes(); ENSURE(m_MapReader.pSimulation2); CSimulation2& sim = *m_MapReader.pSimulation2; CmpPtr cmpPlayerManager(sim, SYSTEM_ENTITY); while (entity_idx < entities.size()) { // all new state at this scope and below doesn't need to be // wrapped, since we only yield after a complete iteration. XMBElement entity = entities[entity_idx++]; ENSURE(entity.GetNodeName() == el_entity); XMBAttributeList attrs = entity.GetAttributes(); CStr uid = attrs.GetNamedItem(at_uid); ENSURE(!uid.empty()); int EntityUid = uid.ToInt(); CStrW TemplateName; int PlayerID = 0; CFixedVector3D Position; CFixedVector3D Orientation; long Seed = -1; // Obstruction control groups. entity_id_t ControlGroup = INVALID_ENTITY; entity_id_t ControlGroup2 = INVALID_ENTITY; XERO_ITER_EL(entity, setting) { int element_name = setting.GetNodeName(); //