Rounded Corner Box

Sharing this because I was looking for it and couldn’t find it so I figured it out based on other code snippets online. Here’s a class to make rounded boxes.

import {
  Application, calculateNormals, createMesh, Mesh, Vec3,
} from 'playcanvas';

// reference: https://github.com/nepluno/RoundCornerBox/blob/master/RoundCornerBox/RoundCornerBox.hpp
export default class RoundedBox {
  app: Application;

  vertices: Vec3[];

  indices: Vec3[];

  m_nEdge: number;

  m_index_to_verts: number[];

  m_radius: number;

  private tmp: Vec3 = new Vec3();

  constructor(N: number, dimension: Vec3, radius: number, app: Application) {
    this.vertices = [];
    this.indices = [];

    this.app = app;

    const b = dimension
      .clone()
      .divScalar(2)
      .subScalar(radius);

    const nEdge = 2 * (N + 1);
    this.m_nEdge = nEdge;
    this.m_index_to_verts = Array(nEdge * nEdge * nEdge).fill(-1);
    this.m_radius = radius;
    const dx = radius / N;

    const sign = [-1.0, 1.0];
    const ks = [0, N * 2 + 1];

    // xy-planes
    for (let kidx = 0; kidx < 2; ++kidx) {
      const k = ks[kidx];
      const origin = this.tmp.clone().set(
        -b.x - radius,
        -b.y - radius,
        (b.z + radius) * sign[kidx],
      );
      for (let j = 0; j <= N; ++j) {
        for (let i = 0; i <= N; ++i) {
          let pos = origin.clone().add(this.tmp.clone().set(dx * i, dx * j, 0.0));
          this.addVertex(i, j, k, pos, this.tmp.clone().set(-b.x, -b.y, b.z * sign[kidx]));

          pos = origin.clone().add(this.tmp.clone().set(dx * i + 2.0 * b.x + radius, dx * j, 0.0));
          this.addVertex(i + N + 1, j, k, pos, this.tmp.clone().set(b.x, -b.y, b.z * sign[kidx]));

          pos = origin.clone().add(this.tmp.clone().set(
            dx * i + 2.0 * b.x + radius,
            dx * j + 2.0 * b.y + radius,
            0.0,
          ));
          this.addVertex(
            i + N + 1,
            j + N + 1,
            k,
            pos,
            this.tmp.clone().set(b.x, b.y, b.z * sign[kidx]),
          );

          pos = origin.clone().add(this.tmp.clone().set(dx * i, dx * j + 2.0 * b.y + radius, 0.0));
          this.addVertex(i, j + N + 1, k, pos, this.tmp.clone().set(-b.x, b.y, b.z * sign[kidx]));
        }
      }
      // corners
      for (let j = 0; j < N; ++j) {
        for (let i = 0; i < N; ++i) {
          this.addFace(
            this.translateIndices(i, j, k),
            this.translateIndices(i + 1, j + 1, k),
            this.translateIndices(i, j + 1, k),

            kidx === 0,
          );
          this.addFace(
            this.translateIndices(i, j, k),
            this.translateIndices(i + 1, j, k),
            this.translateIndices(i + 1, j + 1, k),

            kidx === 0,
          );

          this.addFace(
            this.translateIndices(i, j + N + 1, k),
            this.translateIndices(i + 1, j + N + 2, k),
            this.translateIndices(i, j + N + 2, k),

            kidx === 0,
          );
          this.addFace(
            this.translateIndices(i, j + N + 1, k),
            this.translateIndices(i + 1, j + N + 1, k),
            this.translateIndices(i + 1, j + N + 2, k),

            kidx === 0,
          );

          this.addFace(
            this.translateIndices(i + N + 1, j + N + 1, k),
            this.translateIndices(i + N + 2, j + N + 2, k),
            this.translateIndices(i + N + 1, j + N + 2, k),

            kidx === 0,
          );
          this.addFace(
            this.translateIndices(i + N + 1, j + N + 1, k),
            this.translateIndices(i + N + 2, j + N + 1, k),
            this.translateIndices(i + N + 2, j + N + 2, k),

            kidx === 0,
          );

          this.addFace(
            this.translateIndices(i + N + 1, j, k),
            this.translateIndices(i + N + 2, j + 1, k),
            this.translateIndices(i + N + 1, j + 1, k),

            kidx === 0,
          );
          this.addFace(
            this.translateIndices(i + N + 1, j, k),
            this.translateIndices(i + N + 2, j, k),
            this.translateIndices(i + N + 2, j + 1, k),

            kidx === 0,
          );
        }
      }
      // sides
      for (let i = 0; i < N; ++i) {
        this.addFace(
          this.translateIndices(i, N, k),
          this.translateIndices(i + 1, N + 1, k),
          this.translateIndices(i, N + 1, k),

          kidx === 0,
        );
        this.addFace(
          this.translateIndices(i, N, k),
          this.translateIndices(i + 1, N, k),
          this.translateIndices(i + 1, N + 1, k),

          kidx === 0,
        );

        this.addFace(
          this.translateIndices(N, i, k),
          this.translateIndices(N + 1, i + 1, k),
          this.translateIndices(N, i + 1, k),

          kidx === 0,
        );
        this.addFace(
          this.translateIndices(N, i, k),
          this.translateIndices(N + 1, i, k),
          this.translateIndices(N + 1, i + 1, k),

          kidx === 0,
        );

        this.addFace(
          this.translateIndices(i + N + 1, N, k),
          this.translateIndices(i + N + 2, N + 1, k),
          this.translateIndices(i + N + 1, N + 1, k),

          kidx === 0,
        );
        this.addFace(
          this.translateIndices(i + N + 1, N, k),
          this.translateIndices(i + N + 2, N, k),
          this.translateIndices(i + N + 2, N + 1, k),

          kidx === 0,
        );

        this.addFace(
          this.translateIndices(N, i + N + 1, k),
          this.translateIndices(N + 1, i + N + 2, k),
          this.translateIndices(N, i + N + 2, k),

          kidx === 0,
        );
        this.addFace(
          this.translateIndices(N, i + N + 1, k),
          this.translateIndices(N + 1, i + N + 1, k),
          this.translateIndices(N + 1, i + N + 2, k),

          kidx === 0,
        );
      }
      // central
      this.addFace(
        this.translateIndices(N, N, k),
        this.translateIndices(N + 1, N + 1, k),
        this.translateIndices(N, N + 1, k),

        kidx === 0,
      );
      this.addFace(
        this.translateIndices(N, N, k),
        this.translateIndices(N + 1, N, k),
        this.translateIndices(N + 1, N + 1, k),

        kidx === 0,
      );
    }

    // xz-planes
    for (let kidx = 0; kidx < 2; ++kidx) {
      const k = ks[kidx];
      const origin = this.tmp.clone().set(
        -b.x - radius,
        (b.y + radius) * sign[kidx],
        -b.z - radius,
      );
      for (let j = 0; j <= N; ++j) {
        for (let i = 0; i <= N; ++i) {
          let pos = origin.clone().add(this.tmp.clone().set(dx * i, 0.0, dx * j));
          this.addVertex(i, k, j, pos, this.tmp.clone().set(-b.x, b.y * sign[kidx], -b.z));

          pos = origin.clone().add(this.tmp.clone().set(dx * i + 2.0 * b.x + radius, 0.0, dx * j));
          this.addVertex(i + N + 1, k, j, pos, this.tmp.clone().set(b.x, b.y * sign[kidx], -b.z));

          pos = origin.clone().add(this.tmp.clone().set(
            dx * i + 2.0 * b.x + radius,
            0.0,
            dx * j + 2.0 * b.z + radius,
          ));
          this.addVertex(
            i + N + 1,
            k,
            j + N + 1,
            pos,
            this.tmp.clone().set(b.x, b.y * sign[kidx], b.z),
          );

          pos = origin.clone().add(this.tmp.clone().set(dx * i, 0.0, dx * j + 2.0 * b.z + radius));
          this.addVertex(i, k, j + N + 1, pos, this.tmp.clone().set(-b.x, b.y * sign[kidx], b.z));
        }
      }
      // corners
      for (let j = 0; j < N; ++j) {
        for (let i = 0; i < N; ++i) {
          this.addFace(
            this.translateIndices(i, k, j),
            this.translateIndices(i + 1, k, j + 1),
            this.translateIndices(i, k, j + 1),

            kidx === 1,
          );
          this.addFace(
            this.translateIndices(i, k, j),
            this.translateIndices(i + 1, k, j),
            this.translateIndices(i + 1, k, j + 1),

            kidx === 1,
          );

          this.addFace(
            this.translateIndices(i, k, j + N + 1),
            this.translateIndices(i + 1, k, j + N + 2),
            this.translateIndices(i, k, j + N + 2),

            kidx === 1,
          );
          this.addFace(
            this.translateIndices(i, k, j + N + 1),
            this.translateIndices(i + 1, k, j + N + 1),
            this.translateIndices(i + 1, k, j + N + 2),

            kidx === 1,
          );

          this.addFace(
            this.translateIndices(i + N + 1, k, j + N + 1),
            this.translateIndices(i + N + 2, k, j + N + 2),
            this.translateIndices(i + N + 1, k, j + N + 2),

            kidx === 1,
          );
          this.addFace(
            this.translateIndices(i + N + 1, k, j + N + 1),
            this.translateIndices(i + N + 2, k, j + N + 1),
            this.translateIndices(i + N + 2, k, j + N + 2),

            kidx === 1,
          );

          this.addFace(
            this.translateIndices(i + N + 1, k, j),
            this.translateIndices(i + N + 2, k, j + 1),
            this.translateIndices(i + N + 1, k, j + 1),

            kidx === 1,
          );
          this.addFace(
            this.translateIndices(i + N + 1, k, j),
            this.translateIndices(i + N + 2, k, j),
            this.translateIndices(i + N + 2, k, j + 1),

            kidx === 1,
          );
        }
      }
      // sides
      for (let i = 0; i < N; ++i) {
        this.addFace(
          this.translateIndices(i, k, N),
          this.translateIndices(i + 1, k, N + 1),
          this.translateIndices(i, k, N + 1),

          kidx === 1,
        );
        this.addFace(
          this.translateIndices(i, k, N),
          this.translateIndices(i + 1, k, N),
          this.translateIndices(i + 1, k, N + 1),

          kidx === 1,
        );

        this.addFace(
          this.translateIndices(N, k, i),
          this.translateIndices(N + 1, k, i + 1),
          this.translateIndices(N, k, i + 1),

          kidx === 1,
        );
        this.addFace(
          this.translateIndices(N, k, i),
          this.translateIndices(N + 1, k, i),
          this.translateIndices(N + 1, k, i + 1),

          kidx === 1,
        );

        this.addFace(
          this.translateIndices(i + N + 1, k, N),
          this.translateIndices(i + N + 2, k, N + 1),
          this.translateIndices(i + N + 1, k, N + 1),

          kidx === 1,
        );
        this.addFace(
          this.translateIndices(i + N + 1, k, N),
          this.translateIndices(i + N + 2, k, N),
          this.translateIndices(i + N + 2, k, N + 1),

          kidx === 1,
        );

        this.addFace(
          this.translateIndices(N, k, i + N + 1),
          this.translateIndices(N + 1, k, i + N + 2),
          this.translateIndices(N, k, i + N + 2),

          kidx === 1,
        );
        this.addFace(
          this.translateIndices(N, k, i + N + 1),
          this.translateIndices(N + 1, k, i + N + 1),
          this.translateIndices(N + 1, k, i + N + 2),

          kidx === 1,
        );
      }
      // central
      this.addFace(
        this.translateIndices(N, k, N),
        this.translateIndices(N + 1, k, N + 1),
        this.translateIndices(N, k, N + 1),

        kidx === 1,
      );
      this.addFace(
        this.translateIndices(N, k, N),
        this.translateIndices(N + 1, k, N),
        this.translateIndices(N + 1, k, N + 1),

        kidx === 1,
      );
    }

    // yz-planes
    for (let kidx = 0; kidx < 2; ++kidx) {
      const k = ks[kidx];
      const origin = this.tmp.clone().set(
        (b.x + radius) * sign[kidx],
        -b.y - radius,
        -b.z - radius,
      );
      for (let j = 0; j <= N; ++j) {
        for (let i = 0; i <= N; ++i) {
          let pos = origin.clone().add(this.tmp.clone().set(0.0, dx * i, dx * j));
          this.addVertex(k, i, j, pos, this.tmp.clone().set(b.x * sign[kidx], -b.y, -b.z));

          pos = origin.clone().add(this.tmp.clone().set(0.0, dx * i + 2.0 * b.y + radius, dx * j));
          this.addVertex(k, i + N + 1, j, pos, this.tmp.clone().set(b.x * sign[kidx], b.y, -b.z));

          pos = origin.clone().add(this.tmp.clone().set(
            0.0,
            dx * i + 2.0 * b.y + radius,
            dx * j + 2.0 * b.z + radius,
          ));
          this.addVertex(
            k,
            i + N + 1,
            j + N + 1,
            pos,
            this.tmp.clone().set(b.x * sign[kidx], b.y, b.z),
          );

          pos = origin.clone().add(this.tmp.clone().set(0.0, dx * i, dx * j + 2.0 * b.z + radius));
          this.addVertex(k, i, j + N + 1, pos, this.tmp.clone().set(b.x * sign[kidx], -b.y, b.z));
        }
      }
      // corners
      for (let j = 0; j < N; ++j) {
        for (let i = 0; i < N; ++i) {
          this.addFace(
            this.translateIndices(k, i, j),
            this.translateIndices(k, i + 1, j + 1),
            this.translateIndices(k, i, j + 1),

            kidx === 0,
          );
          this.addFace(
            this.translateIndices(k, i, j),
            this.translateIndices(k, i + 1, j),
            this.translateIndices(k, i + 1, j + 1),

            kidx === 0,
          );

          this.addFace(
            this.translateIndices(k, i, j + N + 1),
            this.translateIndices(k, i + 1, j + N + 2),
            this.translateIndices(k, i, j + N + 2),

            kidx === 0,
          );
          this.addFace(
            this.translateIndices(k, i, j + N + 1),
            this.translateIndices(k, i + 1, j + N + 1),
            this.translateIndices(k, i + 1, j + N + 2),

            kidx === 0,
          );

          this.addFace(
            this.translateIndices(k, i + N + 1, j + N + 1),
            this.translateIndices(k, i + N + 2, j + N + 2),
            this.translateIndices(k, i + N + 1, j + N + 2),

            kidx === 0,
          );
          this.addFace(
            this.translateIndices(k, i + N + 1, j + N + 1),
            this.translateIndices(k, i + N + 2, j + N + 1),
            this.translateIndices(k, i + N + 2, j + N + 2),

            kidx === 0,
          );

          this.addFace(
            this.translateIndices(k, i + N + 1, j),
            this.translateIndices(k, i + N + 2, j + 1),
            this.translateIndices(k, i + N + 1, j + 1),

            kidx === 0,
          );
          this.addFace(
            this.translateIndices(k, i + N + 1, j),
            this.translateIndices(k, i + N + 2, j),
            this.translateIndices(k, i + N + 2, j + 1),

            kidx === 0,
          );
        }
      }
      // sides
      for (let i = 0; i < N; ++i) {
        this.addFace(
          this.translateIndices(k, i, N),
          this.translateIndices(k, i + 1, N + 1),
          this.translateIndices(k, i, N + 1),

          kidx === 0,
        );
        this.addFace(
          this.translateIndices(k, i, N),
          this.translateIndices(k, i + 1, N),
          this.translateIndices(k, i + 1, N + 1),

          kidx === 0,
        );

        this.addFace(
          this.translateIndices(k, N, i),
          this.translateIndices(k, N + 1, i + 1),
          this.translateIndices(k, N, i + 1),

          kidx === 0,
        );
        this.addFace(
          this.translateIndices(k, N, i),
          this.translateIndices(k, N + 1, i),
          this.translateIndices(k, N + 1, i + 1),

          kidx === 0,
        );

        this.addFace(
          this.translateIndices(k, i + N + 1, N),
          this.translateIndices(k, i + N + 2, N + 1),
          this.translateIndices(k, i + N + 1, N + 1),

          kidx === 0,
        );
        this.addFace(
          this.translateIndices(k, i + N + 1, N),
          this.translateIndices(k, i + N + 2, N),
          this.translateIndices(k, i + N + 2, N + 1),

          kidx === 0,
        );

        this.addFace(
          this.translateIndices(k, N, i + N + 1),
          this.translateIndices(k, N + 1, i + N + 2),
          this.translateIndices(k, N, i + N + 2),

          kidx === 0,
        );
        this.addFace(
          this.translateIndices(k, N, i + N + 1),
          this.translateIndices(k, N + 1, i + N + 1),
          this.translateIndices(k, N + 1, i + N + 2),

          kidx === 0,
        );
      }
      // central
      this.addFace(
        this.translateIndices(k, N, N),
        this.translateIndices(k, N + 1, N + 1),
        this.translateIndices(k, N, N + 1),

        kidx === 0,
      );
      this.addFace(
        this.translateIndices(k, N, N),
        this.translateIndices(k, N + 1, N),
        this.translateIndices(k, N + 1, N + 1),

        kidx === 0,
      );
    }
  }

  addVertex(i: number, j: number, k: number, pos: Vec3, basePos: Vec3) {
    const pidx = k * this.m_nEdge * this.m_nEdge + j * this.m_nEdge + i;
    if (this.m_index_to_verts[pidx] < 0) {
      this.m_index_to_verts[pidx] = this.vertices.length;

      const dir = pos.sub(basePos);
      if (dir.length() > 0.0) {
        dir.normalize();
        this.vertices.push(basePos.add(dir.mulScalar(this.m_radius)));
      } else { this.vertices.push(pos); }
    }
  }

  translateIndices(i: number, j: number, k: number) {
    const pidx = k * this.m_nEdge * this.m_nEdge + j * this.m_nEdge + i;
    return this.m_index_to_verts[pidx];
  }

  addFace(i: number, j: number, k: number, inversed: boolean) {
    if (inversed) {
      this.indices.push(this.tmp.clone().set(i, k, j));
    } else {
      this.indices.push(this.tmp.clone().set(i, j, k));
    }
  }

  toMesh() {
    const vertexPositions = [];
    const vertexIndices = [];

    this.vertices.forEach((vertex) => {
      vertexPositions.push(vertex.x, vertex.y, vertex.z);
    });

    this.indices.forEach((idx) => {
      vertexIndices.push(idx.x, idx.y, idx.z);
    });

    const mesh = createMesh(
      this.app.graphicsDevice,
      vertexPositions,
      {
        indices: vertexIndices,
        normals: calculateNormals(vertexPositions, vertexIndices),
      },
    );

    return mesh;
  }

  static create(N: number, dimension: Vec3, radius: number, app: Application): Mesh {
    const box = new RoundedBox(N, dimension, radius, app);
    return box.toMesh();
  }
}

1 Like

Hi @joshb and welcome,

Many thanks for sharing!