GPU Rigid Body Physics – Part 2

Utilising the GPU for rigid body physics calculations – Part 2 – Raycasting Targets

If you missed Part 1 – Go Here First!


We decided it would be great to add a targeting feature which would significantly enhance the gameplay and give the player a more active role in targeting the enemy units. This was envisaged simply as players clicking/touching the screen to select the specific unit they wanted to target, and then player units prioritising their fire towards these units.

Now, this is definitely more complex than collision detection, instead of detecting a collision between two units, we now need to detect a collision between units and a ray. A ray has a start point and a direction (and sometimes an end point), so it can collide with multiple units along its path. To deal with this we need to be able to handle multiple raycast results. To do this lets setup a struct. The size of the RaycastResults buffer will be RaycastResultLimit. We can pick a reasonable number like 50 which should never be exceeded.

HLSL
struct RaycastResultStruct {
    uint hitTest; //True/false - should only be true;
    float distance; //Distance from camera;
    uint unit; //First unit hit;
}; 

RWStructuredBuffer<RaycastResultStruct> RaycastResults;
const uint RaycastResultLimit;
HLSL

We could have used a sphere collider again, but it was felt that user click (and especially phone touches) are likely to be imprecise. Therefore we chose to use a box collider for units in this situation, to add a little leeway offered by the slightly larger size of box colliders.

Next we need some way to populate it. We are going to implement a version of the Separating Axis Theorem (SAT). This algorithm checks if the ray lies between each set of planes of the box in turn (if you picture the tops, then sides, then front& back. If the ray plane lies between all of them then must intersect the box. Or slightly more efficiently, if any set of planes do not contain the ray plane, then we know the ray does not collide with the box.

Now this algorithm relies of on the box being an Axis-Aligned Bounding Box (AABB), which essentially means the box sides are parallel to the world XYZ axis. The alternative to this would be Orientated Bounding Box (OBB). This just mean the box has been rotated so the fundamental axis of the box are no longer aligned. It would be pretty unhelpful if our box colliders couldn’t be rotate, but thankfully there is a straight forward solution. To convert and OOB to and AABB simply reverse the objects rotation! Now the ray must also be adjusted to account for this rotation in the opposite direction. This can be observed in the code below with the two dot products.

Given our units typically only rotate around the y-axis, we are choosing to ignore any rotations around other axis. You can see the function we produced for this process below.

HLSL
RaycastResultStruct IntersectRayBox(float3 center, float3 extents, uint iz) {
    float tMin = -1000000.0;
    float tMax = 1000000.0;
    RaycastResultStruct result;
    result.unit = iz;
    result.distance = 1000000.0;
    result.hitTest = 0;
    
    float3 p = center - clickPos;
    float3 local_axis[3];
  

    //COMPUTE ROTATION MATRIX FOR AXIS ALIGNMENT
    //******************************************
        
      //Adjust local axis based on mesh rotation:
      float rotationAngle = radians(-CombinedBuffer[iz].CBrotationAngle); // Convert degrees to radians if needed
  
      // Create a rotation matrix around the y-axis
      float4x4 rotationMatrix = float4x4(
          cos(rotationAngle), 0, sin(rotationAngle), 0,
          0, 1, 0, 0,
          -sin(rotationAngle), 0, cos(rotationAngle), 0,
          0, 0, 0, 1
      );
  
      // Calculate the new axes by multiplying the original axes with the rotation matrix
      local_axis[2] = mul(rotationMatrix, float4(0.0f, 0.0f, 1.0f, 0.0f)).xyz;
      local_axis[0] = mul(rotationMatrix, float4(1.0f, 0.0f, 0.0f, 0.0f)).xyz;
      local_axis[1] = mul(rotationMatrix, float4(0.0f, 1.0f, 0.0f, 0.0f)).xyz;
      
    
    // SAT METHOD - ALLOWS NON-ALIGNED AXIS 
    //******************************************  
    
      for (int i = 0; i < 3; ++i) { //For each axis;
          //Adjust for axis alignment
          float3 axis = local_axis[i]; 
          float e = dot(axis, p); //Got contribution of error (in world space) in this axis;
          float f = dot(cameraDir, axis); // Get contribution of ray in axis (or axis in the rays direction)
  
          //Test axis slab
          if (abs(f) > 0.00001f) {
              float t1 = (e + extents[i]) / f;
              float t2 = (e - extents[i]) / f;
              if (t1 > t2)
              {
                  float temp = t1;
                  t1 = t2;
                  t2 = temp;
              }
              tMin = max(tMin, t1);
              tMax = min(tMax, t2);
              if (tMin > tMax) {
                  return result;
              }
          } else if (-e - extents[i] > 0 || -e + extents[i] < 0) {
              return result;
          }
      }
    

    result.hitTest = 1;
    result.distance = tMin;
    return result;
}
HLSL

Great, now we’ve got a function we just have to use it.

Updates!

(Mar-25) Part 3 – Full Primitive Collisions Detection

Scroll to Top