back to posts


2019-08-14 · 1214 words · 7 min

Revisiting Revolvers, part 2 - On straight lines and curves

'Gon with the wind

Cross-section illustration of a polygonal revolver cylinder

Continuing where we left off last time - before we start adding grooves to our cylinder, let’s quickly implement an alternative design. That’s right, we’re building a little homage to the Chiappa Rhino and generating smooth polygonal cylinders.

The groundwork

As usual, let’s see how to get what we want. I’ll just focus on a single segment of the cylinder here, everything else looks the same, just rotated by \(\frac{2 \pi}{n}\).

polygonal cylinder constraints

A quick refresher:

  • \(n\) is the number of chambers (in this example, 6)

  • \(R_c\) is the radius of a single chamber (half of the caliber)

  • \(R_i\) is the added thickness on the inside of the cylinder

  • \(R_o\) is the added thickness of the chamber’s outer wall

  • \(R = \lvert AE \rvert \) is the radius of the cylinder

  • \(A\) is the center of the cylinder

  • \( \lvert CD \rvert = \frac{4}{3}R_c\)

This time, sadly, we can’t just ignore \(R_i\) and \(R_o\).

To generate the cylinder, we’ll need to first build a polygon with vertices at the ends of each non-curved segment of the outer wall, and then overlay \(n\) circles with radius \( \lvert CE \rvert \) on top of it.

We’ll need to find \( \lvert CE \rvert \) - that’s extremely easy:

$$ \begin{align} \lvert CE \rvert & = \lvert CD \rvert + \lvert DE \rvert \\ & = \frac{4}{3}R_c + R_o \end{align} $$

But we’ll also need to find each of the points \(F\), \(G\) etc. Instead of finding their actual coordinates, let’s make use of the fact that we know \( \lvert CE \rvert \) and instead calculate \( \angle FCE \). We can then use that in our generation: for a chamber \( C_k \) with its center at \(( \cos(k\alpha)\lvert AC \rvert, \sin(k\alpha)\lvert AC \rvert )\), the vertices of the underlying polygon will lie at

\( C_k + (\cos(k\alpha + \angle FCE) \lvert CE \rvert, \sin(k\alpha + \angle FCE) \lvert CE \rvert)\)

and

\( C_k + (\cos(k\alpha - \angle FCE) \lvert CE \rvert, \sin(k\alpha - \angle FCE) \lvert CE \rvert)\).

Since \( \triangle AEE’ \) is an isosceles triangle (\(E’\) is not marked on the illustration, but it lies at the intersection point of the line \(AC’\) with the cylinder, just like \(E\)), and \( \angle AHG \) is a right angle, we can see that \(AH\) bisects \( \alpha \). Furthermore, \(AH \parallel CF\), meaning that \( \angle HAE = \angle FCE \). Since \( \angle HAE = \frac{\alpha}{2} = \frac{\frac{2 \pi}{n}}{2} = \frac{\pi}{n}\), we have our answer - the points we’re looking for lie at

\( C_k + (\cos((k - \frac{1}{2})\alpha) \lvert CE \rvert, \sin((k - \frac{1}{2})\alpha) \lvert CE \rvert)\)

and

\( C_k + (\cos((k + \frac{1}{2})\alpha) \lvert CE \rvert, \sin((k + \frac{1}{2})\alpha) \lvert CE \rvert)\).

Let’s start implementing it, then! The code is more interesting than the maths this time, so let’s quickly run through it.

function demo(n, rc, r_i, r_o, polygonal) {
  var reader = new jsts.io.WKTReader();
  var chamber_centers = [];
  var polygon_verts = [];

  var alpha = (2 * Math.PI)/n;
  var ce = (4/3) * rc + r_o;

Here we initialize the lists of vertices and chamber centers we’ll need later, and precalculate \( \lvert CE \rvert \) and \( \alpha \) to save time.

if (n == 2) {
  var ak = 0;
} else {
  var ak = ((21 * Math.cos(alpha / 2) - 4) * rc) / (9 * Math.sin(alpha));
}
var ac = ak + r_i + (4 / 3) * rc;

In this snippet, exactly the same as in the previous post’s code, we precalculate \( \lvert AK \rvert = \lvert AC \rvert - \frac{4}{3}R_c - R_i\). There’s also a workaround for \(n = 2\), to avoid division by zero. Having \( \lvert AK \rvert \) lets us find \( \lvert AC \rvert \).

  for(i=0; i<n; i++){
    var chamber_center_x = Math.cos(i*alpha) * ac;
    var chamber_center_y = Math.sin(i*alpha) * ac;

    chamber_centers.push([chamber_center_x, chamber_center_y]);

Here we iterate over all the chambers, calculate the center of each one, and save it in our list.

    if(polygonal){
      var cw_vert_x = chamber_center_x + (
        Math.cos((i-0.5) * alpha)*ce
      );
      var cw_vert_y = chamber_center_y + (
        Math.sin((i-0.5) * alpha)*ce
      );

      var ccw_vert_x = chamber_center_x + (
        Math.cos((i+0.5) * alpha)*ce
      );
      var ccw_vert_y = chamber_center_y + (
        Math.sin((i+0.5) * alpha)*ce
      );

      polygon_verts.push(
        [cw_vert_x, cw_vert_y],
        [ccw_vert_x, ccw_vert_y]
      );
    }
  }

  polygon_verts.push(polygon_verts[0]);

Here is the new part - if we’re generating a polygonal cylinder, we calculate where the two vertices of the underlying polygon that are closest to the given chamber should be, using the formula we’ve just found. cw_vert is the clockwise vertex, ccw_vert is the counter clockwise one - since we iterate over the chambers in counter-clockwise order, we define our vertices in that order as well. We also duplicate the first vertex at the end of our list, because WKT (the geometry specification format that JSTS, the library I’m using to draw the demo, uses) requires polygons to be defined as such.

if (polygonal) {
  var cylinder = reader.read(
    "POLYGON ((" +
      polygon_verts
        .map(function (vert) {
          return vert[0].toFixed(8) + " " + vert[1].toFixed(8);
        })
        .join(", ") +
      "))"
  );
} else {
  var cylinder = reader.read("POINT (0 0)").buffer(ac + ce);
}

Here we finally start creating geometry. If the cylinder is polygonal, our base object will be a polygon - we turn the list of vertices into a string of comma-separated coordinates with precision fixed to eight significant numbers (so that Javascript doesn’t convert it to scientific notation for very small or very big coordinates).

If the cylinder should be round, we just define a point and buffer (grow it in size) it by \(\lvert AC \rvert + \lvert CE \rvert = \lvert AE \rvert = R\).

if (polygonal) {
  for (i = 0; i < chamber_centers.length; i++) {
    cylinder = cylinder.union(
      reader
        .read(
          "POINT (" + chamber_centers[i][0] + " " + chamber_centers[i][1] + ")"
        )
        .buffer(ce)
    );
  }
}
for (i = 0; i < chamber_centers.length; i++) {
  cylinder = cylinder.difference(
    reader
      .read(
        "POINT (" + chamber_centers[i][0] + " " + chamber_centers[i][1] + ")"
      )
      .buffer(rc)
  );
}

visualize(cylinder);

Having our cylinder, we now iterate over the chambers - twice, if it’s polygonal. We have to do it this way, since otherwise the circles we draw for the curved edges could obscure the previous chambers! We first draw circles with radius \( \lvert CE \rvert \) (these are the nice curved edges of our polygonal cylinders) centered on each chamber if the cylinder is polygonal. Then, we cut out each chamber - a circle with radius \( R_c \) centered on the same spot - and we’re done! You can play with the demo here:





As always, you can peruse the full source code for this demo here.


home · posts · gear · tech