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

Working Example

Description

This example demonstrates the useCannon hooks of usePlane, useBox,useSphere,useCylinder,useConvexPolyhedron, useTrimesh and the Cannon Debug renderer.

See Working Example

./src/App.jsx

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
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>
  )
}

./src/CannonUtils.jsx

// MIT License
// Copyright (c) 2022 Sean Bradley
// Based on https://sbcode.net/threejs/physics-cannonDebugrenderer/#srcclientutilscannonutilsts
// Re written for my React Three Fiber course.
// Course Coupons @ https://sbcode.net/coupons
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

GitHub Branch

git clone https://github.com/Sean-Bradley/React-Three-Fiber-Boilerplate.git
cd React-Three-Fiber-Boilerplate
git checkout cannon
npm install
npm start