2
\$\begingroup\$

I want to draw a 3D capsule defined by a segment (two points start->end) and a radius:

capsule diagram
Image source: Orionist on Wikimedia Commons

I already have a sphere leaning on this answer: https://stackoverflow.com/a/31326534/1902536. I understand that I can expand the Parametric Equation to an ellipse one but I'm not sure how to do it with the segment (that may have rotation).

what is the best way to draw/model a capsule?

\$\endgroup\$
  • 3
    \$\begingroup\$ A capsule (two hemispheres joined by a cylinder) is not the same shape as an ellipsoid (a stretched/squashed sphere). Can you clarify which one you want to draw? \$\endgroup\$ – DMGregory Aug 4 '18 at 15:52
  • 2
    \$\begingroup\$ Based FloFu's definition of the shape as a segment + radius, I've updated it to describe specifically just a capsule. @FloFu, if you meant an ellipsoid (and not a capsule) could you update? \$\endgroup\$ – doppelgreener Aug 4 '18 at 15:54
  • \$\begingroup\$ Hey, thanks for coming back to post your findings. Keep in mind that the answer will have more visibility if it is posted in an answer post (and as a bonus, you'll be able to get more rep ;P) instead of an edit to the question. (It's totally OK to post answer to your own question!) \$\endgroup\$ – Alexandre Vaillancourt Aug 7 '18 at 14:03
  • \$\begingroup\$ OK, I did it :) \$\endgroup\$ – FloFu Aug 8 '18 at 5:55
2
\$\begingroup\$

First we can construct a local basis for the space of the capsule.

axis = end - start;
length = Length(axis)

localZ = axis/length

localX = GetAnyPerpendicularUnitVector(localZ)

localY = Cross(localZ, localX)

Now we can parametrize points on the cylinder segment using two variables u & v in the range 0...1:

shaftPoint = start
      + localX * cos(2 * pi * u) * r
      + localY * sin(2 * pi * u) * r
      + localZ * v * length

And points on the hemispheres similarly:

latitude = (pi/2) * (v - 1)
startHemispherePoint = start
      + localX * cos(2 * pi * u) * cos(latitude) * r
      + localY * sin(2 * pi * u) * cos(latitude) * r
      + localZ * sin(latitude) * r

latitude = (pi/2) * v
endHemispherePoint = end
      + localX * cos(2 * pi * u) * cos(latitude) * r
      + localY * sin(2 * pi * u) * cos(latitude) * r
      + localZ * sin(latitude) * r

Note that the vertices produced for v = 1 in the start hemisphere formula are the same as those made by the shaft formula at v = 0. And likewise the shaft's v = 1 is the end hemisphere's v = 0, ensuring all three parts match up seamlessly. You can generate vertices on just one side of the seam or the other so as not to produce duplicates.

\$\endgroup\$
1
\$\begingroup\$

Based on the answer from @DMGregory here is my implementation:

glm::vec3 GetAnyPerpendicularUnitVector(const glm::vec3& vec)
{
  if (vec.y != 0.0f || vec.z != 0.0f)
    return glm::vec3(1, 0, 0);
  else
    return glm::vec3(0, 1, 0);
}

void DebugManager::drawCapsule(const Capsule& c, const glm::vec4& color)
{
  const glm::vec3 axis   = c.end - c.start;
  const float     length = glm::length(axis);
  const glm::vec3 localZ = axis / length;
  const glm::vec3 localX = GetAnyPerpendicularUnitVector(localZ);
  const glm::vec3 localY = glm::cross(localZ, localX);

  using glm::cos;
  using glm::sin;
  constexpr float pi = glm::pi<float>();

  const glm::vec3 start(0.0f);
  const glm::vec3 end(1.0f);
  const float     resolution = 16.0f;

  const glm::vec3 step = (end - start) / resolution;

  auto cylinder = [localX, localY, localZ, c, length](const float u,
                                                      const float v) {
    return c.start                                  //
           + localX * cos(2.0f * pi * u) * c.radius //
           + localY * sin(2.0f * pi * u) * c.radius //
           + localZ * v * length;                   //

  };

  auto sphereStart = [localX, localY, localZ, c](const float u,
                                                 const float v) -> glm::vec3 {
    const float latitude = (pi / 2.0f) * (v - 1);

    return c.start                                                  //
           + localX * cos(2.0f * pi * u) * cos(latitude) * c.radius //
           + localY * sin(2.0f * pi * u) * cos(latitude) * c.radius //
           + localZ * sin(latitude) * c.radius;
  };

  auto sphereEnd = [localX, localY, localZ, c](const float u, const float v) {
    const float latitude = (pi / 2.0f) * v;
    return c.end                                                    //
           + localX * cos(2.0f * pi * u) * cos(latitude) * c.radius //
           + localY * sin(2.0f * pi * u) * cos(latitude) * c.radius //
           + localZ * sin(latitude) * c.radius;
  };

  for (float i = 0; i < resolution; ++i) {
    for (float j = 0; j < resolution; ++j) {
      const float u = i * step.x + start.x;
      const float v = j * step.y + start.y;

      const float un =
        (i + 1 == resolution) ? end.x : (i + 1) * step.x + start.x;
      const float vn =
        (j + 1 == resolution) ? end.y : (j + 1) * step.y + start.y;

    // Draw Cylinder
      {
        const glm::vec3 p0 = cylinder(u, v);
        const glm::vec3 p1 = cylinder(u, vn);
        const glm::vec3 p2 = cylinder(un, v);
        const glm::vec3 p3 = cylinder(un, vn);

        drawTriangle(p0, p1, p2, color);
        drawTriangle(p3, p1, p2, color);
      }

    // Draw Sphere start
      {
        const glm::vec3 p0       = sphereStart(u, v);
        const glm::vec3 p1       = sphereStart(u, vn);
        const glm::vec3 p2       = sphereStart(un, v);
        const glm::vec3 p3       = sphereStart(un, vn);
        drawTriangle(p0, p1, p2, color);
        drawTriangle(p3, p1, p2, color);
      }

    // Draw Sphere end
      {
        const glm::vec3 p0       = sphereEnd(u, v);
        const glm::vec3 p1       = sphereEnd(u, vn);
        const glm::vec3 p2       = sphereEnd(un, v);
        const glm::vec3 p3       = sphereEnd(un, vn);
        drawTriangle(p0, p1, p2, color);
        drawTriangle(p3, p1, p2, color);
      }
    }
  }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.