diff --git a/include/reign.h b/include/reign.h
index 13cbaa9..4c0e154 100644
--- a/include/reign.h
+++ b/include/reign.h
@@ -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);
diff --git a/meson.build b/meson.build
index a59f42e..70c13fa 100644
--- a/meson.build
+++ b/meson.build
@@ -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)
diff --git a/src/3d/3d_internal.h b/src/3d/3d_internal.h
index 7a90dda..f0d8192 100644
--- a/src/3d/3d_internal.h
+++ b/src/3d/3d_internal.h
@@ -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 */
diff --git a/src/3d/collision.c b/src/3d/collision.c
index 5443ef2..91ecfd0 100644
--- a/src/3d/collision.c
+++ b/src/3d/collision.c
@@ -14,6 +14,7 @@
* along with this program; if not, see .
*/
+#include
#include
#include
@@ -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;
}
diff --git a/src/3d/model.c b/src/3d/model.c
index 46c4c7a..48d90ef 100644
--- a/src/3d/model.c
+++ b/src/3d/model.c
@@ -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);
diff --git a/src/3d/reign.c b/src/3d/reign.c
index 7a953d3..94fe5f8 100644
--- a/src/3d/reign.c
+++ b/src/3d/reign.c
@@ -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)
diff --git a/src/hll/ReignEngine.c b/src/hll/ReignEngine.c
index 4f93a35..b765ff4 100644
--- a/src/hll/ReignEngine.c
+++ b/src/hll/ReignEngine.c
@@ -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)
{
diff --git a/subprojects/cglm.wrap b/subprojects/cglm.wrap
index 964ff79..77212e5 100644
--- a/subprojects/cglm.wrap
+++ b/subprojects/cglm.wrap
@@ -1,3 +1,3 @@
[wrap-git]
url = https://github.com/recp/cglm.git
-revision = v0.8.5
+revision = v0.9.2