Packages

Spatial partitioning data structures for efficient 3D queries: octrees, colliders, and spatial algorithms

Current section

Files

Jump to
spatial src spatial@collider.erl
Raw

src/spatial@collider.erl

-module(spatial@collider).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/spatial/collider.gleam").
-export([box/2, box_from_center/2, sphere/2, capsule/3, cylinder/3, center/1, size/1, from_rotation/4, contains_point/2, intersects/2]).
-export_type([internal_collider/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Collision volumes for spatial queries and collision detection.\n"
"\n"
" Provides Box, Sphere, Capsule, and Cylinder colliders with intersection tests\n"
" and rotation-aware collision volume computation.\n"
).
-type internal_collider() :: {box,
vec@vec3:vec3(float()),
vec@vec3:vec3(float())} |
{sphere, vec@vec3:vec3(float()), float()} |
{capsule, vec@vec3:vec3(float()), vec@vec3:vec3(float()), float()} |
{cylinder, vec@vec3:vec3(float()), float(), float()}.
-file("src/spatial/collider.gleam", 38).
?DOC(
" Create a box collider from min and max points.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let bounds = collider.box(\n"
" min: vec3.Vec3(-1.0, -1.0, -1.0),\n"
" max: vec3.Vec3(1.0, 1.0, 1.0),\n"
" )\n"
" ```\n"
).
-spec box(vec@vec3:vec3(float()), vec@vec3:vec3(float())) -> internal_collider().
box(Min, Max) ->
{box, Min, Max}.
-file("src/spatial/collider.gleam", 51).
?DOC(
" Create a box collider from center and half-extents.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let bounds = collider.box_from_center(\n"
" center: vec3.Vec3(0.0, 5.0, 0.0),\n"
" half_extents: vec3.Vec3(2.0, 1.0, 2.0),\n"
" )\n"
" ```\n"
).
-spec box_from_center(vec@vec3:vec3(float()), vec@vec3:vec3(float())) -> internal_collider().
box_from_center(Center, Half_extents) ->
{box,
vec@vec3f:subtract(Center, Half_extents),
vec@vec3f:add(Center, Half_extents)}.
-file("src/spatial/collider.gleam", 70).
?DOC(
" Create a sphere collider from center and radius.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let bounds = collider.sphere(\n"
" center: vec3.Vec3(0.0, 0.0, 0.0),\n"
" radius: 2.5,\n"
" )\n"
" ```\n"
).
-spec sphere(vec@vec3:vec3(float()), float()) -> internal_collider().
sphere(Center, Radius) ->
{sphere, Center, Radius}.
-file("src/spatial/collider.gleam", 86).
?DOC(
" Create a capsule collider from start point, end point, and radius.\n"
"\n"
" A capsule is a line segment with a radius - perfect for character controllers.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let character = collider.capsule(\n"
" start: vec3.Vec3(0.0, 0.0, 0.0),\n"
" end: vec3.Vec3(0.0, 2.0, 0.0),\n"
" radius: 0.5,\n"
" )\n"
" ```\n"
).
-spec capsule(vec@vec3:vec3(float()), vec@vec3:vec3(float()), float()) -> internal_collider().
capsule(Start, End, Radius) ->
{capsule, Start, End, Radius}.
-file("src/spatial/collider.gleam", 106).
?DOC(
" Create a cylinder collider from center, radius, and height.\n"
"\n"
" The cylinder is aligned along the Y axis in local space.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let pillar = collider.cylinder(\n"
" center: vec3.Vec3(0.0, 5.0, 0.0),\n"
" radius: 1.0,\n"
" height: 10.0,\n"
" )\n"
" ```\n"
).
-spec cylinder(vec@vec3:vec3(float()), float(), float()) -> internal_collider().
cylinder(Center, Radius, Height) ->
{cylinder, Center, Radius, Height}.
-file("src/spatial/collider.gleam", 292).
?DOC(" Get the center of a collider.\n").
-spec center(internal_collider()) -> vec@vec3:vec3(float()).
center(Collider) ->
case Collider of
{box, Min, Max} ->
{vec3,
(erlang:element(2, Min) + erlang:element(2, Max)) / 2.0,
(erlang:element(3, Min) + erlang:element(3, Max)) / 2.0,
(erlang:element(4, Min) + erlang:element(4, Max)) / 2.0};
{sphere, Center, _} ->
Center;
{capsule, Start, End, _} ->
{vec3,
(erlang:element(2, Start) + erlang:element(2, End)) / 2.0,
(erlang:element(3, Start) + erlang:element(3, End)) / 2.0,
(erlang:element(4, Start) + erlang:element(4, End)) / 2.0};
{cylinder, Center@1, _, _} ->
Center@1
end.
-file("src/spatial/collider.gleam", 314).
?DOC(
" Get the size (dimensions) of a collider.\n"
"\n"
" Returns the bounding box dimensions for all collider types.\n"
).
-spec size(internal_collider()) -> vec@vec3:vec3(float()).
size(Collider) ->
case Collider of
{box, Min, Max} ->
vec@vec3f:subtract(Max, Min);
{sphere, _, Radius} ->
Diameter = Radius * 2.0,
{vec3, Diameter, Diameter, Diameter};
{capsule, Start, End, Radius@1} ->
Length = vec@vec3f:distance(Start, End),
Diameter@1 = Radius@1 * 2.0,
{vec3, Diameter@1, Length + Diameter@1, Diameter@1};
{cylinder, _, Radius@2, Height} ->
Diameter@2 = Radius@2 * 2.0,
{vec3, Diameter@2, Height, Diameter@2}
end.
-file("src/spatial/collider.gleam", 445).
-spec transform_point(
vec@vec3:vec3(float()),
vec@vec3:vec3(float()),
quaternion:quaternion(),
vec@vec3:vec3(float())
) -> vec@vec3:vec3(float()).
transform_point(Point, Position, Rotation, Scale) ->
Scaled = {vec3,
erlang:element(2, Point) * erlang:element(2, Scale),
erlang:element(3, Point) * erlang:element(3, Scale),
erlang:element(4, Point) * erlang:element(4, Scale)},
Rotated = quaternion:rotate(Rotation, Scaled),
vec@vec3f:add(Rotated, Position).
-file("src/spatial/collider.gleam", 362).
?DOC(
" Create a new collider from a local-space collider with position, rotation, and scale.\n"
"\n"
" For Box: Computes a new axis-aligned bounding box that encompasses\n"
" all 8 corners after rotation, translation, and scaling.\n"
"\n"
" For Sphere: Transforms the center and scales the radius by the maximum\n"
" scale component (since spheres remain spherical under uniform scaling).\n"
"\n"
" **Time Complexity**: O(1) - transforms a constant number of points (8 for Box).\n"
"\n"
" ## Example\n"
" ```gleam\n"
" // Local space box\n"
" let local_box = collider.box(\n"
" min: vec3.Vec3(-1.0, -1.0, -1.0),\n"
" max: vec3.Vec3(1.0, 1.0, 1.0),\n"
" )\n"
"\n"
" // Rotated 45 degrees around Y axis\n"
" let rotation = q.from_axis_angle(vec3.Vec3(0.0, 1.0, 0.0), 0.785)\n"
"\n"
" // Get world-space collider\n"
" let world_box = collider.from_rotation(\n"
" local_box,\n"
" position: vec3.Vec3(5.0, 0.0, 0.0),\n"
" rotation: rotation,\n"
" scale: vec3.Vec3(1.0, 1.0, 1.0),\n"
" )\n"
" ```\n"
).
-spec from_rotation(
internal_collider(),
vec@vec3:vec3(float()),
quaternion:quaternion(),
vec@vec3:vec3(float())
) -> internal_collider().
from_rotation(Collider, Position, Rotation, Scale) ->
case Collider of
{box, Min, Max} ->
Corners = [{vec3,
erlang:element(2, Min),
erlang:element(3, Min),
erlang:element(4, Min)},
{vec3,
erlang:element(2, Max),
erlang:element(3, Min),
erlang:element(4, Min)},
{vec3,
erlang:element(2, Min),
erlang:element(3, Max),
erlang:element(4, Min)},
{vec3,
erlang:element(2, Max),
erlang:element(3, Max),
erlang:element(4, Min)},
{vec3,
erlang:element(2, Min),
erlang:element(3, Min),
erlang:element(4, Max)},
{vec3,
erlang:element(2, Max),
erlang:element(3, Min),
erlang:element(4, Max)},
{vec3,
erlang:element(2, Min),
erlang:element(3, Max),
erlang:element(4, Max)},
{vec3,
erlang:element(2, Max),
erlang:element(3, Max),
erlang:element(4, Max)}],
Transformed_corners = gleam@list:map(
Corners,
fun(Corner) ->
transform_point(Corner, Position, Rotation, Scale)
end
),
Init_min = {vec3, 1.0e10, 1.0e10, 1.0e10},
Init_max = {vec3, -1.0e10, -1.0e10, -1.0e10},
{New_min, New_max} = gleam@list:fold(
Transformed_corners,
{Init_min, Init_max},
fun(Acc, Point) ->
{Current_min, Current_max} = Acc,
{{vec3,
gleam@float:min(
erlang:element(2, Current_min),
erlang:element(2, Point)
),
gleam@float:min(
erlang:element(3, Current_min),
erlang:element(3, Point)
),
gleam@float:min(
erlang:element(4, Current_min),
erlang:element(4, Point)
)},
{vec3,
gleam@float:max(
erlang:element(2, Current_max),
erlang:element(2, Point)
),
gleam@float:max(
erlang:element(3, Current_max),
erlang:element(3, Point)
),
gleam@float:max(
erlang:element(4, Current_max),
erlang:element(4, Point)
)}}
end
),
{box, New_min, New_max};
{sphere, Center, Radius} ->
New_center = transform_point(Center, Position, Rotation, Scale),
Max_scale = gleam@float:max(
erlang:element(2, Scale),
gleam@float:max(
erlang:element(3, Scale),
erlang:element(4, Scale)
)
),
New_radius = Radius * Max_scale,
{sphere, New_center, New_radius};
{capsule, Start, End, Radius@1} ->
New_start = transform_point(Start, Position, Rotation, Scale),
New_end = transform_point(End, Position, Rotation, Scale),
Max_scale@1 = gleam@float:max(
erlang:element(2, Scale),
gleam@float:max(
erlang:element(3, Scale),
erlang:element(4, Scale)
)
),
New_radius@1 = Radius@1 * Max_scale@1,
{capsule, New_start, New_end, New_radius@1};
{cylinder, Center@1, Radius@2, Height} ->
New_center@1 = transform_point(Center@1, Position, Rotation, Scale),
Max_radial_scale = gleam@float:max(
erlang:element(2, Scale),
erlang:element(4, Scale)
),
New_radius@2 = Radius@2 * Max_radial_scale,
New_height = Height * erlang:element(3, Scale),
{cylinder, New_center@1, New_radius@2, New_height}
end.
-file("src/spatial/collider.gleam", 464).
-spec point_to_line_segment_distance(
vec@vec3:vec3(float()),
vec@vec3:vec3(float()),
vec@vec3:vec3(float())
) -> float().
point_to_line_segment_distance(Point, Start, End) ->
Line_vec = vec@vec3f:subtract(End, Start),
Point_vec = vec@vec3f:subtract(Point, Start),
Line_len_sq = vec@vec3f:dot(Line_vec, Line_vec),
case Line_len_sq < 0.0001 of
true ->
spatial_ffi:distance(Point, Start);
false ->
T = gleam@float:clamp(case Line_len_sq of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> vec@vec3f:dot(Point_vec, Line_vec) / Gleam@denominator
end, +0.0, 1.0),
Projection = vec@vec3f:add(Start, vec@vec3f:scale(Line_vec, T)),
spatial_ffi:distance(Point, Projection)
end.
-file("src/spatial/collider.gleam", 119).
?DOC(
" Check if a point is inside a collider.\n"
"\n"
" Works for Box, Sphere, Capsule, and Cylinder colliders.\n"
"\n"
" **Time Complexity**: O(1) - constant time geometric calculation.\n"
).
-spec contains_point(internal_collider(), vec@vec3:vec3(float())) -> boolean().
contains_point(Collider, Point) ->
case Collider of
{box, Min, Max} ->
spatial_ffi:box_contains_point(Min, Max, Point);
{sphere, Center, Radius} ->
spatial_ffi:distance_squared(Center, Point) =< (Radius * Radius);
{capsule, Start, End, Radius@1} ->
Distance = point_to_line_segment_distance(Point, Start, End),
Distance =< Radius@1;
{cylinder, Center@1, Radius@2, Height} ->
Half_height = Height / 2.0,
Dx = erlang:element(2, Point) - erlang:element(2, Center@1),
Dz = erlang:element(4, Point) - erlang:element(4, Center@1),
Radial_dist_sq = (Dx * Dx) + (Dz * Dz),
Y_in_range = (erlang:element(3, Point) >= (erlang:element(
3,
Center@1
)
- Half_height))
andalso (erlang:element(3, Point) =< (erlang:element(3, Center@1) + Half_height)),
(Radial_dist_sq =< (Radius@2 * Radius@2)) andalso Y_in_range
end.
-file("src/spatial/collider.gleam", 484).
-spec line_segment_to_line_segment_distance(
vec@vec3:vec3(float()),
vec@vec3:vec3(float()),
vec@vec3:vec3(float()),
vec@vec3:vec3(float())
) -> float().
line_segment_to_line_segment_distance(Start_a, End_a, Start_b, End_b) ->
D1 = vec@vec3f:subtract(End_a, Start_a),
D2 = vec@vec3f:subtract(End_b, Start_b),
R = vec@vec3f:subtract(Start_a, Start_b),
A = vec@vec3f:dot(D1, D1),
E = vec@vec3f:dot(D2, D2),
F = vec@vec3f:dot(D2, R),
case (A < 0.0001) andalso (E < 0.0001) of
true ->
spatial_ffi:distance(Start_a, Start_b);
false ->
case A < 0.0001 of
true ->
T = gleam@float:clamp(case E of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> F / Gleam@denominator
end, +0.0, 1.0),
Point_on_b = vec@vec3f:add(Start_b, vec@vec3f:scale(D2, T)),
spatial_ffi:distance(Start_a, Point_on_b);
false ->
case E < 0.0001 of
true ->
S = gleam@float:clamp(case A of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@1 -> vec@vec3f:dot(D1, R)
/ Gleam@denominator@1
end, +0.0, 1.0),
Point_on_a = vec@vec3f:add(
Start_a,
vec@vec3f:scale(D1, S)
),
spatial_ffi:distance(Point_on_a, Start_b);
false ->
C = vec@vec3f:dot(D1, R),
B = vec@vec3f:dot(D1, D2),
Denom = (A * E) - (B * B),
S@1 = case Denom < 0.0001 of
true ->
+0.0;
false ->
gleam@float:clamp(case Denom of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@2 -> ((B * F) - (C
* E))
/ Gleam@denominator@2
end, +0.0, 1.0)
end,
T@1 = case E of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@3 -> ((B * S@1) + F) / Gleam@denominator@3
end,
T_clamped = gleam@float:clamp(T@1, +0.0, 1.0),
Point_on_a@1 = vec@vec3f:add(
Start_a,
vec@vec3f:scale(D1, S@1)
),
Point_on_b@1 = vec@vec3f:add(
Start_b,
vec@vec3f:scale(D2, T_clamped)
),
spatial_ffi:distance(Point_on_a@1, Point_on_b@1)
end
end
end.
-file("src/spatial/collider.gleam", 538).
-spec line_segment_intersects_box(
vec@vec3:vec3(float()),
vec@vec3:vec3(float()),
vec@vec3:vec3(float()),
vec@vec3:vec3(float())
) -> boolean().
line_segment_intersects_box(Start, End, Min, Max) ->
Dir = vec@vec3f:subtract(End, Start),
T_min = +0.0,
T_max = 1.0,
Inv_dir_x = case gleam@float:absolute_value(erlang:element(2, Dir)) < 0.0001 of
true ->
1.0e10;
false ->
case erlang:element(2, Dir) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> 1.0 / Gleam@denominator
end
end,
Tx1 = (erlang:element(2, Min) - erlang:element(2, Start)) * Inv_dir_x,
Tx2 = (erlang:element(2, Max) - erlang:element(2, Start)) * Inv_dir_x,
T_min@1 = gleam@float:max(T_min, gleam@float:min(Tx1, Tx2)),
T_max@1 = gleam@float:min(T_max, gleam@float:max(Tx1, Tx2)),
case T_max@1 < T_min@1 of
true ->
false;
false ->
Inv_dir_y = case gleam@float:absolute_value(erlang:element(3, Dir))
< 0.0001 of
true ->
1.0e10;
false ->
case erlang:element(3, Dir) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@1 -> 1.0 / Gleam@denominator@1
end
end,
Ty1 = (erlang:element(3, Min) - erlang:element(3, Start)) * Inv_dir_y,
Ty2 = (erlang:element(3, Max) - erlang:element(3, Start)) * Inv_dir_y,
T_min@2 = gleam@float:max(T_min@1, gleam@float:min(Ty1, Ty2)),
T_max@2 = gleam@float:min(T_max@1, gleam@float:max(Ty1, Ty2)),
case T_max@2 < T_min@2 of
true ->
false;
false ->
Inv_dir_z = case gleam@float:absolute_value(
erlang:element(4, Dir)
)
< 0.0001 of
true ->
1.0e10;
false ->
case erlang:element(4, Dir) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@2 -> 1.0 / Gleam@denominator@2
end
end,
Tz1 = (erlang:element(4, Min) - erlang:element(4, Start)) * Inv_dir_z,
Tz2 = (erlang:element(4, Max) - erlang:element(4, Start)) * Inv_dir_z,
T_min@3 = gleam@float:max(
T_min@2,
gleam@float:min(Tz1, Tz2)
),
T_max@3 = gleam@float:min(
T_max@2,
gleam@float:max(Tz1, Tz2)
),
T_max@3 >= T_min@3
end
end.
-file("src/spatial/collider.gleam", 149).
?DOC(
" Check if two colliders intersect.\n"
"\n"
" Handles all collider type combinations.\n"
"\n"
" **Time Complexity**: O(1) - constant time geometric calculation.\n"
).
-spec intersects(internal_collider(), internal_collider()) -> boolean().
intersects(A, B) ->
case {A, B} of
{{box, Min_a, Max_a}, {box, Min_b, Max_b}} ->
spatial_ffi:box_intersects(Min_a, Max_a, Min_b, Max_b);
{{sphere, Center_a, Radius_a}, {sphere, Center_b, Radius_b}} ->
spatial_ffi:sphere_intersects(
Center_a,
Radius_a,
Center_b,
Radius_b
);
{{box, Min, Max}, {sphere, Center, Radius}} ->
Closest_x = gleam@float:clamp(
erlang:element(2, Center),
erlang:element(2, Min),
erlang:element(2, Max)
),
Closest_y = gleam@float:clamp(
erlang:element(3, Center),
erlang:element(3, Min),
erlang:element(3, Max)
),
Closest_z = gleam@float:clamp(
erlang:element(4, Center),
erlang:element(4, Min),
erlang:element(4, Max)
),
Closest = {vec3, Closest_x, Closest_y, Closest_z},
spatial_ffi:distance_squared(Center, Closest) =< (Radius * Radius);
{{sphere, Center, Radius}, {box, Min, Max}} ->
Closest_x = gleam@float:clamp(
erlang:element(2, Center),
erlang:element(2, Min),
erlang:element(2, Max)
),
Closest_y = gleam@float:clamp(
erlang:element(3, Center),
erlang:element(3, Min),
erlang:element(3, Max)
),
Closest_z = gleam@float:clamp(
erlang:element(4, Center),
erlang:element(4, Min),
erlang:element(4, Max)
),
Closest = {vec3, Closest_x, Closest_y, Closest_z},
spatial_ffi:distance_squared(Center, Closest) =< (Radius * Radius);
{{capsule, Start_a, End_a, Radius_a@1},
{capsule, Start_b, End_b, Radius_b@1}} ->
Distance = line_segment_to_line_segment_distance(
Start_a,
End_a,
Start_b,
End_b
),
Distance =< (Radius_a@1 + Radius_b@1);
{{sphere, Center@1, Sphere_radius}, {capsule, Start, End, Cap_radius}} ->
Distance@1 = point_to_line_segment_distance(Center@1, Start, End),
Distance@1 =< (Sphere_radius + Cap_radius);
{{capsule, Start, End, Cap_radius}, {sphere, Center@1, Sphere_radius}} ->
Distance@1 = point_to_line_segment_distance(Center@1, Start, End),
Distance@1 =< (Sphere_radius + Cap_radius);
{{box, Min@1, Max@1}, {capsule, Start@1, End@1, Radius@1}} ->
Expanded_min = {vec3,
erlang:element(2, Min@1) - Radius@1,
erlang:element(3, Min@1) - Radius@1,
erlang:element(4, Min@1) - Radius@1},
Expanded_max = {vec3,
erlang:element(2, Max@1) + Radius@1,
erlang:element(3, Max@1) + Radius@1,
erlang:element(4, Max@1) + Radius@1},
line_segment_intersects_box(
Start@1,
End@1,
Expanded_min,
Expanded_max
);
{{capsule, Start@1, End@1, Radius@1}, {box, Min@1, Max@1}} ->
Expanded_min = {vec3,
erlang:element(2, Min@1) - Radius@1,
erlang:element(3, Min@1) - Radius@1,
erlang:element(4, Min@1) - Radius@1},
Expanded_max = {vec3,
erlang:element(2, Max@1) + Radius@1,
erlang:element(3, Max@1) + Radius@1,
erlang:element(4, Max@1) + Radius@1},
line_segment_intersects_box(
Start@1,
End@1,
Expanded_min,
Expanded_max
);
{{cylinder, Cyl_center, Cyl_radius, Height},
{sphere, Sphere_center, Sphere_radius@1}} ->
Half_height = Height / 2.0,
Dx = erlang:element(2, Sphere_center) - erlang:element(
2,
Cyl_center
),
Dz = erlang:element(4, Sphere_center) - erlang:element(
4,
Cyl_center
),
Radial_dist_sq = (Dx * Dx) + (Dz * Dz),
Radial_dist = case gleam@float:square_root(Radial_dist_sq) of
{ok, D} ->
D;
{error, _} ->
+0.0
end,
Y_clamped = gleam@float:clamp(
erlang:element(3, Sphere_center),
erlang:element(3, Cyl_center) - Half_height,
erlang:element(3, Cyl_center) + Half_height
),
Closest_y_dist = gleam@float:absolute_value(
erlang:element(3, Sphere_center) - Y_clamped
),
(Radial_dist =< (Cyl_radius + Sphere_radius@1)) andalso (Closest_y_dist
=< Sphere_radius@1);
{{sphere, Sphere_center, Sphere_radius@1},
{cylinder, Cyl_center, Cyl_radius, Height}} ->
Half_height = Height / 2.0,
Dx = erlang:element(2, Sphere_center) - erlang:element(
2,
Cyl_center
),
Dz = erlang:element(4, Sphere_center) - erlang:element(
4,
Cyl_center
),
Radial_dist_sq = (Dx * Dx) + (Dz * Dz),
Radial_dist = case gleam@float:square_root(Radial_dist_sq) of
{ok, D} ->
D;
{error, _} ->
+0.0
end,
Y_clamped = gleam@float:clamp(
erlang:element(3, Sphere_center),
erlang:element(3, Cyl_center) - Half_height,
erlang:element(3, Cyl_center) + Half_height
),
Closest_y_dist = gleam@float:absolute_value(
erlang:element(3, Sphere_center) - Y_clamped
),
(Radial_dist =< (Cyl_radius + Sphere_radius@1)) andalso (Closest_y_dist
=< Sphere_radius@1);
{{box, Min@2, Max@2}, {cylinder, Center@2, Radius@2, Height@1}} ->
Half_height@1 = Height@1 / 2.0,
Cyl_min = {vec3,
erlang:element(2, Center@2) - Radius@2,
erlang:element(3, Center@2) - Half_height@1,
erlang:element(4, Center@2) - Radius@2},
Cyl_max = {vec3,
erlang:element(2, Center@2) + Radius@2,
erlang:element(3, Center@2) + Half_height@1,
erlang:element(4, Center@2) + Radius@2},
(((((erlang:element(2, Cyl_min) =< erlang:element(2, Max@2)) andalso (erlang:element(
2,
Cyl_max
)
>= erlang:element(2, Min@2)))
andalso (erlang:element(3, Cyl_min) =< erlang:element(3, Max@2)))
andalso (erlang:element(3, Cyl_max) >= erlang:element(3, Min@2)))
andalso (erlang:element(4, Cyl_min) =< erlang:element(4, Max@2)))
andalso (erlang:element(4, Cyl_max) >= erlang:element(4, Min@2));
{{cylinder, Center@2, Radius@2, Height@1}, {box, Min@2, Max@2}} ->
Half_height@1 = Height@1 / 2.0,
Cyl_min = {vec3,
erlang:element(2, Center@2) - Radius@2,
erlang:element(3, Center@2) - Half_height@1,
erlang:element(4, Center@2) - Radius@2},
Cyl_max = {vec3,
erlang:element(2, Center@2) + Radius@2,
erlang:element(3, Center@2) + Half_height@1,
erlang:element(4, Center@2) + Radius@2},
(((((erlang:element(2, Cyl_min) =< erlang:element(2, Max@2)) andalso (erlang:element(
2,
Cyl_max
)
>= erlang:element(2, Min@2)))
andalso (erlang:element(3, Cyl_min) =< erlang:element(3, Max@2)))
andalso (erlang:element(3, Cyl_max) >= erlang:element(3, Min@2)))
andalso (erlang:element(4, Cyl_min) =< erlang:element(4, Max@2)))
andalso (erlang:element(4, Cyl_max) >= erlang:element(4, Min@2));
{{capsule, Start@2, End@2, Cap_radius@1},
{cylinder, Cyl_center@1, Cyl_radius@1, Height@2}} ->
Half_height@2 = Height@2 / 2.0,
Min_dist = point_to_line_segment_distance(
Cyl_center@1,
Start@2,
End@2
),
Y_in_range = ((erlang:element(3, Start@2) =< (erlang:element(
3,
Cyl_center@1
)
+ Half_height@2))
andalso (erlang:element(3, Start@2) >= (erlang:element(
3,
Cyl_center@1
)
- Half_height@2)))
orelse ((erlang:element(3, End@2) =< (erlang:element(
3,
Cyl_center@1
)
+ Half_height@2))
andalso (erlang:element(3, End@2) >= (erlang:element(
3,
Cyl_center@1
)
- Half_height@2))),
(Min_dist =< (Cyl_radius@1 + Cap_radius@1)) andalso Y_in_range;
{{cylinder, Cyl_center@1, Cyl_radius@1, Height@2},
{capsule, Start@2, End@2, Cap_radius@1}} ->
Half_height@2 = Height@2 / 2.0,
Min_dist = point_to_line_segment_distance(
Cyl_center@1,
Start@2,
End@2
),
Y_in_range = ((erlang:element(3, Start@2) =< (erlang:element(
3,
Cyl_center@1
)
+ Half_height@2))
andalso (erlang:element(3, Start@2) >= (erlang:element(
3,
Cyl_center@1
)
- Half_height@2)))
orelse ((erlang:element(3, End@2) =< (erlang:element(
3,
Cyl_center@1
)
+ Half_height@2))
andalso (erlang:element(3, End@2) >= (erlang:element(
3,
Cyl_center@1
)
- Half_height@2))),
(Min_dist =< (Cyl_radius@1 + Cap_radius@1)) andalso Y_in_range;
{{cylinder, Center_a@1, Radius_a@2, Height_a},
{cylinder, Center_b@1, Radius_b@2, Height_b}} ->
Half_height_a = Height_a / 2.0,
Half_height_b = Height_b / 2.0,
Dx@1 = erlang:element(2, Center_a@1) - erlang:element(2, Center_b@1),
Dz@1 = erlang:element(4, Center_a@1) - erlang:element(4, Center_b@1),
Radial_dist_sq@1 = (Dx@1 * Dx@1) + (Dz@1 * Dz@1),
Radial_dist@1 = case gleam@float:square_root(Radial_dist_sq@1) of
{ok, D@1} ->
D@1;
{error, _} ->
+0.0
end,
Y_overlap = gleam@float:min(
erlang:element(3, Center_a@1) + Half_height_a,
erlang:element(3, Center_b@1) + Half_height_b
)
> gleam@float:max(
erlang:element(3, Center_a@1) - Half_height_a,
erlang:element(3, Center_b@1) - Half_height_b
),
(Radial_dist@1 =< (Radius_a@2 + Radius_b@2)) andalso Y_overlap
end.