Merge pull request #194 from kichikuou/collision

TapirEngine: Proper collision detection
This commit is contained in:
Nunuhara Cabbage
2024-09-30 10:53:47 -07:00
committed by GitHub
8 changed files with 217 additions and 46 deletions
+1
View File
@@ -212,6 +212,7 @@ bool RE_instance_set_vertex_pos(struct RE_instance *instance, int index, float x
int RE_instance_get_bone_index(struct RE_instance *instance, const char *name);
bool RE_instance_trans_local_pos_to_world_pos_by_bone(struct RE_instance *instance, int bone, vec3 offset, vec3 out);
float RE_instance_calc_height(struct RE_instance *instance, float x, float z);
float RE_instance_calc_2d_detection_height(struct RE_instance *instance, float x, float z);
bool RE_instance_calc_2d_detection(struct RE_instance *instance, float x0, float y0, float z0, float x1, float y1, float z1, float *x2, float *y2, float *z2, float radius);
bool RE_instance_set_debug_draw_shadow_volume(struct RE_instance *instance, bool draw);
+1 -1
View File
@@ -21,7 +21,7 @@ ffi = dependency('libffi', static : static_libs)
tj = dependency('libturbojpeg', static : static_libs)
webp = dependency('libwebp', static : static_libs)
png = dependency('libpng', static : static_libs)
cglm = dependency('cglm', version : '>=0.8.3', fallback : ['cglm', 'cglm_dep'])
cglm = dependency('cglm', version : '>=0.9.2', fallback : ['cglm', 'cglm_dep'])
sndfile = dependency('sndfile', static : static_libs)
avcodec = dependency('libavcodec', version : '>=59.37.100', required: false, static : static_libs)
+14 -4
View File
@@ -515,15 +515,25 @@ struct amt_material *amt_find_material(struct amt *amt, const char *name);
struct collider {
struct collider_triangle *triangles;
uint32_t nr_triangles;
struct collider_edge *edges; // boundary edges
uint32_t nr_edges;
};
struct collider_triangle {
vec3 vertices[3];
vec3 aabb[2];
vec2 vertices[3]; // xz coordinates
vec2 aabb[2];
vec2 slope;
float intercept;
};
struct collider *collider_create(struct pol *pol);
struct collider_edge {
vec2 vertices[2]; // xz coordinates
vec2 aabb[2];
};
struct collider *collider_create(struct pol_mesh *mesh);
void collider_free(struct collider *collider);
bool check_collision(struct collider *collider, vec3 p0, vec3 p1, float radius, vec3 out);
bool collider_height(struct collider *collider, vec2 xz, float *h_out);
bool check_collision(struct collider *collider, vec2 p0, vec2 p1, float radius, vec2 out);
#endif /* SYSTEM4_3D_3D_INTERNAL_H */
+172 -29
View File
@@ -14,6 +14,7 @@
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include <assert.h>
#include <stdlib.h>
#include <cglm/cglm.h>
@@ -21,52 +22,194 @@
#include "3d_internal.h"
struct collider *collider_create(struct pol *pol)
void init_triangles(struct collider *collider, struct pol_mesh *mesh)
{
struct collider *collider = xcalloc(1, sizeof(struct collider));
int nr_triangles = 0;
for (int i = 0; i < pol->nr_meshes; i++) {
nr_triangles += pol->meshes[i]->nr_triangles;
}
collider->nr_triangles = nr_triangles;
collider->triangles = xcalloc(nr_triangles, sizeof(struct collider_triangle));
collider->nr_triangles = mesh->nr_triangles;
collider->triangles = xcalloc(mesh->nr_triangles, sizeof(struct collider_triangle));
struct collider_triangle *t = collider->triangles;
for (int mesh_i = 0; mesh_i < pol->nr_meshes; mesh_i++) {
struct pol_mesh *mesh = pol->meshes[mesh_i];
for (int tri_i = 0; tri_i < mesh->nr_triangles; tri_i++) {
glm_aabb_invalidate(t->aabb);
for (int i = 0; i < 3; i++) {
glm_vec3_copy(mesh->vertices[mesh->triangles[tri_i].vert_index[i]].pos, t->vertices[i]);
glm_vec3_minv(t->vertices[i], t->aabb[0], t->aabb[0]);
glm_vec3_maxv(t->vertices[i], t->aabb[1], t->aabb[1]);
for (int tri_i = 0; tri_i < mesh->nr_triangles; tri_i++, t++) {
vec3 vertices[3];
glm_aabb2d_invalidate(t->aabb);
for (int i = 0; i < 3; i++) {
glm_vec3_copy(mesh->vertices[mesh->triangles[tri_i].vert_index[i]].pos, vertices[i]);
t->vertices[i][0] = vertices[i][0];
t->vertices[i][1] = vertices[i][2];
glm_vec2_minv(t->vertices[i], t->aabb[0], t->aabb[0]);
glm_vec2_maxv(t->vertices[i], t->aabb[1], t->aabb[1]);
}
// Do some precomputation so that we can calculate height(x, z) quickly.
vec3 v1, v2, normal;
glm_vec3_sub(vertices[1], vertices[0], v1);
glm_vec3_sub(vertices[2], vertices[0], v2);
glm_vec3_cross(v1, v2, normal);
t->slope[0] = -normal[0] / normal[1];
t->slope[1] = -normal[2] / normal[1];
t->intercept = glm_vec3_dot(normal, vertices[0]) / normal[1];
}
}
void init_edges(struct collider *collider, struct pol_mesh *mesh)
{
// Find boundary edges, i.e., edges that belong to only one triangle.
const int nv = mesh->nr_vertices;
const int bv_size = (nv * nv + 31) / 32;
uint32_t *bv = xcalloc(bv_size, 4);
int nr_edges = 0;
for (int i = 0; i < mesh->nr_triangles; i++) {
for (int j = 0; j < 3; j++) {
int v1 = mesh->triangles[i].vert_index[j];
int v2 = mesh->triangles[i].vert_index[(j + 1) % 3];
int b = v2 * nv + v1;
if (bv[b >> 5] & 1U << (b & 31)) {
bv[b >> 5] &= ~(1U << (b & 31));
nr_edges--;
} else {
b = v1 * nv + v2;
bv[b >> 5] |= 1U << (b & 31);
nr_edges++;
}
t++;
}
}
collider->nr_edges = nr_edges;
collider->edges = xcalloc(nr_edges, sizeof(struct collider_edge));
struct collider_edge* edge = collider->edges;
for (int i = 0; i < bv_size; i++) {
if (!bv[i]) continue;
for (int j = 0; j < 32; j++) {
if (!(bv[i] & 1U << j)) continue;
int b = i << 5 | j;
int v1 = b / nv;
int v2 = b % nv;
edge->vertices[0][0] = mesh->vertices[v1].pos[0];
edge->vertices[0][1] = mesh->vertices[v1].pos[2];
edge->vertices[1][0] = mesh->vertices[v2].pos[0];
edge->vertices[1][1] = mesh->vertices[v2].pos[2];
glm_vec2_minv(edge->vertices[0], edge->vertices[1], edge->aabb[0]);
glm_vec2_maxv(edge->vertices[0], edge->vertices[1], edge->aabb[1]);
edge++;
}
}
assert(edge == collider->edges + nr_edges);
free(bv);
}
struct collider *collider_create(struct pol_mesh *mesh)
{
struct collider *collider = xcalloc(1, sizeof(struct collider));
init_triangles(collider, mesh);
init_edges(collider, mesh);
NOTICE("collider: %d triangles %d edges", collider->nr_triangles, collider->nr_edges);
return collider;
}
void collider_free(struct collider *collider)
{
free(collider->triangles);
free(collider->edges);
free(collider);
}
// When a sphere of radius `radius` moves from `p0` to `p1`, return the point
// where it collides with `collider` in `out`. If no collision is detected,
// return `p1` in `out`.
bool check_collision(struct collider *collider, vec3 p0, vec3 p1, float radius, vec3 out)
static bool in_triangle(struct collider_triangle* t, vec2 xz)
{
// XXX: Very rough collision detection; just check if p1 is inside any triangle's AABB.
for (int i = 0; i < collider->nr_triangles; i++) {
if (glm_aabb_point(collider->triangles[i].aabb, p1)) {
glm_vec3_copy(p0, out);
return true;
for (int i = 0; i < 3; i++) {
vec2 a, b;
glm_vec2_sub(t->vertices[(i + 1) % 3], t->vertices[i], a);
glm_vec2_sub(xz, t->vertices[i], b);
if (glm_vec2_cross(a, b) > 0.f)
return false;
}
return true;
}
static struct collider_triangle* find_triangle(struct collider *collider, vec2 xz)
{
struct collider_triangle* t = collider->triangles;
for (; t < collider->triangles + collider->nr_triangles; t++) {
if (!glm_aabb2d_point(t->aabb, xz))
continue;
if (in_triangle(t, xz))
return t;
}
return NULL;
}
bool collider_height(struct collider *collider, vec2 xz, float *h_out)
{
struct collider_triangle* t = find_triangle(collider, xz);
if (!t)
return false;
*h_out = glm_vec2_dot(t->slope, xz) + t->intercept;
return true;
}
static float distance_point_to_edge(vec2 p, struct collider_edge *e, vec2 closest_point_out)
{
vec2 v, w;
glm_vec2_sub(e->vertices[1], e->vertices[0], v);
glm_vec2_sub(p, e->vertices[0], w);
float c1 = glm_vec2_dot(w, v);
if (c1 < 0.f) {
glm_vec2_copy(e->vertices[0], closest_point_out);
return glm_vec2_distance(p, e->vertices[0]);
}
float c2 = glm_vec2_norm2(v);
if (c2 <= c1) {
glm_vec2_copy(e->vertices[1], closest_point_out);
return glm_vec2_distance(p, e->vertices[1]);
}
float t = c1 / c2;
glm_vec2_copy(e->vertices[0], closest_point_out);
glm_vec2_muladds(v, t, closest_point_out);
return glm_vec2_distance(p, closest_point_out);
}
bool check_collision(struct collider *collider, vec2 p0, vec2 p1, float radius, vec2 out)
{
if (find_triangle(collider, p1)) {
glm_vec2_copy(p1, out);
} else {
if (!find_triangle(collider, p0))
return false;
// Find a point on the "collision" mesh, using binary search.
vec2 good, bad;
glm_vec2_copy(p0, good);
glm_vec2_copy(p1, bad);
float w = glm_vec2_distance(good, bad);
while (w > radius) {
vec2 mid;
glm_vec2_center(good, bad, mid);
if (find_triangle(collider, mid)) {
glm_vec2_copy(mid, good);
} else {
glm_vec2_copy(mid, bad);
}
w /= 2.f;
}
glm_vec2_copy(good, out);
}
// Ensure that `out` is at least `radius` away from any boundary edge.
vec2 aabb[2];
glm_vec2_subs(out, radius, aabb[0]);
glm_vec2_adds(out, radius, aabb[1]);
for (int i = 0; i < collider->nr_edges; i++) {
struct collider_edge *e = &collider->edges[i];
if (!glm_aabb2d_aabb(e->aabb, aabb))
continue;
vec2 q;
float distance = distance_point_to_edge(out, e, q);
if (distance < radius) {
// out = q + normalize(out - q) * radius
vec2 v;
glm_vec2_sub(out, q, v);
glm_vec2_normalize(v);
glm_vec2_copy(q, out);
glm_vec2_muladds(v, radius, out);
// update AABB
glm_vec2_subs(out, radius, aabb[0]);
glm_vec2_adds(out, radius, aabb[1]);
}
}
glm_vec3_copy(p1, out);
return true;
}
+7 -4
View File
@@ -463,6 +463,13 @@ struct model *model_load(struct archive *aar, const char *path)
for (uint32_t i = 0; i < pol->nr_meshes; i++) {
if (!pol->meshes[i])
continue;
if (!strcmp(pol->meshes[i]->name, "collision")) {
if (model->collider)
WARNING("multiple collision meshes");
else
model->collider = collider_create(pol->meshes[i]);
continue;
}
struct pol_material_group *mg = &pol->materials[pol->meshes[i]->material];
int m_off = material_offsets[pol->meshes[i]->material];
if (mg->nr_children == 0) {
@@ -474,10 +481,6 @@ struct model *model_load(struct archive *aar, const char *path)
}
}
// Collision detection is only required for maps.
if (strstr(path, "Map\\"))
model->collider = collider_create(pol);
pol_compute_aabb(pol, model->aabb);
free(material_offsets);
+16 -6
View File
@@ -549,19 +549,29 @@ float RE_instance_calc_height(struct RE_instance *instance, float x, float z)
return cnt ? total / cnt : 0.0f;
}
float RE_instance_calc_2d_detection_height(struct RE_instance *instance, float x, float z)
{
if (!instance || !instance->model->collider)
return false;
vec2 xz = { x, -z };
float h;
if (collider_height(instance->model->collider, xz, &h))
return h;
return 0.f;
}
bool RE_instance_calc_2d_detection(struct RE_instance *instance, float x0, float y0, float z0, float x1, float y1, float z1, float *x2, float *y2, float *z2, float radius)
{
if (!instance || !instance->model->collider)
return false;
vec3 p0 = { x0, y0, -z0 };
vec3 p1 = { x1, y1, -z1 };
vec3 p2;
vec2 p0 = { x0, -z0 };
vec2 p1 = { x1, -z1 };
vec2 p2;
if (!check_collision(instance->model->collider, p0, p1, radius, p2))
return false;
*x2 = p2[0];
*y2 = p2[1];
*z2 = -p2[2];
return true;
*z2 = -p2[1];
return collider_height(instance->model->collider, p2, y2);
}
bool RE_instance_set_debug_draw_shadow_volume(struct RE_instance *inst, bool draw)
+5 -1
View File
@@ -1825,7 +1825,11 @@ static int TapirEngine_CreatePlugin(void)
HLL_WARN_UNIMPLEMENTED(false, bool, TapirEngine, SetInstanceDrawParam, int plugin_number, int instance_number, int draw_param, int value);
//bool TapirEngine_GetInstanceDrawParam(int PluginNumber, int InstanceNumber, int DrawParam, int *Value);
HLL_WARN_UNIMPLEMENTED(0.0f, float, TapirEngine, CalcInstance2DDetectionHeight, int plugin_number, int instance_number, float x, float z);
static float TapirEngine_CalcInstance2DDetectionHeight(int plugin, int instance, float x, float z)
{
return RE_instance_calc_2d_detection_height(get_instance(plugin, instance), x, z);
}
static bool TapirEngine_CalcInstance2DDetection(int plugin, int instance, float x0, float y0, float z0, float x1, float y1, float z1, float *x2, float *y2, float *z2, float radius)
{
+1 -1
View File
@@ -1,3 +1,3 @@
[wrap-git]
url = https://github.com/recp/cglm.git
revision = v0.8.5
revision = v0.9.2