Bodies, joints and collisions
Make shapes fall, carry one another and hang from joints, and run code when they collide.
Add physics, in the ball pit tutorial, gives the first shapes bodies. This guide goes further: moving platforms, joints, collision handlers and the rules that keep a simulation steady.
Give a shape a body
DrawPrimitive:add_body gives a shape on a draw canvas a body in your
app's 2D world, and SceneNode:add_body gives a 3D node one in its 3D
world. Without a shape, the collider fits what the handle draws. Sizes
and positions are in the handle's own units: canvas pixels on a draw
canvas.
Each body has a type, which decides what moves it:
| Type | What moves it | Use it for |
|---|---|---|
dynamic | the simulation: gravity, collisions, impulses | things that fall, roll and bounce |
kinematic | your code alone; it carries what rests on it | platforms, elevators, doors |
static | your code alone; it pushes what rests on it aside | floors, walls, pins |
Gravity, physics.gravity, starts out right for a 3D scene measured in
meters. On a canvas measured in pixels that is a gentle pull, so the
program below sets a stronger one.
This program drops three boxes onto a sliding platform, turning each box red when it first touches something, and swings a bar from a pin:
local color = datumhue.color
local vec2 = datumhue.math.vec2
local vec3 = datumhue.math.vec3
local physics = datumhue.physics
-- A canvas measures in pixels, so pull harder than the default.
physics.gravity = vec2(0, -600)
local canvas = datumhue.draw.new({
width = 480,
height = 320,
background = color("#2d353b"),
})
canvas:mount({ width = 480, height = 320 })
-- A kinematic platform goes where you move it, and carries
-- what lands on it.
local platform = canvas:rect({
pos = vec2(-200, -120),
width = 160,
height = 16,
color = color("#7fbbb3"),
})
platform:add_body({ type = "kinematic" })
platform:animate({
pos = vec2(-20, -112),
duration = 2,
easing = "sine_in_out",
looping = true,
})
-- Dynamic boxes fall onto it, and turn red at their first
-- touch.
for i = 0, 2 do
local box = canvas:rect({
pos = vec2(-150 + i * 30, 20 + i * 50),
width = 24,
height = 24,
color = color("#dbbc7f"),
})
box:add_body({ type = "dynamic" })
box:on_collision_start(function()
box:update({ color = color("#e67e80") })
end)
end
-- A pendulum: a bar hinged at its left end to a fixed pin.
local pin = canvas:circle({
pos = vec2(120, 100),
radius = 6,
color = color("#d3c6aa"),
})
pin:add_body({ type = "static" })
local bar = canvas:rect({
pos = vec2(120, 95),
width = 120,
height = 10,
color = color("#a7c080"),
})
bar:add_body({ type = "dynamic" })
-- Anchors are in each shape's own units, from its center: the
-- pin's center, and the bar's left end, 60 px left of its own.
physics.add_joint(pin, bar, {
type = "revolute",
anchor2 = vec3(-60, 0, 0),
})
In this example: datumhue.color datumhue.math.vec2 datumhue.math.vec3 datumhue.physics datumhue.physics.gravity datumhue.draw.new DrawCanvas:mount DrawCanvas:rect DrawPrimitive:add_body DrawPrimitive:animate DrawPrimitive:on_collision_start DrawPrimitive:update DrawCanvas:circle datumhue.physics.add_joint
Move bodies the right way
A static or kinematic body goes wherever its handle goes. Assign pos
or rotation, call update, or tween it with animate, and the body
follows at the next physics step. Move a group, and the bodies inside it
follow too.
A dynamic body moves itself. Push it with DrawPrimitive:apply_impulse
for a kick or DrawPrimitive:apply_force for a steady push, or assign
DrawPrimitive.velocity.
A static or kinematic body inside a dynamic one, in a group or under a scene node, rides along with the nearest dynamic body above it, one step behind, and never collides with it.
Join bodies
physics.add_joint joins two bodies of the same world. A revolute
joint is a hinge, as the pendulum's; prismatic slides along an axis,
distance keeps bodies within a rope's length, spherical is a ball
joint in 3D, and fixed welds them together. The joint holds the pose
the bodies are in when you make it, and limits and motors on
physics.JointOptions bound its travel or drive it.
React to collisions
DrawPrimitive:on_collision_start runs when a body starts touching
another, as the boxes do, and DrawPrimitive:on_collision_end when they
part. The handler gets the other handle and, on start, a
physics.Contact with the point and direction of the hit.
A body with sensor = true collides with nothing, but still reports
what enters and leaves it: a trigger zone for a goal or a pickup.
layers and collides_with decide which bodies meet at all, so bullets
can pass through their own team.
Ask what is where
physics.raycast finds the first body along a ray, and
physics.point_query every body at a point. For a character that walks
and climbs steps under your own controls, give a kinematic body
DrawPrimitive:.
Each app simulates in its own worlds: its bodies, joints, queries and
gravity never meet another app's, and physics needs no permission.
physics.world makes a further world of your own, with its own gravity.
See also
Reference used on this page: datumhue.color datumhue.math.vec2 datumhue.math.vec3 datumhue.physics datumhue.physics.gravity datumhue.draw.new DrawCanvas:mount DrawCanvas:rect DrawPrimitive:add_body DrawPrimitive:animate DrawPrimitive:on_collision_start DrawPrimitive:update DrawCanvas:circle datumhue.physics.add_joint SceneNode:add_body DrawPrimitive:apply_impulse DrawPrimitive:apply_force DrawPrimitive.velocity physics.JointOptions DrawPrimitive:on_collision_end physics.Contact datumhue.physics.raycast datumhue.physics.point_query DrawPrimitive:character_controller datumhue.physics.world