// 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)...); } };