Files
armorpaint/base/sources/ts/raycast.ts
T

327 lines
8.1 KiB
TypeScript
Raw Normal View History

2024-11-10 22:11:29 +01:00
type ray_t = {
origin?: vec4_t;
dir?: vec4_t;
};
type plane_t = {
normal?: vec4_t;
constant?: f32;
};
let _raycast_vp_inv: mat4_t = mat4_identity();
let _raycast_p_inv: mat4_t = mat4_identity();
let _raycast_v_inv: mat4_t = mat4_identity();
function raycast_get_ray(input_x: f32, input_y: f32, camera: camera_object_t): ray_t {
let start: vec4_t = vec4_create();
let end: vec4_t = vec4_create();
// Get 3D point form screen coords
// Set two vectors with opposing z values
2025-03-11 19:49:42 +01:00
start.x = (input_x / sys_w()) * 2.0 - 1.0;
start.y = -((input_y / sys_h()) * 2.0 - 1.0);
2024-11-10 22:11:29 +01:00
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);
}
function raycast_box_intersect(transform: transform_t, input_x: f32, input_y: f32, camera: camera_object_t): vec4_t {
let ray: ray_t = raycast_get_ray(input_x, input_y, camera);
let t: transform_t = transform;
let c: vec4_t = vec4_create(transform_world_x(t), transform_world_y(t), transform_world_z(t));
let s: vec4_t = vec4_create(t.dim.x, t.dim.y, t.dim.z);
return ray_intersect_box(ray, c, s);
}
function raycast_closest_box_intersect(transforms: transform_t[], input_x: f32, input_y: f32, camera: camera_object_t): transform_t {
let intersects: transform_t[] = [];
// Get intersects
for (let i: i32 = 0; i < transforms.length; ++i) {
let t: transform_t = transforms[i];
let intersect: vec4_t = raycast_box_intersect(t, input_x, input_y, camera);
if (!vec4_isnan(intersect)) {
array_push(intersects, t);
}
}
// No intersects
if (intersects.length == 0) {
return null;
}
// Get closest intersect
let closest: transform_t = null;
let inf: f32 = 100000;
let min_dist: f32 = inf;
for (let i: i32 = 0; i < intersects.length; ++i) {
let t: transform_t = intersects[i];
let dist: f32 = vec4_dist(t.loc, camera.base.transform.loc);
if (dist < min_dist) {
min_dist = dist;
closest = t;
}
}
return closest;
}
function plane_create(): plane_t {
let raw: plane_t = {};
raw.normal = vec4_create(1.0, 0.0, 0.0);
raw.constant = 0.0;
return raw;
}
function raycast_plane_intersect(normal: vec4_t, a: vec4_t, input_x: f32, input_y: f32, camera: camera_object_t): vec4_t {
let ray: ray_t = raycast_get_ray(input_x, input_y, camera);
let plane: plane_t = plane_create();
plane_set(plane, normal, a);
return ray_intersect_plane(ray, plane);
}
function ray_create(origin: vec4_t, dir: vec4_t): ray_t {
let raw: ray_t = {};
raw.origin = origin;
raw.dir = dir;
return raw;
}
function ray_at(raw: ray_t, t: f32): vec4_t {
return vec4_add(vec4_mult(raw.dir, t), raw.origin);
}
function ray_dist_to_point(raw: ray_t, point: vec4_t): f32 {
let v1: vec4_t = vec4_create();
let dir_dist: f32 = vec4_dot(vec4_sub(point, raw.origin), raw.dir);
// Point behind the ray
if (dir_dist < 0) {
return vec4_dist(raw.origin, point);
}
raw.dir = vec4_mult(raw.dir, dir_dist);
raw.dir = vec4_add(raw.dir, raw.origin);
return vec4_dist(v1, point);
}
function ray_intersects_sphere(raw: ray_t, sphere_center: vec4_t, sphere_radius: f32): bool {
return ray_dist_to_point(raw, sphere_center) <= sphere_radius;
}
function ray_intersects_plane(raw: ray_t, plane: plane_t): bool {
// Check if the ray lies on the plane first
let dist_to_point: f32 = plane_dist_to_point(plane, raw.origin);
if (dist_to_point == 0) {
return true;
}
let denominator: f32 = 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;
}
function ray_dist_to_plane(raw: ray_t, plane: plane_t): f32 {
let denominator: f32 = 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;
}
let t: f32 = -(vec4_dot(raw.origin, plane.normal) + plane.constant) / denominator;
// Return if the ray never intersects the plane
return t >= 0 ? t : -1;
}
function ray_intersect_plane(raw: ray_t, plane: plane_t): vec4_t {
let t: f32 = ray_dist_to_plane(raw, plane);
if (t == -1) {
return vec4_nan();
}
return ray_at(raw, t);
}
function ray_intersect_box(raw: ray_t, center: vec4_t, dim: vec4_t): vec4_t {
// http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-box-intersection/
let tmin: f32;
let tmax: f32;
let tymin: f32;
let tymax: f32;
let tzmin: f32;
let tzmax: f32;
let half_x: f32 = dim.x / 2;
let half_y: f32 = dim.y / 2;
let half_z: f32 = dim.z / 2;
let box_min_x: f32 = center.x - half_x;
let box_min_y: f32 = center.y - half_y;
let box_min_z: f32 = center.z - half_z;
let box_max_x: f32 = center.x + half_x;
let box_max_y: f32 = center.y + half_y;
let box_max_z: f32 = center.z + half_z;
let invdirx: f32 = 1 / raw.dir.x;
let invdiry: f32 = 1 / raw.dir.y;
let invdirz: f32 = 1 / raw.dir.z;
let origin: vec4_t = 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);
}
function ray_intersect_triangle(raw: ray_t, a: vec4_t, b: vec4_t, c: vec4_t, cull_backface: bool): vec4_t {
// Compute the offset origin, edges, and normal
let diff: vec4_t = vec4_create();
let edge1: vec4_t = vec4_create();
let edge2: vec4_t = vec4_create();
let normal: vec4_t = vec4_create();
// from http://www.geometrictools.com/LibMathematics/Intersection/Wm5IntrRay3Triangle3.cpp
edge1 = vec4_sub(b, a);
edge2 = vec4_sub(c, a);
normal = vec4_cross(edge1, edge2);
let ddn: f32 = vec4_dot(raw.dir, normal);
let sign: i32;
if (ddn > 0) {
if (cull_backface) {
return vec4_nan();
}
sign = 1;
}
else if (ddn < 0) {
sign = -1;
ddn = -ddn;
}
else {
return vec4_nan();
}
diff = vec4_sub(raw.origin, a);
let ddqxe2: f32 = sign * vec4_dot(raw.dir, vec4_cross(diff, edge2));
// b1 < 0, no intersection
if (ddqxe2 < 0) {
return vec4_nan();
}
let dde1xq: f32 = 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.
let qdn: f32 = -sign * vec4_dot(diff, normal);
// t < 0, no intersection
if (qdn < 0) {
return vec4_nan();
}
// Ray intersects triangle.
return ray_at(raw, qdn / ddn);
}
function plane_dist_to_point(raw: plane_t, point: vec4_t): f32 {
return vec4_dot(raw.normal, point) + raw.constant;
}
function plane_set(raw: plane_t, normal: vec4_t, point: vec4_t): plane_t {
raw.normal = vec4_clone(normal);
raw.constant = -vec4_dot(point, raw.normal);
return raw;
}