www.xbdev.net
xbdev - software development
Saturday June 7, 2025
Home | Contact | Support | WebGPU Graphics and Compute ...
     
 

WebGPU/WGSL Tutorials and Articles

Graphics and Compute ...

 


The Minkowski collison detection algorithm is beautiful! The implementation is very compact and very robust and also provides accurate contact information


Minkowski Collision Detection (Minkowski Difference)


Interactive demo to demonstrate and provide a minimal working example on implementing the Minkowski algorithm for collision detection.

While the algorithm itself isn't very parallel - we'll implement a single implementation on a single thread to detect the collision between two objects. The concept can be scaled up so that it can be run repeatedly for all the object pair collision calculations.

Key points
• WGSL Implementation (Run on GPU)
• Array of triangles
• Uses the vertex positions (doesn't depend on the order or layout - even handle broken shapes)
• Convex shape
• Gets contact information (contact points, contact normal and penetration distance)


How it works


The minkowski collision detection algorithm is in two parts - first we calculate the difference between the two shapes. This produces a new set of vertices. These vertices give us the minkowski shape - if this new shape has the origin inside it - then we have a hit!

The contact and distance information is found by calculating the nearest point on the surface to the origin.

We calculate the minkowski difference by subtracting every point of one shape from every point on the other. Now, this can produce a lot of points and can be very computationally expensive!

Imagine we have a cube with 36 vertex positions and a capsule with 600 points - this would produce 36x600=21600 vertex points! As every vertex on shape A is subtracted from every vertex on shape B and vice versa.

Instead of doing this the brute force way - we'll a support point algorithm using direction. We won't store the minkowski difference points - instead we'll loop over the points and calculate it as needed.


Tetrahedrons and Bookkeeping


Start by building a tetrahedron that encapsulates the origin (0,0,0).

• v0 - Calculate the centroid (use the centroid from shape A to shape B)
• v1 - From the centroid (v0) to the origin (0,0,0) - find the support point v1
• v2 - With the origin we have 3 points - take the cross product to find a third
• v3 - v0v1v2 gives us a triangle and a normal which can be used to find v3 (need to do an extra check to make sure the origin is on the correct side)


The code below shows the start of the algorithm for finding the starting* tetrahedron.

    var v0 MeshSupportPoint( (centroids.centroids.B), );

    var 
v1 GetSupportVertexorigin v0.);
    var 
v2 GetSupportVertexcross(origin-v0.porigin-v1.p) );
    var 
dir3 cross(v1.p-v0.pv2.p-v0.p);

    if ( 
dotdir3v0.origin ) > 0.0 ) { dir3 = -dir3; }
    var 
v3 GetSupportVertexdir3);




Minkowski difference and tetrahedron to find the closest point on the surface.
Minkowski difference and tetrahedron to find the closest point on the surface.



We need to reduce this tetrahedron as much as possible - so that we have a triangle for the surface.

A very very important point - when calculating the vertices using the support point algorithm - we keep track of which vertex in the original shape the vertex came from (both shape A and shape B).

Once we've found the closest triangle to the origin - we can map the information back to the original coordinates to find the position and distance on the original shapes.


Implementation Example


The output for the implementation is shown in the figure below - the green circles are used to show the triangles on both shape A and shape B from the tetrahedron.

The line and the red/white sphere mark the point on the surface and the closest point between the two shapes. When the distance is positive the shapes are intersecting, when it's negative it provides the shortest distance.



Minkowski collision detection algorithm example (Minkowski difference)
Minkowski collision detection algorithm example (Minkowski difference)



Below gives the WGSL compute code. We pass the vertex positions for the two shapes. We run the algorithm as a single thread (
workgroup(1,1,1)
).



// Transform for each chape
struct Transforms {
    
matrixA mat4x4<f32>,
    
matrixB mat4x4<f32>,
};

// Results we read back from the calculation
struct Results {
    
data0vec4<f32>,
    
outContactPoint     vec4<f32>,
    
outContactNormal    vec4<f32>,
    
outPenetrationDepth vec4<f32>,
    
outContactPointB    vec4<f32>, // 4
    
    
vec4<f32>, // 5 -> 
    
vec4<f32>,
    
vec4<f32>,
    
vec4<f32>,
    
    
vec4<f32>, //
    
vec4<f32>,
    
vec4<f32>,
    
vec4<f32>,
}

// binding information for the in/out from the comopute shader
@group(0) @binding(0) var<uniformtransforms Transforms;
@
group(0) @binding(1) var<storageread_writeshapeA   : array< f32 >;  // use f32 to avoid any alignment issues 
@group(0) @binding(2) var<storageread_writeshapeB   : array< f32 >;  // tighly packed to 4 byte
@group(0) @binding(3) var<storageread_writeresults  Results;
@
group(0) @binding(4) var<storageread_writetmpg     f32;

/*
Instead of passing an array of 'vec3<f32>' - we pass a single array of floats - every 3 floats is a vertex position.

This was a workaround for the alignment - compute shader likes to pad structures to 16 bytes (4 floats).
*/
fn GetVertexshapeId:u32indx:u32 ) -> vec3<f32>
{
    var 
vec3<f32>  = vec3<f32>(0);
    if ( 
shapeId == ) { 
        
p.x=shapeA[indx*3+0]; p.y=shapeA[indx*3+1]; p.z=shapeA[indx*3+2]; 
        
= (transforms.matrixA vec4<f32>(p,1.0) ).xyz;
    };
    if ( 
shapeId == ) { 
        
p.x=shapeB[indx*3+0]; p.y=shapeB[indx*3+1]; p.z=shapeB[indx*3+2]; 
        
= (transforms.matrixB vec4<f32>(p,1.0) ).xyz;
    };
    return 
p;
}

struct Centroids {
    
Avec3<f32>,
    
Bvec3<f32>
}

fn 
GetCentroids() -> Centroids {
      
    var 
centroidsCentroids;
      
    
centroids.vec3<f32>(0);
    for (var 
i:u32=0iu32(arrayLength(&shapeA)/3);  i++)
    {
        
let pA GetVertex0);
        
centroids.+= pA;
    } 
    
centroids.A  centroids.1.0/f32arrayLength(&shapeA)/);
     
    
centroids.vec3<f32>(0);
    for (var 
k:u32=0ku32(arrayLength(&shapeB)/3);  k++)
    {
        
let pB GetVertex(1,k);
        
centroids.+= pB;
    } 
    
centroids.B  centroids.1.0/f32arrayLength(&shapeB)/);
      
    return 
centroids;
}

struct MeshSupportPoint {
    
pvec3<f32>,
    
iu32
}
      
fn 
GetMeshSupportVertexshapeId:u32n:vec3<f32>) -> MeshSupportPoint
{
    var 
numShapePoints:u32 0;
    if ( 
shapeId==) { numShapePoints=arrayLength(&shapeA)/3; };
    if ( 
shapeId==) { numShapePoints=arrayLength(&shapeB)/3; };
      
    var 
supportPoint vec3<f32>;
    var 
mmax = -1000000.0;
    var 
ip:u32 0;
    for (var 
i:u32=0i<numShapePointsi++)
    {
        var 
vec3<f32> = GetVertex(shapeIdi);
        var 
val dotp);

        if ( 
val mmax )
        {
            
supportPoint p;
            
ip i/3
            
mmax val;
        }
    }
    return 
MeshSupportPoint(supportPointip);
}
      
struct SupportPoint {
    
pvec3<f32>,
    
nvec3<f32>,
    
aMeshSupportPoint,
    
bMeshSupportPoint
}

fn 
GetSupportVertexn:vec3<f32>) -> SupportPoint
{
    var 
v0 GetMeshSupportVertex0,  );
    var 
v1 GetMeshSupportVertex1, -);
    return 
SupportPoint(v0.v1.pnv0v1);
}
// End GetSupportVertex(..)

fn MapPointOrigin(A:vec3<f32>,   B:vec3<f32>,  C:vec3<f32>,
                  
tA:vec3<f32>, tB:vec3<f32>, tC:vec3<f32> ) -> vec3<f32>
{                               
    
let origin vec3<f32>(0,0,0);
    
let n normalizecrossA-BA-) );
    
let p origin dotnorigin );

    var 
tt lengthcross(p-CA-C) );
    var 
tb lengthcross(B-CA-C) );

    var 
st lengthcross(p-CB-C) );
    var 
sb lengthcross(A-CB-C) );

    var 
tt/tb;
    
clamp(t0);

    var 
st/sb;
    
clamp(s0);

    return 
tC + (tA-tC)*+ (tB-tC)*t;

}
// End MapPointOrigin(..)

/* -----------------------------------------------------------------------------

Compute shader entry point

----------------------------------------------------------------------------- */
@compute @workgroup_size(11)
fn 
main(@builtin(global_invocation_idglobalId      vec3<u32>,
        @
builtin(local_invocation_id)  localId       vec3<u32>,
        @
builtin(workgroup_id)         workgroupId   vec3<u32>,
        @
builtin(num_workgroups)       workgroupSize vec3<u32>
        ) 
{
    
// These are the objectives - what we want to find! - fill them with debug values
    
var outPenetrationDepth f32       1.23456;
    var 
outContactPoint     vec3<f32> = vec3<f32>(1.23456);
    var 
outContactNormal    vec3<f32> = vec3<f32>(1.23456);
               
    
let centroids GetCentroids();
    
    var 
origin vec3<f32>(0,0,0); // our target!
    
    
var v0 SupportPoint(centroids.centroids.B
                          
vec3<f32>(0), 
                          
MeshSupportPoint(vec3<f32>(0),0), 
                          
MeshSupportPoint(vec3<f32>(0),0)  );

    var 
v1 GetSupportVertexorigin v0.);
    var 
v2 GetSupportVertexcross(origin-v0.porigin-v1.p) );
    var 
dir3 cross(v1.p-v0.pv2.p-v0.p);

    if ( 
dotdir3v0.origin ) > 0.0 ) { dir3 = -dir3; }
    var 
v3 GetSupportVertexdir3);
    
    
// v0, v1, v2 and v3 form a tetrahedron - however, we need to do
    // a bit of looping around to 'ensure' the tetrahedron encloses the origin
    
for (var nn=0nn<100nn++)
    {
        if ( 
dotcross(v1.p-v0.pv3.p-v0.p), v0.origin ) < )
        {
            
v2 v3;
            
v3 GetSupportVertexcross(v1.p-v0.pv3.p-v0.p) );
            continue;
        }

        if ( 
dotcross(v3.p-v0.p,v2.p-v0.p), v0.origin ) < 0.0 )
        {
            
v1 v3;
            
v3 GetSupportVertexcross(v3.p-v0.pv2.p-v0.p));
            continue;                                                    
        }
        break;
    }
          
    
// Use for with uppper limit instead of a while - avoid chance of an ininit loop
    // reduce to control fidelity/performance factors
    
for (var nn=0nn<10nn++)
    {
        
// Compute normal of the wedge face
        
var v4 GetSupportVertexcross(v2.v1.pv3.v1.p) );
                
        
// Is our new point already on the plane of our triangle, 
        // if so, we're already as close as we can get to the target
        
var delta dot( (v4.v3.p), v4.);
        if ( 
abs(delta) < 0.01 )
        {
            var 
normalizev4.);
            
// Compute distance from origin to wedge face
            
outPenetrationDepth dot(nv1.p-origin);

            
// we're inside the wedge and have a collision
            // break and return closest point, normal and peneration/distance
            
break;
        }

        
// We'll create three baby tetrahedrons and decide
        // which one will replace the current tetrahedron
        // v1v2 - v2v3 - v3v1 - current tetrahedron edges
        // leads to 3 new tetrahedrons:
        // v1v2v4
        // v2v3v4
        // v3v1v4
        // Use for with uppper limit instead of a while - avoid chance of an ininit loop
        //while ( true ) 
        
for (var gg=0gg<100gg++)
        {
            var 
na = -crossv2.p-v0.pv4.p-v0.); // Normal to the triangle v2v4v0
            
var nb = -crossv4.p-v0.pv1.p-v0.); // Normal to the triangle v4v1v0
            
if ( dotnav0.p-origin ) > &&
                 
dotnbv0.p-origin ) > )
            {
                
// Inside this tetrahedron
                
v3   v4;
                break;
            }

            
na = -crossv3.p-v0.pv4.p-v0.); // Normal to the triangle v3v4v0
            
nb = -crossv4.p-v0.pv2.p-v0.); // Normal to the triangle v4v2v0
            
if ( dotnav0.p-origin ) > &&
                 
dotnbv0.p-origin ) > )
            {
                
// Inside this tetrahedron
                
v1 v4;
                break;
            }

            
na = -crossv1.p-v0.pv4.p-v0.); // Normal to the triangle v1v4v0
            
nb = -crossv4.p-v0.pv3.p-v0.); // Normal to the triangle v4v3v0
            
if ( dotnav0.p-origin ) > &&
                 
dotnbv0.p-origin ) > )
            {
                
// Inside this tetrahedron
                
v2 v4;
                break;
            }
               
            
// Should never get here - as it must be in
            // one of the children!
            // assert - fix algorithm!! 
            
break;
        }

    }
// End loop
              
    // Barycentric coordinates to map from the minkowski
    // difference onto the original shape
    
outContactPoint MapPointOriginv1.pv2.pv3.p,
                                      
v1.a.pv2.a.pv3.a.);

    var 
outContactPointB MapPointOriginv1.p,   v2.p,   v3.p,
                                           
v1.b.pv2.b.pv3.b.);

               
    
outContactNormal crossv1.p-v2.pv1.p-v3.);
    
outContactNormal normalizeoutContactNormal );

    
// store extra debug information - triangles on each shape
    
results.A     vec4<f32>( v1.a.p0.0);
    
results.B     vec4<f32>( v2.a.p0.0);
    
results.C     vec4<f32>( v3.a.p0.0);
    
    
results.X     vec4<f32>( v1.b.p0.0); 
    
results.Y     vec4<f32>( v2.b.p0.0);
    
results.Z     vec4<f32>( v3.b.p0.0);
  
    
results.outContactPoint     vec4<f32>( outContactPoint0.0);
    
results.outContactNormal    vec4<f32>( outContactNormal0.0);
    
    
// outPenetrationDepth < 0 - hit/otherwise no hit
    
results.outPenetrationDepth vec4<f32>( outPenetrationDepth00);
    
    
results.outContactPointB    vec4<f32>( outContactPointB);

    
results.data0.f32arrayLength(&shapeA) );
    
results.data0.f32arrayLength(&shapeB) );
}


The implementation is available online - that you can test out in real-time. It's a WebGPU application so it runs in a webpage without having to install anything (links at the end).




Resources & Links


Minkowski 2D Demo Code WebGPU Lab
Minkowski 3D Demo Code WebGPU Lab























101 WebGPU Programming Projects. WebGPU Development Pixels - coding fragment shaders from post processing to ray tracing! WebGPU by Example: Fractals, Image Effects, Ray-Tracing, Procedural Geometry, 2D/3D, Particles, Simulations WebGPU Games WGSL 2d 3d interactive web-based fun learning WebGPU Compute WebGPU API - Owners WebGPU Development Cookbook - coding recipes for all your webgpu needs! WebGPU & WGSL Essentials: A Hands-On Approach to Interactive Graphics, Games, 2D Interfaces, 3D Meshes, Animation, Security and Production Kenwright graphics and animations using the webgpu api 12 week course kenwright learn webgpu api kenwright programming compute and graphics applications with html5 and webgpu api kenwright real-time 3d graphics with webgpu kenwright webgpu for dummies kenwright webgpu wgsl compute graphics all in one kenwright webgpu api develompent a quick start guide kenwright webgpu by example 2022 kenwright webgpu gems kenwright webgpu interactive compute and graphics visualization cookbook kenwright wgsl webgpu shading language cookbook kenwright WebGPU Shader Language Development: Vertex, Fragment, Compute Shaders for Programmers Kenwright WGSL Fundamentals book kenwright WebGPU Data Visualization Cookbook kenwright Special Effects Programming with WebGPU kenwright WebGPU Programming Guide: Interactive Graphics and Compute Programming with WebGPU & WGSL kenwright Ray-Tracing with WebGPU kenwright



 
Advert (Support Website)

 
 Visitor:
Copyright (c) 2002-2025 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.