Created
March 26, 2015 09:22
-
-
Save bruce965/e4629ca864138e5458cf to your computer and use it in GitHub Desktop.
Simple Physics Simulation
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
@tsc --sourceMap --out out/physics.js Physics.ts | |
@if not "%errorlevel%"=="0" @pause |
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
@if exist "./docs/" @rmdir "./docs/" /S /Q | |
@typedoc --readme none --name Physics Test --hideGenerator --out ./docs/ Physics.ts | |
@if not "%errorlevel%"=="0" @pause |
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
/// <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); | |
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
<!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