UNPKG

cannon-es-control

Version:

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

264 lines (214 loc) 9.2 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>cannon.js - worker example</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" /> <script type="module"> import { addTitle, addSourceButton } from './js/dom-utils.js' addTitle() addSourceButton() </script> </head> <body> <!-- Worker script, will be run in separate thread --> <script id="worker1" type="text/js-worker"> import * as CANNON from '../dist/cannon-es.js' // Number of objects, could be also sent via a // first init message, to avoid duplicatoin const N = 40 // The bodies shared with three.js const bodies = [] // Setup world const world = new CANNON.World() world.gravity.set(0, -10, 0) world.solver.tolerance = 0.001 // Ground plane const groundShape = new CANNON.Plane() const groundBody = new CANNON.Body({ mass: 0 }) groundBody.addShape(groundShape) groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0) world.addBody(groundBody) // Create N cubes const boxShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5)) for (let i = 0; i < N; i++) { const body = new CANNON.Body({ mass: 1 }) body.addShape(boxShape) body.position.set(Math.random() - 0.5, i * 2.5 + 0.5, Math.random() - 0.5) bodies.push(body) world.addBody(body) } self.addEventListener('message', (event) => { const { positions, quaternions, timeStep } = event.data // Step the world world.step(timeStep) // Copy the cannon.js data into the buffers for (let i = 0; i < bodies.length; i++) { const body = bodies[i] positions[i * 3 + 0] = body.position.x positions[i * 3 + 1] = body.position.y positions[i * 3 + 2] = body.position.z quaternions[i * 4 + 0] = body.quaternion.x quaternions[i * 4 + 1] = body.quaternion.y quaternions[i * 4 + 2] = body.quaternion.z quaternions[i * 4 + 3] = body.quaternion.w } // Send data back to the main thread self.postMessage( { positions, quaternions, }, // Specify that we want actually transfer the memory, not copy it over. This is faster. [positions.buffer, quaternions.buffer] ) }) </script> <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 { OrbitControls } from 'https://unpkg.com/three@0.122.0/examples/jsm/controls/OrbitControls.js' // Parameters const timeStep = 1 / 60 const N = 40 // number of objects // Data arrays. Contains all our kinematic data we need for rendering. // These will be replaced with the new arrays the worker will send us. let positions = new Float32Array(N * 3) let quaternions = new Float32Array(N * 4) // Get the worker code let workerScript = document.querySelector('#worker1').textContent // BUG Relative urls don't currently work in an inline // module worker in Chrome // https://bugs.chromium.org/p/chromium/issues/detail?id=1161710 const href = window.location.href.replace('/examples/worker', '') workerScript = workerScript .replace(/from '\.\.\//g, `from '${href}/`) .replace(/from '\.\//g, `from '${href}/examples/`) // Create a blob for the inline worker code const blob = new Blob([workerScript], { type: 'text/javascript' }) // Create worker const worker = new Worker(window.URL.createObjectURL(blob), { type: 'module' }) // Time when we sent last message let sendTime // Send the array buffers that will be populated in the // worker with cannon.js' data function requestDataFromWorker() { sendTime = performance.now() worker.postMessage( { timeStep, positions, quaternions, }, // Specify that we want actually transfer the memory, not copy it over. This is faster. [positions.buffer, quaternions.buffer] ) } // The mutated position and quaternion data we // get back from the worker worker.addEventListener('message', (event) => { // Get fresh data from the worker positions = event.data.positions quaternions = event.data.quaternions // Update the three.js meshes for (let i = 0; i < meshes.length; i++) { meshes[i].position.set(positions[i * 3 + 0], positions[i * 3 + 1], positions[i * 3 + 2]) meshes[i].quaternion.set( quaternions[i * 4 + 0], quaternions[i * 4 + 1], quaternions[i * 4 + 2], quaternions[i * 4 + 3] ) } // Delay the next step by the amount of timeStep remaining, // otherwise run it immediatly const delay = timeStep * 1000 - (performance.now() - sendTime) setTimeout(requestDataFromWorker, Math.max(delay, 0)) }) worker.addEventListener('error', (event) => { console.error(event.message) }) // Initialize three.js let camera, scene, renderer, stats let controls // The meshes that will be passed to cannon.js const meshes = [] initThree() animate() // Start the worker! requestDataFromWorker() function initThree() { // Camera camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.5, 10000) camera.position.set(Math.cos(Math.PI / 5) * 30, 5, Math.sin(Math.PI / 5) * 30) // Scene scene = new THREE.Scene() scene.fog = new THREE.Fog(0x000000, 500, 10000) // 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 renderer.outputEncoding = THREE.sRGBEncoding document.body.appendChild(renderer.domElement) // Stats.js stats = new Stats() document.body.appendChild(stats.dom) // Orbit controls controls = new OrbitControls(camera, renderer.domElement) controls.enableDamping = true controls.enablePan = false controls.dampingFactor = 0.3 controls.minDistance = 10 controls.maxDistance = 500 // Lights const ambientLight = new THREE.AmbientLight(0x666666) scene.add(ambientLight) const directionalLight = new THREE.DirectionalLight(0xffffff, 1.75) const distance = 20 directionalLight.position.set(distance, distance, distance) directionalLight.castShadow = true directionalLight.shadow.mapSize.width = 1024 directionalLight.shadow.mapSize.height = 1024 directionalLight.shadow.camera.left = -distance directionalLight.shadow.camera.right = distance directionalLight.shadow.camera.top = distance directionalLight.shadow.camera.bottom = -distance directionalLight.shadow.camera.far = 3 * distance directionalLight.shadow.camera.near = distance scene.add(directionalLight) // Floor const floorGeometry = new THREE.PlaneBufferGeometry(100, 100, 1, 1) floorGeometry.rotateX(-Math.PI / 2) const floorMaterial = new THREE.MeshLambertMaterial({ color: 0x777777 }) const floor = new THREE.Mesh(floorGeometry, floorMaterial) floor.receiveShadow = true scene.add(floor) // Cubes const cubeGeometry = new THREE.BoxBufferGeometry(1, 1, 1, 10, 10) const cubeMaterial = new THREE.MeshPhongMaterial({ color: 0x888888 }) for (let i = 0; i < N; i++) { const cubeMesh = new THREE.Mesh(cubeGeometry, cubeMaterial) cubeMesh.castShadow = true meshes.push(cubeMesh) scene.add(cubeMesh) } window.addEventListener('resize', onWindowResize) } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight camera.updateProjectionMatrix() renderer.setSize(window.innerWidth, window.innerHeight) } function animate() { requestAnimationFrame(animate) controls.update() renderer.render(scene, camera) stats.update() } </script> </body> </html>