Creating a rudimentary pool table game using React, Three JS and react-three-fiber: Part 1
Tags: react, threejs
Published: 2019-10-28 20:43:28

For a long time, I wanted to learn and get started with WebGL. Having done some work with OpenGL I thought WebGL would be a great addition to know and learn. This is when I came across three.js.

[**Three.js**](https://threejs.org) is an awesome 3D library for working with graphics on the web. It is written in JavaScript but does not have native support for React. Having worked with React a lot I wanted to use the expressiveness of React combined with the power of three js. This is when I found [**react-three-fiber**](https://github.com/react-spring/react-three-fiber). It is a lightweight React renderer for three.js and works well.

This is a three-part series of articles where we will see how we can use React, three.js, and react-three-fiber to create a game of pool table.

- Part 1: Getting started with React, three.js, and react-three-fiber.
- Part 2: Setting up the basic scene (Coming soon).
- Part 3: Adding physics and finishing up(Coming soon).

## First things first

- Create a React project. The best way to do this is by using [**create-react-app**](https://github.com/facebook/create-react-app)
- The next step is to install the three.js and react-three-fiber modules. Depending on the package manager of your choice go ahead and install them. `npm i three react-three-fiber` or `yarn add three react-three-fiber`

Now that our project is set up let's do the fun part and jump into coding.

## Organizing the project

This is a project structure that I like to follow and by no means, you've to do this. This is how I like to organize but feel free to move things around.

Inside the src directory let's create different directories for components, views, utils, and assets. Your directory structure should look something like this

```directory
project
│   README.md
└───src
│   │   index.js
│   │   App.js
│   │
│   └───assets
│   └───components
│   └───utils
│   └───views
```

## Creating a basic scene

- Go ahead and create a file called Scene.js inside the views directory.
- Just copy and paste the code below in the Scene.js file.

```react
import React from 'react';

function Scene() {
  return (
    <mesh>
      <boxBufferGeometry attach='geometry' args={[1, 1, 1]} />
      <meshNormalMaterial attach='material' />
    </mesh>
  );
}

export default Scene;

```

This will create a cube mesh for us.

Let's go and see what each line does.

All the jsx tags that you see are react-three-fiber wrappers around three.js objects

- The mesh component is the [Mesh](https://threejs.org/docs/#api/en/objects/Mesh) object from the three js library. The same is true for [boxBufferGeometry](https://threejs.org/docs/#api/en/geometries/BoxBufferGeometry) and [meshNormalMaterial](https://threejs.org/docs/#api/en/materials/MeshNormalMaterial).
- If you check out the docs for the components on the three js website you'll see BoxBufferGeometry has a constructor with a bunch of parameters.
- The way you can create a new instance in React with react-three-fiber is by using the **args** prop for that component and passing in the parameters as an array.
- So in the above example, `<boxBufferGeometry attach='geometry' args={[1, 1, 1]} />` will create a new BoxBufferGeometry (aka cube) with params 1, 1, and 1 for width, height and depth respectively. The **attach** prop tells the renderer what kind of object the given component is. You can use all the properties for this given object and from its superclass as props to the component. You can find all the properties in the [docs](https://threejs.org/docs/index.html#manual/en/introduction/Creating-a-scene) for three js.
- Similarly, the meshNormalMaterial can be used to color the geometry among many other uses which we will see later.

Congratulations you just created a cube and added it to the scene. The next step is to render the scene inside of a canvas element. You all know how to do this, so bye-bye and happy coding.

I was just kidding. So now, let's create a canvas.

## Creating the Canvas

- Open up the App.js file and copy and paste the code given below.

```react
import React from 'react';
import { Canvas } from 'react-three-fiber';

import Scene from './views/Scene';

function App() {
  return (
    <Canvas>
      <Scene />
    </Canvas>
  );
}

export default App;

```

- Here the Canvas component will add a canvas element to the dom and render the scene as part of the HTML canvas element.

We are done at this point. Just go and run `npm start` and you'll be able to see your beautiful cube in the browser.

Your output should look something like this

![shot1](https://raw.githubusercontent.com/manan30/blog-posts/feature/dev-to-billiards-1/Billiards/shot1.png?token=AC4HGT4DNBY27C2SG3VS2SC5YCC2Q)

- Just one last thing to do here is the canvas doesn't take the entire height of the screen.
- So in your index.css just add the following lines

```css
body {
  margin: 0;
  height: 100vh;
  width: 100vw;
  background-color: black;
}

#root {
  height: 100%;
}
```

- And finally, you'll see a cube that is in the center of your screen.

I hope you're happy with the effort you just put in but as you can see the cube looks more like a square. Don't panic, believe me, it is a cube. To see it as a 3D object let's add mouse/track-pad controls so that we can perform pan, rotate and zoom(aka orbit controls).

### Adding Orbit Controls

- Let's go ahead and create a file called Controls.js and copy and paste in the code given below.

```react
import React, { useRef } from 'react';
import { extend, useThree, useFrame } from 'react-three-fiber';
import OrbitControls from 'three/examples/jsm/controls/OrbitControls';

extend({ OrbitControls });

function Controls() {
  const controlsRef = useRef();
  const { camera, gl } = useThree();

  useFrame(() => controlsRef.current && controlsRef.current.update());

  return (
    <orbitControls
      ref={controlsRef}
      args={[camera, gl.domElement]}
      enableRotate
      enablePan={false}
      maxDistance={100}
      minDistance={5}
      minPolarAngle={Math.PI / 6}
      maxPolarAngle={Math.PI / 2}
    />
  );
}

export default Controls;
```

- The first thing to understand here is, OrbitControls is not part of the main three module, hence you cannot use it directly as we saw in the previous mesh and geometry code for the cube.
- To deal with this react-three-fiber provides an `extend` function which can be used for modules outside of the main three js codebase. Remember to call the extend function in the beginning before the component function and after that, you'll be able to use the extended module like any other three js module.
- So, now as we saw earlier while using mesh and geometry we can use orbit controls in the same way along with all its [properties](https://threejs.org/docs/#examples/en/controls/OrbitControls).
- Let's talk about the hooks used above as well `useRef`, `useThree`, and `useFrame`.
- useRef among other things is Reacts' way of giving us access to the underlying dom node. You can read more about it [here](https://reactjs.org/docs/hooks-reference.html#useref)
- [useThree](https://github.com/react-spring/react-three-fiber#hooks) is a react-three-fiber hook that essentially gives us access to everything that is added to the scene. This is going to be super helpful to us later on.
- [useFrame](https://github.com/react-spring/react-three-fiber#hooks) also a react-three-fiber hook is called for every frame that is drawn. If you have used RequestAnimationFrame API provided by the browser, this hook is similar to that. It will be formulating the basics of our physics calculation later on in the example.
- And the final step is adding the newly created controls to the canvas. To do this open the App.js file and replace the current code with the code below.

```react
import React from 'react';
import { Canvas } from 'react-three-fiber';

import Scene from './views/Scene';
import Controls from './components/Controls';

function App() {
  return (
    <>
      <Canvas>
        <Scene />
        <Controls />
      </Canvas>
    </>
  );
}

export default App;
```

Start the app and nothing will have changed but now you'll be able to use your mouse-wheel/track-pad to zoom in and out while holding on to the left click will allow you to rotate and inspect the cube from all sides as well as allow you to pan. You should be able to do something like this shown below.

![shot2](https://raw.githubusercontent.com/manan30/blog-posts/feature/dev-to-billiards-1/Billiards/shot2.png?token=AC4HGT2PF3C3OHGQW76LRE25YCDBI)

Before we jump into modeling out our pool table there is just one last thing that we want to do. Let us just adjust our camera a little bit so that we can see how to change the default settings on the camera.

### Editing the camera settings

- Go ahead and open your Scene.js file and replace the contents with the code below.

```react
import React from 'react';
import { useThree } from 'react-three-fiber';

function Scene() {
  const { camera } = useThree();

  camera.fov = 45;
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.near = 0.1;
  camera.far = 1000;

  camera.up.set(0, 0, 1);
  camera.position.set(-5, 7, 5);

  return (
    <mesh>
      <boxBufferGeometry attach='geometry' args={[1, 1, 1]} />
      <meshNormalMaterial attach='material' />
    </mesh>
  );
}

export default Scene;
```

- As we had seen earlier, `useThree` gives up access to the default camera. We are just tweaking some settings on that so that we can see the scene better.
- You will be able to find all the properties and functions the camera object has [here](https://threejs.org/docs/#api/en/cameras/PerspectiveCamera).

This concludes part 1 of the three-part series. I'll be posting the upcoming parts in the following days.

Also, this is my first blog post ever. It will be great if you could leave some feedback in the comments so that I can understand and improve my content delivery. You can follow me on [Twitter](https://twitter.com/Manan_30) and [Instagram](https://www.instagram.com/manan__joshi/).

Peace out and happy coding!!!