useCannon
Description
This example is based on my TypeScript Cannon Debug Renderer example.
It demonstrates the useCannon
hooks of usePlane
, useBox
,useSphere
,useCylinder
,useConvexPolyhedron
, useTrimesh
and the Debug
renderer.
./src/CannonUtils.jsx
import * as THREE from 'three'
import * as CANNON from 'cannon-es'
class CannonUtils {
static toTrimeshProps(geometry) {
let vertices
if (geometry.index === null) {
vertices = geometry.attributes.position.array
} else {
vertices = geometry.clone().toNonIndexed().attributes.position.array
}
const indices = Object.keys(vertices).map(Number)
return [vertices, indices]
}
static toConvexPolyhedronProps(geometry) {
const position = geometry.attributes.position
const normal = geometry.attributes.normal
const vertices = []
for (let i = 0; i < position.count; i++) {
vertices.push(new THREE.Vector3().fromBufferAttribute(position, i))
}
const faces = []
for (let i = 0; i < position.count; i += 3) {
const vertexNormals =
normal === undefined
? []
: [
new THREE.Vector3().fromBufferAttribute(normal, i),
new THREE.Vector3().fromBufferAttribute(normal, i + 1),
new THREE.Vector3().fromBufferAttribute(normal, i + 2),
]
const face = {
a: i,
b: i + 1,
c: i + 2,
normals: vertexNormals,
}
faces.push(face)
}
const verticesMap = {}
const points = []
const changes = []
for (let i = 0, il = vertices.length; i < il; i++) {
const v = vertices[i]
const key =
Math.round(v.x * 100) +
'_' +
Math.round(v.y * 100) +
'_' +
Math.round(v.z * 100)
if (verticesMap[key] === undefined) {
verticesMap[key] = i
points.push(
new CANNON.Vec3(vertices[i].x, vertices[i].y, vertices[i].z)
)
changes[i] = points.length - 1
} else {
changes[i] = changes[verticesMap[key]]
}
}
const faceIdsToRemove = []
for (let i = 0, il = faces.length; i < il; i++) {
const face = faces[i]
face.a = changes[face.a]
face.b = changes[face.b]
face.c = changes[face.c]
const indices = [face.a, face.b, face.c]
for (let n = 0; n < 3; n++) {
if (indices[n] === indices[(n + 1) % 3]) {
faceIdsToRemove.push(i)
break
}
}
}
for (let i = faceIdsToRemove.length - 1; i >= 0; i--) {
const idx = faceIdsToRemove[i]
faces.splice(idx, 1)
}
const cannonFaces = faces.map(function (f) {
return [f.a, f.b, f.c]
})
return [points.map((v) => [v.x, v.y, v.z]), cannonFaces]
}
static offsetCenterOfMass(body, centreOfMass) {
body.shapeOffsets.forEach(function (offset) {
centreOfMass.vadd(offset, centreOfMass)
})
centreOfMass.scale(1 / body.shapes.length, centreOfMass)
body.shapeOffsets.forEach(function (offset) {
offset.vsub(centreOfMass, offset)
})
const worldCenterOfMass = new CANNON.Vec3()
body.vectorToWorldFrame(centreOfMass, worldCenterOfMass)
body.position.vadd(worldCenterOfMass, body.position)
}
}
export default CannonUtils
./src/App.jsx
import { Stats, OrbitControls, useGLTF } from '@react-three/drei'
import { Canvas } from '@react-three/fiber'
import { useRef, useMemo } from 'react'
import {
Debug,
Physics,
useBox,
usePlane,
useSphere,
useTrimesh,
useCylinder,
useConvexPolyhedron,
} from '@react-three/cannon'
import {
MeshNormalMaterial,
IcosahedronGeometry,
TorusKnotGeometry,
} from 'three'
import { useControls } from 'leva'
import CannonUtils from './CannonUtils'
function Plane(props) {
const [ref] = usePlane(() => ({ ...props }), useRef())
return (
<mesh ref={ref} receiveShadow>
<planeGeometry args={[25, 25]} />
<meshStandardMaterial />
</mesh>
)
}
function Box(props) {
const [ref] = useBox(
() => ({ args: [1, 1, 1], mass: 1, ...props }),
useRef()
)
return (
<mesh ref={ref} castShadow>
<boxGeometry args={[1, 1, 1]} />
<meshNormalMaterial />
</mesh>
)
}
function Sphere(props) {
const [ref] = useSphere(
() => ({ args: [0.75], mass: 1, ...props }),
useRef()
)
return (
<mesh ref={ref} castShadow>
<sphereGeometry args={[0.75]} />
<meshNormalMaterial />
</mesh>
)
}
function Cylinder(props) {
const [ref] = useCylinder(
() => ({ args: [1, 1, 2, 8], mass: 1, ...props }),
useRef()
)
return (
<mesh ref={ref} castShadow>
<cylinderGeometry args={[1, 1, 2, 8]} />
<meshNormalMaterial />
</mesh>
)
}
function Icosahedron(props) {
const geometry = useMemo(() => new IcosahedronGeometry(1, 0), [])
const args = useMemo(
() => CannonUtils.toConvexPolyhedronProps(geometry),
[geometry]
)
const [ref] = useConvexPolyhedron(
() => ({ args, mass: 1, ...props }),
useRef()
)
return (
<mesh ref={ref} castShadow geometry={geometry}>
<meshNormalMaterial />
</mesh>
)
}
function TorusKnot(props) {
const geometry = useMemo(() => new TorusKnotGeometry(), [])
const args = useMemo(() => CannonUtils.toTrimeshProps(geometry), [geometry])
const [ref] = useTrimesh(() => ({ args, mass: 1, ...props }), useRef())
return (
<mesh ref={ref} castShadow>
<torusKnotGeometry />
<meshNormalMaterial />
</mesh>
)
}
export function Monkey(props) {
const { nodes } = useGLTF(
'https://cdn.jsdelivr.net/gh/Sean-Bradley/React-Three-Fiber-Boilerplate@cannon/public/models/monkey.glb'
)
const args = useMemo(
() => CannonUtils.toTrimeshProps(nodes.Suzanne.geometry),
[nodes.Suzanne.geometry]
)
const [ref] = useTrimesh(() => ({ args, mass: 1, ...props }), useRef())
return (
<group ref={ref} {...props} dispose={null}>
<mesh
castShadow
geometry={nodes.Suzanne.geometry}
material={useMemo(() => new MeshNormalMaterial(), [])}
/>
</group>
)
}
export default function App() {
const gravity = useControls('Gravity', {
x: { value: 0, min: -10, max: 10, step: 0.1 },
y: { value: -9.8, min: -10, max: 10, step: 0.1 },
z: { value: 0, min: -10, max: 10, step: 0.1 },
})
return (
<Canvas shadows camera={{ position: [0, 2, 4] }}>
<spotLight
position={[2.5, 5, 5]}
angle={Math.PI / 4}
penumbra={0.5}
castShadow
/>
<spotLight
position={[-2.5, 5, 5]}
angle={Math.PI / 4}
penumbra={0.5}
castShadow
/>
<Physics gravity={[gravity.x, gravity.y, gravity.z]}>
<Debug>
<Plane rotation={[-Math.PI / 2, 0, 0]} />
<Box position={[-4, 3, 0]} />
<Sphere position={[-2, 3, 0]} />
<Cylinder position={[0, 3, 0]} />
<Icosahedron position={[2, 3, 0]} />
<TorusKnot position={[4, 3, 0]} />
<Monkey position={[-2, 20, 0]} />
</Debug>
</Physics>
<OrbitControls target-y={0.5} />
<Stats />
</Canvas>
)
}