Skip to content

Commit

Permalink
Examples: compute convexHull for other shapes
Browse files Browse the repository at this point in the history
  • Loading branch information
marcofugaro committed Dec 26, 2020
1 parent e2f1dc1 commit 3b15153
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 48 deletions.
31 changes: 29 additions & 2 deletions examples/js/three-conversion-utils.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import * as CANNON from '../../dist/cannon-es.js'
import * as THREE from 'https://unpkg.com/three@0.122.0/build/three.module.js'
import { ConvexGeometry } from 'https://unpkg.com/three@0.122.0/examples/jsm/geometries/ConvexGeometry.js'
import { SimplifyModifier } from 'https://unpkg.com/three@0.122.0/examples/jsm/modifiers/SimplifyModifier.js'

/**
* Converts a cannon.js shape to a three.js geometry
* ⚠️ Warning: it will not work if the shape has been rotated
* or scaled beforehand, for example with ConvexPolyhedron.transformAllPoints().
* @param {Shape} shape The cannon.js shape
* @param {Object} options The options of the conversion
* @return {Geometry} The three.js geometry
Expand Down Expand Up @@ -165,7 +169,9 @@ export function bodyToMesh(body, material) {

/**
* Converts a three.js shape to a cannon.js geometry.
* If you want a more elaborate transformation, use this library:
* ⚠️ Warning: it will not work if the geometry has been rotated
* or scaled beforehand.
* If you want a more robust conversion, use this library:
* https://github.com/donmccurdy/three-to-cannon
* @param {Geometry} geometry The three.js geometry
* @return {Shape} The cannon.js shape
Expand Down Expand Up @@ -198,8 +204,29 @@ export function geometryToShape(geometry) {
return new CANNON.Cylinder(radiusTop, radiusBottom, height, radialSegments)
}

// Create a ConvexPolyhedron with the convex hull if
// it's none of these
default: {
throw new Error(`Geometry not recognized: "${geometry.type}"`)
// Simplify the geometry if it has too many points,
// make it have no more than MAX_VERTEX_COUNT vertices
const vertexCount = geometry.isBufferGeometry ? geometry.attributes.position.count : geometry.vertices.length

const MAX_VERTEX_COUNT = 150
const simplifiedGeometry = new SimplifyModifier().modify(geometry, Math.max(vertexCount - MAX_VERTEX_COUNT, 0))

const points = new THREE.Geometry().fromBufferGeometry(simplifiedGeometry).vertices

// Generate convex hull
const hullGeometry = new ConvexGeometry(points)

const vertices = hullGeometry.vertices.map((v) => new CANNON.Vec3().copy(v))
const faces = hullGeometry.faces.map((f) => [f.a, f.b, f.c])
const normals = hullGeometry.faces.map((f) => new CANNON.Vec3().copy(f.normal))

// Construct polyhedron
const polyhedron = new CANNON.ConvexPolyhedron({ vertices, faces, normals })

return polyhedron
}
}
}
13 changes: 9 additions & 4 deletions examples/worker.html
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,15 @@
let quaternions = new Float32Array(N * 4)

// Get the worker code
const workerScript = document
.querySelector('#worker1')
// BUG Somehow relative urls don't work in a module worker
.textContent.replace('..', window.location.origin)
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' })
Expand Down
118 changes: 76 additions & 42 deletions examples/worker_sharedarraybuffer.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<!-- Worker script, will be run in separate thread -->
<script id="worker1" type="text/js-worker">
import * as CANNON from '../dist/cannon-es.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 dt = 1 / 60
Expand All @@ -40,7 +41,12 @@

// When we get the init message
self.addEventListener('message', (event) => {
const { geometries, positionsSharedBuffer, quaternionsSharedBuffer } = event.data
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)
Expand Down Expand Up @@ -93,45 +99,20 @@
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 N = 40 // number of objects

// Data arrays. Contains all our kinematic data we need for rendering.
// SharedArrayBuffer 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(N * 3 * Float32Array.BYTES_PER_ELEMENT)
const quaternionsSharedBuffer = new SharedArrayBuffer(N * 4 * Float32Array.BYTES_PER_ELEMENT)
const positions = new Float32Array(positionsSharedBuffer)
const quaternions = new Float32Array(quaternionsSharedBuffer)

// Get the worker code
const workerScript = document
.querySelector('#worker1')
// BUG Somehow relative urls don't work in a module worker
.textContent.replace(/from '\.\.\//g, `from '${window.location.origin}/`)
.replace(/from '\.\//g, `from '${window.location.origin}/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(event.data)
})
worker.addEventListener('error', (event) => {
console.error(event.message)
})

// Initialize three.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()
Expand Down Expand Up @@ -196,22 +177,39 @@
// Floor
const floorGeometry = new THREE.PlaneGeometry(100, 100, 1, 1)
floorGeometry.rotateX(-Math.PI / 2)
const floorMaterial = new THREE.MeshLambertMaterial({ color: 0x777777 })
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.BoxGeometry(1, 1, 1, 10, 10)
const cubeMaterial = new THREE.MeshPhongMaterial({ color: 0x888888 })
for (let i = 0; i < N; i++) {
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)
}

Expand All @@ -222,6 +220,15 @@
}

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]
Expand All @@ -235,10 +242,36 @@
quaternions[i * 4 + 3] = mesh.quaternion.w
}

// Send the geometry data to setup the cannon.js bodies and
// the initial position and rotation data
// 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({
geometries: meshes.map((m) => m.geometry),
// serialize the geometries as json to pass
// them to the worker
geometries: meshes.map((m) => m.geometry.toJSON()),
positionsSharedBuffer,
quaternionsSharedBuffer,
})
Expand All @@ -247,7 +280,8 @@
function animate() {
requestAnimationFrame(animate)

// Update the three.js meshes
// 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(
Expand Down
Binary file modified screenshots/worker_sharedarraybuffer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 3b15153

Please sign in to comment.