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.mpr;
30 
31 import dlib.math.vector;
32 import dlib.math.matrix;
33 import dlib.math.transformation;
34 import dlib.math.utils;
35 
36 import dmech.shape;
37 import dmech.contact;
38 
39 /*
40  * Implementation of the Minkowski Portal Refinement algorithm
41  */
42 
43 // TODO: use ShapeComponent.supportPointGlobal instead of this
44 void supportTransformed(ShapeComponent s, Vector3f dir, out Vector3f result)
45 {
46     Matrix4x4f m = s.transformation;
47 
48     result.x = ((dir.x * m.a11) + (dir.y * m.a21)) + (dir.z * m.a31);
49     result.y = ((dir.x * m.a12) + (dir.y * m.a22)) + (dir.z * m.a32);
50     result.z = ((dir.x * m.a13) + (dir.y * m.a23)) + (dir.z * m.a33);
51 
52     result = s.geometry.supportPoint(result);
53 
54     float x = ((result.x * m.a11) + (result.y * m.a12)) + (result.z * m.a13);
55     float y = ((result.x * m.a21) + (result.y * m.a22)) + (result.z * m.a23);
56     float z = ((result.x * m.a31) + (result.y * m.a32)) + (result.z * m.a33);
57 
58     result.x = m.a14 + x;
59     result.y = m.a24 + y;
60     result.z = m.a34 + z;
61 }
62 
63 /*
64  * TODO:
65  * - write c.fact here
66  */
67 
68 bool MPRCollisionTest(
69     ShapeComponent s1,
70     ShapeComponent s2,
71     ref Contact c)
72 {
73     enum float collideEpsilon = 1e-4f;
74     enum maxIterations = 10;
75 
76     // Used variables
77     Vector3f temp1;
78     Vector3f v01, v02, v0;
79     Vector3f v11, v12, v1;
80     Vector3f v21, v22, v2;
81     Vector3f v31, v32, v3;
82     Vector3f v41, v42, v4;
83 
84     Matrix4x4f transform1 = s1.transformation;
85     Matrix4x4f transform2 = s2.transformation;
86 
87     // Initialization of the output
88     c.point = Vector3f(0.0f, 0.0f, 0.0f);
89     c.normal = Vector3f(0.0f, 0.0f, 0.0f);
90     c.penetration = 0.0f;
91 
92     // Get the center of shape1 in world coordinates
93     v01 = transform1.translation;
94 
95     // Get the center of shape2 in world coordinates
96     v02 = transform2.translation;
97 
98     // v0 is the center of the Minkowski difference
99     v0 = v02 - v01;
100 
101     // Avoid case where centers overlap - any direction is fine in this case
102     if (v0.isAlmostZero)
103         v0 = Vector3f(0.00001f, 0.0f, 0.0f);
104 
105     // v1 = support in direction of origin
106     c.normal = -v0;
107 
108     supportTransformed(s1, v0, v11);
109     supportTransformed(s2, c.normal, v12);
110     v1 = v12 - v11;
111 
112     if (dot(v1, c.normal) <= 0.0f)
113         return false;
114 
115     // v2 = support perpendicular to v1,v0
116     c.normal = cross(v1, v0);
117 
118     if (c.normal.isAlmostZero)
119     {
120         c.normal = v1 - v0;
121         c.normal.normalize();
122 
123         c.point = v11;
124         c.point += v12;
125         c.point *= 0.5f;
126 
127         c.penetration = dot(v12 - v11, c.normal);
128 
129         return true;
130     }
131 
132     supportTransformed(s1, -c.normal, v21);
133     supportTransformed(s2,  c.normal, v22);
134     v2 = v22 - v21;
135 
136     if (dot(v2, c.normal) <= 0.0f)
137         return false;
138 
139     // Determine whether origin is on + or - side of plane (v1,v0,v2)
140     c.normal = cross(v1 - v0, v2 - v0);
141 
142     float dist = dot(c.normal, v0);
143 
144     // If the origin is on the - side of the plane, reverse the direction of the plane
145     if (dist > 0.0f)
146     {
147         swap(&v1, &v2);
148         swap(&v11, &v21);
149         swap(&v12, &v22);
150         c.normal = -c.normal;
151     }
152 
153     int phase2 = 0;
154     int phase1 = 0;
155     bool hit = false;
156 
157     // Phase One: Identify a portal
158     while (true)
159     {
160         if (phase1 > maxIterations)
161             return false;
162 
163         phase1++;
164 
165         // Obtain the support point in a direction perpendicular to the existing plane
166         // Note: This point is guaranteed to lie off the plane
167         supportTransformed(s1, -c.normal, v31);
168         supportTransformed(s2,  c.normal, v32);
169         v3 = v32 - v31;
170 
171         if (dot(v3, c.normal) <= 0.0f)
172             return false;
173 
174         // If origin is outside (v1,v0,v3), then eliminate v2 and loop
175         temp1 = cross(v1, v3);
176         if (dot(temp1, v0) < 0.0f)
177         {
178             v2 = v3;
179             v21 = v31;
180             v22 = v32;
181             c.normal = cross(v1 - v0, v3 - v0);
182             continue;
183         }
184 
185         // If origin is outside (v3,v0,v2), then eliminate v1 and loop
186         temp1 = cross(v3, v2);
187         if (dot(temp1, v0) < 0.0f)
188         {
189             v1 = v3;
190             v11 = v31;
191             v12 = v32;
192             c.normal = cross(v3 - v0, v2 - v0);
193             continue;
194         }
195 
196         // Phase Two: Refine the portal
197         // We are now inside of a wedge...
198         while (true)
199         {
200             phase2++;
201 
202             // Compute normal of the wedge face
203             c.normal = cross(v2 - v1, v3 - v1);
204 
205             // Can this happen? Can it be handled more cleanly?
206             //if (c.normal.isAlmostZero)
207             //    return true;
208 
209             c.normal.normalize();
210 
211             // Compute distance from origin to wedge face
212             float d = dot(c.normal, v1);
213 
214             // If the origin is inside the wedge, we have a hit
215             if (d >= 0 && !hit)
216                 hit = true;
217 
218             // Find the support point in the direction of the wedge face
219             supportTransformed(s1, -c.normal, v41);
220             supportTransformed(s2,  c.normal, v42);
221             v4 = v42 - v41;
222 
223             float delta = dot(v4 - v3, c.normal);
224             c.penetration = dot(v4, c.normal);
225 
226             // If the boundary is thin enough or the origin is outside
227             // the support plane for the newly discovered vertex, then we can terminate
228             if (delta <= collideEpsilon || c.penetration <= 0.0f || phase2 > maxIterations)
229             {
230                 if (hit)
231                 {
232                     float b0 = dot(cross(v1, v2), v3);
233                     float b1 = dot(cross(v3, v2), v0);
234                     float b2 = dot(cross(v0, v1), v3);
235                     float b3 = dot(cross(v2, v1), v0);
236 
237                     float sum = b0 + b1 + b2 + b3;
238 
239                     if (sum <= 0)
240                     {
241                         b0 = 0;
242                         b1 = dot(cross(v2, v3), c.normal);
243                         b2 = dot(cross(v3, v1), c.normal);
244                         b3 = dot(cross(v1, v2), c.normal);
245 
246                         sum = b1 + b2 + b3;
247                     }
248 
249                     float inv = 1.0f / sum;
250 
251                     c.point = v01 * b0;
252                     c.point += v11 * b1;
253                     c.point += v21 * b2;
254                     c.point += v31 * b3;
255 
256                     c.point += v02 * b0;
257                     c.point += v12 * b1;
258                     c.point += v22 * b2;
259                     c.point += v32 * b3;
260 
261                     c.point *= inv * 0.5f;
262                 }
263 
264                 return hit;
265             }
266 
267             // Compute the tetrahedron dividing face (v4,v0,v3)
268             temp1 = cross(v4, v0);
269             float d2 = dot(temp1, v1);
270 
271             if (d2 >= 0.0f)
272             {
273                 d2 = dot(temp1, v2);
274                 if (d2 >= 0.0f)
275                 {
276                     // Inside d1 & inside d2 ==> eliminate v1
277                     v1 = v4;
278                     v11 = v41;
279                     v12 = v42;
280                 }
281                 else
282                 {
283                     // Inside d1 & outside d2 ==> eliminate v3
284                     v3 = v4;
285                     v31 = v41;
286                     v32 = v42;
287                 }
288             }
289             else
290             {
291                 d2 = dot(temp1, v3);
292 
293                 if (d2 >= 0.0f)
294                 {
295                     // Outside d1 & inside d3 ==> eliminate v2
296                     v2 = v4;
297                     v21 = v41;
298                     v22 = v42;
299                 }
300                 else
301                 {
302                     // Outside d1 & outside d3 ==> eliminate v1
303                     v1 = v4;
304                     v11 = v41;
305                     v12 = v42;
306                 }
307             }
308         }
309     }
310 
311     // Should never get here
312     return false;
313 }