Venus Lua API › Physics

Physics

Deterministic scene queries — raycasts, shape casts, overlaps.

authored lua/venus_lua/src/lua_physics.rs

Physics

A module singleton (global functions only, no handles) for querying the deterministic physics
world: raycasts, shape sweeps, overlap tests, and the layer-collision matrix. Fixed-point
results are converted to f32 at this script boundary.

All queries run against rigid bodies in the deterministic FixedXpbdWorld. Two consequences
that catch scripters:

Vector arguments (origin, direction, center, halfExtents, …) take a Vector3.
layerMask is an integer bitmask selecting which layers the query tests.

Result tables

A single hit is a table { point: Vector3, normal: Vector3, distance: number, gameObject: GameObject }. Multi-hit queries return a 1-based array of these (or of GameObjects, as noted).

A terrain hit’s gameObject is a dead handle, not nil. A voxel-terrain hit (from one of the
casts above) has no owning object, but hit.gameObject is still a present GameObject userdata
wrapping the null handle — it is truthy, so if hit.gameObject then is always true. Tell terrain
from a real body with GameObject.Exists(hit.gameObject) (false = terrain).

Functions

Physics.Raycast( Vector3 origin, Vector3 direction, integer layerMask ) : table or nil
Physics.Raycast( Vector3 origin, Vector3 direction, integer layerMask, number maxDistance ) : table or nil
Casts a ray and returns the closest hit as a {point, normal, distance, gameObject} table, or
nil if nothing was hit. maxDistance defaults to 10000 world units when omitted (not
unbounded); RaycastAll shares the same default.

Physics.RaycastAll( Vector3 origin, Vector3 direction, integer layerMask ) : table
Physics.RaycastAll( Vector3 origin, Vector3 direction, integer layerMask, number maxDistance ) : table
Returns an array of every hit along the ray (empty table if none).

Physics.LinecastAll( Vector3 start, Vector3 end, integer layerMask ) : table
Returns an array of all hits on the segment from start to end.

Physics.SphereTestAll( Vector3 center, number radius, integer layerMask ) : table
Returns an array of the GameObjects whose colliders overlap the sphere.

Physics.SphereCast( Vector3 center, number radius, Vector3 direction, number maxDistance, integer layerMask ) : table or nil
Sweeps a sphere and returns the closest hit, or nil.

Physics.CapsuleCast( Vector3 center, Quaternion rotation, number radius, number halfHeight, Vector3 direction, number maxDistance, integer layerMask ) : table or nil
Sweeps an oriented capsule and returns the closest hit, or nil.

Physics.BoxCast( Vector3 center, Quaternion rotation, Vector3 halfExtents, Vector3 direction, number maxDistance, integer layerMask ) : table or nil
Sweeps an oriented box and returns the closest hit, or nil.

Physics.OverlapBox( Vector3 center, Quaternion rotation, Vector3 halfExtents, integer layerMask ) : table
Returns an array of the GameObjects overlapping the oriented box.

Physics.CheckSphere( Vector3 center, number radius, integer layerMask ) : boolean
true if any collider overlaps the sphere.

Physics.CheckBox( Vector3 center, Quaternion rotation, Vector3 halfExtents, integer layerMask ) : boolean
true if any collider overlaps the oriented box.

Physics.IgnoreLayerCollision( integer a, integer b, boolean ignore ) : void
Sets whether layers a and b ignore each other in the solver (Unity semantics: ignore == true clears the collide bit). Intended for startup/config — the layer table is treated as
immutable during simulation. Out-of-range indices (≥ 32) are a no-op.

Physics.GetIgnoreLayerCollision( integer a, integer b ) : boolean
true if layers a and b are set not to collide. On a wired world, out-of-range
indices (≥ 32) return true
(they cannot collide); only an unwired world returns false.

Constants

Physics exposes no constants. Force modes are quoted strings, not a constants table — see
FixedRigidBody → AddForce.

rb:AddForce(Vector3.new(0, 12, 0), "Impulse")   -- correct

Removed (2026-07). A global ForceMode table of integers (Force=0 … Acceleration=3)
used to be registered here. It was added for an earlier integer-taking rigid-body binding that
the fixed-dynamics rewrite retired; the replacement takes mode names, so every use of the
table silently applied Force (luaL_checkstring coerces 1"1", which matched nothing).
The table is gone, and an unrecognized mode name now raises a Lua error instead of defaulting.
ForceMode is nil — reaching for it is a loud error at the call site, which is the point.

Example

-- Ground check: cast a short ray straight down from the player.
function Script.Update(self)
    local x, y, z = self.object:WorldPosition()
    local hit = Physics.Raycast(Vector3.new(x, y, z), Vector3.new(0, -1, 0), self.groundMask, 1.2)
    self.grounded = hit ~= nil
    if hit then
        -- hit.point, hit.normal, hit.distance, hit.gameObject
        self.groundNormal = hit.normal
    end
end