Visual Computing Library
Loading...
Searching...
No Matches
misc.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_MISC_H
24#define VCL_ALGORITHMS_CORE_INTERSECTION_MISC_H
25
26#include <vclib/space/core/box.h>
27#include <vclib/space/core/plane.h>
28#include <vclib/space/core/segment.h>
29#include <vclib/space/core/sphere.h>
30#include <vclib/space/core/triangle.h>
31
32#include <optional>
33
34namespace vcl {
35
36namespace detail {
37
38/*
39 * ===== plane segment intersection functions =====
40 */
41
42template<PlaneConcept PlaneType, Segment3Concept SegmentType>
43void projectSegmentEndPoints(
44 const PlaneType& p,
45 const SegmentType& s,
46 typename SegmentType::ScalarType& p0Proj,
47 typename SegmentType::ScalarType& p1Proj)
48{
49 using ScalarType = SegmentType::ScalarType;
50
51 // Compute the projection of the segment endpoints onto the plane.
52 p0Proj = s.p1() * p.direction() - p.offset();
53 p1Proj = s.p0() * p.direction() - p.offset();
54}
55
56/*
57 * ===== triangle box intersection functions =====
58 */
59
60template<typename ScalarType>
61inline void findMinMax(
62 ScalarType x0,
63 ScalarType x1,
64 ScalarType x2,
65 ScalarType& min,
66 ScalarType& max)
67{
68 min = max = x0;
69 if (x1 < min)
70 min = x1;
71 if (x1 > max)
72 max = x1;
73 if (x2 < min)
74 min = x2;
75 if (x2 > max)
76 max = x2;
77}
78
79/*======================== X-tests ========================*/
80template<typename ScalarType, PointConcept PointType>
81inline bool axisTestX01(
82 ScalarType a,
83 ScalarType b,
84 ScalarType fa,
85 ScalarType fb,
86 const PointType& v0,
87 const PointType& v2,
88 const PointType& bHalfSize)
89{
90 ScalarType p0 = a * v0.y() - b * v0.z();
91 ScalarType p2 = a * v2.y() - b * v2.z();
92 ScalarType min, max;
93 if (p0 < p2) {
94 min = p0;
95 max = p2;
96 }
97 else {
98 min = p2;
99 max = p0;
100 }
101 ScalarType rad = fa * bHalfSize.y() + fb * bHalfSize.z();
102 if (min > rad || max < -rad)
103 return false;
104 return true;
105}
106
107template<typename ScalarType, PointConcept PointType>
108inline bool axisTestX2(
109 ScalarType a,
110 ScalarType b,
111 ScalarType fa,
112 ScalarType fb,
113 const PointType& v0,
114 const PointType& v1,
115 const PointType& bHalfSize)
116{
117 ScalarType p0 = a * v0.y() - b * v0.z();
118 ScalarType p1 = a * v1.y() - b * v1.z();
119 ScalarType min, max;
120 if (p0 < p1) {
121 min = p0;
122 max = p1;
123 }
124 else {
125 min = p1;
126 max = p0;
127 }
128 ScalarType rad = fa * bHalfSize.y() + fb * bHalfSize.z();
129 if (min > rad || max < -rad)
130 return false;
131 return true;
132}
133
134/*======================== Y-tests ========================*/
135template<typename ScalarType, PointConcept PointType>
136inline bool axisTestY02(
137 ScalarType a,
138 ScalarType b,
139 ScalarType fa,
140 ScalarType fb,
141 const PointType& v0,
142 const PointType& v2,
143 const PointType& bHalfSize)
144{
145 ScalarType p0 = -a * v0.x() + b * v0.z();
146 ScalarType p2 = -a * v2.x() + b * v2.z();
147 ScalarType min, max;
148 if (p0 < p2) {
149 min = p0;
150 max = p2;
151 }
152 else {
153 min = p2;
154 max = p0;
155 }
156 ScalarType rad = fa * bHalfSize.x() + fb * bHalfSize.z();
157 if (min > rad || max < -rad)
158 return false;
159 return true;
160}
161
162template<typename ScalarType, PointConcept PointType>
163inline bool axisTestY1(
164 ScalarType a,
165 ScalarType b,
166 ScalarType fa,
167 ScalarType fb,
168 const PointType& v0,
169 const PointType& v1,
170 const PointType& bHalfSize)
171{
172 ScalarType p0 = -a * v0.x() + b * v0.z();
173 ScalarType p1 = -a * v1.x() + b * v1.z();
174 ScalarType min, max;
175 if (p0 < p1) {
176 min = p0;
177 max = p1;
178 }
179 else {
180 min = p1;
181 max = p0;
182 }
183 ScalarType rad = fa * bHalfSize.x() + fb * bHalfSize.z();
184 if (min > rad || max < -rad)
185 return false;
186 return true;
187}
188
189/*======================== Z-tests ========================*/
190template<typename ScalarType, PointConcept PointType>
191inline bool axisTestZ12(
192 ScalarType a,
193 ScalarType b,
194 ScalarType fa,
195 ScalarType fb,
196 const PointType& v1,
197 const PointType& v2,
198 const PointType& bHalfSize)
199{
200 ScalarType p1 = a * v1.x() - b * v1.y();
201 ScalarType p2 = a * v2.x() - b * v2.y();
202 ScalarType min, max;
203 if (p1 < p2) {
204 min = p1;
205 max = p2;
206 }
207 else {
208 min = p2;
209 max = p1;
210 }
211 ScalarType rad = fa * bHalfSize.x() + fb * bHalfSize.y();
212 if (min > rad || max < -rad)
213 return false;
214 return true;
215}
216
217template<typename ScalarType, PointConcept PointType>
218inline bool axisTestZ0(
219 ScalarType a,
220 ScalarType b,
221 ScalarType fa,
222 ScalarType fb,
223 const PointType& v0,
224 const PointType& v1,
225 const PointType& bHalfSize)
226{
227 ScalarType p0 = a * v0.x() - b * v0.y();
228 ScalarType p1 = a * v1.x() - b * v1.y();
229 ScalarType min, max;
230 if (p0 < p1) {
231 min = p0;
232 max = p1;
233 }
234 else {
235 min = p1;
236 max = p0;
237 }
238 ScalarType rad = fa * bHalfSize.x() + fb * bHalfSize.y();
239 if (min > rad || max < -rad)
240 return false;
241 return true;
242}
243
244} // namespace detail
245
261template<PlaneConcept PlaneType, Box3Concept BoxType>
262bool intersect(const PlaneType& plane, const BoxType& box)
263{
264 using PointType = BoxType::PointType;
265 using ScalarType = PointType::ScalarType;
266
267 // Convert AABB to center-extents representation
268 PointType c = box.center(); // Compute AABB center
269 PointType e = box.max() - c; // Compute positive extents
270
271 PointType n = plane.direction();
272 // Compute the projection interval radius of b onto L(t) = b.c + t * p.n
273 ScalarType r =
274 e[0] * std::abs(n[0]) + e[1] * std::abs(n[1]) + e[2] * std::abs(n[2]);
275
276 // Compute distance of box center from plane
277 ScalarType s = n.dot(c) - plane.offset();
278
279 // Intersection occurs when distance s falls within [-r,+r] interval
280 return std::abs(s) <= r;
281}
282
288template<Box3Concept BoxType, PlaneConcept PlaneType>
289bool intersect(const BoxType& box, const PlaneType& p)
290{
291 return intersect(p, box);
292}
293
311template<PlaneConcept PlaneType, Segment3Concept SegmentType>
313{
314 using ScalarType = SegmentType::ScalarType;
315
316 ScalarType p0Proj;
317 ScalarType p1Proj;
318
319 detail::projectSegmentEndPoints(plane, segment, p1Proj, p0Proj);
320
321 // If both endpoints are on the same side of the plane, there is no
322 // intersection.
323 if ((p0Proj > 0) - (p1Proj < 0))
324 return false;
325
326 // If both endpoints have the same projection onto the plane, there is no
327 // intersection.
328 if (p1Proj == p0Proj)
329 return false;
330
331 return true;
332}
333
339template<Segment3Concept SegmentType, PlaneConcept PlaneType>
341{
342 return intersect(plane, segment);
343}
344
367template<PlaneConcept PlaneType, Segment3Concept SegmentType>
368std::optional<typename SegmentType::PointType> intersection(
369 const PlaneType& plane,
370 const SegmentType& segment)
371{
372 std::optional<typename SegmentType::PointType> intersection;
373
374 using ScalarType = SegmentType::ScalarType;
375
376 ScalarType p0Proj;
377 ScalarType p1Proj;
378
379 detail::projectSegmentEndPoints(plane, segment, p1Proj, p0Proj);
380
381 // If either endpoint is on the opposite side of the plane, there is an
382 // intersection.
383 if ((p0Proj > 0) != (p1Proj > 0) || p1Proj != p0Proj) {
384 // check that we perform the computation in a way that is independent
385 // with v0 v1 swaps
386 if (p1Proj < p0Proj)
388 segment.p0() + (segment.p1() - segment.p0()) *
389 std::abs(p1Proj / (p0Proj - p1Proj));
390 if (p1Proj > p0Proj)
392 segment.p1() + (segment.p0() - segment.p1()) *
393 std::abs(p0Proj / (p1Proj - p0Proj));
394 }
395
396 return intersection;
397}
398
411template<SphereConcept SphereType, Box3Concept BoxType>
412bool intersect(const SphereType& sphere, const BoxType& box)
413{
414 return sphere.intersects(box);
415}
416
422template<Box3Concept BoxType, SphereConcept SphereType>
423bool intersect(const BoxType& box, const SphereType& sphere)
424{
425 return sphere.intersects(box);
426}
427
448template<Triangle2Concept TriangleType, Point2Concept PointType>
449bool intersect(const TriangleType& triangle, const PointType& point)
450{
451 using TP = TriangleType::PointType;
452 using ScalarType = TP::ScalarType;
453
454 const TP& p0 = triangle.point(0);
455 const TP& p1 = triangle.point(1);
456 const TP& p2 = triangle.point(2);
457
458 ScalarType A = triangle.area();
459 ScalarType sign = A < 0 ? -1 : 1;
460
461 ScalarType s =
462 (p0.y() * p2.x() - p0.x() * p2.y() + (p2.y() - p0.y()) * point.x() +
463 (p0.x() - p2.x()) * point.y()) *
464 sign;
465 ScalarType tt =
466 (p0.x() * p1.y() - p0.y() * p1.x() + (p0.y() - p1.y()) * point.x() +
467 (p1.x() - p0.x()) * point.y()) *
468 sign;
469
470 return s > 0 && tt > 0 && (s + tt) < 2 * A * sign;
471}
472
478template<Point2Concept PointType, Triangle2Concept TriangleType>
479bool intersect(const PointType& point, const TriangleType& triangle)
480{
481 return intersect(triangle, point);
482}
483
498template<Triangle3Concept TriangleType, Point3Concept PointType>
499bool intersect(const TriangleType& triangle, const PointType& point)
500{
501 PointType v1 = triangle.point(1) - triangle.point(0);
502 PointType v2 = triangle.point(2) - triangle.point(0);
503 PointType v3 = point - triangle.point(0);
504
505 return v1.dot(v2.cross(v3)) > 0;
506}
507
513template<Point3Concept PointType, Triangle3Concept TriangleType>
514bool intersect(const PointType& point, const TriangleType& triangle)
515{
516 return intersect(triangle, point);
517}
518
541template<Triangle3Concept TriangleType, Box3Concept BoxType>
542bool intersect(const TriangleType& triangle, const BoxType& box)
543{
544 using PointType = TriangleType::PointType;
545 using ScalarType = PointType::ScalarType;
546
547 PointType boxCenter = box.center();
548 PointType bHalfSize = box.size() / 2;
549
550 /* use separating axis theorem to test overlap between triangle and box
551 * need to test for overlap in these directions:
552 * 1) the {x,y,z}-directions (actually, since we use the AABB of the
553 * triangle we do not even need to test these)
554 * 2) normal of the triangle
555 * 3) cross product(edge from tri, {x,y,z}-direction)
556 * this gives 3x3=9 more tests
557 */
558 ScalarType min, max;
559 PointType normal;
560
561 /* This is the fastest branch on Sun */
562 /* move everything so that the boxCenter is in (0,0,0) */
563 PointType v0 = triangle.point(0) - boxCenter;
564 PointType v1 = triangle.point(1) - boxCenter;
565 PointType v2 = triangle.point(2) - boxCenter;
566
567 /* compute triangle edges */
568 PointType e0 = v1 - v0;
569 PointType e1 = v2 - v1;
570 PointType e2 = v0 - v2;
571
572 /* Bullet 3: */
573 /* test the 9 tests first (this was faster) */
574 ScalarType fex = std::abs(e0.x());
575 ScalarType fey = std::abs(e0.y());
576 ScalarType fez = std::abs(e0.z());
577
578 if (!detail::axisTestX01(e0.z(), e0.y(), fez, fey, v0, v2, bHalfSize))
579 return false;
580 if (!detail::axisTestY02(e0.z(), e0.x(), fez, fex, v0, v2, bHalfSize))
581 return false;
582 if (!detail::axisTestZ12(e0.y(), e0.x(), fey, fex, v1, v2, bHalfSize))
583 return false;
584
585 fex = std::abs(e1.x());
586 fey = std::abs(e1.y());
587 fez = std::abs(e1.z());
588
589 if (!detail::axisTestX01(e1.z(), e1.y(), fez, fey, v0, v2, bHalfSize))
590 return false;
591 if (!detail::axisTestY02(e1.z(), e1.x(), fez, fex, v0, v2, bHalfSize))
592 return false;
593 if (!detail::axisTestZ0(e1.y(), e1.x(), fey, fex, v0, v1, bHalfSize))
594 return false;
595
596 fex = std::abs(e2.x());
597 fey = std::abs(e2.y());
598 fez = std::abs(e2.z());
599 if (!detail::axisTestX2(e2.z(), e2.y(), fez, fey, v0, v1, bHalfSize))
600 return false;
601 if (!detail::axisTestY1(e2.z(), e2.x(), fez, fex, v0, v1, bHalfSize))
602 return false;
603 if (!detail::axisTestZ12(e2.y(), e2.x(), fey, fex, v1, v2, bHalfSize))
604 return false;
605
606 /* Bullet 1:
607 * first test overlap in the {x,y,z}-directions
608 * find min, max of the triangle each direction, and test for overlap in
609 * that direction -- this is equivalent to testing a minimal AABB around
610 * the triangle against the AABB
611 */
612
613 /* test in X-direction */
614 detail::findMinMax(v0.x(), v1.x(), v2.x(), min, max);
615 if (min > bHalfSize.x() || max < -bHalfSize.x())
616 return false;
617
618 /* test in Y-direction */
619 detail::findMinMax(v0.y(), v1.y(), v2.y(), min, max);
620 if (min > bHalfSize.y() || max < -bHalfSize.y())
621 return false;
622
623 /* test in Z-direction */
624 detail::findMinMax(v0.z(), v1.z(), v2.z(), min, max);
625 if (min > bHalfSize.z() || max < -bHalfSize.z())
626 return false;
627
628 /* Bullet 2:
629 * test if the box intersects the plane of the triangle
630 * compute plane equation of triangle: normal*x+d=0
631 */
632 normal = e0.cross(e1);
634 triangle.point(0), triangle.point(1), triangle.point(2));
635 if (!intersect(plane, box))
636 return false;
637
638 return true; /* box and triangle overlaps */
639}
640
646template<Box3Concept BoxType, Triangle3Concept TriangleType>
647bool intersect(const BoxType& box, const TriangleType& triangle)
648{
649 return intersect(triangle, box);
650}
651
666template<
667 Triangle3Concept TriangleType,
668 SphereConcept SphereType,
669 Point3Concept PointType,
670 typename ScalarType>
672 const TriangleType& triangle,
673 const SphereType& sphere,
674 PointType& witness,
675 std::pair<ScalarType, ScalarType>& res)
676{
677 bool penetrationDetected = false;
678
679 ScalarType radius = sphere.radius();
680 PointType center = sphere.center();
681 PointType p0 = triangle.point0() - center;
682 PointType p1 = triangle.point1() - center;
683 PointType p2 = triangle.point2() - center;
684
685 PointType p10 = p1 - p0;
686 PointType p21 = p2 - p1;
687 PointType p20 = p2 - p0;
688
689 ScalarType delta0_p01 = p10.dot(p1);
690 ScalarType delta1_p01 = -p10.dot(p0);
691 ScalarType delta0_p02 = p20.dot(p2);
692 ScalarType delta2_p02 = -p20.dot(p0);
693 ScalarType delta1_p12 = p21.dot(p2);
694 ScalarType delta2_p12 = -p21.dot(p1);
695
696 // the closest point can be one of the vertices of the triangle
697 if (delta1_p01 <= ScalarType(0.0) && delta2_p02 <= ScalarType(0.0))
698 witness = p0;
699 else if (delta0_p01 <= ScalarType(0.0) && delta2_p12 <= ScalarType(0.0))
700 witness = p1;
701 else if (delta0_p02 <= ScalarType(0.0) && delta1_p12 <= ScalarType(0.0))
702 witness = p2;
703 else {
704 ScalarType temp = p10.dot(p2);
707 ScalarType delta2_p012 =
708 delta2_p02 * delta0_p01 - delta1_p01 * (p20.dot(p1));
709
710 // otherwise, can be a point lying on same edge of the triangle
711 if (delta0_p012 <= ScalarType(0.0)) {
712 ScalarType denominator = delta1_p12 + delta2_p12;
713 ScalarType mu1 = delta1_p12 / denominator;
714 ScalarType mu2 = delta2_p12 / denominator;
715
716 witness = (p1 * mu1 + p2 * mu2);
717 }
718 else if (delta1_p012 <= ScalarType(0.0)) {
719 ScalarType denominator = delta0_p02 + delta2_p02;
720 ScalarType mu0 = delta0_p02 / denominator;
721 ScalarType mu2 = delta2_p02 / denominator;
722
723 witness = (p0 * mu0 + p2 * mu2);
724 }
725 else if (delta2_p012 <= ScalarType(0.0)) {
726 ScalarType denominator = delta0_p01 + delta1_p01;
727 ScalarType mu0 = delta0_p01 / denominator;
728 ScalarType mu1 = delta1_p01 / denominator;
729
730 witness = (p0 * mu0 + p1 * mu1);
731 }
732 else {
733 // or else can be an point detail to the triangle
735 ScalarType lambda0 = delta0_p012 / denominator;
736 ScalarType lambda1 = delta1_p012 / denominator;
737 ScalarType lambda2 = delta2_p012 / denominator;
738
739 witness = p0 * lambda0 + p1 * lambda1 + p2 * lambda2;
740 }
741 }
742
743 ScalarType witness_norm = witness.norm();
744
745 res.first = std::max<ScalarType>(witness_norm - radius, ScalarType(0.0));
746 res.second = std::max<ScalarType>(radius - witness_norm, ScalarType(0.0));
747
748 penetrationDetected = (witness.squaredNorm() <= (radius * radius));
749 witness += center;
750 return penetrationDetected;
751}
752
762template<Triangle3Concept TriangleType, SphereConcept SphereType>
764{
765 using SScalar = SphereType::ScalarType;
766 typename TriangleType::PointType witness;
767 std::pair<SScalar, SScalar> res;
769}
770
776template<SphereConcept SphereType, Triangle3Concept TriangleType>
778{
779 return intersect(t, sphere);
780}
781
782} // namespace vcl
783
784#endif // VCL_ALGORITHMS_CORE_INTERSECTION_MISC_H
A class representing a line segment in n-dimensional space. The class is parameterized by a PointConc...
Definition segment.h:43
PointT & p0()
Returns the first endpoint of the segment.
Definition segment.h:83
PointT & p1()
Returns the second endpoint of the segment.
Definition segment.h:97
bool intersect(const FaceType &f, const Box< PointType > &box)
Checks if a face intersects a box.
Definition element.h:57
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 misc.h:368
constexpr auto max(const T &p1, const T &p2)
Returns the maximum between the two parameters.
Definition min_max.h:83
constexpr auto min(const T &p1, const T &p2)
Returns the minimum between the two parameters.
Definition min_max.h:42