Cannon physics - 3D web3 serie
Tags: web3, react, javascript, vite
Published: 2022-08-19 14:46:00

Hey Reader,

This is the 3rd post of 3D-web3 Series.

1 - [Vite config and basic three.js](https://dev.to/uigla/setting-up-3d-website-using-threejs-fiber-react-web3-and-vite-as-bundler-f1f)
2 - [Three.js (fiber & drei)](https://dev.to/uigla/chapter-2-3d-website-using-threejs-fiber-react-web3-and-vite-as-bundler-using-more-packages-4p02)
**3 - 3D web - Cannon physics**
4 - [Web3](https://dev.to/uigla/3d-web-react-web3-44e7)

["Cannon"](https://github.com/pmndrs/use-cannon/blob/master/packages/react-three-cannon/README.md) is the rigid body physics engine who includes simple collision detection, various body shapes, contacts, friction and constraints.

`npm i @react-three/cannon`

Simple steps to make it work:
1_ Import and Create a physics world

```javascript
import { Physics, useBox, ... } from '@react-three/cannon'

<Physics>{/* Physics related objects in here please */}</Physics>
```

2_ Pick a shape that suits your objects contact surface, it could be a box, plane, sphere, etc. Give it a mass, too

```javascript
const [ref, api] = useBox(() => ({ mass: 1 }))
```

3_ Take your object, it could be a mesh, line, gltf, anything, and tie it to the reference you have just received. It will now be affected by gravity and other objects inside the physics world.

```javascript
<mesh ref={ref} geometry={...} material={...} />
```

4_ You can interact with it by using the api, which lets you apply positions, rotations, velocities, forces and impulses

```javascript
useFrame(({ clock }) => api.position.set(Math.sin(clock.getElapsedTime()) * 5, 0, 0))
```

5_ You can use the body api to subscribe to properties to get updates on each frame

```javascript
const velocity = useRef([0, 0, 0])
useEffect(() => {
  const unsubscribe = api.velocity.subscribe((v) => (velocity.current = v))
  return unsubscribe
}, [])
```
All the steps in "Box.jsx" component looks like:

```javascript
import { Physics, useBox } from '@react-three/cannon'
import { useFrame } from '@react-three/fiber';

const Box = () => {

    const [ref, api] = useBox(() => ({ mass: 1 }))

    useFrame(({ clock }) => api.position.set(Math.sin(clock.getElapsedTime()) * 5, 0, 0))

    const velocity = useRef([0, 0, 0])
    useEffect(() => {
        const unsubscribe = api.velocity.subscribe((v) => (velocity.current = v))
        return unsubscribe
    }, [])

    return (
        <Physics>
            <mesh ref={ref}>
                <boxGeometry attach='geometry' args={[1, 1, 1]} />
                <meshStandardMaterial attach="material" color={'#000'} />
            </mesh>

        </Physics>
    )
}

export default Box
```

Let's apply this package to our repo.

**App logic__** 

> First check previous post if you haven't, to understand why we are not using in code-sand-box our gltf model
> Instead, we are using a "yellow box" with an onClick method to change between two "camera modes"


Include the "ActivateSpawner" component which will be the parent of the other 3 components we need. 

In camera RIG mode we'll see a "black box" with an onClick method to activate :

a) "Spawner" component: It creates "x" number of bubbles with "y" velocity. "Spawner" has "Bubble" component as child.

b) "PlayerBox" component: Mimics your movement and you've to avoid coming bubbles

_Both components have a collider property. So, if "PlayerBox" collides with a "Bubble" component, game will be stoped_

We'll be using (previous tutorial "objects/hooks" are not included):

- From "Fiber": useThree, useFrame
- From "Cannon": useBox, useSphere
- From "Three": Vector3

**Step_1** Create a "ActivateSpawner" component

Notice we're giving a "mass" of 0 to the box

```javascript
import React from 'react'
import { useBox } from '@react-three/cannon';
import { useState } from 'react';

import Spawner from './Spawner';
import PlayerBox from './PlayerBox';

const ActivateSpawner = () => {

    const [play, setPlay] = useState(false);

    // This box is used to start the game
    const [ref] = useBox(() => ({
        mass: 0,
        position: [-5, 2, -10],
        type: 'Dynamic',
        args: [1, 1, 1],
    }));

    return (
        <group>
            <mesh
                ref={ref}
                onClick={() => {
                    console.log(!play)
                    setPlay(!play)
                }}
            >
                <boxGeometry attach='geometry' args={[1, 1, 1]} />
                <meshStandardMaterial attach="material" color={'#000'} />
            </mesh>
            {play && (<>
                <Spawner />
                <PlayerBox setPlay={setPlay} />
            </>
            )}
        </group>
    )
}

export default ActivateSpawner
```

**Step_2** Create "Spawner" component

Get random data (position, delay, color) for each "bubble" using a for loop and "randomIntBetween(a,b)" & randomIntBetweenAlsoNegatives(a,b) functions

```javascript
import { Vector3 } from 'three';
import Bubble from './Bubble';


const Spawner = () => {


	function randomIntBetween(min, max) { // min and max included 
		return Math.floor(Math.random() * (max - min + 1) + min)
	}

	function randomIntBetweenAlsoNegatives(min, max) { // min and max included 
		const math = Math.floor(Math.random() * (max - min + 1) + min)
		const random = Math.random()
		const zeroOrOne = Math.round(random)
		if (zeroOrOne) return -(math)
		return math
	}

	const attackersArray = [];

	for (let i = 0; i < 20; i++) {

		let position = new Vector3(
			randomIntBetweenAlsoNegatives(0, 2),
			randomIntBetweenAlsoNegatives(0, 2),
			0)

		let wait = randomIntBetween(1, 12) * 10

		let color = `#${Math.random().toString(16).substring(2, 8)}`

		const att = [position, wait, color]
		attackersArray.push(att)
	}

	return (
		<group>
			{attackersArray.map((attackers, key) => {
				return <Bubble
					key={key}
					pos={attackers[0]}
					wait={attackers[1]}
					color={attackers[2]}
				/>
			})}
		</group>
	);
};

export default Spawner;

```

**Step_3** Create "PlayerBox" component

Use "useThree" hook from '@react-three/fiber' to create a reference to our canvas "camera" object. Now we are able to give same value to our "PlayerBox" using "useFrame" hook

> This hook calls you back every frame, which is good for running effects, updating controls, etc.

Add to our "Box" a "collisionFilterGroup" and "collisionFilterMask" properties. 
The first defines in which group it is and the second which group it may collide with

```javascript
import { useBox, } from '@react-three/cannon';
import { useFrame } from '@react-three/fiber';
import { useThree } from '@react-three/fiber'

const PlayerBox = (props) => {

	const { camera } = useThree()

	const [ref, api] = useBox(() => ({
		mass: 0,
		type: 'Dynamic',
		position: [0, 0, -5],
		args: [0.3, 0.3, 0.1], // collision box size
		collisionFilterGroup: 1,
		// 1 PlayerBox 2 Objetive 3 BulletBox 4 Attackers
		collisionFilterMask: 4,
		onCollide: (e) => {
			props.setPlay(false);
			console.log('game over')
		},
	}));

	// Tambien simula el movimiento de la camara (y por lo tnato el del objetivo), para poder tener un collider para el game over
	useFrame(() => {
		api.position.set(camera.position.x, camera.position.y, -2);
	});

	return (
		<>
			<mesh ref={ref}>
				<boxBufferGeometry attach='geometry' args={[0.1, 0.1, 0.1]} /> {/* box size */}
				<meshStandardMaterial attach="material" color={'#000'} />

			</mesh>
		</>
	);
};

export default PlayerBox;

```

**Step_4** Create "Bubble" component

In order to use the same "bubble" object to run the same race "x" number of times, add "setTimeout" function to reset the bubble position inside for loop.

```javascript
import { useSphere } from '@react-three/cannon';
import { useFrame } from '@react-three/fiber';

const Bubble = (props) => {

	let zMovement = -20;

	const [ref, api] = useSphere(() => ({
		mass: 0,
		position: [props.pos.x, props.pos.y, props.pos.z - 200],
		type: 'Dynamic',
		// args: [1, 1, 1],
		// 1 PlayerBox 2 Objetive 3 BulletBox 4 Bubble
		collisionFilterGroup: 4,
		// No te va a colisionar, sino que vas a colisionar contra el
		collisionFilterMask: 1,
	}));

	useFrame(() => {
		api.position.set(
			props.pos.x,
			props.pos.y,
			(zMovement += 0.1) - props.wait
		);
	});

	for (let i = 1; i < 3; i++) {
		window.setTimeout(() => {
			zMovement = -50;
			api.position.set(0, 0, -zMovement);
			// 6 segs * i * wait= posicion de cada cubo para hacer que algunos salgan antes que otros
		}, 6 * 1000 + props.wait * 100);
	}

	return (
		<mesh ref={ref}>
			<sphereGeometry attach='geometry' args={[1, 32, 32]} />
			<meshStandardMaterial attach="material" color={props.color} />
		</mesh>
	);
};

export default Bubble;

```

**Step_5** Add "ActivateSpawner" in our App.jsx using "physics" node imported from "@react-three/cannon"

All components we've defined will be rendered in our DOM when
cameraMode is false => camera RIG mode setted
```javascript
import { Canvas } from '@react-three/fiber';
import ActivateSpawner from './geometry/ActivateSpawner';
...
return (
...
{!cameraMode &&
						< Physics >
							<ActivateSpawner />
						</Physics>
					}
...
)
```

Resume of components: ActivateSpawner , Spawner, PlayerBox, Bubble

{% embed https://codesandbox.io/embed/three-react-part-3-e6orzy?fontsize=14&hidenavigation=1&theme=dark %}

Web3 will be added in next post

I hope it has been helpful.