Luxe rectangles physics engine (WIP, and very basic)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ; | |
import luxe.Component; | |
import luxe.Rectangle; | |
import luxe.Vector; | |
class PhysBody { | |
public var rect:Rectangle; | |
public var vel:Vector; | |
public var acc:Vector; | |
public var velCap:Vector; | |
public var touching:DirectionStates; | |
public function new(?_rect:Rectangle) { | |
rect = (_rect == null) ? new Rectangle() : _rect; | |
vel = new Vector(); | |
acc = new Vector(); | |
velCap = new Vector(); | |
touching = { left:false, right:false, top:false, bottom:false }; | |
} | |
public function clearTouching():Void { | |
touching.left = false; | |
touching.right = false; | |
touching.top = false; | |
touching.bottom = false; | |
} | |
} | |
typedef DirectionStates = { | |
var left:Bool; | |
var right:Bool; | |
var top:Bool; | |
var bottom:Bool; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ; | |
import luxe.Rectangle; | |
import luxe.Vector; | |
class RectExt { | |
public static function move(_this:Rectangle, v:Vector) { | |
_this.x += v.x; | |
_this.y += v.y; | |
} | |
public static function intersection( _this:Rectangle, _other:Rectangle ):Rectangle { | |
if (!_this.overlaps(_other)) return null; | |
var left = Math.max(_this.x, _other.x); | |
var top = Math.max(_this.y, _other.y); | |
var right = Math.min(_this.x + _this.w, _other.x + _other.w); | |
var bottom = Math.min(_this.y + _this.h, _other.y + _other.h); | |
return new Rectangle(left, top, right - left, bottom - top); | |
} | |
public static inline function right(_this:Rectangle):Float { | |
return _this.x + _this.w; | |
} | |
public static inline function bottom(_this:Rectangle):Float { | |
return _this.y + _this.h; | |
} | |
public static inline function leftOf(_this:Rectangle, _other:Rectangle):Bool { | |
return right(_this) <= _other.x; | |
} | |
public static inline function rightOf(_this:Rectangle, _other:Rectangle):Bool { | |
return _this.x >= right(_other); | |
} | |
public static inline function above(_this:Rectangle, _other:Rectangle):Bool { | |
return bottom(_this) <= _other.y; | |
} | |
public static inline function below(_this:Rectangle, _other:Rectangle):Bool { | |
return _this.y >= bottom(_other); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ; | |
import luxe.Color; | |
import luxe.Physics.PhysicsEngine; | |
import luxe.Rectangle; | |
import luxe.utils.Maths; | |
import luxe.Vector; | |
using RectExt; | |
//This should really be called the Mosaaic engine | |
//Anagram for "MOve Stuff Around And It Collides" Engine | |
//Alternatively just "Mosaic", for "MOve Stuff And It Collides" | |
class RectPhysics extends PhysicsEngine { | |
public var dynamicBodies:Array<PhysBody>; | |
public var staticBodies:Array<Rectangle>; | |
public var staticColor:Color = new Color(0.5, 0.5, 1); | |
public var dynamicColor:Color = new Color(0.5, 1, 0.5); | |
public var collisionColor:Color = new Color(0.8, 0.0, 0.0); | |
public var touchingThreshold:Float = 0.5; | |
override public function init() { | |
dynamicBodies = new Array<PhysBody>(); | |
staticBodies = new Array<Rectangle>(); | |
} | |
override public function render() { | |
if (!draw) return; | |
for (rect in staticBodies) { | |
Luxe.draw.rectangle( { | |
immediate:true, | |
rect:rect, | |
color:staticColor | |
}); | |
} | |
for (body in dynamicBodies) { | |
Luxe.draw.rectangle( { | |
immediate:true, | |
rect:body.rect, | |
color:dynamicColor | |
}); | |
if (body.touching.left) { | |
Luxe.draw.line( { | |
p0:new Vector(body.rect.x, body.rect.y), | |
p1:new Vector(body.rect.x, body.rect.bottom()), | |
color:collisionColor, | |
immediate:true | |
}); | |
} | |
if (body.touching.right) { | |
Luxe.draw.line( { | |
p0:new Vector(body.rect.right(), body.rect.y), | |
p1:new Vector(body.rect.right(), body.rect.bottom()), | |
color:collisionColor, | |
immediate:true | |
}); | |
} | |
if (body.touching.top) { | |
Luxe.draw.line( { | |
p0:new Vector(body.rect.x, body.rect.y), | |
p1:new Vector(body.rect.right(), body.rect.y), | |
color:collisionColor, | |
immediate:true | |
}); | |
} | |
if (body.touching.bottom) { | |
Luxe.draw.line( { | |
p0:new Vector(body.rect.x, body.rect.bottom()), | |
p1:new Vector(body.rect.right(), body.rect.bottom()), | |
color:collisionColor, | |
immediate:true | |
}); | |
} | |
} | |
} | |
override public function update() { | |
if (paused) return; | |
var oldBodyRect = new Rectangle(); | |
var vel = new Vector(); | |
var acc = new Vector(); | |
var intersection:Rectangle; | |
for (body in dynamicBodies) { | |
oldBodyRect.copy_from(body.rect); | |
acc.copy_from(body.acc); | |
acc.multiplyScalar(Luxe.physics.step_delta * Luxe.timescale); | |
body.vel.add(acc); | |
body.vel.x = Maths.sign(body.vel.x) * Math.min(Math.abs(body.vel.x), body.velCap.x); | |
body.vel.y = Maths.sign(body.vel.y) * Math.min(Math.abs(body.vel.y), body.velCap.y); | |
vel.copy_from(body.vel); | |
vel.multiplyScalar(Luxe.physics.step_delta * Luxe.timescale); | |
body.rect.move(vel); | |
body.clearTouching(); | |
var intersection:Rectangle; | |
for (rect in staticBodies) { | |
intersection = oldBodyRect.intersection(rect); | |
if (intersection != null) { | |
if (intersection.h <= intersection.w) { | |
oldBodyRect.y += (oldBodyRect.y < rect.y) ? -intersection.h : intersection.h; | |
} | |
else { | |
oldBodyRect.x += (oldBodyRect.x < rect.x) ? -intersection.w : intersection.w; | |
} | |
} | |
if (body.rect.overlaps(rect)) { | |
if (oldBodyRect.leftOf(rect)) { | |
body.rect.x = rect.x - body.rect.w; | |
} | |
else if (oldBodyRect.rightOf(rect)) { | |
body.rect.x = rect.right(); | |
} | |
else if (oldBodyRect.above(rect)) { | |
body.rect.y = rect.y - body.rect.h; | |
} | |
else if (oldBodyRect.below(rect)) { | |
body.rect.y = rect.bottom(); | |
} | |
} // if dynamic overlaps static | |
//the body is regarded as touching when it is less than one pixel away from the edge of the object | |
//(i.e. difference between the edge of the body and the edge of the obstacle is less than one) | |
//Use range check because the body may be far beyond the obstacle, so the difference will be negative, and less than 1 would be true | |
if (body.rect.bottom() > rect.y && body.rect.y < rect.bottom()) { | |
if (Maths.within_range(rect.x - body.rect.right(), 0, touchingThreshold)) body.touching.right = true; | |
if (Maths.within_range(body.rect.x - rect.right(), 0, touchingThreshold)) body.touching.left = true; | |
} | |
if (body.rect.right() > rect.x && body.rect.x < rect.right()) { | |
if (Maths.within_range(body.rect.y - rect.bottom(), 0, touchingThreshold)) body.touching.top = true; | |
if (Maths.within_range(rect.y - body.rect.bottom(), 0, touchingThreshold)) body.touching.bottom = true; | |
} | |
} // for each static body | |
} // for each dynamic body | |
Luxe.events.fire('RectPhysics.update.complete'); | |
} // update | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment