16 constexpr float EPS = 1e-6f;
18 bool intersectSegmentSphere(
23 const float a = d.
dot(d);
28 const Vector3 m = start - center;
29 const float b = m.
dot(d);
30 const float c = m.dot(m) - radius * radius;
31 if (c > 0.0f && b > 0.0f) {
35 const float discr = b * b - a * c;
40 const float sqrtDiscr = std::sqrt(discr);
41 float t = (-b - sqrtDiscr) / a;
42 if (t < 0.0f || t > 1.0f) {
43 t = (-b + sqrtDiscr) / a;
44 if (t < 0.0f || t > 1.0f) {
50 outPoint = start + d * t;
51 outNormal = (outPoint - center).normalized();
52 if (outNormal.lengthSquared() <= 1e-8f) {
53 outNormal =
Vector3(0.0f, 1.0f, 0.0f);
58 bool intersectSegmentAabb(
65 Vector3 hitNormal(0.0f, 0.0f, 0.0f);
67 const float minB[3] = {-halfExtents.getX(), -halfExtents.getY(), -halfExtents.getZ()};
68 const float maxB[3] = { halfExtents.getX(), halfExtents.getY(), halfExtents.getZ()};
69 const float s[3] = {start.getX(), start.getY(), start.getZ()};
70 const float v[3] = {d.getX(), d.getY(), d.getZ()};
72 for (
int axis = 0; axis < 3; ++axis) {
73 if (std::abs(v[axis]) < EPS) {
74 if (s[axis] < minB[axis] || s[axis] > maxB[axis]) {
80 float t1 = (minB[axis] - s[axis]) / v[axis];
81 float t2 = (maxB[axis] - s[axis]) / v[axis];
82 float enter = std::min(t1, t2);
83 float exit = std::max(t1, t2);
87 if (axis == 0) hitNormal =
Vector3((t1 > t2) ? 1.0f : -1.0f, 0.0f, 0.0f);
88 if (axis == 1) hitNormal =
Vector3(0.0f, (t1 > t2) ? 1.0f : -1.0f, 0.0f);
89 if (axis == 2) hitNormal =
Vector3(0.0f, 0.0f, (t1 > t2) ? 1.0f : -1.0f);
91 tMax = std::min(tMax, exit);
97 if (tMin < 0.0f || tMin > 1.0f) {
102 outPoint = start + d * tMin;
103 outNormal = hitNormal.lengthSquared() > EPS ? hitNormal :
Vector3(0.0f, 1.0f, 0.0f);
107 bool intersectSegmentCapsuleY(
108 const Vector3& start,
const Vector3& end,
const float radius,
const float height,
112 const float halfLine = std::max(0.0f, height * 0.5f - radius);
114 float bestT = std::numeric_limits<float>::max();
119 const float a = d.
getX() * d.getX() + d.getZ() * d.getZ();
120 const float b = 2.0f * (start.getX() * d.getX() + start.getZ() * d.getZ());
121 const float c = start.getX() * start.getX() + start.getZ() * start.getZ() - radius * radius;
123 const float disc = b * b - 4.0f * a * c;
125 const float sqrtDisc = std::sqrt(disc);
126 const float tCand[2] = {
127 (-b - sqrtDisc) / (2.0f * a),
128 (-b + sqrtDisc) / (2.0f * a)
130 for (
const float t : tCand) {
131 if (t < 0.0f || t > 1.0f) {
134 const float y = start.getY() + d.getY() * t;
135 if (y < -halfLine || y > halfLine) {
139 const Vector3 p = start + d * t;
151 const Vector3 capTop(0.0f, halfLine, 0.0f);
152 const Vector3 capBottom(0.0f, -halfLine, 0.0f);
153 float tSphere = 0.0f;
156 if (intersectSegmentSphere(start, end, capTop, radius, tSphere, pSphere, nSphere) && tSphere < bestT) {
159 bestNormal = nSphere;
162 if (intersectSegmentSphere(start, end, capBottom, radius, tSphere, pSphere, nSphere) && tSphere < bestT) {
165 bestNormal = nSphere;
174 outPoint = bestPoint;
175 outNormal = bestNormal;
181 bool intersectCollisionShape(
185 if (!collision || !collision->entity()) {
189 const Matrix4 world = collision->entity()->worldTransform();
191 const Vector3 localStart = invWorld.transformPoint(start);
192 const Vector3 localEnd = invWorld.transformPoint(end);
198 const std::string type = collision->type();
201 hit = intersectSegmentAabb(localStart, localEnd, collision->halfExtents(), localT, localPoint, localNormal);
202 }
else if (type ==
"sphere") {
203 hit = intersectSegmentSphere(localStart, localEnd,
Vector3(0.0f, 0.0f, 0.0f), collision->radius(),
204 localT, localPoint, localNormal);
205 }
else if (type ==
"capsule") {
206 hit = intersectSegmentCapsuleY(localStart, localEnd, collision->radius(), collision->height(),
207 localT, localPoint, localNormal);
210 float sphereT = 0.0f;
214 hit = intersectSegmentSphere(start, end, sphere.center(), sphere.radius(), sphereT, spherePoint, sphereNormal);
217 outPoint = spherePoint;
218 outNormal = sphereNormal;
227 const Vector3 worldPoint = world.transformPoint(localPoint);
229 if (worldNormal.lengthSquared() <= EPS) {
230 worldNormal =
Vector3(0.0f, 1.0f, 0.0f);
233 outT = std::clamp(localT, 0.0f, 1.0f);
234 outPoint = worldPoint;
235 outNormal = worldNormal;
251 std::vector<RaycastResult> results;
255 if (!rigidbody || !rigidbody->enabled() || !rigidbody->entity()) {
259 auto* collision = rigidbody->collision();
260 if (!collision || !collision->enabled()) {
267 if (!intersectCollisionShape(collision, start, end, t, point, normal)) {
272 result.
entity = rigidbody->entity();
275 result.
point = point;
278 results.push_back(result);
282 if (std::abs(a.hitFraction - b.hitFraction) > 1e-6f) {
283 return a.hitFraction < b.hitFraction;
Bounding sphere defined by center and radius for intersection and containment tests.
static const std::vector< RigidBodyComponent * > & instances()
std::optional< RaycastResult > raycastFirst(const Vector3 &start, const Vector3 &end) const
std::vector< RaycastResult > raycastAll(const Vector3 &start, const Vector3 &end) const
4x4 column-major transformation matrix with SIMD acceleration.
RigidBodyComponent * rigidbody
CollisionComponent * collision
3D vector for positions, directions, and normals with multi-backend SIMD acceleration.
Vector3 normalized() const
Vector3 transformNormal(const Matrix4 &mat) const
float dot(const Vector3 &other) const
float lengthSquared() const