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