Skip to content

Instantly share code, notes, and snippets.

@hamaluik
Last active March 14, 2024 16:57
Show Gist options
  • Save hamaluik/048d24c5d3ce154316f3 to your computer and use it in GitHub Desktop.
Save hamaluik/048d24c5d3ce154316f3 to your computer and use it in GitHub Desktop.
Minkowski Difference Collision Detection
package ;
import openfl.display.Sprite;
/**
* ...
* @author Kenton Hamaluik
*/
class AABB
{
public var center:Vector = new Vector();
public var extents:Vector = new Vector();
public var min(get, never):Vector;
public function get_min()
{
return new Vector(center.x - extents.x, center.y - extents.y);
}
public var max(get, never):Vector;
public function get_max()
{
return new Vector(center.x + extents.x, center.y + extents.y);
}
public var size(get, never):Vector;
public function get_size()
{
return new Vector(extents.x * 2, extents.y * 2);
}
public function new(center:Vector, extents:Vector)
{
this.center = center;
this.extents = extents;
}
public function draw(container:Sprite, colour:Int = 0xffffff)
{
container.graphics.beginFill(colour, 0.5);
container.graphics.drawRect(min.x, min.y, size.x, size.y);
container.graphics.endFill();
}
public function minkowskiDifference(other:AABB):AABB
{
var topLeft:Vector = min - other.max;
var fullSize:Vector = size + other.size;
return new AABB(topLeft + (fullSize / 2), fullSize / 2);
}
public function closestPointOnBoundsToPoint(point:Vector):Vector
{
// test x first
var minDist:Float = Math.abs(point.x - min.x);
var boundsPoint:Vector = new Vector(min.x, point.y);
if (Math.abs(max.x - point.x) < minDist)
{
minDist = Math.abs(max.x - point.x);
boundsPoint = new Vector(max.x, point.y);
}
if (Math.abs(max.y - point.y) < minDist)
{
minDist = Math.abs(max.y - point.y);
boundsPoint = new Vector(point.x, max.y);
}
if (Math.abs(min.y - point.y) < minDist)
{
minDist = Math.abs(min.y - point.y);
boundsPoint = new Vector(point.x, min.y);
}
return boundsPoint;
}
}
package ;
import flash.display.Sprite;
import flash.events.Event;
import flash.Lib;
import openfl.display.FPS;
/**
* ...
* @author Kenton Hamaluik
*/
class Main extends Sprite
{
var inited:Bool;
var drawContainer:Sprite = new Sprite();
var boxA:AABB;
var boxB:AABB;
/* ENTRY POINT */
function resize(e)
{
if (!inited) init();
else
{
// resize or orientation change
boxA = new AABB(new Vector(0, 0), new Vector(10, 10));
boxB = new AABB(new Vector(stage.stageWidth / 2, stage.stageHeight / 2), new Vector(stage.stageWidth / 5, stage.stageHeight / 5));
}
}
function init()
{
if (inited) return;
inited = true;
this.addChild(drawContainer);
boxA = new AABB(new Vector(0, 0), new Vector(10, 10));
boxB = new AABB(new Vector(stage.stageWidth / 2, stage.stageHeight / 2), new Vector(stage.stageWidth / 5, stage.stageHeight / 5));
stage.addChild(new FPS(10, 10, 0xffffff));
addEventListener(Event.ENTER_FRAME, onFrame);
}
function onFrame(event:Event):Void
{
var mousePos:Vector = new Vector(stage.mouseX, stage.mouseY);
// move boxA around
boxA.center = mousePos;
var boxAColour:Int = 0xff0000;
// get the minkowski difference between the two
var md:AABB = boxB.minkowskiDifference(boxA);
var penetrationVector:Vector = null;
if (md.min.x <= 0 &&
md.max.x >= 0 &&
md.min.y <= 0 &&
md.max.y >= 0)
{
// collision is occurring!
boxAColour = 0x00ff00;
// find the penetration depth
penetrationVector = md.closestPointOnBoundsToPoint(Vector.zero);
boxA.center += penetrationVector;
}
// redraw
drawContainer.graphics.clear();
boxB.draw(drawContainer);
boxA.draw(drawContainer, boxAColour);
md.draw(drawContainer, 0x0000ff);
if (penetrationVector != null)
penetrationVector.draw(drawContainer, new Vector(Lib.current.stage.stageWidth / 2, Lib.current.stage.stageHeight / 2), 0xff0000);
}
/* SETUP */
public function new()
{
super();
addEventListener(Event.ADDED_TO_STAGE, added);
}
function added(e)
{
removeEventListener(Event.ADDED_TO_STAGE, added);
stage.addEventListener(Event.RESIZE, resize);
#if ios
haxe.Timer.delay(init, 100); // iOS 6
#else
init();
#end
}
public static function main()
{
// static entry point
Lib.current.stage.align = flash.display.StageAlign.TOP_LEFT;
Lib.current.stage.scaleMode = flash.display.StageScaleMode.NO_SCALE;
Lib.current.addChild(new Main());
}
}
package ;
import openfl.display.Sprite;
/**
* ...
* @author Kenton Hamaluik
*/
private class VectorRaw
{
public var x:Float = 0;
public var y:Float = 0;
public function new(x:Float = 0, y:Float = 0)
{
this.x = x;
this.y = y;
}
}
@:forward(x, y)
abstract Vector(VectorRaw) from VectorRaw to VectorRaw
{
public function new(x:Float = 0, y:Float = 0)
{
this = new VectorRaw(x, y);
}
public static var zero(get, never):Vector;
private static inline function get_zero():Vector
{
return new Vector(0, 0);
}
public var length(get, never):Float;
private inline function get_length():Float
{
return Math.sqrt((this.x * this.x) + (this.y * (this.y)));
}
@:op(A * B)
@:commutative
public static inline function multiplyScalar(a:Vector, s:Float)
{
return new Vector(a.x * s, a.y * s);
}
@:op(A / B)
public static inline function divideByScalar(a:Vector, s:Float)
{
return new Vector(a.x / s, a.y / s);
}
@:op(A + B)
public static inline function addVectors(a:Vector, b:Vector)
{
return new Vector(a.x + b.x, a.y + b.y);
}
@:op(A - B)
public static inline function subtractVectors(a:Vector, b:Vector)
{
return new Vector(a.x - b.x, a.y - b.y);
}
public function toString():String
{
return "[" + this.x + ", " + this.y + "]";
}
public function draw(container:Sprite, origin:Vector, colour:Int = 0xffffff)
{
container.graphics.moveTo(origin.x, origin.y);
container.graphics.lineStyle(1, colour, 1);
container.graphics.lineTo(origin.x + this.x, origin.y + this.y);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment