I find a great article about physics in a game N and want to implement it in JavaScript
I would rewrite the article in a simplified way:
Collisions
All platformer games need to handle collisions. To handle them games usually:
- Get all nearest objects
- Check collision with all of them
Step 1 can be implemented using a grid system (like in mario) or with QuadTree if we do not have a regular grid.
Let's focus on a step 2 and calculate all forces.
Velocity applied from some point to the collision plane would be split into parallel and perpendicular vectors, vectors would be multiplied to friction and bounce (correspondingly) to get the after collision velocity.
Here you can play with it:
var somePoint = new Point( 40, 40 );
var plane = [
new Point( 10, h - 60 ),
new Point( w - 10, h - 60 )
];
// You can drag all this points!
plane.forEach( p => movablePoints.push( p ) );
movablePoints.push( somePoint );
var frictionSlider = new Slider( {
p1: new Point( 10, h - 30 ),
p2: new Point( w - 10, h - 30 ),
from: 0, to: 1, value: 0.5,
color: 0x880000,
label: 'friction'
} );
var bounceSlider = new Slider( {
p1: new Point( 10, h - 10 ),
p2: new Point( w - 10, h - 10 ),
from: 0, to: 1, value: 0.5,
color: 0x008800,
label: 'bounce'
} );
move = function() {
clear();
// you can drag the plane (draw draggers)
plane.forEach(p => circle( p.x, p.y, 2, 0x008800 ) );
line( plane[ 0 ], plane[ 1 ], 0x000090 );
var midPoint = plane[ 1 ].addClone( plane[ 0 ] ).div( 2 );
arrow( somePoint, midPoint, 0xCC00AA, 5 );
/* For projection — we need to be in a same coordinate system,
so we subtract the beginning of the plane from somePoint and planeEnd */
var projection = somePoint.subClone( plane[ 0 ] )
.projection( plane[ 1 ].subClone( plane[ 0 ] ) )
.add( plane[ 0 ] );
circle( somePoint.x, somePoint.y, 2, 0x008800 );
arrow( somePoint, projection, 0x6677CC, 5 );
// vector from projection point to midPoint (collision point)
var projectionToMid = midPoint.subClone( projection );
arrow( projection, projectionToMid.clone().absoluteAdd( -2 ).add( projection ), 0xCC0077, 5 );
/* Friction slider have value in range [0, 1] with initial value 0.5
By multiplying vector from projection point to collision point on `friction`
we get the result friction Vector */
var frictionVector = projectionToMid.clone().mul( frictionSlider.value );
arrow( midPoint, frictionVector.addClone( midPoint ), 0xCC0077, 5 );
// By multiplying the projectionVector on -bounce we get the bounce vector
var bounceVector = somePoint.subClone( projection ).mul( bounceSlider.value );
// Just add everything together and we get the result
var endPoint = midPoint.addClone(frictionVector).add( bounceVector );
arrow( frictionVector.addClone( midPoint ), endPoint, 0x6677CC, 5 );
arrow( midPoint, endPoint, 0xCC00AA, 5 );
// draw sliders
frictionSlider.draw();
bounceSlider.draw();
};
Great paper by David Eberly describes how we can find collision of two convex shapes. The general idea is: if we can find any axis along which the projection of the two shapes does not overlap, then the shapes don't overlap.
Separating Axis Theorem
The separating axis theorem tells us that, for two convex shapes if we can find an axis along which the projection of the two shapes does not overlap, then the shapes don't overlap.
Applying to the 2D – potential separating axes is perpendicular to one of the sides of each shape.
var points = [
new Point( 50, 50 ),
new Point( 150, 50 ),
new Point( 200, 100 ),
new Point( 40, 150 )
];
points.forEach( p => movablePoints.push( p ) );
move = function() {
clear();
points.forEach(p => circle( p.x, p.y, 2, 0x008800 ) );
for(var i = 0, _i = points.length; i < _i; i++){
var p1 = points[i], p2 = points[(i+1)%_i];
line(p1, p2, 0xCC00AA);
var center = p1.addClone(p2).div(2);
var perpendicular = p1.subClone(p2).rotate(Math.PI/2).normalize();
arrow(center, perpendicular.mulClone(30).add(center), 0x000090, 3);
var p3 = center.addClone(p2.subClone(p1).normalize().mul(5));
var squareSide = perpendicular.mulClone(5);
line(p3, p3.addClone(squareSide), 0x000090);
line(squareSide.addClone(center), p3.addClone(squareSide), 0x000090);
}
}
Rectangle
var rectangle = new Point( 120, 100 );
rectangle.width = 40;
rectangle.height = 20;
rectangle.distance = function(p){
if(p.x >= this.x-this.width &&
p.x <= this.x+this.width &&
p.y >= this.y-this.height &&
p.y <= this.y+this.height)
return 0;
return Point.prototype.distance.call(this, p);
};
var point = new Point(120, 180);
var points = [
rectangle,
point
];
points.forEach( p => movablePoints.push( p ) );
var center = new Point(w/2, h/2);
move = function() {
clear();
point.borrow(center.clone().sub(point).normalize().mul(-90).add(center))
points.forEach(p => circle( p.x, p.y, 2, 0x008800 ) );
rect(
rectangle.x-rectangle.width/2,
rectangle.y-rectangle.height/2,
rectangle.width,
rectangle.height,
rectangle.focused ? 0x9999aa : 0x7777aa
)
}