p2s
Version:
A JavaScript 2D physics engine.
418 lines (350 loc) • 12.3 kB
HTML
<html lang="en">
<head>
<title>p2.js Canvas raycast example</title>
<meta charset="utf-8">
<script src="../../build/p2.js"></script>
</head>
<body>
<!-- The canvas, where we draw stuff -->
<canvas width="600" height="400" id="myCanvas"></canvas>
<input type="checkbox" id="reflect"> Reflect
<script>
var canvas, ctx, w, h, world, boxBody, planeBody, planeShape, lineBody, lineShape;
var scaleX = 50, scaleY = -50;
var start = [0,0];
var end = [0,0];
var direction = [0,0];
var reflect = false;
var result = new p2.RaycastResult();
var hitPoint = p2.vec2.create();
var ray = new p2.Ray({
mode: p2.Ray.CLOSEST
});
document.getElementById("reflect").addEventListener('change', function(evt){
reflect = document.getElementById("reflect").checked;
});
init();
requestAnimationFrame(animate);
function init(){
// Init canvas
canvas = document.getElementById("myCanvas");
w = canvas.width;
h = canvas.height;
ctx = canvas.getContext("2d");
ctx.lineWidth = 0.02;
ctx.fillStyle = 'white';
// Init p2.js
world = new p2.World({
gravity: [0, 0]
});
// Add a box
boxShape = new p2.Box({ width: 2, height: 1 });
boxBody = new p2.Body({
mass:1,
position:[0,2],
angularVelocity:0,
angularDamping: 0
});
boxBody.addShape(boxShape, [0,0], 0);
world.addBody(boxBody);
// Add a circle
circleShape = new p2.Circle({ radius: 0.5 });
circleBody = new p2.Body({
mass:1,
position:[0,-1],
angularVelocity:1
});
circleBody.addShape(circleShape,[0.5,0]);
world.addBody(circleBody);
// Add a capsule
capsuleShape = new p2.Capsule({ length: 1, radius: 0.5 });
capsuleBody = new p2.Body({
mass:1,
position:[-1.5,0],
angularVelocity:1,
angularDamping: 0
});
capsuleBody.addShape(capsuleShape);
world.addBody(capsuleBody);
// Add a plane
planeShape = new p2.Plane();
planeBody = new p2.Body({
position: [3,0],
angle: Math.PI / 3
});
planeBody.addShape(planeShape, [-1,0], Math.PI / 16);
world.addBody(planeBody);
// Add a line
lineShape = new p2.Line({ length: 2 });
lineBody = new p2.Body({
position: [2,0],
angle: Math.PI / 3
});
lineBody.addShape(lineShape, [0,0], Math.PI / 16);
world.addBody(lineBody);
// Add a convex
var vertices = [];
var size = 2;
for(var i=0, N=3; i<N; i++){
var a = 2*Math.PI / N * i;
var vertex = [size*0.5*Math.cos(a), size*0.5*Math.sin(a)]; // Note: vertices are added counter-clockwise
vertices.push(vertex);
}
convexShape = new p2.Convex({ vertices: vertices });
convexBody = new p2.Body({
mass: 1,
position: [1,0],
angle: Math.PI / 3,
angularVelocity: 1
});
convexBody.addShape(convexShape);
world.addBody(convexBody);
// Heightfield
var data = [];
var numDataPoints = 200;
for(var i=0; i<numDataPoints; i++){
data.push(0.1*Math.sin(i / numDataPoints * Math.PI * 8));
}
var heightfieldShape = new p2.Heightfield({
heights: data,
elementWidth: 5 / numDataPoints
});
var heightfield = new p2.Body({
position:[2,-2],
angle: Math.PI / 2
});
heightfield.addShape(heightfieldShape);
world.addBody(heightfield);
}
function drawbox(){
ctx.beginPath();
var x = boxBody.interpolatedPosition[0],
y = boxBody.interpolatedPosition[1];
ctx.save();
ctx.translate(x, y); // Translate to the center of the box
ctx.rotate(boxBody.interpolatedAngle); // Rotate to the box body frame
ctx.translate(boxShape.position[0], boxShape.position[1]); // Translate to the center of the shape
ctx.rotate(boxShape.angle); // Rotate to the box shape frame
ctx.rect(-boxShape.width/2, -boxShape.height/2, boxShape.width, boxShape.height);
ctx.stroke();
ctx.restore();
}
function drawPlane(){
ctx.beginPath();
var x = planeBody.interpolatedPosition[0],
y = planeBody.interpolatedPosition[1];
ctx.save();
ctx.translate(x, y);
ctx.rotate(planeBody.interpolatedAngle);
ctx.translate(planeShape.position[0], planeShape.position[1]);
ctx.rotate(planeShape.angle);
ctx.moveTo(-100, 0);
ctx.lineTo(100, 0);
ctx.stroke();
ctx.restore();
}
function drawLine(){
ctx.beginPath();
var x = lineBody.interpolatedPosition[0],
y = lineBody.interpolatedPosition[1];
ctx.save();
ctx.translate(x, y);
ctx.rotate(lineBody.interpolatedAngle);
ctx.translate(lineShape.position[0], lineShape.position[1]);
ctx.rotate(lineShape.angle);
ctx.moveTo(-lineShape.length/2, 0);
ctx.lineTo(lineShape.length/2, 0);
ctx.stroke();
ctx.restore();
}
function drawCircle(){
ctx.beginPath();
var x = circleBody.interpolatedPosition[0],
y = circleBody.interpolatedPosition[1];
ctx.save();
ctx.translate(x, y);
ctx.rotate(circleBody.interpolatedAngle);
ctx.translate(circleShape.position[0], circleShape.position[1]);
ctx.rotate(circleShape.angle);
ctx.arc(0,0,circleShape.radius,0,2*Math.PI);
ctx.stroke();
ctx.restore();
}
function drawCapsule(){
var x = capsuleBody.interpolatedPosition[0],
y = capsuleBody.interpolatedPosition[1];
ctx.save();
ctx.translate(x, y);
ctx.rotate(capsuleBody.interpolatedAngle);
ctx.translate(capsuleShape.position[0], capsuleShape.position[1]);
ctx.rotate(capsuleShape.angle);
var radius = capsuleShape.radius;
var len = capsuleShape.length;
// Draw circles at ends
ctx.beginPath();
ctx.arc(-len/2, 0, capsuleShape.radius, 0, 2*Math.PI);
ctx.arc( len/2, 0, capsuleShape.radius, -Math.PI, Math.PI);
ctx.fill();
ctx.stroke();
// Draw rectangle
ctx.beginPath();
ctx.moveTo(-len/2, -radius);
ctx.lineTo( len/2, -radius);
ctx.lineTo( len/2, radius);
ctx.lineTo(-len/2, radius);
ctx.fill();
// Draw lines in between
ctx.beginPath();
ctx.moveTo(-len/2, -radius);
ctx.lineTo( len/2, -radius);
ctx.stroke();
ctx.beginPath();
ctx.lineTo( len/2, radius);
ctx.lineTo(-len/2, radius);
ctx.stroke();
ctx.restore();
}
function drawConvex(){
ctx.beginPath();
var x = convexBody.interpolatedPosition[0],
y = convexBody.interpolatedPosition[1];
ctx.save();
ctx.translate(x, y);
ctx.rotate(convexBody.interpolatedAngle);
ctx.moveTo(convexShape.vertices[0][0], convexShape.vertices[0][1]);
for (var i = 1; i < convexShape.vertices.length+1; i++) {
ctx.lineTo(convexShape.vertices[i%convexShape.vertices.length][0], convexShape.vertices[i%convexShape.vertices.length][1]);
}
ctx.stroke();
ctx.restore();
}
function drawRay(start, end){
// Draw line
ctx.beginPath();
ctx.moveTo(start[0], start[1]);
ctx.lineTo(end[0], end[1]);
ctx.stroke();
}
function drawRayResult(result){
// Draw hit point
if(result.hasHit){
ctx.beginPath();
ctx.arc(result.hitPointWorld[0],result.hitPointWorld[1],0.1,0,2*Math.PI);
ctx.stroke();
}
// Draw hit normal
ctx.beginPath();
ctx.moveTo(result.hitPointWorld[0], result.hitPointWorld[1]);
ctx.lineTo(
result.hitPointWorld[0] + result.normal[0],
result.hitPointWorld[1] + result.normal[1]
);
ctx.stroke();
};
var vec2 = p2.vec2;
var airIndex = 1;
var shapeIndex = 1.5;
function refract(out, direction, normal, airIndex, shapeIndex){
var dot = p2.vec2.dot(normal, direction);
var tangent = p2.vec2.fromValues(normal[0], normal[1]);
p2.vec2.rotate(tangent, tangent, -Math.PI / 2);
var outAngle;
var side = p2.vec2.dot(tangent, direction);
if(dot < 0){
// Into the material
dot = p2.vec2.dot(normal, direction);
inAngle = Math.acos(dot);
p2.vec2.scale(normal, normal, -1);
var a = airIndex / shapeIndex * Math.sin(inAngle);
if(a <= 1){
outAngle = Math.asin(a);
// Construct new refracted direction - just rotate the negative normal
p2.vec2.rotate(out, normal, outAngle * (side < 0 ? -1 : 1));
} else {
p2.vec2.reflect(out, direction, normal);
}
} else {
// Out of the material - flip the indices
dot = p2.vec2.dot(normal, direction);
inAngle = Math.acos(dot);
var a = shapeIndex / airIndex * Math.sin(inAngle);
if(a <= 1){
outAngle = Math.asin(a);
// Construct new refracted direction - just rotate the negative normal
p2.vec2.rotate(out, normal, outAngle * (side < 0 ? 1 : -1));
} else {
p2.vec2.reflect(out, direction, normal);
}
}
}
function drawRays(time){
var N = 10;
for (var i = 0; i < N; i++) {
ray.from[0] = -3;
ray.from[1] = 0;
var angle = .5 * Math.sin(time / 1000 * 1 - 1)-0.005 * (i/N)*10 + 0.1;
ray.direction[0] = Math.cos(angle);
ray.direction[1] = Math.sin(angle);
ray.to[0] = ray.from[0] + ray.direction[0] * 100;
ray.to[1] = ray.from[1] + ray.direction[1] * 100;
ray.update();
// Closest
ctx.strokeStyle = 'blue';
var hits = 0;
while(world.raycast(result, ray) && hits++ < 10){
result.getHitPoint(hitPoint, ray);
drawRay(ray.from, hitPoint);
// move start to the hit point
p2.vec2.copy(ray.from, hitPoint);
ray.update();
if(reflect){
// reflect the direction
p2.vec2.reflect(ray.direction, ray.direction, result.normal);
} else {
refract(ray.direction, ray.direction, result.normal, airIndex, shapeIndex);
}
// move out a bit
ray.from[0] += ray.direction[0] * 0.001;
ray.from[1] += ray.direction[1] * 0.001;
ray.to[0] = ray.from[0] + ray.direction[0] * 100;
ray.to[1] = ray.from[1] + ray.direction[1] * 100;
result.reset();
}
drawRay(ray.from, ray.to);
}
ctx.strokeStyle = 'black';
}
function render(time){
// Clear the canvas
ctx.clearRect(0,0,w,h);
// Transform the canvas
ctx.save();
ctx.translate(w/2, h/2); // Translate to the center
ctx.scale(scaleX, scaleY);
// Draw all bodies
drawbox();
drawPlane();
drawCircle();
drawConvex();
drawLine();
drawCapsule();
drawRays(time);
// Restore transform
ctx.restore();
}
var lastTime, timeStep = 1 / 60, maxSubSteps = 5;
// Animation loop
function animate(time){
requestAnimationFrame(animate);
var dt = lastTime ? (time - lastTime) / 1000 : 0;
dt = Math.min(1 / 10, dt);
lastTime = time;
// Move physics bodies forward in time
world.step(timeStep, dt, maxSubSteps);
// Render scene
render(time);
}
</script>
</body>
</html>