UNPKG

cannon-es-control

Version:

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

302 lines (248 loc) 11.4 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>cannon.js - worker example using SharedArrayBuffer</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-control.js' import * as THREE from 'https://unpkg.com/three@0.122.0/build/three.module.js' import { geometryToShape } from './js/three-conversion-utils.js' const timeStep = 1 / 60 // The bodies passed down from three.js const bodies = [] let positions let quaternions // 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) // When we get the init message self.addEventListener('message', (event) => { const { geometries: geometriesSerialized, positionsSharedBuffer, quaternionsSharedBuffer } = event.data // Parse the serialized geometries received from // the main thread const unserialized = new THREE.ObjectLoader().parseGeometries(geometriesSerialized) const geometries = geometriesSerialized.map(g => unserialized[g.uuid]) // Save the reference to the SharedArrayBuffer positions = new Float32Array(positionsSharedBuffer) quaternions = new Float32Array(quaternionsSharedBuffer) // Create the bodies from the three.js geometries for (let i = 0; i < geometries.length; i++) { const geometry = geometries[i] const shape = geometryToShape(geometry) const body = new CANNON.Body({ mass: 1 }) body.addShape(shape) body.position.set(positions[i * 3 + 0], positions[i * 3 + 1], positions[i * 3 + 2]) body.quaternion.set( quaternions[i * 4 + 0], quaternions[i * 4 + 1], quaternions[i * 4 + 2], quaternions[i * 4 + 3] ) bodies.push(body) world.addBody(body) } // Start the loop setInterval(update, timeStep * 1000) }) function update() { // 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 } } </script> <script type="module"> import * as CANNON from '../dist/cannon-es.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' // three.js variables let camera, scene, renderer, stats let controls // The meshes that will be passed to cannon.js const meshes = [] // The SharedArrayBuffers. They contain all our kinematic data we need for rendering. // SharedArrayBuffers are shared between the main // and worker thread. Cannon.js will update the data while // three.js will read from them. let positions let quaternions initThree() initCannonWorker() animate() 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.PlaneGeometry(100, 100, 1, 1) floorGeometry.rotateX(-Math.PI / 2) const floorMaterial = new THREE.MeshStandardMaterial({ color: 0x777777 }) const floor = new THREE.Mesh(floorGeometry, floorMaterial) floor.receiveShadow = true scene.add(floor) // Cubes const cubeGeometry = new THREE.BoxBufferGeometry() const cubeMaterial = new THREE.MeshStandardMaterial({ color: '#888' }) for (let i = 0; i < 40; i++) { const cubeMesh = new THREE.Mesh(cubeGeometry, cubeMaterial) cubeMesh.position.set(Math.random() - 0.5, i * 2.5 + 0.5, Math.random() - 0.5) cubeMesh.castShadow = true cubeMesh.receiveShadow = true meshes.push(cubeMesh) scene.add(cubeMesh) } // Torus knots const torusGeometry = new THREE.TorusKnotBufferGeometry() const torusMaterial = new THREE.MeshStandardMaterial({ color: '#2b4c7f' }) for (let i = 0; i < 8; i++) { const torusMesh = new THREE.Mesh(torusGeometry, torusMaterial) torusMesh.position.set((Math.random() - 0.5) * 20, i * 10 + 20, (Math.random() - 0.5) * 20) // random rotation torusMesh.quaternion.setFromEuler( new THREE.Euler(Math.random() * Math.PI * 2, Math.random() * Math.PI * 2, Math.random() * Math.PI * 2) ) torusMesh.castShadow = true torusMesh.receiveShadow = true meshes.push(torusMesh) scene.add(torusMesh) } window.addEventListener('resize', onWindowResize) } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight camera.updateProjectionMatrix() renderer.setSize(window.innerWidth, window.innerHeight) } function initCannonWorker() { // Initialize the SharedArrayBuffers. // SharedArrayBuffers are shared between the main // and worker thread. Cannon.js will update the data while // three.js will read from them. const positionsSharedBuffer = new SharedArrayBuffer(meshes.length * 3 * Float32Array.BYTES_PER_ELEMENT) const quaternionsSharedBuffer = new SharedArrayBuffer(meshes.length * 4 * Float32Array.BYTES_PER_ELEMENT) positions = new Float32Array(positionsSharedBuffer) quaternions = new Float32Array(quaternionsSharedBuffer) // Copy the initial meshes data into the buffers for (let i = 0; i < meshes.length; i++) { const mesh = meshes[i] positions[i * 3 + 0] = mesh.position.x positions[i * 3 + 1] = mesh.position.y positions[i * 3 + 2] = mesh.position.z quaternions[i * 4 + 0] = mesh.quaternion.x quaternions[i * 4 + 1] = mesh.quaternion.y quaternions[i * 4 + 2] = mesh.quaternion.z quaternions[i * 4 + 3] = mesh.quaternion.w } // 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_sharedarraybuffer', '') 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' }) worker.addEventListener('message', (event) => { console.log('Message from worker', event.data) }) worker.addEventListener('error', (event) => { console.error('Error in worker', event.message) }) // Send the geometry data to setup the cannon.js bodies // and the initial position and rotation data worker.postMessage({ // serialize the geometries as json to pass // them to the worker geometries: meshes.map((m) => m.geometry.toJSON()), positionsSharedBuffer, quaternionsSharedBuffer, }) } function animate() { requestAnimationFrame(animate) // Read from the SharedArrayBuffers and // 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] ) } controls.update() renderer.render(scene, camera) stats.update() } </script> </body> </html>