Bounding Boxes

Discover bounding boxes: what they are, why we use them, and how to create them
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Get Started for Free
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Posted

So far, our game objects exist only as single points (Vec2 Position). While great for movement, it's not enough for interactions like checking if a projectile hits a character.

This lesson tackles that by introducing bounding boxes – simple rectangular shapes that represent the space an object occupies. We'll focus on Axis-Aligned Bounding Boxes (AABBs), implement a BoundingBox class using SDL_FRect, add it to our GameObject, and learn how to draw these boxes for debugging purposes.

In the next lesson, we’ll learn how to use these bounding boxes to detect when they intersect, and some practical reasons why that is useful.

Finally, we’ll end the chapter by revisiting our physics system and use our new bounding boxes to let our objects physically interact with each other.

Bounding Boxes / Volumes

Let’s imagine we want to add an enemy to our game that can fire projectiles that our player needs to dodge. When our player’s character is represented by a simple point in space, we can’t easily determine if a projectile hit them:

Image showing our dwarf character being hit by a fireball

Unfortunately, detecting which objects in our scene are colliding with each other is not a something we can do directly. in real-world games, each object can have a complex shape comprising thousands of pixels (or vertices, in a 3D game) and our scene may have thousands of objects. Understanding how all of these complex shapes might be interacting on every frame is unreasonably expensive.

To help with this, we use bounding volumes. These are much simpler shapes that "bound" (or contain) all the components of a more complex object. They’re not visible to players but, behind the scenes, we use them to understand how the objects in our scene are interacting.

The most common bounding volume we use is a bounding box, which is a simple rectangle in 2D, or a cuboid in 3D:

Image showing examples of bounding boxes and volumes in games
A bounding box rendered in Unreal Engine

Now, to understand if our player was hit by a fireball, we just need to do the much simpler calculation of checking whether two rectangles are overlapping:

Image showing our scene with bounding boxes

High-Accuracy Checks

Quite often, a game needs to check if something hits the actual, complex shape of an object. Common examples include a lighting calculation to understand how an object should be rendered, or a competitive shooter where we want to check if a bullet hit the player, not just their bounding box.

Even in these scenarios, bounding volumes are used as a performance optimization. Our scene can have thousands of objects and any given ray of light or bullet will hit maybe one of them. We can use our bounding volumes to quickly exclude objects from consideration because, if something didn’t hit a bounding volume, we know it didn’t hit anything within that volume either.

More complex projects take this idea further and create bounding volume hierarchies (BVHs) where a bounding volume contains smaller, more accurate volumes, allowing this culling process to be done multiple times.

Axis-Aligned Bounding Boxes (AABBs)

In 2D games, bounding boxes are simple rectangles. Axis-aligned bounding boxes, or AABBs, are boxes whose edges are parallel to the axes of our space. More simply, we can think of AABBs as being rectangles that have not been rotated:

Diagram showing examples of two bounding boxes - one axis-aligned, and one not

A bounding box that can be rotated is typically called an oriented bounding box, or OBB.

AABBs are friendlier and faster to work with as they simplify a lot of the calculations required to determine things like whether two bounding boxes intersect.

AABBs are also easier and more memory-efficient to represent, requiring only four scalar values in 2D, or six in 3D.

We’re working with 2D for now, and there are two main ways to represent an AABB in two dimensions:

  • The top-left corner of the box, its width, and its height. These values are typically labelled xx, yy, ww, and hh.
  • The top-left corner and bottom-right corner of the box. These use values typically labelled x1x_1, y1y_1, x2x_2, and y2y_2.
Diagram showing the two ways of representing axis-aligned bounding boxes

As we’ve seen, SDL_Rect uses the first of these conventions, with members called xx, yy, ww, and hh. Often, we need to use both conventions within the same program, but it’s relatively easy to convert one to the other.

We can calculate the bottom right corner (x2,y2x_2, y_2) of an SDL_Rect through addition:

x2=x+wy2=y+h x_2 = x + w \\ y_2 = y + h \\

If we have a box defined by x1x_1, y1y_1, x2x_2, and y2y_2 values, we can calculate its width and height through subtraction:

w=x2x1h=y2y1 w = x_2 - x_1 \\ h = y_2 - y_1 \\

Implementing AABBs

Our bounding boxes are defined in world space, so we’ll represent them using an SDL_FRect, which works in the same way as an SDL_Rect except that the x, y, w, and h values are stored as floating point numbers instead of integers:

// BoundingBox.h
#pragma once
#include <SDL.h>

class BoundingBox {
public:
  BoundingBox(const SDL_FRect& InitialRect)
  : Rect{InitialRect} {}

private:
  SDL_FRect Rect;
};

We’ll also add a SetPosition() method to move our bounding box by setting the x and y values of the SDL_FRect:

// BoundingBox.h
#pragma once
#include <SDL.h>
#include "Vec2.h"

class BoundingBox {
public:
  BoundingBox(const SDL_FRect& InitialRect)
  : Rect{InitialRect} {}

  void SetPosition(const Vec2& Position) {
    Rect.x = Position.x;
    Rect.y = Position.y;
  }

private:
  SDL_FRect Rect;
};

Let’s update our GameObject class to give each object a BoundingBox. Our GameObject instances already have a Position variable that can be used to set the top-left corner of the bounding box. We also need to specify its width and height, so let’s add those as constructor parameters:

// GameObject.h
// ...
#include "BoundingBox.h"
// ...

class GameObject {
 public:
  GameObject(
    const std::string& ImagePath,
    const Vec2& InitialPosition,
    float Width,
    float Height,
    const Scene& Scene
  ) : Image{ImagePath},
      Position{InitialPosition},
      Scene{Scene},
      Bounds{SDL_FRect{
        InitialPosition.x, InitialPosition.y,
        Width, Height
      }}
    {}
    
  // ...
  
 private:
  // ...
  BoundingBox Bounds;
};

Once we’ve calculated our object’s new Position at the end of each Tick() function, we’ll notify our bounding box:

// GameObject.h
// ...

class GameObject {
 public:
  // ...
  void Tick(float DeltaTime) {
    ApplyForce(FrictionForce(DeltaTime));
    ApplyForce(DragForce());
    Velocity += Acceleration * DeltaTime;
    Position += Velocity * DeltaTime;

    Acceleration = {0, -9.8};

    // Don't fall through the floor
    if (Position.y < 2) {
      Position.y = 2;
      Velocity.y = 0;
    }
    Bounds.SetPosition(Position); 
    Clamp(Velocity);
  }
  // ...
};

Finally, let’s update our scene to include the width and height of our objects, bearing in mind that our world space uses meters as its unit of distance.

In this case, the 1.9 and 1.7 values were chosen based on the size of the image we’re using to represent our character:

// Scene.h
// ...

class Scene {
public:
  Scene() {
    Objects.emplace_back(
      "dwarf.png", Vec2{6, 2}, 1.9, 1.7, *this);
  }
  // ...
};

Drawing Bounding Boxes

Normally, bounding boxes are not rendered to the screen but, when developing a complex game, it’s useful to have that capability to help us understand what’s going on.

To enable this, we need to be able to convert our world space bounding box to an equivalent screen space version. Our Scene class already has a ToScreenSpace() function for converting Vec2 objects from world space to screen space. We’ll overload this function with a variation that works with SDL_FRect objects.

To convert the rectangle's top-left corner (x, y), we can use our existing ToScreenSpace(Vec2) function. For the width (w) and height (h), we only need to scale them by the horizontal and vertical scaling factors, respectively:

// Scene.h
// ...

class Scene {
public:
  // ...
Vec2 ToScreenSpace(Vec2& Pos) {/*...*/} SDL_FRect ToScreenSpace(const SDL_FRect& Rect) const { Vec2 ScreenPos{ToScreenSpace(Vec2{Rect.x, Rect.y})}; float HorizontalScaling{Viewport.w / WorldSpaceWidth}; float VerticalScaling{Viewport.h / WorldSpaceHeight}; return { ScreenPos.x, ScreenPos.y, Rect.w * HorizontalScaling, Rect.h * VerticalScaling }; } // ... };

Next, we’ll add a Render() function to our BoundingBox class. We’ll need to access our Scene for the ToScreenSpace() function we just created.

We need access to the Scene object within our bounding box’s Render() method, but we can't #include the Scene header file directly in BoundingBox.h. Doing so would create a circular dependency because Scene.h includes GameObject.h, which in turn includes BoundingBox.h.

To resolve this, we can forward-declare Scene in BoundingBox.h. We then include "Scene.h" in a new implementation file - BoundingBox.cpp - where the Render() method is actually defined and needs the full Scene definition.

// BoundingBox.h
// ...

class Scene; 
class BoundingBox {
public:
  // ...

 void Render(
   SDL_Surface* Surface, const Scene& Scene);

  // ...
};
// BoundingBox.cpp
#include <SDL.h>
#include "Scene.h"
#include "BoundingBox.h"

void BoundingBox::Render(
  SDL_Surface* Surface, const Scene& Scene
) {
  // ...
}

In addition to converting our bounding box’s SDL_FRect to screen space, we also need to round its x, y, w, and h floating point numbers to integers to specify which pixels they occupy on the SDL_Surface.

To do this rounding, we’ll create a function for converting SDL_FRect objects to SDL_Rects.

This rounding conversion is a general utility function. Often, you'd place such utilities in a separate header file. For simplicity in this lesson, we'll add it directly to our BoundingBox class:

// BoundingBox.h
// ...

class BoundingBox {
public:
  // ..
  static SDL_Rect Round(const SDL_FRect& Rect) {
    return {
      static_cast<int>(std::round(Rect.x)),
      static_cast<int>(std::round(Rect.y)),
      static_cast<int>(std::round(Rect.w)),
      static_cast<int>(std::round(Rect.h))
    };
  }
  // ...
};

Since the Round() function doesn't need access to any specific BoundingBox object's data (like Rect), we can declare it as static.

This makes it usable even in situations where we don’t have a bounding box instance - we can invoke it using BoundingBox::Round() in those scenarios.

We can now combine our ToScreenSpace() and Round() functions to get our bounding box in screen space, and with its components as rounded int values:

// BoundingBox.cpp
// ...

void BoundingBox::Render(
  SDL_Surface* Surface, const Scene& Scene
) {
  auto [x, y, w, h]{
    Round(Scene.ToScreenSpace(Rect))};
    
  // ...
};

Unlike previous examples, we don’t want the full area of our rectangle to be filled with a solid color. Instead, we just want to draw it as a rectangular border:

Diagram showing our desired output

To do this, we can draw four lines - one for each of the top, bottom, left, and right edges. To draw each line, we can simply render a thin rectangle.

For example, our top border will start at the top left of our bounding box and span its full width. As such, it will share its x, y, and w values. However, the height (h) of this rectangle will be much shorter, representing the thickness of our line.

Let’s set that up:

// BoundingBox.cpp
// ...

void BoundingBox::Render(
  SDL_Surface* Surface, const Scene& Scene
) {
  auto [x, y, w, h]{
    Round(Scene.ToScreenSpace(Rect))};
    
  int LineWidth{4};
  SDL_Rect Top{x, y, w, LineWidth};
  // ...
};

We can draw the rectangle representing our line in the usual way, passing the surface, rectangle, and color to SDL_FillRect():

// BoundingBox.cpp
// ...

void BoundingBox::Render(
  SDL_Surface* Surface, const Scene& Scene
) {
  auto [x, y, w, h]{
    Round(Scene.ToScreenSpace(Rect))};
    
  int LineWidth{4};
  SDL_Rect Top{x, y, w, LineWidth};
    
  Uint32 LineColor{SDL_MapRGB(
    Surface->format, 220, 0, 0)};  
  SDL_FillRect(Surface, &Top, LineColor); 
}

Let’s draw our other edges by following the same logic. Because each SDL_Rect defines the top left corner of where the rectangle will be drawn, the right edge will draw slightly outside of our bounding box if we don’t intervene.

To fix this, we move it left by reducing its x value based on our LineWidth:

Diagram showing why we’re moving our right edge to the left

The bottom edge will have a similar problem where it is rendered below our bounding box, so we need to move it up by the LineWidth value. Because we’re working with screen space values at this point in our function, moving up corresponds to reducing the y value.

Putting everything together looks like this:

// BoundingBox.cpp
// ...

void BoundingBox::Render(
  SDL_Surface* Surface, const Scene& Scene
) {
  auto [x, y, w, h]{
    Round(Scene.ToScreenSpace(Rect))};
    
  int LineWidth{4};
  Uint32 LineColor{SDL_MapRGB(
    Surface->format, 220, 0, 0)};
    
  SDL_Rect Top{x, y, w, LineWidth};
  SDL_Rect Left{x, y, LineWidth, h};
  SDL_Rect Bottom{
    x, y + h - LineWidth, w, LineWidth};
  SDL_Rect Right{
    x + w - LineWidth, y, LineWidth, h};

  SDL_FillRect(Surface, &Top, LineColor);
  SDL_FillRect(Surface, &Left, LineColor);
  SDL_FillRect(Surface, &Bottom, LineColor);
  SDL_FillRect(Surface, &Right, LineColor);
}

Finally, we need to update our GameObject class to call the Render() method on our bounding box. We only want the bounding boxes to be drawn when we’re debugging our program, so this is a scenario where we might want to use a preprocessor directive:

// GameObject.cpp
// ...
#include "BoundingBox.h" 

#define DRAW_BOUNDING_BOXES 

void GameObject::Render(SDL_Surface* Surface) {
  Image.Render(
    Surface, Scene.ToScreenSpace(Position)
  );
#ifdef DRAW_BOUNDING_BOXES 
  Bounds.Render(Surface, Scene); 
#endif 
}

// ...

If we run our program, we should now see our character’s bounding box drawn, and its position updates as our character moves:

Screenshot showing our character moving with their bounding box

Note that our screenshots include additional trajectory lines to show how objects move. The code to render these lines is included in the GameObject.cpp file below for those interested.

Complete Code

A complete version of our BoundingBox class is included below. We have also provided the GameObject and Scene classes with the code we added in this lesson highlighted:

#pragma once
#include <SDL.h>
#include "Vec2.h"

class Scene;
class BoundingBox {
public:
  BoundingBox(const SDL_FRect& InitialRect)
  : Rect{InitialRect} {}

  void SetPosition(const Vec2& Position) {
    Rect.x = Position.x;
    Rect.y = Position.y;
  }

  static SDL_Rect Round(const SDL_FRect& Rect) {
    return {
      static_cast<int>(std::round(Rect.x)),
      static_cast<int>(std::round(Rect.y)),
      static_cast<int>(std::round(Rect.w)),
      static_cast<int>(std::round(Rect.h))
    };
  }

 void Render(
   SDL_Surface* Surface, const Scene& Scene
 );

private:
  SDL_FRect Rect;
};
#include <SDL.h>
#include "Scene.h"
#include "BoundingBox.h"

void BoundingBox::Render(
  SDL_Surface* Surface, const Scene& Scene
) {
  int LineWidth{4};
  Uint32 LineColor{
    SDL_MapRGB(Surface->format, 220, 0, 0)};

  auto [x, y, w, h]{
    Round(Scene.ToScreenSpace(Rect))};
  SDL_Rect Top{x, y, w, LineWidth};
  SDL_Rect Bottom{
    x, y + h - LineWidth, w, LineWidth};
  SDL_Rect Left{x, y, LineWidth, h};
  SDL_Rect Right{
    x + w - LineWidth, y, LineWidth, h};

  SDL_FillRect(Surface, &Top, LineColor);
  SDL_FillRect(Surface, &Bottom, LineColor);
  SDL_FillRect(Surface, &Left, LineColor);
  SDL_FillRect(Surface, &Right, LineColor);
}
#pragma once
#include <SDL.h>
#include <vector>
#include "GameObject.h"

class Scene {
public:
  Scene() {
    Objects.emplace_back(
      "dwarf.png", Vec2{6, 2}, 1.9, 1.7, *this);
  }

  Vec2 ToScreenSpace(const Vec2& Pos) const {
    auto [vx, vy, vw, vh]{Viewport};
    float HorizontalScaling{vw / WorldSpaceWidth};
    float VerticalScaling{vh / WorldSpaceHeight};

    return {
      vx + Pos.x * HorizontalScaling,
      vy + (WorldSpaceHeight - Pos.y)
        * VerticalScaling
    };
  }

  SDL_FRect ToScreenSpace(const SDL_FRect& Rect) const {
    Vec2 ScreenPos{ToScreenSpace(Vec2{Rect.x, Rect.y})};
    float HorizontalScaling{Viewport.w / WorldSpaceWidth};
    float VerticalScaling{Viewport.h / WorldSpaceHeight};

    return {
      ScreenPos.x,
      ScreenPos.y,
      Rect.w * HorizontalScaling,
      Rect.h * VerticalScaling
    };
  }

  Vec2 ToWorldSpace(const Vec2& Pos) const {
    auto [vx, vy, vw, vh]{Viewport};
    float HorizontalScaling{WorldSpaceWidth / vw};
    float VerticalScaling{WorldSpaceHeight / vh};

    return {
      (Pos.x - vx) * HorizontalScaling,
      WorldSpaceHeight - (Pos.y - vy)
        * VerticalScaling
    };
  }

  void HandleEvent(SDL_Event& E) {
    for (GameObject& Object : Objects) {
      Object.HandleEvent(E);
    }
  }

  void Tick(float DeltaTime) {
    for (GameObject& Object : Objects) {
      Object.Tick(DeltaTime);
    }
  }

  void Render(SDL_Surface* Surface) {
    SDL_GetClipRect(Surface, &Viewport);
    for (GameObject& Object : Objects) {
      Object.Render(Surface);
    }
  }

private:
  SDL_Rect Viewport;
  std::vector<GameObject> Objects;
  float WorldSpaceWidth{14}; // meters
  float WorldSpaceHeight{6}; // meters
};
// GameObject.h
#pragma once
#include <SDL.h>

#include "BoundingBox.h"
#include "Vec2.h"
#include "Image.h"

class Scene;

class GameObject {
 public:
  GameObject(
    const std::string& ImagePath,
    const Vec2& InitialPosition,
    float Width,
    float Height,
    const Scene& Scene
  ) : Image{ImagePath},
      Position{InitialPosition},
      Scene{Scene},
      Bounds{SDL_FRect{
        InitialPosition.x, InitialPosition.y,
        Width, Height
      }}
    {}

  void HandleEvent(const SDL_Event& E);

  void Tick(float DeltaTime) {
    ApplyForce(FrictionForce(DeltaTime));
    ApplyForce(DragForce());
    Velocity += Acceleration * DeltaTime;
    Position += Velocity * DeltaTime;

    Acceleration = {0, -9.8};

    // Don't fall through the floor
    if (Position.y < 2) {
      Position.y = 2;
      Velocity.y = 0;
    }
    Bounds.SetPosition(Position);
    Clamp(Velocity);
  }

  void Render(SDL_Surface* Surface);

  void ApplyForce(const Vec2& Force) {
    Acceleration += Force / Mass;
  }

 private:
  Image Image;
  const Scene& Scene;

  Vec2 Position{0, 0};
  Vec2 Velocity{0, 0};
  Vec2 Acceleration{0, -9.8};
  float Mass{70};
  BoundingBox Bounds;

  float DragCoefficient{0.2};
  Vec2 DragForce() const {
    return -Velocity * DragCoefficient
      * Velocity.GetLength();
  }

  float GetFrictionCoefficient() const {
    if (Position.y > 2) {
      return 0;
    }

    return 0.5;
  }
  
  Vec2 FrictionForce(float DeltaTime) const {
    float MaxMagnitude{GetFrictionCoefficient()
      * Mass * -Acceleration.y};
    if (MaxMagnitude <= 0) return Vec2(0, 0);

    float StoppingMagnitude{Mass *
      Velocity.GetLength() / DeltaTime};

    return -Velocity.Normalize() * std::min(
      MaxMagnitude, StoppingMagnitude);
  }

  void Clamp(Vec2& V) const {
    V.x = std::abs(V.x) > 0.01 ? V.x : 0;
    V.y = std::abs(V.y) > 0.01 ? V.y : 0;
  }

  void ApplyImpulse(const Vec2& Impulse) {
    Velocity += Impulse / Mass;
  }

  void ApplyPositionalImpulse(
    const Vec2& Origin, float Magnitude
  ) {
    Vec2  Displacement{Position - Origin};
    Vec2  Direction{Displacement.Normalize()};
    float Distance{Displacement.GetLength()};
  
    // Apply inverse-square law with a small
    // offset to prevent extreme forces
    float AdjustedMagnitude{Magnitude /
      ((Distance + 0.1f) * (Distance + 0.1f))};
  
    ApplyImpulse(Direction * AdjustedMagnitude);
  }
};
#include <SDL.h>
#include "GameObject.h"
#include "Scene.h"
#include "BoundingBox.h"

#define DRAW_BOUNDING_BOXES
#define DRAW_TRAJECTORIES

#ifdef DRAW_TRAJECTORIES
namespace{
SDL_Surface* Trajectories{
  SDL_CreateRGBSurfaceWithFormat(
    0, 700, 300, 32,
    SDL_PIXELFORMAT_RGBA32
  )};
}
#endif

void GameObject::Render(SDL_Surface* Surface) {
#ifdef DRAW_TRAJECTORIES
  auto [x, y]{Scene.ToScreenSpace(Position)};
  SDL_Rect PositionIndicator{
    int(x)-16, int(y), 20, 20};
  SDL_FillRect(
    Trajectories, &PositionIndicator,
    SDL_MapRGB(Trajectories->format, 220, 0, 0)
  );
                   
  SDL_BlitSurface(
    Trajectories, nullptr, Surface, nullptr
  );
#endif
  Image.Render(Surface, Scene.ToScreenSpace(Position));
#ifdef DRAW_BOUNDING_BOXES
  Bounds.Render(Surface, Scene);
#endif
}

void GameObject::HandleEvent(const SDL_Event& E) {
  if (E.type == SDL_MOUSEBUTTONDOWN) {
    // Create explosion at click position
    if (E.button.button == SDL_BUTTON_LEFT) {
      ApplyPositionalImpulse(
        Scene.ToWorldSpace({
          static_cast<float>(E.button.x),
          static_cast<float>(E.button.y)
      }), 1000);
    }
  } else if (E.type == SDL_KEYDOWN) {
    // Jump
    if (E.key.keysym.sym == SDLK_SPACE) {
      if (Position.y > 2) return;
      ApplyImpulse({0.0f, 300.0f});
    }
  }
}

Summary

In this lesson, we introduced axis-aligned bounding boxes (AABBs) as a simple way to represent the space occupied by game objects. We created a BoundingBox class using SDL_FRect to store its position and dimensions in world space, integrated it into our GameObject, and updated the bounding box's position each frame.

We also implemented a rendering function to draw the bounding box outlines for debugging, requiring coordinate space conversion and rounding. Key Takeaways:

  • Bounding Boxes: Simple shapes (like rectangles) used to approximate complex object boundaries for efficient interaction checks.
  • AABBs: Axis-Aligned Bounding Boxes; rectangles that aren't rotated, simplifying calculations.
  • Representation: AABBs can be defined by top-left corner and dimensions (x, y, w, h) or by two opposing corners. SDL_FRect uses the former with floats.
  • Implementation: We created a BoundingBox class storing an SDL_FRect and added it to GameObject.
  • Updating: The bounding box position must be updated whenever the GameObject's position changes.
  • Rendering: Drawing bounding boxes involves converting world coordinates to screen coordinates and rounding to integer pixel values (from SDL_FRect to SDL_Rect) before drawing.
Free and Unlimited Access

Professional C++

Unlock the true power of C++ by mastering complex features, optimizing performance, and learning expert workflows used in professional development

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Ryan McCombe
Ryan McCombe
Posted
Lesson Contents

Bounding Boxes

Discover bounding boxes: what they are, why we use them, and how to create them

sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Get Started for Free
Motion and Collisions
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

This course includes:

  • 99 Lessons
  • 92% Positive Reviews
  • Regularly Updated
  • Help and FAQs
Free and Unlimited Access

Professional C++

Unlock the true power of C++ by mastering complex features, optimizing performance, and learning expert workflows used in professional development

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2025 - All Rights Reserved