Visual Computing Library  devel
Loading...
Searching...
No Matches
intersect.h
1/*****************************************************************************
2 * VCLib *
3 * Visual Computing Library *
4 * *
5 * Copyright(C) 2021-2025 *
6 * Visual Computing Lab *
7 * ISTI - Italian National Research Council *
8 * *
9 * All rights reserved. *
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the Mozilla Public License Version 2.0 as published *
13 * by the Mozilla Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 * This program is distributed in the hope that it will be useful, *
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
19 * Mozilla Public License Version 2.0 *
20 * (https://www.mozilla.org/en-US/MPL/2.0/) for more details. *
21 ****************************************************************************/
22
23#ifndef VCL_ALGORITHMS_CORE_INTERSECTION_INTERSECT_H
24#define VCL_ALGORITHMS_CORE_INTERSECTION_INTERSECT_H
25
26#include <vclib/space/core.h>
27
28#include <optional>
29
30namespace vcl {
31
32namespace detail {
33
34/*
35 * ===== plane segment intersection functions =====
36 */
37
38template<PlaneConcept PlaneType, Segment3Concept SegmentType>
39void projectSegmentEndPoints(
40 const PlaneType& p,
41 const SegmentType& s,
42 typename SegmentType::ScalarType& p0Proj,
43 typename SegmentType::ScalarType& p1Proj)
44{
45 using ScalarType = SegmentType::ScalarType;
46
47 // Compute the projection of the segment endpoints onto the plane.
48 p0Proj = s.p1() * p.direction() - p.offset();
49 p1Proj = s.p0() * p.direction() - p.offset();
50}
51
52/*
53 * ===== triangle box intersection functions =====
54 */
55
56template<typename ScalarType>
57inline void findMinMax(
58 ScalarType x0,
59 ScalarType x1,
60 ScalarType x2,
61 ScalarType& min,
62 ScalarType& max)
63{
64 min = max = x0;
65 if (x1 < min)
66 min = x1;
67 if (x1 > max)
68 max = x1;
69 if (x2 < min)
70 min = x2;
71 if (x2 > max)
72 max = x2;
73}
74
75/*======================== X-tests ========================*/
76template<typename ScalarType, PointConcept PointType>
77inline bool axisTestX01(
78 ScalarType a,
79 ScalarType b,
80 ScalarType fa,
81 ScalarType fb,
82 const PointType& v0,
83 const PointType& v2,
84 const PointType& bHalfSize)
85{
86 ScalarType p0 = a * v0.y() - b * v0.z();
87 ScalarType p2 = a * v2.y() - b * v2.z();
88 ScalarType min, max;
89 if (p0 < p2) {
90 min = p0;
91 max = p2;
92 }
93 else {
94 min = p2;
95 max = p0;
96 }
97 ScalarType rad = fa * bHalfSize.y() + fb * bHalfSize.z();
98 if (min > rad || max < -rad)
99 return false;
100 return true;
101}
102
103template<typename ScalarType, PointConcept PointType>
104inline bool axisTestX2(
105 ScalarType a,
106 ScalarType b,
107 ScalarType fa,
108 ScalarType fb,
109 const PointType& v0,
110 const PointType& v1,
111 const PointType& bHalfSize)
112{
113 ScalarType p0 = a * v0.y() - b * v0.z();
114 ScalarType p1 = a * v1.y() - b * v1.z();
115 ScalarType min, max;
116 if (p0 < p1) {
117 min = p0;
118 max = p1;
119 }
120 else {
121 min = p1;
122 max = p0;
123 }
124 ScalarType rad = fa * bHalfSize.y() + fb * bHalfSize.z();
125 if (min > rad || max < -rad)
126 return false;
127 return true;
128}
129
130/*======================== Y-tests ========================*/
131template<typename ScalarType, PointConcept PointType>
132inline bool axisTestY02(
133 ScalarType a,
134 ScalarType b,
135 ScalarType fa,
136 ScalarType fb,
137 const PointType& v0,
138 const PointType& v2,
139 const PointType& bHalfSize)
140{
141 ScalarType p0 = -a * v0.x() + b * v0.z();
142 ScalarType p2 = -a * v2.x() + b * v2.z();
143 ScalarType min, max;
144 if (p0 < p2) {
145 min = p0;
146 max = p2;
147 }
148 else {
149 min = p2;
150 max = p0;
151 }
152 ScalarType rad = fa * bHalfSize.x() + fb * bHalfSize.z();
153 if (min > rad || max < -rad)
154 return false;
155 return true;
156}
157
158template<typename ScalarType, PointConcept PointType>
159inline bool axisTestY1(
160 ScalarType a,
161 ScalarType b,
162 ScalarType fa,
163 ScalarType fb,
164 const PointType& v0,
165 const PointType& v1,
166 const PointType& bHalfSize)
167{
168 ScalarType p0 = -a * v0.x() + b * v0.z();
169 ScalarType p1 = -a * v1.x() + b * v1.z();
170 ScalarType min, max;
171 if (p0 < p1) {
172 min = p0;
173 max = p1;
174 }
175 else {
176 min = p1;
177 max = p0;
178 }
179 ScalarType rad = fa * bHalfSize.x() + fb * bHalfSize.z();
180 if (min > rad || max < -rad)
181 return false;
182 return true;
183}
184
185/*======================== Z-tests ========================*/
186template<typename ScalarType, PointConcept PointType>
187inline bool axisTestZ12(
188 ScalarType a,
189 ScalarType b,
190 ScalarType fa,
191 ScalarType fb,
192 const PointType& v1,
193 const PointType& v2,
194 const PointType& bHalfSize)
195{
196 ScalarType p1 = a * v1.x() - b * v1.y();
197 ScalarType p2 = a * v2.x() - b * v2.y();
198 ScalarType min, max;
199 if (p1 < p2) {
200 min = p1;
201 max = p2;
202 }
203 else {
204 min = p2;
205 max = p1;
206 }
207 ScalarType rad = fa * bHalfSize.x() + fb * bHalfSize.y();
208 if (min > rad || max < -rad)
209 return false;
210 return true;
211}
212
213template<typename ScalarType, PointConcept PointType>
214inline bool axisTestZ0(
215 ScalarType a,
216 ScalarType b,
217 ScalarType fa,
218 ScalarType fb,
219 const PointType& v0,
220 const PointType& v1,
221 const PointType& bHalfSize)
222{
223 ScalarType p0 = a * v0.x() - b * v0.y();
224 ScalarType p1 = a * v1.x() - b * v1.y();
225 ScalarType min, max;
226 if (p0 < p1) {
227 min = p0;
228 max = p1;
229 }
230 else {
231 min = p1;
232 max = p0;
233 }
234 ScalarType rad = fa * bHalfSize.x() + fb * bHalfSize.y();
235 if (min > rad || max < -rad)
236 return false;
237 return true;
238}
239
240} // namespace detail
241
257template<PlaneConcept PlaneType, Box3Concept BoxType>
258bool intersect(const PlaneType& plane, const BoxType& box)
259{
260 using PointType = BoxType::PointType;
261 using ScalarType = PointType::ScalarType;
262
263 // Convert AABB to center-extents representation
264 PointType c = box.center(); // Compute AABB center
265 PointType e = box.max() - c; // Compute positive extents
266
267 PointType n = plane.direction();
268 // Compute the projection interval radius of b onto L(t) = b.c + t * p.n
269 ScalarType r =
270 e[0] * std::abs(n[0]) + e[1] * std::abs(n[1]) + e[2] * std::abs(n[2]);
271
272 // Compute distance of box center from plane
273 ScalarType s = n.dot(c) - plane.offset();
274
275 // Intersection occurs when distance s falls within [-r,+r] interval
276 return std::abs(s) <= r;
277}
278
284template<Box3Concept BoxType, PlaneConcept PlaneType>
285bool intersect(const BoxType& box, const PlaneType& p)
286{
287 return intersect(p, box);
288}
289
307template<PlaneConcept PlaneType, Segment3Concept SegmentType>
309{
310 using ScalarType = SegmentType::ScalarType;
311
312 ScalarType p0Proj;
313 ScalarType p1Proj;
314
315 detail::projectSegmentEndPoints(plane, segment, p1Proj, p0Proj);
316
317 // If both endpoints are on the same side of the plane, there is no
318 // intersection.
319 if ((p0Proj > 0) - (p1Proj < 0))
320 return false;
321
322 // If both endpoints have the same projection onto the plane, there is no
323 // intersection.
324 if (p1Proj == p0Proj)
325 return false;
326
327 return true;
328}
329
335template<Segment3Concept SegmentType, PlaneConcept PlaneType>
337{
338 return intersect(plane, segment);
339}
340
363template<PlaneConcept PlaneType, Segment3Concept SegmentType>
364std::optional<typename SegmentType::PointType> intersection(
365 const PlaneType& plane,
366 const SegmentType& segment)
367{
368 std::optional<typename SegmentType::PointType> intersection;
369
370 using ScalarType = SegmentType::ScalarType;
371
372 ScalarType p0Proj;
373 ScalarType p1Proj;
374
375 detail::projectSegmentEndPoints(plane, segment, p1Proj, p0Proj);
376
377 // If either endpoint is on the opposite side of the plane, there is an
378 // intersection.
379 if ((p0Proj > 0) != (p1Proj > 0) || p1Proj != p0Proj) {
380 // check that we perform the computation in a way that is independent
381 // with v0 v1 swaps
382 if (p1Proj < p0Proj)
384 segment.p0() + (segment.p1() - segment.p0()) *
385 std::abs(p1Proj / (p0Proj - p1Proj));
386 if (p1Proj > p0Proj)
388 segment.p1() + (segment.p0() - segment.p1()) *
389 std::abs(p0Proj / (p1Proj - p0Proj));
390 }
391
392 return intersection;
393}
394
407template<SphereConcept SphereType, Box3Concept BoxType>
408bool intersect(const SphereType& sphere, const BoxType& box)
409{
410 return sphere.intersects(box);
411}
412
418template<Box3Concept BoxType, SphereConcept SphereType>
419bool intersect(const BoxType& box, const SphereType& sphere)
420{
421 return sphere.intersects(box);
422}
423
444template<Triangle2Concept TriangleType, Point2Concept PointType>
445bool intersect(const TriangleType& triangle, const PointType& point)
446{
447 using TP = TriangleType::PointType;
448 using ScalarType = TP::ScalarType;
449
450 const TP& p0 = triangle.point(0);
451 const TP& p1 = triangle.point(1);
452 const TP& p2 = triangle.point(2);
453
454 ScalarType A = triangle.area();
455 ScalarType sign = A < 0 ? -1 : 1;
456
457 ScalarType s =
458 (p0.y() * p2.x() - p0.x() * p2.y() + (p2.y() - p0.y()) * point.x() +
459 (p0.x() - p2.x()) * point.y()) *
460 sign;
461 ScalarType tt =
462 (p0.x() * p1.y() - p0.y() * p1.x() + (p0.y() - p1.y()) * point.x() +
463 (p1.x() - p0.x()) * point.y()) *
464 sign;
465
466 return s > 0 && tt > 0 && (s + tt) < 2 * A * sign;
467}
468
474template<Point2Concept PointType, Triangle2Concept TriangleType>
475bool intersect(const PointType& point, const TriangleType& triangle)
476{
477 return intersect(triangle, point);
478}
479
494template<Triangle3Concept TriangleType, Point3Concept PointType>
495bool intersect(const TriangleType& triangle, const PointType& point)
496{
497 PointType v1 = triangle.point(1) - triangle.point(0);
498 PointType v2 = triangle.point(2) - triangle.point(0);
499 PointType v3 = point - triangle.point(0);
500
501 return v1.dot(v2.cross(v3)) > 0;
502}
503
509template<Point3Concept PointType, Triangle3Concept TriangleType>
510bool intersect(const PointType& point, const TriangleType& triangle)
511{
512 return intersect(triangle, point);
513}
514
537template<Triangle3Concept TriangleType, Box3Concept BoxType>
538bool intersect(const TriangleType& triangle, const BoxType& box)
539{
540 using PointType = TriangleType::PointType;
541 using ScalarType = PointType::ScalarType;
542
543 PointType boxCenter = box.center();
544 PointType bHalfSize = box.size() / 2;
545
546 /* use separating axis theorem to test overlap between triangle and box
547 * need to test for overlap in these directions:
548 * 1) the {x,y,z}-directions (actually, since we use the AABB of the
549 * triangle we do not even need to test these)
550 * 2) normal of the triangle
551 * 3) cross product(edge from tri, {x,y,z}-direction)
552 * this gives 3x3=9 more tests
553 */
554 ScalarType min, max;
555 PointType normal;
556
557 /* This is the fastest branch on Sun */
558 /* move everything so that the boxCenter is in (0,0,0) */
559 PointType v0 = triangle.point(0) - boxCenter;
560 PointType v1 = triangle.point(1) - boxCenter;
561 PointType v2 = triangle.point(2) - boxCenter;
562
563 /* compute triangle edges */
564 PointType e0 = v1 - v0;
565 PointType e1 = v2 - v1;
566 PointType e2 = v0 - v2;
567
568 /* Bullet 3: */
569 /* test the 9 tests first (this was faster) */
570 ScalarType fex = std::abs(e0.x());
571 ScalarType fey = std::abs(e0.y());
572 ScalarType fez = std::abs(e0.z());
573
574 if (!detail::axisTestX01(e0.z(), e0.y(), fez, fey, v0, v2, bHalfSize))
575 return false;
576 if (!detail::axisTestY02(e0.z(), e0.x(), fez, fex, v0, v2, bHalfSize))
577 return false;
578 if (!detail::axisTestZ12(e0.y(), e0.x(), fey, fex, v1, v2, bHalfSize))
579 return false;
580
581 fex = std::abs(e1.x());
582 fey = std::abs(e1.y());
583 fez = std::abs(e1.z());
584
585 if (!detail::axisTestX01(e1.z(), e1.y(), fez, fey, v0, v2, bHalfSize))
586 return false;
587 if (!detail::axisTestY02(e1.z(), e1.x(), fez, fex, v0, v2, bHalfSize))
588 return false;
589 if (!detail::axisTestZ0(e1.y(), e1.x(), fey, fex, v0, v1, bHalfSize))
590 return false;
591
592 fex = std::abs(e2.x());
593 fey = std::abs(e2.y());
594 fez = std::abs(e2.z());
595 if (!detail::axisTestX2(e2.z(), e2.y(), fez, fey, v0, v1, bHalfSize))
596 return false;
597 if (!detail::axisTestY1(e2.z(), e2.x(), fez, fex, v0, v1, bHalfSize))
598 return false;
599 if (!detail::axisTestZ12(e2.y(), e2.x(), fey, fex, v1, v2, bHalfSize))
600 return false;
601
602 /* Bullet 1:
603 * first test overlap in the {x,y,z}-directions
604 * find min, max of the triangle each direction, and test for overlap in
605 * that direction -- this is equivalent to testing a minimal AABB around
606 * the triangle against the AABB
607 */
608
609 /* test in X-direction */
610 detail::findMinMax(v0.x(), v1.x(), v2.x(), min, max);
611 if (min > bHalfSize.x() || max < -bHalfSize.x())
612 return false;
613
614 /* test in Y-direction */
615 detail::findMinMax(v0.y(), v1.y(), v2.y(), min, max);
616 if (min > bHalfSize.y() || max < -bHalfSize.y())
617 return false;
618
619 /* test in Z-direction */
620 detail::findMinMax(v0.z(), v1.z(), v2.z(), min, max);
621 if (min > bHalfSize.z() || max < -bHalfSize.z())
622 return false;
623
624 /* Bullet 2:
625 * test if the box intersects the plane of the triangle
626 * compute plane equation of triangle: normal*x+d=0
627 */
628 normal = e0.cross(e1);
630 triangle.point(0), triangle.point(1), triangle.point(2));
631 if (!intersect(plane, box))
632 return false;
633
634 return true; /* box and triangle overlaps */
635}
636
642template<Box3Concept BoxType, Triangle3Concept TriangleType>
643bool intersect(const BoxType& box, const TriangleType& triangle)
644{
645 return intersect(triangle, box);
646}
647
662template<
663 Triangle3Concept TriangleType,
664 SphereConcept SphereType,
665 Point3Concept PointType,
666 typename ScalarType>
668 const TriangleType& triangle,
669 const SphereType& sphere,
670 PointType& witness,
671 std::pair<ScalarType, ScalarType>& res)
672{
673 bool penetrationDetected = false;
674
675 ScalarType radius = sphere.radius();
676 PointType center = sphere.center();
677 PointType p0 = triangle.point0() - center;
678 PointType p1 = triangle.point1() - center;
679 PointType p2 = triangle.point2() - center;
680
681 PointType p10 = p1 - p0;
682 PointType p21 = p2 - p1;
683 PointType p20 = p2 - p0;
684
685 ScalarType delta0_p01 = p10.dot(p1);
686 ScalarType delta1_p01 = -p10.dot(p0);
687 ScalarType delta0_p02 = p20.dot(p2);
688 ScalarType delta2_p02 = -p20.dot(p0);
689 ScalarType delta1_p12 = p21.dot(p2);
690 ScalarType delta2_p12 = -p21.dot(p1);
691
692 // the closest point can be one of the vertices of the triangle
693 if (delta1_p01 <= ScalarType(0.0) && delta2_p02 <= ScalarType(0.0))
694 witness = p0;
695 else if (delta0_p01 <= ScalarType(0.0) && delta2_p12 <= ScalarType(0.0))
696 witness = p1;
697 else if (delta0_p02 <= ScalarType(0.0) && delta1_p12 <= ScalarType(0.0))
698 witness = p2;
699 else {
700 ScalarType temp = p10.dot(p2);
703 ScalarType delta2_p012 =
704 delta2_p02 * delta0_p01 - delta1_p01 * (p20.dot(p1));
705
706 // otherwise, can be a point lying on same edge of the triangle
707 if (delta0_p012 <= ScalarType(0.0)) {
708 ScalarType denominator = delta1_p12 + delta2_p12;
709 ScalarType mu1 = delta1_p12 / denominator;
710 ScalarType mu2 = delta2_p12 / denominator;
711
712 witness = (p1 * mu1 + p2 * mu2);
713 }
714 else if (delta1_p012 <= ScalarType(0.0)) {
715 ScalarType denominator = delta0_p02 + delta2_p02;
716 ScalarType mu0 = delta0_p02 / denominator;
717 ScalarType mu2 = delta2_p02 / denominator;
718
719 witness = (p0 * mu0 + p2 * mu2);
720 }
721 else if (delta2_p012 <= ScalarType(0.0)) {
722 ScalarType denominator = delta0_p01 + delta1_p01;
723 ScalarType mu0 = delta0_p01 / denominator;
724 ScalarType mu1 = delta1_p01 / denominator;
725
726 witness = (p0 * mu0 + p1 * mu1);
727 }
728 else {
729 // or else can be an point detail to the triangle
731 ScalarType lambda0 = delta0_p012 / denominator;
732 ScalarType lambda1 = delta1_p012 / denominator;
733 ScalarType lambda2 = delta2_p012 / denominator;
734
735 witness = p0 * lambda0 + p1 * lambda1 + p2 * lambda2;
736 }
737 }
738
739 ScalarType witness_norm = witness.norm();
740
741 res.first = std::max<ScalarType>(witness_norm - radius, ScalarType(0.0));
742 res.second = std::max<ScalarType>(radius - witness_norm, ScalarType(0.0));
743
744 penetrationDetected = (witness.squaredNorm() <= (radius * radius));
745 witness += center;
746 return penetrationDetected;
747}
748
758template<Triangle3Concept TriangleType, SphereConcept SphereType>
760{
761 using SScalar = SphereType::ScalarType;
762 typename TriangleType::PointType witness;
763 std::pair<SScalar, SScalar> res;
765}
766
772template<SphereConcept SphereType, Triangle3Concept TriangleType>
774{
775 return intersect(t, sphere);
776}
777
778} // namespace vcl
779
780#endif // VCL_ALGORITHMS_CORE_INTERSECTION_INTERSECT_H
A class representing a box in N-dimensional space.
Definition box.h:46
bool intersects(const Box< PointT > &b) const
Same as Box::overlap.
Definition box.h:239
PointT & max()
Returns a reference to the maximum point of the box.
Definition box.h:104
PointT center() const
Calculates the center point of the box.
Definition box.h:259
PointT size() const
Computes the size of the box.
Definition box.h:267
constexpr auto max(const T &p1, const T &p2)
Returns the maximum between the two parameters.
Definition min_max.h:81
constexpr auto min(const T &p1, const T &p2)
Returns the minimum between the two parameters.
Definition min_max.h:40
bool intersect(const PlaneType &plane, const BoxType &box)
Checks if a plane intersects with a box.
Definition intersect.h:258
std::optional< typename SegmentType::PointType > intersection(const PlaneType &plane, const SegmentType &segment)
Returns the intersection point between a plane and a segment, if it exists.
Definition intersect.h:364