Skip to content

Instantly share code, notes, and snippets.

@bruce965
Created March 26, 2015 09:22
Show Gist options
  • Save bruce965/e4629ca864138e5458cf to your computer and use it in GitHub Desktop.
Save bruce965/e4629ca864138e5458cf to your computer and use it in GitHub Desktop.
Simple Physics Simulation
@tsc --sourceMap --out out/physics.js Physics.ts
@if not "%errorlevel%"=="0" @pause
@if exist "./docs/" @rmdir "./docs/" /S /Q
@typedoc --readme none --name Physics Test --hideGenerator --out ./docs/ Physics.ts
@if not "%errorlevel%"=="0" @pause
/// <reference path="deps/jquery.d.ts"/>
module fg.math {
export class Vector2 implements Vector2
{
public x: number;
public y: number;
public constructor();
public constructor(x: number, y: number);
constructor(x?: number, y?: number) {
this.x = x || 0;
this.y = y || 0;
}
public add(other: Vector2): Vector2 {
return new Vector2(
this.x + other.x,
this.y + other.y
);
}
public sub(other: Vector2): Vector2 {
return new Vector2(
this.x - other.x,
this.y - other.y
);
}
public mul(value: number): Vector2 {
return new Vector2(
this.x * value,
this.y * value
);
}
public div(value: number): Vector2 {
return new Vector2(
this.x / value,
this.y / value
);
}
public dot(other: Vector2): number {
return this.x * other.x + this.y * other.y;
}
public abs(): Vector2 {
return new Vector2(
this.x > 0 ? this.x : -this.x,
this.y > 0 ? this.y : -this.y
);
}
public magnitudeSquare(): number {
return this.x*this.x + this.y*this.y;
}
public magnitude(): number {
return Math.sqrt(this.x*this.x + this.y*this.y);
}
public toString(): string {
return "{x: "+this.x+", y: "+this.y+"}";
}
}
}
module fg.physics {
import Vector2 = fg.math.Vector2;
var undefined = void 0;
var numberType = typeof(0);
export class StaticObject
{
public size: Vector2;
public position: Vector2;
public constructor(size: Vector2, position: Vector2) {
this.size = size || new Vector2();
this.position = position || new Vector2();
}
public getCollisionResolutionVector(other: StaticObject): Vector2 {
var coll = new Vector2();
if(this.position.x < other.position.x) { // AAAAA BBBB
if(this.position.x + this.size.x <= other.position.x) { // AAAAA BBBB
// no collision
} else { // AAAXXBB
coll.x = other.position.x - this.position.x - this.size.x;
}
} else { // BBBB AAAAA
if(other.position.x + other.size.x <= this.position.x) { // BBBB AAAAA
// no collision
} else { // BBXXAAA
coll.x = other.position.x - this.position.x + other.size.x;
}
}
if(this.position.y < other.position.y) { // AAAAA BBBB
if(this.position.y + this.size.y <= other.position.y) { // AAAAA BBBB
// no collision
} else { // AAAXXBB
coll.y = other.position.y - this.position.y - this.size.y;
}
} else { // BBBB AAAAA
if(other.position.y + other.size.y <= this.position.y) { // BBBB AAAAA
// no collision
} else { // BBXXAAA
coll.y = other.position.y - this.position.y + other.size.y;
}
}
if(coll.x * coll.x > coll.y * coll.y)
coll.x = 0;
else
coll.y = 0;
if(!coll.x && !coll.y)
return;
return coll;
}
public toString(): string {
return "{size: "+this.size+", position: "+this.position+"}";
}
}
export class DynamicObject extends StaticObject
{
public speed: Vector2;
public mass: number;
public constructor(size?: Vector2, position?: Vector2, speed?: Vector2, mass?: number) {
super(size, position);
this.speed = speed || new Vector2();
this.mass = mass === undefined ? 1 : mass;
}
public physicsStep(time: number): void {
this.position = this.position.add(this.speed.mul(time));
}
public accelerate(acceleration: Vector2, time: number) {
this.speed = this.speed.add(acceleration.mul(time));
}
public resolveCollision(other: DynamicObject): void {
var resolutionVector = super.getCollisionResolutionVector(other);
if(resolutionVector === undefined)
return;
var thisSpeedMagnitude = this.speed.magnitude();
var otherSpeedMagnitude = other.speed.magnitude();
var totalSpeedMangnitude = thisSpeedMagnitude + otherSpeedMagnitude;
this.position = this.position.sub (resolutionVector.mul(thisSpeedMagnitude / totalSpeedMangnitude));
other.position = other.position.sub(resolutionVector.mul(otherSpeedMagnitude / totalSpeedMangnitude));
//this.speed = new Vector2();
//other.speed = new Vector2();
var thisSpeed = this.speed;
var otherSpeed = other.speed;
var RESTITUTION_COEFFICIENT = 1;
//var relativeSpeed = this.speed.sub(other.speed);
//var totalMass = this.mass + other.mass;
//this.speed = otherSpeed.mul(other.mass / totalMass).sub(thisSpeed.mul (this.mass / totalMass).mul(RESTITUTION_COEFFICIENT));
//other.speed = thisSpeed.mul (this.mass / totalMass).sub(otherSpeed.mul(other.mass / totalMass).mul(RESTITUTION_COEFFICIENT));
// http://stackoverflow.com/a/345863/1135019
this.speed = thisSpeed.mul(this.mass - other.mass).add(otherSpeed.mul(other.mass*2)).div(this.mass + other.mass).mul(RESTITUTION_COEFFICIENT);
other.speed = otherSpeed.mul(other.mass - this.mass).add(thisSpeed.mul(this.mass*2)).div(other.mass + this.mass).mul(RESTITUTION_COEFFICIENT);
}
public toString(): string {
return "{size: "+this.size+", position: "+this.position+", speed: "+this.speed+", mass: "+this.mass+"}";
}
}
}
function debugDrawStaticObject(obj: fg.physics.StaticObject, color: string = 'red') {
$('<div>').text(JSON.stringify(obj, null, 2)).css({
fontSize: 8,
overflow: 'visible',
whiteSpace: 'pre',
fontFamily: 'monospace',
position: 'fixed',
'box-sizing': 'border-box',
border: '1px solid black',
background: color,
width: obj.size.x,
height: obj.size.y,
left: obj.position.x,
top: obj.position.y
}).appendTo(document.body);
}
function debugDrawLine(start: fg.math.Vector2, end: fg.math.Vector2, originMark: boolean = false, color: string = 'black') {
var x1 = start.x,
x2 = end.x,
y1 = start.y,
y2 = end.y;
if(x2 < x1) {
var temp = x1;
x1 = x2;
x2 = temp;
temp = y1;
y1 = y2;
y2 = temp;
}
var length = Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
var angle = Math.atan((y2-y1)/(x2-x1));
$('<div>').attr('name', start.toString()+" ~ "+end.toString()).css({
position: 'fixed',
'box-sizing': 'border-box',
left: x1,// - 0.5*length*(1 - Math.cos(angle)),
top: y1,// + 0.5*length*Math.sin(angle),
width: length,
height: 1,
background: color,
transform: 'rotate(' + angle + 'rad)',
transformOrigin: '0 0'
}).appendTo(document.body);
if(originMark) {
$('<div>').css({
position: 'fixed',
'box-sizing': 'border-box',
left: start.x - 2,
top: start.y - 2,
width: 0,
height: 0,
border: '2px solid red',
'border-color': color,
//border: '6px solid transparent',
//'border-left-color': color,
//'margin-left': -1,
//'margin-top': -1,
//background: color,
//transform: 'rotate(' + angle + 'rad)',
transformOrigin: '0 0'
}).appendTo(document.body);
}
}
function debugKeepInsideScreen(obj: fg.physics.DynamicObject) {
var w = $(window).width();
var h = $(window).height();
if(obj.position.x < 0)
obj.position.x += w;
if(obj.position.x > w)
obj.position.x -= w;
if(obj.position.y < 0)
obj.position.y += h;
if(obj.position.y > h)
obj.position.y -= h;
}
function testCouple(w1, h1, x1, y1, w2, h2, x2, y2) {
var s1 = new fg.physics.StaticObject(new fg.math.Vector2(w1, h1), new fg.math.Vector2(x1, y1));
var s2 = new fg.physics.StaticObject(new fg.math.Vector2(w2, h2), new fg.math.Vector2(x2, y2));
debugDrawStaticObject(s1, 'rgba(255, 0, 0, 0.1)');
debugDrawStaticObject(s2, 'rgba(0, 0, 255, 0.1)');
var collision = s1.getCollisionResolutionVector(s2);
if(collision) {
debugDrawLine(s1.position.add(s1.size.div(2)), s1.position.add(s1.size.div(2)).add(collision), true, 'red');
debugDrawLine(s2.position.add(s2.size.div(2)), s2.position.add(s2.size.div(2)).add(collision.mul(-1)), true, 'blue');
}
}
/*$(() => {
testCouple(100, 100, 10, 10, 80, 50, 100, 30);
testCouple(100, 100, 270, 10, 100, 50, 190, 30);
testCouple(100, 100, 380, 60, 30, 80, 400, 10);
testCouple(100, 100, 500, 40, 50, 60, 490, 10);
testCouple(100, 100, 610, 10, 60, 60, 640, 40);
});*/
import Vector2 = fg.math.Vector2;
import DynamicObject = fg.physics.DynamicObject;
var obj1 = new DynamicObject(new Vector2(100, 100), new Vector2(100, 100), new Vector2(+20, +5), 4);
var obj2 = new DynamicObject(new Vector2(50, 50), new Vector2(300, 200), new Vector2(-60, -30), 1);
function drawDebugScene() {
$(document.body).empty();
debugDrawStaticObject(obj1, 'rgba(255, 0, 0, 0.1)');
debugDrawStaticObject(obj2, 'rgba(0, 0, 255, 0.1)');
var collision = obj1.getCollisionResolutionVector(obj2);
if(collision) {
debugDrawLine(obj1.position.add(obj1.size.div(2)), obj1.position.add(obj1.size.div(2)).add(collision), true, 'red');
debugDrawLine(obj2.position.add(obj2.size.div(2)), obj2.position.add(obj2.size.div(2)).add(collision.mul(-1)), true, 'blue');
}
}
var lastFrame = new Date().getTime() / 1000;
setInterval(() => {
var now = new Date().getTime() / 1000;
var timePassed = now - lastFrame;
lastFrame = now;
obj1.physicsStep(timePassed);
obj2.physicsStep(timePassed);
debugKeepInsideScreen(obj1);
debugKeepInsideScreen(obj2);
drawDebugScene();
obj1.resolveCollision(obj2);
//obj2.resolveCollision(obj1);
drawDebugScene();
}, 2);
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Physics Test</title>
<script type="text/javascript" src="//code.jquery.com/jquery-1.11.2.min.js"></script>
<script type="text/javascript" src="out/physics.js"></script>
</head>
<body>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment