2021SC@SDUSC
这是Overload引擎相关的第十篇文章,同时也是OvEditor分析的第五篇。Overload引擎的Github主页在这里。
本篇文章将会介绍OvEditor的Panels文件夹中与AView相关的文件,具体应该会涉及AView和AViewControllable,也就是所有视图的基础。
/**
* Base class for any view
*/
class AView : public OvUI::Panels::PanelWindow
{
public:
/**
* Constructor
* @param p_title
* @param p_opened
* @param p_windowSettings
*/
AView
(
const std::string& p_title,
bool p_opened,
const OvUI::Settings::PanelWindowSettings& p_windowSettings
);
/**
* Update the view
* @param p_deltaTime
*/
virtual void Update(float p_deltaTime);
/**
* Custom implementation of the draw method
*/
void _Draw_Impl() override;
/**
* Custom implementation of the render method to define in dervied classes
*/
virtual void _Render_Impl() = 0;
/**
* Render the view
*/
void Render();
/**
* Defines the camera position
* @param p_position
*/
void SetCameraPosition(const OvMaths::FVector3& p_position);
/**
* Defines the camera rotation
* @param p_rotation
*/
void SetCameraRotation(const OvMaths::FQuaternion& p_rotation);
/**
* Returns the camera position
*/
const OvMaths::FVector3& GetCameraPosition() const;
/**
* Returns the camera rotation
*/
const OvMaths::FQuaternion& GetCameraRotation() const;
/**
* Returns the camera used by this view
*/
OvRendering::LowRenderer::Camera& GetCamera();
/**
* Returns the size of the panel ignoring its titlebar height
*/
std::pair<uint16_t, uint16_t> GetSafeSize() const;
/**
* Returns the grid color of the view
*/
const OvMaths::FVector3& GetGridColor() const;
/**
* Defines the grid color of the view
* @param p_color
*/
void SetGridColor(const OvMaths::FVector3& p_color);
/**
* Fill the UBO using the view settings
*/
void FillEngineUBO();
protected:
/**
* Update camera matrices
*/
void PrepareCamera();
protected:
OvEditor::Core::EditorRenderer& m_editorRenderer;
OvRendering::LowRenderer::Camera m_camera;
OvMaths::FVector3 m_cameraPosition;
OvMaths::FQuaternion m_cameraRotation;
OvUI::Widgets::Visual::Image* m_image;
OvMaths::FVector3 m_gridColor = OvMaths::FVector3 { 0.176f, 0.176f, 0.176f };
OvRendering::Buffers::Framebuffer m_fbo;
};
定义了所有视图的基类,具体函数在cpp文件实现时讨论。
我们按代码编写顺序进行讲解。
OvEditor::Panels::AView::AView
(
const std::string& p_title,
bool p_opened,
const OvUI::Settings::PanelWindowSettings& p_windowSettings
) : PanelWindow(p_title, p_opened, p_windowSettings), m_editorRenderer(EDITOR_RENDERER())
{
m_cameraPosition = { -10.0f, 3.0f, 10.0f };
m_cameraRotation = OvMaths::FQuaternion({0.0f, 135.0f, 0.0f});
m_image = &CreateWidget<OvUI::Widgets::Visual::Image>(m_fbo.GetTextureID(), OvMaths::FVector2{ 0.f, 0.f });
scrollable = false;
}
首先是构造函数。除了基础的赋值,还设定了摄像机的初始位置,以及用四元数表示的摄像机旋转。创建了一副图像,同时设定视图不可滚动。
void OvEditor::Panels::AView::Update(float p_deltaTime)
{
auto[winWidth, winHeight] = GetSafeSize();
m_image->size = OvMaths::FVector2(static_cast<float>(winWidth), static_cast<float>(winHeight));
m_fbo.Resize(winWidth, winHeight);
}
Update首先会寻找一个安全的窗口大小:
std::pair<uint16_t, uint16_t> OvEditor::Panels::AView::GetSafeSize() const
{
auto result = GetSize() - OvMaths::FVector2{ 0.f, 25.f }; // 25 == title bar height
return { static_cast<uint16_t>(result.x), static_cast<uint16_t>(result.y) };
}
具体是在获得窗口大小之后减去标题栏的高度,之后返回。
接下来会更改m_image的大小,并且把frame buffer object也同时更改大小。
void OvEditor::Panels::AView::_Draw_Impl()
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
OvUI::Panels::PanelWindow::_Draw_Impl();
ImGui::PopStyleVar();
}
_Draw_Impl函数用于添加自定义的绘制方法。
首先执行imgui内的函数PushStyleVar:
void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val)
{
const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2)
{
ImGuiContext& g = *GImGui;
ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style);
g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar));
*pvar = val;
return;
}
IM_ASSERT(0 && "Called PushStyleVar() ImVec2 variant but variable is not a ImVec2!");
}
实际用于更改ui的样式,给定样式的序号和特殊值。
接下来执行PanelWindow的_Draw_Impl,真正的绘制了视图:
void OvUI::Panels::PanelWindow::_Draw_Impl()
{
if (m_opened)
{
int windowFlags = ImGuiWindowFlags_None;
if (!resizable) windowFlags |= ImGuiWindowFlags_NoResize;
if (!movable) windowFlags |= ImGuiWindowFlags_NoMove;
if (!dockable) windowFlags |= ImGuiWindowFlags_NoDocking;
if (hideBackground) windowFlags |= ImGuiWindowFlags_NoBackground;
if (forceHorizontalScrollbar) windowFlags |= ImGuiWindowFlags_AlwaysHorizontalScrollbar;
if (forceVerticalScrollbar) windowFlags |= ImGuiWindowFlags_AlwaysVerticalScrollbar;
if (allowHorizontalScrollbar) windowFlags |= ImGuiWindowFlags_HorizontalScrollbar;
if (!bringToFrontOnFocus) windowFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus;
if (!collapsable) windowFlags |= ImGuiWindowFlags_NoCollapse;
if (!allowInputs) windowFlags |= ImGuiWindowFlags_NoInputs;
if (!scrollable) windowFlags |= ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar;
if (!titleBar) windowFlags |= ImGuiWindowFlags_NoTitleBar;
ImVec2 minSizeConstraint = Internal::Converter::ToImVec2(minSize);
ImVec2 maxSizeConstraint = Internal::Converter::ToImVec2(maxSize);
/* Cancel constraint if x or y is <= 0.f */
if (minSizeConstraint.x <= 0.f || minSizeConstraint.y <= 0.f)
minSizeConstraint = { 0.0f, 0.0f };
if (maxSizeConstraint.x <= 0.f || maxSizeConstraint.y <= 0.f)
maxSizeConstraint = { 10000.f, 10000.f };
ImGui::SetNextWindowSizeConstraints(minSizeConstraint, maxSizeConstraint);
if (ImGui::Begin((name + m_panelID).c_str(), closable ? &m_opened : nullptr, windowFlags))
{
m_hovered = ImGui::IsWindowHovered();
m_focused = ImGui::IsWindowFocused();
auto scrollY = ImGui::GetScrollY();
m_scrolledToBottom = scrollY == ImGui::GetScrollMaxY();
m_scrolledToTop = scrollY == 0.0f;
if (!m_opened)
CloseEvent.Invoke();
Update();
if (m_mustScrollToBottom)
{
ImGui::SetScrollY(ImGui::GetScrollMaxY());
m_mustScrollToBottom = false;
}
if (m_mustScrollToTop)
{
ImGui::SetScrollY(0.0f);
m_mustScrollToTop = false;
}
DrawWidgets();
}
ImGui::End();
}
}
最后用PopStyleVar,把之前给出的样式去除:
void ImGui::PopStyleVar(int count)
{
ImGuiContext& g = *GImGui;
while (count > 0)
{
// We avoid a generic memcpy(data, &backup.Backup.., GDataTypeSize[info->Type] * info->Count), the overhead in Debug is not worth it.
ImGuiStyleMod& backup = g.StyleModifiers.back();
const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx);
void* data = info->GetVarPtr(&g.Style);
if (info->Type == ImGuiDataType_Float && info->Count == 1) { ((float*)data)[0] = backup.BackupFloat[0]; }
else if (info->Type == ImGuiDataType_Float && info->Count == 2) { ((float*)data)[0] = backup.BackupFloat[0]; ((float*)data)[1] = backup.BackupFloat[1]; }
g.StyleModifiers.pop_back();
count--;
}
}
void OvEditor::Panels::AView::Render()
{
FillEngineUBO();
auto [winWidth, winHeight] = GetSafeSize();
EDITOR_CONTEXT(shapeDrawer)->SetViewProjection(m_camera.GetProjectionMatrix() * m_camera.GetViewMatrix());
EDITOR_CONTEXT(renderer)->SetViewPort(0, 0, winWidth, winHeight);
_Render_Impl();
}
Render函数首先会用视图的设定来填充UBO,以准备渲染:
void OvEditor::Panels::AView::FillEngineUBO()
{
auto& engineUBO = *EDITOR_CONTEXT(engineUBO);
auto[winWidth, winHeight] = GetSafeSize();
size_t offset = sizeof(OvMaths::FMatrix4); // We skip the model matrix (Which is a special case, modified every draw calls)
engineUBO.SetSubData(OvMaths::FMatrix4::Transpose(m_camera.GetViewMatrix()), std::ref(offset));
engineUBO.SetSubData(OvMaths::FMatrix4::Transpose(m_camera.GetProjectionMatrix()), std::ref(offset));
engineUBO.SetSubData(m_cameraPosition, std::ref(offset));
}
在此首先获得UBO,之后获得安全的窗口大小以及以矩阵定义的偏移。此偏移只使用了View矩阵和Projection矩阵,最后再放入摄像机的位置。
Render()函数接着获得安全大小,并且设定ViewProjection矩阵(View * Projection),设定视口大小位置之后,开始渲染。
void OvEditor::Panels::AView::SetCameraPosition(const OvMaths::FVector3 & p_position)
{
m_cameraPosition = p_position;
}
void OvEditor::Panels::AView::SetCameraRotation(const OvMaths::FQuaternion& p_rotation)
{
m_cameraRotation = p_rotation;
}
const OvMaths::FVector3 & OvEditor::Panels::AView::GetCameraPosition() const
{
return m_cameraPosition;
}
const OvMaths::FQuaternion& OvEditor::Panels::AView::GetCameraRotation() const
{
return m_cameraRotation;
}
OvRendering::LowRenderer::Camera & OvEditor::Panels::AView::GetCamera()
{
return m_camera;
}
std::pair<uint16_t, uint16_t> OvEditor::Panels::AView::GetSafeSize() const
{
auto result = GetSize() - OvMaths::FVector2{ 0.f, 25.f }; // 25 == title bar height
return { static_cast<uint16_t>(result.x), static_cast<uint16_t>(result.y) };
}
const OvMaths::FVector3& OvEditor::Panels::AView::GetGridColor() const
{
return m_gridColor;
}
void OvEditor::Panels::AView::SetGridColor(const OvMaths::FVector3& p_color)
{
m_gridColor = p_color;
}
这几个都是非常简单的设定值和返回值的函数,不多讲了。
void OvEditor::Panels::AView::PrepareCamera()
{
auto [winWidth, winHeight] = GetSafeSize();
m_camera.CacheMatrices(winWidth, winHeight, m_cameraPosition, m_cameraRotation);
}
最后PrepareCamera函数会获得安全大小,之后缓存我们对应视窗和摄像机的投影,视图和视锥体对应矩阵:
void OvRendering::LowRenderer::Camera::CacheMatrices(uint16_t p_windowWidth, uint16_t p_windowHeight, const OvMaths::FVector3& p_position, const OvMaths::FQuaternion& p_rotation)
{
CacheProjectionMatrix(p_windowWidth, p_windowHeight);
CacheViewMatrix(p_position, p_rotation);
CacheFrustum(m_viewMatrix, m_projectionMatrix);
class AViewControllable : public OvEditor::Panels::AView
{
public:
/**
* Constructor
* @param p_title
* @param p_opened
* @param p_windowSettings
* @param p_enableFocusInputs
*/
AViewControllable
(
const std::string& p_title,
bool p_opened,
const OvUI::Settings::PanelWindowSettings& p_windowSettings,
bool p_enableFocusInputs = false
);
/**
* Update the controllable view (Handle inputs)
* @param p_deltaTime
*/
virtual void Update(float p_deltaTime) override;
/**
* Returns the camera controller of the controllable view
*/
OvEditor::Core::CameraController& GetCameraController();
protected:
OvEditor::Core::CameraController m_cameraController;
};
定义了AViewControllable类,具体函数实现在下面讲。
OvEditor::Panels::AViewControllable::AViewControllable
(
const std::string& p_title,
bool p_opened,
const OvUI::Settings::PanelWindowSettings& p_windowSettings,
bool p_enableFocusInputs
) : AView(p_title, p_opened, p_windowSettings), m_cameraController(*this, m_camera, m_cameraPosition, m_cameraRotation, p_enableFocusInputs)
{
}
首先是构造函数,主要是赋值,同时创建了AView和m_cameraController
void OvEditor::Panels::AViewControllable::Update(float p_deltaTime)
{
m_cameraController.HandleInputs(p_deltaTime);
AView::Update(p_deltaTime);
}
Update函数首先处理鼠标和键盘的输入,让摄像机做出相应的变换:
void OvEditor::Core::CameraController::HandleInputs(float p_deltaTime)
{
if (m_view.IsHovered())
{
UpdateMouseState();
ImGui::GetIO().DisableMouseUpdate = m_rightMousePressed || m_middleMousePressed;
if (!ImGui::IsAnyItemActive() && m_enableFocusInputs)
{
if (EDITOR_EXEC(IsAnyActorSelected()))
{
auto targetPos = EDITOR_EXEC(GetSelectedActor()).transform.GetWorldPosition();
float dist = GetActorFocusDist(EDITOR_EXEC(GetSelectedActor()));
if (m_inputManager.IsKeyPressed(OvWindowing::Inputs::EKey::KEY_F))
{
MoveToTarget(EDITOR_EXEC(GetSelectedActor()));
}
auto focusObjectFromAngle = [this, &targetPos, &dist]( const OvMaths::FVector3& offset)
{
auto camPos = targetPos + offset * dist;
auto direction = OvMaths::FVector3::Normalize(targetPos - camPos);
m_cameraRotation = OvMaths::FQuaternion::LookAt(direction, abs(direction.y) == 1.0f ? OvMaths::FVector3::Right : OvMaths::FVector3::Up);
m_cameraDestinations.push({ camPos, m_cameraRotation });
};
if (m_inputManager.IsKeyPressed(OvWindowing::Inputs::EKey::KEY_UP)) focusObjectFromAngle(OvMaths::FVector3::Up);
if (m_inputManager.IsKeyPressed(OvWindowing::Inputs::EKey::KEY_DOWN)) focusObjectFromAngle(-OvMaths::FVector3::Up);
if (m_inputManager.IsKeyPressed(OvWindowing::Inputs::EKey::KEY_RIGHT)) focusObjectFromAngle(OvMaths::FVector3::Right);
if (m_inputManager.IsKeyPressed(OvWindowing::Inputs::EKey::KEY_LEFT)) focusObjectFromAngle(-OvMaths::FVector3::Right);
if (m_inputManager.IsKeyPressed(OvWindowing::Inputs::EKey::KEY_PAGE_UP)) focusObjectFromAngle(OvMaths::FVector3::Forward);
if (m_inputManager.IsKeyPressed(OvWindowing::Inputs::EKey::KEY_PAGE_DOWN)) focusObjectFromAngle(-OvMaths::FVector3::Forward);
}
}
}
if (!m_cameraDestinations.empty())
{
m_currentMovementSpeed = 0.0f;
while (m_cameraDestinations.size() != 1)
m_cameraDestinations.pop();
auto& [destPos, destRotation] = m_cameraDestinations.front();
float t = m_focusLerpCoefficient * p_deltaTime;
if (OvMaths::FVector3::Distance(m_cameraPosition, destPos) <= 0.03f)
{
m_cameraPosition = destPos;
m_cameraRotation = destRotation;
m_cameraDestinations.pop();
}
else
{
m_cameraPosition = OvMaths::FVector3::Lerp(m_cameraPosition, destPos, t);
m_cameraRotation = OvMaths::FQuaternion::Lerp(m_cameraRotation, destRotation, t);
}
}
else
{
if (m_rightMousePressed || m_middleMousePressed || m_leftMousePressed)
{
auto [xPos, yPos] = m_inputManager.GetMousePosition();
bool wasFirstMouse = m_firstMouse;
if (m_firstMouse)
{
m_lastMousePosX = xPos;
m_lastMousePosY = yPos;
m_firstMouse = false;
}
OvMaths::FVector2 mouseOffset
{
static_cast<float>(xPos - m_lastMousePosX),
static_cast<float>(m_lastMousePosY - yPos)
};
m_lastMousePosX = xPos;
m_lastMousePosY = yPos;
if (m_rightMousePressed)
{
HandleCameraFPSMouse(mouseOffset, wasFirstMouse);
}
else
{
if (m_middleMousePressed)
{
if (m_inputManager.GetKeyState(OvWindowing::Inputs::EKey::KEY_LEFT_ALT) == OvWindowing::Inputs::EKeyState::KEY_DOWN)
{
if (EDITOR_EXEC(IsAnyActorSelected()))
{
HandleCameraOrbit(mouseOffset, wasFirstMouse);
}
}
else
{
HandleCameraPanning(mouseOffset, wasFirstMouse);
}
}
}
}
if (m_view.IsHovered())
{
HandleCameraZoom();
}
HandleCameraFPSKeyboard(p_deltaTime);
}
}
与大部分3d软件的摄像机操控相同。
Update函数之后会调用AView的Update。
OvEditor::Core::CameraController& OvEditor::Panels::AViewControllable::GetCameraController()
{
return m_cameraController;
}
此函数用于获得m_cameraController。
本篇文章讲了AView相关的文件,也就是所有视图的基础,内容并不算多。之后会继续讲Panels文件夹内的文件。