1 // =============================================================================
2 // PROJECT CHRONO - http://projectchrono.org
3 //
4 // Copyright (c) 2021 projectchrono.org
5 // All rights reserved.
6 //
7 // Use of this source code is governed by a BSD-style license that can be found
8 // in the LICENSE file at the top level of the distribution and at
9 // http://projectchrono.org/license-chrono.txt.
10 //
11 // =============================================================================
12 // Authors: Radu Serban
13 // =============================================================================
14 
15 #include "chrono/collision/ChCollisionUtilsBullet.h"
16 #include "chrono/collision/bullet/LinearMath/btConvexHull.h"
17 
18 namespace chrono {
19 namespace collision {
20 namespace bt_utils {
21 
22 // -----------------------------------------------------------------------------
23 
24 // Project point onto line.
ProjectPointOnLine(const btVector3 & lP,const btVector3 & lD,const btVector3 & P)25 btVector3 ProjectPointOnLine(const btVector3& lP,  // point on line
26                              const btVector3& lD,  // line direction (unit vector)
27                              const btVector3& P    // point
28 ) {
29     return lP + (P - lP).dot(lD) * lD;
30 }
31 
DistancePointToLine(const btVector3 & lP,const btVector3 & lD,const btVector3 & P)32 btScalar DistancePointToLine(const btVector3& lP,  // point on line
33                              const btVector3& lD,  // line direction (unit vector)
34                              const btVector3& P    // point
35 ) {
36     btVector3 Q = ProjectPointOnLine(lP, lD, P);
37     return (Q - P).length();
38 }
39 
40 // -----------------------------------------------------------------------------
41 
42 // Snap the specified location to a point on a box with given half-dimensions.
43 // The in/out location is assumed to be specified in the frame of the box (which is therefore assumed to be an AABB
44 // centered at the origin).  The return code indicates the box axes that caused snapping:
45 //   - first bit (least significant) corresponds to x-axis
46 //   - second bit corresponds to y-axis
47 //   - third bit corresponds to z-axis
48 //
49 // Therefore:
50 //   code = 0 indicates an interior point
51 //   code = 1 or code = 2 or code = 4  indicates snapping to a face
52 //   code = 3 or code = 5 or code = 6  indicates snapping to an edge
53 //   code = 7 indicates snapping to a corner
SnapPointToBox(const btVector3 & hdims,btVector3 & loc)54 int SnapPointToBox(const btVector3& hdims,  // box half-dimensions
55                    btVector3& loc           // point (in/out)
56 ) {
57     int code = 0;
58 
59     if (std::abs(loc[0]) > hdims[0]) {
60         code |= 1;
61         loc[0] = (loc[0] > 0) ? hdims[0] : -hdims[0];
62     }
63     if (std::abs(loc[1]) > hdims[1]) {
64         code |= 2;
65         loc[1] = (loc[1] > 0) ? hdims[1] : -hdims[1];
66     }
67     if (std::abs(loc[2]) > hdims[2]) {
68         code |= 4;
69         loc[2] = (loc[2] > 0) ? hdims[2] : -hdims[2];
70     }
71 
72     return code;
73 }
74 
75 // -----------------------------------------------------------------------------
76 
77 // Check if given point is inside box (point expressed in box frame).
PointInsideBox(const btVector3 & hdims,const btVector3 & loc)78 bool PointInsideBox(const btVector3& hdims,  // box half-dimensions
79                     const btVector3& loc     // point
80 ) {
81     for (int i = 0; i < 3; i++) {
82         if (loc[i] > hdims[i] || loc[i] < -hdims[i])
83             return false;
84     }
85     return true;
86 }
87 
88 // -----------------------------------------------------------------------------
89 
90 // Find the closest box face to the given point (expressed in box frame).
91 // Returns +1, +2, +3 (for a "positive" face in x, y, z, respectively) or -1, -2, -3 (for a "negative" face).
FindClosestBoxFace(const btVector3 & hdims,const btVector3 & loc)92 int FindClosestBoxFace(const btVector3& hdims,  // box half-dimensions
93                        const btVector3& loc     // point
94 ) {
95     int code = 0;
96     btScalar dist = +BT_LARGE_FLOAT;
97     for (int i = 0; i < 3; i++) {
98         btScalar p_dist = std::abs(loc[i] - hdims[i]);
99         btScalar n_dist = std::abs(loc[i] + hdims[i]);
100         if (p_dist < dist) {
101             code = i + 1;
102             dist = p_dist;
103         }
104         if (n_dist < dist) {
105             code = -(i + 1);
106             dist = n_dist;
107         }
108     }
109 
110     return code;
111 }
112 
113 // -----------------------------------------------------------------------------
114 
115 // Utility function for intersecting a box with a line segment.
116 // It is assumed that the box is centered at the origin and the segment is expressed in the box frame.
117 // The function returns false if the segment does not intersect the box.
118 // Algorithm:
119 //  - check intersection of the supporting line with the box slabs in the 3 directions
120 //  - if segment parallel to a slab, no intersection if segment far from box
121 //  - otherwise, update line parameters with points where line intersects box faces
122 //    (keep track of the most "inner" points at all times)
123 //  - finally, clamp to segment length
IntersectSegmentBox(const btVector3 & hdims,const btVector3 & c,const btVector3 & a,const btScalar hlen,const btScalar tol,btScalar & tMin,btScalar & tMax)124 bool IntersectSegmentBox(const btVector3& hdims,  // box half-dimensions
125                          const btVector3& c,      // segment center point
126                          const btVector3& a,      // segment direction (unit vector)
127                          const btScalar hlen,     // segment half-length
128                          const btScalar tol,      // tolerance for parallelism test
129                          btScalar& tMin,          // segment parameter of first intersection point
130                          btScalar& tMax           // segment parameter of second intersection point
131 ) {
132     tMin = -BT_LARGE_FLOAT;
133     tMax = +BT_LARGE_FLOAT;
134 
135     if (std::abs(a.x()) < tol) {
136         // Segment parallel to the box x-faces
137         if (std::abs(c.x()) > hdims.x())
138             return false;
139     } else {
140         btScalar t1 = (-hdims.x() - c.x()) / a.x();
141         btScalar t2 = (+hdims.x() - c.x()) / a.x();
142 
143         tMin = btMax(tMin, btMin(t1, t2));
144         tMax = btMin(tMax, btMax(t1, t2));
145 
146         if (tMin > tMax)
147             return false;
148     }
149 
150     if (std::abs(a.y()) < tol) {
151         // Segment parallel to the box y-faces
152         if (std::abs(c.y()) > hdims.y())
153             return false;
154     } else {
155         btScalar t1 = (-hdims.y() - c.y()) / a.y();
156         btScalar t2 = (+hdims.y() - c.y()) / a.y();
157 
158         tMin = btMax(tMin, btMin(t1, t2));
159         tMax = btMin(tMax, btMax(t1, t2));
160 
161         if (tMin > tMax)
162             return false;
163     }
164 
165     if (std::abs(a.z()) < tol) {
166         // Capsule axis parallel to the box z-faces
167         if (std::abs(c.z()) > hdims.z())
168             return false;
169     } else {
170         btScalar t1 = (-hdims.z() - c.z()) / a.z();
171         btScalar t2 = (+hdims.z() - c.z()) / a.z();
172 
173         tMin = btMax(tMin, btMin(t1, t2));
174         tMax = btMin(tMax, btMax(t1, t2));
175 
176         if (tMin > tMax)
177             return false;
178     }
179 
180     // If both intersection points are outside the segment, no intersection
181     if ((tMin < -hlen && tMax < -hlen) || (tMin > +hlen && tMax > +hlen))
182         return false;
183 
184     // Clamp intersection points to segment length
185     ChClampValue(tMin, -hlen, +hlen);
186     ChClampValue(tMax, -hlen, +hlen);
187 
188     return true;
189 }
190 
191 // -----------------------------------------------------------------------------
192 
193 // Utility function to intersect a line with a plane
194 // Plane equation: pN.X = pN.pP
195 // Line equation:  X = lP + t * lD
196 // Solution:       t = pN.(pP-lP) / pN.lD
IntersectLinePlane(const btVector3 & lP,const btVector3 & lD,const btVector3 & pP,const btVector3 & pN,const btScalar tol,btScalar & t)197 bool IntersectLinePlane(const btVector3& lP,  // point on line
198                         const btVector3& lD,  // line direction (unit vector)
199                         const btVector3& pP,  // point on plane
200                         const btVector3& pN,  // plane normal (unit vector)
201                         const btScalar tol,   // tolerance for orthogonality test
202                         btScalar& t           // line parameter of intersection point
203 ) {
204     btScalar nd = pN.dot(lD);
205 
206     if (std::abs(nd) < tol) {
207         // Line parallel to plane
208         return false;
209     }
210 
211     t = pN.dot(pP - lP) / nd;
212     return true;
213 }
214 
215 // -----------------------------------------------------------------------------
216 
217 // Utility function to intersect a segment with a cylinder.
218 // Segment assumed to be parameterized as sP = sC + t * sD, with -sH <= t <= sH.
219 // Cylinder given by its center cC, axis direction cD, halh-lengh cH, and radius cR.
220 // Assume |sD| = |cD| = 1.
221 // (1) Find tMin and tMax where the segment supporting line intersects cylindrical surface by finding
222 //     points on segment at a distance cR from the cylinder axis line.
223 // (2) Clamp result to cylinder end-caps.
224 // (3) Clamp result to segment length.
225 //
226 // Distance between a point P and cylinder axis:
227 //    d = |(sP-cC) x cD| / |cD| = |(sP-cC) x cD|
228 // using |cD|=1.
229 // Solve:
230 //    cR^2 = |(sP-cC) x cD|^2 = |sP-cC|^2 * |cD|^2 - [cD.(sP-cC)]^2 = |sP-cC|^2 - [cD.(sP-cC)]^2
231 //    cR^2 = |sC-cC+t*sD|^2 - [cD.(sC-cC+t*sD)]^2
232 //       0 = t^2 [ 1 - (cD.sD)^2 ] + 2t [ (v.sD) - (cD.v)(cD.sD) ] + v.v - (cD.v)^2 - cR^2
233 //       0 = a t^2 + 2bt + c
234 // where v = sC-cC and using |sD|=1.
IntersectSegmentCylinder(const btVector3 & sC,const btVector3 & sD,const btScalar sH,const btVector3 & cC,const btVector3 & cD,const btScalar cH,const btScalar cR,const btScalar tol,btScalar & tMin,btScalar & tMax)235 bool IntersectSegmentCylinder(const btVector3& sC,  // segment center point
236                               const btVector3& sD,  // segment direction (unit vector)
237                               const btScalar sH,    // segment half-length
238                               const btVector3& cC,  // cylinder axis center
239                               const btVector3& cD,  // cylinder axis direction (unit vector)
240                               const btScalar cH,    // cylinder axis half-length (cylinder halh-height)
241                               const btScalar cR,    // cylinder radius
242                               const btScalar tol,   // tolerance for parallelism test
243                               btScalar& tMin,       // segment parameter of first intersection point
244                               btScalar& tMax        // segment parameter of second intersection point
245 ) {
246     tMin = -BT_LARGE_FLOAT;
247     tMax = +BT_LARGE_FLOAT;
248 
249     btVector3 v = sC - cC;
250     btScalar cDsD = cD.dot(sD);
251     btScalar vcD = v.dot(cD);
252     btScalar vsD = v.dot(sD);
253     btScalar vv = v.dot(v);
254     btScalar a = 1 - cDsD * cDsD;
255     btScalar b = vsD - vcD * cDsD;
256     btScalar c = vv - vcD * vcD - cR * cR;
257 
258     // Intersection with cylindrical surface.
259     // a >= 0 always
260     // a == 0 indicates line parallel to cylinder axis
261     if (std::abs(a) < tol) {
262         // line parallel to cylinder axis
263         btScalar dist2 = (v - vcD * cD).length2();
264         if (dist2 > cR * cR)
265             return false;
266         tMin = -sH;
267         tMax = +sH;
268     } else {
269         // line intersects cylindrical surface
270         btScalar discr = b * b - a * c;
271         if (discr < 0)
272             return false;  // no real roots, no intersection
273         discr = btSqrt(discr);
274         tMin = (-b - discr) / a;
275         tMax = (-b + discr) / a;
276     }
277 
278     // Intersection with end-caps.
279     btScalar t1;
280     bool code1 = IntersectLinePlane(sC, sD, cC + cH * cD, cD, tol, t1);
281     btScalar t2;
282     bool code2 = IntersectLinePlane(sC, sD, cC - cH * cD, cD, tol, t2);
283     assert(code1 == code2);
284     if (code1 && code2) {
285         // line intersects end-caps
286         if (t1 < t2) {
287             tMin = btMax(tMin, t1);
288             tMax = btMin(tMax, t2);
289         } else {
290             tMin = btMax(tMin, t2);
291             tMax = btMin(tMax, t1);
292         }
293         if (tMax < tMin)
294             return false;
295     } else {
296         // line parallel to end-cap planes
297         btScalar d1 = std::abs(cD.dot(cC + cH * cD - sC));
298         btScalar d2 = std::abs(cD.dot(cC - cH * cD - sC));
299         if (d1 > 2 * cH || d2 > 2 * cH)
300             return false;
301     }
302 
303     // If both intersection points are outside the segment, no intersection
304     if ((tMin < -sH && tMax < -sH) || (tMin > +sH && tMax > +sH))
305         return false;
306 
307     // Clamp to segment length
308     btClamp(tMin, -sH, +sH);
309     btClamp(tMax, -sH, +sH);
310 
311     assert(tMin <= tMax);
312 
313     return true;
314 }
315 
316 // -----------------------------------------------------------------------------
317 
ChConvexHullLibraryWrapper()318 ChConvexHullLibraryWrapper::ChConvexHullLibraryWrapper() {}
319 
ComputeHull(const std::vector<ChVector<>> & points,geometry::ChTriangleMeshConnected & vshape)320 void ChConvexHullLibraryWrapper::ComputeHull(const std::vector<ChVector<> >& points,
321                                              geometry::ChTriangleMeshConnected& vshape) {
322     HullLibrary hl;
323     HullResult hresult;
324     HullDesc desc;
325 
326     desc.SetHullFlag(QF_TRIANGLES);
327 
328     btVector3* btpoints = new btVector3[points.size()];
329     for (unsigned int ip = 0; ip < points.size(); ++ip) {
330         btpoints[ip].setX((btScalar)points[ip].x());
331         btpoints[ip].setY((btScalar)points[ip].y());
332         btpoints[ip].setZ((btScalar)points[ip].z());
333     }
334     desc.mVcount = (unsigned int)points.size();
335     desc.mVertices = btpoints;
336     desc.mVertexStride = sizeof(btVector3);
337 
338     HullError hret = hl.CreateConvexHull(desc, hresult);
339 
340     if (hret == QE_OK) {
341         vshape.Clear();
342 
343         vshape.getIndicesVertexes().resize(hresult.mNumFaces);
344         for (unsigned int it = 0; it < hresult.mNumFaces; ++it) {
345             vshape.getIndicesVertexes()[it] = ChVector<int>(
346                 hresult.m_Indices[it * 3 + 0], hresult.m_Indices[it * 3 + 1], hresult.m_Indices[it * 3 + 2]);
347         }
348         vshape.getCoordsVertices().resize(hresult.mNumOutputVertices);
349         for (unsigned int iv = 0; iv < hresult.mNumOutputVertices; ++iv) {
350             vshape.getCoordsVertices()[iv] = ChVector<>(
351                 hresult.m_OutputVertices[iv].x(), hresult.m_OutputVertices[iv].y(), hresult.m_OutputVertices[iv].z());
352         }
353     }
354 
355     delete[] btpoints;
356 
357     hl.ReleaseResult(hresult);
358 }
359 
360 }  // namespace bt_utils
361 }  // namespace collision
362 }  // namespace chrono
363