top of page

Physics System

This is a physics static library I developed for the Cross-Platform Game Engine. It then also got integrated into my game AI project on openFrameworks.

Specs
Date

C++

Static Library

Collision Detection

11/2023

Details

  • Independently developed a physics static library used in my Cross-Platform Game Engine, ensuring performance and modularity and attracting multiple fellow classmates to use it in their own game engines for game development.


  • Designed and implemented an innovative three-phase collision detection system (Bounding Sphere Detection, AABB Detection, and SAT Detection) for rotated box colliders to maximize performance.


  • Seamlessly integrated the system to my Game AI Project that was built in openFrameworks to manage movement and collision.


  • Researched and selected std::unordered_set to manage colliding colliders, making the data queries constant time complexity.



What Is This:

This is an individually developed physics system static library project, built on a C++ game engine that I developed over the course of the semester. One of the primary goals is to ensure modularity, enabling my peers, who have developed similar C++ game engines, to use it in their own game development.


The system has two main parts: Rigid body and collisions.


The Rigid Body component includes basic features such as velocity, acceleration, and angular velocity. Additionally, I’ve incorporated features commonly found in game engines, such as mass, gravity, and rotation constraints. Notably, net force is used as the primary method for determining acceleration, and consequently, velocity.


As demonstrated in the video, the controlled cube has gravity enabled, unlike the other two cubes. The larger cube is more difficult for the smaller cube to push due to its significantly greater mass.


The colliders are much more complex. The main representation of the colliders is a Collider class, which is an abstract class served as the base class for the SphereCollider class and BoxCollider class. It has features including center offset, bounciness, friction coefficient, and overlap capability.


In the demonstration video, the floor exhibits some bounciness, causing the small cube to bounce slightly upon landing. The large cube, with a bounciness of zero, prevents the small cube from bouncing off completely. The floor has a significant amount of friction, while the large cube has none, causing the small cube to move slower and stop quicker on the floor compared to the large cube. The spinning cube on the left has its isTrigger property set to true, allowing the small cube to overlap with it rather than being repelled.


The SphereCollider class, in addition to the features inherited from the Collider base class, has an additional property - radius. The BoxCollider class also has an additional property - extents, a vector representing the half-size of the box collider along each axis.


Implementing collision detection was challenging, but I successfully managed it. In addition to sphere-sphere and sphere-box collisions, I implemented box-box collisions for both axis-aligned and rotating boxes. To optimize performance for box-box collision detections, I designed a three-phase detection logic, which will be further discussed in the Details section.


Upon collision detection, two actions are triggered: Collision Callbacks and Collision Handling.


Collision Callbacks are common functions used in game engines like Unity and Unreal Engine, including OnCollisionEnter(), OnCollisionExit(), and OnCollisionStay().


Collision Handling ensures that if both colliders are not triggers (i.e., they can overlap), they will repel each other to prevent overlap.


As shown in the demonstration video, the floor prevented the little cube to fall. And the reason why it did not get budged is because it is a static object, an object that does not have a rigid body but can still possess a collider. The rotating cube is a trigger, allowing the small cube to overlap with it. When they begin to collide, the OnCollisionEnter() function is called, changing the color of the small cube. Conversely, when they separate, the OnCollisionExit() function is called, restoring the color of the small cube.


Details:

I am very pleased with the extensive knowledge I have gained in both system design and C++ throughout the process of developing the game engine and my physics system.


Performance optimization is a constant consideration for me. For instance, the three-phase collision detection I designed has notably enhanced the performance of box collision detections. Let me explain why.


Typically, box collision detection is designed as a two-phase process:

  1. Broad Phase Detection: This is the initial phase where potential collisions are identified. It is a quick and efficient way to rule out objects that are far apart and unlikely to collide.

  2. SAT (Separating Axis Theorem) Detection: This is the final phase where precise collision detection occurs. The SAT is a method for determining whether two convex shapes intersect. It is much more computationally expensive than the broad phase detection, so it is only used once the broad phase has identified potential collisions.


However, in my design, I have implemented a three-phase detection process to optimize performance:

  1. Bounding Sphere Detection: This is the first phase where a simple sphere is used to encompass the object. Any object that does not intersect with this sphere is quickly eliminated from further checks. This is a fast operation because checking sphere-sphere intersection is computationally inexpensive.

  2. AABB (Axis-Aligned Bounding Box) Detection: In the second phase, an axis-aligned bounding box, which is a box that aligns with the coordinate axes, is used to encompass the object. It is more precise than the bounding sphere detection but still less computationally expensive than the SAT detection.

  3. SAT (Separating Axis Theorem) Detection: This is the final phase, just like in the traditional two-phase process. At this stage, precise collision detection occurs.

By adding an extra phase (AABB Detection) between the broad phase (Bounding Sphere Detection) and the narrow phase (SAT Detection), I have managed to rule out more potential non-collisions before the computationally expensive SAT detection. This results in a significant improvement in performance for box collision detections.


Further more, I made a strategic choice to use std::unordered_set instead of std::vector for maintaining a list of colliders currently in collision with. This decision was also driven by a focus on performance optimization.


The std::unordered_set, which stores unique elements based on their hash values, provides average constant time complexity for search, insert, and delete operations. This is particularly beneficial in a physics system where collider order is not important, but efficiency is. By employing std::unordered_set, the system ensures efficient checks for existing collisions, registration of new collisions, or removal of existing ones. This choice is especially advantageous when dealing with a large number of collisions, highlighting the importance of selecting appropriate data structures for optimizing performance. Throughout this semester, our instructor consistently emphasized one point: Never underestimate the performance differences that can arise from various design choices. This was a principle I kept in mind. I now always imagine what will happen if the scale of the data becomes larger and larger in the future.


Regarding modularity, I initially had reservations about my physics system needing to be aware of the game object class. In an ideal world, a fully modular physics system should not need to “know” anything that is not directly related to physics. However, the reality is that a physics system is intrinsically tied to the game object. This realization caused me some struggle. After discussing with my instructor, I learned that this is a common design due to the close relationship between the two. He suggested some ideas to avoid making my physics system aware of the game object class if I really wanted to achieve complete modularity. However, he also acknowledged that these changes would make my users miserable. Therefore, after careful consideration, I decided to maintain the current design. This decision ensures that users can use the rigid bodies and colliders in a manner similar to common game engines like Unity and Unreal Engine.


In conclusion, the development of this physics system has been a journey of learning and growth. It has provided valuable insights into the intricacies of system design, performance optimization, and the importance of user experience. The challenges encountered along the way have only served to deepen the understanding of these complex concepts. The end result is a physics system that’s not just modular and efficient, but also user-friendly, much like what you would see in common game engines like Unity and Unreal Engine. I am very glad that I managed to finish it in such a limited amount of time.


How To Use:

  • Download my CZPhysics static library project

    • Unzip it

    • Copy the CZPhysics folder into your Engine/ folder

  • Add my project into your solution

    • Right-click the Engine folder in Solution Explorer and choose Add->Existing Project...

    • Navigate into the CZPhysics folder in your Engine/

    • Double-click the CZPhysics.vcxproj to add it into your Engine folder in your solution

    • If anything went wrong, you may delete the CZPhysics.vcxproj and CZPhysics.vcxproj.filters in the CZPhysics folder and create a new CZPhysics project for you solution on your own by following the Assignment 01 description – Graphics Library section

  • Make sure the property sheets for the new project are added

    • If you are not sure how to do this review the Solution Setup Example

    • Remember that the order that property sheets are listed matters! Add EngineDefaults first to the entire project, and then add OpenGL to the Win32 configurations and Direct3D to the x64 configurations

  • Add CZPhysics project as a reference to your game project

  • Call ChrisZ::Physics::Update() along with calling your other updates

    • You should add #include <Engine/CZPhysics/CZPhysics.h> in that file

    • For example, I call it in the UpdateSimulationBasedOnTime()

  • Modify your game object class

    • You should add #include <Engine/CZPhysics/CZPhysics.h> in that file

    • Add virtual collision callback methods to be overridden in your child classes:

      • virtual void OnCollisionEnter(ChrisZ::Physics::Collider* other) {}

      • virtual void OnCollisionExit(ChrisZ::Physics::Collider* other) {}

      • virtual void OnCollisionStay(ChrisZ::Physics::Collider* other) {}

    • Implement the following methods:

      • Math::sVector GetPosition() const;

      • void SetPosition(const Math::sVector& i_position);

      • Math::cQuaternion GetOrientation() const;

      • void SetOrientation(const Math::cQuaternion& i_orientation);

    • Add a pointer to the ChrisZ::Physics::RigidBody

    • Add a pointer to the ChrisZ::Physics::Collider

    • Implement the following methods:

      • inline ChrisZ::Physics::RigidBody* GetRigidBody();

      • inline ChrisZ::Physics::Collider* GetCollider();

    • Change how you get the future transform predictions

      • Call the PredictFutureTransform() from the rigid body if this game object has a valid rigid body (i.e., a movable game object)

      • Use eae6320::Math::cMatrix_transformation(this->GetOrientation(), this->GetPosition()) if this game object does not own a valid rigid body (i.e., a static object)

    • If your game object class is not named GameObject, or if your game object class is not located in the Engine/Assets project, you may update the #includes in the CZPhysics project

  • If you have done everything correctly you should now be able to use my physics library smoothly. You could refer to the header files to get to know about the interfaces

  • If the objects jitter a lot while colliding with each other, you may increase your game’s simulation rate for more accurate collision detections.

    • Find the GetSimulationUpdatePeriod_inSeconds() located in the Engine/Application/iApplication.h

    • By default, it is 1.0f / 15.0f, which means 15 simulations per second. You may experiment with different values.


Here are some examples of using my library:


The code above is basically how I implemented the mechanisms in the demonstration video. As you can see, this is fairly similar to coding C# in Unity.

bottom of page