w0rm/elm-physics/6.2.0

Physics

Bodies

type alias Body =
Body

A body is anything the simulation moves or collides — a ball, a box, a wall, a moving platform. It is defined in BodyCoordinates and positioned in WorldCoordinates. Bodies start out centered on the origin; use moveTo to set the position.

There are three kinds of bodies:

  • dynamic — moved by the engine in response to forces, gravity, and contacts. The default for block, sphere, cylinder, capsule, cone, and pointMass; use dynamic to combine several shapes into one body.

  • static — never moves. Used for floors, walls, and other immovable scenery. The default for plane; use static to combine several shapes into one body.

  • kinematic — moves at the velocity you set via setVelocityTo and setAngularVelocityTo; ignores forces, gravity, and contacts, but other dynamic bodies feel its motion through friction. Used for moving platforms, elevators, and turntables. Construct with kinematic.

type alias BodyCoordinates =
BodyCoordinates
type alias WorldCoordinates =
WorldCoordinates
block : Block3d Meters BodyCoordinates -> Material Dense -> Body
plane : Plane3d Meters BodyCoordinates -> Material any -> Body

Create a static plane, collidable only from the direction of the normal, e.g. for +Z:

floor =
    Physics.plane Plane3d.xy Material.wood
sphere : Sphere3d Meters BodyCoordinates -> Material Dense -> Body
cylinder : Cylinder3d Meters BodyCoordinates -> Material Dense -> Body

Create a cylinder, approximated with 12 side faces. For more subdivisions, use dynamic with Shape.cylinder.

capsule : Cylinder3d Meters BodyCoordinates -> Material Dense -> Body

Create a capsule (a cylinder with hemispherical end caps).

cone : Cone3d Meters BodyCoordinates -> Material Dense -> Body

Create a cone, approximated with 12 side faces. For more subdivisions, use dynamic with Shape.cone.

pointMass : Point3d Meters WorldCoordinates -> Mass -> Material any -> Body

Create a point mass — a body with mass but no extent. Two point masses pass through each other; with all other bodies they collide normally.

Positioning

moveTo : Point3d Meters WorldCoordinates -> Body -> Body

Set the position of the body, e.g. to raise a body 5 meters above the origin:

movedBody =
    body
        |> moveTo (Point3d.meters 0 0 5)
translateBy : Vector3d Meters WorldCoordinates -> Body -> Body

Move the body relative to its current position, e.g. to translate a body down by 5 meters:

translatedBody =
    body
        |> translateBy (Vector3d.meters 0 0 -5)
rotateAround : Axis3d Meters WorldCoordinates -> Angle -> Body -> Body

Rotate the body around an axis in the world, e.g. to rotate a body 45 degrees around the Z axis:

rotatedBody =
    body
        |> rotateAround Axis3d.z (Angle.degrees 45)
Frame3d Meters WorldCoordinates { defines : BodyCoordinates }
-> Body
-> Body

Set the position and orientation of a body. Like moveTo but also sets the orientation.

placedBody =
    body
        |> place
            (Frame3d.atPoint (Point3d.meters 2 0 1)
                |> Frame3d.rotateAround Axis3d.z
                    (Angle.degrees 45)
            )

Left-handed frames are not supported — Z is recomputed from X and Y.

Simulation

Config id
-> List ( id, Body )
-> ( List ( id, Body ), Contacts id )

Advance the simulation by one fixed step of Config.duration, returning updated bodies and contacts. With onEarth that step is 1/60th of a second in Earth gravity:

( simulated, contacts ) =
    simulate onEarth model.bodies

Keep the step constant — don’t feed it onAnimationFrameDelta directly, or the solver will jitter. Render in between steps with interpolatedFrame; the companion elm-timestep package handles the bookkeeping.

To improve solver stability for stacked objects, pass contacts from the previous frame back via the config’s contacts field:

( simulated, contacts ) =
    simulate { onEarth | contacts = model.contacts }
        model.bodies
onEarth : Config id

A ready-to-use simulation config with Earth gravity pointing down (-Z) at 60 fps. Customize it by updating individual fields, see Config for available options.

type alias Config id =
{ gravity : Vector3d MetersPerSecondSquared WorldCoordinates
, duration : Duration
, solverIterations : Int
, contacts : Contacts id
, constrain : id -> Maybe (id -> List Constraint)
, collide : id -> id -> Bool
}

Configures a simulation.

onEarth =
    { gravity = Vector3d.gees 0 0 -1
    , duration = Duration.seconds (1 / 60)
    , solverIterations = 20
    , contacts = emptyContacts
    , constrain = \_ -> Nothing
    , collide = \_ _ -> True
    }
  • gravity — set the gravity vector, or Vector3d.zero for no gravity

  • duration — set to Duration.seconds (1 / 60) for 60 fps

  • solverIterations — balance between precision and performance, 20 is a sweet spot

  • contacts — pass Contacts from the previous frame for warm starting, or leave as default for cold start

  • constrain — limit body movement relative to each other, see Constraint

  • collide — decide which bodies can collide with each other

type alias Contacts id =
Contacts id

Contacts from the most recent simulation frame. Contains contact points and solver state for warm starting.

emptyContacts : Contacts id

Empty contacts for the first simulation frame (no warm starting).

(id -> id -> Bool)
-> Contacts id
-> List ( id, id, List (Point3d Meters WorldCoordinates) )

Get contact points from the most recent simulation frame, filtered by a predicate. Each entry is a pair of body ids and a list of contact points between them.

crash =
    Physics.contactPoints
        (\a b -> a == "jeep" && b == "wall")
        contacts

Properties

Body
-> Frame3d Meters WorldCoordinates { defines : BodyCoordinates }

Get the position and orientation of the body as Frame3d. Useful to transform points and directions between world and body coordinates, e.g. for rendering.

Float
-> Body
-> Body
-> Frame3d Meters WorldCoordinates { defines : BodyCoordinates }

Interpolate the frame between two simulation states for smooth rendering. progress is the lerp factor in [0, 1]; both bodies must be the same logical body across consecutive simulation steps.

Physics.interpolatedFrame 0.5 previousBody currentBody
originPoint : Body -> Point3d Meters WorldCoordinates

Get the origin point of a body.

velocity : Body -> Vector3d MetersPerSecond WorldCoordinates

Get the current linear velocity of a body.

angularVelocity : Body -> Vector3d RadiansPerSecond WorldCoordinates

Get the current angular velocity of a body.

Point3d Meters WorldCoordinates
-> Body
-> Vector3d MetersPerSecond WorldCoordinates

Get the linear velocity of a point on a body. Takes into account both linear and angular velocities.

centerOfMass : Body -> Maybe (Point3d Meters WorldCoordinates)

Get the center of mass of a body. Returns Nothing for static and kinematic bodies (which have infinite mass).

mass : Body -> Maybe Mass

Get the mass of a body. Returns Nothing for static and kinematic bodies (which have infinite mass).

Interaction

Axis3d Meters WorldCoordinates
-> List ( id, Body )
-> Maybe
( id
, Body
, { point : Point3d Meters WorldCoordinates
, normal : Direction3d WorldCoordinates
}
)

Find the closest intersection of a ray against a list of bodies.

  • point masses are always excluded because they are infinitely small
  • a plane only intersects when the ray is facing the plane’s normal
Vector3d Newtons WorldCoordinates
-> Point3d Meters WorldCoordinates
-> Body
-> Body

Apply a force at a point on a body.

Keep applying the force every simulation step to accelerate.

force =
    Vector3d.withLength (Force.newtons 50)
        Direction3d.positiveY


pushedBox =
    box
        |> applyForce force pointOnBox
Vector3d (Product Newtons Seconds) WorldCoordinates
-> Point3d Meters WorldCoordinates
-> Body
-> Body

Apply an impulse at a point on a body, adding to its velocity and angular velocity.

impulse =
    Vector3d.withLength
        (Quantity.times (Duration.seconds 0.005)
            (Force.newtons 50)
        )
        Direction3d.positiveY


hitCueBall =
    cueBall
        |> applyImpulse impulse hitPoint
applyTorque : Vector3d NewtonMeters WorldCoordinates -> Body -> Body

Apply a pure torque to a body — spin without translation.

Keep applying the torque every simulation step to spin up.

torque =
    Vector3d.withLength (Torque.newtonMeters 5)
        Direction3d.positiveZ


spinningTop =
    top
        |> applyTorque torque
Vector3d (Product NewtonMeters Seconds) WorldCoordinates
-> Body
-> Body

Apply an angular impulse to a body, adding to its angular velocity.

angularImpulse =
    Vector3d.withLength
        (Quantity.times (Duration.seconds 0.005)
            (Torque.newtonMeters 50)
        )
        Direction3d.positiveZ


spunUp =
    body
        |> applyAngularImpulse angularImpulse

Composite bodies

dynamic : List ( Shape, Material Dense ) -> Body

Create a dynamic body from shapes and materials. Mass and center of mass are derived from geometry and density.

static : List ( Shape, Material any ) -> Body

Create a static body from shapes and materials. Static bodies only collide with dynamic bodies, not other static bodies.

kinematic : List ( Shape, Material any ) -> Body

Create a kinematic body from shapes and materials. Kinematic bodies have infinite mass like static bodies — forces, gravity, and contacts don’t push them — but they’re moved by the engine according to their velocity, set via setVelocityTo and setAngularVelocityTo.

Useful for moving platforms, elevators, conveyor belts, and turntables. Dynamic bodies see the kinematic’s velocity at the contact, so a box rests on a moving elevator without sliding off, and a conveyor carries crates along its surface.

elevator =
    Physics.kinematic [ ( Shape.block platform, Material.steel ) ]
        |> Physics.setVelocityTo (Vector3d.metersPerSecond 0 0 0.5)


turntable =
    Physics.kinematic [ ( Shape.cylinder 16 disc, Material.plastic ) ]
        |> Physics.setAngularVelocityTo
            (Vector3d.withLength (AngularSpeed.radiansPerSecond 1)
                Direction3d.positiveZ
            )

Overrides

Change body state directly, bypassing the simulation.

setVelocityTo : Vector3d MetersPerSecond WorldCoordinates -> Body -> Body

Replace the linear velocity of a body. Works on both dynamic and kinematic bodies.

For dynamic bodies, prefer applyImpulse — using setVelocityTo after applyImpulse silently discards the impulse:

body
    |> applyImpulse impulse point
    -- erased by the next line
    |> setVelocityTo newVelocity

For kinematic bodies, this is the primary way to drive motion — the engine integrates the position from the velocity each frame.

Has no effect on static bodies.

setAngularVelocityTo : Vector3d RadiansPerSecond WorldCoordinates -> Body -> Body

Replace the angular velocity of a body. See setVelocityTo for guidance. Has no effect on static bodies.

scaleMassTo : Mass -> Body -> Body

Scale a body to the given mass. The volume and center of mass are preserved. Has no effect on static or kinematic bodies.

Advanced

{ linear : Float
, angular : Float
}
-> Body
-> Body

Set linear and angular damping, in order to decrease velocity over time. These parameters specify the proportion of velocity lost per second. Values are clamped to [0, 1]. Default: 0.01 for both.

lock : List Lock -> Body -> Body

Restrict a body’s degrees of freedom along world axes. The list fully describes the lock state — calling lock again replaces the previous masks. An empty list clears all locks.

-- 2D-in-3D gameplay on the XY plane
body |> lock [ Lock.translateZ, Lock.rotateX, Lock.rotateY ]


-- character controller: slides freely, never tips
body |> lock Lock.allRotation

See Physics.Lock for the available tokens. Has no effect on static or kinematic bodies.

Body
-> Vector3d units WorldCoordinates
-> Vector3d
(Rate units (Product Kilograms SquareMeters))
WorldCoordinates

Apply the inverse inertia tensor of a body to a vector. Returns Vector3d.zero for static and kinematic bodies. For common cases, see angularAccelerationFromTorque and angularVelocityDeltaFromAngularImpulse.

Body
-> Vector3d NewtonMeters WorldCoordinates
-> Vector3d RadiansPerSecondSquared WorldCoordinates

Compute angular acceleration from torque: α = I⁻¹τ

How fast will this torque make the body spin up?

Body
-> Vector3d (Product NewtonMeters Seconds) WorldCoordinates
-> Vector3d RadiansPerSecond WorldCoordinates

Compute angular velocity change from an angular impulse: Δω = I⁻¹L

How much does this impulse add to my current spin?