UNPKG

p2s

Version:

A JavaScript 2D physics engine.

418 lines (350 loc) 12.3 kB
<!DOCTYPE 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>