1 /*
2 Copyright (c) 2013-2018 Timur Gafarov 
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 
6 Permission is hereby granted, free of charge, to any person or organization
7 obtaining a copy of the software and accompanying documentation covered by
8 this license (the "Software") to use, reproduce, display, distribute,
9 execute, and transmit the Software, and to prepare derivative works of the
10 Software, and to permit third-parties to whom the Software is furnished to
11 do so, all subject to the following:
12 
13 The copyright notices in the Software and this entire statement, including
14 the above license grant, this restriction and the following disclaimer,
15 must be included in all copies of the Software, in whole or in part, and
16 all derivative works of the Software, unless such copies or derivative
17 works are solely in the form of machine-executable object code generated by
18 a source language processor.
19 
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 DEALINGS IN THE SOFTWARE.
27 */
28 
29 module dmech.cm;
30 
31 import std.math;
32 
33 import dlib.math.vector;
34 import dlib.math.matrix;
35 import dlib.math.quaternion;
36 import dlib.math.utils;
37 import dlib.geometry.plane;
38 import dlib.geometry.triangle;
39 
40 import dmech.contact;
41 import dmech.rigidbody;
42 import dmech.geometry;
43 import dmech.shape;
44 import dmech.mpr;
45 import dmech.clipping;
46 
47 // TODO: move projectPointOnPlane and planeBasis to dlib.geometry.plane
48 /*
49 Vector3f projectPointOnPlane(Vector3f point, Vector3f planeOrigin, Vector3f planeNormal)
50 {
51     Vector3f v = point - planeOrigin;
52     float dist = dot(v, planeNormal);
53     Vector3f projectedPoint = point - dist * planeNormal;
54     return projectedPoint;
55 }
56 
57 // Z direction in this basis is plane normal
58 Matrix4x4f planeBasis(Vector3f planeOrigin, Vector3f planeNormal)
59 {
60     Matrix4x4f basis = directionToMatrix(planeNormal);
61     basis.translation = planeOrigin;
62     return basis;
63 }
64 */
65 
66 Vector2f projectPoint(
67     Vector3f point, 
68     Vector3f origin, 
69     Vector3f right,
70     Vector3f up)
71 {
72     Vector2f res;
73     res.x = dot(right, point - origin);
74     res.y = dot(up, point - origin);
75     return res;
76 }
77 
78 Vector3f unprojectPoint(
79     Vector2f point, 
80     Vector3f origin, 
81     Vector3f right,
82     Vector3f up)
83 {
84     Vector3f res;
85     res = origin + up * point.y + right * point.x;
86     return res;
87 }
88 
89 /*
90  * Contact manifold.
91  * Stores information about two bodies' collision.
92  * Contacts are generated at once using axis rotation method
93  * (for incremental solution see pcm.d).
94  */
95 struct ContactManifold
96 {
97     Contact[8] contacts;
98     uint numContacts = 0;
99     Contact mainContact;
100     Feature f1;
101     Feature f2;
102     Vector2f[8] pts;
103 
104     // Find all contact points at once
105     void computeContacts(Contact c)
106     {
107         mainContact = c;
108 
109         uint numPts = 0;
110 
111         // Calculate tangent space for contact normal
112         Vector3f n0, n1;
113         if (dot(c.normal, Vector3f(1,0,0)) < 0.5f)
114             n0 = cross(c.normal, Vector3f(1,0,0)); 
115         else
116             n0 = cross(c.normal, Vector3f(0,0,1));
117         n1 = cross(n0, c.normal);
118         n0.normalize();
119         n1.normalize();
120 
121         // If colliding with a sphere, there's only one contact
122         if (c.shape1.geometry.type == GeomType.Sphere ||
123             c.shape2.geometry.type == GeomType.Sphere)
124         {
125             contacts[0] = c;
126             contacts[0].fdir1 = n0;
127             contacts[0].fdir2 = n1;
128             numContacts = 1;
129             return;
130         }
131 
132         if (c.shape1.geometry.type == GeomType.Ellipsoid ||
133             c.shape2.geometry.type == GeomType.Ellipsoid)
134         {
135             contacts[0] = c;
136             contacts[0].fdir1 = n0;
137             contacts[0].fdir2 = n1;
138             numContacts = 1;
139             return;
140         }
141         
142         Vector3f right = n0;
143         Vector3f up = n1;
144         
145         // Calculate basis for contact plane
146         Plane contactPlane;
147         contactPlane.fromPointAndNormal(c.point, c.normal);
148 
149         // Scan contact features by rotating axis
150         f1.numVertices = 0;
151         f2.numVertices = 0;
152         
153         float eps = 0.05f;
154         
155         // If colliding with a cylinder, use smaller contact area
156         if (c.shape1.geometry.type == GeomType.Cylinder ||
157             c.shape2.geometry.type == GeomType.Cylinder)
158         {
159             eps = 0.01f;
160         }
161 
162         if (c.shape1.geometry.type == GeomType.Cone ||
163             c.shape2.geometry.type == GeomType.Cone)
164         {
165             eps = 0.01f;
166         }
167 
168         float startAng = PI / 4;
169         float stepAng = PI / 2; // 90 degrees
170 
171         uint numAxes1 = 4;
172 
173         if (c.shape1.geometry.type == GeomType.Triangle)
174         {
175             numAxes1 = 3;
176             startAng = 0;
177             stepAng = radtodeg(120.0f); // 2*PI/3 = 120 degrees
178         }
179 
180         for(uint i = 0; i < numAxes1; i++)
181         {
182             float ang = startAng + stepAng * i;
183 
184             Vector3f axis = (c.normal + n0 * cos(ang) * eps + n1 * sin(ang) * eps).normalized;
185             
186             Vector3f p;
187 
188             supportTransformed(c.shape1, -axis, p);
189             
190             if (contactPlane.distance(p) < 0.0f)
191             {
192                 Vector2f planePoint = projectPoint(p, c.point, right, up);
193                 f1.vertices[f1.numVertices] = planePoint;
194                 f1.numVertices++;
195             }
196         }
197 
198         uint numAxes2 = 4;
199 
200         if (c.shape2.geometry.type == GeomType.Triangle)
201         {
202             numAxes2 = 3;
203             startAng = 0;
204             stepAng = radtodeg(120.0f); // 2*PI/3 = 120 degrees
205         }
206         
207         for(uint i = 0; i < numAxes2; i++)
208         {
209             float ang = startAng + stepAng * i;
210 
211             Vector3f axis = (c.normal + n0 * cos(ang) * eps + n1 * sin(ang) * eps).normalized;
212             
213             Vector3f p;
214 
215             supportTransformed(c.shape2, axis, p);
216             if (contactPlane.distance(p) > 0.0f)
217             {
218                 Vector2f planePoint = projectPoint(p, c.point, right, up);
219                 f2.vertices[f2.numVertices] = planePoint;
220                 f2.numVertices++;
221             }
222         }
223 
224         // Clip features in 2D space: find their overlapping polygon
225         clip(f1, f2, pts, numPts);
226 
227         // Transform the resulting points back into 3D space       
228         Contact[8] newManifold;
229         Vector3f centroid = Vector3f(0.0f, 0.0f, 0.0f);
230         for(uint i = 0; i < numPts; i++)
231         {
232             Contact newc;
233             newc.fact = true;
234             newc.body1 = c.body1;
235             newc.body2 = c.body2;
236             newc.shape1 = c.shape1;
237             newc.shape2 = c.shape2;
238             newc.point = unprojectPoint(pts[i], c.point, right, up);
239             newc.normal = c.normal;
240             newc.penetration = c.penetration;
241             if (numPts > 1)
242                 centroid += newc.point;
243             else
244             {
245                 newc.fdir1 = n0;
246                 newc.fdir2 = n1;
247             }
248             newManifold[i] = newc;
249         }
250 
251         if (numPts > 1)
252         {
253             centroid /= numPts;
254 
255             for(uint i = 0; i < numPts; i++)
256             {
257                 newManifold[i].fdir1 = (newManifold[i].point - centroid).normalized;
258                 newManifold[i].fdir2 = cross(newManifold[i].fdir1, c.normal);
259             }
260         }
261         
262         // Update the existing manifold
263         updateManifold(newManifold, numPts);
264     }
265     
266     void updateManifold(ref Contact[8] newManifold, uint numNewContacts)
267     {
268         numContacts = 0;
269     
270         Contact[8] res;
271         bool[8] used;
272         uint resNum = 0;
273         
274         foreach(i; 0..numContacts)
275         {        
276             Vector3f p1 = contacts[i].point;
277             
278             foreach(j; 0..numNewContacts)
279             {
280                 Vector3f p2 = newManifold[j].point;
281                 
282                 if (distance(p1, p2) < 0.1f)
283                 {
284                     res[resNum] = contacts[i];
285                     res[resNum].shape1 = newManifold[j].shape1;
286                     res[resNum].shape2 = newManifold[j].shape2;
287                     res[resNum].body1 = newManifold[j].body1;
288                     res[resNum].body2 = newManifold[j].body2;
289                     res[resNum].point = p2; //(p1 + p2) * 0.5f;
290                     res[resNum].normal = newManifold[j].normal;
291                     res[resNum].penetration = newManifold[j].penetration;
292                     res[resNum].fdir1 = newManifold[j].fdir1;
293                     res[resNum].fdir2 = newManifold[j].fdir2;
294                     resNum++;
295                     used[j] = true;
296                 }
297             }
298         }
299         
300         foreach(i; 0..numNewContacts)
301         {
302             if (!used[i])
303             {
304                 res[resNum] = newManifold[i];
305                 resNum++;
306             }
307         }
308         
309         foreach(i; 0..resNum)
310         {
311             contacts[i] = res[i];
312             numContacts++;
313         }
314     }
315 }
316