cannon-es-control
Version:
A lightweight 3D physics engine written in JavaScript with control system tools
264 lines (214 loc) • 9.2 kB
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>