Index: ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp (revision 26729) +++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp (revision 26730) @@ -1,790 +1,786 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 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 "Object.h" #include "Buttons/ToolButton.h" #include "General/Datafile.h" #include "ScenarioEditor/ScenarioEditor.h" #include "ScenarioEditor/Tools/Common/ObjectSettings.h" #include "ScenarioEditor/Tools/Common/MiscState.h" #include "VariationControl.h" #include "GameInterface/Messages.h" #include "wx/busyinfo.h" #include "wx/wxcrt.h" enum { ID_ObjectType = 1, ID_ObjectFilter, ID_ObjectExactFilter, ID_PlayerSelect, ID_SelectObject, ID_ToggleViewer, ID_ViewerWireframe, ID_ViewerMove, ID_ViewerGround, ID_ViewerWater, ID_ViewerShadows, ID_ViewerPolyCount, ID_ViewerAnimation, ID_ViewerBoundingBox, ID_ViewerAxesMarker, ID_ViewerPropPoints, ID_ViewerPlay, ID_ViewerPause, ID_ViewerSlow }; // Helper function for adding tooltips static wxWindow* Tooltipped(wxWindow* window, const wxString& tip) { window->SetToolTip(tip); return window; } class ObjectBottomBar : public wxPanel { public: ObjectBottomBar( wxWindow* parent, Observable& objectSettings, Observable& mapSettings, ObjectSidebarImpl* p ); void OnFirstDisplay(); void ShowActorViewer(bool show); void OnSelectedObjectsChange(const std::vector& selectedObjects); private: void OnViewerSetting(wxCommandEvent& evt); void OnSelectAnim(wxCommandEvent& evt); void OnSpeed(wxCommandEvent& evt); bool m_ViewerWireframe; bool m_ViewerMove; bool m_ViewerGround; bool m_ViewerWater; - bool m_ViewerShadows; bool m_ViewerPolyCount; bool m_ViewerBoundingBox; bool m_ViewerAxesMarker; int m_ViewerPropPointsMode; // 0 disabled, 1 for point markers, 2 for point markers + axes wxPanel* m_ViewerPanel; wxScrolledWindow* m_TemplateNames; ObjectSidebarImpl* p; DECLARE_EVENT_TABLE(); }; struct ObjectSidebarImpl { ObjectSidebarImpl(ScenarioEditor& scenarioEditor) : m_ObjectListBox(NULL), m_ActorViewerActive(false), m_ActorViewerEntity(L"actor|structures/fndn_1x1.xml"), m_ActorViewerAnimation("idle"), m_ActorViewerSpeed(0.f), m_ObjectSettings(scenarioEditor.GetObjectSettings()) { } wxListBox* m_ObjectListBox; std::vector m_Objects; ObservableScopedConnection m_ToolConn; bool m_ActorViewerActive; std::wstring m_ActorViewerEntity; std::string m_ActorViewerAnimation; float m_ActorViewerSpeed; Observable& m_ObjectSettings; struct SearchItem { // Weight of the pattern (higher - better). size_t weight; // Index in the original sequence to save the order. size_t index; bool operator<(const SearchItem& other) const { if (weight != other.weight) return weight > other.weight; return index < other.index; } }; // Cache the std::vector to prevent frequent reallocations. std::vector m_SearchItems; struct SearchItemFilter { SearchItemFilter(const wxString& filterName) { bool firstCharacterInWord = true; for (const wxUniChar& ch : filterName) { if (wxIsalpha(ch)) { if (firstCharacterInWord) m_FilterWords.push_back(wxString()); firstCharacterInWord = false; m_FilterWords.back().Append(ch); } else firstCharacterInWord = true; } } // Returns positive integer if filter words can present in the name. // Otherwise returns zero. size_t CalculateWeight(const wxString& name) { if (m_FilterWords.empty() || name.IsEmpty()) return 0; std::vector filterWordWeights(m_FilterWords.size(), 0); for (size_t wordBegin = 0; wordBegin < name.Length();) { if (!wxIsalpha(name[wordBegin])) { ++wordBegin; continue; } size_t wordEnd = wordBegin; while (wordEnd < name.Length() && wxIsalpha(name[wordEnd])) ++wordEnd; for (size_t i = 0; i < m_FilterWords.size(); ++i) { // TODO: we might have a copy here, maybe we can use // something similar to std::string_view but for wxString. size_t weight = CalculateLCS( m_FilterWords[i], name.Mid(wordBegin, wordEnd - wordBegin)); // We use a simple heuristic, if LCS shorter than a half // of the filter word, then it's not good for us. if (weight < m_FilterWords[i].size() / 2) continue; filterWordWeights[i] = std::max(filterWordWeights[i], weight); } wordBegin = wordEnd; } size_t totalWeight = 0; for (size_t weight : filterWordWeights) { // If a filter word was't found at all, then we can't accept // the search item name. if (!weight) return 0; totalWeight += weight; } return totalWeight; } // We use longest common subsequence to calculate the weight // between two words: filter and item names. size_t CalculateLCS(const wxString& filterWord, const wxString& word) { if (filterWord.IsEmpty() || word.IsEmpty()) return 0; m_LCSGrid.resize(filterWord.Length() * word.Length()); for (size_t i = 0; i < filterWord.Length(); ++i) for (size_t j = 0; j < word.Length(); ++j) { #define GRID(x, y) m_LCSGrid[(x) * word.Length() + (y)] GRID(i, j) = wxTolower(filterWord.GetChar(i)) == wxTolower(word.GetChar(j)); if (i && j) GRID(i, j) += GRID(i - 1, j - 1); if (i) GRID(i, j) = std::max(GRID(i, j), GRID(i - 1, j)); if (j) GRID(i, j) = std::max(GRID(i, j), GRID(i, j - 1)); #undef GRID } return m_LCSGrid.back(); } std::vector m_FilterWords; // Cache the std::vector to prevent frequent reallocations. std::vector m_LCSGrid; }; void ActorViewerPostToGame() { POST_MESSAGE(SetActorViewer, (m_ActorViewerEntity.c_str(), m_ActorViewerAnimation.c_str(), m_ObjectSettings.GetPlayerID(), m_ActorViewerSpeed, false)); } }; ObjectSidebar::ObjectSidebar( ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer ) : Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer), m_Impl(new ObjectSidebarImpl(scenarioEditor)) { wxSizer* scrollSizer = new wxBoxSizer(wxVERTICAL); wxScrolledWindow* scrolledWindow = new wxScrolledWindow(this); scrolledWindow->SetScrollRate(10, 10); scrolledWindow->SetSizer(scrollSizer); m_MainSizer->Add(scrolledWindow, wxSizerFlags().Proportion(1).Expand()); wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(new wxStaticText(scrolledWindow, wxID_ANY, _("Filter")), wxSizerFlags().Align(wxALIGN_CENTER)); sizer->AddSpacer(2); sizer->Add( Tooltipped( new wxTextCtrl(scrolledWindow, ID_ObjectFilter), _("Enter text to filter object list") ), wxSizerFlags().Expand().Proportion(1) ); scrollSizer->Add(sizer, wxSizerFlags().Expand()); scrollSizer->AddSpacer(3); wxCheckBox* exactSearchCheckBox = new wxCheckBox( scrolledWindow, ID_ObjectExactFilter, _("Exact Search")); exactSearchCheckBox->SetValue(true); scrollSizer->Add(Tooltipped(exactSearchCheckBox, _("Provides a search with a strict string equality"))); scrollSizer->AddSpacer(3); // ------------------------------------------------------------------------------------------ wxArrayString strings; strings.Add(_("Entities")); strings.Add(_("Actors (all)")); wxChoice* objectType = new wxChoice(scrolledWindow, ID_ObjectType, wxDefaultPosition, wxDefaultSize, strings); objectType->SetSelection(0); scrollSizer->Add(objectType, wxSizerFlags().Expand()); scrollSizer->AddSpacer(3); // ------------------------------------------------------------------------------------------ m_Impl->m_ObjectListBox = new wxListBox(scrolledWindow, ID_SelectObject, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxLB_SINGLE|wxLB_HSCROLL); scrollSizer->Add(m_Impl->m_ObjectListBox, wxSizerFlags().Proportion(1).Expand()); scrollSizer->AddSpacer(3); // ------------------------------------------------------------------------------------------ scrollSizer->Add(new wxButton(scrolledWindow, ID_ToggleViewer, _("Switch to Actor Viewer")), wxSizerFlags().Expand()); // ------------------------------------------------------------------------------------------ m_BottomBar = new ObjectBottomBar( bottomBarContainer, scenarioEditor.GetObjectSettings(), scenarioEditor.GetMapSettings(), m_Impl ); m_Impl->m_ToolConn = scenarioEditor.GetToolManager().GetCurrentTool().RegisterObserver(0, &ObjectSidebar::OnToolChange, this); } ObjectSidebar::~ObjectSidebar() { delete m_Impl; } void ObjectSidebar::OnToolChange(ITool* tool) { if (wxString(tool->GetClassInfo()->GetClassName()) == _T("ActorViewerTool")) { m_Impl->m_ActorViewerActive = true; m_Impl->ActorViewerPostToGame(); wxDynamicCast(FindWindow(ID_ToggleViewer), wxButton)->SetLabel(_("Return to game view")); } else { m_Impl->m_ActorViewerActive = false; wxDynamicCast(FindWindow(ID_ToggleViewer), wxButton)->SetLabel(_("Switch to Actor Viewer")); } static_cast(m_BottomBar)->ShowActorViewer(m_Impl->m_ActorViewerActive); } void ObjectSidebar::OnFirstDisplay() { static_cast(m_BottomBar)->OnFirstDisplay(); wxBusyInfo busy (_("Loading list of objects")); // Get the list of objects from the game AtlasMessage::qGetObjectsList qry; qry.Post(); m_Impl->m_Objects = *qry.objects; // Display first group of objects FilterObjects(); } void ObjectSidebar::FilterObjects() { int filterType = wxDynamicCast(FindWindow(ID_ObjectType), wxChoice)->GetSelection(); wxString filterName = wxDynamicCast(FindWindow(ID_ObjectFilter), wxTextCtrl)->GetValue(); m_Impl->m_ObjectListBox->Freeze(); m_Impl->m_ObjectListBox->Clear(); if (wxDynamicCast(FindWindow(ID_ObjectExactFilter), wxCheckBox)->IsChecked() || filterName.IsEmpty()) { for (std::vector::iterator it = m_Impl->m_Objects.begin(); it != m_Impl->m_Objects.end(); ++it) { if (it->type != filterType) continue; wxString id = it->id.c_str(); wxString name = it->name.c_str(); if (name.Lower().Find(filterName.Lower()) != wxNOT_FOUND) m_Impl->m_ObjectListBox->Append(name, new wxStringClientData(id)); } } else { ObjectSidebarImpl::SearchItemFilter filter(filterName); for (size_t idx = 0; idx < m_Impl->m_Objects.size(); ++idx) { const AtlasMessage::sObjectsListItem& it = m_Impl->m_Objects[idx]; if (it.type != filterType) continue; wxString name = it.name.c_str(); size_t weight = filter.CalculateWeight(name); // We don't want to show items with zero weight. if (!weight) continue; m_Impl->m_SearchItems.push_back({weight, idx}); } std::sort(m_Impl->m_SearchItems.begin(), m_Impl->m_SearchItems.end()); for (const ObjectSidebarImpl::SearchItem& item : m_Impl->m_SearchItems) { const AtlasMessage::sObjectsListItem& it = m_Impl->m_Objects[item.index]; wxString id = it.id.c_str(); wxString name = it.name.c_str(); m_Impl->m_ObjectListBox->Append(name, new wxStringClientData(id)); } m_Impl->m_SearchItems.clear(); } m_Impl->m_ObjectListBox->Thaw(); } void ObjectSidebar::OnToggleViewer(wxCommandEvent& WXUNUSED(evt)) { if (m_Impl->m_ActorViewerActive) { m_ScenarioEditor.GetToolManager().SetCurrentTool(_T(""), NULL); } else { m_ScenarioEditor.GetToolManager().SetCurrentTool(_T("ActorViewerTool"), NULL); } } void ObjectSidebar::OnSelectType(wxCommandEvent& WXUNUSED(evt)) { FilterObjects(); } void ObjectSidebar::OnSelectObject(wxCommandEvent& evt) { if (evt.GetInt() < 0) return; wxString id = static_cast(evt.GetClientObject())->GetData(); // Always update the actor viewer's state even if it's inactive, // so it will be correct when first enabled m_Impl->m_ActorViewerEntity = id; if (m_Impl->m_ActorViewerActive) { m_Impl->ActorViewerPostToGame(); } else { // On selecting an object, enable the PlaceObject tool with this object m_ScenarioEditor.GetToolManager().SetCurrentTool(_T("PlaceObject"), &id); } } void ObjectSidebar::OnSelectFilter(wxCommandEvent& WXUNUSED(evt)) { FilterObjects(); } void ObjectSidebar::OnToggleExactFilter(wxCommandEvent& WXUNUSED(evt)) { FilterObjects(); } BEGIN_EVENT_TABLE(ObjectSidebar, Sidebar) EVT_CHOICE(ID_ObjectType, ObjectSidebar::OnSelectType) EVT_TEXT(ID_ObjectFilter, ObjectSidebar::OnSelectFilter) EVT_LISTBOX(ID_SelectObject, ObjectSidebar::OnSelectObject) EVT_BUTTON(ID_ToggleViewer, ObjectSidebar::OnToggleViewer) EVT_CHECKBOX(ID_ObjectExactFilter, ObjectSidebar::OnToggleExactFilter) END_EVENT_TABLE(); ////////////////////////////////////////////////////////////////////////// class PlayerComboBox : public wxComboBox { public: PlayerComboBox(wxWindow* parent, Observable& objectSettings, Observable& mapSettings) : wxComboBox(parent, ID_PlayerSelect, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, 0, wxCB_READONLY) , m_ObjectSettings(objectSettings), m_MapSettings(mapSettings) { m_ObjectConn = m_ObjectSettings.RegisterObserver(1, &PlayerComboBox::OnObjectSettingsChange, this); m_MapConn = m_MapSettings.RegisterObserver(1, &PlayerComboBox::OnMapSettingsChange, this); } void SetPlayers(wxArrayString& names) { m_Players = names; OnMapSettingsChange(m_MapSettings); } private: ObservableScopedConnection m_ObjectConn; Observable& m_ObjectSettings; ObservableScopedConnection m_MapConn; Observable& m_MapSettings; wxArrayString m_Players; void SetSelection(int playerID) { // This control may not be loaded yet (before first display) // or may have less items than we expect, which could cause // an assertion failure, so handle that here if ((unsigned int)playerID < GetCount()) { wxComboBox::SetSelection(playerID); } else { // Invalid selection wxComboBox::SetSelection(wxNOT_FOUND); } } void OnObjectSettingsChange(const ObjectSettings& settings) { SetSelection(settings.GetPlayerID()); } void OnMapSettingsChange(const AtObj& settings) { // Reload displayed player names Clear(); size_t numPlayers = settings["PlayerData"]["item"].count(); for (size_t i = 0; i <= numPlayers && i < m_Players.Count(); ++i) { Append(m_Players[i]); } SetSelection(m_ObjectSettings.GetPlayerID()); } void OnSelect(wxCommandEvent& evt) { m_ObjectSettings.SetPlayerID(evt.GetInt()); m_ObjectSettings.NotifyObserversExcept(m_ObjectConn); } DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(PlayerComboBox, wxComboBox) EVT_COMBOBOX(wxID_ANY, PlayerComboBox::OnSelect) END_EVENT_TABLE(); ////////////////////////////////////////////////////////////////////////// ObjectBottomBar::ObjectBottomBar( wxWindow* parent, Observable& objectSettings, Observable& mapSettings, ObjectSidebarImpl* p ) : wxPanel(parent, wxID_ANY), p(p) { m_ViewerWireframe = false; m_ViewerMove = false; m_ViewerGround = true; m_ViewerWater = false; - m_ViewerShadows = true; m_ViewerPolyCount = false; m_ViewerBoundingBox = false; m_ViewerAxesMarker = false; m_ViewerPropPointsMode = 0; wxSizer* mainSizer = new wxBoxSizer(wxHORIZONTAL); // --- viewer options panel ------------------------------------------------------------------------------- m_ViewerPanel = new wxPanel(this, wxID_ANY); wxSizer* viewerSizer = new wxBoxSizer(wxHORIZONTAL); wxSizer* viewerButtonsSizer = new wxStaticBoxSizer(wxHORIZONTAL, m_ViewerPanel, _("Display settings")); { wxSizer* viewerButtonsLeft = new wxBoxSizer(wxVERTICAL); viewerButtonsLeft->SetMinSize(110, -1); viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerWireframe, _("Wireframe")), _("Toggle wireframe / solid rendering")), wxSizerFlags().Expand()); viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerMove, _("Move")), _("Toggle movement along ground when playing walk/run animations")), wxSizerFlags().Expand()); viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerGround, _("Ground")), _("Toggle the ground plane")), wxSizerFlags().Expand()); // TODO: disabled until http://trac.wildfiregames.com/ticket/2692 is fixed wxButton* waterButton = new wxButton(m_ViewerPanel, ID_ViewerWater, _("Water")); waterButton->Enable(false); viewerButtonsLeft->Add(Tooltipped(waterButton, _("Toggle the water plane")), wxSizerFlags().Expand()); viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerShadows, _("Shadows")), _("Toggle shadow rendering")), wxSizerFlags().Expand()); viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerPolyCount, _("Poly count")), _("Toggle polygon-count statistics - turn off ground and shadows for more useful data")), wxSizerFlags().Expand()); wxSizer* viewerButtonsRight = new wxBoxSizer(wxVERTICAL); viewerButtonsRight->SetMinSize(110,-1); viewerButtonsRight->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerBoundingBox, _("Bounding Boxes")), _("Toggle bounding boxes")), wxSizerFlags().Expand()); viewerButtonsRight->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerAxesMarker, _("Axes Marker")), _("Toggle the axes marker (R=X, G=Y, B=Z)")), wxSizerFlags().Expand()); viewerButtonsRight->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerPropPoints, _("Prop Points")), _("Toggle prop points (works best in wireframe mode)")), wxSizerFlags().Expand()); viewerButtonsSizer->Add(viewerButtonsLeft, wxSizerFlags().Expand()); viewerButtonsSizer->Add(viewerButtonsRight, wxSizerFlags().Expand()); } viewerSizer->Add(viewerButtonsSizer, wxSizerFlags().Expand()); viewerSizer->AddSpacer(3); // --- animations panel ------------------------------------------------------------------------------- wxSizer* viewerAnimSizer = new wxStaticBoxSizer(wxVERTICAL, m_ViewerPanel, _("Animation")); // TODO: this list should come from the actor wxArrayString animChoices; AtObj anims (Datafile::ReadList("animations")); for (AtIter a = anims["item"]; a.defined(); ++a) { animChoices.Add(wxString(*a)); } wxChoice* viewerAnimSelector = new wxChoice(m_ViewerPanel, ID_ViewerAnimation, wxDefaultPosition, wxDefaultSize, animChoices); viewerAnimSelector->SetSelection(0); viewerAnimSizer->Add(viewerAnimSelector, wxSizerFlags().Expand()); wxSizer* viewerAnimSpeedSizer = new wxBoxSizer(wxHORIZONTAL); viewerAnimSpeedSizer->Add(new wxButton(m_ViewerPanel, ID_ViewerPlay, _("Play"), wxDefaultPosition, wxSize(50, -1)), wxSizerFlags().Expand()); viewerAnimSpeedSizer->Add(new wxButton(m_ViewerPanel, ID_ViewerPause, _("Pause"), wxDefaultPosition, wxSize(50, -1)), wxSizerFlags().Expand()); viewerAnimSpeedSizer->Add(new wxButton(m_ViewerPanel, ID_ViewerSlow, _("Slow"), wxDefaultPosition, wxSize(50, -1)), wxSizerFlags().Expand()); viewerAnimSizer->Add(viewerAnimSpeedSizer); viewerSizer->Add(viewerAnimSizer, wxSizerFlags().Expand()); // --- add viewer-specific options ------------------------------------------------------------------------------- m_ViewerPanel->SetSizer(viewerSizer); mainSizer->Add(m_ViewerPanel, wxSizerFlags().Expand()); m_ViewerPanel->Layout(); // prevents strange visibility glitch of the animation buttons on my machine (Vista 32-bit SP1) -- vtsj m_ViewerPanel->Show(false); // --- add player/variation selection ------------------------------------------------------------------------------- wxSizer* playerSelectionSizer = new wxBoxSizer(wxHORIZONTAL); wxSizer* playerVariationSizer = new wxBoxSizer(wxVERTICAL); // TODO: make this a wxChoice instead wxComboBox* playerSelect = new PlayerComboBox(this, objectSettings, mapSettings); playerSelectionSizer->Add(new wxStaticText(this, wxID_ANY, _("Player:")), wxSizerFlags().Align(wxALIGN_CENTER)); playerSelectionSizer->AddSpacer(3); playerSelectionSizer->Add(playerSelect); playerVariationSizer->Add(playerSelectionSizer); playerVariationSizer->AddSpacer(3); wxWindow* variationSelect = new VariationControl(this, objectSettings); variationSelect->SetMinSize(wxSize(160, -1)); wxSizer* variationSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Variation")); variationSizer->Add(variationSelect, wxSizerFlags().Proportion(1).Expand()); playerVariationSizer->Add(variationSizer, wxSizerFlags().Proportion(1)); mainSizer->AddSpacer(3); mainSizer->Add(playerVariationSizer, wxSizerFlags().Expand()); // ---------------------------------------------------------------------------------- // --- display template name wxSizer* displaySizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Selected entities")); m_TemplateNames = new wxScrolledWindow(this); m_TemplateNames->SetMinSize(wxSize(250, -1)); m_TemplateNames->SetScrollRate(0, 5); wxSizer* scrollwindowSizer = new wxBoxSizer(wxVERTICAL); m_TemplateNames->SetSizer(scrollwindowSizer); displaySizer->Add(m_TemplateNames, wxSizerFlags().Proportion(1).Expand()); m_TemplateNames->Layout(); mainSizer->AddSpacer(3); mainSizer->Add(displaySizer, wxSizerFlags().Proportion(1).Expand()); g_SelectedObjects.RegisterObserver(0, &ObjectBottomBar::OnSelectedObjectsChange, this); SetSizer(mainSizer); } static wxControl* CreateTemplateNameObject(wxWindow* parent, const std::string& templateName, int counterTemplate) { wxString idTemplate(wxString::FromUTF8(templateName.c_str())); if (counterTemplate > 1) idTemplate.Append(wxString::Format(wxT(" (%i)"), counterTemplate)); wxStaticText* templateNameObject = new wxStaticText(parent, wxID_ANY, idTemplate); return templateNameObject; } void ObjectBottomBar::OnSelectedObjectsChange(const std::vector& selectedObjects) { Freeze(); wxSizer* sizer = m_TemplateNames->GetSizer(); sizer->Clear(true); AtlasMessage::qGetSelectedObjectsTemplateNames objectTemplatesName(selectedObjects); objectTemplatesName.Post(); std::vector names = *objectTemplatesName.names; int counterTemplate = 0; std::string lastTemplateName = ""; for (std::vector::const_iterator it = names.begin(); it != names.end(); ++it) { if (lastTemplateName == "") lastTemplateName = (*it); if (lastTemplateName == (*it)) { ++counterTemplate; continue; } sizer->Add(CreateTemplateNameObject(m_TemplateNames, lastTemplateName, counterTemplate), wxSizerFlags().Align(wxALIGN_LEFT)); lastTemplateName = (*it); counterTemplate = 1; } // Add the remaining template sizer->Add(CreateTemplateNameObject(m_TemplateNames, lastTemplateName, counterTemplate), wxSizerFlags().Align(wxALIGN_LEFT)); Thaw(); sizer->FitInside(m_TemplateNames); } void ObjectBottomBar::OnFirstDisplay() { // We use messages here because the simulation is not init'd otherwise (causing a crash) // Get player names wxArrayString players; AtlasMessage::qGetPlayerDefaults qryPlayers; qryPlayers.Post(); AtObj playerData = AtlasObject::LoadFromJSON(*qryPlayers.defaults); AtObj playerDefs = *playerData["PlayerData"]; for (AtIter iterator = playerDefs["item"]; iterator.defined(); ++iterator) players.Add(wxString::FromUTF8(iterator["Name"])); wxDynamicCast(FindWindow(ID_PlayerSelect), PlayerComboBox)->SetPlayers(players); // Initialise the game with the default settings POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"wireframe", m_ViewerWireframe)); POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"walk", m_ViewerMove)); POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"ground", m_ViewerGround)); POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"water", m_ViewerWater)); - POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"shadows", m_ViewerShadows)); POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"stats", m_ViewerPolyCount)); POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"bounding_box", m_ViewerBoundingBox)); POST_MESSAGE(SetViewParamI, (AtlasMessage::eRenderView::ACTOR, L"prop_points", m_ViewerPropPointsMode)); } void ObjectBottomBar::ShowActorViewer(bool show) { m_ViewerPanel->Show(show); Layout(); } void ObjectBottomBar::OnViewerSetting(wxCommandEvent& evt) { switch (evt.GetId()) { case ID_ViewerWireframe: m_ViewerWireframe = !m_ViewerWireframe; POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"wireframe", m_ViewerWireframe)); break; case ID_ViewerMove: m_ViewerMove = !m_ViewerMove; POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"walk", m_ViewerMove)); break; case ID_ViewerGround: m_ViewerGround = !m_ViewerGround; POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"ground", m_ViewerGround)); break; case ID_ViewerWater: m_ViewerWater = !m_ViewerWater; POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"water", m_ViewerWater)); break; case ID_ViewerShadows: - m_ViewerShadows = !m_ViewerShadows; - POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"shadows", m_ViewerShadows)); + POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"shadows", true)); break; case ID_ViewerPolyCount: m_ViewerPolyCount = !m_ViewerPolyCount; POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"stats", m_ViewerPolyCount)); break; case ID_ViewerBoundingBox: m_ViewerBoundingBox = !m_ViewerBoundingBox; POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"bounding_box", m_ViewerBoundingBox)); break; case ID_ViewerAxesMarker: m_ViewerAxesMarker = !m_ViewerAxesMarker; POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"axes_marker", m_ViewerAxesMarker)); break; case ID_ViewerPropPoints: m_ViewerPropPointsMode = (m_ViewerPropPointsMode+1) % 3; POST_MESSAGE(SetViewParamI, (AtlasMessage::eRenderView::ACTOR, L"prop_points", m_ViewerPropPointsMode)); break; } } void ObjectBottomBar::OnSelectAnim(wxCommandEvent& evt) { p->m_ActorViewerAnimation = evt.GetString(); p->ActorViewerPostToGame(); } void ObjectBottomBar::OnSpeed(wxCommandEvent& evt) { switch (evt.GetId()) { case ID_ViewerPlay: p->m_ActorViewerSpeed = 1.0f; break; case ID_ViewerPause: p->m_ActorViewerSpeed = 0.0f; break; case ID_ViewerSlow: p->m_ActorViewerSpeed = 0.1f; break; } p->ActorViewerPostToGame(); } BEGIN_EVENT_TABLE(ObjectBottomBar, wxPanel) EVT_BUTTON(ID_ViewerWireframe, ObjectBottomBar::OnViewerSetting) EVT_BUTTON(ID_ViewerMove, ObjectBottomBar::OnViewerSetting) EVT_BUTTON(ID_ViewerGround, ObjectBottomBar::OnViewerSetting) EVT_BUTTON(ID_ViewerWater, ObjectBottomBar::OnViewerSetting) EVT_BUTTON(ID_ViewerShadows, ObjectBottomBar::OnViewerSetting) EVT_BUTTON(ID_ViewerPolyCount, ObjectBottomBar::OnViewerSetting) EVT_CHOICE(ID_ViewerAnimation, ObjectBottomBar::OnSelectAnim) EVT_BUTTON(ID_ViewerPlay, ObjectBottomBar::OnSpeed) EVT_BUTTON(ID_ViewerPause, ObjectBottomBar::OnSpeed) EVT_BUTTON(ID_ViewerSlow, ObjectBottomBar::OnSpeed) EVT_BUTTON(ID_ViewerBoundingBox, ObjectBottomBar::OnViewerSetting) EVT_BUTTON(ID_ViewerAxesMarker, ObjectBottomBar::OnViewerSetting) EVT_BUTTON(ID_ViewerPropPoints, ObjectBottomBar::OnViewerSetting) END_EVENT_TABLE(); Index: ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp (revision 26729) +++ ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp (revision 26730) @@ -1,559 +1,563 @@ /* Copyright (C) 2022 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 "ActorViewer.h" #include "View.h" #include "graphics/Canvas2D.h" #include "graphics/ColladaManager.h" #include "graphics/LOSTexture.h" #include "graphics/MiniMapTexture.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "graphics/ObjectManager.h" #include "graphics/ParticleManager.h" #include "graphics/Patch.h" #include "graphics/SkeletonAnimManager.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "graphics/TerritoryTexture.h" #include "graphics/Unit.h" #include "graphics/UnitManager.h" #include "graphics/Overlay.h" #include "maths/MathUtil.h" #include "ps/Filesystem.h" #include "ps/CLogger.h" #include "ps/GameSetup/Config.h" #include "ps/ProfileViewer.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/Scene.h" #include "renderer/SceneRenderer.h" #include "renderer/SkyManager.h" #include "renderer/WaterManager.h" #include "scriptinterface/ScriptContext.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpAttack.h" #include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpTerrain.h" #include "simulation2/components/ICmpUnitMotion.h" #include "simulation2/components/ICmpVisual.h" #include "simulation2/components/ICmpWaterManager.h" #include "simulation2/helpers/Render.h" struct ActorViewerImpl : public Scene { NONCOPYABLE(ActorViewerImpl); public: ActorViewerImpl() : Entity(INVALID_ENTITY), Terrain(), ColladaManager(g_VFS), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager), UnitManager(), Simulation2(&UnitManager, g_ScriptContext, &Terrain), ObjectManager(MeshManager, SkeletonAnimManager, Simulation2), LOSTexture(Simulation2), TerritoryTexture(Simulation2), MiniMapTexture(Simulation2) { UnitManager.SetObjectManager(ObjectManager); } entity_id_t Entity; CStrW CurrentUnitID; CStr CurrentUnitAnim; float CurrentSpeed; bool WalkEnabled; bool GroundEnabled; bool WaterEnabled; bool ShadowsEnabled; // Whether shadows, sky and water are enabled outside of the actor viewer. bool OldShadows; bool OldSky; bool OldWater; bool SelectionBoxEnabled; bool AxesMarkerEnabled; int PropPointsMode; // 0 disabled, 1 for point markers, 2 for point markers + axes CTerrain Terrain; CColladaManager ColladaManager; CMeshManager MeshManager; CSkeletonAnimManager SkeletonAnimManager; CUnitManager UnitManager; CSimulation2 Simulation2; CObjectManager ObjectManager; // Keep this after Simulation2 - it needs it for initialisation. CLOSTexture LOSTexture; CTerritoryTexture TerritoryTexture; CMiniMapTexture MiniMapTexture; SOverlayLine SelectionBoxOverlay; SOverlayLine AxesMarkerOverlays[3]; std::vector Props; std::vector PropPointOverlays; // Simplistic implementation of the Scene interface virtual void EnumerateObjects(const CFrustum& frustum, SceneCollector* c) { if (GroundEnabled) { for (ssize_t pj = 0; pj < Terrain.GetPatchesPerSide(); ++pj) for (ssize_t pi = 0; pi < Terrain.GetPatchesPerSide(); ++pi) c->Submit(Terrain.GetPatch(pi, pj)); } CmpPtr cmpVisual(Simulation2, Entity); if (cmpVisual) { // add selection box outlines manually if (SelectionBoxEnabled) { SelectionBoxOverlay.m_Color = CColor(35/255.f, 86/255.f, 188/255.f, .75f); // pretty blue SelectionBoxOverlay.m_Thickness = 0.1f; SimRender::ConstructBoxOutline(cmpVisual->GetSelectionBox(), SelectionBoxOverlay); c->Submit(&SelectionBoxOverlay); } // add origin axis thingy if (AxesMarkerEnabled) { CMatrix3D worldSpaceAxes; // offset from the ground a little bit to prevent fighting with the floor texture (also note: SetTranslation // sets the identity 3x3 transformation matrix, which are the world axes) worldSpaceAxes.SetTranslation(cmpVisual->GetPosition() + CVector3D(0, 0.02f, 0)); SimRender::ConstructAxesMarker(worldSpaceAxes, AxesMarkerOverlays[0], AxesMarkerOverlays[1], AxesMarkerOverlays[2]); c->Submit(&AxesMarkerOverlays[0]); c->Submit(&AxesMarkerOverlays[1]); c->Submit(&AxesMarkerOverlays[2]); } // add prop point overlays if (PropPointsMode > 0 && Props.size() > 0) { PropPointOverlays.clear(); // doesn't clear capacity, but should be ok since the number of prop points is usually pretty limited for (size_t i = 0; i < Props.size(); ++i) { CModel::Prop& prop = Props[i]; if (prop.m_Model) // should always be the case { // prop point positions are automatically updated during animations etc. by CModel::ValidatePosition const CMatrix3D& propCoordSystem = prop.m_Model->GetTransform(); SOverlayLine pointGimbal; pointGimbal.m_Color = CColor(1.f, 0.f, 1.f, 1.f); SimRender::ConstructGimbal(propCoordSystem.GetTranslation(), 0.05f, pointGimbal); PropPointOverlays.push_back(pointGimbal); if (PropPointsMode > 1) { // scale the prop axes coord system down a bit to distinguish them from the main world-space axes markers CMatrix3D displayCoordSystem = propCoordSystem; displayCoordSystem.Scale(0.5f, 0.5f, 0.5f); // revert translation scaling displayCoordSystem._14 = propCoordSystem._14; displayCoordSystem._24 = propCoordSystem._24; displayCoordSystem._34 = propCoordSystem._34; // construct an XYZ axes marker for the prop's coordinate system SOverlayLine xAxis, yAxis, zAxis; SimRender::ConstructAxesMarker(displayCoordSystem, xAxis, yAxis, zAxis); PropPointOverlays.push_back(xAxis); PropPointOverlays.push_back(yAxis); PropPointOverlays.push_back(zAxis); } } } for (size_t i = 0; i < PropPointOverlays.size(); ++i) { c->Submit(&PropPointOverlays[i]); } } } // send a RenderSubmit message so the components can submit their visuals to the renderer Simulation2.RenderSubmit(*c, frustum, false); } virtual CLOSTexture& GetLOSTexture() { return LOSTexture; } virtual CTerritoryTexture& GetTerritoryTexture() { return TerritoryTexture; } virtual CMiniMapTexture& GetMiniMapTexture() { return MiniMapTexture; } /** * Recursively fetches the props of the currently displayed entity model and its submodels, and stores them for rendering. */ void UpdatePropList(); void UpdatePropListRecursive(CModelAbstract* model); }; void ActorViewerImpl::UpdatePropList() { Props.clear(); CmpPtr cmpVisual(Simulation2, Entity); if (cmpVisual) { CUnit* unit = cmpVisual->GetUnit(); if (unit) { CModelAbstract& modelAbstract = unit->GetModel(); UpdatePropListRecursive(&modelAbstract); } } } void ActorViewerImpl::UpdatePropListRecursive(CModelAbstract* modelAbstract) { ENSURE(modelAbstract); CModel* model = modelAbstract->ToCModel(); if (model) { std::vector& modelProps = model->GetProps(); for (CModel::Prop& modelProp : modelProps) { Props.push_back(modelProp); if (modelProp.m_Model) UpdatePropListRecursive(modelProp.m_Model); } } } ActorViewer::ActorViewer() : m(*new ActorViewerImpl()) { m.WalkEnabled = false; m.GroundEnabled = true; m.WaterEnabled = false; m.ShadowsEnabled = g_RenderingOptions.GetShadows(); m.SelectionBoxEnabled = false; m.AxesMarkerEnabled = false; m.PropPointsMode = 0; // Create a tiny empty piece of terrain, just so we can put shadows // on it without having to think too hard m.Terrain.Initialize(2, NULL); CTerrainTextureEntry* tex = g_TexMan.FindTexture("whiteness"); if (tex) { for (ssize_t pi = 0; pi < m.Terrain.GetPatchesPerSide(); ++pi) { for (ssize_t pj = 0; pj < m.Terrain.GetPatchesPerSide(); ++pj) { CPatch* patch = m.Terrain.GetPatch(pi, pj); for (ssize_t i = 0; i < PATCH_SIZE; ++i) { for (ssize_t j = 0; j < PATCH_SIZE; ++j) { CMiniPatch& mp = patch->m_MiniPatches[i][j]; mp.Tex = tex; mp.Priority = 0; } } } } } else { debug_warn(L"Failed to load whiteness texture"); } // Prepare the simulation m.Simulation2.LoadDefaultScripts(); m.Simulation2.ResetState(); // Set player data m.Simulation2.SetMapSettings(m.Simulation2.GetPlayerDefaults()); m.Simulation2.LoadPlayerSettings(true); // Tell the simulation we've already loaded the terrain CmpPtr cmpTerrain(m.Simulation2, SYSTEM_ENTITY); if (cmpTerrain) cmpTerrain->ReloadTerrain(false); // Remove FOW since we're in Atlas CmpPtr cmpRangeManager(m.Simulation2, SYSTEM_ENTITY); if (cmpRangeManager) cmpRangeManager->SetLosRevealAll(-1, true); m.Simulation2.InitGame(); } ActorViewer::~ActorViewer() { delete &m; } CSimulation2* ActorViewer::GetSimulation2() { return &m.Simulation2; } entity_id_t ActorViewer::GetEntity() { return m.Entity; } void ActorViewer::UnloadObjects() { m.ObjectManager.UnloadObjects(); } void ActorViewer::SetActor(const CStrW& name, const CStr& animation, player_id_t playerID) { bool needsAnimReload = false; CStrW id = name; // Recreate the entity, if we don't have one or if the new one is different if (m.Entity == INVALID_ENTITY || id != m.CurrentUnitID) { // Delete the old entity (if any) if (m.Entity != INVALID_ENTITY) { m.Simulation2.DestroyEntity(m.Entity); m.Simulation2.FlushDestroyedEntities(); m.Entity = INVALID_ENTITY; } // Clear particles associated with deleted entity g_Renderer.GetSceneRenderer().GetParticleManager().ClearUnattachedEmitters(); // If there's no actor to display, return with nothing loaded if (id.empty()) return; m.Entity = m.Simulation2.AddEntity(L"preview|" + id); if (m.Entity == INVALID_ENTITY) return; CmpPtr cmpPosition(m.Simulation2, m.Entity); if (cmpPosition) { ssize_t c = TERRAIN_TILE_SIZE * m.Terrain.GetPatchesPerSide()*PATCH_SIZE/2; cmpPosition->JumpTo(entity_pos_t::FromInt(c), entity_pos_t::FromInt(c)); cmpPosition->SetYRotation(entity_angle_t::Pi()); } CmpPtr cmpOwnership(m.Simulation2, m.Entity); if (cmpOwnership) cmpOwnership->SetOwner(playerID); needsAnimReload = true; } if (animation != m.CurrentUnitAnim) needsAnimReload = true; if (needsAnimReload) { // Emulate the typical simulation animation behaviour. CStr anim = animation.LowerCase(); float speed = 1.0f; // Speed will be ignored if we have a repeat time. float repeatTime = 0.0f; m.CurrentSpeed = 0.0f; if (anim == "walk") { CmpPtr cmpUnitMotion(m.Simulation2, m.Entity); if (cmpUnitMotion) speed = cmpUnitMotion->GetWalkSpeed().ToFloat(); else speed = 7.f; // Typical unit walk speed. m.CurrentSpeed = speed; } else if (anim == "run") { CmpPtr cmpUnitMotion(m.Simulation2, m.Entity); if (cmpUnitMotion) speed = cmpUnitMotion->GetWalkSpeed().ToFloat() * cmpUnitMotion->GetRunMultiplier().ToFloat(); else speed = 12.f; // Typical unit run speed. m.CurrentSpeed = speed; } else if (anim.Find("attack_") == 0) { CmpPtr cmpAttack(m.Simulation2, m.Entity); if (cmpAttack) for (const CStr& type : cmpAttack->GetAttackTypes()) if (anim == "attack_" + type.LowerCase()) { repeatTime = GetRepeatTimeByAttackType(type); break; } } CmpPtr cmpVisual(m.Simulation2, m.Entity); if (cmpVisual) { // TODO: SetEntitySelection(anim) cmpVisual->SelectAnimation(anim, false, fixed::FromFloat(speed)); if (repeatTime > 0.0f) cmpVisual->SetAnimationSyncRepeat(fixed::FromFloat(repeatTime)); } // update prop list for new entity/animation (relies on needsAnimReload also getting called for entire entity changes) m.UpdatePropList(); } m.CurrentUnitID = id; m.CurrentUnitAnim = animation; } void ActorViewer::SetEnabled(bool enabled) { if (enabled) { // Set shadows, sky and water. m.OldShadows = g_RenderingOptions.GetShadows(); SetShadowsEnabled(m.ShadowsEnabled); m.OldSky = g_Renderer.GetSceneRenderer().GetSkyManager().IsSkyVisible(); g_Renderer.GetSceneRenderer().GetSkyManager().SetSkyVisible(false); m.OldWater = g_Renderer.GetSceneRenderer().GetWaterManager().m_RenderWater; g_Renderer.GetSceneRenderer().GetWaterManager().m_RenderWater = m.WaterEnabled; } else { // Restore the old renderer state SetShadowsEnabled(m.OldShadows); g_Renderer.GetSceneRenderer().GetSkyManager().SetSkyVisible(m.OldSky); g_Renderer.GetSceneRenderer().GetWaterManager().m_RenderWater = m.OldWater; } } void ActorViewer::SetWalkEnabled(bool enabled) { m.WalkEnabled = enabled; } void ActorViewer::SetGroundEnabled(bool enabled) { m.GroundEnabled = enabled; } void ActorViewer::SetWaterEnabled(bool enabled) { m.WaterEnabled = enabled; // Adjust water level entity_pos_t waterLevel = entity_pos_t::FromFloat(enabled ? 10.f : 0.f); CmpPtr cmpWaterManager(m.Simulation2, SYSTEM_ENTITY); if (cmpWaterManager) cmpWaterManager->SetWaterLevel(waterLevel); } void ActorViewer::SetShadowsEnabled(bool enabled) { g_RenderingOptions.SetShadows(enabled); m.ShadowsEnabled = enabled; } +void ActorViewer::ToggleShadows() +{ + SetShadowsEnabled(!m.ShadowsEnabled); +} void ActorViewer::SetBoundingBoxesEnabled(bool enabled) { m.SelectionBoxEnabled = enabled; } void ActorViewer::SetAxesMarkerEnabled(bool enabled) { m.AxesMarkerEnabled = enabled; } void ActorViewer::SetPropPointsMode(int mode) { m.PropPointsMode = mode; } void ActorViewer::SetStatsEnabled(bool enabled) { if (enabled) g_ProfileViewer.ShowTable("renderer"); else g_ProfileViewer.ShowTable(""); } float ActorViewer::GetRepeatTimeByAttackType(const std::string& type) const { CmpPtr cmpAttack(m.Simulation2, m.Entity); if (cmpAttack) return cmpAttack->GetRepeatTime(type); return 0.0f; } void ActorViewer::Render() { // TODO: ActorViewer should reuse CRenderer code and not duplicate it. // Set simulation context for rendering purposes g_Renderer.GetSceneRenderer().SetSimulation(&m.Simulation2); // Find the centre of the interesting region, in the middle of the patch // and half way up the model (assuming there is one) CVector3D centre; CmpPtr cmpVisual(m.Simulation2, m.Entity); if (cmpVisual) cmpVisual->GetBounds().GetCenter(centre); else centre.Y = 0.f; centre.X = centre.Z = TERRAIN_TILE_SIZE * m.Terrain.GetPatchesPerSide()*PATCH_SIZE/2; CCamera camera = AtlasView::GetView_Actor()->GetCamera(); camera.m_Orientation.Translate(centre.X, centre.Y, centre.Z); camera.UpdateFrustum(); g_Renderer.GetSceneRenderer().SetSceneCamera(camera, camera); g_Renderer.BeginFrame(); g_Renderer.GetSceneRenderer().RenderScene(g_Renderer.GetDeviceCommandContext(), m); { CCanvas2D canvas(g_Renderer.GetDeviceCommandContext()); g_Logger->Render(canvas); g_ProfileViewer.RenderProfile(canvas); } g_Renderer.EndFrame(); ogl_WarnIfError(); } void ActorViewer::Update(float simFrameLength, float realFrameLength) { m.Simulation2.Update((int)(simFrameLength*1000)); m.Simulation2.Interpolate(simFrameLength, 0, realFrameLength); if (m.WalkEnabled && m.CurrentSpeed) { CmpPtr cmpPosition(m.Simulation2, m.Entity); if (cmpPosition) { // Move the model by speed*simFrameLength forwards float z = cmpPosition->GetPosition().Z.ToFloat(); z -= m.CurrentSpeed*simFrameLength; // Wrap at the edges, so it doesn't run off into the horizon ssize_t c = TERRAIN_TILE_SIZE * m.Terrain.GetPatchesPerSide()*PATCH_SIZE/2; if (z < c - TERRAIN_TILE_SIZE*PATCH_SIZE * 0.1f) z = c + TERRAIN_TILE_SIZE*PATCH_SIZE * 0.1f; cmpPosition->JumpTo(cmpPosition->GetPosition().X, entity_pos_t::FromFloat(z)); } } } Index: ps/trunk/source/tools/atlas/GameInterface/ActorViewer.h =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/ActorViewer.h (revision 26729) +++ ps/trunk/source/tools/atlas/GameInterface/ActorViewer.h (revision 26730) @@ -1,57 +1,58 @@ /* Copyright (C) 2022 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_ACTORVIEWER #define INCLUDED_ACTORVIEWER #include "ps/CStrForward.h" #include "simulation2/helpers/Player.h" #include "simulation2/system/Entity.h" struct ActorViewerImpl; struct SColor4ub; class CSimulation2; class ActorViewer { NONCOPYABLE(ActorViewer); public: ActorViewer(); ~ActorViewer(); CSimulation2* GetSimulation2(); entity_id_t GetEntity(); void SetActor(const CStrW& id, const CStr8& animation, player_id_t playerID); void SetEnabled(bool enabled); void UnloadObjects(); void SetWalkEnabled(bool enabled); void SetGroundEnabled(bool enabled); void SetWaterEnabled(bool enabled); void SetShadowsEnabled(bool enabled); + void ToggleShadows(); void SetStatsEnabled(bool enabled); void SetBoundingBoxesEnabled(bool enabled); void SetAxesMarkerEnabled(bool enabled); void SetPropPointsMode(int mode); void Render(); void Update(float simFrameLength, float realFrameLength); private: ActorViewerImpl& m; float GetRepeatTimeByAttackType(const std::string& type) const; }; #endif // INCLUDED_ACTORVIEWER Index: ps/trunk/source/tools/atlas/GameInterface/View.cpp =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/View.cpp (revision 26729) +++ ps/trunk/source/tools/atlas/GameInterface/View.cpp (revision 26730) @@ -1,461 +1,461 @@ /* Copyright (C) 2022 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 "View.h" #include "ActorViewer.h" #include "GameLoop.h" #include "Messages.h" #include "SimState.h" #include "graphics/Canvas2D.h" #include "graphics/CinemaManager.h" #include "graphics/GameView.h" #include "graphics/ParticleManager.h" #include "graphics/UnitManager.h" #include "lib/timer.h" #include "lib/utf8.h" #include "maths/MathUtil.h" #include "ps/Game.h" #include "ps/GameSetup/GameSetup.h" #include "ps/VideoMode.h" #include "ps/World.h" #include "renderer/backend/gl/Device.h" #include "renderer/DebugRenderer.h" #include "renderer/Renderer.h" #include "renderer/SceneRenderer.h" #include "simulation2/components/ICmpObstructionManager.h" #include "simulation2/components/ICmpParticleManager.h" #include "simulation2/components/ICmpPathfinder.h" #include "simulation2/Simulation2.h" #include "soundmanager/ISoundManager.h" extern void (*Atlas_GLSwapBuffers)(void* context); extern int g_xres, g_yres; ////////////////////////////////////////////////////////////////////////// void AtlasView::SetParam(const std::wstring& UNUSED(name), bool UNUSED(value)) { } void AtlasView::SetParam(const std::wstring& UNUSED(name), const AtlasMessage::Color& UNUSED(value)) { } void AtlasView::SetParam(const std::wstring& UNUSED(name), const std::wstring& UNUSED(value)) { } void AtlasView::SetParam(const std::wstring& UNUSED(name), int UNUSED(value)) { } ////////////////////////////////////////////////////////////////////////// AtlasViewActor::AtlasViewActor() : m_SpeedMultiplier(1.f), m_ActorViewer(new ActorViewer()) { } AtlasViewActor::~AtlasViewActor() { delete m_ActorViewer; } void AtlasViewActor::Update(float realFrameLength) { m_ActorViewer->Update(realFrameLength * m_SpeedMultiplier, realFrameLength); } void AtlasViewActor::Render() { SViewPort vp = { 0, 0, g_xres, g_yres }; CCamera& camera = GetCamera(); camera.SetViewPort(vp); camera.SetPerspectiveProjection(2.f, 512.f, DEGTORAD(20.f)); camera.UpdateFrustum(); m_ActorViewer->Render(); Atlas_GLSwapBuffers((void*)g_AtlasGameLoop->glCanvas); } CCamera& AtlasViewActor::GetCamera() { return m_Camera; } CSimulation2* AtlasViewActor::GetSimulation2() { return m_ActorViewer->GetSimulation2(); } entity_id_t AtlasViewActor::GetEntityId(AtlasMessage::ObjectID UNUSED(obj)) { return m_ActorViewer->GetEntity(); } bool AtlasViewActor::WantsHighFramerate() { if (m_SpeedMultiplier != 0.f) return true; return false; } void AtlasViewActor::SetEnabled(bool enabled) { m_ActorViewer->SetEnabled(enabled); } void AtlasViewActor::SetSpeedMultiplier(float speedMultiplier) { m_SpeedMultiplier = speedMultiplier; } ActorViewer& AtlasViewActor::GetActorViewer() { return *m_ActorViewer; } void AtlasViewActor::SetParam(const std::wstring& name, bool value) { if (name == L"wireframe") g_Renderer.GetSceneRenderer().SetModelRenderMode(value ? WIREFRAME : SOLID); else if (name == L"walk") m_ActorViewer->SetWalkEnabled(value); else if (name == L"ground") m_ActorViewer->SetGroundEnabled(value); // TODO: this causes corruption of WaterManager's global state // which should be asociated with terrain or simulation instead // see http://trac.wildfiregames.com/ticket/2692 //else if (name == L"water") //m_ActorViewer->SetWaterEnabled(value); else if (name == L"shadows") - m_ActorViewer->SetShadowsEnabled(value); + m_ActorViewer->ToggleShadows(); else if (name == L"stats") m_ActorViewer->SetStatsEnabled(value); else if (name == L"bounding_box") m_ActorViewer->SetBoundingBoxesEnabled(value); else if (name == L"axes_marker") m_ActorViewer->SetAxesMarkerEnabled(value); } void AtlasViewActor::SetParam(const std::wstring& name, int value) { if (name == L"prop_points") m_ActorViewer->SetPropPointsMode(value); } void AtlasViewActor::SetParam(const std::wstring& UNUSED(name), const AtlasMessage::Color& UNUSED(value)) { } ////////////////////////////////////////////////////////////////////////// AtlasViewGame::AtlasViewGame() : m_SpeedMultiplier(0.f), m_IsTesting(false), m_DrawMoveTool(false) { ENSURE(g_Game); } AtlasViewGame::~AtlasViewGame() { for (const std::pair& p : m_SavedStates) delete p.second; } CSimulation2* AtlasViewGame::GetSimulation2() { return g_Game->GetSimulation2(); } void AtlasViewGame::Update(float realFrameLength) { const float actualFrameLength = realFrameLength * m_SpeedMultiplier; // Clean up any entities destroyed during UI message processing g_Game->GetSimulation2()->FlushDestroyedEntities(); if (m_SpeedMultiplier == 0.f) { // Update unit interpolation g_Game->Interpolate(0.0, realFrameLength); } else { // Update the whole world // (Tell the game update not to interpolate graphics - we'll do that // ourselves) g_Game->Update(actualFrameLength, false); // Interpolate the graphics - we only want to do this once per visual frame, // not in every call to g_Game->Update g_Game->Interpolate(actualFrameLength, realFrameLength); } // Run sound idle tasks every frame. if (g_SoundManager) g_SoundManager->IdleTask(); // Cinematic motion should be independent of simulation update, so we can // preview the cinematics by themselves g_Game->GetView()->GetCinema()->Update(realFrameLength); } void AtlasViewGame::Render() { SViewPort vp = { 0, 0, g_xres, g_yres }; CCamera& camera = GetCamera(); camera.SetViewPort(vp); camera.SetProjectionFromCamera(*g_Game->GetView()->GetCamera()); camera.UpdateFrustum(); g_Renderer.RenderFrame(false); Atlas_GLSwapBuffers((void*)g_AtlasGameLoop->glCanvas); // In case of atlas the device's present will do only internal stuff // without calling a real backbuffer swap. g_VideoMode.GetBackendDevice()->Present(); } void AtlasViewGame::DrawCinemaPathTool() { if (!m_DrawMoveTool) return; const CVector3D focus = m_MoveTool; const CVector3D camera = GetCamera().GetOrientation().GetTranslation(); const float scale = (focus - camera).Length(); const float axisLength = scale / 10.0f; const float lineWidth = scale / 1e3f; g_Renderer.GetDebugRenderer().DrawLine( focus, focus + CVector3D(axisLength, 0, 0), CColor(1.0f, 0.0f, 0.0f, 1.0f), lineWidth, false); g_Renderer.GetDebugRenderer().DrawLine( focus, focus + CVector3D(0, axisLength, 0), CColor(0.0f, 1.0f, 0.0f, 1.0f), lineWidth, false); g_Renderer.GetDebugRenderer().DrawLine( focus, focus + CVector3D(0, 0, axisLength), CColor(0.0f, 0.0f, 1.0f, 1.0f), lineWidth, false); } void AtlasViewGame::DrawOverlays(CCanvas2D& canvas) { if (m_Bandbox.left >= m_Bandbox.right || m_Bandbox.top >= m_Bandbox.bottom) return; const std::vector outerPoints = { m_Bandbox.TopLeft() + CVector2D(-1.0f, -1.0f), m_Bandbox.TopRight() + CVector2D(1.0f, -1.0f), m_Bandbox.BottomRight() + CVector2D(1.0f, 1.0f), m_Bandbox.BottomLeft() + CVector2D(-1.0f, 1.0f), m_Bandbox.TopLeft() + CVector2D(-1.0f, -1.0f) }; canvas.DrawLine(outerPoints, 1.5f, CColor(0.0f, 0.0f, 0.0f, 1.0f)); const std::vector innerPoints = { m_Bandbox.TopLeft(), m_Bandbox.TopRight(), m_Bandbox.BottomRight(), m_Bandbox.BottomLeft(), m_Bandbox.TopLeft() }; canvas.DrawLine(innerPoints, 1.5f, CColor(1.0f, 1.0f, 1.0f, 1.0f)); } void AtlasViewGame::SetParam(const std::wstring& name, bool value) { if (name == L"priorities") g_Renderer.GetSceneRenderer().SetDisplayTerrainPriorities(value); else if (name == L"movetool") m_DrawMoveTool = value; } void AtlasViewGame::SetParam(const std::wstring& name, float value) { if (name == L"movetool_x") m_MoveTool.X = value; else if (name == L"movetool_y") m_MoveTool.Y = value; else if (name == L"movetool_z") m_MoveTool.Z = value; } void AtlasViewGame::SetParam(const std::wstring& name, const std::wstring& value) { if (name == L"passability") { m_DisplayPassability = CStrW(value).ToUTF8(); CmpPtr cmpPathfinder(*GetSimulation2(), SYSTEM_ENTITY); if (cmpPathfinder) { if (!value.empty()) cmpPathfinder->SetAtlasOverlay(true, cmpPathfinder->GetPassabilityClass(m_DisplayPassability)); else cmpPathfinder->SetAtlasOverlay(false); } } } CCamera& AtlasViewGame::GetCamera() { return *g_Game->GetView()->GetCamera(); } bool AtlasViewGame::WantsHighFramerate() { if (g_Game->GetView()->GetCinema()->IsPlaying()) return true; if (m_SpeedMultiplier != 0.f) return true; return false; } void AtlasViewGame::SetSpeedMultiplier(float speed) { m_SpeedMultiplier = speed; } void AtlasViewGame::SetTesting(bool testing) { m_IsTesting = testing; // If we're testing, particles should freeze on pause (like in-game), otherwise they keep going CmpPtr cmpParticleManager(*GetSimulation2(), SYSTEM_ENTITY); if (cmpParticleManager) cmpParticleManager->SetUseSimTime(m_IsTesting); } void AtlasViewGame::SaveState(const std::wstring& label) { delete m_SavedStates[label]; // in case it already exists m_SavedStates[label] = SimState::Freeze(); } void AtlasViewGame::RestoreState(const std::wstring& label) { SimState* simState = m_SavedStates[label]; if (! simState) return; simState->Thaw(); } std::wstring AtlasViewGame::DumpState(bool binary) { std::stringstream stream; if (binary) { if (! g_Game->GetSimulation2()->SerializeState(stream)) return L"(internal error)"; // We can't return raw binary data, because we want to handle it with wxJS which // doesn't like \0 bytes in strings, so return it as hex static const char digits[] = "0123456789abcdef"; std::string str = stream.str(); std::wstring ret; ret.reserve(str.length()*3); for (size_t i = 0; i < str.length(); ++i) { ret += digits[(unsigned char)str[i] >> 4]; ret += digits[(unsigned char)str[i] & 0x0f]; ret += ' '; } return ret; } else { if (! g_Game->GetSimulation2()->DumpDebugState(stream)) return L"(internal error)"; return wstring_from_utf8(stream.str()); } } void AtlasViewGame::SetBandbox(bool visible, float x0, float y0, float x1, float y1) { if (visible) { // Make sure corners are arranged in correct order if (x0 > x1) std::swap(x0, x1); if (y0 > y1) std::swap(y0, y1); m_Bandbox = CRect(x0, y0, x1, y1); } else { m_Bandbox = CRect{}; } } ////////////////////////////////////////////////////////////////////////// AtlasViewNone* view_None = NULL; AtlasViewGame* view_Game = NULL; AtlasViewActor* view_Actor = NULL; AtlasView::~AtlasView() { } AtlasView* AtlasView::GetView(int /*eRenderView*/ view) { switch (view) { case AtlasMessage::eRenderView::NONE: return AtlasView::GetView_None(); case AtlasMessage::eRenderView::GAME: return AtlasView::GetView_Game(); case AtlasMessage::eRenderView::ACTOR: return AtlasView::GetView_Actor(); default: debug_warn(L"Invalid view type"); return AtlasView::GetView_None(); } } AtlasView* AtlasView::GetView_None() { if (! view_None) view_None = new AtlasViewNone(); return view_None; } AtlasViewGame* AtlasView::GetView_Game() { if (! view_Game) view_Game = new AtlasViewGame(); return view_Game; } AtlasViewActor* AtlasView::GetView_Actor() { if (! view_Actor) view_Actor = new AtlasViewActor(); return view_Actor; } void AtlasView::DestroyViews() { delete view_None; view_None = NULL; delete view_Game; view_Game = NULL; delete view_Actor; view_Actor = NULL; }