cannon
Version:
A lightweight 3D physics engine written in JavaScript.
408 lines (310 loc) • 15.9 kB
HTML
<html>
<head>
<meta charset="utf-8">
<title>cannon.js + three.js physics shooter</title>
<style>
html, body {
width: 100%;
height: 100%;
}
body {
background-color: #ffffff;
margin: 0;
overflow: hidden;
font-family: arial;
}
#blocker {
position: absolute;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
}
#instructions {
width: 100%;
height: 100%;
display: -webkit-box;
display: -moz-box;
display: box;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
box-orient: horizontal;
-webkit-box-pack: center;
-moz-box-pack: center;
box-pack: center;
-webkit-box-align: center;
-moz-box-align: center;
box-align: center;
color: #ffffff;
text-align: center;
cursor: pointer;
}
</style>
</head>
<body>
<script src="../libs/Three.js"></script>
<script src="../build/cannon.js"></script>
<script src="js/PointerLockControls.js"></script>
<div id="blocker">
<div id="instructions">
<span style="font-size:40px">Click to play</span>
<br />
(W,A,S,D = Move, SPACE = Jump, MOUSE = Look, CLICK = Shoot)
</div>
</div>
<script>
var sphereShape, sphereBody, world, physicsMaterial, walls=[], balls=[], ballMeshes=[], boxes=[], boxMeshes=[];
var camera, scene, renderer;
var geometry, material, mesh;
var controls,time = Date.now();
var blocker = document.getElementById( 'blocker' );
var instructions = document.getElementById( 'instructions' );
var havePointerLock = 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document;
if ( havePointerLock ) {
var element = document.body;
var pointerlockchange = function ( event ) {
if ( document.pointerLockElement === element || document.mozPointerLockElement === element || document.webkitPointerLockElement === element ) {
controls.enabled = true;
blocker.style.display = 'none';
} else {
controls.enabled = false;
blocker.style.display = '-webkit-box';
blocker.style.display = '-moz-box';
blocker.style.display = 'box';
instructions.style.display = '';
}
}
var pointerlockerror = function ( event ) {
instructions.style.display = '';
}
// Hook pointer lock state change events
document.addEventListener( 'pointerlockchange', pointerlockchange, false );
document.addEventListener( 'mozpointerlockchange', pointerlockchange, false );
document.addEventListener( 'webkitpointerlockchange', pointerlockchange, false );
document.addEventListener( 'pointerlockerror', pointerlockerror, false );
document.addEventListener( 'mozpointerlockerror', pointerlockerror, false );
document.addEventListener( 'webkitpointerlockerror', pointerlockerror, false );
instructions.addEventListener( 'click', function ( event ) {
instructions.style.display = 'none';
// Ask the browser to lock the pointer
element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock;
if ( /Firefox/i.test( navigator.userAgent ) ) {
var fullscreenchange = function ( event ) {
if ( document.fullscreenElement === element || document.mozFullscreenElement === element || document.mozFullScreenElement === element ) {
document.removeEventListener( 'fullscreenchange', fullscreenchange );
document.removeEventListener( 'mozfullscreenchange', fullscreenchange );
element.requestPointerLock();
}
}
document.addEventListener( 'fullscreenchange', fullscreenchange, false );
document.addEventListener( 'mozfullscreenchange', fullscreenchange, false );
element.requestFullscreen = element.requestFullscreen || element.mozRequestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen;
element.requestFullscreen();
} else {
element.requestPointerLock();
}
}, false );
} else {
instructions.innerHTML = 'Your browser doesn\'t seem to support Pointer Lock API';
}
initCannon();
init();
animate();
function initCannon(){
// Setup our world
world = new CANNON.World();
world.quatNormalizeSkip = 0;
world.quatNormalizeFast = false;
var solver = new CANNON.GSSolver();
world.defaultContactMaterial.contactEquationStiffness = 1e9;
world.defaultContactMaterial.contactEquationRelaxation = 4;
solver.iterations = 7;
solver.tolerance = 0.1;
var split = true;
if(split)
world.solver = new CANNON.SplitSolver(solver);
else
world.solver = solver;
world.gravity.set(0,-20,0);
world.broadphase = new CANNON.NaiveBroadphase();
// Create a slippery material (friction coefficient = 0.0)
physicsMaterial = new CANNON.Material("slipperyMaterial");
var physicsContactMaterial = new CANNON.ContactMaterial(physicsMaterial,
physicsMaterial,
0.0, // friction coefficient
0.3 // restitution
);
// We must add the contact materials to the world
world.addContactMaterial(physicsContactMaterial);
// Create a sphere
var mass = 5, radius = 1.3;
sphereShape = new CANNON.Sphere(radius);
sphereBody = new CANNON.Body({ mass: mass });
sphereBody.addShape(sphereShape);
sphereBody.position.set(0,5,0);
sphereBody.linearDamping = 0.9;
world.add(sphereBody);
// Create a plane
var groundShape = new CANNON.Plane();
var groundBody = new CANNON.Body({ mass: 0 });
groundBody.addShape(groundShape);
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1,0,0),-Math.PI/2);
world.add(groundBody);
}
function init() {
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
scene = new THREE.Scene();
scene.fog = new THREE.Fog( 0x000000, 0, 500 );
var ambient = new THREE.AmbientLight( 0x111111 );
scene.add( ambient );
light = new THREE.SpotLight( 0xffffff );
light.position.set( 10, 30, 20 );
light.target.position.set( 0, 0, 0 );
if(true){
light.castShadow = true;
light.shadowCameraNear = 20;
light.shadowCameraFar = 50;//camera.far;
light.shadowCameraFov = 40;
light.shadowMapBias = 0.1;
light.shadowMapDarkness = 0.7;
light.shadowMapWidth = 2*512;
light.shadowMapHeight = 2*512;
//light.shadowCameraVisible = true;
}
scene.add( light );
controls = new PointerLockControls( camera , sphereBody );
scene.add( controls.getObject() );
// floor
geometry = new THREE.PlaneGeometry( 300, 300, 50, 50 );
geometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) );
material = new THREE.MeshLambertMaterial( { color: 0xdddddd } );
mesh = new THREE.Mesh( geometry, material );
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add( mesh );
renderer = new THREE.WebGLRenderer();
renderer.shadowMapEnabled = true;
renderer.shadowMapSoft = true;
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( scene.fog.color, 1 );
document.body.appendChild( renderer.domElement );
window.addEventListener( 'resize', onWindowResize, false );
// Add boxes
var halfExtents = new CANNON.Vec3(1,1,1);
var boxShape = new CANNON.Box(halfExtents);
var boxGeometry = new THREE.BoxGeometry(halfExtents.x*2,halfExtents.y*2,halfExtents.z*2);
for(var i=0; i<7; i++){
var x = (Math.random()-0.5)*20;
var y = 1 + (Math.random()-0.5)*1;
var z = (Math.random()-0.5)*20;
var boxBody = new CANNON.Body({ mass: 5 });
boxBody.addShape(boxShape);
var boxMesh = new THREE.Mesh( boxGeometry, material );
world.add(boxBody);
scene.add(boxMesh);
boxBody.position.set(x,y,z);
boxMesh.position.set(x,y,z);
boxMesh.castShadow = true;
boxMesh.receiveShadow = true;
boxes.push(boxBody);
boxMeshes.push(boxMesh);
}
// Add linked boxes
var size = 0.5;
var he = new CANNON.Vec3(size,size,size*0.1);
var boxShape = new CANNON.Box(he);
var mass = 0;
var space = 0.1 * size;
var N = 5, last;
var boxGeometry = new THREE.BoxGeometry(he.x*2,he.y*2,he.z*2);
for(var i=0; i<N; i++){
var boxbody = new CANNON.Body({ mass: mass });
boxbody.addShape(boxShape);
var boxMesh = new THREE.Mesh(boxGeometry, material);
boxbody.position.set(5,(N-i)*(size*2+2*space) + size*2+space,0);
boxbody.linearDamping = 0.01;
boxbody.angularDamping = 0.01;
// boxMesh.castShadow = true;
boxMesh.receiveShadow = true;
world.add(boxbody);
scene.add(boxMesh);
boxes.push(boxbody);
boxMeshes.push(boxMesh);
if(i!=0){
// Connect this body to the last one
var c1 = new CANNON.PointToPointConstraint(boxbody,new CANNON.Vec3(-size,size+space,0),last,new CANNON.Vec3(-size,-size-space,0));
var c2 = new CANNON.PointToPointConstraint(boxbody,new CANNON.Vec3(size,size+space,0),last,new CANNON.Vec3(size,-size-space,0));
world.addConstraint(c1);
world.addConstraint(c2);
} else {
mass=0.3;
}
last = boxbody;
}
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
var dt = 1/60;
function animate() {
requestAnimationFrame( animate );
if(controls.enabled){
world.step(dt);
// Update ball positions
for(var i=0; i<balls.length; i++){
ballMeshes[i].position.copy(balls[i].position);
ballMeshes[i].quaternion.copy(balls[i].quaternion);
}
// Update box positions
for(var i=0; i<boxes.length; i++){
boxMeshes[i].position.copy(boxes[i].position);
boxMeshes[i].quaternion.copy(boxes[i].quaternion);
}
}
controls.update( Date.now() - time );
renderer.render( scene, camera );
time = Date.now();
}
var ballShape = new CANNON.Sphere(0.2);
var ballGeometry = new THREE.SphereGeometry(ballShape.radius, 32, 32);
var shootDirection = new THREE.Vector3();
var shootVelo = 15;
var projector = new THREE.Projector();
function getShootDir(targetVec){
var vector = targetVec;
targetVec.set(0,0,1);
projector.unprojectVector(vector, camera);
var ray = new THREE.Ray(sphereBody.position, vector.sub(sphereBody.position).normalize() );
targetVec.copy(ray.direction);
}
window.addEventListener("click",function(e){
if(controls.enabled==true){
var x = sphereBody.position.x;
var y = sphereBody.position.y;
var z = sphereBody.position.z;
var ballBody = new CANNON.Body({ mass: 1 });
ballBody.addShape(ballShape);
var ballMesh = new THREE.Mesh( ballGeometry, material );
world.add(ballBody);
scene.add(ballMesh);
ballMesh.castShadow = true;
ballMesh.receiveShadow = true;
balls.push(ballBody);
ballMeshes.push(ballMesh);
getShootDir(shootDirection);
ballBody.velocity.set( shootDirection.x * shootVelo,
shootDirection.y * shootVelo,
shootDirection.z * shootVelo);
// Move the ball outside the player sphere
x += shootDirection.x * (sphereShape.radius*1.02 + ballShape.radius);
y += shootDirection.y * (sphereShape.radius*1.02 + ballShape.radius);
z += shootDirection.z * (sphereShape.radius*1.02 + ballShape.radius);
ballBody.position.set(x,y,z);
ballMesh.position.set(x,y,z);
}
});
</script>
</body>
</html>