UNPKG

cannon-es-control

Version:

A lightweight 3D physics engine written in JavaScript with control system tools

329 lines (264 loc) 10.6 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>cannon.js + three.js - voxel shooter</title> <link rel="stylesheet" href="css/style.css" type="text/css" /> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" /> <style> #instructions { position: fixed; left: 0; top: 0; width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; background-color: rgba(0, 0, 0, 0.5); color: #ffffff; text-align: center; cursor: pointer; } #instructions span { font-size: 40px; } </style> <script type="module"> import { addTitle, addSourceButton } from './js/dom-utils.js' addTitle() addSourceButton() </script> </head> <body> <div id="instructions"> <span>Click to play</span> <br /> (W,A,S,D = Move, SPACE = Jump, MOUSE = Look, CLICK = Shoot) </div> <script type="module"> import * as CANNON from '../dist/cannon-es-control.js' import * as THREE from 'https://unpkg.com/three@0.122.0/build/three.module.js' import Stats from 'https://unpkg.com/three@0.122.0/examples/jsm/libs/stats.module.js' import { PointerLockControlsCannon } from './js/PointerLockControlsCannon.js' import { VoxelLandscape } from './js/VoxelLandscape.js' /** * Example construction of a voxel world and player. */ // three.js variables let camera, scene, renderer, stats let material let floor // cannon.js variables let world let controls const timeStep = 1 / 60 let lastCallTime = performance.now() / 1000 let sphereShape let sphereBody let physicsMaterial let voxels const balls = [] const ballMeshes = [] const boxes = [] const boxMeshes = [] // Number of voxels const nx = 50 const ny = 8 const nz = 50 // Scale of voxels const sx = 0.5 const sy = 0.5 const sz = 0.5 initThree() initCannon() initPointerLock() animate() function initThree() { // Camera camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000) // Scene scene = new THREE.Scene() scene.fog = new THREE.Fog(0x000000, 0, 500) // Renderer renderer = new THREE.WebGLRenderer({ antialias: true }) renderer.setSize(window.innerWidth, window.innerHeight) renderer.setClearColor(scene.fog.color) renderer.shadowMap.enabled = true renderer.shadowMap.type = THREE.PCFSoftShadowMap document.body.appendChild(renderer.domElement) // Stats.js stats = new Stats() document.body.appendChild(stats.dom) // Lights const ambientLight = new THREE.AmbientLight(0xffffff, 0.1) scene.add(ambientLight) const spotlight = new THREE.SpotLight(0xffffff, 0.7, 0, Math.PI / 4, 1) spotlight.position.set(10, 30, 20) spotlight.target.position.set(0, 0, 0) spotlight.castShadow = true spotlight.shadow.camera.near = 20 spotlight.shadow.camera.far = 50 spotlight.shadow.camera.fov = 40 spotlight.shadow.bias = -0.001 spotlight.shadow.mapSize.width = 2048 spotlight.shadow.mapSize.height = 2048 scene.add(spotlight) // Generic material material = new THREE.MeshLambertMaterial({ color: 0xdddddd }) // Floor const floorGeometry = new THREE.PlaneBufferGeometry(300, 300, 50, 50) floorGeometry.rotateX(-Math.PI / 2) floor = new THREE.Mesh(floorGeometry, material) floor.receiveShadow = true scene.add(floor) window.addEventListener('resize', onWindowResize) } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight camera.updateProjectionMatrix() renderer.setSize(window.innerWidth, window.innerHeight) } function initCannon() { // Setup world world = new CANNON.World() // Tweak contact properties. // Contact stiffness - use to make softer/harder contacts world.defaultContactMaterial.contactEquationStiffness = 1e9 // Stabilization time in number of timesteps world.defaultContactMaterial.contactEquationRelaxation = 4 const solver = new CANNON.GSSolver() solver.iterations = 7 solver.tolerance = 0.1 world.solver = new CANNON.SplitSolver(solver) // use this to test non-split solver // world.solver = solver world.gravity.set(0, -20, 0) world.broadphase.useBoundingBoxes = true // Create a slippery material (friction coefficient = 0.0) physicsMaterial = new CANNON.Material('physics') const physics_physics = new CANNON.ContactMaterial(physicsMaterial, physicsMaterial, { friction: 0.0, restitution: 0.3, }) // We must add the contact materials to the world world.addContactMaterial(physics_physics) // Create the user collision sphere const radius = 1.3 sphereShape = new CANNON.Sphere(radius) sphereBody = new CANNON.Body({ mass: 5, material: physicsMaterial }) sphereBody.addShape(sphereShape) sphereBody.position.set(nx * sx * 0.5, ny * sy + radius * 2, nz * sz * 0.5) sphereBody.linearDamping = 0.9 world.addBody(sphereBody) // Create the ground plane const groundShape = new CANNON.Plane() const groundBody = new CANNON.Body({ mass: 0, material: physicsMaterial }) groundBody.addShape(groundShape) groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0) world.addBody(groundBody) // Voxels voxels = new VoxelLandscape(world, nx, ny, nz, sx, sy, sz) for (let i = 0; i < nx; i++) { for (let j = 0; j < ny; j++) { for (let k = 0; k < nz; k++) { let filled = true // Insert map constructing logic here if (Math.sin(i * 0.1) * Math.sin(k * 0.1) < (j / ny) * 2 - 1) { filled = false } voxels.setFilled(i, j, k, filled) } } } voxels.update() console.log(`${voxels.boxes.length} voxel physics bodies`) // Voxel meshes for (let i = 0; i < voxels.boxes.length; i++) { const box = voxels.boxes[i] const voxelGeometry = new THREE.BoxBufferGeometry(voxels.sx * box.nx, voxels.sy * box.ny, voxels.sz * box.nz) const voxelMesh = new THREE.Mesh(voxelGeometry, material) voxelMesh.castShadow = true voxelMesh.receiveShadow = true boxMeshes.push(voxelMesh) scene.add(voxelMesh) } // The shooting balls const shootVelocity = 15 const ballShape = new CANNON.Sphere(0.2) const ballGeometry = new THREE.SphereBufferGeometry(ballShape.radius, 32, 32) // Returns a vector pointing the the diretion the camera is at function getShootDirection() { const vector = new THREE.Vector3(0, 0, 1) vector.unproject(camera) const ray = new THREE.Ray(sphereBody.position, vector.sub(sphereBody.position).normalize()) return ray.direction } window.addEventListener('click', (event) => { if (!controls.enabled) { return } const ballBody = new CANNON.Body({ mass: 1 }) ballBody.addShape(ballShape) const ballMesh = new THREE.Mesh(ballGeometry, material) ballMesh.castShadow = true ballMesh.receiveShadow = true world.addBody(ballBody) scene.add(ballMesh) balls.push(ballBody) ballMeshes.push(ballMesh) const shootDirection = getShootDirection() ballBody.velocity.set( shootDirection.x * shootVelocity, shootDirection.y * shootVelocity, shootDirection.z * shootVelocity ) // Move the ball outside the player sphere const x = sphereBody.position.x + shootDirection.x * (sphereShape.radius * 1.02 + ballShape.radius) const y = sphereBody.position.y + shootDirection.y * (sphereShape.radius * 1.02 + ballShape.radius) const z = sphereBody.position.z + shootDirection.z * (sphereShape.radius * 1.02 + ballShape.radius) ballBody.position.set(x, y, z) ballMesh.position.copy(ballBody.position) }) } function initPointerLock() { controls = new PointerLockControlsCannon(camera, sphereBody) scene.add(controls.getObject()) instructions.addEventListener('click', () => { controls.lock() }) controls.addEventListener('lock', () => { controls.enabled = true instructions.style.display = 'none' }) controls.addEventListener('unlock', () => { controls.enabled = false instructions.style.display = null }) } function animate() { requestAnimationFrame(animate) const time = performance.now() / 1000 const dt = time - lastCallTime lastCallTime = time if (controls.enabled) { world.step(timeStep, dt) // Update ball positions for (let i = 0; i < balls.length; i++) { ballMeshes[i].position.copy(balls[i].position) ballMeshes[i].quaternion.copy(balls[i].quaternion) } // Update box positions for (let i = 0; i < voxels.boxes.length; i++) { boxMeshes[i].position.copy(voxels.boxes[i].position) boxMeshes[i].quaternion.copy(voxels.boxes[i].quaternion) } } controls.update(dt) renderer.render(scene, camera) stats.update() } </script> </body> </html>