DatumHueEnterprise v0.0.24datumhue.com
Contents

namespacePhysics7 functions, 1 propertyUI apps only

datumhue.physics

Unified 2D/3D physics. A DrawPrimitive's body simulates in the app's 2D world, a SceneNode's or Voxel's in its 3D world. Each app simulates in its own worlds: bodies, joints, queries, and gravity never interact with other apps.

Returns handles
Vec3, PhysicsWorld, Joint, DrawPrimitive, SceneNode, Voxel

The examples on this page start from:

lua
local vec2 = datumhue.math.vec2
local vec3 = datumhue.math.vec3
local canvas =
    datumhue.draw.new({ background = datumhue.color("#2d353b") })
canvas:mount({ width = 400, height = 300 })
local floor =
    canvas:rect({ pos = vec2(-200, -150), width = 400, height = 20 })
floor:add_body({ type = "static" })

Fields 1

FieldTypeAccessDescription
gravityVec3read/writeThis app's gravity vector (Y-up; default {0, -9.81, 0}). 2D bodies feel its X/Y components. Assign a vec2/vec3.
Example
print(datumhue.physics.gravity) --> vec3(0.0, -9.8100004196167, 0.0)
-- Canvas units are pixels: a 2D game wants a stronger pull.
datumhue.physics.gravity = vec2(0, -600)
print(datumhue.physics.gravity) --> vec3(0.0, -600.0, 0.0)

Functions 7

datumhue.physics.world

functionUI apps only
datumhue.physics.world() -> PhysicsWorld

Create an additional isolated physics world. Bodies join it via add_body{world = ...}; it has its own gravity and query methods. Bodies in different worlds never interact.

datumhue.physics.on_step

functionUI apps only
datumhue.physics.on_step(callback: (fun(dt: number))?)

Register this app's physics step handler, called once per completed simulation step with dt the fixed timestep in seconds -- the constant interval every step advances by, independent of frame rate. Steps finish before the frame's Lua callbacks run, so the handler reads post-step state; within a frame it runs after the same steps' collision callbacks and before on_update, and a frame that ran several catch-up steps calls it once per step. At most one handler is active per app -- calling again replaces the previous one. Pass nil to clear it.

datumhue.physics.add_joint

functionUI apps only
datumhue.physics.add_joint(body1: DrawPrimitive | SceneNode | Voxel, body2: DrawPrimitive | SceneNode | Voxel, options: physics.JointOptions) -> Joint

Create a physics joint (fixed, revolute, prismatic, distance, or spherical) between two bodies of the same dimension and world. The joint rests in the pose the bodies are in when it is made: a fixed joint keeps their turn, a hinge's zero angle is that pose. The anchors and axis follow each body's handle as it scales, and the joint survives add_body replacing a body in the same world (moving a jointed body to another world raises). Raises for a body joined to itself, bodies of different worlds or dimensions, and an option its kind does not take.

Example
local hub = canvas:circle({ radius = 6 })
local arm = canvas:rect({ pos = vec2(0, -4), width = 80, height = 8 })
hub:add_body({ type = "static" })
arm:add_body({ mass = 1 })
-- A hinge at the hub's center. Anchors are in each body's own
-- frame, so the arm's is its left end. The motor turns it.
datumhue.physics.add_joint(hub, arm, {
    type = "revolute",
    anchor2 = vec3(-40, 0, 0),
    motor_speed = math.pi,
    motor_force = 100000,
})

options physics.JointOptions options 14 keys

KeyTypeDefaultDescription
type"fixed" | "revolute" | "prismatic" | "distance" | "spherical""fixed"Joint kind. An option the kind does not take raises.
anchor1Vec3datumhue.math.vec3(0, 0, 0)Attachment point on the first body, in its handle's own frame and units: it stays on the drawing as the handle moves and scales.
anchor2Vec3datumhue.math.vec3(0, 0, 0)Attachment point on the second body, in its handle's own frame and units.
axisVec3+z for a revolute joint, +x for a prismatic or spherical oneHinge axis (revolute), slide axis (prismatic), or twist axis (spherical): a non-zero direction in the first body's handle frame; the second body takes the same line as it is drawn. A 2D revolute turns about +z and takes no other axis.
angle_limitsnumber[]the hinge turns freelyRevolute-only {min, max} angle range in radians from the pose the joint was made in, positive counter-clockwise about the axis as it points from the first body.
limitsnumber[]the body slides freely along the axisPrismatic-only {min, max} travel along the axis, in world units.
distance_limitsnumber[]a distance joint raisesDistance-only, {0, max}: the anchors stay at most max world units apart (at most 1000000), like a rope; nothing holds them apart.
swing_limitsnumber[]the swing is freeSpherical-only {min, max} angle in radians between the two bodies' twist axes, min 0 or more: a cone around the axis.
twist_limitsnumber[]the twist is freeSpherical-only {min, max} twist about the axis, in radians from the pose the joint was made in.
motor_speednumberno velocity motorTarget motor velocity (radians/s revolute, world units/s prismatic).
motor_forcenumberthe motor's torque or force is unlimitedMaximum motor torque (revolute) or force (prismatic), 0 or more.
motor_targetnumberno position motorMotor target position (radians revolute, world units prismatic).
motor_stiffnessnumber1000 with a motor_target, else 0Spring gain toward motor_target, 0 or more.
motor_dampingnumber100 with a motor_speed, 30 with only a motor_target, else 0Damper gain toward motor_speed, 0 or more.

datumhue.physics.raycast

functionUI apps only
datumhue.physics.raycast(ray: Ray, options?: physics.RaycastOptions) -> physics.RayHit?

Cast a ray and return the nearest body hit, or nil. An options value that is not a table is ignored.

Example
-- New bodies are found once the simulation has stepped.
datumhue.time.sleep(0.1)
-- A 2D ray runs in the canvas plane, at z = 0.
local down = datumhue.math.ray(vec3(0, 100, 0), vec3(0, -1, 0))
local hit = datumhue.physics.raycast(down, { max_distance = 500 })
-- The floor's top is at y = -130.
print(hit.body == floor, hit.distance) --> true 230.0
print(hit.point) --> vec3(0.0, -130.0, 0.0)
print(hit.normal) --> vec3(0.0, 1.0, 0.0)

options physics.RaycastOptions options 4 keys

KeyTypeDefaultDescription
max_distancenumber1000000Maximum ray length in world units, 0 or more; math.huge casts without a limit.
layersinteger[]every layerOnly hit bodies in these layer bit indices 0..=31.
excludeDrawPrimitive | SceneNode | Voxelno body is ignoredA body to ignore.
solidbooleantrueCount a ray starting inside a collider as a hit at distance 0.

returns physics.RayHit record 4 keys

KeyTypeDescription
bodyDrawPrimitive | SceneNode | VoxelThe body that was hit.
pointVec3World-space contact point.
normalVec3World-space surface normal at the hit.
distancenumberDistance from the origin to the hit.

datumhue.physics.raycast_all

functionUI apps only
datumhue.physics.raycast_all(ray: Ray, options?: physics.RaycastAllOptions) -> physics.RayHit[]

Cast a ray and return every body along it, nearest first. An options value that is not a table is ignored.

Example
local box = canvas:rect({ pos = vec2(-20, 0), width = 40, height = 40 })
box:add_body({ type = "static" })
-- New bodies are found once the simulation has stepped.
datumhue.time.sleep(0.1)
local down = datumhue.math.ray(vec3(0, 100, 0), vec3(0, -1, 0))
local hits = datumhue.physics.raycast_all(down, { max_hits = 8 })
-- Nearest first.
print(#hits, hits[1].body == box, hits[2].body == floor) --> 2 true true

options physics.RaycastAllOptions options 5 keys, 4 inherited

KeyTypeDefaultDescription
max_hitsinteger64Maximum hits to return, 0 or more.
From physics.RaycastOptions
max_distancenumber1000000Maximum ray length in world units, 0 or more; math.huge casts without a limit.
layersinteger[]every layerOnly hit bodies in these layer bit indices 0..=31.
excludeDrawPrimitive | SceneNode | Voxelno body is ignoredA body to ignore.
solidbooleantrueCount a ray starting inside a collider as a hit at distance 0.

returns physics.RayHit record 4 keys

KeyTypeDescription
bodyDrawPrimitive | SceneNode | VoxelThe body that was hit.
pointVec3World-space contact point.
normalVec3World-space surface normal at the hit.
distancenumberDistance from the origin to the hit.

datumhue.physics.point_query

functionUI apps only
datumhue.physics.point_query(point: Vec2 | Vec3, options?: physics.PointQueryOptions) -> (DrawPrimitive | SceneNode | Voxel)[]

Return every body whose collider overlaps the given world-space point. An options value that is not a table is ignored.

Example
-- New bodies are found once the simulation has stepped.
datumhue.time.sleep(0.1)
local under = datumhue.physics.point_query(vec2(0, -140))
print(#under, under[1] == floor) --> 1 true
local others = datumhue.physics.point_query(vec2(0, -140), {
    exclude = floor,
})
print(#others) --> 0

options physics.PointQueryOptions options 2 keys

KeyTypeDefaultDescription
layersinteger[]every layerOnly include bodies in these layer bit indices 0..=31.
excludeDrawPrimitive | SceneNode | Voxelno body is ignoredA body to ignore.

datumhue.physics.shapecast

functionUI apps only
datumhue.physics.shapecast(ray: Ray, options: physics.ShapecastOptions) -> physics.RayHit?

Sweep a 3D collider shape along the ray and return the nearest body hit, or nil.

Example
-- It sweeps a 3D shape, so it finds bodies in a 3D scene.
local scene = datumhue.scene.new({ width = 320, height = 240 })
local ground = scene:cube({ scale = vec3(10, 1, 10) })
ground:add_body({ type = "static" })
-- New bodies are found once the simulation has stepped.
datumhue.time.sleep(0.1)
local down = datumhue.math.ray(vec3(0, 5, 0), vec3(0, -1, 0))
local box = { shape = "cube", width = 2, height = 1, depth = 2 }
local hit = datumhue.physics.shapecast(down, box)
-- The box's bottom, 0.5 below the ray, meets the top at y = 0.5.
print(hit.body == ground, hit.distance) --> true 4.0

options physics.ShapecastOptions options 9 keys

KeyTypeDefaultDescription
shapephysics.Shape"cube"3D collider shape to cast.
widthnumber1Box width.
heightnumber1Box height / capsule length.
depthnumber1Box depth.
radiusnumber0.5Sphere / capsule / cylinder / cone radius.
rotationQuatdatumhue.math.quat.identity()Shape orientation.
max_distancenumber1000000Maximum cast length in world units, 0 or more; math.huge casts without a limit.
layersinteger[]every layerOnly hit bodies in these layer bit indices 0..=31.
excludeDrawPrimitive | SceneNode | Voxelno body is ignoredA body to ignore.

returns physics.RayHit record 4 keys

KeyTypeDescription
bodyDrawPrimitive | SceneNode | VoxelThe body that was hit.
pointVec3World-space contact point.
normalVec3World-space surface normal at the hit.
distancenumberDistance from the origin to the hit.

Types 17

physics.AutostepConfig

options

options 3 keys

KeyTypeDefaultDescription
max_heightnumber | physics.RelativeLength{relative = 0.25}Max step height the character climbs.
min_widthnumber | physics.RelativeLength{relative = 0.5}Minimum free width that must remain on the step.
include_dynamic_bodiesbooleantrueWhether dynamic bodies can act as steps.

Used by: CharacterController.autostep

Nested in: physics.CharacterControllerOptions

physics.Axis

enum
ValueDescription
"x"
"y"
"z"

Nested in: physics.BodyOptions, physics.LockedAxesOptions

physics.BodyOptions

options

options 18 keys

KeyTypeDefaultDescription
type"dynamic" | "static" | "kinematic""dynamic"Body dynamics.
shapephysics.Shapethe collider fits what the handle drawsCollider shape.
widthnumbertaken from what the handle draws, or 1Box width, in the handle's own units.
heightnumbertaken from what the handle draws, or 1Box, cylinder, or cone height, or a capsule's straight length between its caps, in the handle's own units.
depthnumbertaken from what the handle draws, or 1Box depth in the handle's own units, 3D only.
radiusnumbertaken from what the handle draws, or 0.5Circle, sphere, capsule, cylinder, or cone radius, in the handle's own units.
restitutionnumber0.3Bounciness, 0 or more: 0 absorbs an impact, 1 keeps all its energy.
frictionnumber0.5Surface friction, 0 or more.
massnumberderived from the collider's sizeMass override, more than 0.
sensorbooleanfalseMake the body a non-colliding trigger volume that reports the bodies overlapping it: dynamic and kinematic ones, and static ones unless the sensor is static itself.
pointsVec3[]a convex_hull, polyline or trimesh shape is a boxVertices for convex_hull / polyline / trimesh in the handle's own frame and units (vec3; vec2 accepted, z = 0).
indicesinteger[][]a trimesh shape is a boxTriangles for the trimesh shape: each three 1-based positions in points.
layersinteger[]every layerCollision membership: layer bit indices 0..=31 the body belongs to.
collides_withinteger[]every layerCollision filter: layer bit indices 0..=31 the body collides with; an empty list collides with nothing.
lock_translation"x" | "y" | "z"[]no axis beyond those constraintsAxes to lock translation on, on top of a 2D body's in-plane constraints.
lock_rotation"x" | "y" | "z"[]no axis beyond those constraintsAxes to lock rotation on, on top of a 2D body's in-plane constraints.
ccdbooleanfalseSweep this body against moving (kinematic and dynamic) bodies too when it moves fast; fast bodies always sweep static geometry so they never tunnel through it.
worldPhysicsWorldthe app's default worldSimulate in this world, made by datumhue.physics.world(), instead of the app's default one.

Used by: SceneNode:add_body, DrawPrimitive:add_body

Extended by: draw.BodyOptions

physics.BodyType

enum
ValueDescription
"dynamic"
"static"
"kinematic"

Nested in: physics.BodyOptions

physics.CharacterCollision

record

record 4 keys

KeyTypeDescription
bodyDrawPrimitive | SceneNode | VoxelThe body that was hit.
pointVec3World-space witness point of the hit.
normalVec3World-space surface normal at the hit (use it for wall-jump or facing).
distancenumberDistance along the attempted movement when the hit occurred.

Nested in: physics.CharacterMove

physics.CharacterControllerOptions

options

options 8 keys

KeyTypeDefaultDescription
upVec2 | Vec3datumhue.math.vec3(0, 1, 0)Floor-defining up direction; selects which contacts count as ground. 2D uses the XY components.
offsetnumber | physics.RelativeLength{relative = 0.01}Small gap kept between the character and surroundings for stability; must be > 0.
slidebooleantrueSlide along blocking walls instead of stopping dead.
max_slope_climb_anglenumbermath.pi / 4Steepest slope, in radians, the character will climb; steeper surfaces act as walls.
min_slope_slide_anglenumbermath.pi / 4Slope angle, in radians, at/above which the character auto-slides down.
snap_to_groundnumber | physics.RelativeLength | boolean{relative = 0.2}Distance to snap down onto ground after a move, keeping the character glued over dips; false disables.
autostepphysics.AutostepConfig | booleanfalseStep-climbing config, or false to disable; stepping is expensive. Needs a flat-bottomed body (cube or cylinder); a capsule's rounded bottom slides back off the ledge. The body also needs head room of its own height plus max_height above where it stands, so a low ceiling stops it stepping.
normal_nudge_factornumber0.0001Tiny anti-stick push along contact normals while sliding, 0 to 1.

Used by: SceneNode:character_controller, DrawPrimitive:character_controller

physics.CollisionLayerOptions

options

options 2 keys

KeyTypeDefaultDescription
layersinteger[]every layerMembership layer bit indices 0..=31.
collides_withinteger[]every layerFilter layer bit indices 0..=31; an empty list collides with nothing.

Used by: SceneNode:set_collision_layers, DrawPrimitive:set_collision_layers

physics.Contact

record

record 3 keys

KeyTypeDescription
pointVec3World-space contact point, where the bodies overlap deepest.
normalVec3World-space contact normal, pointing out of this body toward the other.
penetrationnumberPenetration depth in world units (positive when overlapping).

Used by: SceneNode:on_collision_start, DrawPrimitive:on_collision_start

physics.JointType

enum
ValueDescription
"fixed"
"revolute"
"prismatic"
"distance"
"spherical"

Nested in: physics.JointOptions

physics.LockedAxesOptions

options

options 2 keys

KeyTypeDefaultDescription
lock_translation"x" | "y" | "z"[]translation is unlockedAxes to lock translation on; the 2D in-plane locks always remain.
lock_rotation"x" | "y" | "z"[]rotation is unlockedAxes to lock rotation on; the 2D in-plane locks always remain.

Used by: SceneNode:set_locked_axes, DrawPrimitive:set_locked_axes

physics.PointQueryOptions

options

options 2 keys

KeyTypeDefaultDescription
layersinteger[]every layerOnly include bodies in these layer bit indices 0..=31.
excludeDrawPrimitive | SceneNode | Voxelno body is ignoredA body to ignore.

Used by: datumhue.physics.point_query, PhysicsWorld:point_query

physics.RaycastAllOptions

options

options 5 keys, 4 inherited

KeyTypeDefaultDescription
max_hitsinteger64Maximum hits to return, 0 or more.
From physics.RaycastOptions
max_distancenumber1000000Maximum ray length in world units, 0 or more; math.huge casts without a limit.
layersinteger[]every layerOnly hit bodies in these layer bit indices 0..=31.
excludeDrawPrimitive | SceneNode | Voxelno body is ignoredA body to ignore.
solidbooleantrueCount a ray starting inside a collider as a hit at distance 0.

Used by: datumhue.physics.raycast_all, PhysicsWorld:raycast_all

physics.RaycastOptions

options

options 4 keys

KeyTypeDefaultDescription
max_distancenumber1000000Maximum ray length in world units, 0 or more; math.huge casts without a limit.
layersinteger[]every layerOnly hit bodies in these layer bit indices 0..=31.
excludeDrawPrimitive | SceneNode | Voxelno body is ignoredA body to ignore.
solidbooleantrueCount a ray starting inside a collider as a hit at distance 0.

Used by: datumhue.physics.raycast, PhysicsWorld:raycast

Extended by: physics.RaycastAllOptions

physics.RayHit

record

record 4 keys

KeyTypeDescription
bodyDrawPrimitive | SceneNode | VoxelThe body that was hit.
pointVec3World-space contact point.
normalVec3World-space surface normal at the hit.
distancenumberDistance from the origin to the hit.

Used by: datumhue.physics.raycast, datumhue.physics.raycast_all, datumhue.physics.shapecast, PhysicsWorld:raycast, PhysicsWorld:raycast_all, PhysicsWorld:shapecast

physics.RelativeLength

options

options 1 key

KeyTypeDefaultDescription
relative requirednumberno defaultFraction of the character shape's relevant extent. The table's other keys are ignored.

Used by: CharacterController.offset, CharacterController.snap_to_ground

Nested in: physics.AutostepConfig, physics.CharacterControllerOptions

physics.Shape

enum
ValueDescription
"rect"
"circle"
"capsule"
"convex_hull"
"polyline"
"cube"
"sphere"
"cylinder"
"cone"
"trimesh"

Nested in: physics.BodyOptions, physics.ShapecastOptions

physics.ShapecastOptions

options

options 9 keys

KeyTypeDefaultDescription
shapephysics.Shape"cube"3D collider shape to cast.
widthnumber1Box width.
heightnumber1Box height / capsule length.
depthnumber1Box depth.
radiusnumber0.5Sphere / capsule / cylinder / cone radius.
rotationQuatdatumhue.math.quat.identity()Shape orientation.
max_distancenumber1000000Maximum cast length in world units, 0 or more; math.huge casts without a limit.
layersinteger[]every layerOnly hit bodies in these layer bit indices 0..=31.
excludeDrawPrimitive | SceneNode | Voxelno body is ignoredA body to ignore.

Used by: datumhue.physics.shapecast, PhysicsWorld:shapecast

datumhue.com

(c) 2026 Shuusei AB. Quote excerpts of this manual, with attribution to DatumHue, in your own documentation, tutorials, course materials, reviews, and other writing or video. Republishing it in whole or in substantial part needs written permission: support@datumhue.com.

Victor Mono is licensed under the SIL Open Font License 1.1
Copyright (c) 2024, Rune Bjørnerås (https://github.com/rubjo)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Try

    ↑↓ selectEnter openTab filter

    Settings

    Theme
    Text
    Print

    Keyboard shortcuts

    / or Ctrl KSearch
    ↑ ↓ EnterPick a result
    TabNext result filter
    j kNext or previous entry
    [ ]Previous or next page
    yCopy a link to the current entry
    ?These shortcuts
    EscClose

    On this page