Skip to content

Commit ba72d80

Browse files
committed
implement 3D GJK algorithm
thank gourd the internet has a wealth of info on this algorithm. And I snuck some raycasting stuff in there, untested, as all good raycasters should be.
1 parent 0f85902 commit ba72d80

7 files changed

Lines changed: 623 additions & 4 deletions

File tree

src/include/sndx/collision.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#pragma once
22

3+
#include "./collision/capsule.hpp"
34
#include "./collision/circle.hpp"
5+
#include "./collision/gjk.hpp"
46
#include "./collision/rect.hpp"
5-
#include "./collision/volume.hpp"
7+
#include "./collision/volume.hpp"
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
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+
}

src/include/sndx/collision/circle.hpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ namespace sndx::collision {
163163
return getPosition();
164164
}
165165

166+
[[nodiscard]]
167+
constexpr Precision getCenter(uint8_t axis) const noexcept {
168+
return getCenter()[axis];
169+
}
170+
166171

167172
/* Collision Related Methods */
168173

@@ -200,6 +205,79 @@ namespace sndx::collision {
200205
constexpr Precision distance(const Volume& other) const noexcept {
201206
return other.distance(getPosition()) - getRadius();
202207
}
208+
209+
[[nodiscard]] // direction should be normalized
210+
constexpr Vec supportPoint(const Vec& direction) const noexcept {
211+
return getPosition() + direction * getRadius();
212+
}
213+
214+
struct RaycastResult {
215+
const Circle<VectorT>* circle = nullptr;
216+
Precision near = std::numeric_limits<Precision>::min();
217+
Precision far = std::numeric_limits<Precision>::max();
218+
VectorT normalNear{}, normalFar{};
219+
220+
[[nodiscard]]
221+
constexpr bool hit() const noexcept {
222+
return circle != nullptr;
223+
}
224+
225+
[[nodiscard]]
226+
constexpr Precision distance() const noexcept {
227+
return near;
228+
}
229+
230+
[[nodiscard]]
231+
constexpr VectorT normal() const noexcept {
232+
return normalNear;
233+
}
234+
235+
void setDistance(Precision dist) noexcept {
236+
near = dist;
237+
}
238+
239+
void setNormal(VectorT norm) noexcept {
240+
normalNear = norm;
241+
}
242+
};
243+
using result_type = RaycastResult;
244+
245+
[[nodiscard]] // cull is ignored
246+
constexpr RaycastResult raycast(const Vec& from, const Vec& dir, bool = false) const noexcept {
247+
RaycastResult out{};
248+
249+
auto idealDir = from - getCenter();
250+
251+
auto dirDelta = glm::dot(idealDir, dir);
252+
auto distSquared = glm::dot(idealDir, idealDir);
253+
auto rad2 = getRadius() * getRadius();
254+
255+
auto discriminant = dirDelta * dirDelta - (distSquared - rad2);
256+
if (discriminant < Precision(0.0)) {
257+
return out;
258+
}
259+
260+
discriminant = std::sqrt(discriminant);
261+
262+
out.near = -dirDelta - discriminant;
263+
out.far = -dirDelta + discriminant;
264+
265+
// ray started past the circle
266+
if (out.near < Precision(0.0) && out.far < Precision(0.0)) {
267+
return out;
268+
}
269+
270+
// ray started inside the circle
271+
if (out.near < Precision(0.0)) {
272+
std::swap(out.near, out.far);
273+
}
274+
275+
out.normalNear = glm::normalize((from + dir * out.near) - getCenter());
276+
out.normalFar = glm::normalize((from + dir * out.far) - getCenter());
277+
278+
out.circle = this;
279+
return out;
280+
}
203281
};
204282

205283
template <size_t Dimensionality, typename InternalT = float, glm::qualifier Qualifier = glm::qualifier::defaultp>

0 commit comments

Comments
 (0)