www.xbdev.net
xbdev - software development
Saturday April 19, 2025
Home | Contact | Support | JavaScript... so much power in such a few lines of code..
     
 

JavaScript...

so much power in such a few lines of code..

 


Minkowski Collision Detection Demo (2D Boxes)


I've written lots of collision detection algorithms using lots of different techniques - however, the Minkowski is one of the best - it's very flexible easy to understand (especially in 2d).

The following gives a tutorial/visualization for working with the Minkowski algorithm.

While the algorithm can work with any 'convex' shapes - this demo focuses on 'boxes'.



Screenshot of the test demo of the minkowski different for collision detection and feature extraction demo.
Screenshot of the test demo of the minkowski different for collision detection and feature extraction demo.



The demo is interactive and runs in a web-browser - you can drag your mouse around and it displays the minkowski information (and the collision feature data).







Complete Code


<html><head></head>
<
body>

<
style>
body {  min-height700px;  }

* { 
margin0;
    
padding0
}

.
grid {
    
height100%;
    
width100%;
    
margin0;
}
.
grid {
    
background-image:
      
repeating-linear-gradient(#ccc 0 1px, transparent 1px 100%),
      
repeating-linear-gradient(90deg#ccc 0 1px, transparent 1px 100%);
    
background-size71px 71px;
}
</
style>

<
div class="grid"></div>

<
div id='hit' style="font-size:20pt;position:absolute;left:10px;top:20px;"></div>

<
div style="font-size:10pt;position:absolute;width:480px;left:10px;bottom:20px;">
A robust 2D collision detection demo with feature extraction informationsuch as, 
contact pointspenetration depth and hit (true/false).
Minkowski difference for 2d boxes and shortest distance algorithms.
Demo shows the Minkowski points (A-B), box Abox B and calculation features (educational purposes). AuthorKenwright (www.xbdev.net)
</
div>

<
script>
function 
vec2(xy)
{
  
this.x;
  
this.y;
  
  
this.set = function(xy)
  {
    
this.x;
    
this.y;
  }
};

vec2.add    = function(v0v1){  return new vec2(v0.x+v1.xv0.y+v1.y);    }
vec2.sub    = function(v0v1){  return new vec2(v0.x-v1.xv0.y-v1.y);    }
vec2.scale  = function(v0){ return new vec2(v0.x*sv0.y*s);          }
vec2.mul    = function(v0v1){ return new vec2(v0.x*v1.xv0.y*v1.y);    }
vec2.cross  = function(v0,v1) { return (v0.x*v1.v0.y*v1.x);   }
vec2.dot    = function(v0v1){  return (v0.x*v1.v0.y*v1.y);            }
// note you can't use 'length' word
//vec2.length = function(v0    ){ return Math.sqrt( v0.x*v0.x + v0.y*v0.y); }
vec2.dist = function(v0) { return Math.sqrtv0.x*v0.v0.y*v0.y); }
vec2.norm   = function(v0    )

  
let ln Math.sqrtv0.x*v0.v0.y*v0.y);
  return 
vec2.scalev01.0/ln );
}
vec2.perp   = function(n     ){ return new vec2(n.y, -n.x);                }


let off = new vec2(300,300);

const 
maxobjs 1000;
var 
numobjs2 0;
var 
objs = [];


function 
cleardraw2()
{
  
numobjs2 0;
  for (
let i=0i<objs.lengthi++)
  {
    
objs[i].style.left '-1000px';
  }  
}
function 
setupdraw()
{
  
numobjs2 0;
  for (
let i=0i<maxobjsi++)
  {
    
let div document.createElement('div');
    
document.body.appendChilddiv );
    
objs.pushdiv );
  }
}

setupdraw();

/*
  helper function for drawing lines using 'div'
  elements (use a bit of trigonometry to set the size
  and orientation to create a line)
*/
function drawLine(s0s1col='blue')
{
  
let ax s0.off.x;
  
let ay s0.off.y;
  
let bx s1.off.x;
  
let by s1.off.y;
  
//console.log( numobjs2 );
  
console.assertnumobjs2 maxobjs );
  
let div objs[numobjs2];
  
numobjs2++;
  
//if ( div == 0 )
  
{
    
//div = document.createElement('div');
    //document.body.appendChild( div );
  
}
  
div.style.position 'absolute';
  
    if(
ay>by)
    {
        
bx=ax+bx;  
        
ax=bx-ax;
        
bx=bx-ax;
        
by=ay+by;  
        
ay=by-ay;  
        
by=by-ay;
    }
    
    
let calc   Math.atan((ax-bx)/(by-ay));
    
calc       calc*180.0/Math.PI;
    
let length Math.sqrt((ax-bx)*(ax-bx)+(ay-by)*(ay-by));
    
div.style.height         length "px";
  
div.style.width          "px";
  
div.style.left           ax "px";
  
div.style.top            ay "px";
  
div.style.transform      'rotate(' calc 'deg)';
  
div.style.transformOrigin="0 0";
  
div.style.backgroundColor=col;//"blue";
}

function 
drawCrossv0scol='black' )
{
  
let x = new vec2(s,0);
  
let y = new vec2(0,s);
  
  
drawLinevec2.sub(v0,x), vec2.add(v0,x), col );
  
drawLinevec2.sub(v0,y), vec2.add(v0,y), col );  
}

function 
drawArrow(v0v1scolor='blue')
{
  
drawLinev0v1color );
  
    
// Calculate the direction of the line
    
const direction vec2.norm( new vec2(v1.v0.xv1.v0.y) );

    
// Perpendicular vectors for the arrowhead
    
const perp1 vec2.scale( new vec2(-direction.ydirection.x), s);
    const 
perp2 vec2.scale( new vec2(direction.y, -direction.x), s);

    
// Calculate the end points of the arrowhead
    
const arrowHeadPoint1 = new vec2(v1.direction.perp1.xv1.direction.perp1.y);
    const 
arrowHeadPoint2 = new vec2(v1.direction.perp2.xv1.direction.perp2.y);

    
// Draw the arrowhead
    
drawLine(v1arrowHeadPoint1color);
    
drawLine(v1arrowHeadPoint2color);
  
}

function 
drawCircle(s0rcol='red')
{
  
let ax s0.off.x;
  
let ay s0.off.y;
  
  
console.assertnumobjs2 maxobjs );
  
let div objs[numobjs2];
  
numobjs2++;

  
div.style.position 'absolute';
    
div.style.height         r*"px";
  
div.style.width          r*"px";
  
div.style.left           = (ax-r) + "px";
  
div.style.top            = (ay-r) + "px";
  
div.style['border-radius'] =  '50%';
  
div.style.backgroundColor=col;
}

function 
drawBox(p0s0anglecol='red')
{
  const 
halfWidth  s0.x;
  const 
halfHeight s0.y;

  
// Calculate the rotated corner points
  
const cos Math.cos(angle);
  const 
sin Math.sin(angle);

  
// Calculate the positions of the four corners
  
const topLeft = {
    
xp0.+ (-halfWidth cos - -halfHeight sin),
    
yp0.+ (-halfWidth sin + -halfHeight cos)
  };
  const 
topRight = {
    
xp0.+ (halfWidth cos - -halfHeight sin),
    
yp0.+ (halfWidth sin + -halfHeight cos)
  };
  const 
bottomRight = {
    
xp0.+ (halfWidth cos halfHeight sin),
    
yp0.+ (halfWidth sin halfHeight cos)
  };
  const 
bottomLeft = {
    
xp0.+ (-halfWidth cos halfHeight sin),
    
yp0.+ (-halfWidth sin halfHeight cos)
  };

  
// Draw the square
    /*
  ctx.beginPath();
  ctx.moveTo(topLeft.x, topLeft.y);
  ctx.lineTo(topRight.x, topRight.y);
  ctx.lineTo(bottomRight.x, bottomRight.y);
  ctx.lineTo(bottomLeft.x, bottomLeft.y);
  ctx.closePath();
  ctx.stroke();
    */

    
drawLinetopLeft,     topRight    );
    
drawLinetopRight,    bottomRight );
    
drawLinebottomRightbottomLeft  );
    
drawLinebottomLeft,  topLeft     );
}
  
function 
drawShape)
{
  for (
let i=1i<s.lengthi++)
  {
    
drawLine(s[i-1], s[i]);
    
drawCircles[i-1], );
  }
  
drawLine(s[0], s[s.length-1]);
  
drawCircles[s.length-1], );
}

function 
shape()
{
  
this.points = [];
  
this.addPoint = function( )
  {
    
this.points.push( new vec2(v.xv.y) );
  }
  
this.draw = function()
  {
    
drawShapethis.points );
  }
  
/*
  this.drawPoints = function()
  {  
    for (let i=1; i<this.points.length; i++)
    {
      drawCircle( this.points[i-1], 6, 'green' );
    }
    drawCircle( this.points[this.points.length-1], 6, 'green' );
  }
  */

  
this.getCentroid = function()
  {
    
let v = new vec2(0,0);
    for (
let i=0i<this.points.lengthi++)
    {
      
vec2.addvthis.points[i] );
    }
    
    
vec2.scalev1/this.points.length );
    return 
v;
  }
}


let boxA = { p: new vec2(100,100),
             
s: new vec2(10050),
             
a0.6 };

let boxB = { p: new vec2(200,50),
             
s: new vec2(60120),
             
a1.1 };


function 
rotatePointpointangle )
{
    
let c Math.cos(angle);
    
let s Math.sin(angle);
    return new 
vec2(
        
point.point.s,
        
point.point.c
    
);
}

function 
stHit() {
  var  
pen    0.0
  var  
normal = new vec2(0,0);
  var  
point  = new vec2(0,0);
};


let cur = new vec2(0,0); // mouse position

function closestPointpApBpX )
{
  
let b vec2.distvec2.sub(pB,pA) );
  if ( 
0.0001 )
  {
    return 
pA;
  }
  
let n vec2.normvec2.sub(pB,pA) );
  
let pAX vec2.sub(pXpA);
  
let s vec2.dotnpAX );
  
let a vec2.distvec2.scale(n,s) );
  
  
let t a/b;
  if ( 
<= ) return pA;
  if ( 
>= ) return pB;
  
  
let pT vec2.addpAvec2.scalevec2.sub(pB,pA), ) );
  return 
pT;
}

// order points for a triangle are in a
// clockwise or counterclockwise direction

function getCW(v1,v2,v3)
{
  
let n1 vec2.normvec2.sub(v2,v1) );
  
let n2 vec2.normvec2.sub(v3,v1) );
  
let d vec2.cross(n1,n2);
  if ( 
) return -1;
  return 
1;
}
  
  
function 
MinkowskiBox(boxAboxB)
{
  
this.shapeA  = [];
  
this.shapeB  = [];
  
this.shapeBA = [];
  
    
let corners =  [ new vec2( -1, -), 
                     new 
vec2(  1, -), 
                     new 
vec2(  1,  ),
                     new 
vec2( -1,  ) ];
  
    for (
let i=0i<4i++)
    {
        
this.shapeA.pushvec2.addrotatePointvec2.mul(corners[i], boxA.s) , boxA.), boxA.) );
        
this.shapeB.pushvec2.addrotatePointvec2.mul(corners[i], boxB.s) , boxB.), boxB.) );
    }
      
  
let c 0;
  for (
let i=0i<this.shapeA.lengthi++)
  {
    
let pA this.shapeA[i];
    for (
let k=0k<this.shapeB.lengthk++)
    {
      
let pB this.shapeB[k];  
      
let pBA vec2.subpBpA );
      
this.shapeBA.push( { p:pBAa:pAi:cb:pBai:ibi:} );
      
c++;
    }
  }
  
  
this.getNumPoints = function()
  {
    return 
this.shapeA.length 
         
this.shapeB.length;
  }
  
  
this.drawPoints = function()
  {
        
drawShapethis.shapeA );
        
drawShapethis.shapeB );
    for (
let i=1i<this.shapeBAi++)
    {
      
drawCirclethis.shapeBA[i-1].p6'green' );
    }
    
drawCirclethis.shapeBA[this.shapeBA.length-1].p6'green' );
  }
  
  
this.getSupport = function( dir )
  {
    
let n vec2.normdir );
    
let d vec2.dotthis.shapeBA[0].p);
    
let h 0;
    for (
let i=0i<this.shapeBA.lengthi++)
    {
      
let t vec2.dotthis.shapeBA[i].p);
      if ( 
)
      {
        
t;
        
i;
      }
    }
    return 
this.shapeBA[h];  
  }  
  
    
this.collision = function()
    {
        
let v1 this.getSupport( new vec2(01) );
        
let v2 this.getSupport( new vec2(10) );
        
let v3 this.getSupport( new vec2(0,-1) );

        if ( 
v2.== v1.|| v2.== v3.)
        {
            
// swap them around to keep things ordered clockwise
            
v2 v3;
            
v3 this.getSupport( new vec2(-10) );
        }

        
console.assertv1.!= v2.i);
        
console.assertv2.!= v3.i);


        
let edges = [];
        
edges.push( [v1v2] );
        
edges.push( [v2v3] );
        
edges.push( [v3v1] );

        
let hullEdges = [];

        
let check 0;
        while ( 
edges.length )
        {
            
let edge edges.pop();

            
let n vec2.normvec2.perpvec2.subedge[0].pedge[1].) ) );

            
let v4 this.getSupport);

            if ( 
v4.== edge[0].||
                 
v4.== edge[1].)
            {
                
// save edge 
                
hullEdges.pushedge );
            }
            else 
            {
                
edges.push( [ edge[0], v4      ] );
                
edges.push( [ v4     edge[1] ] );
            }

            
console.assertedges.length 100 );
            
check++;
            
console.assert(check<100);
        }

        
let sdist 10000;
        
let indx  0;

        for (
let i=0i<hullEdges.lengthi++)
        {
            
let edge hullEdges[i];
            
drawLineedge[0].pedge[1].p'orange' );

            
let cp closestPointedge[0].pedge[1].p, new vec2(0,0) );
            
//drawCircle( cp, 4, 'yellow' );

            
let d vec2.distvec2.subcp, new vec2(0,0) ) );
            if ( 
sdist )
            {
                
sdist d;
                
indx  i;
            }
        }

        
let inside getCW( new vec2(0,0),
                            
hullEdges[indx][ ].p,
                            
hullEdges[indx][ ].);

        
let best closestPointhullEdges[indx][ ].p
                                 
hullEdges[indx][ ].p
                                 new 
vec2(0,0) );
        
drawCirclebest6'yellow' );


        
let df vec2.distvec2.sub(hullEdges[indx][ ].p,
                                     
hullEdges[indx][ ].p) );
        
let d1 vec2.distvec2.sub(hullEdges[indx][ ].p,
                                                       
best) );
      
         
let t d1/df;
        
//console.log( 't:' + t + ', df:' + df + ', d1: ' + d1 );

        
console.assert>=&& <= );

        
let a0 hullEdges[indx][ ].a;
        
let a1 hullEdges[indx][ ].a;
        
let x vec2.adda0vec2.scale(vec2.suba1,a0 ), ) );
        
drawCrossx40'orange' );

        
let b0 hullEdges[indx][ ].b;
        
let b1 hullEdges[indx][ ].b;
        
let y vec2.addb0vec2.scale(vec2.subb1,b0 ), ) );
        
drawCrossy40'orange' );

        
drawLinexy'red' );

        
let pen vec2.distvec2.subx) );
        
let nor vec2.normvec2.subx) );

        
let data = {hit:false,
                    
ppen,
                    
nnor };

        if ( 
inside <= )
        {
            
data.hit true;
        }
      
        var 
hit = new stHit();
        
hit.pen    pen  * (inside<=? -1);
        
hit.point  x;
        
hit.normal nor;
        return 
hit// return collision data

    
}// end collision function
}



document.onmousemove = function(e)
{  
  
cur.e.pageX;
  
cur.e.pageY;
  
  
  
cleardraw2();
  
  
drawCircle(new vec2(0,0), 8'black'); // origin
  
drawCross( new vec2(0,0), 20 ); // draw origin
  
    //drawBox( boxA.p, boxA.s, boxA.a );
    //drawBox( boxB.p, boxB.s, boxB.a );
  
    
drawCirclecur3'orange' );
  
    
boxA.vec2.sub(cur,off);
  
  
    
let mink = new MinkowskiBox(boxAboxB);
  
mink.drawPoints();
  
    
let hit mink.collision();
  
    
/*
  // get mouse position and offset the origin - debugging/visualization
  // purposes
  let cA = shapeA.getCentroid();
  let delta = vec2.sub( vec2.sub(cur,off), cA );
  for (let i=0; i<shapeA.points.length; i++)
  {
    shapeA.points[i] = vec2.add( shapeA.points[i], delta );
  }
  */
  
    
let h0 hit.point;
    
let h1 vec2.addhit.pointvec2.scalehit.normal, -50.0 ) );
  
    
h0.+= 0;
    
h1.+= 0;
  
    
drawArrowh0
               
h1
               
20.0'blue' );
  
  
let div document.getElementById('hit');
  
div.innerHTML 'Hit: ' hit.pen.toFixed(2) + ( hit.pen ' <b>(HIT)</b>' '' );
}
document.onmousemove( {pageX:100pageY:100} );
</
script>

</
body>
</
html>


The code is actually quiet elegant and compact - the code draws the lines and points using 'pure' html elements (no canvas drawing).

Just manipulates the html elements (div) so they can be used for drawing (of course combined with JavaScript).




Resources and Links


• Current Page (Share) [LINK]

• 2d 'any' convex shape demo version [LINK]

• Tutorial on the Minkowski algorithm (2d and 3d) [LINK]

• Other physics tutorials [LINK]

• Notebook Test Code [LINK]

• WebGPU Version (WebGPU Lab) - [LINK]




























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.