Skip to content


 Zabbix
 Grafana
 Prometheus
 React Three Fiber
 Threejs and TypeScript
 SocketIO and TypeScript
 Blender Topological Earth
 Sweet Home 3D
 Design Patterns Python
 Design Patterns TypeScript
   
 Course Coupon Codes
Three.js and TypeScript
Kindle Edition
$6.99 $9.99 Paperback 
$22.99 $29.99




Design Patterns in TypeScript
Kindle Edition
$6.99 $9.99 Paperback
$11.99 $19.99




Design Patterns in Python
Kindle Edition
$6.99 $9.99 Paperback
$11.99 $19.99




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>
  )
}

Working Example