Files
armorpaint/base/sources/iron_raycast.c
T
2026-08-21 22:35:30 +02:00

308 lines
7.7 KiB
C

#include "iron_raycast.h"
#include "iron_alloc.h"
#include "iron_math.h"
#include <math.h>
i32 sys_w(void);
i32 sys_h(void);
mat4_t _raycast_vp_inv = {0};
mat4_t _raycast_p_inv = {0};
mat4_t _raycast_v_inv = {0};
ray_t *raycast_get_ray(f32 input_x, f32 input_y, camera_object_t *camera) {
vec4_t start = (vec4_t){0.0, 0.0, 0.0, 0.0};
vec4_t end = (vec4_t){0.0, 0.0, 0.0, 0.0};
// Get 3D point form screen coords
// Set two vectors with opposing z values
start.x = (input_x / sys_w()) * 2.0 - 1.0;
start.y = -((input_y / sys_h()) * 2.0 - 1.0);
start.z = -1.0;
end.x = start.x;
end.y = start.y;
end.z = 1.0;
_raycast_p_inv = mat4_inv(camera->p);
_raycast_v_inv = mat4_inv(camera->v);
_raycast_vp_inv = mat4_mult_mat(_raycast_p_inv, _raycast_v_inv);
start = vec4_apply_proj(start, _raycast_vp_inv);
end = vec4_apply_proj(end, _raycast_vp_inv);
// Find direction from start to end
end = vec4_sub(end, start);
end = vec4_norm(end);
end.x *= camera->data->far_plane;
end.y *= camera->data->far_plane;
end.z *= camera->data->far_plane;
return ray_create(start, end);
}
vec4_t raycast_box_intersect(transform_t *transform, f32 input_x, f32 input_y, camera_object_t *camera) {
ray_t *ray = raycast_get_ray(input_x, input_y, camera);
vec4_t c = (vec4_t){transform_world_x(transform), transform_world_y(transform), transform_world_z(transform), 0.0};
vec4_t s = (vec4_t){transform->dim.x, transform->dim.y, transform->dim.z, 0.0};
return ray_intersect_box(ray, c, s);
}
transform_t *raycast_closest_box_intersect(any_array_t *transforms, f32 input_x, f32 input_y, camera_object_t *camera) {
any_array_t *intersects = any_array_create(0);
// Get intersects
for (i32 i = 0; i < (i32)transforms->length; ++i) {
transform_t *t = (transform_t *)transforms->buffer[i];
vec4_t intersect = raycast_box_intersect(t, input_x, input_y, camera);
if (!vec4_isnan(intersect)) {
any_array_push(intersects, t);
}
}
// No intersects
if (intersects->length == 0) {
return NULL;
}
// Get closest intersect
transform_t *closest = NULL;
f32 min_dist = 100000.0;
for (i32 i = 0; i < (i32)intersects->length; ++i) {
transform_t *t = (transform_t *)intersects->buffer[i];
f32 dist = vec4_dist(t->loc, camera->base->transform->loc);
if (dist < min_dist) {
min_dist = dist;
closest = t;
}
}
return closest;
}
vec4_t raycast_plane_intersect(vec4_t normal, vec4_t a, f32 input_x, f32 input_y, camera_object_t *camera) {
ray_t *ray = raycast_get_ray(input_x, input_y, camera);
plane_t plane = plane_create();
plane_set(&plane, normal, a);
return ray_intersect_plane(ray, &plane);
}
ray_t *ray_create(vec4_t origin, vec4_t dir) {
ray_t *raw = calloc(1, sizeof(ray_t));
raw->origin = origin;
raw->dir = dir;
return raw;
}
vec4_t ray_at(ray_t *raw, f32 t) {
return vec4_add(vec4_mult(raw->dir, t), raw->origin);
}
f32 ray_dist_to_point(ray_t *raw, vec4_t point) {
vec4_t dir = vec4_norm(raw->dir);
f32 t = vec4_dot(vec4_sub(point, raw->origin), dir);
if (t < 0.0f)
t = 0.0f;
vec4_t closest = vec4_add(raw->origin, vec4_mult(dir, t));
return vec4_dist(closest, point);
}
bool ray_intersects_sphere(ray_t *raw, vec4_t sphere_center, f32 sphere_radius) {
return ray_dist_to_point(raw, sphere_center) <= sphere_radius;
}
bool ray_intersects_plane(ray_t *raw, plane_t *plane) {
// Check if the ray lies on the plane first
f32 dist_to_point = plane_dist_to_point(plane, raw->origin);
if (dist_to_point == 0) {
return true;
}
f32 denominator = vec4_dot(plane->normal, raw->dir);
if (denominator * dist_to_point < 0) {
return true;
}
// Ray origin is behind the plane (and is pointing behind it)
return false;
}
f32 ray_dist_to_plane(ray_t *raw, plane_t *plane) {
f32 denominator = vec4_dot(plane->normal, raw->dir);
if (denominator == 0) {
// Line is coplanar, return origin
if (plane_dist_to_point(plane, raw->origin) == 0) {
return 0;
}
return -1;
}
f32 t = -(vec4_dot(raw->origin, plane->normal) + plane->constant) / denominator;
// Return if the ray never intersects the plane
return t >= 0 ? t : -1;
}
vec4_t ray_intersect_plane(ray_t *raw, plane_t *plane) {
f32 t = ray_dist_to_plane(raw, plane);
if (t == -1) {
return vec4_nan();
}
return ray_at(raw, t);
}
vec4_t ray_intersect_box(ray_t *raw, vec4_t center, vec4_t dim) {
// http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-box-intersection/
f32 tmin;
f32 tmax;
f32 tymin;
f32 tymax;
f32 tzmin;
f32 tzmax;
f32 half_x = dim.x / 2;
f32 half_y = dim.y / 2;
f32 half_z = dim.z / 2;
f32 box_min_x = center.x - half_x;
f32 box_min_y = center.y - half_y;
f32 box_min_z = center.z - half_z;
f32 box_max_x = center.x + half_x;
f32 box_max_y = center.y + half_y;
f32 box_max_z = center.z + half_z;
f32 invdirx = 1.0 / raw->dir.x;
f32 invdiry = 1.0 / raw->dir.y;
f32 invdirz = 1.0 / raw->dir.z;
vec4_t origin = raw->origin;
if (invdirx >= 0) {
tmin = (box_min_x - origin.x) * invdirx;
tmax = (box_max_x - origin.x) * invdirx;
}
else {
tmin = (box_max_x - origin.x) * invdirx;
tmax = (box_min_x - origin.x) * invdirx;
}
if (invdiry >= 0) {
tymin = (box_min_y - origin.y) * invdiry;
tymax = (box_max_y - origin.y) * invdiry;
}
else {
tymin = (box_max_y - origin.y) * invdiry;
tymax = (box_min_y - origin.y) * invdiry;
}
if ((tmin > tymax) || (tymin > tmax)) {
return vec4_nan();
}
// These lines also handle the case where tmin or tmax is nan
// (result of 0 * inf). x != x returns true if x is nan
if (tymin > tmin || tmin != tmin) {
tmin = tymin;
}
if (tymax < tmax || tmax != tmax) {
tmax = tymax;
}
if (invdirz >= 0) {
tzmin = (box_min_z - origin.z) * invdirz;
tzmax = (box_max_z - origin.z) * invdirz;
}
else {
tzmin = (box_max_z - origin.z) * invdirz;
tzmax = (box_min_z - origin.z) * invdirz;
}
if ((tmin > tzmax) || (tzmin > tmax)) {
return vec4_nan();
}
if (tzmin > tmin || tmin != tmin) {
tmin = tzmin;
}
if (tzmax < tmax || tmax != tmax) {
tmax = tzmax;
}
// Return point closest to the ray (positive side)
if (tmax < 0) {
return vec4_nan();
}
return ray_at(raw, tmin >= 0 ? tmin : tmax);
}
vec4_t ray_intersect_triangle(ray_t *raw, vec4_t a, vec4_t b, vec4_t c, bool cull_backface) {
// Compute the offset origin, edges, and normal
// from http://www.geometrictools.com/LibMathematics/Intersection/Wm5IntrRay3Triangle3.cpp
vec4_t edge1 = vec4_sub(b, a);
vec4_t edge2 = vec4_sub(c, a);
vec4_t normal = vec4_cross(edge1, edge2);
f32 ddn = vec4_dot(raw->dir, normal);
i32 sign;
if (ddn > 0) {
if (cull_backface) {
return vec4_nan();
}
sign = 1;
}
else if (ddn < 0) {
sign = -1;
ddn = -ddn;
}
else {
return vec4_nan();
}
vec4_t diff = vec4_sub(raw->origin, a);
f32 ddqxe2 = sign * vec4_dot(raw->dir, vec4_cross(diff, edge2));
// b1 < 0, no intersection
if (ddqxe2 < 0) {
return vec4_nan();
}
f32 dde1xq = sign * vec4_dot(raw->dir, vec4_cross(edge1, diff));
// b2 < 0, no intersection
if (dde1xq < 0) {
return vec4_nan();
}
// b1+b2 > 1, no intersection
if (ddqxe2 + dde1xq > ddn) {
return vec4_nan();
}
// Line intersects triangle, check if ray does.
f32 qdn = -sign * vec4_dot(diff, normal);
// t < 0, no intersection
if (qdn < 0) {
return vec4_nan();
}
// Ray intersects triangle.
return ray_at(raw, qdn / ddn);
}
plane_t plane_create(void) {
plane_t raw;
raw.normal = (vec4_t){1.0, 0.0, 0.0, 0.0};
raw.constant = 0.0;
return raw;
}
f32 plane_dist_to_point(plane_t *raw, vec4_t point) {
return vec4_dot(raw->normal, point) + raw->constant;
}
plane_t plane_set(plane_t *raw, vec4_t normal, vec4_t point) {
raw->normal = normal;
raw->constant = -vec4_dot(point, raw->normal);
return *raw;
}