-module(expresso@collision). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/expresso/collision.gleam"). -export([build_bvh/1, detect_collisions_with_bvh/2, detect_collisions/1, detect_collisions_with_index/2, bodies_to_bvh_items/1, extract_bodies_from_results/2, filter_by_radius/3, filter_by_region/3, find_nearest/2, analyze_distribution/1, calculate_world_bounds/1, raycast/6]). -export_type([spatial_index/1, contact/1, raycast_hit/1]). -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 detection with spatial acceleration.\n" "\n" " This module provides efficient collision detection using spatial partitioning\n" " data structures from the `spatial` library.\n" "\n" " ## Features\n" "\n" " - **Multi-shape support**: Sphere, Box, Capsule, and Cylinder colliders\n" " - **Spatial acceleration**: BVH (O(n log n)) and Grid (O(c + k))\n" " - **Adaptive selection**: Auto-choose BVH or Grid based on distribution\n" " - **Accurate detection**: Uses `spatial` library's collision algorithms\n" "\n" " ## Collision Detection Strategies\n" "\n" " ### Brute Force (< 10 bodies)\n" " - Complexity: O(n²)\n" " - Used automatically for small scenes\n" " - No spatial index overhead\n" "\n" " ### BVH (10-1000+ bodies, dynamic)\n" " - Complexity: O(n log n + k) where k = collision pairs\n" " - Best for dynamic scenes with moving objects\n" " - Handles non-uniform distributions well\n" "\n" " ### Grid (100-1000+ bodies, uniform)\n" " - Complexity: O(c + k) where c = cells checked, k = collision pairs\n" " - Best for particle systems and uniformly distributed bodies\n" " - Excellent for 1000s of particles\n" "\n" " ## Usage\n" "\n" " Most users won't call this module directly - the `world` module handles\n" " collision detection automatically. However, advanced users can use these\n" " functions for custom collision detection:\n" "\n" " ```gleam\n" " import expresso/collision\n" " import gleam/dict\n" "\n" " // Simple collision detection (builds BVH internally)\n" " let contacts = collision.detect_collisions(bodies)\n" "\n" " // With pre-built BVH (more efficient if reusing)\n" " let bvh = collision.build_bvh(bodies)\n" " let contacts = collision.detect_collisions_with_bvh(bodies, option.Some(bvh))\n" " ```\n" ). -type spatial_index(MAH) :: {b_v_h_index, spatial@bvh:b_v_h(MAH)} | {grid_index, spatial@grid:grid(MAH)}. -type contact(MAI) :: {contact, MAI, MAI, vec@vec3:vec3(float()), float(), vec@vec3:vec3(float())}. -type raycast_hit(MAJ) :: {raycast_hit, MAJ, expresso@body:body(MAJ), vec@vec3:vec3(float()), vec@vec3:vec3(float()), float()}. -file("src/expresso/collision.gleam", 147). ?DOC(" Internal: Build BVH from body list\n"). -spec build_bvh_internal(list({MBH, expresso@body:body(MBH)})) -> {ok, spatial@bvh:b_v_h({MBH, expresso@body:body(MBH)})} | {error, nil}. build_bvh_internal(Body_list) -> Bvh_items = gleam@list:map( Body_list, fun(Pair) -> {Id, Body} = Pair, Pos_3d = {vec3, erlang:element(2, erlang:element(3, Body)), erlang:element(3, erlang:element(3, Body)), +0.0}, {Pos_3d, {Id, Body}} end ), spatial@bvh:from_items(Bvh_items, 8). -file("src/expresso/collision.gleam", 140). ?DOC(" Build a BVH spatial index from bodies\n"). -spec build_bvh(gleam@dict:dict(MAZ, expresso@body:body(MAZ))) -> {ok, spatial@bvh:b_v_h({MAZ, expresso@body:body(MAZ)})} | {error, nil}. build_bvh(Bodies) -> build_bvh_internal(maps:to_list(Bodies)). -file("src/expresso/collision.gleam", 298). ?DOC( " Check if two bodies are colliding using their colliders\n" "\n" " Uses the spatial library's collider.intersects() for accurate collision detection.\n" " Respects collision layers - bodies only collide if their layers match.\n" ). -spec check_collision(expresso@body:body(MCZ), expresso@body:body(MCZ)) -> {ok, contact(MCZ)} | {error, nil}. check_collision(Body_a, Body_b) -> case expresso@body:should_collide(Body_a, Body_b) of false -> {error, nil}; true -> Collider_a = expresso@body:world_collider(Body_a), Collider_b = expresso@body:world_collider(Body_b), case spatial@collider:intersects(Collider_a, Collider_b) of false -> {error, nil}; true -> Center_a = spatial@collider:center(Collider_a), Center_b = spatial@collider:center(Collider_b), Delta = vec@vec3f:subtract(Center_b, Center_a), Distance_3d = vec@vec3f:length(Delta), Size_a = spatial@collider:size(Collider_a), Size_b = spatial@collider:size(Collider_b), Radius_a = gleam@float:max( erlang:element(2, Size_a), erlang:element(3, Size_a) ) / 2.0, Radius_b = gleam@float:max( erlang:element(2, Size_b), erlang:element(3, Size_b) ) / 2.0, Penetration = (Radius_a + Radius_b) - Distance_3d, Normal = case Distance_3d > 0.0001 of true -> vec@vec3f:scale(Delta, case Distance_3d of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> 1.0 / Gleam@denominator end); false -> {vec3, 1.0, +0.0, +0.0} end, Contact_point = vec@vec3f:add( Center_a, vec@vec3f:scale(Delta, 0.5) ), {ok, {contact, erlang:element(2, Body_a), erlang:element(2, Body_b), Normal, Penetration, Contact_point}} end end. -file("src/expresso/collision.gleam", 161). ?DOC(" Brute force collision detection for small body counts\n"). -spec detect_collisions_brute_force(list({MBO, expresso@body:body(MBO)})) -> list(contact(MBO)). detect_collisions_brute_force(Body_list) -> gleam@list:flat_map( Body_list, fun(Pair_a) -> {Id_a, Body_a} = Pair_a, gleam@list:filter_map( Body_list, fun(Pair_b) -> {Id_b, Body_b} = Pair_b, case Id_a =:= Id_b of false -> check_collision(Body_a, Body_b); _ -> {error, nil} end end ) end ). -file("src/expresso/collision.gleam", 349). ?DOC(" Calculate the maximum bounding radius among all bodies\n"). -spec calculate_max_radius(list({MDF, expresso@body:body(MDF)})) -> float(). calculate_max_radius(Body_list) -> gleam@list:fold( Body_list, +0.0, fun(Max_r, Pair) -> {_, Body} = Pair, gleam@float:max(Max_r, expresso@body:bounding_radius(Body)) end ). -file("src/expresso/collision.gleam", 180). ?DOC(" BVH-accelerated collision detection\n"). -spec detect_collisions_with_bvh_internal( list({MBT, expresso@body:body(MBT)}), spatial@bvh:b_v_h({MBT, expresso@body:body(MBT)}) ) -> list(contact(MBT)). detect_collisions_with_bvh_internal(Body_list, Bvh_tree) -> gleam@list:flat_map( Body_list, fun(Pair_a) -> {Id_a, Body_a} = Pair_a, Max_radius = calculate_max_radius(Body_list), Query_radius = expresso@body:bounding_radius(Body_a) + Max_radius, Pos_3d = {vec3, erlang:element(2, erlang:element(3, Body_a)), erlang:element(3, erlang:element(3, Body_a)), +0.0}, Nearby = spatial@bvh:query_radius(Bvh_tree, Pos_3d, Query_radius), gleam@list:filter_map( Nearby, fun(Candidate) -> {_, {Id_b, Body_b}} = Candidate, case Id_a =:= Id_b of true -> {error, nil}; false -> case Id_a =:= Id_b of false -> check_collision(Body_a, Body_b); _ -> {error, nil} end end end ) end ). -file("src/expresso/collision.gleam", 112). ?DOC( " Detect collisions using an optional pre-built BVH for acceleration\n" "\n" " This is the recommended API for performance-critical code.\n" ). -spec detect_collisions_with_bvh( gleam@dict:dict(MAQ, expresso@body:body(MAQ)), gleam@option:option(spatial@bvh:b_v_h({MAQ, expresso@body:body(MAQ)})) ) -> list(contact(MAQ)). detect_collisions_with_bvh(Bodies, Bvh_option) -> Body_list = maps:to_list(Bodies), case Bvh_option of {some, Bvh_tree} -> detect_collisions_with_bvh_internal(Body_list, Bvh_tree); none -> case erlang:length(Body_list) < 10 of true -> detect_collisions_brute_force(Body_list); false -> Bvh_tree@1 = build_bvh_internal(Body_list), case Bvh_tree@1 of {error, nil} -> []; {ok, Tree} -> detect_collisions_with_bvh_internal(Body_list, Tree) end end end. -file("src/expresso/collision.gleam", 105). ?DOC( " Detect all collisions between bodies\n" "\n" " Returns a list of contacts for all overlapping pairs.\n" " \n" " **Performance:**\n" " - For best performance, use `detect_collisions_with_index()` with a pre-built spatial index\n" " - This function uses brute force or builds a BVH internally\n" ). -spec detect_collisions(gleam@dict:dict(MAK, expresso@body:body(MAK))) -> list(contact(MAK)). detect_collisions(Bodies) -> detect_collisions_with_bvh(Bodies, none). -file("src/expresso/collision.gleam", 231). ?DOC(" Detect collisions using BVH (optimized version with ID-based storage)\n"). -spec detect_collisions_with_bvh_new( gleam@dict:dict(MCH, expresso@body:body(MCH)), list({MCH, expresso@body:body(MCH)}), spatial@bvh:b_v_h(MCH) ) -> list(contact(MCH)). detect_collisions_with_bvh_new(Bodies, Body_list, Bvh_tree) -> gleam@list:flat_map( Body_list, fun(Pair_a) -> {Id_a, Body_a} = Pair_a, Max_radius = calculate_max_radius(Body_list), Query_radius = expresso@body:bounding_radius(Body_a) + Max_radius, Pos_3d = {vec3, erlang:element(2, erlang:element(3, Body_a)), erlang:element(3, erlang:element(3, Body_a)), +0.0}, Nearby = spatial@bvh:query_radius(Bvh_tree, Pos_3d, Query_radius), gleam@list:filter_map( Nearby, fun(Candidate) -> {_, Id_b} = Candidate, case Id_a =:= Id_b of true -> {error, nil}; false -> case gleam_stdlib:map_get(Bodies, Id_b) of {ok, Body_b} -> check_collision(Body_a, Body_b); {error, _} -> {error, nil} end end end ) end ). -file("src/expresso/collision.gleam", 263). ?DOC(" Detect collisions using a Grid spatial index (with ID-based storage)\n"). -spec detect_collisions_with_grid_new( gleam@dict:dict(MCQ, expresso@body:body(MCQ)), list({MCQ, expresso@body:body(MCQ)}), spatial@grid:grid(MCQ) ) -> list(contact(MCQ)). detect_collisions_with_grid_new(Bodies, Body_list, Grid_index) -> gleam@list:flat_map( Body_list, fun(Pair_a) -> {Id_a, Body_a} = Pair_a, Max_radius = calculate_max_radius(Body_list), Query_radius = expresso@body:bounding_radius(Body_a) + Max_radius, Pos_3d = {vec3, erlang:element(2, erlang:element(3, Body_a)), erlang:element(3, erlang:element(3, Body_a)), +0.0}, Nearby = spatial@grid:query_radius(Grid_index, Pos_3d, Query_radius), gleam@list:filter_map( Nearby, fun(Candidate) -> {_, Id_b} = Candidate, case Id_a =:= Id_b of true -> {error, nil}; false -> case gleam_stdlib:map_get(Bodies, Id_b) of {ok, Body_b} -> check_collision(Body_a, Body_b); {error, _} -> {error, nil} end end end ) end ). -file("src/expresso/collision.gleam", 218). ?DOC(" Detect collisions using a spatial index (BVH or Grid)\n"). -spec detect_collisions_with_index( gleam@dict:dict(MCA, expresso@body:body(MCA)), spatial_index(MCA) ) -> list(contact(MCA)). detect_collisions_with_index(Bodies, Spatial_index) -> Body_list = maps:to_list(Bodies), case Spatial_index of {b_v_h_index, Tree} -> detect_collisions_with_bvh_new(Bodies, Body_list, Tree); {grid_index, Grid} -> detect_collisions_with_grid_new(Bodies, Body_list, Grid) end. -file("src/expresso/collision.gleam", 361). ?DOC(" Convert bodies to BVH items\n"). -spec bodies_to_bvh_items(list({binary(), expresso@body:body(MDI)})) -> list({vec@vec3:vec3(float()), expresso@body:body(MDI)}). bodies_to_bvh_items(Body_list) -> gleam@list:map( Body_list, fun(Pair) -> {_, Body} = Pair, Pos_3d = {vec3, erlang:element(2, erlang:element(3, Body)), erlang:element(3, erlang:element(3, Body)), +0.0}, {Pos_3d, Body} end ). -file("src/expresso/collision.gleam", 372). ?DOC(" Extract body IDs from query results and lookup bodies from dict\n"). -spec extract_bodies_from_results( list({vec@vec3:vec3(float()), MDP}), gleam@dict:dict(MDP, expresso@body:body(MDP)) ) -> list({MDP, expresso@body:body(MDP)}). extract_bodies_from_results(Results, Bodies) -> gleam@list:filter_map( Results, fun(Result) -> {_, Id} = Result, case gleam_stdlib:map_get(Bodies, Id) of {ok, Body} -> {ok, {Id, Body}}; {error, _} -> {error, nil} end end ). -file("src/expresso/collision.gleam", 386). ?DOC(" Filter bodies by radius (brute force)\n"). -spec filter_by_radius( list({MDW, expresso@body:body(MDW)}), vec@vec3:vec3(float()), float() ) -> list({MDW, expresso@body:body(MDW)}). filter_by_radius(Body_list, Center, Radius) -> gleam@list:filter( Body_list, fun(Pair) -> {_, Body} = Pair, Distance = vec@vec3f:distance(Center, erlang:element(3, Body)), Distance =< (Radius + expresso@body:bounding_radius(Body)) end ). -file("src/expresso/collision.gleam", 399). ?DOC(" Filter bodies by rectangular region (brute force)\n"). -spec filter_by_region( list({MEC, expresso@body:body(MEC)}), vec@vec3:vec3(float()), vec@vec3:vec3(float()) ) -> list({MEC, expresso@body:body(MEC)}). filter_by_region(Body_list, Min, Max) -> gleam@list:filter( Body_list, fun(Pair) -> {_, Body} = Pair, (((((erlang:element(2, erlang:element(3, Body)) >= erlang:element( 2, Min )) andalso (erlang:element(2, erlang:element(3, Body)) =< erlang:element( 2, Max ))) andalso (erlang:element(3, erlang:element(3, Body)) >= erlang:element( 3, Min ))) andalso (erlang:element(3, erlang:element(3, Body)) =< erlang:element( 3, Max ))) andalso (erlang:element(4, erlang:element(3, Body)) >= erlang:element( 4, Min ))) andalso (erlang:element(4, erlang:element(3, Body)) =< erlang:element( 4, Max )) end ). -file("src/expresso/collision.gleam", 416). ?DOC(" Find the nearest body to a point\n"). -spec find_nearest(list({MEJ, expresso@body:body(MEJ)}), vec@vec3:vec3(float())) -> gleam@option:option({MEJ, expresso@body:body(MEJ), float()}). find_nearest(Candidates, Point) -> gleam@list:fold( Candidates, none, fun(Best, Pair) -> {Id, Body} = Pair, Distance = vec@vec3f:distance(Point, erlang:element(3, Body)), case Best of none -> {some, {Id, Body, Distance}}; {some, {_, _, Best_dist}} -> case Distance < Best_dist of true -> {some, {Id, Body, Distance}}; false -> Best end end end ). -file("src/expresso/collision.gleam", 471). ?DOC(" Calculate variance in positions (simplified)\n"). -spec calculate_position_variance(list(vec@vec3:vec3(float()))) -> float(). calculate_position_variance(Positions) -> case erlang:length(Positions) of 0 -> +0.0; N -> Sum = gleam@list:fold( Positions, {vec3, +0.0, +0.0, +0.0}, fun(Acc, Pos) -> vec@vec3f:add(Acc, Pos) end ), Centroid = vec@vec3f:scale(Sum, case erlang:float(N) of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> 1.0 / Gleam@denominator end), Total_sq_dist = gleam@list:fold( Positions, +0.0, fun(Acc@1, Pos@1) -> Dist = vec@vec3f:distance(Centroid, Pos@1), Acc@1 + (Dist * Dist) end ), case erlang:float(N) of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator@1 -> Total_sq_dist / Gleam@denominator@1 end end. -file("src/expresso/collision.gleam", 438). ?DOC( " Analyze body distribution to determine if uniform\n" "\n" " Returns #(is_uniform, average_bounding_radius)\n" ). -spec analyze_distribution(gleam@dict:dict(MEP, expresso@body:body(MEP))) -> {boolean(), float()}. analyze_distribution(Bodies) -> Body_list = maps:to_list(Bodies), Count = erlang:length(Body_list), case Count of 0 -> {false, 1.0}; _ -> Total_radius = gleam@list:fold( Body_list, +0.0, fun(Sum, Pair) -> {_, Body} = Pair, Sum + expresso@body:bounding_radius(Body) end ), Avg_radius = case erlang:float(Count) of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> Total_radius / Gleam@denominator end, Positions = gleam@list:map( Body_list, fun(Pair@1) -> {_, Body@1} = Pair@1, erlang:element(3, Body@1) end ), Variance = calculate_position_variance(Positions), Is_uniform = Variance < 100.0, {Is_uniform, Avg_radius} end. -file("src/expresso/collision.gleam", 495). ?DOC(" Calculate world bounds for Grid creation\n"). -spec calculate_world_bounds(gleam@dict:dict(MEV, expresso@body:body(MEV))) -> spatial@collider:internal_collider(). calculate_world_bounds(Bodies) -> Body_list = maps:to_list(Bodies), case Body_list of [] -> spatial@collider:box( {vec3, -100.0, -100.0, -1.0}, {vec3, 100.0, 100.0, 1.0} ); _ -> Init_min = {vec3, 1.0e10, 1.0e10, 1.0e10}, Init_max = {vec3, -1.0e10, -1.0e10, -1.0e10}, {Min_3d, Max_3d} = gleam@list:fold( Body_list, {Init_min, Init_max}, fun(Acc, Pair) -> {Current_min, Current_max} = Acc, {_, Body} = Pair, Radius = expresso@body:bounding_radius(Body), Body_min = {vec3, erlang:element(2, erlang:element(3, Body)) - Radius, erlang:element(3, erlang:element(3, Body)) - Radius, erlang:element(4, erlang:element(3, Body)) - Radius}, Body_max = {vec3, erlang:element(2, erlang:element(3, Body)) + Radius, erlang:element(3, erlang:element(3, Body)) + Radius, erlang:element(4, erlang:element(3, Body)) + Radius}, {{vec3, gleam@float:min( erlang:element(2, Current_min), erlang:element(2, Body_min) ), gleam@float:min( erlang:element(3, Current_min), erlang:element(3, Body_min) ), gleam@float:min( erlang:element(4, Current_min), erlang:element(4, Body_min) )}, {vec3, gleam@float:max( erlang:element(2, Current_max), erlang:element(2, Body_max) ), gleam@float:max( erlang:element(3, Current_max), erlang:element(3, Body_max) ), gleam@float:max( erlang:element(4, Current_max), erlang:element(4, Body_max) )}} end ), Padding = 10.0, spatial@collider:box( {vec3, erlang:element(2, Min_3d) - Padding, erlang:element(3, Min_3d) - Padding, erlang:element(4, Min_3d) - Padding}, {vec3, erlang:element(2, Max_3d) + Padding, erlang:element(3, Max_3d) + Padding, erlang:element(4, Max_3d) + Padding} ) end. -file("src/expresso/collision.gleam", 659). ?DOC(" Test ray against a single body's collider\n"). -spec raycast_body( vec@vec3:vec3(float()), vec@vec3:vec3(float()), float(), MFM, expresso@body:body(MFM) ) -> {ok, raycast_hit(MFM)} | {error, nil}. raycast_body(Origin, Direction, Max_distance, Body_id, Body_data) -> Collider_center = erlang:element(3, Body_data), Radius = expresso@body:bounding_radius(Body_data), To_center = vec@vec3f:subtract(Collider_center, Origin), Projection = vec@vec3f:dot(To_center, Direction), case Projection < +0.0 of true -> {error, nil}; false -> Closest_point = vec@vec3f:add( Origin, vec@vec3f:scale(Direction, Projection) ), Distance_to_center = vec@vec3f:distance( Closest_point, Collider_center ), case Distance_to_center =< Radius of false -> {error, nil}; true -> Offset_sq = (Radius * Radius) - (Distance_to_center * Distance_to_center), Offset = case Offset_sq of +0.0 -> +0.0; _ -> case begin _pipe = Offset_sq, gleam@float:square_root(_pipe) end of {ok, O} -> O; {error, _} -> +0.0 end end, Hit_distance = Projection - Offset, case (Hit_distance >= +0.0) andalso (Hit_distance =< Max_distance) of false -> {error, nil}; true -> Hit_point = vec@vec3f:add( Origin, vec@vec3f:scale(Direction, Hit_distance) ), Normal_vec = vec@vec3f:subtract( Hit_point, Collider_center ), Normal_length = vec@vec3f:length(Normal_vec), Normal = case Normal_length > 0.0001 of true -> vec@vec3f:scale( Normal_vec, case Normal_length of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> 1.0 / Gleam@denominator end ); false -> {vec3, 1.0, +0.0, +0.0} end, {ok, {raycast_hit, Body_id, Body_data, Hit_point, Normal, Hit_distance}} end end end. -file("src/expresso/collision.gleam", 569). ?DOC( " Cast a ray and find the first body it hits\n" "\n" " Returns the closest hit along the ray, or None if no hit.\n" ). -spec raycast( gleam@dict:dict(MEZ, expresso@body:body(MEZ)), gleam@option:option(spatial_index(MEZ)), vec@vec3:vec3(float()), vec@vec3:vec3(float()), float(), gleam@option:option(integer()) ) -> gleam@option:option(raycast_hit(MEZ)). raycast(Bodies, Spatial_index, Origin, Direction, Max_distance, Layer_mask) -> Dir_length = vec@vec3f:length(Direction), Dir_normalized = case Dir_length > 0.0001 of true -> vec@vec3f:scale(Direction, case Dir_length of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> 1.0 / Gleam@denominator end); false -> {vec3, 1.0, +0.0, +0.0} end, Candidates = case Spatial_index of none -> maps:to_list(Bodies); {some, {b_v_h_index, Tree}} -> Endpoint = vec@vec3f:add( Origin, vec@vec3f:scale(Dir_normalized, Max_distance) ), Center_3d = {vec3, (erlang:element(2, Origin) + erlang:element(2, Endpoint)) / 2.0, (erlang:element(3, Origin) + erlang:element(3, Endpoint)) / 2.0, (erlang:element(4, Origin) + erlang:element(4, Endpoint)) / 2.0}, Radius = (Max_distance / 2.0) + 10.0, Results = spatial@bvh:query_radius(Tree, Center_3d, Radius), extract_bodies_from_results(Results, Bodies); {some, {grid_index, Grid}} -> Endpoint@1 = vec@vec3f:add( Origin, vec@vec3f:scale(Dir_normalized, Max_distance) ), Center_3d@1 = {vec3, (erlang:element(2, Origin) + erlang:element(2, Endpoint@1)) / 2.0, (erlang:element(3, Origin) + erlang:element(3, Endpoint@1)) / 2.0, (erlang:element(4, Origin) + erlang:element(4, Endpoint@1)) / 2.0}, Radius@1 = (Max_distance / 2.0) + 10.0, Results@1 = spatial@grid:query_radius(Grid, Center_3d@1, Radius@1), extract_bodies_from_results(Results@1, Bodies) end, Hits = gleam@list:filter_map( Candidates, fun(Pair) -> {Id, Candidate_body} = Pair, case Layer_mask of {some, Mask} -> case erlang:'band'(erlang:element(14, Candidate_body), Mask) =:= 0 of true -> {error, nil}; false -> raycast_body( Origin, Dir_normalized, Max_distance, Id, Candidate_body ) end; none -> raycast_body( Origin, Dir_normalized, Max_distance, Id, Candidate_body ) end end ), gleam@list:fold(Hits, none, fun(Closest, Hit) -> case Closest of none -> {some, Hit}; {some, Closest_hit} -> case erlang:element(6, Hit) < erlang:element(6, Closest_hit) of true -> {some, Hit}; false -> Closest end end end).