Adding Trackball Controls to a Three.js Scene with Sprites
Tags: javascript, gamedev, threejs, graphics
Published: 2020-12-08 08:16:42

---
title: Adding Trackball Controls to a Three.js Scene with Sprites
published: true
description: I'm building a social media network and collaboration tool based on mind maps, documenting my work in this series of blog posts
tags: javascript,gamedev,threejs,graphics
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/8ue1rerx349e7d97d1b1.png
series: Nuffshell Coding Diary
---

*I'm building a social media network and collaboration tool based on mind maps, documenting my work in this series of blog posts. Follow me if you're interested in what I've learned along the way about building web apps with React, Tailwind CSS, Firebase, Apollo/GraphQL, three.js and TypeScript.*

# Today's Goal

In the previous parts of this series, I've built a mind map with [three.js](https://threejs.org) and [React](https://reactjs.org) using a [force-directed graph](https://en.wikipedia.org/wiki/Force-directed_graph_drawing).

Some commenters have (justifiably) asked why I have to use a fully-fledged 3D rendering library to just draw a mind map – I might have just as well drawn it on a 2D canvas, or just used SVG or even DOM nodes to achieve the same without jumping through the hoops of texturing, calculating camera angles, rendering sprites, etc.

Well, my vision for the project is to have a fluid, animated user experience – my mind map should always be in motion, just like the thoughts in our minds never stop moving.

Today I want to add the ability to zoom, rotate and pan my mind map to take an important step in that direction and actually achieve a level of interactiveness that only a 3D model can provide.

# Three.js Trackball Controls

Most of the examples on the three.js use a library called “Trackball Controls” to add zooming, rotating and panning to the demos. It is not really a library, more a plain old JavaScript program that someone wrote that gets copied around all over the place. The “official file” is [found in the examples directory](https://github.com/mrdoob/three.js/blob/master/examples/js/controls/TrackballControls.js) of the three.js source code.

This is “old school” JavaScript, using global variables without any support for module import, but luckily, someone wrapped it up in an npm package and added some helpful info on how to use it:

* [three-trackballcontrols](https://www.npmjs.com/package/three-trackballcontrols)

I add this library to my mind map demo and “hook it up” by adding some code to my [initializeScene](https://codesandbox.io/s/step-14-trackball-controls-bad-connections-itu92?file=/src/initializeScene.js) function:

```javascript
function initializeScene(div) {
  const canvas = createCanvas(window.innerWidth, window.innerHeight);
  const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
  div.appendChild(renderer.domElement);

  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0xffffff);

  const camera = new THREE.PerspectiveCamera(
    50,
    window.innerWidth / window.innerHeight,
    0.1,
    500000
  );
  camera.position.z = 1.5;

  const controls = new TrackballControls(camera, renderer.domElement);

  return { scene, renderer, camera, controls };
}
```

In addition to `scene`, `renderer` and `camera`, the function now also exports the `controls` object.

I'm using this in my [renderMindMap](https://codesandbox.io/s/step-14-trackball-controls-bad-connections-itu92?file=/src/renderMindMap.js) function to update the camera position inside my `animate` loop, like so:

```javascript
(function animate() {
  graph.tickFrame();
  controls.update();
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
})();
```

As a result, I can now zoom, rotate and pan my 3D mind map – that was easy!

{% youtube YgLcM5-g0SM %}

Try it out yourself here:

{% codesandbox itu92 %}

* To zoom, use the mouse wheel (or two fingers trackpad swipe up/down on Macs)
* To pan (move the viewport), right-click and drag the mouse
* To rotate, left-click and drag the mouse

# Looks Good, but Not Great!

What still bothers me at this point is that the connecting lines between the mind map nodes overlap the nodes when I rotate the mind map, making the text labels hard to read and just looking wonky overall.

# The Fix

StackOverflow to the rescue:

{% stackoverflow 12666937 %}

The trick is to manipulate the [renderOrder](https://threejs.org/docs/index.html#api/en/core/Object3D.renderOrder) property of the mind map node sprites (kinda like the Z-index in CSS), then call [clearDepth](https://threejs.org/docs/index.html#api/en/renderers/WebGLRenderer.clearDepth) on the renderer before each sprite is rendered.

Here is my updated code that renders the mind map nodes in my function [renderMindMap](https://codesandbox.io/s/step-15-trackball-controls-fixed-xdrgv?file=/src/renderMindMap.js):

```javascript
data.nodes = await Promise.all(
  data.nodes.map((node) =>
    renderToSprite(<MindMapNode label={node.name} level={node.level} />, {
      width: 128,
      height: 64
    }).then((sprite) => {
      sprite.renderOrder = 999;
      sprite.onBeforeRender = (renderer) => renderer.clearDepth();
      return { ...node, sprite };
    })
  )
);
```

This has the desired effect – I can rotate my mind map to my heart's content and the connecting lines never overlap the mind map nodes:

{% youtube eJv0aUGPTtw %}

Try it out:

{% codesandbox xdrgv %}

# To Be Continued…

I'm planning to turn my mind map into a social media network and collaboration tool and will continue to blog about my progress in follow-up articles. Stay tuned!