summaryrefslogtreecommitdiff
path: root/engine-ocean/Game/Systems/CollisionSystems/ellipsoidtrianglecollisionsystem.cpp
blob: 52b891637282be19bbab81c17c998e3848df6481 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
#include "ellipsoidtrianglecollisionsystem.h"
#include "Game/Components/CollisionComponents/BoundingTriangle.h"
#include "Game/Components/CollisionComponents/CollisionComponent.h"
#include "Game/Components/CollisionComponents/boundingellipsoid.h"
#include "Game/Components/DrawComponent.h"
#include "Game/Components/TransformComponent.h"
#include "Game/GameObjects/GameObject.h"
#include <memory>

EllipsoidTriangleCollisionSystem::EllipsoidTriangleCollisionSystem(std::map<std::string, std::shared_ptr<GameObject>>& rigid_gameobjects) :
    m_rigid_gameobjects(rigid_gameobjects),
    m_bvh_system(std::make_unique<BVHTree>(rigid_gameobjects))
{
}


Triangle EllipsoidTriangleCollisionSystem::convertTriangleToSphereSpace(const glm::vec3 &orig_R, const Triangle &orig_triangle){
    Triangle transformed_triangle;

    // divide vertex pos by Rs
    transformed_triangle.vertexA = orig_triangle.vertexA / orig_R;
    transformed_triangle.vertexB = orig_triangle.vertexB / orig_R;
    transformed_triangle.vertexC = orig_triangle.vertexC / orig_R;

    transformed_triangle.edge1 = transformed_triangle.vertexB - transformed_triangle.vertexA;
    transformed_triangle.edge2 = transformed_triangle.vertexC - transformed_triangle.vertexA;

    // does the normal stay the same across transformations?
    transformed_triangle.normal = glm::normalize(glm::cross(transformed_triangle.edge1, transformed_triangle.edge2));

    return transformed_triangle;
}


glm::vec3 EllipsoidTriangleCollisionSystem::getIntersectionPt(const glm::vec3 &ray_p, const glm::vec3 &ray_d, const float t){
    return ray_p + t*ray_d;
}

float EllipsoidTriangleCollisionSystem::solveQuadratic(float a, float b, float c){
    float discriminant = pow(b,2) - 4*a*c;

    // check for imaginary numbers
    if (discriminant >= 0){
        float pos_quad = (-b + sqrt(discriminant)) / (2*a);
        float neg_quad = (-b - sqrt(discriminant)) / (2*a);

        // if the mininum is a neg number, then return the larger of the two
        if (fmin(pos_quad, neg_quad) < ZERO){
            return fmax(pos_quad, neg_quad);
        } else {
            return fmin(pos_quad, neg_quad);
        }
    }
    return -1;
}


float EllipsoidTriangleCollisionSystem::raycastSphere(const glm::vec3 &ray_p, const glm::vec3 &ray_d, const glm::vec3 posA, const glm::vec3 posB){
    // shift ray and ellipsoid to center first in order to use regular implicit sphere equation
    glm::vec3 p = ray_p - posA;
    glm::vec3 d = ray_d - posA;

    float a = (d.x*d.x) + (d.y*d.y) + (d.z*d.z);
    float b = (2.f*p.x*d.x) + (2.f*p.y*d.y) + (2.f*p.z*d.z);
    float c = (p.x*p.x) + (p.y*p.y) + (p.z*p.z) - 1.f;

    return solveQuadratic(a,b,c);
}

float EllipsoidTriangleCollisionSystem::raycastTriangle(const glm::vec3 &ray_p, const glm::vec3 &ray_d, const Triangle &triangle){
    float t = (glm::dot((triangle.vertexA - ray_p), triangle.normal))/(glm::dot(ray_d, triangle.normal));
    return t;
}

glm::vec3 EllipsoidTriangleCollisionSystem::getTriangleEdge(const glm::vec3 v1, const glm::vec3 v2){
    return v2 - v1;
}

bool EllipsoidTriangleCollisionSystem::isSameDirection(const glm::vec3 edge1, const glm::vec3 edge2, const glm::vec3 n){
    if (glm::dot(glm::cross(edge1, edge2), n) > 0){
        return true;
    }
    return false;
}

void EllipsoidTriangleCollisionSystem::makeCollisionData(const float t, const glm::vec3 triangle_n,
                                                         const glm::vec3 intersection_pos,
                                                         const glm::vec3 point_of_contact,
                                                         const bool isVertex){
    CollisionData data;
    data.t = t;
    data.triangle_n = triangle_n;
    data.intersection_pos = intersection_pos;
    data.point_of_contact = point_of_contact;
    data.isVertex = isVertex;
    m_collisionData.push_back(data);
}


bool EllipsoidTriangleCollisionSystem::detectSphere_with_Interior(const Triangle &triangle, const glm::vec3 posA, const glm::vec3 posB){
    // calculate point on sphere closest to the plane and cast a ray
    glm::vec3 ray_p = posA-triangle.normal; // closest point on unit sphere
    glm::vec3 ray_d = (posB-posA);

    glm::vec3 n = triangle.normal;
    glm::vec3 v = triangle.vertexA;
    float plane_const = -1 * (n.x*v.x + n.y*v.y + n.z*v.z);

    // pdf equation
    float t = (1-(glm::dot(n, posA) + plane_const)) / (glm::dot(n, (posB-posA)));
    if (t < ZERO || t > ONE){
        return false;
    }

    // the point P on the sphere closest to plane surface
    glm::vec3 P_edge = getIntersectionPt(ray_p, ray_d, t);


    // determine if point is on triangle plane
        // determine if point is also within triangle bounds
        glm::vec3 AB = getTriangleEdge(triangle.vertexA, triangle.vertexB);
        glm::vec3 BC = getTriangleEdge(triangle.vertexB, triangle.vertexC);
        glm::vec3 CA = getTriangleEdge(triangle.vertexC, triangle.vertexA);

        glm::vec3 AP = getTriangleEdge(triangle.vertexA, P_edge);
        glm::vec3 BP = getTriangleEdge(triangle.vertexB, P_edge);
        glm::vec3 CP = getTriangleEdge(triangle.vertexC, P_edge);

        if (isSameDirection(AB, AP, triangle.normal) &&
                isSameDirection(BC, BP, triangle.normal) &&
                isSameDirection(CA, CP, triangle.normal)){

            // the actual center of sphere
            glm::vec3 P_centered = getIntersectionPt(posA, posB-posA, t);
            makeCollisionData(t, triangle.normal, P_centered, P_edge, false);
            return true;
        }
    return false;
}

// do this for each vertex pair AB, BC, CA
bool EllipsoidTriangleCollisionSystem::detectSphere_with_Edge(const glm::vec3 &v1, const glm::vec3 &v2,
                                                              const glm::vec3 &triangle_normal,
                                                              const glm::vec3 &posA, const glm::vec3 &posB){
    glm::vec3 C = v1;
    glm::vec D = v2;

    float a = pow(glm::length(glm::cross(posB-posA, D-C)),2);
    float b = glm::dot(2.f*glm::cross(posB-posA, D-C), glm::cross(posA-C, D-C));
    float c = pow(glm::length(glm::cross(posA-C, D-C)),2) - pow(glm::length(D-C),2);

    float t = solveQuadratic(a,b,c);

    // discard a negative t --> indicates no collision with edge
    if (t < ZERO || t > ONE){
        return false;
    }

    glm::vec3 P = getIntersectionPt(posA, posB-posA, t);

    glm::vec3 AP = P-C;
    glm::vec3 AB = D-C;
    glm::vec3 normal = AP - (glm::dot(AB,AP)/glm::dot(AB,AB))*AB;

    // determine if intersection pt is between C and D
    if ( (0 < glm::dot(P-C,D-C)) &&  (glm::dot(P-C,D-C) < pow(glm::length(D-C),2)) ){
        makeCollisionData(t, normal, P, P, false);
        return true;
    }

    return false;
}

// needs to be done with each vertex
bool EllipsoidTriangleCollisionSystem::detectSphere_with_Vertex(const glm::vec3 &v, const glm::vec3 triangle_normal, const glm::vec3 &posA, const glm::vec3 &posB){
    glm::vec3 ray_p = v;
    //glm::vec3 ray_d = v-(posB-posA);

    // convert to origin space:
    glm::vec3 vel = posB-posA;
    glm::vec3 v_origined = v - posA;

    float a = glm::dot(vel,vel);
    float b = 2*(glm::dot(vel, -v_origined));
    float c = pow(glm::length(v_origined),2) - 1.f;

    float t = solveQuadratic(a,b,c);
    if (t < ZERO || t > ONE){
        return false;
    }

    // where the center is when the sphere hits vertex
    glm::vec3 center_pt_origined = t*vel;
    // convert back to non-origin space
    glm::vec3 center_pt = center_pt_origined + posA;
    glm::vec3 normal = center_pt - v;
    makeCollisionData(t, normal, center_pt, v, true);
     return true;
}

CollisionData EllipsoidTriangleCollisionSystem::getNearestCollision(const std::vector<CollisionData> &collisionData, const Triangle &sphere_tri){
    CollisionData nearestCollision;
    nearestCollision.t = INFINITY;

    for (const CollisionData &data : collisionData){
        if (data.t < nearestCollision.t){
            // set nearest collision to datapoint
            nearestCollision = data;
        }
    }
    return nearestCollision;
}

// SPHERE SPACE
CollisionData EllipsoidTriangleCollisionSystem::detectTriangleCollision(const Triangle &sphere_tri, const glm::vec3 &posA, const glm::vec3 &posB){

    m_collisionData.clear();
    // if interior is true, no need to check rest. get the only collision data
    if (detectSphere_with_Interior(sphere_tri, posA, posB)){
        return m_collisionData[0];
    }

    // otherwise check rest:

    // detect edge with all 3 edges
    // | operator executes regardless of if left hand side is true or not
    if (
    detectSphere_with_Edge(sphere_tri.vertexA, sphere_tri.vertexB, sphere_tri.normal, posA, posB) |
    detectSphere_with_Edge(sphere_tri.vertexB, sphere_tri.vertexC, sphere_tri.normal, posA, posB) |
    detectSphere_with_Edge(sphere_tri.vertexC, sphere_tri.vertexA, sphere_tri.normal, posA, posB) ){
        return getNearestCollision(m_collisionData, sphere_tri);
    }

    // detect vertex with all 3 vertices
    detectSphere_with_Vertex(sphere_tri.vertexA, sphere_tri.normal, posA, posB);
    detectSphere_with_Vertex(sphere_tri.vertexB, sphere_tri.normal, posA, posB);
    detectSphere_with_Vertex(sphere_tri.vertexC, sphere_tri.normal, posA, posB);

    return getNearestCollision(m_collisionData, sphere_tri);
}

CollisionData EllipsoidTriangleCollisionSystem::revertDataToOriginalSpace(const CollisionData &sphere_datum, const glm::vec3 &ellip_R, const glm::vec3 orig_triangle_n){
    CollisionData reverted_datum = sphere_datum;
    reverted_datum.intersection_pos = reverted_datum.intersection_pos*ellip_R;
    reverted_datum.point_of_contact = reverted_datum.point_of_contact*ellip_R;

    return reverted_datum;
}

void EllipsoidTriangleCollisionSystem::setEllipsoidDimensions(const glm::vec3 &dimensions){
    m_ellipsoid_dimensions = dimensions;
}

CollisionData EllipsoidTriangleCollisionSystem::collideWithWorld(const glm::vec3 &posA, const glm::vec3 &posB){
    // makes ellipsoids
    Ellipsoid ellipsoidA;
    ellipsoidA.R = m_ellipsoid_dimensions;
    ellipsoidA.center_pos = posA;


    Ellipsoid ellipsoidB;
    ellipsoidB.R = m_ellipsoid_dimensions;
    ellipsoidB.center_pos = posB;

    // convert ellipsoid centers to sphere space
    glm::vec3 sphere_posA = ellipsoidA.center_pos/m_ellipsoid_dimensions;
    glm::vec3 sphere_posB = ellipsoidB.center_pos/m_ellipsoid_dimensions;

    CollisionData nearestCollision;
    nearestCollision.t = INFINITY;


    std::vector<Triangle> candidates = m_bvh_system->getBVHDetectedCollisions(posA, posB, ellipsoidA.R);
   // std::cout << "candidate size: " << candidates.size() << std::endl;


//    for (auto &rigid_go : candidates){
//        //std::cout << "candidate shape name: " << rigid_go->getComponent<DrawComponent>()->getShapeName() << std::endl;
//        if (rigid_go->hasComponent<CollisionComponent>()){
//            if (rigid_go->getComponent<CollisionComponent>()->hasCollisionShape<BoundingTriangle>()){
//                // for every triangle in game obj, transform to sphere space and collide with ellipsoid (aka sphere)
                for (int i=0; i< candidates.size(); i++){
                    Triangle triangle = candidates[i];
//                    std::cout<< "triangle i: " << i << ", v1: (" << triangle.vertexA.x << ", " << triangle.vertexA.y << ", " << triangle.vertexA.z << "), v2: ("
//                                                                 << triangle.vertexB.x << ", " << triangle.vertexB.y << ", " << triangle.vertexB.z << "), v3: ("
//                                                                 << triangle.vertexC.x << ", " << triangle.vertexC.y << ", " << triangle.vertexC.z << ")" << std::endl;

                    // transform triangle to sphere space and detect collision data (in sphere space) for this specific triangle
                    Triangle sphere_triangle = convertTriangleToSphereSpace(m_ellipsoid_dimensions, triangle);
                    CollisionData sphere_collisionDatum = detectTriangleCollision(sphere_triangle, sphere_posA, sphere_posB);
                    CollisionData ellip_collisionDatum = revertDataToOriginalSpace(sphere_collisionDatum, m_ellipsoid_dimensions, triangle.normal);

                    // keep track of nearest Collision
                    if (ellip_collisionDatum.t < nearestCollision.t && ellip_collisionDatum.t <= ONE && ellip_collisionDatum.t >= ZERO){
                        nearestCollision = ellip_collisionDatum;
                    }
                    // repeat for the other triangles
                }
            //}
        //}
        // repeat for all gameobjects
    //}

    return nearestCollision;
}

glm::vec3 EllipsoidTriangleCollisionSystem::doNudge(glm::vec3 curr_pos, CollisionData collision){
    glm::vec3 nudge = m_scale_normals_mt*collision.triangle_n;

    glm::vec3 pos_nudged =  collision.intersection_pos + nudge * .01f;
    int MAX_NUDGES = 3;
    for (int i=0; i<MAX_NUDGES; i++){
        CollisionData nudge_collision = collideWithWorld(curr_pos, pos_nudged);
        if (nudge_collision.t == INFINITY){
            curr_pos = pos_nudged;
            break;
        } else {
            if (glm::length((m_scale_normals_mt*nudge_collision.triangle_n) - nudge) < .0001 || glm::length((m_scale_normals_mt*nudge_collision.triangle_n) + nudge) < .0001){
                nudge = -m_scale_normals_mt*nudge_collision.triangle_n;
            } else {
                nudge = m_scale_normals_mt*nudge_collision.triangle_n;
            }
            pos_nudged = nudge_collision.intersection_pos + nudge * .01f;
        }
    }
    return curr_pos;

}

glm::mat3 EllipsoidTriangleCollisionSystem::makeConversionMT(glm::vec3 initial_center_pos, glm::vec3 dimensions){
    m_conversion_mt = std::make_shared<ModelTransform>();
    m_conversion_mt->translate(-initial_center_pos);
    m_conversion_mt->scale(dimensions);
    m_scale_normals_mt = glm::transpose((glm::inverse(glm::mat3(m_conversion_mt->getModelMatrix()))));
    m_scale_normals_mt = glm::mat4(1.f);
    return m_scale_normals_mt;
}

// in sphere space
std::pair<std::vector<CollisionData>, glm::vec3> EllipsoidTriangleCollisionSystem::mtvSlide(glm::vec3 initial_pos, glm::vec3 final_pos, glm::vec3 dimensions){
    setEllipsoidDimensions(dimensions);
    glm::mat3 normals_scale = makeConversionMT(initial_pos, dimensions);

    std::vector<CollisionData> collisions;
    glm::vec3 curr_pos = initial_pos;
    glm::vec3 next_pos = final_pos;
    int MAX_TRANSLATIONS = 3;
    for (int i=0; i<MAX_TRANSLATIONS; i++){
        CollisionData c = collideWithWorld(initial_pos, next_pos);

        // if no collisions [0,1] found, then continue on regular path
        if (c.t == INFINITY){
            //std::cout << "--mtv-- " << next_pos.x << "," << next_pos.y << "," << next_pos.z << std::endl;
            return std::pair<std::vector<CollisionData>, glm::vec3>(collisions, next_pos);
        } else {
            // nudge along the collision data
            //std::cout << "COLLIDING......" << std::endl;
            curr_pos = doNudge(curr_pos, c);
            glm::vec3 d = next_pos - curr_pos;
            glm::vec3 d_corrected = (d - glm::dot(d, normals_scale*c.triangle_n)) * (normals_scale*c.triangle_n);
            next_pos = curr_pos + d_corrected;
            collisions.push_back(c);
        }

    }

    return std::pair<std::vector<CollisionData>, glm::vec3>(collisions, curr_pos);
}