Hi friends! I wrote another one of these OpenGL tutorials just for you. I guess this’ll be the seventh one. Here are the previous six:

- Just Draw A Stupid Triangle With OpenGL
- Making A Dopey Circle Thing With OpenGL
- Just Interpolate Already With OpenGL
- Make A Stinking Sprite With OpenGL
- Distorting Sprites’ UV Mappings With OpenGL
- Silly Spiky Stars And Donuts With OpenGL

If you’ve been following along you already know that to run this code, you’ll need to have a folder on your computer called `opengl_examples`

with the three.js library in the `js`

subfolder. Go back to the first tutorial if you’d like a better explanation.

Anyway, today I thought I’d show you a trick for rounding the corners on certain meshes. We’ll start with an updated implementation of the regular polygon mesh we made in the second tutorial, and from there I’ll show you how to make a rectangle with rounded corners and even a regular polygon with rounded corners. This is how they look in the end:

We’ll call this file `rounding_corners.html`

. It goes in `opengl_examples`

, along with all the other tutorials. Here’s the code to copy in:

```
<!DOCTYPE html>
<html lang="en">
<head>
<title>Rounding All The Corners With OpenGL</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
color: #ffffff;
background-color: #000000;
margin: 0px;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="js/three.js"></script>
<script src="js/Detector.js"></script>
<script>
var webGlSupported = Detector.webgl;
if (!webGlSupported) {
Detector.addGetWebGLMessage();
}
const BRIGHT_GREEN = new THREE.Color(0.0, 1.0, 0.0);
const DARK_GREEN = new THREE.Color(0.0, 0.3, 0.0);
const VERTEX_COLORS = [DARK_GREEN, BRIGHT_GREEN, BRIGHT_GREEN];
var container = undefined;
var camera = undefined;
var material = undefined;
var scene = undefined;
var renderer = undefined;
initializeCamera();
initializeMaterial();
initializeScene();
initializeRenderer();
renderScene();
function initializeMaterial() {
material = new THREE.MeshBasicMaterial({
vertexColors: THREE.VertexColors,
side: THREE.DoubleSide
});
}
function initializeScene() {
scene = new THREE.Scene();
addRegularPolygonToScene(0.0, 75.0);
addRoundedRectangleToScene(0.0, 0.0);
addRoundedRegularPolygonToScene(0.0, -75.0);
}
function addMeshToScene(geometry, material, x, y) {
var mesh = new THREE.Mesh(geometry, material);
mesh.position.set(x, y, 0.0);
scene.add(mesh);
}
////////////////////////////////////////
// Regular Polygon
////////////////////////////////////////
function addRegularPolygonToScene(x, y) {
var radius = 40.0;
var sidesCount = 6;
var geometry = getRegularPolygonGeometry(radius, sidesCount);
addMeshToScene(geometry, material, x, y);
}
function getRegularPolygonGeometry(radius, sidesCount) {
var geometry = new THREE.Geometry();
geometry.vertices = getRegularPolygonVertices(radius, sidesCount);
geometry.faces = getRadialFaces(sidesCount);
return geometry;
}
// 1
function getRegularPolygonVertices(radius, sidesCount) {
var vertices = [];
var centerPoint = new THREE.Vector3(0.0, 0.0, 0.0);
vertices.push(centerPoint);
for (sideIndex = 0; sideIndex < sidesCount; sideIndex++) {
var sideProgress = sideIndex / sidesCount;
var point = getRadialPoint(sideProgress, radius);
vertices.push(point);
}
return vertices;
}
////////////////////////////////////////
// Rounded Rectangle
////////////////////////////////////////
function addRoundedRectangleToScene(x, y) {
var width = 180.0;
var height = 50.0;
var roundingRadius = 15.0;
var sidesCountPerRounding = 20;
var geometry = getRoundedRectangleGeometry(width, height, roundingRadius, sidesCountPerRounding);
addMeshToScene(geometry, material, x, y);
}
function getRoundedRectangleGeometry(width, height, roundingRadius, sidesCountPerRounding) {
var geometry = new THREE.Geometry();
geometry.vertices = getRoundedRectangleVertices(width, height, roundingRadius, sidesCountPerRounding);
// 4
var cornersCount = 4;
var facesCount = sidesCountPerRounding * cornersCount + cornersCount;
geometry.faces = getRadialFaces(facesCount);
return geometry;
}
// 3
function getRoundedRectangleVertices(width, height, roundingRadius, sidesCountPerRounding) {
var vertices = [];
var centerPoint = new THREE.Vector3(0.0, 0.0, 0.0);
vertices.push(centerPoint);
var quadrantPositivities = [
{x: 1.0, y: 1.0},
{x: -1.0, y: 1.0},
{x: -1.0, y: -1.0},
{x: 1.0, y: -1.0}
];
var cornersCount = 4;
for (var cornerIndex = 0; cornerIndex < cornersCount; cornerIndex++) {
var startProgress = cornerIndex / cornersCount;
var endProgress = (cornerIndex + 1) / cornersCount;
var quadrantPositivity = quadrantPositivities[cornerIndex];
var cornerOuterX = quadrantPositivity.x * 0.5 * width;
var cornerRoundingCenterX = cornerOuterX - quadrantPositivity.x * roundingRadius;
var cornerOuterY = quadrantPositivity.y * 0.5 * height;
var cornerRoundingCenterY = cornerOuterY - quadrantPositivity.y * roundingRadius;
var cornerVertices = getRoundingVertices(
startProgress,
endProgress,
roundingRadius,
sidesCountPerRounding,
cornerRoundingCenterX,
cornerRoundingCenterY
);
vertices = vertices.concat(cornerVertices);
}
return vertices;
}
////////////////////////////////////////
// Rounded Regular Polygon
////////////////////////////////////////
function addRoundedRegularPolygonToScene(x, y) {
var radius = 40.0;
var roundingRadius = 10.0;
var sidesCount = 6;
var sidesCountPerRounding = 20;
var geometry = getRoundedRegularPolygonGeometry(radius, roundingRadius, sidesCount, sidesCountPerRounding);
addMeshToScene(geometry, material, x, y);
}
function getRoundedRegularPolygonGeometry(radius, roundingRadius, sidesCount, sidesCountPerRounding) {
var geometry = new THREE.Geometry();
geometry.vertices = getRoundedRegularPolygonVertices(radius, roundingRadius, sidesCount, sidesCountPerRounding);
var facesCount = sidesCountPerRounding * sidesCount + sidesCount;
geometry.faces = getRadialFaces(facesCount);
return geometry;
}
// 5
function getRoundedRegularPolygonVertices(radius, roundingRadius, sidesCount, sidesCountPerRounding) {
var vertices = [];
var centerPoint = new THREE.Vector3(0.0, 0.0, 0.0);
vertices.push(centerPoint);
var roundingCenterRadius = radius - roundingRadius;
var progressPerRounding = 1.0 / sidesCount;
for (sideIndex = 0; sideIndex < sidesCount; sideIndex++) {
var roundingCenterProgress = sideIndex / sidesCount;
var roundingStartProgress = roundingCenterProgress - 0.5 * progressPerRounding;
var roundingEndProgress = roundingCenterProgress + 0.5 * progressPerRounding;
var roundingCenterPoint = getRadialPoint(roundingCenterProgress, roundingCenterRadius);
var roundingVertices = getRoundingVertices(
roundingStartProgress,
roundingEndProgress,
roundingRadius,
sidesCountPerRounding,
roundingCenterPoint.x,
roundingCenterPoint.y
);
vertices = vertices.concat(roundingVertices);
}
return vertices;
}
////////////////////////////////////////
// 2
function getRadialFaces(facesCount) {
var faces = [];
var centerPointIndex = 0;
for (faceIndex = 0; faceIndex < facesCount; faceIndex++) {
var face = new THREE.Face3();
var trailingOuterPointIndex = faceIndex + 1;
var leadingOuterPointIndex = faceIndex + 2;
if (leadingOuterPointIndex > facesCount) {
leadingOuterPointIndex -= facesCount;
}
var normal = undefined;
var face = new THREE.Face3(centerPointIndex, trailingOuterPointIndex, leadingOuterPointIndex, normal, VERTEX_COLORS);
faces.push(face);
}
return faces;
}
function getRoundingVertices(startProgress, endProgress, radius, sidesCount, centerX = 0.0, centerY = 0.0) {
var vertices = [];
var progressRange = endProgress - startProgress;
for (vertexIndex = 0; vertexIndex <= sidesCount; vertexIndex++) {
var vertexProgress = startProgress + progressRange * (vertexIndex / sidesCount);
var vertex = getRadialPoint(vertexProgress, radius, centerX, centerY);
vertices.push(vertex);
}
return vertices;
}
function getRadialPoint(progress, radius, centerX = 0.0, centerY = 0.0) {
var angle = progress * 2.0 * Math.PI;
var x = centerX + Math.cos(angle) * radius;
var y = centerY + Math.sin(angle) * radius;
return new THREE.Vector3(x, y, 0.0);
}
function initializeCamera() {
var aspectRatio = window.innerWidth / window.innerHeight;
var screenWidth = undefined;
var screenHeight = undefined;
if (aspectRatio > 1.0) {
screenWidth = 320.0 * aspectRatio;
screenHeight = 320.0;
} else {
screenWidth = 320.0;
screenHeight = 320.0 / aspectRatio;
}
var nearPlane = 1.0;
var farPlane = 1000.0;
camera = new THREE.OrthographicCamera(
-0.5 * screenWidth,
0.5 * screenWidth,
0.5 * screenHeight,
-0.5 * screenHeight,
nearPlane,
farPlane
);
var distanceFromScene = 500.0;
camera.position.set(0.0, 0.0, distanceFromScene);
}
function initializeRenderer() {
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container = document.getElementById("container");
container.appendChild(renderer.domElement);
}
function renderScene() {
renderer.render(scene, camera);
}
</script>
</body>
</html>
```

By this point you know the drill. I’m not going to write as much about the basics. Instead, I’ll just give you a high-level overview of the procedure I’m following. Now that you’ve seen a few examples, hopefully my code is clear enough that you can follow what’s going on.

Like I said, we’re making three shapes in this tutorial. The first one is the same kind of regular polygon that we’ve already been using to approximate circles in previous tutorials. Here’s the code to set up the vertices. Note that it gets called from `getRegularPolygonGeometry`

, which is part of `addRegularPolygonToScene`

, which is part of `initializeScene`

.

```
// 1
function getRegularPolygonVertices(radius, sidesCount) {
var vertices = [];
var centerPoint = new THREE.Vector3(0.0, 0.0, 0.0);
vertices.push(centerPoint);
for (sideIndex = 0; sideIndex < sidesCount; sideIndex++) {
var sideProgress = sideIndex / sidesCount;
var point = getRadialPoint(sideProgress, radius);
vertices.push(point);
}
return vertices;
}
```

What’s new? This time around I’m relying on `getRadialPoint`

to build the `vertices`

array. In fact, I’m using it to build *all the vertices* in this tutorial. I’ve added optional arguments to the end of `getRadialPoint`

’s signature so that it’s possible to provide a center point from which to plot the radial point. If you leave the arguments blank (as I’m doing in `getRegularPolygonVertices`

), then `(0.0, 0.0)`

will be used for the center point.

Did you want to know a secret? While I was writing this example, I realized that all three shapes that we’re making can rely on the same function to build their `Face3`

s. That’s because they’re all sort of *radial* shapes. At least–they all have a center point, and all of their triangles fan out from it. Here’s the function I wrote up to connect all the dots:

```
// 2
function getRadialFaces(facesCount) {
var faces = [];
var centerPointIndex = 0;
for (faceIndex = 0; faceIndex < facesCount; faceIndex++) {
var face = new THREE.Face3();
var trailingOuterPointIndex = faceIndex + 1;
var leadingOuterPointIndex = faceIndex + 2;
if (leadingOuterPointIndex > facesCount) {
leadingOuterPointIndex -= facesCount;
}
var normal = undefined;
var face = new THREE.Face3(centerPointIndex, trailingOuterPointIndex, leadingOuterPointIndex, normal, VERTEX_COLORS);
faces.push(face);
}
return faces;
}
```

This time the `VERTEX_COLORS`

are handled in constant definitions near the top of the main program. That helps shorten our function a little, which helps keep our code a little more tidy.

But you’re not really interested in drawing the shapes we’ve already drawn, are you? Let’s move on to the *rounded rectangle*. This should be good. Take a look at how we build up its vertices.

```
// 3
function getRoundedRectangleVertices(width, height, roundingRadius, sidesCountPerRounding) {
var vertices = [];
var centerPoint = new THREE.Vector3(0.0, 0.0, 0.0);
vertices.push(centerPoint);
var quadrantPositivities = [
{x: 1.0, y: 1.0},
{x: -1.0, y: 1.0},
{x: -1.0, y: -1.0},
{x: 1.0, y: -1.0}
];
var cornersCount = 4;
for (var cornerIndex = 0; cornerIndex < cornersCount; cornerIndex++) {
var startProgress = cornerIndex / cornersCount;
var endProgress = (cornerIndex + 1) / cornersCount;
var quadrantPositivity = quadrantPositivities[cornerIndex];
var cornerOuterX = quadrantPositivity.x * 0.5 * width;
var cornerRoundingCenterX = cornerOuterX - quadrantPositivity.x * roundingRadius;
var cornerOuterY = quadrantPositivity.y * 0.5 * height;
var cornerRoundingCenterY = cornerOuterY - quadrantPositivity.y * roundingRadius;
var cornerVertices = getRoundingVertices(
startProgress,
endProgress,
roundingRadius,
sidesCountPerRounding,
cornerRoundingCenterX,
cornerRoundingCenterY
);
vertices = vertices.concat(cornerVertices);
}
return vertices;
}
```

What’s actually happening here? Well, we’re going to divide our rectangle up into four quadrants. `quadrantPositivities`

is just recording whether *x* and *y* coordinates are positive or negative in each quadrant. For each quadrant, we use the corresponding `quadrantPositivity`

to find the coordinates of a center point. From that center point, we use `getRoundingVertices`

to plot points that make up one quarter of a circle. We save those points in our `vertices`

array, but we discard the center point’s coordinate.

If you think about it, you can form a rounded rectangle by starting with a circle. That circle’s radius is the corner radius (or `roundingRadius`

in the case of our program). If you slice that circle into quadrants and move each slice so its outermost points are separated horizontally by the rectangle’s `width`

and vertically by its `height`

, you have your four corners. Then you just have to fill in the space between them. Which we do with our `Face3`

s. Take a look at `// 4`

.

```
function getRoundedRectangleGeometry(width, height, roundingRadius, sidesCountPerRounding) {
var geometry = new THREE.Geometry();
geometry.vertices = getRoundedRectangleVertices(width, height, roundingRadius, sidesCountPerRounding);
// 4
var cornersCount = 4;
var facesCount = sidesCountPerRounding * cornersCount + cornersCount;
geometry.faces = getRadialFaces(facesCount);
return geometry;
}
```

Like I said earlier, we’re using `getRadialFaces`

to build the `faces`

arrays for all three of our shapes. But I wanted to call your attention to these few lines because the formula for calculating the *number* of triangles you need to build varies from shape to shape. For the regular polygon, the number of faces happens to be the same as the number of sides, so we just passed in `sidesCount`

. For the rounded rectangle, however, we need to do something a little more advanced. We’ll need `sidesCountPerRounding`

triangles for each of the four corners, but what about the *sides* of the rectangle? That’s what the extra `cornersCount`

that we add to our total is for.

Now we’re moving on to our last shape of the day: the *rounded regular polygon*. The way we build this one is something of a combination of the previous two techniques. Once again, let’s look at the function for building the `vertices`

.

```
// 5
function getRoundedRegularPolygonVertices(radius, roundingRadius, sidesCount, sidesCountPerRounding) {
var vertices = [];
var centerPoint = new THREE.Vector3(0.0, 0.0, 0.0);
vertices.push(centerPoint);
var roundingCenterRadius = radius - roundingRadius;
var progressPerRounding = 1.0 / sidesCount;
for (sideIndex = 0; sideIndex < sidesCount; sideIndex++) {
var roundingCenterProgress = sideIndex / sidesCount;
var roundingStartProgress = roundingCenterProgress - 0.5 * progressPerRounding;
var roundingEndProgress = roundingCenterProgress + 0.5 * progressPerRounding;
var roundingCenterPoint = getRadialPoint(roundingCenterProgress, roundingCenterRadius);
var roundingVertices = getRoundingVertices(
roundingStartProgress,
roundingEndProgress,
roundingRadius,
sidesCountPerRounding,
roundingCenterPoint.x,
roundingCenterPoint.y
);
vertices = vertices.concat(roundingVertices);
}
return vertices;
}
```

If the rounded rectangle was formed by splitting a circle into four slices, then you can think of the rounded regular polygon as a circle split into *n* slices, where *n* is the number of corners on the polygon. And that’s precisely how we’re building up our `vertices`

. Each iteration through our loop builds up the points for one of the polygon’s *rounding* sections–one of its corners. First, we have to find the center point for our rounding circle. We do that with exactly the same approach that we used to find the vertices of the *original* regular polygon. Except this time we use a radius that is smaller than the shape’s full radius. It’s smaller by `roundingRadius`

units. Once we’ve found each rounding’s center point, we use it to find the `roundingVertices`

with `getRoundingVertices`

. Of course, we also have to calculate and pass in the `roundingStartProgress`

and `roundingEndProgress`

. Once again, we store all the `roundingVertices`

and discard the `roundingCenterPoint`

. It helps to visualize it. Here’s a horribly crude diagram.

In this poorly-drawn example, we’re slicing our rounding circle up into five equal slices. That means each slice’s arc length will take up one *fifth* of the circle’s circumference. Then we push those slices outward to the borders of our shape. Each slice’s pointy end will be resting on one of the corners of the inner pentagon (colored red here). In our code, each corner of the red pentagon was a `roundingCenterPoint`

. I’ve connected up the slices’ outside edges with yellow lines. The yellow lines and the exposed green section of each slice will form our final shape. Neato!

Three shapes. Not bad for a day’s work. As always, I encourage you to mess around with the code. Perhaps you can come up with some more funky shapes. Maybe invert some of the angles? Maybe increase some radii? As `Mesh`

es in 2D games go, this geometry is pretty advanced. And there are certainly easier ways to draw rounded shapes with OpenGL. We’ll cover some of those in future tutorials. But now that you’ve encountered some of the pain points of working with complex `Mesh`

es, you’ll appreciate the other methods I’m going to teach you that much more. 😉

Click here to run the finished product!

And here’s a link to a downloadable version of the source code: rounding_corners.html. *Note that you’ll need to run it from inside the folder structure that we set up in the first tutorial.*