Creating a rudimentary pool table game using React, Three JS and react-three-fiber: Part 1

Oct 28, 2019

6 min read

Last Updated On Apr 13, 2021

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 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. 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
  • 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

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.
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 object from the three js library. The same is true for boxBufferGeometry and 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 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.
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

  • 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
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.
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.
  • 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
  • useThree 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 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.
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

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.
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.

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

Please feel free to DM me on Twitter or Instagram with your questions, comments or feedback and I'll be happy to answer them for you.

Peace out and happy coding!!!