/*
* Libretro 3DEngine
* Copyright (C) 2013-2014 - Hans-Kristian Arntzen
* Copyright (C) 2013-2014 - Daniel De Matteis
*
* Libretro 3DEngine is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* Libretro 3DEngine is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Libretro 3DEngine.
* If not, see .
*/
#include "libretro.h"
#include "program.h"
#include "../engine/mesh.hpp"
#include
#include
using namespace glm;
struct Triangle
{
vec3 a, b, c;
vec3 normal;
float n0;
};
static std::vector triangles;
/* Probably not the most efficient way to do collision handling ... :) */
static inline bool inside_triangle(const Triangle& tri, const vec3& pos)
{
vec3 real_normal = -tri.normal;
vec3 ab = tri.b - tri.a;
vec3 ac = tri.c - tri.a;
vec3 ap = pos - tri.a;
vec3 bp = pos - tri.b;
vec3 bc = tri.c - tri.b;
/* Checks if point exists inside triangle. */
if (dot(cross(ab, ap), real_normal) < 0.0f)
return false;
if (dot(cross(ap, ac), real_normal) < 0.0f)
return false;
if (dot(cross(bc, bp), real_normal) < 0.0f)
return false;
return true;
}
#define TWIDDLE_FACTOR (-0.5f)
/* Here be dragons. 2-3 pages of mathematical derivations. */
static inline float point_crash_time(const vec3& pos, const vec3& v, const vec3& edge)
{
vec3 l = pos - edge;
float A = dot(v, v);
float B = 2 * dot(l, v);
float C = dot(l, l) - 1;
float d = B * B - 4.0f * A * C;
if (d < 0.0f) /* No solution, can't hit the sphere ever. */
return 10.0f; /* Return number > 1.0f to signal no collision. Makes taking min() easier. */
float d_sqrt = std::sqrt(d);
float sol0 = (-B + d_sqrt) / (2.0f * A);
float sol1 = (-B - d_sqrt) / (2.0f * A);
if (sol0 >= TWIDDLE_FACTOR && sol1 >= TWIDDLE_FACTOR)
return std::min(sol0, sol1);
else if (sol0 >= TWIDDLE_FACTOR && sol1 < TWIDDLE_FACTOR)
return sol0;
else if (sol0 < TWIDDLE_FACTOR && sol1 >= TWIDDLE_FACTOR)
return sol1;
return 10.0f;
}
static inline float line_crash_time(const vec3& pos, const vec3& v, const vec3& a, const vec3& b, vec3& crash_pos)
{
crash_pos = vec3(0.0f);
vec3 ab = b - a;
vec3 d = pos - a;
float ab_sqr = dot(ab, ab);
float T = dot(ab, v) / ab_sqr;
float S = dot(ab, d) / ab_sqr;
vec3 V = v - vec3(T) * ab;
vec3 W = d - vec3(S) * ab;
float A = dot(V, V);
float B = 2.0f * dot(V, W);
float C = dot(W, W) - 1.0f;
float D = B * B - 4.0f * A * C;
if (D < 0.0f) /* No solutions exist :( */
return 10.0f;
float D_sqrt = std::sqrt(D);
float sol0 = (-B + D_sqrt) / (2.0f * A);
float sol1 = (-B - D_sqrt) / (2.0f * A);
float solution;
if (sol0 >= TWIDDLE_FACTOR && sol1 >= TWIDDLE_FACTOR)
solution = std::min(sol0, sol1);
else if (sol0 >= TWIDDLE_FACTOR && sol1 < TWIDDLE_FACTOR)
solution = sol0;
else if (sol0 < TWIDDLE_FACTOR && sol1 >= TWIDDLE_FACTOR)
solution = sol1;
else
return 10.0f;
/* Check if solution hits the actual line ... */
float k = dot(ab, d + vec3(solution) * v) / ab_sqr;
if (k >= 0.0f && k <= 1.0f)
{
crash_pos = a + vec3(k) * ab;
return solution;
}
return 10.0f;
}
/////////// End dragons
void coll_wall_hug_detection(vec3& player_pos)
{
float min_dist = 1.0f;
const Triangle *closest_triangle_hug = 0;
for (unsigned i = 0; i < triangles.size(); i++)
{
const Triangle& tri = triangles[i];
float plane_dist = tri.n0 - dot(player_pos, tri.normal);
// Might be hugging too close.
if (plane_dist >= -0.01f && plane_dist < min_dist)
{
vec3 projected_pos = player_pos + tri.normal * vec3(plane_dist);
if (inside_triangle(tri, projected_pos))
{
min_dist = plane_dist;
closest_triangle_hug = &tri;
}
}
}
if (closest_triangle_hug)
{
#if 0
if (log_cb)
log_cb(RETRO_LOG_INFO, "Fixup hugging: Dist: %.6f.\n", min_dist);
#endif
// Push player out.
player_pos += vec3(min_dist - 1.0f) * closest_triangle_hug->normal;
}
}
void coll_detection(vec3& player_pos, vec3& velocity)
{
if (velocity == vec3(0.0))
return;
unsigned i;
float min_time = 1.0f;
bool crash = false;
vec3 crash_point = vec3(0.0f);
const Triangle *closest_triangle = 0;
for (i = 0; i < triangles.size(); i++)
{
const Triangle& tri = triangles[i];
float plane_dist = tri.n0 - dot(player_pos, tri.normal);
float towards_plane_v = dot(velocity, tri.normal);
if (towards_plane_v > 0.00001f) // We're moving towards the plane.
{
float ticks_to_hit = (plane_dist - 1.0f) / towards_plane_v;
// We'll hit the plane in this frame.
if (ticks_to_hit >= 0.0f && ticks_to_hit < min_time)
{
vec3 projected_pos = (player_pos + tri.normal) + vec3(ticks_to_hit) * velocity;
if (inside_triangle(tri, projected_pos))
{
min_time = ticks_to_hit;
closest_triangle = &tri;
crash = false;
}
}
else if (plane_dist >= 0.0f && plane_dist < 1.0f + towards_plane_v) // Can potentially hit vertex ...
{
vec3 crash_pos_ab, crash_pos_ac, crash_pos_bc;
/* Check how we can hit the triangle. Can hit edges or lines ... */
float min_time_crash = point_crash_time(player_pos, velocity, tri.a);
vec3 crash_pos_tmp = tri.a;
float time_point_b = point_crash_time(player_pos, velocity, tri.b);
if (time_point_b < min_time_crash)
{
crash_pos_tmp = tri.b;
min_time_crash = time_point_b;
}
float time_point_c = point_crash_time(player_pos, velocity, tri.c);
if (time_point_c < min_time_crash)
{
crash_pos_tmp = tri.c;
min_time_crash = time_point_c;
}
float time_line_ab = line_crash_time(player_pos, velocity, tri.a, tri.b, crash_pos_ab);
if (time_line_ab < min_time_crash)
{
crash_pos_tmp = crash_pos_ab;
min_time_crash = time_line_ab;
}
float time_line_ac = line_crash_time(player_pos, velocity, tri.a, tri.c, crash_pos_ac);
if (time_line_ac < min_time_crash)
{
crash_pos_tmp = crash_pos_ac;
min_time_crash = time_line_ac;
}
float time_line_bc = line_crash_time(player_pos, velocity, tri.b, tri.c, crash_pos_bc);
if (time_line_bc < min_time_crash)
{
crash_pos_tmp = crash_pos_bc;
min_time_crash = time_line_bc;
}
if (min_time_crash < min_time)
{
min_time = min_time_crash;
closest_triangle = &tri;
crash = true;
crash_point = crash_pos_tmp;
}
}
}
}
if (closest_triangle)
{
if (!crash)
{
vec3 normal = closest_triangle->normal;
// Move player to wall.
player_pos += vec3(1.0f * min_time) * velocity;
// Make velocity vector parallel with plane.
velocity -= vec3(dot(velocity, normal)) * normal;
// Used up some time moving to wall.
velocity *= vec3(1.0f - min_time);
}
else
{
// Avoid possible numerical inaccuracies by going fully to crash point.
player_pos += vec3(1.0f * min_time) * velocity;
vec3 normal = crash_point - player_pos;
velocity -= vec3(dot(velocity, normal) / dot(normal, normal)) * normal;
velocity *= vec3(1.0f - min_time);
}
}
}
static inline bool fequal(float a, float b)
{
return std::fabs(a - b) < 0.0001f;
}
static inline bool vequal(const vec3& a, const vec3& b)
{
return fequal(a[0], b[0]) &&
fequal(a[1], b[1]) &&
fequal(a[2], b[2]);
}
void coll_test_crash_detection(void)
{
vec3 out_pos;
vec3 pos = vec3(0.0f);
float a = point_crash_time(pos, vec3(1, 0, 0), vec3(3, 0, 0));
float b = point_crash_time(pos, vec3(1, 0, 0), vec3(2, 2, 0));
float c = point_crash_time(pos, vec3(1, 0, 0), vec3(1.0, 0.5, 0.0));
float d = point_crash_time(pos, vec3(0, 1, 0), vec3(0.5, 1.0, 0.0));
float e = line_crash_time(pos, vec3(1, 0, 0), vec3(4, -1, 0), vec3(4, 1, 0), out_pos);
assert(fequal(a, 2.0f));
assert(fequal(b, 10.0f));
assert(fequal(c, 1.0f - std::cos(30.0f / 180.0f * M_PI)));
assert(fequal(d, 1.0f - std::cos(30.0f / 180.0f * M_PI)));
assert(fequal(e, 3.0f) && vequal(out_pos, vec3(4, 0, 0)));
if (log_cb)
log_cb(RETRO_LOG_INFO, "Collision tests passed!\n");
}
void coll_triangles_push(unsigned v, const std::vector& vertices, vec3 player_size)
{
Triangle tri;
tri.a = vertices[v + 0].vert / player_size;
tri.b = vertices[v + 1].vert / player_size;
tri.c = vertices[v + 2].vert / player_size;
/* Make normals point inward. Makes for simpler computation. */
tri.normal = -normalize(cross(tri.b - tri.a, tri.c - tri.a));
/* Plane constant */
tri.n0 = dot(tri.normal, tri.a);
triangles.push_back(tri);
}
void coll_triangles_clear(void)
{
triangles.clear();
}