1+ #pragma once
2+
3+ #include " ./volume.hpp"
4+
5+ #include " ./circle.hpp"
6+ #include " ./rect.hpp"
7+
8+ namespace sndx ::collision {
9+ // A Capsule as described by two points and radius.
10+ template <Vector VectorT = glm::vec2>
11+ class Capsule {
12+ public:
13+ using Vec = VectorT;
14+ using Precision = typename Vec::value_type;
15+
16+ static constexpr size_t dimensionality () noexcept {
17+ return Vec::length ();
18+ }
19+
20+ private:
21+ Vec m_a, m_b;
22+ Precision m_radius;
23+
24+ /* Unsafe Construction Methods */
25+
26+ constexpr Capsule& setRawRadius (Precision radius) noexcept {
27+ m_radius = radius;
28+
29+ return *this ;
30+ }
31+
32+ // constructs a capsule from 2 points and radius, does not verify preconditions
33+ explicit constexpr Capsule (const Vec& posA, const Vec& posB, Precision radius, std::nullptr_t ) noexcept :
34+ m_a(posA), m_b(posB), m_radius(radius) {
35+ }
36+
37+ public:
38+ /* Safe Construction Methods */
39+
40+ constexpr Capsule (const Capsule&) = default;
41+
42+ // constructs from positions and radius
43+ constexpr Capsule (const Vec& posA, const Vec& posB, Precision radius):
44+ m_a{ posA }, m_b{posB}, m_radius{} {
45+
46+ setRadius (radius);
47+ }
48+
49+ /* Transformation Methods */
50+
51+ // set the position
52+ constexpr Capsule& setPositionA (const Vec& pos) noexcept {
53+ m_a = pos;
54+
55+ return *this ;
56+ }
57+
58+ constexpr Capsule& setPositionB (const Vec& pos) noexcept {
59+ m_b = pos;
60+
61+ return *this ;
62+ }
63+
64+ // safely set the radius
65+ constexpr Capsule& setRadius (Precision radius) {
66+ if (radius < Precision (0.0 )) {
67+ throw std::invalid_argument (" Radius cannot be negative" );
68+ }
69+
70+ m_radius = radius;
71+ return *this ;
72+ }
73+
74+ // moves both points by vec
75+ constexpr Capsule& translate (const Vec& vec) noexcept {
76+ m_a += vec;
77+ m_b += vec;
78+
79+ return *this ;
80+ }
81+
82+ /* Info Methods */
83+
84+ // get positions
85+ [[nodiscard]]
86+ constexpr const Vec& getPointA () const noexcept {
87+ return m_a;
88+ }
89+
90+ [[nodiscard]]
91+ constexpr const Vec& getPointB () const noexcept {
92+ return m_b;
93+ }
94+
95+ // get the radius
96+ [[nodiscard]]
97+ constexpr Precision getRadius () const noexcept {
98+ return m_radius;
99+ }
100+
101+ // get the dimensions of the Capsule
102+ [[nodiscard]]
103+ constexpr Vec getSize () const noexcept {
104+ Rect<Vec> boundingRect{ getPointA (), getPointB () };
105+ return boundingRect.getSize () + m_radius * Precision (2.0 );
106+ }
107+
108+ // get the Volume/Area of the Circle
109+ [[nodiscard]]
110+ constexpr Precision getArea () const noexcept {
111+ const auto & r = getRadius ();
112+
113+ if constexpr (dimensionality () == 1 ) {
114+ return glm::distance (m_a, m_b) + r * Precision (2.0 );
115+ }
116+ else {
117+ auto cylinder = CircleND<dimensionality () - 1 , Precision>{ r };
118+ auto cap = Circle<Vec>{ r };
119+
120+ return cylinder.getArea () * glm::distance (m_a, m_b) + cap.getArea ();
121+ }
122+ }
123+
124+ // get the center point of the Circle
125+ [[nodiscard]]
126+ constexpr Vec getCenter () const noexcept {
127+ return (m_a + m_b) / Precision (2.0 );
128+ }
129+
130+ [[nodiscard]]
131+ constexpr Precision getCenter (uint8_t axis) const noexcept {
132+ return getCenter ()[axis];
133+ }
134+
135+
136+ /* Collision Related Methods */
137+
138+ // get the signed distance from a point
139+ [[nodiscard]]
140+ constexpr Precision distance (const Vec& point) const noexcept {
141+ auto pa = point - m_a;
142+ auto ba = m_b - m_a;
143+ auto h = glm::clamp (glm::dot (pa, ba) / glm::dot (ba, ba), Precision (0.0 ), Precision (1.0 ));
144+ return glm::length (pa - ba * h) - getRadius ();
145+ }
146+
147+ [[nodiscard]]
148+ constexpr bool contains (const Vec& point) const noexcept {
149+ return distance (point) <= Precision (0.0 );
150+ }
151+
152+ struct RaycastResult {
153+ const Capsule<VectorT>* capsule = nullptr ;
154+ Precision near = std::numeric_limits<Precision>::min();
155+ VectorT norm;
156+
157+ [[nodiscard]]
158+ constexpr bool hit () const noexcept {
159+ return capsule != nullptr ;
160+ }
161+
162+ [[nodiscard]]
163+ constexpr Precision distance () const noexcept {
164+ return near;
165+ }
166+
167+ [[nodiscard]]
168+ constexpr VectorT normal () const noexcept {
169+ return norm;
170+ }
171+
172+ void setDistance (Precision dist) noexcept {
173+ near = dist;
174+ }
175+
176+ void setNormal (VectorT norm) noexcept {
177+ this ->norm = norm;
178+ }
179+ };
180+ using result_type = RaycastResult;
181+
182+ [[nodiscard]] // cull is ignored
183+ constexpr RaycastResult raycast (const Vec& from, const Vec& dir, bool = false ) const noexcept {
184+ RaycastResult out{};
185+
186+ auto axis = m_b - m_a;
187+ auto rel = from - m_a;
188+
189+ auto relLen = glm::dot (rel, rel);
190+ auto axisLen = glm::dot (axis, axis);
191+ auto dirDif = glm::dot (axis, dir);
192+ auto relDif = glm::dot (axis, rel);
193+ auto relDir = glm::dot (dir, rel);
194+
195+ auto a = axisLen - dirDif * dirDif;
196+ auto b = axisLen * relDir - relDif * dirDif;
197+ auto c = axisLen * relLen - relDif * relDif - m_radius * m_radius * axisLen;
198+
199+ // cylinder test
200+ if (a != Precision (0.0 )) {
201+ if (auto discriminant = b * b - a * c; discriminant >= Precision (0.0 )) {
202+ discriminant = std::sqrt (discriminant);
203+
204+ auto dist = (-b - discriminant) / a;
205+ if (dist >= Precision (0.0 )) {
206+ auto axisPos = relDif + dist * dirDif;
207+ if (axisPos > Precision (0.0 ) && axisPos < axisLen) {
208+ out.near = dist;
209+ out.capsule = this ;
210+
211+ auto hitPnt = from + dir * dist;
212+ auto onAxis = m_a + axis * (axisPos / axisLen);
213+
214+ out.norm = glm::normalize (hitPnt - onAxis);
215+ }
216+
217+ }
218+ }
219+ }
220+
221+ auto cap = Circle<VectorT>{ m_a, m_radius };
222+ if (auto tmpRes = cap.raycast (from, dir); tmpRes.hit ()) {
223+ if (tmpRes.distance () >= Precision (0.0 ) && tmpRes.distance () < out.near ) {
224+ out.capsule = this ;
225+ out.near = tmpRes.distance ();
226+ out.norm = tmpRes.normal ();
227+ }
228+ }
229+
230+ cap.setPosition (m_b);
231+ if (auto tmpRes = cap.raycast (from, dir); tmpRes.hit ()) {
232+ if (tmpRes.distance () >= Precision (0.0 ) && tmpRes.distance () < out.near ) {
233+ out.capsule = this ;
234+ out.near = tmpRes.distance ();
235+ out.norm = tmpRes.normal ();
236+ }
237+ }
238+
239+ return out;
240+ }
241+ };
242+
243+ template <size_t Dimensionality, typename InternalT = float , glm::qualifier Qualifier = glm::qualifier::defaultp>
244+ using CapsuleND = Capsule<glm::vec<Dimensionality, InternalT, Qualifier>>;
245+
246+ using Capsule1D = CapsuleND<1 >;
247+ using Capsule2D = CapsuleND<2 >;
248+ using Capsule3D = CapsuleND<3 >;
249+ using Capsule4D = CapsuleND<4 >;
250+
251+ static_assert (VolumeN<Capsule1D, 1 >);
252+ static_assert (VolumeN<Capsule2D, 2 >);
253+ static_assert (VolumeN<Capsule3D, 3 >);
254+ static_assert (VolumeN<Capsule4D, 4 >);
255+ }
0 commit comments