Packages

Lightweight 3D physics engine for Gleam game development

Current section

Files

Jump to
expresso src expresso@body.erl
Raw

src/expresso@body.erl

-module(expresso@body).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/expresso/body.gleam").
-export([kinematic/1, dynamic/1, with_mass/2, with_friction/2, with_static_friction/2, with_dynamic_friction/2, with_ccd/2, with_velocity/2, with_angular_velocity/2, with_rotation/2, with_layer/2, with_collision_mask/2, combine_layers/1, should_collide/2, trigger/1, inverse_mass/1, world_collider/1, bounding_radius/1, new_sphere/3, new_box/3, new_capsule/4, new/3]).
-export_type([body/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(
" Rigid body physics with flexible collider shapes.\n"
"\n"
" This module provides the `Body` type and functions for creating and managing\n"
" rigid bodies in a 2D physics simulation.\n"
"\n"
" ## Collider Shapes\n"
"\n"
" Bodies can use any of the collider shapes from the `spatial` library:\n"
"\n"
" - **Sphere** - Fast, perfect for projectiles and particles\n"
" - **Box** - Axis-aligned bounding boxes for walls, platforms, crates\n"
" - **Capsule** - Ideal for character controllers (no corner catching!)\n"
" - **Cylinder** - For cylindrical objects like pillars or barrels\n"
"\n"
" ## Body Types\n"
"\n"
" - **Dynamic** - Affected by forces and collisions (enemies, objects, particles)\n"
" - **Kinematic** - Controlled externally, not affected by forces (player, moving platforms)\n"
"\n"
" ## Quick Start\n"
"\n"
" ```gleam\n"
" import expresso/body\n"
" import vec/vec2\n"
"\n"
" // Create a dynamic circle (common case)\n"
" let bullet = body.new_circle(\"bullet\", vec2.Vec2(0.0, 0.0), radius: 0.25)\n"
" |> body.with_velocity(vec2.Vec2(10.0, 0.0))\n"
"\n"
" // Create a kinematic character with capsule collider\n"
" let player = body.new_capsule(\"player\", vec2.Vec2(0.0, 0.0), height: 2.0, radius: 0.5)\n"
" |> body.kinematic()\n"
"\n"
" // Create a static wall with box collider\n"
" let wall = body.new_box(\"wall\", vec2.Vec2(5.0, 0.0), half_width: 5.0, half_height: 1.0)\n"
" |> body.with_mass(0.0) // Infinite mass = static\n"
" ```\n"
"\n"
" ## Advanced Usage\n"
"\n"
" For full control over the collider, use `body.new()` with a custom collider:\n"
"\n"
" ```gleam\n"
" import spatial/collider\n"
" import vec/vec3\n"
"\n"
" let custom_collider = collider.cylinder(\n"
" center: vec3.Vec3(0.0, 0.0, 0.0),\n"
" radius: 1.0,\n"
" height: 3.0,\n"
" )\n"
" let tower = body.new(\"tower\", vec2.Vec2(5.0, 0.0), custom_collider)\n"
" ```\n"
).
-type body(LKK) :: {body,
LKK,
vec@vec3:vec3(float()),
vec@vec3:vec3(float()),
quaternion:quaternion(),
vec@vec3:vec3(float()),
spatial@collider:internal_collider(),
float(),
boolean(),
float(),
float(),
float(),
boolean(),
integer(),
integer(),
boolean()}.
-file("src/expresso/body.gleam", 278).
?DOC(
" Make a body kinematic (not affected by physics)\n"
"\n"
" Kinematic bodies can push dynamic bodies but are not pushed themselves.\n"
" Useful for player-controlled entities.\n"
).
-spec kinematic(body(LKY)) -> body(LKY).
kinematic(Body) ->
{body,
erlang:element(2, Body),
erlang:element(3, Body),
erlang:element(4, Body),
erlang:element(5, Body),
erlang:element(6, Body),
erlang:element(7, Body),
+0.0,
true,
erlang:element(10, Body),
erlang:element(11, Body),
erlang:element(12, Body),
erlang:element(13, Body),
erlang:element(14, Body),
erlang:element(15, Body),
erlang:element(16, Body)}.
-file("src/expresso/body.gleam", 283).
?DOC(" Make a body dynamic (affected by physics)\n").
-spec dynamic(body(LLB)) -> body(LLB).
dynamic(Body) ->
{body,
erlang:element(2, Body),
erlang:element(3, Body),
erlang:element(4, Body),
erlang:element(5, Body),
erlang:element(6, Body),
erlang:element(7, Body),
1.0,
false,
erlang:element(10, Body),
erlang:element(11, Body),
erlang:element(12, Body),
erlang:element(13, Body),
erlang:element(14, Body),
erlang:element(15, Body),
erlang:element(16, Body)}.
-file("src/expresso/body.gleam", 288).
?DOC(" Set the mass of a body\n").
-spec with_mass(body(LLE), float()) -> body(LLE).
with_mass(Body, Mass) ->
{body,
erlang:element(2, Body),
erlang:element(3, Body),
erlang:element(4, Body),
erlang:element(5, Body),
erlang:element(6, Body),
erlang:element(7, Body),
Mass,
erlang:element(9, Body),
erlang:element(10, Body),
erlang:element(11, Body),
erlang:element(12, Body),
erlang:element(13, Body),
erlang:element(14, Body),
erlang:element(15, Body),
erlang:element(16, Body)}.
-file("src/expresso/body.gleam", 293).
?DOC(" Set the velocity damping of a body (0.0 = no damping, 1.0 = full damping)\n").
-spec with_friction(body(LLH), float()) -> body(LLH).
with_friction(Body, Friction) ->
{body,
erlang:element(2, Body),
erlang:element(3, Body),
erlang:element(4, Body),
erlang:element(5, Body),
erlang:element(6, Body),
erlang:element(7, Body),
erlang:element(8, Body),
erlang:element(9, Body),
Friction,
erlang:element(11, Body),
erlang:element(12, Body),
erlang:element(13, Body),
erlang:element(14, Body),
erlang:element(15, Body),
erlang:element(16, Body)}.
-file("src/expresso/body.gleam", 301).
?DOC(
" Set the static friction coefficient (prevents sliding, typical: 0.3-0.6)\n"
"\n"
" Static friction must be overcome to start sliding.\n"
" Higher values make objects harder to push.\n"
).
-spec with_static_friction(body(LLK), float()) -> body(LLK).
with_static_friction(Body, Friction) ->
{body,
erlang:element(2, Body),
erlang:element(3, Body),
erlang:element(4, Body),
erlang:element(5, Body),
erlang:element(6, Body),
erlang:element(7, Body),
erlang:element(8, Body),
erlang:element(9, Body),
erlang:element(10, Body),
Friction,
erlang:element(12, Body),
erlang:element(13, Body),
erlang:element(14, Body),
erlang:element(15, Body),
erlang:element(16, Body)}.
-file("src/expresso/body.gleam", 309).
?DOC(
" Set the dynamic friction coefficient (opposes sliding, typical: 0.2-0.4)\n"
"\n"
" Dynamic friction opposes motion while sliding.\n"
" Should usually be less than static friction.\n"
).
-spec with_dynamic_friction(body(LLN), float()) -> body(LLN).
with_dynamic_friction(Body, Friction) ->
{body,
erlang:element(2, Body),
erlang:element(3, Body),
erlang:element(4, Body),
erlang:element(5, Body),
erlang:element(6, Body),
erlang:element(7, Body),
erlang:element(8, Body),
erlang:element(9, Body),
erlang:element(10, Body),
erlang:element(11, Body),
Friction,
erlang:element(13, Body),
erlang:element(14, Body),
erlang:element(15, Body),
erlang:element(16, Body)}.
-file("src/expresso/body.gleam", 318).
?DOC(
" Enable continuous collision detection for fast-moving objects\n"
"\n"
" CCD prevents fast objects from tunneling through thin obstacles.\n"
" Use for bullets, fast projectiles, or critical objects.\n"
" Has a small performance cost.\n"
).
-spec with_ccd(body(LLQ), boolean()) -> body(LLQ).
with_ccd(Body, Enabled) ->
{body,
erlang:element(2, Body),
erlang:element(3, Body),
erlang:element(4, Body),
erlang:element(5, Body),
erlang:element(6, Body),
erlang:element(7, Body),
erlang:element(8, Body),
erlang:element(9, Body),
erlang:element(10, Body),
erlang:element(11, Body),
erlang:element(12, Body),
Enabled,
erlang:element(14, Body),
erlang:element(15, Body),
erlang:element(16, Body)}.
-file("src/expresso/body.gleam", 323).
?DOC(" Set the velocity of a body\n").
-spec with_velocity(body(LLT), vec@vec3:vec3(float())) -> body(LLT).
with_velocity(Body, Velocity) ->
{body,
erlang:element(2, Body),
erlang:element(3, Body),
Velocity,
erlang:element(5, Body),
erlang:element(6, Body),
erlang:element(7, Body),
erlang:element(8, Body),
erlang:element(9, Body),
erlang:element(10, Body),
erlang:element(11, Body),
erlang:element(12, Body),
erlang:element(13, Body),
erlang:element(14, Body),
erlang:element(15, Body),
erlang:element(16, Body)}.
-file("src/expresso/body.gleam", 328).
?DOC(" Set the angular velocity of a body (axis-angle form, radians per second)\n").
-spec with_angular_velocity(body(LLX), vec@vec3:vec3(float())) -> body(LLX).
with_angular_velocity(Body, Angular_velocity) ->
{body,
erlang:element(2, Body),
erlang:element(3, Body),
erlang:element(4, Body),
erlang:element(5, Body),
Angular_velocity,
erlang:element(7, Body),
erlang:element(8, Body),
erlang:element(9, Body),
erlang:element(10, Body),
erlang:element(11, Body),
erlang:element(12, Body),
erlang:element(13, Body),
erlang:element(14, Body),
erlang:element(15, Body),
erlang:element(16, Body)}.
-file("src/expresso/body.gleam", 336).
?DOC(" Set the rotation of a body\n").
-spec with_rotation(body(LMB), quaternion:quaternion()) -> body(LMB).
with_rotation(Body, Rotation) ->
{body,
erlang:element(2, Body),
erlang:element(3, Body),
erlang:element(4, Body),
Rotation,
erlang:element(6, Body),
erlang:element(7, Body),
erlang:element(8, Body),
erlang:element(9, Body),
erlang:element(10, Body),
erlang:element(11, Body),
erlang:element(12, Body),
erlang:element(13, Body),
erlang:element(14, Body),
erlang:element(15, Body),
erlang:element(16, Body)}.
-file("src/expresso/body.gleam", 350).
?DOC(
" Set the collision layer of a body\n"
"\n"
" The layer determines which \"group\" this body belongs to.\n"
" Use the predefined constants or custom powers of 2.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let bullet = body.new_circle(\"bullet\", vec2.Vec2(0.0, 0.0), 0.2)\n"
" |> body.with_layer(body.layer_1)\n"
" ```\n"
).
-spec with_layer(body(LME), integer()) -> body(LME).
with_layer(Body, Layer) ->
{body,
erlang:element(2, Body),
erlang:element(3, Body),
erlang:element(4, Body),
erlang:element(5, Body),
erlang:element(6, Body),
erlang:element(7, Body),
erlang:element(8, Body),
erlang:element(9, Body),
erlang:element(10, Body),
erlang:element(11, Body),
erlang:element(12, Body),
erlang:element(13, Body),
Layer,
erlang:element(15, Body),
erlang:element(16, Body)}.
-file("src/expresso/body.gleam", 369).
?DOC(
" Set the collision mask of a body\n"
"\n"
" The collision mask determines which layers this body can collide with.\n"
" Use bitwise OR to combine multiple layers.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" // Player bullet only collides with enemies and environment\n"
" let bullet = body.new_circle(\"bullet\", vec2.Vec2(0.0, 0.0), 0.2)\n"
" |> body.with_layer(body.layer_1)\n"
" |> body.with_collision_mask(combine_layers([\n"
" body.layer_2,\n"
" body.layer_3,\n"
" ]))\n"
" ```\n"
).
-spec with_collision_mask(body(LMH), integer()) -> body(LMH).
with_collision_mask(Body, Mask) ->
{body,
erlang:element(2, Body),
erlang:element(3, Body),
erlang:element(4, Body),
erlang:element(5, Body),
erlang:element(6, Body),
erlang:element(7, Body),
erlang:element(8, Body),
erlang:element(9, Body),
erlang:element(10, Body),
erlang:element(11, Body),
erlang:element(12, Body),
erlang:element(13, Body),
erlang:element(14, Body),
Mask,
erlang:element(16, Body)}.
-file("src/expresso/body.gleam", 379).
?DOC(
" Combine multiple collision layers into a single mask using bitwise OR\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let mask = combine_layers([layer_2, layer_3])\n"
" ```\n"
).
-spec combine_layers(list(integer())) -> integer().
combine_layers(Layers) ->
gleam@list:fold(Layers, 0, fun(Acc, Layer) -> erlang:'bor'(Acc, Layer) end).
-file("src/expresso/body.gleam", 388).
?DOC(
" Check if two bodies should collide based on their layers\n"
"\n"
" Bodies collide if:\n"
" - Body A's layer is in Body B's collision mask, AND\n"
" - Body B's layer is in Body A's collision mask\n"
).
-spec should_collide(body(LML), body(LML)) -> boolean().
should_collide(Body_a, Body_b) ->
A_in_b_mask = erlang:'band'(
erlang:element(14, Body_a),
erlang:element(15, Body_b)
)
/= 0,
B_in_a_mask = erlang:'band'(
erlang:element(14, Body_b),
erlang:element(15, Body_a)
)
/= 0,
A_in_b_mask andalso B_in_a_mask.
-file("src/expresso/body.gleam", 404).
?DOC(
" Make a body a trigger (sensor)\n"
"\n"
" Triggers detect collisions but don't physically interact.\n"
" Perfect for damage zones, checkpoints, pickups, etc.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let checkpoint = body.new_circle(\"checkpoint\", vec2.Vec2(10.0, 0.0), 1.0)\n"
" |> body.trigger()\n"
" ```\n"
).
-spec trigger(body(LMO)) -> body(LMO).
trigger(Body) ->
{body,
erlang:element(2, Body),
erlang:element(3, Body),
erlang:element(4, Body),
erlang:element(5, Body),
erlang:element(6, Body),
erlang:element(7, Body),
+0.0,
erlang:element(9, Body),
erlang:element(10, Body),
erlang:element(11, Body),
erlang:element(12, Body),
erlang:element(13, Body),
erlang:element(14, Body),
erlang:element(15, Body),
true}.
-file("src/expresso/body.gleam", 411).
?DOC(
" Get the inverse mass (used in physics calculations)\n"
"\n"
" Returns 0.0 for kinematic bodies or bodies with 0 mass.\n"
).
-spec inverse_mass(body(any())) -> float().
inverse_mass(Body) ->
case erlang:element(9, Body) orelse (erlang:element(8, Body) =:= +0.0) of
true ->
+0.0;
false ->
case erlang:element(8, Body) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> 1.0 / Gleam@denominator
end
end.
-file("src/expresso/body.gleam", 515).
?DOC(" Compute AABB half-extents from a list of points\n").
-spec compute_aabb_extents(list(vec@vec3:vec3(float()))) -> vec@vec3:vec3(float()).
compute_aabb_extents(Points) ->
case Points of
[] ->
{vec3, +0.0, +0.0, +0.0};
[First | Rest] ->
{Min_x@1, Max_x@1, Min_y@1, Max_y@1, Min_z@1, Max_z@1} = gleam@list:fold(
Rest,
{erlang:element(2, First),
erlang:element(2, First),
erlang:element(3, First),
erlang:element(3, First),
erlang:element(4, First),
erlang:element(4, First)},
fun(Acc, Point) ->
{Min_x, Max_x, Min_y, Max_y, Min_z, Max_z} = Acc,
{gleam@float:min(Min_x, erlang:element(2, Point)),
gleam@float:max(Max_x, erlang:element(2, Point)),
gleam@float:min(Min_y, erlang:element(3, Point)),
gleam@float:max(Max_y, erlang:element(3, Point)),
gleam@float:min(Min_z, erlang:element(4, Point)),
gleam@float:max(Max_z, erlang:element(4, Point))}
end
),
{vec3,
(Max_x@1 - Min_x@1) / 2.0,
(Max_y@1 - Min_y@1) / 2.0,
(Max_z@1 - Min_z@1) / 2.0}
end.
-file("src/expresso/body.gleam", 421).
?DOC(
" Get the collider in world space (updated with body position and rotation)\n"
"\n"
" This is useful for collision detection.\n"
).
-spec world_collider(body(any())) -> spatial@collider:internal_collider().
world_collider(Body) ->
World_pos = erlang:element(3, Body),
Rotation = erlang:element(5, Body),
case erlang:element(7, Body) of
{sphere, _, Radius} ->
spatial@collider:sphere(World_pos, Radius);
{box, _, _} ->
Size = spatial@collider:size(erlang:element(7, Body)),
Half_extents = {vec3,
erlang:element(2, Size) / 2.0,
erlang:element(3, Size) / 2.0,
erlang:element(4, Size) / 2.0},
Corners = [{vec3,
erlang:element(2, Half_extents),
erlang:element(3, Half_extents),
erlang:element(4, Half_extents)},
{vec3,
erlang:element(2, Half_extents),
erlang:element(3, Half_extents),
+0.0 - erlang:element(4, Half_extents)},
{vec3,
erlang:element(2, Half_extents),
+0.0 - erlang:element(3, Half_extents),
erlang:element(4, Half_extents)},
{vec3,
erlang:element(2, Half_extents),
+0.0 - erlang:element(3, Half_extents),
+0.0 - erlang:element(4, Half_extents)},
{vec3,
+0.0 - erlang:element(2, Half_extents),
erlang:element(3, Half_extents),
erlang:element(4, Half_extents)},
{vec3,
+0.0 - erlang:element(2, Half_extents),
erlang:element(3, Half_extents),
+0.0 - erlang:element(4, Half_extents)},
{vec3,
+0.0 - erlang:element(2, Half_extents),
+0.0 - erlang:element(3, Half_extents),
erlang:element(4, Half_extents)},
{vec3,
+0.0 - erlang:element(2, Half_extents),
+0.0 - erlang:element(3, Half_extents),
+0.0 - erlang:element(4, Half_extents)}],
Rotated_corners = gleam@list:map(
Corners,
fun(Corner) -> quaternion:rotate(Rotation, Corner) end
),
New_half_extents = compute_aabb_extents(Rotated_corners),
spatial@collider:box_from_center(World_pos, New_half_extents);
{capsule, Start, End, Radius@1} ->
Half_length_x = (erlang:element(2, End) - erlang:element(2, Start))
/ 2.0,
Half_length_y = (erlang:element(3, End) - erlang:element(3, Start))
/ 2.0,
Half_length_z = (erlang:element(4, End) - erlang:element(4, Start))
/ 2.0,
Local_start = {vec3,
+0.0 - Half_length_x,
+0.0 - Half_length_y,
+0.0 - Half_length_z},
Local_end = {vec3, Half_length_x, Half_length_y, Half_length_z},
Rotated_start = quaternion:rotate(Rotation, Local_start),
Rotated_end = quaternion:rotate(Rotation, Local_end),
World_start = vec@vec3f:add(World_pos, Rotated_start),
World_end = vec@vec3f:add(World_pos, Rotated_end),
spatial@collider:capsule(World_start, World_end, Radius@1);
{cylinder, _, Radius@2, Height} ->
Half_height = Height / 2.0,
Local_bottom = {vec3, +0.0, +0.0 - Half_height, +0.0},
Local_top = {vec3, +0.0, Half_height, +0.0},
Rotated_bottom = quaternion:rotate(Rotation, Local_bottom),
Rotated_top = quaternion:rotate(Rotation, Local_top),
World_start@1 = vec@vec3f:add(World_pos, Rotated_bottom),
World_end@1 = vec@vec3f:add(World_pos, Rotated_top),
spatial@collider:capsule(World_start@1, World_end@1, Radius@2)
end.
-file("src/expresso/body.gleam", 550).
?DOC(
" Get the bounding radius of the collider (for spatial queries)\n"
"\n"
" This returns the radius of the smallest sphere that contains the collider.\n"
).
-spec bounding_radius(body(any())) -> float().
bounding_radius(Body) ->
case erlang:element(7, Body) of
{sphere, _, Radius} ->
Radius;
{box, _, _} ->
Size = spatial@collider:size(erlang:element(7, Body)),
Max_extent = case erlang:element(2, Size) > erlang:element(3, Size) of
true ->
erlang:element(2, Size);
false ->
erlang:element(3, Size)
end,
Max_extent / 2.0;
{capsule, Start, End, Radius@1} ->
Length_vec = {vec3,
erlang:element(2, End) - erlang:element(2, Start),
erlang:element(3, End) - erlang:element(3, Start),
erlang:element(4, End) - erlang:element(4, Start)},
Length_sq = ((erlang:element(2, Length_vec) * erlang:element(
2,
Length_vec
))
+ (erlang:element(3, Length_vec) * erlang:element(3, Length_vec)))
+ (erlang:element(4, Length_vec) * erlang:element(4, Length_vec)),
Length = case Length_sq of
+0.0 ->
+0.0;
_ ->
case begin
_pipe = Length_sq,
gleam@float:square_root(_pipe)
end of
{ok, L} ->
L;
{error, _} ->
+0.0
end
end,
Radius@1 + (Length / 2.0);
{cylinder, _, Radius@2, Height} ->
Max_dim = case Radius@2 > (Height / 2.0) of
true ->
Radius@2;
false ->
Height / 2.0
end,
Max_dim
end.
-file("src/expresso/body.gleam", 132).
?DOC(
" Create a new dynamic body with a sphere collider\n"
"\n"
" This is a convenience constructor for spherical bodies.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let enemy = body.new_sphere(\n"
" id: \"enemy_1\",\n"
" position: vec3.Vec3(10.0, 5.0, 0.0),\n"
" radius: 0.5,\n"
" )\n"
" ```\n"
).
-spec new_sphere(LKL, vec@vec3:vec3(float()), float()) -> body(LKL).
new_sphere(Id, Position, Radius) ->
{body,
Id,
Position,
{vec3, +0.0, +0.0, +0.0},
{quaternion, +0.0, +0.0, +0.0, 1.0},
{vec3, +0.0, +0.0, +0.0},
spatial@collider:sphere({vec3, +0.0, +0.0, +0.0}, Radius),
1.0,
false,
0.1,
0.5,
0.3,
false,
1,
16#FFFFFFFF,
false}.
-file("src/expresso/body.gleam", 166).
?DOC(
" Create a new dynamic body with a box collider\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let wall = body.new_box(\n"
" id: \"wall_1\",\n"
" position: vec3.Vec3(10.0, 5.0, 0.0),\n"
" half_extents: vec3.Vec3(5.0, 0.5, 0.5),\n"
" )\n"
" ```\n"
).
-spec new_box(LKO, vec@vec3:vec3(float()), vec@vec3:vec3(float())) -> body(LKO).
new_box(Id, Position, Half_extents) ->
{body,
Id,
Position,
{vec3, +0.0, +0.0, +0.0},
{quaternion, +0.0, +0.0, +0.0, 1.0},
{vec3, +0.0, +0.0, +0.0},
spatial@collider:box_from_center(Position, Half_extents),
1.0,
false,
0.1,
0.5,
0.3,
false,
1,
16#FFFFFFFF,
false}.
-file("src/expresso/body.gleam", 206).
?DOC(
" Create a new dynamic body with a capsule collider\n"
"\n"
" Perfect for character controllers!\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let player = body.new_capsule(\n"
" id: \"player\",\n"
" position: vec3.Vec3(0.0, 0.0, 0.0),\n"
" height: 2.0,\n"
" radius: 0.5,\n"
" )\n"
" ```\n"
).
-spec new_capsule(LKS, vec@vec3:vec3(float()), float(), float()) -> body(LKS).
new_capsule(Id, Position, Height, Radius) ->
Half_height = Height / 2.0,
Start = {vec3,
erlang:element(2, Position),
erlang:element(3, Position),
erlang:element(4, Position) - Half_height},
End = {vec3,
erlang:element(2, Position),
erlang:element(3, Position),
erlang:element(4, Position) + Half_height},
{body,
Id,
Position,
{vec3, +0.0, +0.0, +0.0},
{quaternion, +0.0, +0.0, +0.0, 1.0},
{vec3, +0.0, +0.0, +0.0},
spatial@collider:capsule(Start, End, Radius),
1.0,
false,
0.1,
0.5,
0.3,
false,
1,
16#FFFFFFFF,
false}.
-file("src/expresso/body.gleam", 250).
?DOC(
" Create a new body with a custom collider\n"
"\n"
" Use this for full control over the collider shape.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let custom_collider = collider.sphere(\n"
" center: vec3.Vec3(0.0, 0.0, 0.0),\n"
" radius: 2.0,\n"
" )\n"
" let body = body.new(\n"
" id: \"custom\",\n"
" position: vec3.Vec3(0.0, 0.0, 0.0),\n"
" collider: custom_collider,\n"
" )\n"
" ```\n"
).
-spec new(LKV, vec@vec3:vec3(float()), spatial@collider:internal_collider()) -> body(LKV).
new(Id, Position, Collider) ->
{body,
Id,
Position,
{vec3, +0.0, +0.0, +0.0},
{quaternion, +0.0, +0.0, +0.0, 1.0},
{vec3, +0.0, +0.0, +0.0},
Collider,
1.0,
false,
0.1,
0.5,
0.3,
false,
1,
16#FFFFFFFF,
false}.