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;
}