저번 주에서 선택한 엔진에 싱글톤이 13개나 있어서 리팩토링을 단행했다.

위와 같은 구조를 아래로 변경하였다.

그래도 쓸만한 구조가 많았다. 특히 DX11 리소스와 외부 에셋 관리하는 부분에서 구조가 잘 짜여 있었다.
대부분 static으로 정의된 Find(FString) 함수를 통해 Pool에 있는 리소스를 찾아와 사용하는 방식이다.



Wavefront라는 회사에서 만든 3D 메쉬, 머터리얼을 표현하는 파일이다.
v 0.123 0.234 0.345 1.0
v ...
...
vt 0.500 1 [0]
vt ...
...
vn 0.707 0.000 0.707
vn ...
...
vp 0.310000 3.210000 2.100000
vp ...
...
f 1 2 3
f 3/1 4/2 5/3
f 6/4/1 3/5/3 7/6/5
f 7//1 8//2 9//3
f ...
...
l 5 8 1 2 4 9
한 줄에 키워드 하나와 여러 숫자가 들어가며 정보를 표시한다.
참고: wikipedia
OBJ 파일에 아래와 같이 작성되어 있으면 링크된 파일을 찾아 읽어들인다.
mtllib [external .mtl file name]

SWindow, SSplitter, SWorldWindow를 두어 트리 구조를 구성하여 무한 분할 다중 뷰포트를 구현하였다.

SWindow모든 SWindow는 parent를 가지며, Screen 좌표계의 Rect를 가져 마우스가 클릭했는지 안했는지를 확인할 수 있다.
class ACamera;
class SSplitter;
struct FViewportClient;
class SWindow {
public:
FRect Rect;
friend class JsonSaveHelper;
virtual bool IsHover(FVector2D coord) const {
return Rect.Contains(coord);
}
virtual void OnResizeUpdate();
virtual void Render() {}
virtual void OnMouseDown(FVector2D) {}
virtual void OnMouseUp(FVector2D) {}
virtual void OnMousePressed(FVector2D) {}
virtual void OnMouseReleased(FVector2D) {}
FVector2D GetNDCPosInWindow(FVector2D InCoord);
std::shared_ptr<SSplitter> child = nullptr;
std::shared_ptr<SWindow> parent = nullptr;
};
또한 외부에서 현재 선택한 SWindow가 어느 것인지를 판단하는 함수를 재귀적으로 호출한다.
std::shared_ptr<SWindow> UEditorManager::GetHoveringWindow(
const FVector& InMouseScreenPos,
const std::shared_ptr<SWindow> InSWindow
) {
if ( !InSWindow->IsHover(FVector2D(InMouseScreenPos.X, InMouseScreenPos.Y)) )
return nullptr;
if ( InSWindow->child == nullptr ) {
return InSWindow;
} else {
std::shared_ptr<SWindow> child = InSWindow->child;
std::shared_ptr<SWindow> childLT = InSWindow->child->SideLT;
std::shared_ptr<SWindow> childRB = InSWindow->child->SideRB;
std::shared_ptr<SWindow> resChild = GetHoveringWindow(InMouseScreenPos, child);
std::shared_ptr<SWindow> resLT = GetHoveringWindow(InMouseScreenPos, childLT);
std::shared_ptr<SWindow> resRB = GetHoveringWindow(InMouseScreenPos, childRB);
if ( resChild != nullptr )
return resChild;
else if ( resLT != nullptr )
return resLT;
else if ( resRB != nullptr )
return resRB;
else
return nullptr;
}
}
SWorldWindowSWindow를 상속받고 FViewportClient를 통해 월드를 렌더한다. 또한 자식이 없는 leaf 노드이다.
class SWorldWindow: public SWindow {
friend class JsonSaveHelper;
private:
FViewportClient* viewportClient;
public:
SWorldWindow() {};
SWorldWindow(FViewportClient* viewportClient);
~SWorldWindow();
inline FViewportClient* GetViewportClient() { return viewportClient; }
virtual void OnResizeUpdate() override;
virtual void OnMousePressed(FVector2D InCoord);
};
이때 FViewportClient는 아래와 같이 Camera와 Viewport만을 가진다.
struct FViewportClient {
ACamera* camera;
FViewport viewport;
FViewportClient(ACamera* camera, FViewport viewport) : camera(camera), viewport(viewport) {};
void PrepareRender();
};
SSplitterchild이 없으며 대신 SWindow 2개를 SideLT, SideRB로 가진다. 또한 SplitPos를 가져 SideLT와 SideRB의 크기 비율을 업데이트해 준다.
class SSplitter : public SWindow {
protected:
bool bIsDragging = false;
public:
std::shared_ptr<SWindow> SideLT; // Left/Top 영역
std::shared_ptr<SWindow> SideRB; // Right/Bottom 영역
float SplitPos = 0.5f; // 분할 비율 (0.0~1.0)
virtual void OnResize() = 0;
virtual FRect GetSplitterRect() const = 0;
bool IsHover(FVector2D coord) const override {
FRect splitterRect = GetSplitterRect();
return splitterRect.Contains(coord);
}
void OnMousePressed(FVector2D coord) override {
if (IsHover(coord)) bIsDragging = true;
}
void OnMouseReleased(FVector2D coord) override {
bIsDragging = false;
}
};
class SSplitterH : public SSplitter { // 가로 분할
friend class JsonSaveHelper;
private:
float SplitterWidth;
public:
SSplitterH() {
SplitterWidth = 5.0f;
};
SSplitterH(std::shared_ptr<SWindow> Left, std::shared_ptr<SWindow> Right) {
SideLT = Left;
SideRB = Right;
SplitterWidth = 5.0f;
}
void OnResize() override {
const float splitX = Rect.Width() * SplitPos;
SideLT->Rect = FRect(Rect.Left, Rect.Top, splitX, Rect.Height());
SideRB->Rect = FRect(Rect.Left + splitX, Rect.Top,
Rect.Width(), Rect.Height());
SideLT->OnResizeUpdate();
SideRB->OnResizeUpdate();
}
virtual void OnResizeUpdate() override;
void OnMouseDown(FVector2D coord) override;
private:
FRect GetSplitterRect() const {
return Rect;
}
};
void FObjArchive::ObjToBinary(const FString& filePath, const TArray<FVertexTextureArray> vertices,
const TArray<uint32> indices, TArray<uint32> intervals, const TArray<FTextureCount>& textureCounts,
const TArray<FSubMesh>& subMeshes)
{
std::ofstream objBinary;
objBinary.open(filePath.ToWideString(), std::ios::binary);
if (!objBinary) {
MsgBoxAssert("can't open file");
return;
}
// 정점 개수 저장 (uint32)
uint32 vertexCount = vertices.Num();
objBinary.write(reinterpret_cast<const char*>(&vertexCount), sizeof(uint32));
// 인덱스 개수 저장 (uint32)
uint32 indexCount = indices.Num();
objBinary.write(reinterpret_cast<const char*>(&indexCount), sizeof(uint32));
// 간격 데이터 개수 저장 (uint32)
uint32 intervalCount = intervals.Num();
objBinary.write(reinterpret_cast<const char*>(&intervalCount), sizeof(uint32));
...
reinterpret_cast<const char*>(&struct)를 이용해 파싱한 Obj 파일을 Binary로 저장하고 불러들일 수 있었다. 처음 로딩하는 시간이 조금 덜 걸리게 되었다.
static_assert(false, ...);환경마다 다르게 동작하는 듯하다. 조금 찾아보니 MSVC에서 static_assert(false, ...)가 나오면 맥락 상관없이 컴파일 에러를 때려버린다는 듯하다. 실제로는 if constexpr를 사용해서 구분하는데도 말이다.
static CharType* Strcpy(CharType* dest, const CharType* src)
{
if constexpr (std::is_same_v<CharType, char>)
{
return std::strcpy(dest, src);
}
else if constexpr (std::is_same_v<CharType, wchar_t>)
{
return std::wcscpy(dest, src);
}
else
{
static_assert(false, "Unsupported character type!");
return nullptr;
}
}
false 대신 sizeof(Type) == 0 처럼 한 단계 늦게 평가되는 식을 쓰면 무사히 컴파일된다.
else
{
static_assert(sizeof(CharType) == 0, "Unsupported character type!");
return nullptr;
}