// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Templates.h"
#include "Engine/Core/Types/BaseTypes.h"
#include "Engine/Core/Memory/Memory.h"
#include "Engine/Core/Math/Math.h"
///
/// Implementation of the rectangles packing into 2D atlas with padding. Uses simple space division.
///
template
struct RectPack
{
// Left and Right slots allow to easily move around the atlas like in a tree structure.
NodeType* Left;
NodeType* Right;
// Position of the entry in the atlas.
SizeType X;
SizeType Y;
// Size of the entry.
SizeType Width;
SizeType Height;
// True, if slot has been allocated, otherwise it's free.
bool IsUsed;
///
/// Initializes a new instance of the struct.
///
/// The x.
/// The y.
/// The width.
/// The height.
RectPack(SizeType x, SizeType y, SizeType width, SizeType height)
: Left(nullptr)
, Right(nullptr)
, X(x)
, Y(y)
, Width(width)
, Height(height)
, IsUsed(false)
{
}
///
/// Finalizes an instance of the class.
///
~RectPack()
{
if (Left)
Delete(Left);
if (Right)
Delete(Right);
}
///
/// Tries to insert an item into this node using rectangle pack algorithm.
///
/// The item width (in pixels).
/// The item height (in pixels).
/// The item padding margin (in pixels).
/// The additional arguments.
/// The node that contains inserted an item or null if failed to find a free space.
template
NodeType* Insert(SizeType itemWidth, SizeType itemHeight, SizeType itemPadding, Args&&...args)
{
NodeType* result;
const SizeType paddedWidth = itemWidth + itemPadding;
const SizeType paddedHeight = itemHeight + itemPadding;
// Check if we're free and just the right size
if (!IsUsed && Width == paddedWidth && Height == paddedHeight)
{
// Insert into this slot
IsUsed = true;
result = (NodeType*)this;
result->OnInsert(Forward(args)...);
return result;
}
// If there are left and right slots there are empty regions around this slot (it also means this slot is occupied)
if (Left || Right)
{
if (Left)
{
result = Left->Insert(itemWidth, itemHeight, itemPadding, Forward(args)...);
if (result)
return result;
}
if (Right)
{
result = Right->Insert(itemWidth, itemHeight, itemPadding, Forward(args)...);
if (result)
return result;
}
}
// This slot can't fit or has been already occupied
if (IsUsed || paddedWidth > Width || paddedHeight > Height)
{
// Not enough space
return nullptr;
}
// The width and height of the new child node
const SizeType remainingWidth = Math::Max(0, Width - paddedWidth);
const SizeType remainingHeight = Math::Max(0, Height - paddedHeight);
// Split the remaining area around this slot into two children
if (remainingHeight <= remainingWidth)
{
// Split vertically
Left = New(X, Y + paddedHeight, paddedWidth, remainingHeight);
Right = New(X + paddedWidth, Y, remainingWidth, Height);
}
else
{
// Split horizontally
Left = New(X + paddedWidth, Y, remainingWidth, paddedHeight);
Right = New(X, Y + paddedHeight, Width, remainingHeight);
}
// Shrink the slot to the actual area
Width = paddedWidth;
Height = paddedHeight;
// Insert into this slot
IsUsed = true;
result = (NodeType*)this;
result->OnInsert(Forward(args)...);
return result;
}
///
/// Frees the node.
///
/// The node that contains inserted an item or null if failed to find a free space.
template
void Free(Args&&...args)
{
ASSERT(IsUsed);
IsUsed = false;
((NodeType*)this)->OnFree(Forward(args)...);
}
};